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

View File

@ -1,37 +1,19 @@
import React from "react"; import React from "react";
import { IAccount, IMailbox } from "./client/types";
import Client from "./client/Client";
import EmailList from "./EmailList";
type MailboxProps = { type MailboxProps = {
account: IAccount | null; accountId: string;
client: Client; id: string;
mailbox: IMailbox | null; 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 ( return (
<EmailList <a href={href} className="p-2" key={id}>
account={this.props.account} {name}
client={this.props.client} </a>
mailbox={this.props.mailbox}
/>
); );
} };
}
export default Mailbox; export default Mailbox;

View File

@ -3,7 +3,7 @@ import Stack from "react-bootstrap/Stack";
import Client from "./client/Client"; import Client from "./client/Client";
import { IAccount } from "./client/types"; import { IAccount } from "./client/types";
import MailboxSummary from "./MailboxSummary"; import Mailbox from "./Mailbox";
type MailboxListProps = { type MailboxListProps = {
account: IAccount | null; account: IAccount | null;
@ -25,8 +25,8 @@ class MailboxList extends React.Component<MailboxListProps, MailboxListState> {
<Stack /> <Stack />
) : ( ) : (
<Stack> <Stack>
{Object.values(this.props.account.mailboxes).map((m) => ( {this.props.account.mailboxes.map((m) => (
<MailboxSummary <Mailbox
accountId={this.props.account!.id} accountId={this.props.account!.id}
id={m.id} id={m.id}
key={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, IEmailStub,
IMailbox, IMailbox,
ISession, ISession,
MailboxIdMap,
MailboxRole, MailboxRole,
PushMessage, PushMessage,
} from "./types"; } from "./types";
@ -26,7 +25,6 @@ export interface IAuth {
} }
export interface ClientState { export interface ClientState {
emailState: string | null;
inFlight: { inFlight: {
emailContents: Set<string>; emailContents: Set<string>;
emailStubs: Set<string>; emailStubs: Set<string>;
@ -35,9 +33,7 @@ export interface ClientState {
mailboxes: { mailboxes: {
trash: IMailbox; trash: IMailbox;
} | null; } | null;
mailboxState: string | null;
session: ISession | null; session: ISession | null;
threadState: string | null;
} }
class Account implements IAccount { class Account implements IAccount {
@ -46,7 +42,7 @@ class Account implements IAccount {
accountCapabilities: { [key: string]: jmaptypes.IMailCapabilities }; accountCapabilities: { [key: string]: jmaptypes.IMailCapabilities };
isPersonal: boolean; isPersonal: boolean;
isReadOnly: boolean; isReadOnly: boolean;
mailboxes: MailboxIdMap | null; mailboxes: Array<IMailbox> | null;
name: string; name: string;
constructor(id: string, props: jmaptypes.IAccount) { constructor(id: string, props: jmaptypes.IAccount) {
@ -59,9 +55,10 @@ class Account implements IAccount {
} }
mailboxByRole(role: MailboxRole): IMailbox | null { mailboxByRole(role: MailboxRole): IMailbox | null {
if (this.mailboxes === null) return null; if (this.mailboxes === null) return null;
for (const mailbox of Object.values(this.mailboxes)) { for (let i = 0; i < this.mailboxes.length; i++) {
if (mailbox.role === role) { const m = this.mailboxes[i];
return mailbox; if (m.role === role) {
return m;
} }
} }
return null; return null;
@ -72,16 +69,13 @@ export default class Client {
callbacks: Array<Callback> = []; callbacks: Array<Callback> = [];
jclient: jmapclient.Client | null = null; jclient: jmapclient.Client | null = null;
state: ClientState = { state: ClientState = {
emailState: null,
inFlight: { inFlight: {
emailContents: new Set(), emailContents: new Set(),
emailStubs: new Set(), emailStubs: new Set(),
mailboxes: new Set(), mailboxes: new Set(),
}, },
mailboxes: null, mailboxes: null,
mailboxState: null,
session: null, session: null,
threadState: null,
}; };
// Get the currently active account // Get the currently active account
@ -180,14 +174,6 @@ export default class Client {
this.doLogin(auth); 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 { email(emailId: string): IEmail | null {
if (this.state.session == null) return null; if (this.state.session == null) return null;
const result = this.state.session.emails[emailId]; const result = this.state.session.emails[emailId];
@ -261,10 +247,8 @@ export default class Client {
receivedAt: e.receivedAt, receivedAt: e.receivedAt,
subject: e.subject, subject: e.subject,
}; };
this._triggerChange(msg + e.id);
}); });
this._triggerChange(msg);
//this.state.session.state = response.sessionState;
this.state.emailState = response.state;
}) })
.catch((x) => { .catch((x) => {
console.error("Failed to get email stub", emailId, 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; if (this.state.session == null) return null;
const account = this.state.session.accounts[accountId]; const account = this.state.session.accounts[accountId];
if (account.mailboxes == null) return null; if (account.mailboxes == null) return null;
const mailbox = account.mailboxes[mailboxId]; for (let i = 0; i < account.mailboxes.length; i++) {
return mailbox; if (account.mailboxes[i].id === mailboxId) {
return account.mailboxes[i];
} }
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]; return null;
if (account.mailboxes === null) {
account.mailboxes = {};
}
account.mailboxes[m.id] = {
...m,
emailIds: null,
};
this.state.mailboxState = response.state;
this.emailList(accountId, id, []);
});
} }
mailboxList(accountId: string, ids: Array<string>) { mailboxList(accountId: string, ids: Array<string>) {
if (this.jclient == null) return; if (this.jclient == null) return;
@ -358,18 +315,14 @@ export default class Client {
.then((response) => { .then((response) => {
if (this.state.session == null) return; if (this.state.session == null) return;
const account = this.state.session.accounts[response.accountId]; const account = this.state.session.accounts[response.accountId];
const mailboxes: MailboxIdMap = {}; const mailboxes: Array<IMailbox> = [];
response.list.forEach((m) => { response.list.forEach((m) => {
// If we already have a mailbox with emails then don't throw that data away mailboxes.push({
const existing =
account.mailboxes === null ? null : account.mailboxes[m.id];
mailboxes[m.id] = {
...m, ...m,
emailIds: existing === null ? null : existing.emailIds, emailIds: null,
}; });
}); });
account.mailboxes = mailboxes; account.mailboxes = mailboxes;
this.state.mailboxState = response.state;
this._triggerChange("Mailboxes " + accountId); this._triggerChange("Mailboxes " + accountId);
}); });
} }
@ -384,70 +337,13 @@ export default class Client {
c(); 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() { _onSession() {
console.log("Session received");
// For the type checker // For the type checker
if (!this.jclient) return; if (!this.jclient) return;
const session = this.jclient.getSession(); const session = this.jclient.getSession();
console.log("Session received: ", session);
// Subscribe to server-pushed events // Subscribe to server-pushed events
if (session.eventSourceUrl) { if (session.eventSourceUrl) {
this._subscribeToEventSource(session.eventSourceUrl); this._subscribeToEventSource(session.eventSourceUrl);
@ -462,7 +358,6 @@ export default class Client {
), ),
emails: {}, emails: {},
emailStubs: {}, emailStubs: {},
mailboxes: {},
}; };
if (!this.state.session) return; if (!this.state.session) return;
this._triggerChange("Session"); this._triggerChange("Session");
@ -478,28 +373,7 @@ export default class Client {
this.jclient.subscribeToEvents( this.jclient.subscribeToEvents(
eventSourceUrl, eventSourceUrl,
(type: string, message: PushMessage) => { (type: string, message: PushMessage) => {
if (type === "ping") {
return;
}
if (type === "state") {
console.log("Got an event!", type, message); 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);
}
}, },
); );
} }

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

View File

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