feat(plugins): sandboxed page/widget frames + trekBridge (M3)

Plugins can now render UI. Page plugins appear as a nav entry and open a
full-page sandboxed iframe; the frame talks to TREK only over the postMessage
bridge.

- Server serves plugin client assets at /plugin-frame/:id/* with a strict path
  guard and a locked-down, per-plugin CSP (default-src none; connect-src limited
  to declared outbound hosts; sandbox WITHOUT allow-same-origin -> opaque origin,
  so the frame can't read the session cookie or the parent DOM). Global CSP
  frameSrc relaxed from 'none' to 'self' for exactly these frames.
- GET /api/plugins feed lists active plugins for the client.
- Client: pluginStore + PluginFrame (the trekBridge host) authenticates every
  inbound message by SENDER WINDOW IDENTITY (event.source), not by a claimed id
  or origin; pushes context (theme/locale/tripId/userId), validates navigation,
  renders notifications as text, resizes widgets, and proxies trek:invoke to the
  plugin's own routes host-side (session cookie stays with the host).
- Page route /plugins/:id + Navbar nav injection for page plugins.

Dashboard widget slot and the trek:request core-data bridge are deferred to a
later slice.
This commit is contained in:
mauriceboe
2026-07-03 17:12:56 +02:00
parent d823ac443b
commit 037eb8a209
38 changed files with 603 additions and 8 deletions
+8 -4
View File
@@ -1,16 +1,20 @@
import { Module } from '@nestjs/common';
import { PluginsController } from './plugins.controller';
import { PluginsFeedController } from './plugins-feed.controller';
import { PluginsProxyController } from './plugins-proxy.controller';
import { PluginFrameController } from './plugin-frame.controller';
import { PluginsService } from './plugins.service';
import { PluginRuntimeService } from './plugin-runtime.service';
/**
* Plugin system (#plugins). M0 read side + M2 isolated runtime: the runtime
* service owns the process supervisor and boots active plugins on startup; the
* proxy controller forwards /api/plugins/:id/* to the isolated child.
* Plugin system (#plugins). M0 read side + M2 isolated runtime + M3 frontend:
* the runtime service owns the process supervisor and boots active plugins on
* startup; the proxy forwards /api/plugins/:id/* to the child; the feed lists
* active plugins for the client; the frame controller serves sandboxed page/
* widget assets at /plugin-frame/:id/*.
*/
@Module({
controllers: [PluginsController, PluginsProxyController],
controllers: [PluginsController, PluginsFeedController, PluginsProxyController, PluginFrameController],
providers: [PluginsService, PluginRuntimeService],
})
export class PluginsModule {}