Compare commits

..

1 Commits

Author SHA1 Message Date
Eli Ribble 1d9a184140 Subscribe to event type and message.
This mirrors a change in the client where we extract the type of the
event.
2024-09-04 11:41:33 -07:00
7 changed files with 40 additions and 205 deletions

View File

@ -4,7 +4,7 @@ import Stack from "react-bootstrap/Stack";
import { IAccount, IEmail, IMailbox } from "./client/types";
import Client from "./client/Client";
import EmailContent from "./EmailContent";
import Mailbox from "./Mailbox";
import EmailList from "./EmailList";
type EmailAreaProps = {
account: IAccount | null;
@ -17,7 +17,7 @@ type EmailAreaProps = {
const EmailArea: React.FC<EmailAreaProps> = (props) => {
if (props.emailId === "") {
return (
<Mailbox
<EmailList
account={props.account}
client={props.client}
mailbox={props.mailbox}
@ -26,7 +26,7 @@ const EmailArea: React.FC<EmailAreaProps> = (props) => {
} else {
return (
<Stack className="text-start">
<Mailbox
<EmailList
account={props.account}
client={props.client}
mailbox={props.mailbox}

View File

@ -1,37 +1,19 @@
import React from "react";
import { IAccount, IMailbox } from "./client/types";
import Client from "./client/Client";
import EmailList from "./EmailList";
type MailboxProps = {
account: IAccount | null;
client: Client;
mailbox: IMailbox | null;
accountId: string;
id: string;
name: string;
};
type MailboxState = {};
const Mailbox: React.FC<MailboxProps> = ({ accountId, id, name }) => {
const href = "#" + accountId + "/" + id;
class Mailbox extends React.Component<MailboxProps, MailboxState> {
componentDidUpdate() {
if (this.props.account == null) return;
if (this.props.client == null) return;
if (this.props.mailbox == null) return;
this.props.client.ensureMailbox(
this.props.account.id,
this.props.mailbox.id,
);
}
render() {
return (
<EmailList
account={this.props.account}
client={this.props.client}
mailbox={this.props.mailbox}
/>
);
}
}
return (
<a href={href} className="p-2" key={id}>
{name}
</a>
);
};
export default Mailbox;

View File

@ -3,7 +3,7 @@ import Stack from "react-bootstrap/Stack";
import Client from "./client/Client";
import { IAccount } from "./client/types";
import MailboxSummary from "./MailboxSummary";
import Mailbox from "./Mailbox";
type MailboxListProps = {
account: IAccount | null;
@ -25,8 +25,8 @@ class MailboxList extends React.Component<MailboxListProps, MailboxListState> {
<Stack />
) : (
<Stack>
{Object.values(this.props.account.mailboxes).map((m) => (
<MailboxSummary
{this.props.account.mailboxes.map((m) => (
<Mailbox
accountId={this.props.account!.id}
id={m.id}
key={m.id}

View File

@ -1,19 +0,0 @@
import React from "react";
type MailboxProps = {
accountId: string;
id: string;
name: string;
};
const Mailbox: React.FC<MailboxProps> = ({ accountId, id, name }) => {
const href = "#" + accountId + "/" + id;
return (
<a href={href} className="p-2" key={id}>
{name}
</a>
);
};
export default Mailbox;

View File

@ -13,7 +13,6 @@ import {
IEmailStub,
IMailbox,
ISession,
MailboxIdMap,
MailboxRole,
PushMessage,
} from "./types";
@ -26,7 +25,6 @@ export interface IAuth {
}
export interface ClientState {
emailState: string | null;
inFlight: {
emailContents: Set<string>;
emailStubs: Set<string>;
@ -35,9 +33,7 @@ export interface ClientState {
mailboxes: {
trash: IMailbox;
} | null;
mailboxState: string | null;
session: ISession | null;
threadState: string | null;
}
class Account implements IAccount {
@ -46,7 +42,7 @@ class Account implements IAccount {
accountCapabilities: { [key: string]: jmaptypes.IMailCapabilities };
isPersonal: boolean;
isReadOnly: boolean;
mailboxes: MailboxIdMap | null;
mailboxes: Array<IMailbox> | null;
name: string;
constructor(id: string, props: jmaptypes.IAccount) {
@ -59,9 +55,10 @@ class Account implements IAccount {
}
mailboxByRole(role: MailboxRole): IMailbox | null {
if (this.mailboxes === null) return null;
for (const mailbox of Object.values(this.mailboxes)) {
if (mailbox.role === role) {
return mailbox;
for (let i = 0; i < this.mailboxes.length; i++) {
const m = this.mailboxes[i];
if (m.role === role) {
return m;
}
}
return null;
@ -72,16 +69,13 @@ export default class Client {
callbacks: Array<Callback> = [];
jclient: jmapclient.Client | null = null;
state: ClientState = {
emailState: null,
inFlight: {
emailContents: new Set(),
emailStubs: new Set(),
mailboxes: new Set(),
},
mailboxes: null,
mailboxState: null,
session: null,
threadState: null,
};
// Get the currently active account
@ -180,14 +174,6 @@ export default class Client {
this.doLogin(auth);
}
// Ensure we have the full email content
ensureMailbox(accountId: string, mailboxId: string) {
if (this.state.session == null) return;
const existing = this.state.session.mailboxes[mailboxId];
if (existing != null) return;
this.mailboxList(accountId, [mailboxId]);
}
email(emailId: string): IEmail | null {
if (this.state.session == null) return null;
const result = this.state.session.emails[emailId];
@ -261,10 +247,8 @@ export default class Client {
receivedAt: e.receivedAt,
subject: e.subject,
};
this._triggerChange(msg + e.id);
});
this._triggerChange(msg);
//this.state.session.state = response.sessionState;
this.state.emailState = response.state;
})
.catch((x) => {
console.error("Failed to get email stub", emailId, x);
@ -311,39 +295,12 @@ export default class Client {
if (this.state.session == null) return null;
const account = this.state.session.accounts[accountId];
if (account.mailboxes == null) return null;
const mailbox = account.mailboxes[mailboxId];
return mailbox;
}
mailboxGet(accountId: string, id: string) {
if (this.jclient == null) return;
if (this.state.session == null) return;
// TODO: do this in a single request with 2 queries when the client can handle it.
this.jclient
.mailbox_get({
accountId: accountId,
ids: [id],
})
.then((response) => {
if (this.state.session == null) return;
const account = this.state.session.accounts[response.accountId];
if (response.list.length !== 1) {
console.error(
"Should only get 1 mailbox in mailboxGet. Got ",
response.list,
);
return;
}
const m = response.list[0];
if (account.mailboxes === null) {
account.mailboxes = {};
}
account.mailboxes[m.id] = {
...m,
emailIds: null,
};
this.state.mailboxState = response.state;
this.emailList(accountId, id, []);
});
for (let i = 0; i < account.mailboxes.length; i++) {
if (account.mailboxes[i].id === mailboxId) {
return account.mailboxes[i];
}
}
return null;
}
mailboxList(accountId: string, ids: Array<string>) {
if (this.jclient == null) return;
@ -358,18 +315,14 @@ export default class Client {
.then((response) => {
if (this.state.session == null) return;
const account = this.state.session.accounts[response.accountId];
const mailboxes: MailboxIdMap = {};
const mailboxes: Array<IMailbox> = [];
response.list.forEach((m) => {
// If we already have a mailbox with emails then don't throw that data away
const existing =
account.mailboxes === null ? null : account.mailboxes[m.id];
mailboxes[m.id] = {
mailboxes.push({
...m,
emailIds: existing === null ? null : existing.emailIds,
};
emailIds: null,
});
});
account.mailboxes = mailboxes;
this.state.mailboxState = response.state;
this._triggerChange("Mailboxes " + accountId);
});
}
@ -384,70 +337,13 @@ export default class Client {
c();
});
}
_onChangedEmail(accountId: string, state: string) {
console.log("Email changed", state);
if (this.jclient === null) return;
if (this.state.session === null) return;
const account = this.account(accountId);
if (account === null) return;
if (this.state.emailState === null) return;
const args = {
accountId: accountId,
sinceState: this.state.emailState,
};
this.jclient.email_changes(args).then((r) => {
console.log("Handle email changes ", r);
});
}
_onChangedMailbox(accountId: string, state: string) {
console.log("Mailbox changed", state);
if (this.jclient === null) return;
if (this.state.session === null) return;
const account = this.account(accountId);
if (account === null) return;
if (this.state.mailboxState === null) return;
const args = {
accountId: accountId,
sinceState: this.state.mailboxState,
};
this.jclient.mailbox_changes(args).then((response) => {
console.log("Handle mailbox changes ", response);
for (let i = 0; i < response.created.length; i++) {
const mailboxId = response.created[i];
this.mailboxGet(accountId, mailboxId);
}
for (let i = 0; i < response.updated.length; i++) {
const mailboxId = response.updated[i];
this.mailboxGet(accountId, mailboxId);
}
if (this.state.session === null) return;
for (let i = 0; i < response.destroyed.length; i++) {
const mailboxId = response.destroyed[i];
delete this.state.session.mailboxes[mailboxId];
}
});
}
_onChangedThread(accountId: string, state: string) {
console.log("Thread changed", state);
if (this.jclient === null) return;
if (this.state.session === null) return;
const account = this.account(accountId);
if (account === null) return;
if (this.state.threadState === null) return;
const args = {
accountId: accountId,
sinceState: this.state.threadState,
};
this.jclient.thread_changes(args).then((r) => {
console.log("Handle thread changes ", r);
});
}
_onSession() {
console.log("Session received");
// For the type checker
if (!this.jclient) return;
const session = this.jclient.getSession();
console.log("Session received: ", session);
// Subscribe to server-pushed events
if (session.eventSourceUrl) {
this._subscribeToEventSource(session.eventSourceUrl);
@ -462,7 +358,6 @@ export default class Client {
),
emails: {},
emailStubs: {},
mailboxes: {},
};
if (!this.state.session) return;
this._triggerChange("Session");
@ -478,28 +373,7 @@ export default class Client {
this.jclient.subscribeToEvents(
eventSourceUrl,
(type: string, message: PushMessage) => {
if (type === "ping") {
return;
}
if (type === "state") {
console.log("Got an event!", type, message);
const stateChange = message as jmaptypes.IStateChange;
for (const [accountId, changes] of Object.entries(
stateChange.changed,
)) {
for (const [changeType, state] of Object.entries(changes)) {
if (changeType === "Email") {
this._onChangedEmail(accountId, state);
} else if (changeType === "Mailbox") {
this._onChangedMailbox(accountId, state);
} else if (changeType === "Thread") {
this._onChangedThread(accountId, state);
}
}
}
} else {
console.log("Not sure what to do with event", type);
}
console.log("Got an event!", type, message);
},
);
}

@ -1 +1 @@
Subproject commit ee2af71a8e764c1a8153a7dcd71f4085f970243f
Subproject commit 5cf6129a517224b90f79cb96f212d57c5bceb51f

View File

@ -1,10 +1,10 @@
import * as client from "./jmap-client-ts/src/types";
export type MailboxIdMapPresence = { [mailboxId: string]: boolean };
export type MailboxIdMap = { [mailboxId: string]: boolean };
export interface IEmailStub {
from: Array<client.IEmailAddress>;
id: string;
mailboxIds: MailboxIdMapPresence;
mailboxIds: MailboxIdMap;
receivedAt: string;
subject: string;
}
@ -12,7 +12,6 @@ export interface IEmailStub {
export interface IMailbox extends client.IMailboxProperties {
emailIds: Array<string> | null;
}
export type MailboxIdMap = { [mailboxId: string]: IMailbox };
export interface IEmail extends client.IEmailProperties {
//sentAt: IutcDate|null,
@ -20,7 +19,7 @@ export interface IEmail extends client.IEmailProperties {
export interface IAccount extends client.IAccount {
id: string;
mailboxes: MailboxIdMap | null;
mailboxes: Array<IMailbox> | null;
mailboxByRole(role: MailboxRole): IMailbox | null;
}
@ -43,5 +42,4 @@ export interface ISession extends client.ISession {
accounts: AccountIdMap;
emails: EmailIdMap;
emailStubs: EmailStubIdMap;
mailboxes: MailboxIdMap;
}