All checks were successful
CD / Build and push images (push) Successful in 3m54s
CD / Deploy to Test (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 4m9s
CI / Build container images (push) Has been skipped
CD / Smoke tests against Test (push) Successful in 1m13s
CD / Promote to Int (push) Successful in 13s
CI / Auth e2e pack (push) Successful in 5m36s
CI / Import/export fidelity gate (push) Successful in 47s
A new block plugin bundling the OFFICIAL draw.io editor — nothing ever
loads from diagrams.net; the sandbox CSP pins every request to the
plugin's own version-pinned asset path (zero-external-network verified
live via a request-capture run).
Plugin (packages/plugins/drawio):
- block data { xml, svg }: xml is the draw.io source (document of
record), svg the rendered snapshot as raw markup — render mode,
office/PDF exports (the existing fallback renderer already inlines
data.svg) and the public view all show the diagram without running
diagram code
- edit mode: snapshot + "edit in fullscreen" (an empty block opens the
editor immediately); the bundled editor runs in a child iframe of the
plugin's own assets and speaks draw.io's JSON embed protocol —
Save & Exit exports xmlsvg, persists { xml, svg } via blockData, and
drops back to the inline size
- build.mjs fetches the pinned release (v30.3.6) into a gitignored
vendor/ cache (fonts-build pattern; skipped in CI — plugin.js still
bundles, the installable ZIP needs a dev machine) and packs a trimmed
webapp subset: no dev sources, no embed.diagrams.net integrations
bundle, no standalone viewers, no MathJax/templates/PWA — 27 MiB ZIP,
85 MiB unpacked, de+en editor languages
Host/SDK extensions (generic, not drawio-specific):
- new ui.enterFullscreen()/exitFullscreen(): the surface's frame becomes
a viewport-covering overlay — same sandboxed iframe, only geometry
changes; destroy removes the frame, so a vanished plugin can never
leave the app covered
- sandbox CSP: connect-src/frame-src now allow the plugin's OWN asset
path (was 'none') — bundled apps lazy-load their resources and run in
a child frame, but the api and external hosts stay unreachable; HTML
assets are served with the same CSP so a packaged page cannot widen
the rules, and child frames inherit the sandbox attribute
- plugin size limits raised (ZIP 5→64 MiB, unpacked 20→256 MiB) for
bundled-app plugins; content types for xml/txt/ico assets
Verified end to end against a local stack (9/9): install via dropzone
(85 MiB validation), block insert, fullscreen entry, bundled editor
boots inside the double sandbox (German UI), shape drawn, Save & Exit
persists, snapshot renders inline, survives reload, zero off-origin
requests throughout.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
60 lines
2.4 KiB
TypeScript
60 lines
2.4 KiB
TypeScript
/**
|
|
* Plugin API capabilities (ADR 0008, plugin-architecture.md §"Plugin API").
|
|
*
|
|
* A capability is a named group of host methods a plugin may call. The plugin
|
|
* declares the capabilities it needs in its manifest `permissions`; the host
|
|
* router rejects any call to a method whose capability was not declared. Every
|
|
* call is executed by the host against the REST API with the **viewing user's**
|
|
* session, so a plugin can never read more than the person looking at it could.
|
|
*/
|
|
|
|
/** The capability names a manifest may declare in `permissions`. */
|
|
export const CAPABILITIES = [
|
|
'readCurrentPage',
|
|
'readPond',
|
|
'readBlock',
|
|
'blockData',
|
|
'ui',
|
|
] as const;
|
|
|
|
export type Capability = (typeof CAPABILITIES)[number];
|
|
|
|
/**
|
|
* Which host methods each capability unlocks. This is the single source of
|
|
* truth mapping an RPC method name to the capability that must be declared for
|
|
* it; both the host router (permission filtering) and the plugin-side `host`
|
|
* proxy derive from it.
|
|
*/
|
|
export const CAPABILITY_METHODS = {
|
|
readCurrentPage: ['getOutline', 'getContent', 'getMeta'],
|
|
readPond: ['listPages', 'getPageOutline', 'getPageContent'],
|
|
readBlock: ['getBlock'],
|
|
blockData: ['getData', 'setData'],
|
|
ui: ['resize', 'openPage', 'toast', 'scrollToHeading', 'enterFullscreen', 'exitFullscreen'],
|
|
} as const satisfies Record<Capability, readonly string[]>;
|
|
|
|
/** Every host method name across all capabilities. */
|
|
export type HostMethod = (typeof CAPABILITY_METHODS)[Capability][number];
|
|
|
|
/** Reverse index: method name → the capability that must be declared for it. */
|
|
export const METHOD_CAPABILITY: Readonly<Record<string, Capability>> = Object.fromEntries(
|
|
CAPABILITIES.flatMap((capability) =>
|
|
CAPABILITY_METHODS[capability].map((method) => [method, capability] as const),
|
|
),
|
|
);
|
|
|
|
/** Returns the capability a host method belongs to, or `undefined` if the
|
|
* method is not part of the v1 API surface. */
|
|
export function capabilityForMethod(method: string): Capability | undefined {
|
|
return METHOD_CAPABILITY[method];
|
|
}
|
|
|
|
/**
|
|
* Lifecycle methods the **host** calls on the **plugin** (the reverse
|
|
* direction of the capability methods above). A code plugin implements the
|
|
* subset it needs; unimplemented methods are answered with an
|
|
* `unknown_method` error by the plugin endpoint.
|
|
*/
|
|
export const PLUGIN_LIFECYCLE_METHODS = ['render', 'edit', 'destroy'] as const;
|
|
export type PluginLifecycleMethod = (typeof PLUGIN_LIFECYCLE_METHODS)[number];
|