IMAP and Gmail sync into a per-account SQLite store, with the mail read and composed in the browser. moved whole from src/servers/sidecar/email (14 files), src/servers/api/email, officer_db/src/email, and the Screens/Dashboard/Email tree. what changed on the way out: - credentials are encrypted. the column was plaintext jsonb holding an IMAP app password that the SMTP transport read straight out of the row; it is now AES-256-GCM under this plugin's own 'email' secret-store purpose, sealed and opened in queries.ts alone so nothing above it can hold ciphertext by accident. no plaintext fallback on read - a value that does not verify is an error. - the router derives its prefix from mountPrefix() instead of the literal '/api/email', which was only correct for a first-party publisher. - the frontend became panels.ts + layout.ts. the two panel appTypes that were null are now declared, because the platform screen used to override them by panel id and a plugin has no such hook. - account management became a modal. it was a section in the platform's Settings, which a plugin cannot contribute to, and a panel does not work either: PluginScreen renders the workspace locked, so a panel outside the default layout is unreachable. it follows Compose, which had already solved this. - 17 palette classes replaced with semantic tokens. not verified: this has never run. it was switched off in the platform on 2026-08-13 and its table was never created, so nothing has exercised sync, sending or the UI since. the first install is the first test.
29 lines
1.5 KiB
TypeScript
29 lines
1.5 KiB
TypeScript
import { createSidecarProxy } from '@@/sidecar/create-proxy';
|
|
import { mountPrefix } from '@@/plugins/manifest';
|
|
import { manifest } from '../manifest';
|
|
|
|
// /api/email/* — auth, then forward to officer-email. No routes of its own and no mail knowledge: not a
|
|
// message, not a folder, not an IMAP or SMTP credential.
|
|
//
|
|
// The mail store, the HTTP routes, account CRUD, resync and IMAP validation all used to live in the
|
|
// platform — ~1,800 lines against a 314-line sidecar, with the sidecar importing BACK into the platform to
|
|
// reach them. They were moved into the sidecar before this extraction, which is the only reason this file
|
|
// is twenty lines and the extraction was tractable at all.
|
|
//
|
|
// ── The prefix is DERIVED, not written ──
|
|
//
|
|
// `createSidecarProxy` strips `prefix.length` characters to build the sidecar path, so a literal
|
|
// '/api/email' is correct only for a first-party publisher. Published by anyone else this mounts at
|
|
// `/api/p/<publisher>/email`, and a literal would forward `/alice/email/messages` to a sidecar expecting
|
|
// `/messages`. `mountPrefix` is the one function allowed to know about provenance.
|
|
//
|
|
// A from-scratch mailbox sync holds a proxied request open well past the 60s idle default, hence the
|
|
// timeout; it applies to the whole prefix because the proxy must not know which routes are slow.
|
|
const proxy = createSidecarProxy({
|
|
name: 'email',
|
|
prefix: `/api${mountPrefix({ appName: 'email', manifest })}`,
|
|
timeoutSeconds: 1800,
|
|
});
|
|
|
|
export const router = proxy.router;
|