@songsid/agend 2.1.12 → 2.2.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/README.zh-TW.md +4 -2
- package/dist/agent-cli.js +6 -1
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +68 -12
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-token-header.d.ts +13 -0
- package/dist/agent-token-header.js +16 -0
- package/dist/agent-token-header.js.map +1 -0
- package/dist/auth/one-time-code.d.ts +19 -0
- package/dist/auth/one-time-code.js +49 -0
- package/dist/auth/one-time-code.js.map +1 -0
- package/dist/auth-api.d.ts +52 -0
- package/dist/auth-api.js +346 -0
- package/dist/auth-api.js.map +1 -0
- package/dist/backend/antigravity.js +4 -3
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +4 -0
- package/dist/backend/claude-code.js +97 -0
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.js +59 -30
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/grok.js +1 -1
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro-agent.d.ts +66 -0
- package/dist/backend/kiro-agent.js +302 -0
- package/dist/backend/kiro-agent.js.map +1 -0
- package/dist/backend/kiro-identity.d.ts +80 -0
- package/dist/backend/kiro-identity.js +537 -0
- package/dist/backend/kiro-identity.js.map +1 -0
- package/dist/backend/kiro-v2-store-worker.d.ts +1 -0
- package/dist/backend/kiro-v2-store-worker.js +87 -0
- package/dist/backend/kiro-v2-store-worker.js.map +1 -0
- package/dist/backend/kiro-v2-store.d.ts +54 -0
- package/dist/backend/kiro-v2-store.js +131 -0
- package/dist/backend/kiro-v2-store.js.map +1 -0
- package/dist/backend/kiro-v3-identity.d.ts +5 -0
- package/dist/backend/kiro-v3-identity.js +22 -0
- package/dist/backend/kiro-v3-identity.js.map +1 -1
- package/dist/backend/kiro.d.ts +90 -7
- package/dist/backend/kiro.js +500 -124
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/muse.js +1 -1
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +51 -3
- package/dist/backend/types.js.map +1 -1
- package/dist/cache-analysis.d.ts +92 -0
- package/dist/cache-analysis.js +147 -0
- package/dist/cache-analysis.js.map +1 -0
- package/dist/cache-ledger.d.ts +108 -0
- package/dist/cache-ledger.js +230 -0
- package/dist/cache-ledger.js.map +1 -0
- package/dist/cache-prices.d.ts +32 -0
- package/dist/cache-prices.js +73 -0
- package/dist/cache-prices.js.map +1 -0
- package/dist/cache-scan.d.ts +39 -0
- package/dist/cache-scan.js +115 -0
- package/dist/cache-scan.js.map +1 -0
- package/dist/cache-service.d.ts +115 -0
- package/dist/cache-service.js +417 -0
- package/dist/cache-service.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +1 -0
- package/dist/channel/adapters/discord.js +29 -10
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +1 -0
- package/dist/channel/adapters/telegram.js +13 -1
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/types.d.ts +9 -1
- package/dist/classic-channel-manager.d.ts +18 -10
- package/dist/classic-channel-manager.js +45 -18
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli-entry.d.ts +19 -0
- package/dist/cli-entry.js +36 -0
- package/dist/cli-entry.js.map +1 -0
- package/dist/cli.js +471 -146
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +5 -3
- package/dist/command-table.js +29 -27
- package/dist/command-table.js.map +1 -1
- package/dist/config-validator.js +46 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/config.js +12 -0
- package/dist/config.js.map +1 -1
- package/dist/connection-secrets.d.ts +6 -0
- package/dist/connection-secrets.js +4 -0
- package/dist/connection-secrets.js.map +1 -1
- package/dist/daemon.d.ts +95 -4
- package/dist/daemon.js +748 -111
- package/dist/daemon.js.map +1 -1
- package/dist/darwin-memory.d.ts +6 -1
- package/dist/darwin-memory.js +17 -6
- package/dist/darwin-memory.js.map +1 -1
- package/dist/delivery-consumed-watch.d.ts +85 -0
- package/dist/delivery-consumed-watch.js +244 -0
- package/dist/delivery-consumed-watch.js.map +1 -0
- package/dist/delivery-outbox.d.ts +61 -0
- package/dist/delivery-outbox.js +120 -2
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-reconciliation.d.ts +14 -5
- package/dist/delivery-reconciliation.js +27 -4
- package/dist/delivery-reconciliation.js.map +1 -1
- package/dist/delivery-reconciliation.test.js +35 -1
- package/dist/delivery-reconciliation.test.js.map +1 -1
- package/dist/event-log.d.ts +4 -1
- package/dist/event-log.js +6 -3
- package/dist/event-log.js.map +1 -1
- package/dist/event-loop-watch.d.ts +28 -0
- package/dist/event-loop-watch.js +75 -3
- package/dist/event-loop-watch.js.map +1 -1
- package/dist/fleet-context.d.ts +22 -2
- package/dist/fleet-control-audit.d.ts +5 -0
- package/dist/fleet-control-audit.js +7 -0
- package/dist/fleet-control-audit.js.map +1 -1
- package/dist/fleet-level-config.d.ts +2 -2
- package/dist/fleet-level-config.js +28 -1
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +339 -12
- package/dist/fleet-manager.js +2623 -461
- package/dist/fleet-manager.js.map +1 -1
- package/dist/full-restart.js +2 -3
- package/dist/full-restart.js.map +1 -1
- package/dist/general-knowledge/skills/persona-emoji/SKILL.md +3 -1
- package/dist/host-memory.d.ts +4 -0
- package/dist/host-memory.js.map +1 -1
- package/dist/install-lock.d.ts +49 -0
- package/dist/install-lock.js +200 -0
- package/dist/install-lock.js.map +1 -0
- package/dist/instance-config-impact.js +8 -2
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +10 -5
- package/dist/instance-lifecycle.js +162 -53
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +164 -16
- package/dist/locale.js.map +1 -1
- package/dist/login-controller.d.ts +2 -0
- package/dist/login-controller.js +17 -5
- package/dist/login-controller.js.map +1 -1
- package/dist/memory-pressure.d.ts +7 -8
- package/dist/memory-pressure.js +76 -15
- package/dist/memory-pressure.js.map +1 -1
- package/dist/monotonic-budget.d.ts +2 -0
- package/dist/monotonic-budget.js +28 -0
- package/dist/monotonic-budget.js.map +1 -0
- package/dist/needs-you-hub.d.ts +114 -0
- package/dist/needs-you-hub.js +370 -0
- package/dist/needs-you-hub.js.map +1 -0
- package/dist/needs-you-live.d.ts +116 -0
- package/dist/needs-you-live.js +228 -0
- package/dist/needs-you-live.js.map +1 -0
- package/dist/needs-you.d.ts +132 -0
- package/dist/needs-you.js +164 -0
- package/dist/needs-you.js.map +1 -0
- package/dist/node-version-guard.d.ts +30 -0
- package/dist/node-version-guard.js +50 -0
- package/dist/node-version-guard.js.map +1 -0
- package/dist/operator-control-paths.d.ts +4 -0
- package/dist/operator-control-paths.js +24 -0
- package/dist/operator-control-paths.js.map +1 -0
- package/dist/outbound-handlers.d.ts +2 -0
- package/dist/outbound-handlers.js +9 -2
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +5 -5
- package/dist/outbound-schemas.js +1 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/package-preimage.d.ts +26 -0
- package/dist/package-preimage.js +96 -0
- package/dist/package-preimage.js.map +1 -0
- package/dist/profile-control.js +4 -20
- package/dist/profile-control.js.map +1 -1
- package/dist/public-web-gateway.d.ts +18 -0
- package/dist/public-web-gateway.js +141 -0
- package/dist/public-web-gateway.js.map +1 -0
- package/dist/public-web-link.d.ts +53 -0
- package/dist/public-web-link.js +175 -0
- package/dist/public-web-link.js.map +1 -0
- package/dist/quickstart-api.d.ts +8 -1
- package/dist/quickstart-api.js +86 -48
- package/dist/quickstart-api.js.map +1 -1
- package/dist/quickstart.d.ts +1 -0
- package/dist/quickstart.js +16 -6
- package/dist/quickstart.js.map +1 -1
- package/dist/restart-guard.d.ts +52 -0
- package/dist/restart-guard.js +109 -0
- package/dist/restart-guard.js.map +1 -0
- package/dist/scheduler/db.d.ts +7 -1
- package/dist/scheduler/db.js +44 -2
- package/dist/scheduler/db.js.map +1 -1
- package/dist/scheduler/index.d.ts +1 -1
- package/dist/scheduler/retry-label.d.ts +11 -0
- package/dist/scheduler/retry-label.js +24 -0
- package/dist/scheduler/retry-label.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +55 -2
- package/dist/scheduler/scheduler.js +220 -8
- package/dist/scheduler/scheduler.js.map +1 -1
- package/dist/scheduler/types.d.ts +22 -0
- package/dist/scheduler/types.js.map +1 -1
- package/dist/secret-store.d.ts +5 -0
- package/dist/secret-store.js +20 -0
- package/dist/secret-store.js.map +1 -1
- package/dist/service-activation.d.ts +152 -0
- package/dist/service-activation.js +407 -0
- package/dist/service-activation.js.map +1 -0
- package/dist/service-installer.d.ts +63 -1
- package/dist/service-installer.js +141 -16
- package/dist/service-installer.js.map +1 -1
- package/dist/service-plan.d.ts +65 -0
- package/dist/service-plan.js +108 -0
- package/dist/service-plan.js.map +1 -0
- package/dist/service-restart-selection.d.ts +6 -5
- package/dist/service-restart-selection.js +6 -5
- package/dist/service-restart-selection.js.map +1 -1
- package/dist/settings-api.d.ts +10 -2
- package/dist/settings-api.js +216 -72
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-baseline.d.ts +20 -0
- package/dist/settings-baseline.js +104 -0
- package/dist/settings-baseline.js.map +1 -0
- package/dist/settings-change.d.ts +21 -0
- package/dist/settings-change.js +161 -0
- package/dist/settings-change.js.map +1 -0
- package/dist/settings-confirmation.d.ts +105 -0
- package/dist/settings-confirmation.js +232 -0
- package/dist/settings-confirmation.js.map +1 -0
- package/dist/settings-control.d.ts +41 -0
- package/dist/settings-control.js +294 -0
- package/dist/settings-control.js.map +1 -0
- package/dist/settings-effect.d.ts +24 -0
- package/dist/settings-effect.js +169 -0
- package/dist/settings-effect.js.map +1 -0
- package/dist/settings-http-confirmation.d.ts +29 -0
- package/dist/settings-http-confirmation.js +123 -0
- package/dist/settings-http-confirmation.js.map +1 -0
- package/dist/settings-instance-patch.d.ts +4 -0
- package/dist/settings-instance-patch.js +26 -0
- package/dist/settings-instance-patch.js.map +1 -0
- package/dist/settings-request-capability.d.ts +16 -0
- package/dist/settings-request-capability.js +32 -0
- package/dist/settings-request-capability.js.map +1 -0
- package/dist/settings-transaction.d.ts +65 -0
- package/dist/settings-transaction.js +223 -0
- package/dist/settings-transaction.js.map +1 -0
- package/dist/setup-auth.d.ts +10 -18
- package/dist/setup-auth.js +14 -38
- package/dist/setup-auth.js.map +1 -1
- package/dist/setup-form.d.ts +1 -1
- package/dist/setup-form.js +75 -18
- package/dist/setup-form.js.map +1 -1
- package/dist/setup-guide.d.ts +13 -0
- package/dist/setup-guide.js +25 -0
- package/dist/setup-guide.js.map +1 -0
- package/dist/setup-host.d.ts +16 -0
- package/dist/setup-host.js +199 -35
- package/dist/setup-host.js.map +1 -1
- package/dist/setup-wizard.js +5 -1
- package/dist/setup-wizard.js.map +1 -1
- package/dist/slash-authz.d.ts +17 -3
- package/dist/slash-authz.js +5 -0
- package/dist/slash-authz.js.map +1 -1
- package/dist/spawn-gate.js +3 -3
- package/dist/spawn-gate.js.map +1 -1
- package/dist/sqlite-open-errors.d.ts +12 -0
- package/dist/sqlite-open-errors.js +13 -0
- package/dist/sqlite-open-errors.js.map +1 -0
- package/dist/statusline-watcher.d.ts +5 -0
- package/dist/statusline-watcher.js +2 -0
- package/dist/statusline-watcher.js.map +1 -1
- package/dist/steer-capability.d.ts +6 -0
- package/dist/steer-capability.js +8 -0
- package/dist/steer-capability.js.map +1 -1
- package/dist/sync-work-attribution.d.ts +14 -0
- package/dist/sync-work-attribution.js +43 -8
- package/dist/sync-work-attribution.js.map +1 -1
- package/dist/tips.js +10 -5
- package/dist/tips.js.map +1 -1
- package/dist/tmux-control.d.ts +57 -5
- package/dist/tmux-control.js +379 -61
- package/dist/tmux-control.js.map +1 -1
- package/dist/tmux-manager.d.ts +20 -6
- package/dist/tmux-manager.js +90 -47
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tmux-read.d.ts +59 -0
- package/dist/tmux-read.js +213 -0
- package/dist/tmux-read.js.map +1 -0
- package/dist/topic-commands.d.ts +21 -10
- package/dist/topic-commands.js +205 -63
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-delta-reader.js +2 -1
- package/dist/transcript-delta-reader.js.map +1 -1
- package/dist/tunnel/cloudflared-install.d.ts +2 -0
- package/dist/tunnel/cloudflared-install.js +1 -1
- package/dist/tunnel/cloudflared-install.js.map +1 -1
- package/dist/tunnel/purpose-lane.d.ts +14 -0
- package/dist/tunnel/purpose-lane.js +43 -0
- package/dist/tunnel/purpose-lane.js.map +1 -0
- package/dist/turn-reply-guard.d.ts +2 -0
- package/dist/turn-reply-guard.js +10 -1
- package/dist/turn-reply-guard.js.map +1 -1
- package/dist/types.d.ts +33 -0
- package/dist/ui/app.html +18 -0
- package/dist/ui/chat-commands.js +43 -0
- package/dist/ui/chat-render.js +478 -0
- package/dist/ui/chat-store.js +318 -0
- package/dist/ui/chat-strings.js +89 -0
- package/dist/ui/chat-thread.js +246 -0
- package/dist/ui/chat-tour.js +116 -0
- package/dist/ui/fleet-cache.js +168 -0
- package/dist/ui/fleet-org.js +142 -0
- package/dist/ui/panel-chat.js +504 -0
- package/dist/ui/panel-fleet.js +427 -0
- package/dist/ui/panel-needs.js +202 -0
- package/dist/ui/panel-settings.js +754 -0
- package/dist/ui/preview-panel.js +202 -0
- package/dist/ui/preview.js +270 -0
- package/dist/ui/settings-apply.js +239 -0
- package/dist/ui/settings-confirm.js +186 -0
- package/dist/ui/settings-dialogs.js +546 -0
- package/dist/ui/settings-model.js +277 -0
- package/dist/ui/settings-strings.js +654 -0
- package/dist/ui/settings-wizard.js +140 -0
- package/dist/ui/shared/agend-auth.js +93 -0
- package/dist/ui/shared/app-ctx.js +80 -0
- package/dist/ui/shared/app-html.js +9 -0
- package/dist/ui/shared/app-i18n.js +95 -0
- package/dist/ui/shared/app-nav.js +73 -0
- package/dist/ui/shared/app-needs.js +114 -0
- package/dist/ui/shared/app-route.js +73 -0
- package/dist/ui/shared/app-session.js +66 -0
- package/dist/ui/shared/app-shell.js +346 -0
- package/dist/ui/shared/app-store.js +51 -0
- package/dist/ui/shared/app-stream.js +201 -0
- package/dist/ui/shared/app.css +820 -0
- package/dist/ui/shared/app.js +88 -0
- package/dist/ui/shared/fonts/OFL.txt +92 -0
- package/dist/ui/shared/fonts/README.md +21 -0
- package/dist/ui/shared/fonts/inter.woff2 +0 -0
- package/dist/ui/shared/htm.module.js +1 -0
- package/dist/ui/shared/panel-view.js +655 -0
- package/dist/ui/shared/preact-hooks.module.js +2 -0
- package/dist/ui/shared/preact.module.js +2 -0
- package/dist/ui/shared/signin.js +142 -0
- package/dist/ui/shared/theme.js +29 -0
- package/dist/ui/shared/tokens.css +153 -0
- package/dist/ui/shared/ui-confirm.js +43 -0
- package/dist/ui/shared/ui-dialog.js +45 -0
- package/dist/ui/shared/ui-icons.js +63 -0
- package/dist/ui/shared/ui-menu.js +39 -0
- package/dist/ui/shared/ui-states.js +20 -0
- package/dist/ui/shared/ui-toast.js +18 -0
- package/dist/ui/shared/vendor/htm.LICENSE +202 -0
- package/dist/ui/shared/vendor/preact.LICENSE +21 -0
- package/dist/ui/shared/vendor/vendor.json +39 -0
- package/dist/ui/shared/view-strings.js +309 -0
- package/dist/ui/signin.html +52 -0
- package/dist/update-check.d.ts +6 -2
- package/dist/update-check.js +12 -3
- package/dist/update-check.js.map +1 -1
- package/dist/update-dispatch.d.ts +28 -0
- package/dist/update-dispatch.js +73 -0
- package/dist/update-dispatch.js.map +1 -0
- package/dist/update-install.d.ts +129 -0
- package/dist/update-install.js +262 -0
- package/dist/update-install.js.map +1 -0
- package/dist/upgrade-notices.d.ts +17 -0
- package/dist/upgrade-notices.js +93 -0
- package/dist/upgrade-notices.js.map +1 -0
- package/dist/usage/statusline-usage.d.ts +2 -0
- package/dist/usage/statusline-usage.js +14 -10
- package/dist/usage/statusline-usage.js.map +1 -1
- package/dist/view-api.d.ts +6 -1
- package/dist/view-api.js +62 -51
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +85 -14
- package/dist/web-api.js +557 -77
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.d.ts +71 -27
- package/dist/web-auth.js +169 -69
- package/dist/web-auth.js.map +1 -1
- package/dist/web-body.d.ts +3 -0
- package/dist/web-body.js +55 -0
- package/dist/web-body.js.map +1 -0
- package/dist/web-channel-echo.d.ts +9 -0
- package/dist/web-channel-echo.js +55 -0
- package/dist/web-channel-echo.js.map +1 -0
- package/dist/web-chat-history.d.ts +125 -0
- package/dist/web-chat-history.js +184 -0
- package/dist/web-chat-history.js.map +1 -0
- package/dist/web-commands.d.ts +77 -0
- package/dist/web-commands.js +81 -0
- package/dist/web-commands.js.map +1 -0
- package/dist/web-continuation.d.ts +7 -0
- package/dist/web-continuation.js +22 -0
- package/dist/web-continuation.js.map +1 -0
- package/dist/web-host-guard.d.ts +34 -0
- package/dist/web-host-guard.js +57 -1
- package/dist/web-host-guard.js.map +1 -1
- package/dist/web-login.d.ts +72 -0
- package/dist/web-login.js +132 -0
- package/dist/web-login.js.map +1 -0
- package/dist/web-org.d.ts +55 -0
- package/dist/web-org.js +67 -0
- package/dist/web-org.js.map +1 -0
- package/dist/web-preview.d.ts +77 -0
- package/dist/web-preview.js +240 -0
- package/dist/web-preview.js.map +1 -0
- package/dist/web-request-context.d.ts +10 -0
- package/dist/web-request-context.js +13 -0
- package/dist/web-request-context.js.map +1 -0
- package/dist/web-session.d.ts +167 -0
- package/dist/web-session.js +389 -0
- package/dist/web-session.js.map +1 -0
- package/dist/web-shell-routes.d.ts +59 -0
- package/dist/web-shell-routes.js +96 -0
- package/dist/web-shell-routes.js.map +1 -0
- package/dist/web-upload.d.ts +183 -0
- package/dist/web-upload.js +468 -0
- package/dist/web-upload.js.map +1 -0
- package/dist/website-tips-renderer.d.ts +2 -2
- package/dist/website-tips-renderer.js +3 -3
- package/dist/website-tips-renderer.js.map +1 -1
- package/launcher/agend +110 -0
- package/launcher/agend-agent +110 -0
- package/launcher/agend-agent.cjs +4 -0
- package/launcher/agend.cjs +4 -0
- package/launcher/install-admission.cjs +105 -0
- package/launcher/launch.cjs +64 -0
- package/launcher/postinstall.cjs +114 -0
- package/launcher/run-in-process.cjs +13 -0
- package/launcher/runtime-platform.cjs +86 -0
- package/launcher/runtime-select.cjs +217 -0
- package/package.json +18 -7
- package/scripts/preinstall-guard.cjs +55 -0
- package/templates/launchd.plist.ejs +2 -2
- package/templates/systemd.service.ejs +1 -1
- package/dist/ui/dashboard.html +0 -774
- package/dist/ui/settings.html +0 -2165
- package/dist/ui/view.html +0 -1139
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dashboard's login codes: who may redeem one, how often they may be wrong,
|
|
3
|
+
* and what happens when they are wrong too often.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately not `SetupCredentials`. That class belongs to a ten-minute
|
|
6
|
+
* escape hatch whose answer to five wrong tries is to destroy itself; a fleet
|
|
7
|
+
* that runs for months must not be closable by whoever finds its address. So the
|
|
8
|
+
* rules here are different, and each is a property worth testing directly:
|
|
9
|
+
*
|
|
10
|
+
* - **No outstanding code, nothing to guess.** A redemption while no code has
|
|
11
|
+
* been issued gets the same refusal as a wrong code and counts against
|
|
12
|
+
* nothing. The door is only a door in the few minutes after the operator asked
|
|
13
|
+
* for a code on a channel only they can read.
|
|
14
|
+
* - **A code dies with its fifth wrong try — the code, not the fleet.** The
|
|
15
|
+
* cost to a stranger who guesses is that the operator asks for another.
|
|
16
|
+
* - **A breaker for the rest.** Enough wrong tries across codes pauses
|
|
17
|
+
* redemption for a while, correct codes included, so a patient guesser cannot
|
|
18
|
+
* turn five-per-code into an unbounded stream. Sessions already signed in are
|
|
19
|
+
* not affected: this object knows nothing about them.
|
|
20
|
+
* - **One code at a time, single use.** Issuing replaces the outstanding code;
|
|
21
|
+
* redeeming it spends it.
|
|
22
|
+
*
|
|
23
|
+
* The code is only an exchange key. What a successful redemption yields is a
|
|
24
|
+
* fresh 256-bit session id from `web-session.ts` that has no relation to it.
|
|
25
|
+
*
|
|
26
|
+
* Deliberately has no idea what an HTTP request is.
|
|
27
|
+
*/
|
|
28
|
+
import { constantTimeMatches, formatOneTimeCode, generateOneTimeCode, normalizeOneTimeCode, } from "./auth/one-time-code.js";
|
|
29
|
+
import { randomBytes } from "node:crypto";
|
|
30
|
+
export const LOGIN_CODE_TTL_MS = 5 * 60 * 1000;
|
|
31
|
+
/** Same reasoning as setup: eight characters on a phone get mistyped. */
|
|
32
|
+
export const MAX_LOGIN_CODE_ATTEMPTS = 5;
|
|
33
|
+
export const LOGIN_BREAKER_WINDOW_MS = 15 * 60 * 1000;
|
|
34
|
+
export const LOGIN_BREAKER_THRESHOLD = 20;
|
|
35
|
+
export const LOGIN_BREAKER_PAUSE_MS = 5 * 60 * 1000;
|
|
36
|
+
/** Compared against when nothing is outstanding, so "no code" costs what "wrong code" costs. */
|
|
37
|
+
const NO_CODE_PLACEHOLDER = "AAAAAAAA";
|
|
38
|
+
export class WebLoginCodes {
|
|
39
|
+
outstanding = null;
|
|
40
|
+
failures = [];
|
|
41
|
+
pausedUntil = 0;
|
|
42
|
+
now;
|
|
43
|
+
generate;
|
|
44
|
+
onEvent;
|
|
45
|
+
constructor(opts = {}) {
|
|
46
|
+
this.now = opts.now ?? Date.now;
|
|
47
|
+
this.generate = opts.generate ?? generateOneTimeCode;
|
|
48
|
+
this.onEvent = opts.onEvent ?? (() => { });
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Issue a code, replacing any outstanding one. Only ever called from a channel the operator controls.
|
|
52
|
+
* `epoch` is the current web token's (`tokenEpoch`): rotating the token — from this process or from
|
|
53
|
+
* `agend web-token rotate` in another — withdraws an unused code, because redemption re-checks it.
|
|
54
|
+
*/
|
|
55
|
+
issue(opts) {
|
|
56
|
+
const code = this.generate();
|
|
57
|
+
const tier = opts.tier ?? "admin";
|
|
58
|
+
const expiresAt = this.now() + LOGIN_CODE_TTL_MS;
|
|
59
|
+
const issuanceId = randomBytes(16).toString("hex");
|
|
60
|
+
this.outstanding = { code, expiresAt, tier, epoch: opts.epoch, failures: 0, issuanceId, audience: opts.audience ?? "local", owner: opts.owner };
|
|
61
|
+
this.onEvent("issued");
|
|
62
|
+
return { display: formatOneTimeCode(code), expiresAt, tier, issuanceId };
|
|
63
|
+
}
|
|
64
|
+
/** Whether a redemption right now would be compared against anything. */
|
|
65
|
+
get hasOutstandingCode() {
|
|
66
|
+
return this.activeCode() !== null;
|
|
67
|
+
}
|
|
68
|
+
/** Withdraw the outstanding code without spending it (`/dashboard revoke`). */
|
|
69
|
+
revoke() {
|
|
70
|
+
this.outstanding = null;
|
|
71
|
+
}
|
|
72
|
+
/** `currentEpoch` is the web token's epoch NOW: a code issued under another token is gone. */
|
|
73
|
+
revokeIfCurrent(issuanceId) {
|
|
74
|
+
if (this.outstanding?.issuanceId === issuanceId)
|
|
75
|
+
this.outstanding = null;
|
|
76
|
+
}
|
|
77
|
+
revokeAudience(audience) {
|
|
78
|
+
if (this.outstanding?.audience === audience)
|
|
79
|
+
this.outstanding = null;
|
|
80
|
+
}
|
|
81
|
+
redeem(input, currentEpoch, audience = "local") {
|
|
82
|
+
const now = this.now();
|
|
83
|
+
if (now < this.pausedUntil)
|
|
84
|
+
return { kind: "paused", retryAfterMs: this.pausedUntil - now };
|
|
85
|
+
const provided = normalizeOneTimeCode(input);
|
|
86
|
+
// Nothing typed is not a guess; it costs nothing (same as the setup page).
|
|
87
|
+
if (!provided)
|
|
88
|
+
return { kind: "invalid" };
|
|
89
|
+
let active = this.activeCode();
|
|
90
|
+
if (active && active.epoch !== currentEpoch) {
|
|
91
|
+
// The token was rotated after this code was issued: the rotation withdrew it.
|
|
92
|
+
this.outstanding = null;
|
|
93
|
+
active = null;
|
|
94
|
+
}
|
|
95
|
+
if (!active) {
|
|
96
|
+
constantTimeMatches(provided, NO_CODE_PLACEHOLDER);
|
|
97
|
+
return { kind: "invalid" };
|
|
98
|
+
}
|
|
99
|
+
if (constantTimeMatches(provided, active.code) && active.audience === audience) {
|
|
100
|
+
this.outstanding = null;
|
|
101
|
+
this.onEvent("spent");
|
|
102
|
+
return { kind: "ok", tier: active.tier, ...(active.owner ? { owner: active.owner } : {}) };
|
|
103
|
+
}
|
|
104
|
+
active.failures += 1;
|
|
105
|
+
this.recordFailure(now);
|
|
106
|
+
if (active.failures >= MAX_LOGIN_CODE_ATTEMPTS) {
|
|
107
|
+
this.outstanding = null;
|
|
108
|
+
this.onEvent("burned");
|
|
109
|
+
}
|
|
110
|
+
return { kind: "invalid" };
|
|
111
|
+
}
|
|
112
|
+
activeCode() {
|
|
113
|
+
if (!this.outstanding)
|
|
114
|
+
return null;
|
|
115
|
+
if (this.now() >= this.outstanding.expiresAt) {
|
|
116
|
+
this.outstanding = null;
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
return this.outstanding;
|
|
120
|
+
}
|
|
121
|
+
recordFailure(now) {
|
|
122
|
+
this.failures.push(now);
|
|
123
|
+
const cutoff = now - LOGIN_BREAKER_WINDOW_MS;
|
|
124
|
+
this.failures = this.failures.filter(t => t > cutoff);
|
|
125
|
+
if (this.failures.length >= LOGIN_BREAKER_THRESHOLD) {
|
|
126
|
+
this.pausedUntil = now + LOGIN_BREAKER_PAUSE_MS;
|
|
127
|
+
this.failures = [];
|
|
128
|
+
this.onEvent("breaker-open");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=web-login.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-login.js","sourceRoot":"","sources":["../src/web-login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAC/C,yEAAyE;AACzE,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC;AACzC,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AACtD,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,CAAC;AAC1C,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAEpD,gGAAgG;AAChG,MAAM,mBAAmB,GAAG,UAAU,CAAC;AA4CvC,MAAM,OAAO,aAAa;IAChB,WAAW,GAAuB,IAAI,CAAC;IACvC,QAAQ,GAAa,EAAE,CAAC;IACxB,WAAW,GAAG,CAAC,CAAC;IACP,GAAG,CAAe;IAClB,QAAQ,CAAe;IACvB,OAAO,CAAkE;IAE1F,YAAY,OAA6B,EAAE;QACzC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;QAChC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,mBAAmB,CAAC;QACrD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,IAAsF;QAC1F,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC;QAClC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,iBAAiB,CAAC;QACjD,MAAM,UAAU,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACnD,IAAI,CAAC,WAAW,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;QAChJ,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACvB,OAAO,EAAE,OAAO,EAAE,iBAAiB,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAC3E,CAAC;IAED,yEAAyE;IACzE,IAAI,kBAAkB;QACpB,OAAO,IAAI,CAAC,UAAU,EAAE,KAAK,IAAI,CAAC;IACpC,CAAC;IAED,+EAA+E;IAC/E,MAAM;QACJ,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;IAC1B,CAAC;IAED,8FAA8F;IAC9F,eAAe,CAAC,UAAkB;QAChC,IAAI,IAAI,CAAC,WAAW,EAAE,UAAU,KAAK,UAAU;YAAE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;IAC3E,CAAC;IAED,cAAc,CAAC,QAAgB;QAC7B,IAAI,IAAI,CAAC,WAAW,EAAE,QAAQ,KAAK,QAAQ;YAAE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;IACvE,CAAC;IAED,MAAM,CAAC,KAAa,EAAE,YAAoB,EAAE,QAAQ,GAAG,OAAO;QAC5D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,GAAG,GAAG,IAAI,CAAC,WAAW;YAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,CAAC,WAAW,GAAG,GAAG,EAAE,CAAC;QAE5F,MAAM,QAAQ,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;QAC7C,2EAA2E;QAC3E,IAAI,CAAC,QAAQ;YAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;QAE1C,IAAI,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAC/B,IAAI,MAAM,IAAI,MAAM,CAAC,KAAK,KAAK,YAAY,EAAE,CAAC;YAC5C,8EAA8E;YAC9E,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;YACxB,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;QACD,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,mBAAmB,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAC;YACnD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;QAC7B,CAAC;QAED,IAAI,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC/E,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;YACxB,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YACtB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAC7F,CAAC;QAED,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;QACrB,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;QACxB,IAAI,MAAM,CAAC,QAAQ,IAAI,uBAAuB,EAAE,CAAC;YAC/C,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;YACxB,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACzB,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAC7B,CAAC;IAEO,UAAU;QAChB,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,OAAO,IAAI,CAAC;QACnC,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,WAAW,CAAC,SAAS,EAAE,CAAC;YAAC,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;YAAC,OAAO,IAAI,CAAC;QAAC,CAAC;QACvF,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAEO,aAAa,CAAC,GAAW;QAC/B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,MAAM,MAAM,GAAG,GAAG,GAAG,uBAAuB,CAAC;QAC7C,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC;QACtD,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,IAAI,uBAAuB,EAAE,CAAC;YACpD,IAAI,CAAC,WAAW,GAAG,GAAG,GAAG,sBAAsB,CAAC;YAChD,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;YACnB,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #1389: the fleet's org chart, as the web shows it (Fleet → Org chart). Presentation only (the user's Discord-first
|
|
3
|
+
* rule): nothing here is a new source of truth. The structure is read from what already exists — fleet.yaml's
|
|
4
|
+
* instances and teams, General (general_topic), each instance's world and thread — and the live state is not here at
|
|
5
|
+
* all: the page takes it from the stream it already follows (status, activity, `needs`).
|
|
6
|
+
*/
|
|
7
|
+
import type { WorldPlace } from "./needs-you.js";
|
|
8
|
+
export interface OrgInstanceConfig {
|
|
9
|
+
description?: unknown;
|
|
10
|
+
tags?: unknown;
|
|
11
|
+
}
|
|
12
|
+
export interface OrgInput {
|
|
13
|
+
/** Every instance the dashboard lists, in its order (fleet.yaml's, then ClassicBot rooms). */
|
|
14
|
+
names: readonly string[];
|
|
15
|
+
instances: Readonly<Record<string, OrgInstanceConfig | undefined>>;
|
|
16
|
+
teams: Readonly<Record<string, {
|
|
17
|
+
members?: unknown;
|
|
18
|
+
description?: unknown;
|
|
19
|
+
} | undefined>> | undefined;
|
|
20
|
+
isGeneral(name: string): boolean;
|
|
21
|
+
isClassic(name: string): boolean;
|
|
22
|
+
/** The platform and group of the world the instance lives in, when it has one. */
|
|
23
|
+
place(name: string): WorldPlace | undefined;
|
|
24
|
+
/** The instance's own thread/topic (or ClassicBot channel) id. */
|
|
25
|
+
topic(name: string): string | undefined;
|
|
26
|
+
}
|
|
27
|
+
export interface OrgThread {
|
|
28
|
+
platform: "discord" | "telegram";
|
|
29
|
+
url: string;
|
|
30
|
+
}
|
|
31
|
+
export interface OrgInstance {
|
|
32
|
+
description?: string;
|
|
33
|
+
tags?: string[];
|
|
34
|
+
general?: true;
|
|
35
|
+
classic?: true;
|
|
36
|
+
thread?: OrgThread;
|
|
37
|
+
}
|
|
38
|
+
export interface OrgTeam {
|
|
39
|
+
name: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
/** As configured, in order; a name the fleet no longer has is kept (the page shows it as missing). */
|
|
42
|
+
members: string[];
|
|
43
|
+
}
|
|
44
|
+
export interface OrgChart {
|
|
45
|
+
/** General instances (one per world at most, in practice): the top of the chart. */
|
|
46
|
+
general: string[];
|
|
47
|
+
teams: OrgTeam[];
|
|
48
|
+
instances: Record<string, OrgInstance>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A browser link to an instance's thread: Discord's channel URL, Telegram's topic link. Undefined when the place or
|
|
52
|
+
* the id is not one a link can be built from (Telegram's General topic, 1, has no topic link).
|
|
53
|
+
*/
|
|
54
|
+
export declare function threadUrl(place: WorldPlace | undefined, topicId: string | undefined): OrgThread | undefined;
|
|
55
|
+
export declare function buildOrgChart(input: OrgInput): OrgChart;
|
package/dist/web-org.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const SNOWFLAKE = /^\d{5,25}$/;
|
|
2
|
+
const MAX_DESCRIPTION = 500;
|
|
3
|
+
const MAX_TAGS = 12;
|
|
4
|
+
/**
|
|
5
|
+
* A browser link to an instance's thread: Discord's channel URL, Telegram's topic link. Undefined when the place or
|
|
6
|
+
* the id is not one a link can be built from (Telegram's General topic, 1, has no topic link).
|
|
7
|
+
*/
|
|
8
|
+
export function threadUrl(place, topicId) {
|
|
9
|
+
if (!place || !topicId)
|
|
10
|
+
return undefined;
|
|
11
|
+
if (place.type === "discord") {
|
|
12
|
+
if (!place.groupId || !SNOWFLAKE.test(place.groupId) || !SNOWFLAKE.test(topicId))
|
|
13
|
+
return undefined;
|
|
14
|
+
return { platform: "discord", url: `https://discord.com/channels/${place.groupId}/${topicId}` };
|
|
15
|
+
}
|
|
16
|
+
if (place.type === "telegram") {
|
|
17
|
+
const m = /^-100(\d+)$/.exec(place.groupId ?? "");
|
|
18
|
+
if (!m || !/^\d+$/.test(topicId) || topicId === "1")
|
|
19
|
+
return undefined;
|
|
20
|
+
return { platform: "telegram", url: `https://t.me/c/${m[1]}/${topicId}` };
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
const text = (v) => {
|
|
25
|
+
if (typeof v !== "string")
|
|
26
|
+
return undefined;
|
|
27
|
+
const s = v.trim();
|
|
28
|
+
return s ? (s.length > MAX_DESCRIPTION ? `${s.slice(0, MAX_DESCRIPTION - 1)}…` : s) : undefined;
|
|
29
|
+
};
|
|
30
|
+
export function buildOrgChart(input) {
|
|
31
|
+
// Keyed by instance name, and any name the validators accept is a key — "__proto__" included (#1467 review): a
|
|
32
|
+
// prototype-less map makes every assignment an own property, so JSON carries it like any other name.
|
|
33
|
+
const instances = Object.create(null);
|
|
34
|
+
const general = [];
|
|
35
|
+
for (const name of input.names) {
|
|
36
|
+
const cfg = input.instances[name];
|
|
37
|
+
const entry = {};
|
|
38
|
+
const description = text(cfg?.description);
|
|
39
|
+
if (description)
|
|
40
|
+
entry.description = description;
|
|
41
|
+
if (Array.isArray(cfg?.tags)) {
|
|
42
|
+
const tags = cfg.tags.filter((t) => typeof t === "string" && t.trim() !== "").map(t => t.trim()).slice(0, MAX_TAGS);
|
|
43
|
+
if (tags.length)
|
|
44
|
+
entry.tags = tags;
|
|
45
|
+
}
|
|
46
|
+
if (input.isGeneral(name)) {
|
|
47
|
+
entry.general = true;
|
|
48
|
+
general.push(name);
|
|
49
|
+
}
|
|
50
|
+
if (input.isClassic(name))
|
|
51
|
+
entry.classic = true;
|
|
52
|
+
const thread = threadUrl(input.place(name), input.topic(name));
|
|
53
|
+
if (thread)
|
|
54
|
+
entry.thread = thread;
|
|
55
|
+
instances[name] = entry;
|
|
56
|
+
}
|
|
57
|
+
const teams = [];
|
|
58
|
+
for (const [name, team] of Object.entries(input.teams ?? {})) {
|
|
59
|
+
if (!team)
|
|
60
|
+
continue;
|
|
61
|
+
const members = Array.isArray(team.members) ? [...new Set(team.members.filter((m) => typeof m === "string" && m !== ""))] : [];
|
|
62
|
+
const description = text(team.description);
|
|
63
|
+
teams.push({ name, ...(description ? { description } : {}), members });
|
|
64
|
+
}
|
|
65
|
+
return { general, teams, instances };
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=web-org.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-org.js","sourceRoot":"","sources":["../src/web-org.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { type Server } from "node:http";
|
|
2
|
+
import { type HostGuardConfig } from "./web-host-guard.js";
|
|
3
|
+
/** The most HTML a preview takes (UTF-8 bytes): the parent refuses more before sending, the shim refuses it too. */
|
|
4
|
+
export declare const PREVIEW_HTML_MAX_BYTES: number;
|
|
5
|
+
export interface PreviewWebConfig {
|
|
6
|
+
preview?: unknown;
|
|
7
|
+
preview_port?: unknown;
|
|
8
|
+
preview_origin?: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface PreviewSettings {
|
|
11
|
+
/** `web.preview` (default true): false means no listener, and cards show Source / Download only. */
|
|
12
|
+
enabled: boolean;
|
|
13
|
+
/** The preview listener's port, on 127.0.0.1; null when there is no valid one (health_port 65535 and no preview_port). */
|
|
14
|
+
port: number | null;
|
|
15
|
+
/** `web.preview_origin`, normalised to scheme://host[:port], or null. */
|
|
16
|
+
origin: string | null;
|
|
17
|
+
}
|
|
18
|
+
/** The settings a fleet runs with: `web.preview`, `web.preview_port` (default health_port + 1), `web.preview_origin`. */
|
|
19
|
+
export declare function previewSettings(web: PreviewWebConfig | null | undefined, healthPort: number): PreviewSettings;
|
|
20
|
+
/** scheme://host[:port] for an http(s) URL with nothing else in it; null otherwise. A default port is dropped. */
|
|
21
|
+
export declare function normalizeOrigin(value: string): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Why a `web.preview_origin` cannot be used, or null when it can (design §3.1). It must be a bare http(s) origin
|
|
24
|
+
* (no path, query, credentials or wildcard), and its host must not be one the dashboard answers to — that would be
|
|
25
|
+
* a same-origin preview by another name.
|
|
26
|
+
*/
|
|
27
|
+
export declare function previewOriginProblem(value: unknown, config: HostGuardConfig | null | undefined): string | null;
|
|
28
|
+
export interface PreviewAvailability {
|
|
29
|
+
/** The origin the server believes the browser is at (from Host and the TLS signal), or null. */
|
|
30
|
+
dashboardOrigin: string | null;
|
|
31
|
+
/** The preview origin for this load, or null: preview disabled for it. */
|
|
32
|
+
previewOrigin: string | null;
|
|
33
|
+
/** Why it is disabled, when it is. */
|
|
34
|
+
reason: string | null;
|
|
35
|
+
}
|
|
36
|
+
/** scheme://host[:port] from a Host header the host guard already accepted; a default port is dropped. */
|
|
37
|
+
export declare function dashboardOriginFor(hostHeader: string | undefined, secure: boolean): string | null;
|
|
38
|
+
/**
|
|
39
|
+
* For one /ui request: a loopback Host and no preview_origin → the same loopback name on the preview port;
|
|
40
|
+
* preview_origin set → that; anything else (a tunnel, a proxy, `hostname`, `allowed_hosts`) → disabled.
|
|
41
|
+
* Only Host is read; X-Forwarded-Host never is.
|
|
42
|
+
*/
|
|
43
|
+
export declare function previewAvailability(settings: PreviewSettings | null, hostHeader: string | undefined, secure: boolean, accepted?: readonly string[]): PreviewAvailability;
|
|
44
|
+
/**
|
|
45
|
+
* The exact dashboard origins a preview may be framed by, and accept HTML from (design §5.1): the loopback names on
|
|
46
|
+
* the dashboard's port, plus — when a preview_origin is set — `https://<name>` for each name the dashboard answers to
|
|
47
|
+
* besides loopback (`hostname`, `web.allowed_hosts`). Never 'self', never *.
|
|
48
|
+
*/
|
|
49
|
+
export declare function dashboardOrigins(settings: PreviewSettings, healthPort: number, config: HostGuardConfig | null | undefined): string[];
|
|
50
|
+
/** The /frame response's Content-Security-Policy (design §5.1): exact, no 'self', no *. */
|
|
51
|
+
export declare function previewFrameCsp(ancestors: readonly string[]): string;
|
|
52
|
+
/**
|
|
53
|
+
* The shim (design §4): posts `ready` with the listener's boot id; takes ONE `render` — from its parent window, from
|
|
54
|
+
* an exact listed dashboard origin (never "null"), with exactly {v, type, ch, html}, html ≤ 1 MiB — removes the WebRTC
|
|
55
|
+
* entry points from its realm (defence in depth only), and writes a heartbeat/height prologue and the HTML into its
|
|
56
|
+
* own document. Every later message is ignored.
|
|
57
|
+
*/
|
|
58
|
+
export declare function buildShim(bootId: string, origins: readonly string[]): string;
|
|
59
|
+
export interface PreviewListener {
|
|
60
|
+
readonly server: Server;
|
|
61
|
+
readonly bootId: string;
|
|
62
|
+
readonly settings: PreviewSettings;
|
|
63
|
+
/** The dashboard origins the shim accepts HTML from and may be framed by. */
|
|
64
|
+
readonly origins: readonly string[];
|
|
65
|
+
close(): void;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The preview listener: GET /frame (no query) → the shim with the §5.1 headers; anything else → empty 404; a Host
|
|
69
|
+
* it does not answer to → empty 403. No X-Frame-Options (it must be framable by the dashboard), no cookie or
|
|
70
|
+
* header parsing beyond Host, nothing logged per request. Built once: the shim and its headers never change.
|
|
71
|
+
*/
|
|
72
|
+
export declare function createPreviewListener(opts: {
|
|
73
|
+
settings: PreviewSettings;
|
|
74
|
+
healthPort: number;
|
|
75
|
+
config: HostGuardConfig | null | undefined;
|
|
76
|
+
bootId?: string;
|
|
77
|
+
}): PreviewListener;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #1306: the preview listener — where an agent's HTML may run, away from the dashboard's authority.
|
|
3
|
+
* Design: docs/design/1306-inline-html-preview.md (r3, approved).
|
|
4
|
+
*
|
|
5
|
+
* A second loopback listener (default health_port + 1) serves one static document, `GET /frame`: a shim that
|
|
6
|
+
* receives HTML by postMessage only from the dashboard origins it was built with, and writes it into its own
|
|
7
|
+
* opaque, sandboxed document. Everything else is an empty 404. It reads no cookie, no header but Host, and logs
|
|
8
|
+
* nothing about a request. The dashboard decides per /ui load whether a preview origin is reachable from the
|
|
9
|
+
* browser's address (Host only — never X-Forwarded-Host), and the page re-checks that against location.origin.
|
|
10
|
+
*
|
|
11
|
+
* What this does NOT promise: that a preview cannot send data out (WebRTC and self-navigation are known ways
|
|
12
|
+
* around the restrictions — design §4.3). It promises the account boundary only (G1): the preview cannot act as
|
|
13
|
+
* the signed-in person.
|
|
14
|
+
*/
|
|
15
|
+
import { randomBytes } from "node:crypto";
|
|
16
|
+
import { createServer } from "node:http";
|
|
17
|
+
import { allowedHostNames, hostnameOf, LOOPBACK_HOST_NAMES } from "./web-host-guard.js";
|
|
18
|
+
/** The most HTML a preview takes (UTF-8 bytes): the parent refuses more before sending, the shim refuses it too. */
|
|
19
|
+
export const PREVIEW_HTML_MAX_BYTES = 1024 * 1024;
|
|
20
|
+
/** The settings a fleet runs with: `web.preview`, `web.preview_port` (default health_port + 1), `web.preview_origin`. */
|
|
21
|
+
export function previewSettings(web, healthPort) {
|
|
22
|
+
// An ephemeral web listener (port 0: tests, harnesses) gets an ephemeral preview listener; its real port is filled
|
|
23
|
+
// in once it listens (createPreviewListener). Otherwise the fixed default, so an SSH user can forward it too.
|
|
24
|
+
const port = typeof web?.preview_port === "number" && Number.isInteger(web.preview_port) ? web.preview_port
|
|
25
|
+
: healthPort === 0 ? 0 : healthPort + 1 <= 65535 ? healthPort + 1 : null;
|
|
26
|
+
const origin = typeof web?.preview_origin === "string" ? normalizeOrigin(web.preview_origin) : null;
|
|
27
|
+
return { enabled: web?.preview !== false, port, origin };
|
|
28
|
+
}
|
|
29
|
+
/** scheme://host[:port] for an http(s) URL with nothing else in it; null otherwise. A default port is dropped. */
|
|
30
|
+
export function normalizeOrigin(value) {
|
|
31
|
+
let u;
|
|
32
|
+
try {
|
|
33
|
+
u = new URL(value.trim());
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
if (u.protocol !== "http:" && u.protocol !== "https:")
|
|
39
|
+
return null;
|
|
40
|
+
if (u.username || u.password || u.search || u.hash || (u.pathname !== "/" && u.pathname !== ""))
|
|
41
|
+
return null;
|
|
42
|
+
if (!u.hostname || u.hostname.includes("*"))
|
|
43
|
+
return null;
|
|
44
|
+
// The raw text must not carry a query/fragment marker either ("https://x/?" parses to an empty search).
|
|
45
|
+
if (/[?#]/.test(value))
|
|
46
|
+
return null;
|
|
47
|
+
return u.origin;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Why a `web.preview_origin` cannot be used, or null when it can (design §3.1). It must be a bare http(s) origin
|
|
51
|
+
* (no path, query, credentials or wildcard), and its host must not be one the dashboard answers to — that would be
|
|
52
|
+
* a same-origin preview by another name.
|
|
53
|
+
*/
|
|
54
|
+
export function previewOriginProblem(value, config) {
|
|
55
|
+
if (typeof value !== "string")
|
|
56
|
+
return "must be an origin such as https://preview.example.net";
|
|
57
|
+
const origin = normalizeOrigin(value);
|
|
58
|
+
if (!origin)
|
|
59
|
+
return "must be a bare http(s) origin — scheme, host and optional port; no path, query, fragment, credentials or wildcard";
|
|
60
|
+
const host = hostnameOf(new URL(origin).host);
|
|
61
|
+
if (!host || allowedHostNames(config).has(host))
|
|
62
|
+
return "must be a separate host name, not one the dashboard answers to (loopback names, hostname, web.allowed_hosts)";
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const DEFAULT_PORT = { "http:": "80", "https:": "443" };
|
|
66
|
+
/** scheme://host[:port] from a Host header the host guard already accepted; a default port is dropped. */
|
|
67
|
+
export function dashboardOriginFor(hostHeader, secure) {
|
|
68
|
+
if (typeof hostHeader !== "string" || hostnameOf(hostHeader) === null)
|
|
69
|
+
return null;
|
|
70
|
+
const scheme = secure ? "https:" : "http:";
|
|
71
|
+
try {
|
|
72
|
+
return new URL(`${scheme}//${hostHeader.trim().toLowerCase()}`).origin;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
// (URL drops a default port: http://x:80 → http://x, so it matches what the browser reports as location.origin.)
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* For one /ui request: a loopback Host and no preview_origin → the same loopback name on the preview port;
|
|
81
|
+
* preview_origin set → that; anything else (a tunnel, a proxy, `hostname`, `allowed_hosts`) → disabled.
|
|
82
|
+
* Only Host is read; X-Forwarded-Host never is.
|
|
83
|
+
*/
|
|
84
|
+
export function previewAvailability(settings, hostHeader, secure, accepted) {
|
|
85
|
+
const dashboardOrigin = dashboardOriginFor(hostHeader, secure);
|
|
86
|
+
const off = (reason) => ({ dashboardOrigin, previewOrigin: null, reason });
|
|
87
|
+
if (!settings || !settings.enabled)
|
|
88
|
+
return off("Previews are turned off for this fleet (web.preview: false).");
|
|
89
|
+
if (!dashboardOrigin)
|
|
90
|
+
return off("This address cannot be checked.");
|
|
91
|
+
if (settings.port === null && !settings.origin)
|
|
92
|
+
return off("Previews have no port: set web.preview_port (health_port is the highest port).");
|
|
93
|
+
// The page must be at an origin the shim takes HTML from (and that may frame it) — exactly, or a frame would never
|
|
94
|
+
// render. A name in web.allowed_hosts served on a non-default port must be listed with that port.
|
|
95
|
+
if (accepted && !accepted.includes(dashboardOrigin))
|
|
96
|
+
return off(`Previews are not offered at ${dashboardOrigin}: list this address (with its port) in web.allowed_hosts, and set web.preview_origin.`);
|
|
97
|
+
let preview;
|
|
98
|
+
if (settings.origin)
|
|
99
|
+
preview = settings.origin;
|
|
100
|
+
else {
|
|
101
|
+
const host = hostnameOf(hostHeader);
|
|
102
|
+
if (!LOOPBACK_HOST_NAMES.includes(host))
|
|
103
|
+
return off("Previews need web.preview_origin when the dashboard is reached through a tunnel or proxy.");
|
|
104
|
+
preview = `http://${host}:${settings.port}`;
|
|
105
|
+
}
|
|
106
|
+
// An https page cannot frame an http preview (mixed content).
|
|
107
|
+
if (dashboardOrigin.startsWith("https:") && preview.startsWith("http:"))
|
|
108
|
+
return off("Previews need an https web.preview_origin when the dashboard is reached over https.");
|
|
109
|
+
if (preview === dashboardOrigin)
|
|
110
|
+
return off("The preview origin cannot be the dashboard's own.");
|
|
111
|
+
return { dashboardOrigin, previewOrigin: preview, reason: null };
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The exact dashboard origins a preview may be framed by, and accept HTML from (design §5.1): the loopback names on
|
|
115
|
+
* the dashboard's port, plus — when a preview_origin is set — `https://<name>` for each name the dashboard answers to
|
|
116
|
+
* besides loopback (`hostname`, `web.allowed_hosts`). Never 'self', never *.
|
|
117
|
+
*/
|
|
118
|
+
export function dashboardOrigins(settings, healthPort, config) {
|
|
119
|
+
// As the browser writes location.origin: a default port is dropped (http://127.0.0.1:80 → http://127.0.0.1).
|
|
120
|
+
const origin = (u) => { try {
|
|
121
|
+
return new URL(u).origin;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return null;
|
|
125
|
+
} };
|
|
126
|
+
const out = LOOPBACK_HOST_NAMES.map(h => origin(`http://${h}:${healthPort}`)).filter((o) => !!o);
|
|
127
|
+
if (settings.origin) {
|
|
128
|
+
// Each extra dashboard name as written — with its port when it has one (fleet.example.net:8443).
|
|
129
|
+
const extra = [config?.hostname, ...(Array.isArray(config?.web?.allowed_hosts) ? config.web.allowed_hosts : [])];
|
|
130
|
+
for (const entry of extra) {
|
|
131
|
+
if (typeof entry !== "string")
|
|
132
|
+
continue;
|
|
133
|
+
const name = hostnameOf(entry);
|
|
134
|
+
if (!name || LOOPBACK_HOST_NAMES.includes(name))
|
|
135
|
+
continue;
|
|
136
|
+
const o = origin(`https://${entry.trim().toLowerCase()}`);
|
|
137
|
+
if (o)
|
|
138
|
+
out.push(o);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return [...new Set(out)];
|
|
142
|
+
}
|
|
143
|
+
// ── The listener ────────────────────────────────────────────────────────────────────────────────────────────
|
|
144
|
+
const PERMISSIONS_POLICY = "camera=(), microphone=(), geolocation=(), clipboard-read=(), clipboard-write=(), usb=(), serial=(), hid=(), bluetooth=(), payment=(), display-capture=(), fullscreen=()";
|
|
145
|
+
/** The /frame response's Content-Security-Policy (design §5.1): exact, no 'self', no *. */
|
|
146
|
+
export function previewFrameCsp(ancestors) {
|
|
147
|
+
return [
|
|
148
|
+
"default-src 'none'", "script-src 'unsafe-inline'", "style-src 'unsafe-inline'",
|
|
149
|
+
"img-src data: blob:", "font-src data:", "media-src data: blob:",
|
|
150
|
+
"connect-src 'none'", "worker-src 'none'", "frame-src 'none'", "child-src 'none'",
|
|
151
|
+
"form-action 'none'", "base-uri 'none'", "manifest-src 'none'", "object-src 'none'",
|
|
152
|
+
"webrtc 'block'",
|
|
153
|
+
`frame-ancestors ${ancestors.join(" ")}`,
|
|
154
|
+
"sandbox allow-scripts",
|
|
155
|
+
].join("; ");
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The shim (design §4): posts `ready` with the listener's boot id; takes ONE `render` — from its parent window, from
|
|
159
|
+
* an exact listed dashboard origin (never "null"), with exactly {v, type, ch, html}, html ≤ 1 MiB — removes the WebRTC
|
|
160
|
+
* entry points from its realm (defence in depth only), and writes a heartbeat/height prologue and the HTML into its
|
|
161
|
+
* own document. Every later message is ignored.
|
|
162
|
+
*/
|
|
163
|
+
export function buildShim(bootId, origins) {
|
|
164
|
+
const script = `(function () {
|
|
165
|
+
"use strict";
|
|
166
|
+
var BOOT = ${JSON.stringify(bootId)}, ORIGINS = ${JSON.stringify([...origins])}, MAX = ${PREVIEW_HTML_MAX_BYTES};
|
|
167
|
+
var done = false;
|
|
168
|
+
function bytes(s) { try { return new TextEncoder().encode(s).length; } catch (e) { return s.length * 3; } }
|
|
169
|
+
window.addEventListener("message", function (e) {
|
|
170
|
+
if (done || e.source !== window.parent || ORIGINS.indexOf(e.origin) < 0) return;
|
|
171
|
+
var d = e.data;
|
|
172
|
+
if (!d || typeof d !== "object" || Object.getPrototypeOf(d) !== Object.prototype) return;
|
|
173
|
+
if (Object.keys(d).sort().join(",") !== "ch,html,type,v" || d.v !== 1 || d.type !== "render") return;
|
|
174
|
+
if (typeof d.ch !== "string" || !/^[0-9a-f]{16,64}$/.test(d.ch) || typeof d.html !== "string" || bytes(d.html) > MAX) return;
|
|
175
|
+
done = true;
|
|
176
|
+
["RTCPeerConnection", "webkitRTCPeerConnection", "RTCDataChannel", "RTCIceTransport", "RTCSctpTransport"].forEach(function (k) {
|
|
177
|
+
try { delete window[k]; } catch (x) { /* not configurable */ }
|
|
178
|
+
try { if (window[k]) Object.defineProperty(window, k, { value: undefined, configurable: false, writable: false }); } catch (x) { /* best effort */ }
|
|
179
|
+
});
|
|
180
|
+
var prologue = "<script>(function(){var ch=" + JSON.stringify(d.ch) + ",p=window.parent,last=-1;" +
|
|
181
|
+
"function size(){var h=Math.ceil(Math.max(document.documentElement?document.documentElement.scrollHeight:0,document.body?document.body.scrollHeight:0));" +
|
|
182
|
+
"if(h!==last){last=h;p.postMessage({v:1,type:\\"resize\\",ch:ch,height:h},\\"*\\");}}" +
|
|
183
|
+
"setInterval(function(){p.postMessage({v:1,type:\\"heartbeat\\",ch:ch},\\"*\\");},2000);" +
|
|
184
|
+
"p.postMessage({v:1,type:\\"heartbeat\\",ch:ch},\\"*\\");" +
|
|
185
|
+
"try{new ResizeObserver(size).observe(document.documentElement);}catch(e){setInterval(size,500);}" +
|
|
186
|
+
"addEventListener(\\"load\\",size);setTimeout(size,0);})();<\\/script>";
|
|
187
|
+
document.open();
|
|
188
|
+
document.write(prologue + d.html);
|
|
189
|
+
document.close();
|
|
190
|
+
});
|
|
191
|
+
window.parent.postMessage({ v: 1, type: "ready", ch: null, boot: BOOT }, "*");
|
|
192
|
+
})();`;
|
|
193
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>Preview</title></head><body><script>${script}</script></body></html>`;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* The preview listener: GET /frame (no query) → the shim with the §5.1 headers; anything else → empty 404; a Host
|
|
197
|
+
* it does not answer to → empty 403. No X-Frame-Options (it must be framable by the dashboard), no cookie or
|
|
198
|
+
* header parsing beyond Host, nothing logged per request. Built once: the shim and its headers never change.
|
|
199
|
+
*/
|
|
200
|
+
export function createPreviewListener(opts) {
|
|
201
|
+
const bootId = opts.bootId ?? randomBytes(16).toString("hex");
|
|
202
|
+
const origins = dashboardOrigins(opts.settings, opts.healthPort, opts.config);
|
|
203
|
+
const body = Buffer.from(buildShim(bootId, origins), "utf8");
|
|
204
|
+
const headers = {
|
|
205
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
206
|
+
"Content-Security-Policy": previewFrameCsp(origins),
|
|
207
|
+
"Permissions-Policy": PERMISSIONS_POLICY,
|
|
208
|
+
"Referrer-Policy": "no-referrer",
|
|
209
|
+
"Cross-Origin-Resource-Policy": "same-origin",
|
|
210
|
+
"X-Content-Type-Options": "nosniff",
|
|
211
|
+
"Cache-Control": "no-store",
|
|
212
|
+
"Content-Length": String(body.length),
|
|
213
|
+
};
|
|
214
|
+
const hosts = new Set(LOOPBACK_HOST_NAMES);
|
|
215
|
+
if (opts.settings.origin) {
|
|
216
|
+
const h = hostnameOf(new URL(opts.settings.origin).host);
|
|
217
|
+
if (h)
|
|
218
|
+
hosts.add(h);
|
|
219
|
+
}
|
|
220
|
+
const empty = (res, status) => {
|
|
221
|
+
res.writeHead(status, { "Content-Length": "0", "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff", "Referrer-Policy": "no-referrer" });
|
|
222
|
+
res.end();
|
|
223
|
+
};
|
|
224
|
+
const server = createServer((req, res) => {
|
|
225
|
+
// Host only. The cookie a same-host port receives (cookies are not port-isolated) is never read.
|
|
226
|
+
const host = typeof req.headers.host === "string" ? hostnameOf(req.headers.host) : null;
|
|
227
|
+
if (!host || !hosts.has(host))
|
|
228
|
+
return empty(res, 403);
|
|
229
|
+
if (req.method !== "GET" || req.url !== "/frame")
|
|
230
|
+
return empty(res, 404);
|
|
231
|
+
res.writeHead(200, headers);
|
|
232
|
+
res.end(body);
|
|
233
|
+
});
|
|
234
|
+
const settings = { ...opts.settings };
|
|
235
|
+
// A listener on port 0 learns its real port when it binds: that is the one a /ui load must be offered.
|
|
236
|
+
server.on("listening", () => { const a = server.address(); if (a && typeof a === "object")
|
|
237
|
+
settings.port = a.port; });
|
|
238
|
+
return { server, bootId, settings, origins, close: () => { server.close(); } };
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=web-preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-preview.js","sourceRoot":"","sources":["../src/web-preview.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Listener-owned provenance. HTTP headers can never select a public surface. */
|
|
2
|
+
export interface GatewayRequestContext {
|
|
3
|
+
readonly surface: "gateway";
|
|
4
|
+
readonly exposureId: string;
|
|
5
|
+
readonly expectedOrigin: string;
|
|
6
|
+
readonly isCurrent: () => boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function bindGatewayRequest(req: object, context: GatewayRequestContext): void;
|
|
9
|
+
export declare function gatewayRequestContext(req: object): GatewayRequestContext | undefined;
|
|
10
|
+
export declare function isWebRequestCurrent(req: object): boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const contexts = new WeakMap();
|
|
2
|
+
export function bindGatewayRequest(req, context) {
|
|
3
|
+
if (contexts.has(req))
|
|
4
|
+
throw new Error("request context already bound");
|
|
5
|
+
contexts.set(req, Object.freeze({ ...context }));
|
|
6
|
+
}
|
|
7
|
+
export function gatewayRequestContext(req) {
|
|
8
|
+
return contexts.get(req);
|
|
9
|
+
}
|
|
10
|
+
export function isWebRequestCurrent(req) {
|
|
11
|
+
return contexts.get(req)?.isCurrent() ?? true;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=web-request-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"web-request-context.js","sourceRoot":"","sources":["../src/web-request-context.ts"],"names":[],"mappings":"AAOA,MAAM,QAAQ,GAAG,IAAI,OAAO,EAAiC,CAAC;AAC9D,MAAM,UAAU,kBAAkB,CAAC,GAAW,EAAE,OAA8B;IAC5E,IAAI,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACxE,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,CAAC;AACnD,CAAC;AACD,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,OAAO,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAC3B,CAAC;AACD,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC7C,OAAO,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,IAAI,IAAI,CAAC;AAChD,CAAC"}
|