feat: add contact methods in plugin API

This commit is contained in:
Paulhenry Saux
2026-07-22 19:34:45 +02:00
parent 959d4bd6ce
commit 7bf62e4bdc
3 changed files with 53 additions and 0 deletions
+45
View File
@@ -14,6 +14,7 @@ import { apiFetch } from '../browser-navigation';
import { awaitDialog, awaitPrompt, type PromptField } from './host-dialog'; import { awaitDialog, awaitPrompt, type PromptField } from './host-dialog';
import { fileStorage } from '../plugin-storage'; import { fileStorage } from '../plugin-storage';
import { generateUUID } from '../utils'; import { generateUUID } from '../utils';
import { ContactCard } from '../jmap/types';
/** /**
* Methods only callable from the privileged (same-origin) tier. These expose * Methods only callable from the privileged (same-origin) tier. These expose
@@ -56,6 +57,11 @@ const PERM_PER_METHOD: Record<string, Permission | null> = {
'upfiles.get' : 'email:blob-write', 'upfiles.get' : 'email:blob-write',
'upfiles.save' : 'email:blob-write', 'upfiles.save' : 'email:blob-write',
'webauthn.getOrCreate': 'crypto:full', 'webauthn.getOrCreate': 'crypto:full',
// contact
'contact.get': 'contacts:read',
'contact.update': 'contacts:write',
'contact.create': 'contacts:write',
'contact.search': 'contacts:read',
// admin // admin
'admin.getConfig': 'admin:config', 'admin.getConfig': 'admin:config',
'admin.getAllConfig': 'admin:config', 'admin.getAllConfig': 'admin:config',
@@ -388,6 +394,40 @@ async function doJmapImportRaw(
); );
} }
async function doContactSearch(query: string): Promise<ContactCard[]> {
const { client } = useAuthStore.getState();
if (!client) {
throw new Error('jmap.importRaw: no active session');
}
return await client.searchContacts(query);
}
async function doContactGet(contactId: string): Promise<ContactCard | null> {
const { client } = useAuthStore.getState();
if (!client) {
throw new Error('jmap.importRaw: no active session');
}
return await client.getContact(contactId);
}
async function doContactUpdate(id: string, contact: Partial<ContactCard>): Promise<void> {
const { client } = useAuthStore.getState();
if (!client) {
throw new Error('jmap.importRaw: no active session');
}
await client.updateContact(id, contact);
}
async function doContactCreate(contact: ContactCard): Promise<ContactCard> {
const { client } = useAuthStore.getState();
if (!client) {
throw new Error('jmap.importRaw: no active session');
}
return await client.createContact(contact);
}
// ─── WebAuthn (privileged tier) ───────────────────────────────────────────── // ─── WebAuthn (privileged tier) ─────────────────────────────────────────────
// This salt acts as a constant context identifier for key derivation. // This salt acts as a constant context identifier for key derivation.
@@ -699,6 +739,11 @@ export async function dispatchApiCall(
case 'upfiles.get' : return getFile(args[0] as string); case 'upfiles.get' : return getFile(args[0] as string);
case 'upfiles.save' : return saveFile(args[0] as string, args[1] as File); case 'upfiles.save' : return saveFile(args[0] as string, args[1] as File);
case 'webauthn.getOrCreate': return doGetOrCreatePRF(args[0] as number[] | undefined, args[1] as string | undefined, args[2] as string | undefined); case 'webauthn.getOrCreate': return doGetOrCreatePRF(args[0] as number[] | undefined, args[1] as string | undefined, args[2] as string | undefined);
case 'contact.get': return doContactGet(args[0] as string);
case 'contact.update': return doContactUpdate(args[0] as string, args[1] as Partial<ContactCard>);
case 'contact.create': return doContactCreate(args[0] as ContactCard);
case 'contact.search': return doContactSearch(args[0] as string);
case 'admin.getConfig': return adminGet(plugin.id, args[0] as string); case 'admin.getConfig': return adminGet(plugin.id, args[0] as string);
case 'admin.getAllConfig': return adminGetAll(plugin.id); case 'admin.getAllConfig': return adminGetAll(plugin.id);
+1
View File
@@ -244,6 +244,7 @@ export const API_METHODS = [
'webauthn.getOrCreate', 'webauthn.getOrCreate',
'jmap.fetchBlob', 'jmap.sendRaw', 'jmap.fetchBlob', 'jmap.sendRaw',
'upfiles.get', 'upfiles.save', 'upfiles.get', 'upfiles.save',
'contact.get', 'contact.update', 'contact.create', 'contact.search',
'admin.getConfig', 'admin.getAllConfig', 'admin.setConfig', 'admin.deleteConfig', 'admin.getConfig', 'admin.getAllConfig', 'admin.setConfig', 'admin.deleteConfig',
'toast.success', 'toast.error', 'toast.info', 'toast.warning', 'toast.success', 'toast.error', 'toast.info', 'toast.warning',
'ui.confirm', 'ui.alert', 'ui.prompt', 'ui.rerenderEmail', 'ui.rerenderFetchedEmails', 'ui.openExternalUrl', 'ui.downloadFile', 'ui.confirm', 'ui.alert', 'ui.prompt', 'ui.rerenderEmail', 'ui.rerenderFetchedEmails', 'ui.openExternalUrl', 'ui.downloadFile',
+7
View File
@@ -29,6 +29,7 @@ import type {
} from './protocol'; } from './protocol';
import { themeSnapshotToCSS, type ThemeSnapshot } from './host-theme'; import { themeSnapshotToCSS, type ThemeSnapshot } from './host-theme';
import type { SlotName } from '../plugin-types'; import type { SlotName } from '../plugin-types';
import { ContactCard } from '../jmap/types';
// ─── Module-scope state ────────────────────────────────────── // ─── Module-scope state ──────────────────────────────────────
@@ -204,6 +205,12 @@ function buildPluginApi(manifest: PluginManifest) {
opts?: { keywords?: Record<string, boolean>; accountId?: string }, opts?: { keywords?: Record<string, boolean>; accountId?: string },
) => callApi('jmap.importRaw', [rawBytes, mailboxRoles, opts]), ) => callApi('jmap.importRaw', [rawBytes, mailboxRoles, opts]),
}, },
contacts: {
get: (contactId: string) => callApi('contact.get', [contactId]) as Promise<ContactCard>,
update: (contactId: string, updates: Partial<ContactCard>) => callApi('contact.update', [contactId, updates]),
create: (contact: ContactCard) => callApi('contact.create', [contact]) as Promise<string>,
search: (query: string) => callApi('contact.search', [query]) as Promise<ContactCard[]>,
},
/** /**
* Used to alterate files before they are uploaded to server. * Used to alterate files before they are uploaded to server.
* Edited files are saved on indexedDB and remove once the upload to server begins. * Edited files are saved on indexedDB and remove once the upload to server begins.