From 66ff523553272d1f0409df51c9ae3e211fa8272d Mon Sep 17 00:00:00 2001 From: Paulhenry Saux Date: Wed, 29 Jul 2026 13:14:09 +0200 Subject: [PATCH] feat(plugins): add 2 new methods : user.getAccounts and user.getIdentities --- lib/plugin-sandbox/consent.ts | 1 + lib/plugin-sandbox/host-api.ts | 45 +++++++++++++++++++++++++++++++++- lib/plugin-sandbox/runtime.tsx | 4 +++ lib/plugin-types.ts | 1 + 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/lib/plugin-sandbox/consent.ts b/lib/plugin-sandbox/consent.ts index d89417327..3b5d22bc5 100644 --- a/lib/plugin-sandbox/consent.ts +++ b/lib/plugin-sandbox/consent.ts @@ -87,6 +87,7 @@ const PERMISSION_LABELS: Record = { 'http:fetch': { title: 'Talk to external services', body: 'Make uncredentialled requests to the third-party origins listed in the manifest.' }, 'admin:config': { title: 'Read/write admin config', body: 'Access this plugin\'s admin-supplied configuration values.' }, 'ui:download-file': { title: 'Download files', body: 'Download custom files generated by the plugin.' }, + 'account:read': { title: 'Read your accounts', body: 'Access the list of accounts you have configured in the app.' }, }; export function describePermission(perm: string): { title: string; body: string } { diff --git a/lib/plugin-sandbox/host-api.ts b/lib/plugin-sandbox/host-api.ts index 56e268cc0..3757228bc 100644 --- a/lib/plugin-sandbox/host-api.ts +++ b/lib/plugin-sandbox/host-api.ts @@ -6,6 +6,8 @@ import type { InstalledPlugin, Permission } from '../plugin-types'; import { IMPLICIT_PERMISSIONS } from '../plugin-types'; import { toast as appToast } from '@/stores/toast-store'; import { useAuthStore } from '@/stores/auth-store'; +import { useAccountStore } from '@/stores/account-store'; +import { useIdentityStore } from '@/stores/identity-store'; import { useEmailStore } from '@/stores/email-store'; import { useFilterStore } from '@/stores/filter-store'; import { useMessageListTabsStore } from '@/stores/message-list-tabs-store'; @@ -14,7 +16,7 @@ import { apiFetch } from '../browser-navigation'; import { awaitDialog, awaitPrompt, type PromptField } from './host-dialog'; import { fileStorage } from '../plugin-storage'; import { generateUUID } from '../utils'; -import { ContactCard } from '../jmap/types'; +import { ContactCard, Identity } from '../jmap/types'; /** * Methods only callable from the privileged (same-origin) tier. These expose @@ -62,6 +64,9 @@ const PERM_PER_METHOD: Record = { 'contact.update': 'contacts:write', 'contact.create': 'contacts:write', 'contact.search': 'contacts:read', + // user + 'user.getAccounts': 'account:read', + 'user.getIdentities': 'identity:read', // admin 'admin.getConfig': 'admin:config', 'admin.getAllConfig': 'admin:config', @@ -157,6 +162,41 @@ function storageKeys(pluginId: string): string[] { return out; } +// ─── user ───────────────────────────────────────────────────── +interface AccountResponse { + id: string; + label: string; + serverUrl: string; + username: string; + displayName: string; + email: string; + avatarColor: string; + isConnected: boolean; + isDefault: boolean; +} +function doUserGetAccounts(): AccountResponse[] { + const state = useAccountStore.getState(); + + // ws remove sensitive fields from the account entries before returning to the plugin + const accounts = state.accounts.map((account) => ({ + id: account.id, + label: account.label, + serverUrl: account.serverUrl, + username: account.username, + displayName: account.displayName, + email: account.email, + avatarColor: account.avatarColor, + isConnected: account.isConnected, + isDefault: account.isDefault, + })); + + return accounts; +} + +function doUserGetIdentities(): Identity[] { + return useIdentityStore.getState().identities; +} + // ─── http.post (same-origin /api/*) ─────────────────────────── /** @@ -745,6 +785,9 @@ export async function dispatchApiCall( case 'contact.create': return doContactCreate(args[0] as ContactCard); case 'contact.search': return doContactSearch(args[0] as string); + case 'user.getAccounts': return doUserGetAccounts(); + case 'user.getIdentities': return doUserGetIdentities(); + case 'admin.getConfig': return adminGet(plugin.id, args[0] as string); case 'admin.getAllConfig': return adminGetAll(plugin.id); case 'admin.setConfig': await adminSet(plugin.id, args[0] as string, args[1]); return undefined; diff --git a/lib/plugin-sandbox/runtime.tsx b/lib/plugin-sandbox/runtime.tsx index 5721079ab..2b526f613 100644 --- a/lib/plugin-sandbox/runtime.tsx +++ b/lib/plugin-sandbox/runtime.tsx @@ -175,6 +175,10 @@ function buildPluginApi(manifest: PluginManifest) { remove: (key: string) => callApi('storage.remove', [key]), keys: () => callApi('storage.keys', []), }, + user: { + getAccounts: () => callApi('user.getAccounts', []), + getIdentities: () => callApi('user.getIdentities', []), + }, http: { post: (path: string, body: Record) => callApi('http.post', [path, body]), fetch: (url: string, init?: unknown) => callApi('http.fetch', [url, init]), diff --git a/lib/plugin-types.ts b/lib/plugin-types.ts index f7fb9442f..accbf1fc4 100644 --- a/lib/plugin-types.ts +++ b/lib/plugin-types.ts @@ -1035,6 +1035,7 @@ export const ALL_PERMISSIONS = [ 'settings:read', 'settings:write', 'security:read', 'auth:observe', + 'account:read', 'http:post', 'http:fetch', 'ui:observe', 'ui:toolbar', 'ui:app-top-banner', 'ui:email-banner', 'ui:email-footer', 'ui:email-details',