diff --git a/lib/plugin-sandbox/assistant/c.batch.ts b/lib/plugin-sandbox/assistant/c.batch.ts index 5ed3b0c8c..b8bc70d5c 100644 --- a/lib/plugin-sandbox/assistant/c.batch.ts +++ b/lib/plugin-sandbox/assistant/c.batch.ts @@ -13,7 +13,7 @@ import { recordUndo, runUndo } from '../../undo-history'; import { KEYWORD_PREFIX, KEYWORD_PREFIX_LEGACY } from '../../thread-utils'; import { readId, readObject, readString, sessionClient, withinRate } from '../host-api-input'; import { MAX_PLUGIN_MOVES_PER_MINUTE } from '../host-api-mail'; -import { issueUndoToken, redeemUndoToken } from './c.undo'; +import { issueUndoToken, redeemUndoToken, undoAccount } from './c.undo'; import type { MailBatchAction, MailBatchResult } from './c.types'; /** Most messages one email.batch call changes. */ @@ -134,7 +134,7 @@ export async function doEmailBatch(pluginId: string, input: unknown): Promise { void run().catch(() => { /* the plugin's undo reports it */ }); }); - const token = issueUndoToken(pluginId, client.getAccountId(), 'batch', { entryId: entry.id, run } satisfies BatchUndo); + const token = issueUndoToken(pluginId, undoAccount(client), 'batch', { entryId: entry.id, run } satisfies BatchUndo); return { done, failed: missing, undoToken: token }; } @@ -142,7 +142,7 @@ export async function doEmailBatch(pluginId: string, input: unknown): Promise { const method = 'email.undoBatch'; const client: IJMAPClient = sessionClient(method); - const { entryId, run } = redeemUndoToken(method, pluginId, client.getAccountId(), 'batch', token); + const { entryId, run } = redeemUndoToken(method, pluginId, undoAccount(client), 'batch', token); // Off the history first, so Ctrl+Z can't run it again; an entry already // taken by Ctrl+Z has run (or is running) the same undo. runUndo(entryId); diff --git a/lib/plugin-sandbox/assistant/c.calendar.ts b/lib/plugin-sandbox/assistant/c.calendar.ts index 0e87f5aff..4e51d7ffa 100644 --- a/lib/plugin-sandbox/assistant/c.calendar.ts +++ b/lib/plugin-sandbox/assistant/c.calendar.ts @@ -12,7 +12,7 @@ import { answerInvitation, attendeeOf, readOwnInvitation, type RsvpStatus } from import { readId, sessionClient } from '../host-api-input'; import { readNamedEmail } from '../host-api-mail'; import { doCalendarDeleteEvent } from '../host-api-calendar'; -import { issueUndoToken, redeemUndoToken } from './c.undo'; +import { issueUndoToken, redeemUndoToken, undoAccount } from './c.undo'; import type { EventDeleteResult, InvitationResponseView } from './c.types'; const STATUSES: readonly RsvpStatus[] = ['accepted', 'tentative', 'declined']; @@ -159,14 +159,14 @@ export async function doCalendarDeleteWithUndo(pluginId: string, idArg: unknown) if (!deleted) return { undoToken: null }; const copy: Record = { ...event }; for (const key of NOT_RECREATED) delete copy[key]; - return { undoToken: issueUndoToken(pluginId, client.getAccountId(), 'event', copy) }; + return { undoToken: issueUndoToken(pluginId, undoAccount(client), 'event', copy) }; } /** Puts back an event calendar.deleteEvent deleted, from the host's copy. Sends no invitations. */ export async function doCalendarUndoDelete(pluginId: string, tokenArg: unknown): Promise<{ id: string } | null> { const method = 'calendar.undoDelete'; const client = sessionClient(method); - const copy = redeemUndoToken>(method, pluginId, client.getAccountId(), 'event', tokenArg); + const copy = redeemUndoToken>(method, pluginId, undoAccount(client), 'event', tokenArg); const created = await useCalendarStore.getState().createEvent(client, copy, false); return created ? { id: created.id } : null; } diff --git a/lib/plugin-sandbox/assistant/c.filters.ts b/lib/plugin-sandbox/assistant/c.filters.ts index a3f938c68..2e7d17aa3 100644 --- a/lib/plugin-sandbox/assistant/c.filters.ts +++ b/lib/plugin-sandbox/assistant/c.filters.ts @@ -20,7 +20,7 @@ import { isValidSieveHeaderName } from '../../sieve/generator'; import { KEYWORD_PREFIX, KEYWORD_PREFIX_LEGACY } from '../../thread-utils'; import { generateUUID } from '../../utils'; import { isPlainRecord, readId, readObject, readString, sessionClient } from '../host-api-input'; -import { issueUndoToken, redeemUndoToken } from './c.undo'; +import { issueUndoToken, redeemUndoToken, undoAccount } from './c.undo'; import type { FilterCreateResult, FilterRuleActionType, FilterRuleField, FilterRuleInfo, FilterRuleOp, FiltersListView, } from './c.types'; @@ -214,14 +214,14 @@ export async function doFiltersCreate(pluginId: string, input: unknown): Promise } if (!change) throw new Error(`${method}: nothing was written`); refreshFilterStore(client, accountId); - return { id: rule.id, undoToken: issueUndoToken(pluginId, client.getAccountId(), 'filters', change) }; + return { id: rule.id, undoToken: issueUndoToken(pluginId, undoAccount(client), 'filters', change) }; } /** Take back a rule filters.create added: the script gets its earlier bytes back. */ export async function doFiltersUndo(pluginId: string, token: unknown): Promise { const method = 'filters.undo'; const client = sieveClient(method); - const change = redeemUndoToken(method, pluginId, client.getAccountId(), 'filters', token); + const change = redeemUndoToken(method, pluginId, undoAccount(client), 'filters', token); try { await restoreAccountFilters(client, change); } catch (err) { diff --git a/lib/plugin-sandbox/assistant/c.settings.ts b/lib/plugin-sandbox/assistant/c.settings.ts index 32b65c8a9..252cba2fd 100644 --- a/lib/plugin-sandbox/assistant/c.settings.ts +++ b/lib/plugin-sandbox/assistant/c.settings.ts @@ -26,7 +26,7 @@ import { } from '../../settings-schema'; import { isPlainRecord, sessionClient } from '../host-api-input'; import { messagesFor } from '../host-api-app'; -import { issueUndoToken, redeemUndoToken } from './c.undo'; +import { issueUndoToken, redeemUndoToken, undoAccount } from './c.undo'; import type { SettingDescription, SettingRefusal, SettingValue, SettingsChangeResult, SettingsUndoResult, } from './c.types'; @@ -239,7 +239,7 @@ export async function doSetSettings(pluginId: string, arg: unknown): Promise 0 - ? issueUndoToken(pluginId, client.getAccountId(), 'settings', previous) + ? issueUndoToken(pluginId, undoAccount(client), 'settings', previous) : null; return { applied, refused, undoToken }; } @@ -252,7 +252,7 @@ export async function doSetSettings(pluginId: string, arg: unknown): Promise { const method = 'app.undoSettings'; const client = sessionClient(method); - const previous = redeemUndoToken>(method, pluginId, client.getAccountId(), 'settings', token); + const previous = redeemUndoToken>(method, pluginId, undoAccount(client), 'settings', token); const restored: string[] = []; const refused: SettingsUndoResult['refused'] = {}; const policy = usePolicyStore.getState(); diff --git a/lib/plugin-sandbox/assistant/c.undo.ts b/lib/plugin-sandbox/assistant/c.undo.ts index cf1d84702..6a59d0d6e 100644 --- a/lib/plugin-sandbox/assistant/c.undo.ts +++ b/lib/plugin-sandbox/assistant/c.undo.ts @@ -5,6 +5,8 @@ // host a copy of something to re-create. import { generateUUID } from '../../utils'; +import type { IJMAPClient } from '../../jmap/client-interface'; +import { useAuthStore } from '@/stores/auth-store'; /** How long an undo token can be used. */ export const UNDO_TOKEN_TTL_MS = 30 * 60_000; @@ -51,6 +53,11 @@ export function redeemUndoToken(method: string, pluginId: string, accountId: return entry.data as T; } +/** The account an undo token is bound to: the client's JMAP account, else the signed-in login. */ +export function undoAccount(client: IJMAPClient): string { + return client.getAccountId?.() || useAuthStore.getState().activeAccountId || ''; +} + /** For tests. */ export function clearUndoTokens(): void { tokens.clear();