@xopcai/xopc 0.0.83 → 0.0.85
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 +2 -0
- package/README.zh-CN.md +3 -1
- package/dist/browser-ext/manifest.json +1 -1
- package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
- package/dist/extensions/feishu/src/plugin.d.ts +2 -0
- package/dist/extensions/feishu/src/plugin.js +10 -0
- package/dist/extensions/feishu/src/plugin.js.map +1 -1
- package/dist/extensions/feishu/src/workflow-progress.d.ts +27 -0
- package/dist/extensions/feishu/src/workflow-progress.js +99 -0
- package/dist/extensions/feishu/src/workflow-progress.js.map +1 -0
- package/dist/extensions/telegram/src/plugin.d.ts +2 -0
- package/dist/extensions/telegram/src/plugin.js +11 -1
- package/dist/extensions/telegram/src/plugin.js.map +1 -1
- package/dist/extensions/telegram/src/routing-integration.js +2 -2
- package/dist/extensions/telegram/src/workflow-progress.d.ts +24 -0
- package/dist/extensions/telegram/src/workflow-progress.js +73 -0
- package/dist/extensions/telegram/src/workflow-progress.js.map +1 -0
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js +158 -0
- package/dist/extensions/weixin/src/__tests__/workflow-progress.test.js.map +1 -0
- package/dist/extensions/weixin/src/api/api.js +2 -2
- package/dist/extensions/weixin/src/auth/accounts.js +1 -1
- package/dist/extensions/weixin/src/cdn/upload.js +1 -1
- package/dist/extensions/weixin/src/media/data-url.js +1 -1
- package/dist/extensions/weixin/src/messaging/debug-mode.js +1 -1
- package/dist/extensions/weixin/src/messaging/inbound.js +1 -1
- package/dist/extensions/weixin/src/messaging/process-message.js +1 -1
- package/dist/extensions/weixin/src/plugin.d.ts +2 -0
- package/dist/extensions/weixin/src/plugin.js +11 -1
- package/dist/extensions/weixin/src/plugin.js.map +1 -1
- package/dist/extensions/weixin/src/storage/sync-buf.js +1 -1
- package/dist/extensions/weixin/src/workflow-progress.d.ts +26 -0
- package/dist/extensions/weixin/src/workflow-progress.js +99 -0
- package/dist/extensions/weixin/src/workflow-progress.js.map +1 -0
- package/dist/gateway/static/root/assets/agents-D3_-kNlZ.js +222 -0
- package/dist/gateway/static/root/assets/apps-page-D7v7649T.js +1 -0
- package/dist/gateway/static/root/assets/channels-settings-nCaMb0a7.js +1 -0
- package/dist/gateway/static/root/assets/channels-status-swr-C1gZBcJV.js +8 -0
- package/dist/gateway/static/root/assets/createLucideIcon-DPHK1VkS.js +1 -0
- package/dist/gateway/static/root/assets/cron-api-CoYK0hlm.js +1 -0
- package/dist/gateway/static/root/assets/cron-page-DeGo-Vjc.js +1 -0
- package/dist/gateway/static/root/assets/dist-BTWC-BTN.js +45 -0
- package/dist/gateway/static/root/assets/{dist-BpQxde0t.js → dist-DaK4dsss.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-CY27wj_p.js → extension-debug-page-BZngZWbO.js} +1 -1
- package/dist/gateway/static/root/assets/extension-page-D6JSyV27.js +1 -0
- package/dist/gateway/static/root/assets/extension-settings-page-8PZcmWI7.js +1 -0
- package/dist/gateway/static/root/assets/fetch-B2MYHbWg.js +1 -0
- package/dist/gateway/static/root/assets/{field-primitives-fa_hiQcX.js → field-primitives-Zzl22MvN.js} +1 -1
- package/dist/gateway/static/root/assets/heartbeat-config-api-BtIcpG0O.js +1 -0
- package/dist/gateway/static/root/assets/index-D4vM3-P7.js +4700 -0
- package/dist/gateway/static/root/assets/index-ew_2L2We.css +1 -0
- package/dist/gateway/static/root/assets/logs-page-_d4UJ-qQ.js +1 -0
- package/dist/gateway/static/root/assets/sessions-page-5N4aF2Wk.js +1 -0
- package/dist/gateway/static/root/assets/settings-form-section-D_tgb8r2.js +1 -0
- package/dist/gateway/static/root/assets/settings-page-C18xBt4X.js +3 -0
- package/dist/gateway/static/root/assets/share-preview-page-D4EG_vM1.js +2 -0
- package/dist/gateway/static/root/assets/skills-page-sPAXhh8w.js +2 -0
- package/dist/gateway/static/root/assets/theme-store-DryYl3qD.js +1 -0
- package/dist/gateway/static/root/assets/url-BwNL6Rgk.js +3 -0
- package/dist/gateway/static/root/assets/utils-CYO9eTCM.js +1 -0
- package/dist/gateway/static/root/assets/voice-api-key-field-Ds51havm.js +1 -0
- package/dist/gateway/static/root/index.html +7 -6
- package/dist/package.js +1 -1
- package/dist/src/agent/agent-manager.js +7 -7
- package/dist/src/agent/bootstrap/load-bootstrap-files.js +1 -1
- package/dist/src/agent/context/workspace-seed.js +3 -3
- package/dist/src/agent/embedded/map-stream-events.js +6 -0
- package/dist/src/agent/embedded/map-stream-events.js.map +1 -1
- package/dist/src/agent/embedded/subscribe-session.js +24 -0
- package/dist/src/agent/embedded/subscribe-session.js.map +1 -1
- package/dist/src/agent/embedded/types.d.ts +19 -0
- package/dist/src/agent/goals/goal-locale.js +2 -2
- package/dist/src/agent/goals/goal-run-store.js +4 -4
- package/dist/src/agent/goals/persistent-goal-service.js +1 -1
- package/dist/src/agent/goals/post-turn.js +2 -2
- package/dist/src/agent/image/load-image-media.js +2 -2
- package/dist/src/agent/ipc/bus.js +1 -1
- package/dist/src/agent/ipc/inbox.js +2 -2
- package/dist/src/agent/ipc/socket.js +1 -1
- package/dist/src/agent/memory/builtin-memory-store.js +1 -1
- package/dist/src/agent/memory/dreaming/deep-promotion.js +1 -1
- package/dist/src/agent/memory/dreaming/events.js +1 -1
- package/dist/src/agent/memory/dreaming/last-run.js +1 -1
- package/dist/src/agent/memory/dreaming/light-sweep.js +1 -1
- package/dist/src/agent/memory/dreaming/preview.js +1 -1
- package/dist/src/agent/memory/dreaming/rem-patterns.js +1 -1
- package/dist/src/agent/memory/dreaming/short-term-store.js +1 -1
- package/dist/src/agent/memory/dreaming/utils.js +1 -1
- package/dist/src/agent/memory/plugin-discovery.js +1 -1
- package/dist/src/agent/models/manager.js +1 -1
- package/dist/src/agent/prompt/service-prompt-builder.js +2 -2
- package/dist/src/agent/reply/post-compaction-context.js +1 -1
- package/dist/src/agent/reply/startup-context.d.ts +3 -0
- package/dist/src/agent/reply/startup-context.js +25 -2
- package/dist/src/agent/reply/startup-context.js.map +1 -1
- package/dist/src/agent/reply/workspace-boundary-read.js +1 -1
- package/dist/src/agent/sandbox/path-policy.js +2 -2
- package/dist/src/agent/service/build-direct-message-content.js +1 -1
- package/dist/src/agent/service.d.ts +1 -0
- package/dist/src/agent/service.js +10 -4
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/session/session-inspector.js +1 -1
- package/dist/src/agent/skills/config.js +1 -1
- package/dist/src/agent/skills/hub-hash.js +2 -2
- package/dist/src/agent/skills/hub-lock.js +1 -1
- package/dist/src/agent/skills/hub-pull.js +3 -3
- package/dist/src/agent/skills/index.js +1 -1
- package/dist/src/agent/skills/managed-store.js +1 -1
- package/dist/src/agent/skills/scanner.js +1 -1
- package/dist/src/agent/skills/skill-manage-ops.js +1 -1
- package/dist/src/agent/skills/skill-manager.js +1 -1
- package/dist/src/agent/tools/create-share-tool.d.ts +27 -0
- package/dist/src/agent/tools/create-share-tool.js +237 -0
- package/dist/src/agent/tools/create-share-tool.js.map +1 -0
- package/dist/src/agent/tools/dreaming-tool.js +1 -1
- package/dist/src/agent/tools/factory.js +35 -1
- package/dist/src/agent/tools/factory.js.map +1 -1
- package/dist/src/agent/tools/image-generate-tool.js +1 -1
- package/dist/src/agent/tools/index.d.ts +2 -0
- package/dist/src/agent/tools/index.js +3 -1
- package/dist/src/agent/tools/send-media.js +1 -1
- package/dist/src/agent/tools/skill-manage-tool.js +1 -1
- package/dist/src/agent/tools/workflow-tool.d.ts +41 -0
- package/dist/src/agent/tools/workflow-tool.js +271 -0
- package/dist/src/agent/tools/workflow-tool.js.map +1 -0
- package/dist/src/agent/tools/write.js +1 -1
- package/dist/src/agent/workflow/builtins/audit-repo.d.ts +9 -0
- package/dist/src/agent/workflow/builtins/audit-repo.js +115 -0
- package/dist/src/agent/workflow/builtins/audit-repo.js.map +1 -0
- package/dist/src/agent/workflow/builtins/index.d.ts +15 -0
- package/dist/src/agent/workflow/builtins/index.js +28 -0
- package/dist/src/agent/workflow/builtins/index.js.map +1 -0
- package/dist/src/agent/workflow/builtins/multi-perspective-review.d.ts +9 -0
- package/dist/src/agent/workflow/builtins/multi-perspective-review.js +113 -0
- package/dist/src/agent/workflow/builtins/multi-perspective-review.js.map +1 -0
- package/dist/src/agent/workflow/builtins/research.d.ts +9 -0
- package/dist/src/agent/workflow/builtins/research.js +129 -0
- package/dist/src/agent/workflow/builtins/research.js.map +1 -0
- package/dist/src/agent/workflow/catalog.d.ts +51 -0
- package/dist/src/agent/workflow/catalog.js +155 -0
- package/dist/src/agent/workflow/catalog.js.map +1 -0
- package/dist/src/agent/workflow/channel-capability.d.ts +76 -0
- package/dist/src/agent/workflow/channel-capability.js +1 -0
- package/dist/src/agent/workflow/index.d.ts +11 -0
- package/dist/src/agent/workflow/index.js +10 -0
- package/dist/src/agent/workflow/last-run-memory.d.ts +42 -0
- package/dist/src/agent/workflow/last-run-memory.js +60 -0
- package/dist/src/agent/workflow/last-run-memory.js.map +1 -0
- package/dist/src/agent/workflow/parser.d.ts +20 -0
- package/dist/src/agent/workflow/parser.js +137 -0
- package/dist/src/agent/workflow/parser.js.map +1 -0
- package/dist/src/agent/workflow/progress-broker.d.ts +80 -0
- package/dist/src/agent/workflow/progress-broker.js +263 -0
- package/dist/src/agent/workflow/progress-broker.js.map +1 -0
- package/dist/src/agent/workflow/runtime.d.ts +31 -0
- package/dist/src/agent/workflow/runtime.js +301 -0
- package/dist/src/agent/workflow/runtime.js.map +1 -0
- package/dist/src/agent/workflow/snapshot.d.ts +18 -0
- package/dist/src/agent/workflow/snapshot.js +144 -0
- package/dist/src/agent/workflow/snapshot.js.map +1 -0
- package/dist/src/agent/workflow/structured-output-tool.d.ts +33 -0
- package/dist/src/agent/workflow/structured-output-tool.js +58 -0
- package/dist/src/agent/workflow/structured-output-tool.js.map +1 -0
- package/dist/src/agent/workflow/subagent-runner.d.ts +42 -0
- package/dist/src/agent/workflow/subagent-runner.js +104 -0
- package/dist/src/agent/workflow/subagent-runner.js.map +1 -0
- package/dist/src/agent/workflow/types.d.ts +137 -0
- package/dist/src/agent/workflow/types.js +1 -0
- package/dist/src/auth/credentials.js +3 -3
- package/dist/src/auth/profiles/store.js +1 -1
- package/dist/src/auth/sync-provider-auth.js +1 -1
- package/dist/src/browser/cache-dir-policy.js +1 -1
- package/dist/src/browser/cdp-local-launcher.js +2 -2
- package/dist/src/browser/providers/browser-ext-install.js +4 -4
- package/dist/src/browser/providers/cloakbrowser.js +4 -4
- package/dist/src/browser/providers/playwright-doctor.js +1 -1
- package/dist/src/browser/stealth.js +1 -1
- package/dist/src/channels/attachments/inbound-persist.js +1 -1
- package/dist/src/channels/attachments/outbound-tts-persist.js +1 -1
- package/dist/src/channels/outbound/persist-store.js +1 -1
- package/dist/src/channels/pairing/allow-from-file.js +1 -1
- package/dist/src/channels/pairing/pairing-store.js +2 -2
- package/dist/src/chat-commands/builtins/config.js +2 -2
- package/dist/src/chat-commands/builtins/model.js +40 -23
- package/dist/src/chat-commands/builtins/model.js.map +1 -1
- package/dist/src/chat-commands/builtins/system.js +30 -15
- package/dist/src/chat-commands/builtins/system.js.map +1 -1
- package/dist/src/chat-commands/builtins/workflow.d.ts +18 -0
- package/dist/src/chat-commands/builtins/workflow.js +167 -0
- package/dist/src/chat-commands/builtins/workflow.js.map +1 -0
- package/dist/src/chat-commands/context.js +1 -1
- package/dist/src/chat-commands/format-output.d.ts +28 -0
- package/dist/src/chat-commands/format-output.js +45 -0
- package/dist/src/chat-commands/format-output.js.map +1 -0
- package/dist/src/chat-commands/index.d.ts +1 -0
- package/dist/src/chat-commands/index.js +3 -1
- package/dist/src/chat-commands/index.js.map +1 -1
- package/dist/src/cli/command-catalog.js +110 -8
- package/dist/src/cli/command-catalog.js.map +1 -1
- package/dist/src/cli/command-loaders.js +2 -0
- package/dist/src/cli/command-loaders.js.map +1 -1
- package/dist/src/cli/command-manifest.js +9 -1
- package/dist/src/cli/command-manifest.js.map +1 -1
- package/dist/src/cli/commands/config.js +71 -20
- package/dist/src/cli/commands/config.js.map +1 -1
- package/dist/src/cli/commands/cron-cli.d.ts +2 -0
- package/dist/src/cli/commands/cron-cli.js +15 -0
- package/dist/src/cli/commands/cron-cli.js.map +1 -0
- package/dist/src/cli/commands/cron.d.ts +4 -1
- package/dist/src/cli/commands/cron.js +76 -41
- package/dist/src/cli/commands/cron.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/channel-config.js +1 -1
- package/dist/src/cli/commands/doctor/checks/channel-config.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/config-health.js +2 -2
- package/dist/src/cli/commands/doctor/checks/config-health.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/cron-health.js +1 -1
- package/dist/src/cli/commands/doctor/checks/cron-health.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/gateway-health.js +2 -2
- package/dist/src/cli/commands/doctor/checks/gateway-health.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/gateway-service.js +2 -2
- package/dist/src/cli/commands/doctor/checks/gateway-service.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
- package/dist/src/cli/commands/doctor/checks/session-integrity.js +1 -1
- package/dist/src/cli/commands/doctor/checks/state-integrity.js +2 -2
- package/dist/src/cli/commands/doctor/checks/state-integrity.js.map +1 -1
- package/dist/src/cli/commands/doctor/checks/workspace-status.js +4 -4
- package/dist/src/cli/commands/doctor/checks/workspace-status.js.map +1 -1
- package/dist/src/cli/commands/extension-dev.js +1 -1
- package/dist/src/cli/commands/extension-marketplace.js +1 -1
- package/dist/src/cli/commands/extension-pack.js +1 -1
- package/dist/src/cli/commands/gateway/index.d.ts +1 -1
- package/dist/src/cli/commands/gateway/index.js +2 -2
- package/dist/src/cli/commands/gateway/lifecycle.js +10 -4
- package/dist/src/cli/commands/gateway/lifecycle.js.map +1 -1
- package/dist/src/cli/commands/gateway/service.d.ts +4 -0
- package/dist/src/cli/commands/gateway/service.js +17 -2
- package/dist/src/cli/commands/gateway/service.js.map +1 -1
- package/dist/src/cli/commands/gateway/shared.js +1 -1
- package/dist/src/cli/commands/gateway/subcommands.js +1 -4
- package/dist/src/cli/commands/gateway/subcommands.js.map +1 -1
- package/dist/src/cli/commands/image.js +1 -1
- package/dist/src/cli/commands/init.js +31 -4
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/models.d.ts +4 -1
- package/dist/src/cli/commands/models.js +86 -74
- package/dist/src/cli/commands/models.js.map +1 -1
- package/dist/src/cli/commands/onboard.js +4 -2
- package/dist/src/cli/commands/onboard.js.map +1 -1
- package/dist/src/cli/commands/profile.d.ts +3 -5
- package/dist/src/cli/commands/profile.js +31 -31
- package/dist/src/cli/commands/profile.js.map +1 -1
- package/dist/src/cli/commands/setup.js +6 -1
- package/dist/src/cli/commands/setup.js.map +1 -1
- package/dist/src/cli/commands/tunnel.js +2 -2
- package/dist/src/cli/gateway-run-argv.js +15 -5
- package/dist/src/cli/gateway-run-argv.js.map +1 -1
- package/dist/src/cli/utils/gateway-client.js +1 -1
- package/dist/src/cli/utils/init-workspace-core.js +2 -2
- package/dist/src/config/agent-profile.js +1 -1
- package/dist/src/config/gateway-bind.js +1 -1
- package/dist/src/config/index.js +5 -5
- package/dist/src/config/loader.js +2 -2
- package/dist/src/config/models-json.js +2 -2
- package/dist/src/config/paths-state.js +1 -1
- package/dist/src/config/profile.js +2 -2
- package/dist/src/config/public-url.d.ts +28 -0
- package/dist/src/config/public-url.js +103 -0
- package/dist/src/config/public-url.js.map +1 -0
- package/dist/src/config/schema.d.ts +82 -0
- package/dist/src/config/schema.js +130 -1
- package/dist/src/config/schema.js.map +1 -1
- package/dist/src/config/workspace-path.js +1 -1
- package/dist/src/cron/executor.js +2 -2
- package/dist/src/cron/persistence.js +1 -1
- package/dist/src/cron/run-log-store.js +1 -1
- package/dist/src/daemon/constants.js +1 -1
- package/dist/src/daemon/install-plan.js +3 -3
- package/dist/src/daemon/install-plan.js.map +1 -1
- package/dist/src/daemon/launchd.js +2 -2
- package/dist/src/daemon/schtasks.js +38 -1
- package/dist/src/daemon/schtasks.js.map +1 -1
- package/dist/src/daemon/systemd.js +2 -2
- package/dist/src/extensions/bundle-mcp.js +1 -1
- package/dist/src/extensions/discover-extensions.js +1 -1
- package/dist/src/extensions/health.js +1 -1
- package/dist/src/extensions/loader.js +1 -1
- package/dist/src/extensions/lockfile.js +2 -2
- package/dist/src/gateway/agents-admin.js +2 -2
- package/dist/src/gateway/file-path-classifier.js +2 -2
- package/dist/src/gateway/heartbeat/service.js +1 -1
- package/dist/src/gateway/hono/app.js +33 -2
- package/dist/src/gateway/hono/app.js.map +1 -1
- package/dist/src/gateway/hono/lib/config-payload.js +1 -1
- package/dist/src/gateway/hono/lib/extension-store.js +2 -2
- package/dist/src/gateway/hono/lib/static-ui.js +2 -2
- package/dist/src/gateway/hono/oauth.js +1 -1
- package/dist/src/gateway/hono/routes/agents.js +1 -1
- package/dist/src/gateway/hono/routes/auth-registry-extensions.js +1 -1
- package/dist/src/gateway/hono/routes/config-patch/misc.js +1 -1
- package/dist/src/gateway/hono/routes/dreaming.js +1 -1
- package/dist/src/gateway/hono/routes/host-fs.js +2 -2
- package/dist/src/gateway/hono/routes/lazy-bundles.js +8 -0
- package/dist/src/gateway/hono/routes/lazy-bundles.js.map +1 -1
- package/dist/src/gateway/hono/routes/models.js +1 -1
- package/dist/src/gateway/hono/routes/shares.js +631 -34
- package/dist/src/gateway/hono/routes/shares.js.map +1 -1
- package/dist/src/gateway/hono/routes/site-shares.d.ts +3 -0
- package/dist/src/gateway/hono/routes/site-shares.js +228 -0
- package/dist/src/gateway/hono/routes/site-shares.js.map +1 -0
- package/dist/src/gateway/hono/routes/tunnel.js +97 -8
- package/dist/src/gateway/hono/routes/tunnel.js.map +1 -1
- package/dist/src/gateway/hono/routes/workspace.js +5 -5
- package/dist/src/gateway/hono/sse.js +2 -2
- package/dist/src/gateway/host.d.ts +3 -1
- package/dist/src/gateway/host.js +3 -1
- package/dist/src/gateway/host.js.map +1 -1
- package/dist/src/gateway/lock.js +3 -3
- package/dist/src/gateway/ports.d.ts +6 -0
- package/dist/src/gateway/ports.js +38 -2
- package/dist/src/gateway/ports.js.map +1 -1
- package/dist/src/gateway/public-url.d.ts +8 -0
- package/dist/src/gateway/public-url.js +10 -0
- package/dist/src/gateway/public-url.js.map +1 -0
- package/dist/src/gateway/security/origin-check.d.ts +9 -1
- package/dist/src/gateway/security/origin-check.js +4 -0
- package/dist/src/gateway/security/origin-check.js.map +1 -1
- package/dist/src/gateway/server.js +15 -0
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/service/agent-runner.js +2 -2
- package/dist/src/gateway/service/marketplace-service.js +2 -2
- package/dist/src/gateway/service/run-gateway-agent.js +2 -2
- package/dist/src/gateway/service.js +3 -2
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/gateway/workspace-fs-file-list.js +1 -1
- package/dist/src/heartbeat/index.js +1 -1
- package/dist/src/i18n/goals-bundle.js +1 -1
- package/dist/src/i18n/index.d.ts +1 -0
- package/dist/src/i18n/index.js +2 -1
- package/dist/src/i18n/locales/share-tool.en.js +15 -0
- package/dist/src/i18n/locales/share-tool.en.js.map +1 -0
- package/dist/src/i18n/locales/share-tool.zh.js +15 -0
- package/dist/src/i18n/locales/share-tool.zh.js.map +1 -0
- package/dist/src/i18n/share-tool-bundle.d.ts +20 -0
- package/dist/src/i18n/share-tool-bundle.js +56 -0
- package/dist/src/i18n/share-tool-bundle.js.map +1 -0
- package/dist/src/infra/gateway-processes.js +1 -0
- package/dist/src/infra/gateway-processes.js.map +1 -1
- package/dist/src/infra/restart.js +2 -2
- package/dist/src/infra/update-check.js +1 -1
- package/dist/src/infra/update-lock.js +3 -3
- package/dist/src/infra/update-runner.js +1 -1
- package/dist/src/infra/update-startup.js +2 -2
- package/dist/src/infra/write-file-atomic.js +2 -2
- package/dist/src/providers/auth-runtime/auth-profile-store.js +1 -1
- package/dist/src/providers/index.js +2 -2
- package/dist/src/providers/model-registry.js +1 -1
- package/dist/src/session/config-store.js +2 -2
- package/dist/src/session/parity/jsonl-transcript-io.js +2 -2
- package/dist/src/session/parity/sessions-json-file.js +1 -1
- package/dist/src/session/parity/transcript-file-lock.js +2 -2
- package/dist/src/session/parity/transcript-paths.js +1 -1
- package/dist/src/session/search-index-cache.js +1 -1
- package/dist/src/session/search-index.js +1 -1
- package/dist/src/session/session-title.js +3 -2
- package/dist/src/session/session-title.js.map +1 -1
- package/dist/src/session/store.js +5 -5
- package/dist/src/share/share-auto.d.ts +74 -0
- package/dist/src/share/share-auto.js +247 -0
- package/dist/src/share/share-auto.js.map +1 -0
- package/dist/src/share/share-config.js +63 -4
- package/dist/src/share/share-config.js.map +1 -1
- package/dist/src/share/share-landing.d.ts +28 -2
- package/dist/src/share/share-landing.js +155 -34
- package/dist/src/share/share-landing.js.map +1 -1
- package/dist/src/share/share-store.d.ts +48 -4
- package/dist/src/share/share-store.js +322 -51
- package/dist/src/share/share-store.js.map +1 -1
- package/dist/src/share/share-thumbnail.d.ts +35 -0
- package/dist/src/share/share-thumbnail.js +277 -0
- package/dist/src/share/share-thumbnail.js.map +1 -0
- package/dist/src/share/share-types.d.ts +68 -10
- package/dist/src/share/share-types.js +18 -1
- package/dist/src/share/share-types.js.map +1 -1
- package/dist/src/share/share-url.js +1 -1
- package/dist/src/share/share-zip.d.ts +35 -0
- package/dist/src/share/share-zip.js +303 -0
- package/dist/src/share/share-zip.js.map +1 -0
- package/dist/src/share/site-proxy.d.ts +35 -0
- package/dist/src/share/site-proxy.js +234 -0
- package/dist/src/share/site-proxy.js.map +1 -0
- package/dist/src/share/site-share-config.d.ts +11 -0
- package/dist/src/share/site-share-config.js +103 -0
- package/dist/src/share/site-share-config.js.map +1 -0
- package/dist/src/share/site-share-router.d.ts +23 -0
- package/dist/src/share/site-share-router.js +147 -0
- package/dist/src/share/site-share-router.js.map +1 -0
- package/dist/src/share/site-share-store.d.ts +53 -0
- package/dist/src/share/site-share-store.js +400 -0
- package/dist/src/share/site-share-store.js.map +1 -0
- package/dist/src/share/site-share-types.d.ts +103 -0
- package/dist/src/share/site-share-types.js +41 -0
- package/dist/src/share/site-share-types.js.map +1 -0
- package/dist/src/share/site-static-serve.d.ts +10 -0
- package/dist/src/share/site-static-serve.js +145 -0
- package/dist/src/share/site-static-serve.js.map +1 -0
- package/dist/src/tui/clipboard-image.js +3 -3
- package/dist/src/tui/theme-manager.js +1 -1
- package/dist/src/tui/tui-commands.js +18 -0
- package/dist/src/tui/tui-commands.js.map +1 -1
- package/dist/src/tui/tui-keybindings-file.js +1 -1
- package/dist/src/tui/tui-scoped-models.js +2 -2
- package/dist/src/tui/tui-settings.js +1 -1
- package/dist/src/tui/tui-workflow-slash.d.ts +32 -0
- package/dist/src/tui/tui-workflow-slash.js +63 -0
- package/dist/src/tui/tui-workflow-slash.js.map +1 -0
- package/dist/src/tui/tui.js +2 -2
- package/dist/src/tunnel/enable-lan-pairing.js +1 -1
- package/dist/src/tunnel/frpc-binary.js +3 -3
- package/dist/src/tunnel/frpc-config.js +1 -1
- package/dist/src/tunnel/frpc-extract.js +1 -1
- package/dist/src/tunnel/index.js +2 -2
- package/dist/src/tunnel/pair-context.d.ts +7 -1
- package/dist/src/tunnel/pair-context.js +25 -9
- package/dist/src/tunnel/pair-context.js.map +1 -1
- package/dist/src/tunnel/pair-url.d.ts +14 -1
- package/dist/src/tunnel/pair-url.js +14 -1
- package/dist/src/tunnel/pair-url.js.map +1 -1
- package/dist/src/tunnel/tunnel-service.js +2 -2
- package/dist/src/tunnel/tunnel-state.js +1 -1
- package/dist/src/utils/logger/audit.js +1 -1
- package/dist/src/utils/logger/log-store.js +1 -1
- package/dist/src/utils/logger/rotation.js +1 -1
- package/dist/src/voice/tts/audio.js +1 -1
- package/dist/src/voice/tts/providers/edge-speech.js +2 -2
- package/package.json +3 -2
- package/dist/gateway/static/root/assets/agents-CrpYTHJS.js +0 -222
- package/dist/gateway/static/root/assets/apps-page-1mcKh5Rh.js +0 -1
- package/dist/gateway/static/root/assets/button-KafIU8dx.js +0 -1
- package/dist/gateway/static/root/assets/channels-settings-zd6QNKPx.js +0 -1
- package/dist/gateway/static/root/assets/channels-status-swr-uRAuhiUo.js +0 -8
- package/dist/gateway/static/root/assets/cron-api-O2Q_ruV6.js +0 -1
- package/dist/gateway/static/root/assets/cron-page-By09AQD-.js +0 -1
- package/dist/gateway/static/root/assets/dist-C57OMHW8.js +0 -48
- package/dist/gateway/static/root/assets/extension-page-C-Ed5ZmP.js +0 -1
- package/dist/gateway/static/root/assets/extension-settings-page-raLux7E7.js +0 -1
- package/dist/gateway/static/root/assets/fetch-2iRFmd3n.js +0 -3
- package/dist/gateway/static/root/assets/heartbeat-config-api-BVl5VHvL.js +0 -1
- package/dist/gateway/static/root/assets/index-BuFldCsB.css +0 -1
- package/dist/gateway/static/root/assets/index-Y-iqo-gL.js +0 -4693
- package/dist/gateway/static/root/assets/logs-page-BdH2n7ZW.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-Vpchzdp-.js +0 -1
- package/dist/gateway/static/root/assets/settings-form-section-Kk1yAGBl.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-KBm0u6Dz.js +0 -3
- package/dist/gateway/static/root/assets/skills-page-BjeXXaOn.js +0 -2
- package/dist/gateway/static/root/assets/theme-store-D01dJt95.js +0 -1
- package/dist/gateway/static/root/assets/utils-DpTxN4AF.js +0 -1
- package/dist/gateway/static/root/assets/voice-api-key-field-CwO8Cf01.js +0 -1
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { createLogger } from "../utils/logger/index.js";
|
|
2
|
+
import { init_logger } from "../utils/logger.js";
|
|
3
|
+
import { request } from "undici";
|
|
4
|
+
import { Readable } from "node:stream";
|
|
5
|
+
import * as ws from "ws";
|
|
6
|
+
//#region src/share/site-proxy.ts
|
|
7
|
+
init_logger();
|
|
8
|
+
const wsAny = ws;
|
|
9
|
+
const WSClient = wsAny.WebSocket ?? wsAny.default;
|
|
10
|
+
const WebSocketServer = wsAny.WebSocketServer ?? wsAny.default?.WebSocketServer;
|
|
11
|
+
const log = createLogger("SiteProxy");
|
|
12
|
+
const HOP_BY_HOP_HEADERS = new Set([
|
|
13
|
+
"connection",
|
|
14
|
+
"keep-alive",
|
|
15
|
+
"proxy-authenticate",
|
|
16
|
+
"proxy-authorization",
|
|
17
|
+
"te",
|
|
18
|
+
"trailer",
|
|
19
|
+
"transfer-encoding",
|
|
20
|
+
"upgrade",
|
|
21
|
+
"host",
|
|
22
|
+
"content-length"
|
|
23
|
+
]);
|
|
24
|
+
const FORWARDED_HEADERS_SKIP = new Set([
|
|
25
|
+
"host",
|
|
26
|
+
"x-forwarded-for",
|
|
27
|
+
"x-forwarded-host",
|
|
28
|
+
"x-forwarded-proto",
|
|
29
|
+
"x-real-ip",
|
|
30
|
+
"connection",
|
|
31
|
+
"keep-alive",
|
|
32
|
+
"upgrade",
|
|
33
|
+
"transfer-encoding"
|
|
34
|
+
]);
|
|
35
|
+
/** Build the upstream URL preserving search params from the incoming request. */
|
|
36
|
+
function buildUpstreamUrl(source, innerPath, search) {
|
|
37
|
+
return `${source.upstreamUrl.replace(/\/+$/, "")}${innerPath.startsWith("/") ? innerPath : `/${innerPath}`}${search}`;
|
|
38
|
+
}
|
|
39
|
+
function filterRequestHeaders(headers) {
|
|
40
|
+
const out = {};
|
|
41
|
+
headers.forEach((value, key) => {
|
|
42
|
+
const lower = key.toLowerCase();
|
|
43
|
+
if (FORWARDED_HEADERS_SKIP.has(lower)) return;
|
|
44
|
+
out[lower] = value;
|
|
45
|
+
});
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
function rewriteSetCookie(value, basePrefix) {
|
|
49
|
+
if (!basePrefix) return value;
|
|
50
|
+
return value.replace(/(;\s*Path=)([^;]*)/gi, (_match, prefix, path) => {
|
|
51
|
+
if (path.startsWith(basePrefix)) return _match;
|
|
52
|
+
if (path.startsWith("/")) return `${prefix}${basePrefix}${path}`;
|
|
53
|
+
return `${prefix}${basePrefix}/${path}`;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function rewriteLocation(value, source, basePrefix) {
|
|
57
|
+
try {
|
|
58
|
+
const upstream = new URL(source.upstreamUrl);
|
|
59
|
+
const target = new URL(value, source.upstreamUrl);
|
|
60
|
+
if (target.host === upstream.host) return `${basePrefix}${target.pathname}${target.search}${target.hash}`;
|
|
61
|
+
return value;
|
|
62
|
+
} catch {
|
|
63
|
+
if (value.startsWith("/") && !value.startsWith("//")) return `${basePrefix}${value}`;
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Forward an HTTP request to the upstream and return a streaming Response.
|
|
69
|
+
* Handles header filtering, forwarded headers, Set-Cookie / Location rewriting,
|
|
70
|
+
* and a per-request timeout.
|
|
71
|
+
*/
|
|
72
|
+
async function forwardHttpRequest(record, store, req, ctx) {
|
|
73
|
+
if (record.source.kind !== "proxy") return { response: new Response("not_proxy", { status: 400 }) };
|
|
74
|
+
const source = record.source;
|
|
75
|
+
const cfg = store.getConfig().proxy;
|
|
76
|
+
const upstreamUrl = buildUpstreamUrl(source, ctx.innerPath, ctx.originalUrl.search);
|
|
77
|
+
const requestHeaders = filterRequestHeaders(req.headers);
|
|
78
|
+
const forwardedFor = req.headers.get("x-forwarded-for");
|
|
79
|
+
requestHeaders["x-forwarded-for"] = forwardedFor ? `${forwardedFor}, ${ctx.clientIp}` : ctx.clientIp;
|
|
80
|
+
requestHeaders["x-forwarded-host"] = req.headers.get("host") ?? "";
|
|
81
|
+
requestHeaders["x-forwarded-proto"] = ctx.forwardedProto;
|
|
82
|
+
requestHeaders["x-real-ip"] = ctx.clientIp;
|
|
83
|
+
requestHeaders.host = new URL(source.upstreamUrl).host;
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
const timer = setTimeout(() => controller.abort(), cfg.requestTimeoutMs);
|
|
86
|
+
timer.unref?.();
|
|
87
|
+
try {
|
|
88
|
+
const method = req.method.toUpperCase();
|
|
89
|
+
const upstream = await request(upstreamUrl, {
|
|
90
|
+
method,
|
|
91
|
+
headers: requestHeaders,
|
|
92
|
+
body: method !== "GET" && method !== "HEAD" && req.body != null ? Readable.fromWeb(req.body) : void 0,
|
|
93
|
+
signal: controller.signal,
|
|
94
|
+
bodyTimeout: cfg.requestTimeoutMs,
|
|
95
|
+
headersTimeout: cfg.requestTimeoutMs
|
|
96
|
+
});
|
|
97
|
+
const responseHeaders = new Headers();
|
|
98
|
+
for (const [rawKey, rawValue] of Object.entries(upstream.headers)) {
|
|
99
|
+
if (rawValue === void 0) continue;
|
|
100
|
+
const lower = rawKey.toLowerCase();
|
|
101
|
+
if (HOP_BY_HOP_HEADERS.has(lower)) continue;
|
|
102
|
+
const values = Array.isArray(rawValue) ? rawValue : [rawValue];
|
|
103
|
+
for (const value of values) {
|
|
104
|
+
if (typeof value !== "string") continue;
|
|
105
|
+
let outValue = value;
|
|
106
|
+
if (lower === "set-cookie" && source.rewriteSetCookiePath) outValue = rewriteSetCookie(value, ctx.basePrefix);
|
|
107
|
+
else if (lower === "location") outValue = rewriteLocation(value, source, ctx.basePrefix);
|
|
108
|
+
responseHeaders.append(rawKey, outValue);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const body = upstream.body ? Readable.toWeb(upstream.body) : null;
|
|
112
|
+
store.recordRequest(record.id, ctx.clientIp);
|
|
113
|
+
return { response: new Response(body, {
|
|
114
|
+
status: upstream.statusCode,
|
|
115
|
+
headers: responseHeaders
|
|
116
|
+
}) };
|
|
117
|
+
} catch (err) {
|
|
118
|
+
log.warn({
|
|
119
|
+
err,
|
|
120
|
+
shareId: record.id,
|
|
121
|
+
upstreamUrl
|
|
122
|
+
}, `Proxy request failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
123
|
+
return { response: new Response("Bad gateway", { status: 502 }) };
|
|
124
|
+
} finally {
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const wsServer = new WebSocketServer({ noServer: true });
|
|
129
|
+
function handleProxyWebSocketUpgrade(args) {
|
|
130
|
+
const { record, store, req, socket, head, innerPath, basePrefix, clientIp } = args;
|
|
131
|
+
if (record.source.kind !== "proxy") {
|
|
132
|
+
socket.destroy();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const source = record.source;
|
|
136
|
+
const cfg = store.getConfig().proxy;
|
|
137
|
+
if (!cfg.forwardWebSocket || !source.forwardWebSocket) {
|
|
138
|
+
socket.destroy();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const upstreamBase = new URL(source.upstreamUrl);
|
|
142
|
+
const wsProto = upstreamBase.protocol === "https:" ? "wss:" : "ws:";
|
|
143
|
+
const search = req.url ? new URL(req.url, "http://localhost").search : "";
|
|
144
|
+
const path = innerPath.startsWith("/") ? innerPath : `/${innerPath}`;
|
|
145
|
+
const upstreamWsUrl = `${wsProto}//${upstreamBase.host}${path}${search}`;
|
|
146
|
+
const forwardHeaders = {};
|
|
147
|
+
for (const [rawKey, rawValue] of Object.entries(req.headers)) {
|
|
148
|
+
if (rawValue === void 0) continue;
|
|
149
|
+
const lower = rawKey.toLowerCase();
|
|
150
|
+
if (FORWARDED_HEADERS_SKIP.has(lower)) continue;
|
|
151
|
+
if (lower.startsWith("sec-websocket-")) continue;
|
|
152
|
+
forwardHeaders[lower] = Array.isArray(rawValue) ? rawValue.join(", ") : String(rawValue);
|
|
153
|
+
}
|
|
154
|
+
const xff = req.headers["x-forwarded-for"];
|
|
155
|
+
forwardHeaders["x-forwarded-for"] = xff ? `${xff}, ${clientIp}` : clientIp;
|
|
156
|
+
forwardHeaders["x-forwarded-host"] = String(req.headers.host ?? "");
|
|
157
|
+
forwardHeaders["x-forwarded-proto"] = "http";
|
|
158
|
+
forwardHeaders["x-real-ip"] = clientIp;
|
|
159
|
+
forwardHeaders.host = upstreamBase.host;
|
|
160
|
+
let upstreamWs;
|
|
161
|
+
try {
|
|
162
|
+
upstreamWs = new WSClient(upstreamWsUrl, {
|
|
163
|
+
headers: forwardHeaders,
|
|
164
|
+
handshakeTimeout: cfg.requestTimeoutMs
|
|
165
|
+
});
|
|
166
|
+
} catch (err) {
|
|
167
|
+
log.warn({
|
|
168
|
+
err,
|
|
169
|
+
shareId: record.id
|
|
170
|
+
}, "WebSocket upstream connect threw");
|
|
171
|
+
socket.destroy();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
let idleTimer = null;
|
|
175
|
+
const resetIdleTimer = (kill) => {
|
|
176
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
177
|
+
idleTimer = setTimeout(kill, cfg.wsIdleTimeoutMs);
|
|
178
|
+
idleTimer.unref?.();
|
|
179
|
+
};
|
|
180
|
+
upstreamWs.once("open", () => {
|
|
181
|
+
wsServer.handleUpgrade(req, socket, head, (clientWs) => {
|
|
182
|
+
store.recordRequest(record.id, clientIp);
|
|
183
|
+
const killAll = () => {
|
|
184
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
185
|
+
try {
|
|
186
|
+
clientWs.close();
|
|
187
|
+
} catch {}
|
|
188
|
+
try {
|
|
189
|
+
upstreamWs.close();
|
|
190
|
+
} catch {}
|
|
191
|
+
};
|
|
192
|
+
resetIdleTimer(killAll);
|
|
193
|
+
const pump = (from, to) => {
|
|
194
|
+
from.on("message", (data, isBinary) => {
|
|
195
|
+
resetIdleTimer(killAll);
|
|
196
|
+
try {
|
|
197
|
+
to.send(data, { binary: isBinary });
|
|
198
|
+
} catch (err) {
|
|
199
|
+
log.warn({
|
|
200
|
+
err,
|
|
201
|
+
shareId: record.id
|
|
202
|
+
}, "WS forward failed");
|
|
203
|
+
killAll();
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
from.on("close", killAll);
|
|
207
|
+
from.on("error", (err) => {
|
|
208
|
+
log.warn({
|
|
209
|
+
err,
|
|
210
|
+
shareId: record.id
|
|
211
|
+
}, "WS error");
|
|
212
|
+
killAll();
|
|
213
|
+
});
|
|
214
|
+
};
|
|
215
|
+
pump(clientWs, upstreamWs);
|
|
216
|
+
pump(upstreamWs, clientWs);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
upstreamWs.once("error", (err) => {
|
|
220
|
+
log.warn({
|
|
221
|
+
err,
|
|
222
|
+
shareId: record.id,
|
|
223
|
+
upstreamWsUrl
|
|
224
|
+
}, "WS upstream error");
|
|
225
|
+
try {
|
|
226
|
+
socket.write("HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\n\r\n");
|
|
227
|
+
} catch {}
|
|
228
|
+
socket.destroy();
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
//#endregion
|
|
232
|
+
export { forwardHttpRequest, handleProxyWebSocketUpgrade };
|
|
233
|
+
|
|
234
|
+
//# sourceMappingURL=site-proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-proxy.js","names":["undiciRequest"],"sources":["../../../src/share/site-proxy.ts"],"sourcesContent":["import { request as undiciRequest } from 'undici';\nimport { Readable } from 'node:stream';\nimport * as ws from 'ws';\n\n// `ws` resolves differently under various loaders (tsx's CJS bridge vs Node's\n// native ESM wrapper). Reach into both shapes so the class refs stay stable.\nconst wsAny = ws as unknown as {\n default?: typeof import('ws').WebSocket & { WebSocketServer?: typeof import('ws').WebSocketServer };\n WebSocket?: typeof import('ws').WebSocket;\n WebSocketServer?: typeof import('ws').WebSocketServer;\n};\nconst WSClient = (wsAny.WebSocket ?? wsAny.default) as typeof import('ws').WebSocket;\nconst WebSocketServer = (wsAny.WebSocketServer ?? wsAny.default?.WebSocketServer) as typeof import('ws').WebSocketServer;\ntype WSClient = import('ws').WebSocket;\nimport type { IncomingMessage } from 'node:http';\nimport type { Socket } from 'node:net';\n\nimport { createLogger } from '../utils/logger.js';\nimport type { SiteShareRecord, SiteProxySource } from './site-share-types.js';\nimport type { SiteShareStore } from './site-share-store.js';\n\nconst log = createLogger('SiteProxy');\n\nconst HOP_BY_HOP_HEADERS = new Set([\n 'connection',\n 'keep-alive',\n 'proxy-authenticate',\n 'proxy-authorization',\n 'te',\n 'trailer',\n 'transfer-encoding',\n 'upgrade',\n 'host',\n 'content-length',\n]);\n\nconst FORWARDED_HEADERS_SKIP = new Set([\n 'host',\n 'x-forwarded-for',\n 'x-forwarded-host',\n 'x-forwarded-proto',\n 'x-real-ip',\n 'connection',\n 'keep-alive',\n 'upgrade',\n 'transfer-encoding',\n]);\n\nexport interface ProxyRequestContext {\n /** Path inside the share to forward (no leading basePrefix). */\n innerPath: string;\n /** Path prefix the share is mounted under (used for Set-Cookie/Location rewrite). For subdomain mode use ''. */\n basePrefix: string;\n /** Client IP for forwarded headers and audit. */\n clientIp: string;\n /** Full request URL (for forwarded proto). */\n originalUrl: URL;\n /** Whether the request reached us via HTTPS at the edge. */\n forwardedProto: 'http' | 'https';\n}\n\nexport interface ProxyResult {\n response: Response;\n}\n\n/** Build the upstream URL preserving search params from the incoming request. */\nfunction buildUpstreamUrl(source: SiteProxySource, innerPath: string, search: string): string {\n const base = source.upstreamUrl.replace(/\\/+$/, '');\n const path = innerPath.startsWith('/') ? innerPath : `/${innerPath}`;\n return `${base}${path}${search}`;\n}\n\nfunction filterRequestHeaders(headers: Headers): Record<string, string> {\n const out: Record<string, string> = {};\n headers.forEach((value, key) => {\n const lower = key.toLowerCase();\n if (FORWARDED_HEADERS_SKIP.has(lower)) return;\n out[lower] = value;\n });\n return out;\n}\n\nfunction rewriteSetCookie(value: string, basePrefix: string): string {\n if (!basePrefix) return value;\n return value.replace(/(;\\s*Path=)([^;]*)/gi, (_match, prefix, path) => {\n if (path.startsWith(basePrefix)) return _match;\n if (path.startsWith('/')) return `${prefix}${basePrefix}${path}`;\n return `${prefix}${basePrefix}/${path}`;\n });\n}\n\nfunction rewriteLocation(value: string, source: SiteProxySource, basePrefix: string): string {\n try {\n const upstream = new URL(source.upstreamUrl);\n const target = new URL(value, source.upstreamUrl);\n if (target.host === upstream.host) {\n return `${basePrefix}${target.pathname}${target.search}${target.hash}`;\n }\n return value;\n } catch {\n // Relative path\n if (value.startsWith('/') && !value.startsWith('//')) {\n return `${basePrefix}${value}`;\n }\n return value;\n }\n}\n\n/**\n * Forward an HTTP request to the upstream and return a streaming Response.\n * Handles header filtering, forwarded headers, Set-Cookie / Location rewriting,\n * and a per-request timeout.\n */\nexport async function forwardHttpRequest(\n record: SiteShareRecord,\n store: SiteShareStore,\n req: Request,\n ctx: ProxyRequestContext,\n): Promise<ProxyResult> {\n if (record.source.kind !== 'proxy') {\n return { response: new Response('not_proxy', { status: 400 }) };\n }\n const source = record.source;\n const cfg = store.getConfig().proxy;\n\n const upstreamUrl = buildUpstreamUrl(source, ctx.innerPath, ctx.originalUrl.search);\n const requestHeaders = filterRequestHeaders(req.headers);\n\n // Forwarded headers\n const forwardedFor = req.headers.get('x-forwarded-for');\n requestHeaders['x-forwarded-for'] = forwardedFor ? `${forwardedFor}, ${ctx.clientIp}` : ctx.clientIp;\n requestHeaders['x-forwarded-host'] = req.headers.get('host') ?? '';\n requestHeaders['x-forwarded-proto'] = ctx.forwardedProto;\n requestHeaders['x-real-ip'] = ctx.clientIp;\n // Override Host to match upstream so vhost-routed dev servers behave\n const upstreamHost = new URL(source.upstreamUrl).host;\n requestHeaders.host = upstreamHost;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), cfg.requestTimeoutMs);\n timer.unref?.();\n\n try {\n const method = req.method.toUpperCase();\n const hasBody = method !== 'GET' && method !== 'HEAD' && req.body != null;\n const upstream = await undiciRequest(upstreamUrl, {\n method: method as Parameters<typeof undiciRequest>[1]['method'],\n headers: requestHeaders,\n body: hasBody ? (Readable.fromWeb(req.body as never) as never) : undefined,\n signal: controller.signal,\n bodyTimeout: cfg.requestTimeoutMs,\n headersTimeout: cfg.requestTimeoutMs,\n });\n\n const responseHeaders = new Headers();\n for (const [rawKey, rawValue] of Object.entries(upstream.headers)) {\n if (rawValue === undefined) continue;\n const lower = rawKey.toLowerCase();\n if (HOP_BY_HOP_HEADERS.has(lower)) continue;\n const values = Array.isArray(rawValue) ? rawValue : [rawValue];\n for (const value of values) {\n if (typeof value !== 'string') continue;\n let outValue = value;\n if (lower === 'set-cookie' && source.rewriteSetCookiePath) {\n outValue = rewriteSetCookie(value, ctx.basePrefix);\n } else if (lower === 'location') {\n outValue = rewriteLocation(value, source, ctx.basePrefix);\n }\n responseHeaders.append(rawKey, outValue);\n }\n }\n\n const body = upstream.body\n ? (Readable.toWeb(upstream.body as unknown as Readable) as ReadableStream)\n : null;\n\n store.recordRequest(record.id, ctx.clientIp);\n\n return {\n response: new Response(body, {\n status: upstream.statusCode,\n headers: responseHeaders,\n }),\n };\n } catch (err) {\n log.warn(\n { err, shareId: record.id, upstreamUrl },\n `Proxy request failed: ${err instanceof Error ? err.message : String(err)}`,\n );\n return {\n response: new Response('Bad gateway', { status: 502 }),\n };\n } finally {\n clearTimeout(timer);\n }\n}\n\n// ── WebSocket bridge ──────────────────────────────────────────────────────────\n\nconst wsServer = new WebSocketServer({ noServer: true });\n\nexport function handleProxyWebSocketUpgrade(args: {\n record: SiteShareRecord;\n store: SiteShareStore;\n req: IncomingMessage;\n socket: Socket;\n head: Buffer;\n innerPath: string;\n basePrefix: string;\n clientIp: string;\n}): void {\n const { record, store, req, socket, head, innerPath, basePrefix, clientIp } = args;\n if (record.source.kind !== 'proxy') {\n socket.destroy();\n return;\n }\n const source = record.source;\n const cfg = store.getConfig().proxy;\n if (!cfg.forwardWebSocket || !source.forwardWebSocket) {\n socket.destroy();\n return;\n }\n\n const upstreamBase = new URL(source.upstreamUrl);\n const wsProto = upstreamBase.protocol === 'https:' ? 'wss:' : 'ws:';\n const search = req.url ? new URL(req.url, 'http://localhost').search : '';\n const path = innerPath.startsWith('/') ? innerPath : `/${innerPath}`;\n const upstreamWsUrl = `${wsProto}//${upstreamBase.host}${path}${search}`;\n\n const forwardHeaders: Record<string, string> = {};\n for (const [rawKey, rawValue] of Object.entries(req.headers)) {\n if (rawValue === undefined) continue;\n const lower = rawKey.toLowerCase();\n if (FORWARDED_HEADERS_SKIP.has(lower)) continue;\n if (lower.startsWith('sec-websocket-')) continue; // ws lib rebuilds these\n forwardHeaders[lower] = Array.isArray(rawValue) ? rawValue.join(', ') : String(rawValue);\n }\n const xff = req.headers['x-forwarded-for'];\n forwardHeaders['x-forwarded-for'] = xff ? `${xff}, ${clientIp}` : clientIp;\n forwardHeaders['x-forwarded-host'] = String(req.headers.host ?? '');\n forwardHeaders['x-forwarded-proto'] = 'http';\n forwardHeaders['x-real-ip'] = clientIp;\n forwardHeaders.host = upstreamBase.host;\n\n let upstreamWs: WSClient;\n try {\n upstreamWs = new WSClient(upstreamWsUrl, {\n headers: forwardHeaders,\n handshakeTimeout: cfg.requestTimeoutMs,\n });\n } catch (err) {\n log.warn({ err, shareId: record.id }, 'WebSocket upstream connect threw');\n socket.destroy();\n return;\n }\n\n let idleTimer: ReturnType<typeof setTimeout> | null = null;\n const resetIdleTimer = (kill: () => void) => {\n if (idleTimer) clearTimeout(idleTimer);\n idleTimer = setTimeout(kill, cfg.wsIdleTimeoutMs);\n idleTimer.unref?.();\n };\n\n upstreamWs.once('open', () => {\n wsServer.handleUpgrade(req, socket, head, (clientWs) => {\n void basePrefix;\n store.recordRequest(record.id, clientIp);\n\n const killAll = () => {\n if (idleTimer) clearTimeout(idleTimer);\n try {\n clientWs.close();\n } catch {\n /* ignore */\n }\n try {\n upstreamWs.close();\n } catch {\n /* ignore */\n }\n };\n\n resetIdleTimer(killAll);\n\n const pump = (from: WSClient, to: WSClient): void => {\n from.on('message', (data: Buffer | ArrayBuffer | Buffer[], isBinary: boolean) => {\n resetIdleTimer(killAll);\n try {\n to.send(data as unknown as Buffer, { binary: isBinary });\n } catch (err) {\n log.warn({ err, shareId: record.id }, 'WS forward failed');\n killAll();\n }\n });\n from.on('close', killAll);\n from.on('error', (err) => {\n log.warn({ err, shareId: record.id }, 'WS error');\n killAll();\n });\n };\n\n pump(clientWs as unknown as WSClient, upstreamWs);\n pump(upstreamWs, clientWs as unknown as WSClient);\n });\n });\n\n upstreamWs.once('error', (err) => {\n log.warn({ err, shareId: record.id, upstreamWsUrl }, 'WS upstream error');\n try {\n socket.write('HTTP/1.1 502 Bad Gateway\\r\\nConnection: close\\r\\n\\r\\n');\n } catch {\n /* ignore */\n }\n socket.destroy();\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Config } from '../config/schema.js';
|
|
2
|
+
import type { GatewayService } from '../gateway/service.js';
|
|
3
|
+
import { type SiteShareConfig } from './site-share-types.js';
|
|
4
|
+
export declare function resolveSiteShareConfig(service: GatewayService): SiteShareConfig;
|
|
5
|
+
export declare function mergeWithDefaults(raw: unknown): SiteShareConfig;
|
|
6
|
+
export declare function mergeSiteShareConfigPatch(config: Config, patch: Record<string, unknown>): {
|
|
7
|
+
ok: true;
|
|
8
|
+
} | {
|
|
9
|
+
ok: false;
|
|
10
|
+
message: string;
|
|
11
|
+
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { SITE_SHARE_CONFIG_DEFAULTS } from "./site-share-types.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/share/site-share-config.ts
|
|
4
|
+
const SiteShareStaticPatchSchema = z.object({
|
|
5
|
+
enabled: z.boolean().optional(),
|
|
6
|
+
maxRootDirSize: z.number().int().min(1048576).max(10737418240).optional(),
|
|
7
|
+
maxFileCount: z.number().int().min(1).max(1e5).optional(),
|
|
8
|
+
rewriteEnabledByDefault: z.boolean().optional()
|
|
9
|
+
});
|
|
10
|
+
const SiteShareProxyPatchSchema = z.object({
|
|
11
|
+
enabled: z.boolean().optional(),
|
|
12
|
+
allowedUpstreamHosts: z.array(z.string().min(1)).optional(),
|
|
13
|
+
allowedUpstreamPorts: z.array(z.number().int().min(1).max(65535)).optional(),
|
|
14
|
+
forwardWebSocket: z.boolean().optional(),
|
|
15
|
+
bodySizeLimit: z.number().int().min(0).max(1073741824).optional(),
|
|
16
|
+
requestTimeoutMs: z.number().int().min(1e3).max(6e5).optional(),
|
|
17
|
+
wsIdleTimeoutMs: z.number().int().min(1e4).max(36e5).optional(),
|
|
18
|
+
rewriteSetCookiePath: z.boolean().optional()
|
|
19
|
+
});
|
|
20
|
+
const SiteSharePatchSchema = z.object({
|
|
21
|
+
enabled: z.boolean().optional(),
|
|
22
|
+
publicHostSuffix: z.string().min(1).optional(),
|
|
23
|
+
defaultTtlMs: z.number().int().min(6e4).max(6048e5).optional(),
|
|
24
|
+
maxTtlMs: z.number().int().min(6e4).max(2592e6).optional(),
|
|
25
|
+
maxActiveSites: z.number().int().min(1).max(1e3).optional(),
|
|
26
|
+
static: SiteShareStaticPatchSchema.optional(),
|
|
27
|
+
proxy: SiteShareProxyPatchSchema.optional()
|
|
28
|
+
});
|
|
29
|
+
function resolveSiteShareConfig(service) {
|
|
30
|
+
const raw = service.currentConfig.gateway?.siteShare;
|
|
31
|
+
return mergeWithDefaults(raw);
|
|
32
|
+
}
|
|
33
|
+
function mergeWithDefaults(raw) {
|
|
34
|
+
const base = SITE_SHARE_CONFIG_DEFAULTS;
|
|
35
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return JSON.parse(JSON.stringify(base));
|
|
36
|
+
const r = raw;
|
|
37
|
+
return {
|
|
38
|
+
enabled: r.enabled ?? base.enabled,
|
|
39
|
+
publicHostSuffix: r.publicHostSuffix ?? base.publicHostSuffix,
|
|
40
|
+
defaultTtlMs: r.defaultTtlMs ?? base.defaultTtlMs,
|
|
41
|
+
maxTtlMs: r.maxTtlMs ?? base.maxTtlMs,
|
|
42
|
+
maxActiveSites: r.maxActiveSites ?? base.maxActiveSites,
|
|
43
|
+
static: {
|
|
44
|
+
enabled: r.static?.enabled ?? base.static.enabled,
|
|
45
|
+
maxRootDirSize: r.static?.maxRootDirSize ?? base.static.maxRootDirSize,
|
|
46
|
+
maxFileCount: r.static?.maxFileCount ?? base.static.maxFileCount,
|
|
47
|
+
rewriteEnabledByDefault: r.static?.rewriteEnabledByDefault ?? base.static.rewriteEnabledByDefault
|
|
48
|
+
},
|
|
49
|
+
proxy: {
|
|
50
|
+
enabled: r.proxy?.enabled ?? base.proxy.enabled,
|
|
51
|
+
allowedUpstreamHosts: r.proxy?.allowedUpstreamHosts ?? [...base.proxy.allowedUpstreamHosts],
|
|
52
|
+
allowedUpstreamPorts: r.proxy?.allowedUpstreamPorts ?? [...base.proxy.allowedUpstreamPorts],
|
|
53
|
+
forwardWebSocket: r.proxy?.forwardWebSocket ?? base.proxy.forwardWebSocket,
|
|
54
|
+
bodySizeLimit: r.proxy?.bodySizeLimit ?? base.proxy.bodySizeLimit,
|
|
55
|
+
requestTimeoutMs: r.proxy?.requestTimeoutMs ?? base.proxy.requestTimeoutMs,
|
|
56
|
+
wsIdleTimeoutMs: r.proxy?.wsIdleTimeoutMs ?? base.proxy.wsIdleTimeoutMs,
|
|
57
|
+
rewriteSetCookiePath: r.proxy?.rewriteSetCookiePath ?? base.proxy.rewriteSetCookiePath
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function mergeSiteShareConfigPatch(config, patch) {
|
|
62
|
+
const parsed = SiteSharePatchSchema.safeParse(patch);
|
|
63
|
+
if (!parsed.success) return {
|
|
64
|
+
ok: false,
|
|
65
|
+
message: parsed.error.issues.map((i) => i.message).join("; ")
|
|
66
|
+
};
|
|
67
|
+
if (!config.gateway) config.gateway = {
|
|
68
|
+
bind: "loopback",
|
|
69
|
+
port: 18790,
|
|
70
|
+
auth: { mode: "token" },
|
|
71
|
+
heartbeat: {
|
|
72
|
+
enabled: true,
|
|
73
|
+
intervalMs: 18e5,
|
|
74
|
+
includeSystemPromptSection: false
|
|
75
|
+
},
|
|
76
|
+
maxSseConnections: 100,
|
|
77
|
+
corsOrigins: []
|
|
78
|
+
};
|
|
79
|
+
const cfg = config.gateway;
|
|
80
|
+
const current = mergeWithDefaults(cfg.siteShare);
|
|
81
|
+
const next = {
|
|
82
|
+
...current,
|
|
83
|
+
...parsed.data,
|
|
84
|
+
static: parsed.data.static ? {
|
|
85
|
+
...current.static,
|
|
86
|
+
...parsed.data.static
|
|
87
|
+
} : current.static,
|
|
88
|
+
proxy: parsed.data.proxy ? {
|
|
89
|
+
...current.proxy,
|
|
90
|
+
...parsed.data.proxy
|
|
91
|
+
} : current.proxy
|
|
92
|
+
};
|
|
93
|
+
if (next.defaultTtlMs > next.maxTtlMs) return {
|
|
94
|
+
ok: false,
|
|
95
|
+
message: "siteShare.defaultTtlMs must not exceed siteShare.maxTtlMs"
|
|
96
|
+
};
|
|
97
|
+
cfg.siteShare = next;
|
|
98
|
+
return { ok: true };
|
|
99
|
+
}
|
|
100
|
+
//#endregion
|
|
101
|
+
export { mergeSiteShareConfigPatch, mergeWithDefaults, resolveSiteShareConfig };
|
|
102
|
+
|
|
103
|
+
//# sourceMappingURL=site-share-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-share-config.js","names":[],"sources":["../../../src/share/site-share-config.ts"],"sourcesContent":["import { z } from 'zod';\n\nimport type { Config } from '../config/schema.js';\nimport type { GatewayService } from '../gateway/service.js';\nimport {\n SITE_SHARE_CONFIG_DEFAULTS,\n type SiteShareConfig,\n} from './site-share-types.js';\n\nconst SiteShareStaticPatchSchema = z.object({\n enabled: z.boolean().optional(),\n maxRootDirSize: z.number().int().min(1_048_576).max(10_737_418_240).optional(),\n maxFileCount: z.number().int().min(1).max(100_000).optional(),\n rewriteEnabledByDefault: z.boolean().optional(),\n});\n\nconst SiteShareProxyPatchSchema = z.object({\n enabled: z.boolean().optional(),\n allowedUpstreamHosts: z.array(z.string().min(1)).optional(),\n allowedUpstreamPorts: z.array(z.number().int().min(1).max(65535)).optional(),\n forwardWebSocket: z.boolean().optional(),\n bodySizeLimit: z.number().int().min(0).max(1_073_741_824).optional(),\n requestTimeoutMs: z.number().int().min(1_000).max(600_000).optional(),\n wsIdleTimeoutMs: z.number().int().min(10_000).max(3_600_000).optional(),\n rewriteSetCookiePath: z.boolean().optional(),\n});\n\nconst SiteSharePatchSchema = z.object({\n enabled: z.boolean().optional(),\n publicHostSuffix: z.string().min(1).optional(),\n defaultTtlMs: z.number().int().min(60_000).max(604_800_000).optional(),\n maxTtlMs: z.number().int().min(60_000).max(2_592_000_000).optional(),\n maxActiveSites: z.number().int().min(1).max(1_000).optional(),\n static: SiteShareStaticPatchSchema.optional(),\n proxy: SiteShareProxyPatchSchema.optional(),\n});\n\nexport function resolveSiteShareConfig(service: GatewayService): SiteShareConfig {\n const raw = (service.currentConfig.gateway as Record<string, unknown> | undefined)?.siteShare;\n return mergeWithDefaults(raw);\n}\n\nexport function mergeWithDefaults(raw: unknown): SiteShareConfig {\n const base = SITE_SHARE_CONFIG_DEFAULTS;\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {\n return JSON.parse(JSON.stringify(base)) as SiteShareConfig;\n }\n const r = raw as Partial<SiteShareConfig> & {\n static?: Partial<SiteShareConfig['static']>;\n proxy?: Partial<SiteShareConfig['proxy']>;\n };\n return {\n enabled: r.enabled ?? base.enabled,\n publicHostSuffix: r.publicHostSuffix ?? base.publicHostSuffix,\n defaultTtlMs: r.defaultTtlMs ?? base.defaultTtlMs,\n maxTtlMs: r.maxTtlMs ?? base.maxTtlMs,\n maxActiveSites: r.maxActiveSites ?? base.maxActiveSites,\n static: {\n enabled: r.static?.enabled ?? base.static.enabled,\n maxRootDirSize: r.static?.maxRootDirSize ?? base.static.maxRootDirSize,\n maxFileCount: r.static?.maxFileCount ?? base.static.maxFileCount,\n rewriteEnabledByDefault: r.static?.rewriteEnabledByDefault ?? base.static.rewriteEnabledByDefault,\n },\n proxy: {\n enabled: r.proxy?.enabled ?? base.proxy.enabled,\n allowedUpstreamHosts: r.proxy?.allowedUpstreamHosts ?? [...base.proxy.allowedUpstreamHosts],\n allowedUpstreamPorts: r.proxy?.allowedUpstreamPorts ?? [...base.proxy.allowedUpstreamPorts],\n forwardWebSocket: r.proxy?.forwardWebSocket ?? base.proxy.forwardWebSocket,\n bodySizeLimit: r.proxy?.bodySizeLimit ?? base.proxy.bodySizeLimit,\n requestTimeoutMs: r.proxy?.requestTimeoutMs ?? base.proxy.requestTimeoutMs,\n wsIdleTimeoutMs: r.proxy?.wsIdleTimeoutMs ?? base.proxy.wsIdleTimeoutMs,\n rewriteSetCookiePath: r.proxy?.rewriteSetCookiePath ?? base.proxy.rewriteSetCookiePath,\n },\n };\n}\n\nexport function mergeSiteShareConfigPatch(\n config: Config,\n patch: Record<string, unknown>,\n): { ok: true } | { ok: false; message: string } {\n const parsed = SiteSharePatchSchema.safeParse(patch);\n if (!parsed.success) {\n return { ok: false, message: parsed.error.issues.map((i) => i.message).join('; ') };\n }\n if (!config.gateway) {\n config.gateway = {\n bind: 'loopback',\n port: 18790,\n auth: { mode: 'token' },\n heartbeat: { enabled: true, intervalMs: 1_800_000, includeSystemPromptSection: false },\n maxSseConnections: 100,\n corsOrigins: [],\n };\n }\n\n const cfg = config.gateway as Record<string, unknown>;\n const current = mergeWithDefaults(cfg.siteShare);\n const next: SiteShareConfig = {\n ...current,\n ...parsed.data,\n static: parsed.data.static ? { ...current.static, ...parsed.data.static } : current.static,\n proxy: parsed.data.proxy ? { ...current.proxy, ...parsed.data.proxy } : current.proxy,\n };\n if (next.defaultTtlMs > next.maxTtlMs) {\n return { ok: false, message: 'siteShare.defaultTtlMs must not exceed siteShare.maxTtlMs' };\n }\n cfg.siteShare = next as unknown as Record<string, unknown>;\n return { ok: true };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Context, Hono } from 'hono';
|
|
2
|
+
import type { GatewayService } from '../gateway/service.js';
|
|
3
|
+
import type { IncomingMessage } from 'node:http';
|
|
4
|
+
import type { Socket } from 'node:net';
|
|
5
|
+
/**
|
|
6
|
+
* Extract the share label from the request Host header when the public host
|
|
7
|
+
* suffix matches. Returns `null` if the request did not target a site-share
|
|
8
|
+
* subdomain.
|
|
9
|
+
*/
|
|
10
|
+
export declare function extractSiteShareLabel(host: string | null | undefined, suffix: string): string | null;
|
|
11
|
+
/**
|
|
12
|
+
* Hono middleware. Intercepts requests whose Host header is `*.share.xopc.ai`
|
|
13
|
+
* (or matches the configured suffix), routes them to the matching site share.
|
|
14
|
+
* Falls through to next() when the host does not match.
|
|
15
|
+
*
|
|
16
|
+
* Also handles the subpath fallback `/site/:token/*` for environments without
|
|
17
|
+
* wildcard DNS — same handler logic, just different prefix extraction.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createSiteShareMiddleware(service: GatewayService): (c: Context, next: () => Promise<void>) => Promise<void | Response>;
|
|
20
|
+
/** Standalone HTTP upgrade handler that bridges WebSocket requests to the proxy share. */
|
|
21
|
+
export declare function handleSiteShareUpgrade(service: GatewayService, req: IncomingMessage, socket: Socket, head: Buffer): boolean;
|
|
22
|
+
/** Register the middleware on a Hono app — call from createHonoApp. */
|
|
23
|
+
export declare function registerSiteShareMiddleware(app: Hono, service: GatewayService): void;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { createLogger } from "../utils/logger/index.js";
|
|
2
|
+
import { init_logger } from "../utils/logger.js";
|
|
3
|
+
import { resolveSiteShareConfig } from "./site-share-config.js";
|
|
4
|
+
import "./share-store.js";
|
|
5
|
+
import { getSiteShareStore } from "./site-share-store.js";
|
|
6
|
+
import { getClientIpFromHeaders } from "../gateway/security/loopback.js";
|
|
7
|
+
import { serveStaticSiteRequest } from "./site-static-serve.js";
|
|
8
|
+
import { forwardHttpRequest, handleProxyWebSocketUpgrade } from "./site-proxy.js";
|
|
9
|
+
import { renderShareExpiredPage } from "./share-landing.js";
|
|
10
|
+
//#region src/share/site-share-router.ts
|
|
11
|
+
init_logger();
|
|
12
|
+
const log = createLogger("SiteShareRouter");
|
|
13
|
+
const SITE_SUBPATH_PREFIX = "/site/";
|
|
14
|
+
/**
|
|
15
|
+
* Extract the share label from the request Host header when the public host
|
|
16
|
+
* suffix matches. Returns `null` if the request did not target a site-share
|
|
17
|
+
* subdomain.
|
|
18
|
+
*/
|
|
19
|
+
function extractSiteShareLabel(host, suffix) {
|
|
20
|
+
if (!host) return null;
|
|
21
|
+
const lowered = host.toLowerCase().split(":")[0];
|
|
22
|
+
const normSuffix = suffix.toLowerCase();
|
|
23
|
+
if (lowered === normSuffix) return null;
|
|
24
|
+
if (!lowered.endsWith(`.${normSuffix}`)) return null;
|
|
25
|
+
const label = lowered.slice(0, -1 * (normSuffix.length + 1));
|
|
26
|
+
if (!label || label.includes(".")) return null;
|
|
27
|
+
return label;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Hono middleware. Intercepts requests whose Host header is `*.share.xopc.ai`
|
|
31
|
+
* (or matches the configured suffix), routes them to the matching site share.
|
|
32
|
+
* Falls through to next() when the host does not match.
|
|
33
|
+
*
|
|
34
|
+
* Also handles the subpath fallback `/site/:token/*` for environments without
|
|
35
|
+
* wildcard DNS — same handler logic, just different prefix extraction.
|
|
36
|
+
*/
|
|
37
|
+
function createSiteShareMiddleware(service) {
|
|
38
|
+
return async (c, next) => {
|
|
39
|
+
const config = resolveSiteShareConfig(service);
|
|
40
|
+
if (!config.enabled) return next();
|
|
41
|
+
const store = getSiteShareStore(config);
|
|
42
|
+
store.updateConfig(config);
|
|
43
|
+
const label = extractSiteShareLabel(c.req.header("host"), config.publicHostSuffix);
|
|
44
|
+
const url = new URL(c.req.url);
|
|
45
|
+
const clientIp = getClientIpFromHeaders({ get: (n) => c.req.header(n) ?? void 0 });
|
|
46
|
+
let record = null;
|
|
47
|
+
let innerPath = url.pathname;
|
|
48
|
+
let basePrefix = "";
|
|
49
|
+
if (label) {
|
|
50
|
+
record = store.resolveByHostLabel(label);
|
|
51
|
+
if (!record) return next();
|
|
52
|
+
} else if (url.pathname.startsWith(SITE_SUBPATH_PREFIX)) {
|
|
53
|
+
const rest = url.pathname.slice(6);
|
|
54
|
+
const slash = rest.indexOf("/");
|
|
55
|
+
const token = slash < 0 ? rest : rest.slice(0, slash);
|
|
56
|
+
const tail = slash < 0 ? "" : rest.slice(slash);
|
|
57
|
+
record = store.getByToken(token) ?? store.getBySubdomain(token);
|
|
58
|
+
if (!record) return next();
|
|
59
|
+
innerPath = tail || "/";
|
|
60
|
+
basePrefix = `${SITE_SUBPATH_PREFIX}${token}`;
|
|
61
|
+
} else return next();
|
|
62
|
+
const validation = store.validateAccess(record);
|
|
63
|
+
if (!validation.valid) return c.html(renderShareExpiredPage(validation.reason), 410);
|
|
64
|
+
if (record.source.kind === "static") try {
|
|
65
|
+
const result = await serveStaticSiteRequest(record, innerPath, basePrefix);
|
|
66
|
+
store.recordRequest(record.id, clientIp);
|
|
67
|
+
return result;
|
|
68
|
+
} catch (err) {
|
|
69
|
+
log.warn({
|
|
70
|
+
err,
|
|
71
|
+
shareId: record.id
|
|
72
|
+
}, "static serve failed");
|
|
73
|
+
return new Response("Internal error", { status: 500 });
|
|
74
|
+
}
|
|
75
|
+
if (record.source.kind === "proxy") {
|
|
76
|
+
const ctx = {
|
|
77
|
+
innerPath,
|
|
78
|
+
basePrefix,
|
|
79
|
+
clientIp,
|
|
80
|
+
originalUrl: url,
|
|
81
|
+
forwardedProto: url.protocol === "https:" ? "https" : "http"
|
|
82
|
+
};
|
|
83
|
+
const { response } = await forwardHttpRequest(record, store, c.req.raw, ctx);
|
|
84
|
+
return response;
|
|
85
|
+
}
|
|
86
|
+
return next();
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** Standalone HTTP upgrade handler that bridges WebSocket requests to the proxy share. */
|
|
90
|
+
function handleSiteShareUpgrade(service, req, socket, head) {
|
|
91
|
+
const config = resolveSiteShareConfig(service);
|
|
92
|
+
if (!config.enabled) return false;
|
|
93
|
+
if (!config.proxy.enabled || !config.proxy.forwardWebSocket) return false;
|
|
94
|
+
const store = getSiteShareStore(config);
|
|
95
|
+
store.updateConfig(config);
|
|
96
|
+
const host = (req.headers.host ?? "").toString();
|
|
97
|
+
const label = extractSiteShareLabel(host, config.publicHostSuffix);
|
|
98
|
+
const url = new URL(req.url ?? "/", `http://${host || "localhost"}`);
|
|
99
|
+
let record = null;
|
|
100
|
+
let innerPath = url.pathname;
|
|
101
|
+
let basePrefix = "";
|
|
102
|
+
if (label) record = store.resolveByHostLabel(label);
|
|
103
|
+
else if (url.pathname.startsWith(SITE_SUBPATH_PREFIX)) {
|
|
104
|
+
const rest = url.pathname.slice(6);
|
|
105
|
+
const slash = rest.indexOf("/");
|
|
106
|
+
const token = slash < 0 ? rest : rest.slice(0, slash);
|
|
107
|
+
const tail = slash < 0 ? "" : rest.slice(slash);
|
|
108
|
+
record = store.getByToken(token) ?? store.getBySubdomain(token);
|
|
109
|
+
innerPath = tail || "/";
|
|
110
|
+
basePrefix = `${SITE_SUBPATH_PREFIX}${token}`;
|
|
111
|
+
}
|
|
112
|
+
if (!record) return false;
|
|
113
|
+
if (!store.validateAccess(record).valid) {
|
|
114
|
+
try {
|
|
115
|
+
socket.write("HTTP/1.1 410 Gone\r\nConnection: close\r\n\r\n");
|
|
116
|
+
} catch {}
|
|
117
|
+
socket.destroy();
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
if (record.source.kind !== "proxy") {
|
|
121
|
+
try {
|
|
122
|
+
socket.write("HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n");
|
|
123
|
+
} catch {}
|
|
124
|
+
socket.destroy();
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
const clientIp = (req.socket.remoteAddress ?? "").replace(/^::ffff:/, "") || "0.0.0.0";
|
|
128
|
+
handleProxyWebSocketUpgrade({
|
|
129
|
+
record,
|
|
130
|
+
store,
|
|
131
|
+
req,
|
|
132
|
+
socket,
|
|
133
|
+
head,
|
|
134
|
+
innerPath,
|
|
135
|
+
basePrefix,
|
|
136
|
+
clientIp
|
|
137
|
+
});
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
/** Register the middleware on a Hono app — call from createHonoApp. */
|
|
141
|
+
function registerSiteShareMiddleware(app, service) {
|
|
142
|
+
app.use(createSiteShareMiddleware(service));
|
|
143
|
+
}
|
|
144
|
+
//#endregion
|
|
145
|
+
export { createSiteShareMiddleware, extractSiteShareLabel, handleSiteShareUpgrade, registerSiteShareMiddleware };
|
|
146
|
+
|
|
147
|
+
//# sourceMappingURL=site-share-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"site-share-router.js","names":[],"sources":["../../../src/share/site-share-router.ts"],"sourcesContent":["import type { Context, Hono } from 'hono';\n\nimport { resolveMimeType } from './share-store.js';\nimport { getSiteShareStore } from './site-share-store.js';\nimport { resolveSiteShareConfig } from './site-share-config.js';\nimport { serveStaticSiteRequest } from './site-static-serve.js';\nimport { forwardHttpRequest, handleProxyWebSocketUpgrade } from './site-proxy.js';\nimport { renderShareExpiredPage } from './share-landing.js';\nimport { getClientIpFromHeaders } from '../gateway/security/loopback.js';\nimport { createLogger } from '../utils/logger.js';\nimport type { GatewayService } from '../gateway/service.js';\nimport type { IncomingMessage } from 'node:http';\nimport type { Socket } from 'node:net';\n\nconst log = createLogger('SiteShareRouter');\n\nconst SITE_SUBPATH_PREFIX = '/site/';\n\n/**\n * Extract the share label from the request Host header when the public host\n * suffix matches. Returns `null` if the request did not target a site-share\n * subdomain.\n */\nexport function extractSiteShareLabel(host: string | null | undefined, suffix: string): string | null {\n if (!host) return null;\n const lowered = host.toLowerCase().split(':')[0];\n const normSuffix = suffix.toLowerCase();\n if (lowered === normSuffix) return null;\n if (!lowered.endsWith(`.${normSuffix}`)) return null;\n const label = lowered.slice(0, -1 * (normSuffix.length + 1));\n if (!label || label.includes('.')) return null;\n return label;\n}\n\n/**\n * Hono middleware. Intercepts requests whose Host header is `*.share.xopc.ai`\n * (or matches the configured suffix), routes them to the matching site share.\n * Falls through to next() when the host does not match.\n *\n * Also handles the subpath fallback `/site/:token/*` for environments without\n * wildcard DNS — same handler logic, just different prefix extraction.\n */\nexport function createSiteShareMiddleware(service: GatewayService) {\n return async (c: Context, next: () => Promise<void>) => {\n const config = resolveSiteShareConfig(service);\n if (!config.enabled) return next();\n const store = getSiteShareStore(config);\n store.updateConfig(config);\n\n const host = c.req.header('host');\n const label = extractSiteShareLabel(host, config.publicHostSuffix);\n const url = new URL(c.req.url);\n const clientIp = getClientIpFromHeaders({ get: (n: string) => c.req.header(n) ?? undefined });\n\n let record = null as ReturnType<typeof store.getByToken>;\n let innerPath = url.pathname;\n let basePrefix = '';\n\n if (label) {\n record = store.resolveByHostLabel(label);\n if (!record) return next();\n } else if (url.pathname.startsWith(SITE_SUBPATH_PREFIX)) {\n const rest = url.pathname.slice(SITE_SUBPATH_PREFIX.length);\n const slash = rest.indexOf('/');\n const token = slash < 0 ? rest : rest.slice(0, slash);\n const tail = slash < 0 ? '' : rest.slice(slash);\n record = store.getByToken(token) ?? store.getBySubdomain(token);\n if (!record) return next();\n innerPath = tail || '/';\n basePrefix = `${SITE_SUBPATH_PREFIX}${token}`;\n } else {\n return next();\n }\n\n const validation = store.validateAccess(record);\n if (!validation.valid) {\n return c.html(renderShareExpiredPage(validation.reason as never), 410);\n }\n\n if (record.source.kind === 'static') {\n try {\n const result = await serveStaticSiteRequest(record, innerPath, basePrefix);\n store.recordRequest(record.id, clientIp);\n return result;\n } catch (err) {\n log.warn({ err, shareId: record.id }, 'static serve failed');\n return new Response('Internal error', { status: 500 });\n }\n }\n\n if (record.source.kind === 'proxy') {\n const ctx = {\n innerPath,\n basePrefix,\n clientIp,\n originalUrl: url,\n forwardedProto: url.protocol === 'https:' ? ('https' as const) : ('http' as const),\n };\n const { response } = await forwardHttpRequest(record, store, c.req.raw, ctx);\n return response;\n }\n\n return next();\n };\n}\n\n/** Standalone HTTP upgrade handler that bridges WebSocket requests to the proxy share. */\nexport function handleSiteShareUpgrade(\n service: GatewayService,\n req: IncomingMessage,\n socket: Socket,\n head: Buffer,\n): boolean {\n const config = resolveSiteShareConfig(service);\n if (!config.enabled) return false;\n if (!config.proxy.enabled || !config.proxy.forwardWebSocket) return false;\n\n const store = getSiteShareStore(config);\n store.updateConfig(config);\n\n const host = (req.headers.host ?? '').toString();\n const label = extractSiteShareLabel(host, config.publicHostSuffix);\n const url = new URL(req.url ?? '/', `http://${host || 'localhost'}`);\n\n let record: ReturnType<typeof store.getByToken> = null;\n let innerPath = url.pathname;\n let basePrefix = '';\n if (label) {\n record = store.resolveByHostLabel(label);\n } else if (url.pathname.startsWith(SITE_SUBPATH_PREFIX)) {\n const rest = url.pathname.slice(SITE_SUBPATH_PREFIX.length);\n const slash = rest.indexOf('/');\n const token = slash < 0 ? rest : rest.slice(0, slash);\n const tail = slash < 0 ? '' : rest.slice(slash);\n record = store.getByToken(token) ?? store.getBySubdomain(token);\n innerPath = tail || '/';\n basePrefix = `${SITE_SUBPATH_PREFIX}${token}`;\n }\n if (!record) return false;\n const validation = store.validateAccess(record);\n if (!validation.valid) {\n try {\n socket.write('HTTP/1.1 410 Gone\\r\\nConnection: close\\r\\n\\r\\n');\n } catch {\n /* ignore */\n }\n socket.destroy();\n return true;\n }\n if (record.source.kind !== 'proxy') {\n try {\n socket.write('HTTP/1.1 400 Bad Request\\r\\nConnection: close\\r\\n\\r\\n');\n } catch {\n /* ignore */\n }\n socket.destroy();\n return true;\n }\n\n const clientIp = (req.socket.remoteAddress ?? '').replace(/^::ffff:/, '') || '0.0.0.0';\n handleProxyWebSocketUpgrade({\n record,\n store,\n req,\n socket,\n head,\n innerPath,\n basePrefix,\n clientIp,\n });\n return true;\n}\n\n/** Register the middleware on a Hono app — call from createHonoApp. */\nexport function registerSiteShareMiddleware(app: Hono, service: GatewayService): void {\n app.use(createSiteShareMiddleware(service));\n}\n\nvoid resolveMimeType;\n"],"mappings":";;;;;;;;;;aASkD;AAKlD,MAAM,MAAM,aAAa,kBAAkB;AAE3C,MAAM,sBAAsB;;;;;;AAO5B,SAAgB,sBAAsB,MAAiC,QAA+B;AACpG,KAAI,CAAC,KAAM,QAAO;CAClB,MAAM,UAAU,KAAK,aAAa,CAAC,MAAM,IAAI,CAAC;CAC9C,MAAM,aAAa,OAAO,aAAa;AACvC,KAAI,YAAY,WAAY,QAAO;AACnC,KAAI,CAAC,QAAQ,SAAS,IAAI,aAAa,CAAE,QAAO;CAChD,MAAM,QAAQ,QAAQ,MAAM,GAAG,MAAM,WAAW,SAAS,GAAG;AAC5D,KAAI,CAAC,SAAS,MAAM,SAAS,IAAI,CAAE,QAAO;AAC1C,QAAO;;;;;;;;;;AAWT,SAAgB,0BAA0B,SAAyB;AACjE,QAAO,OAAO,GAAY,SAA8B;EACtD,MAAM,SAAS,uBAAuB,QAAQ;AAC9C,MAAI,CAAC,OAAO,QAAS,QAAO,MAAM;EAClC,MAAM,QAAQ,kBAAkB,OAAO;AACvC,QAAM,aAAa,OAAO;EAG1B,MAAM,QAAQ,sBADD,EAAE,IAAI,OAAO,OACc,EAAE,OAAO,iBAAiB;EAClE,MAAM,MAAM,IAAI,IAAI,EAAE,IAAI,IAAI;EAC9B,MAAM,WAAW,uBAAuB,EAAE,MAAM,MAAc,EAAE,IAAI,OAAO,EAAE,IAAI,KAAA,GAAW,CAAC;EAE7F,IAAI,SAAS;EACb,IAAI,YAAY,IAAI;EACpB,IAAI,aAAa;AAEjB,MAAI,OAAO;AACT,YAAS,MAAM,mBAAmB,MAAM;AACxC,OAAI,CAAC,OAAQ,QAAO,MAAM;aACjB,IAAI,SAAS,WAAW,oBAAoB,EAAE;GACvD,MAAM,OAAO,IAAI,SAAS,MAAM,EAA2B;GAC3D,MAAM,QAAQ,KAAK,QAAQ,IAAI;GAC/B,MAAM,QAAQ,QAAQ,IAAI,OAAO,KAAK,MAAM,GAAG,MAAM;GACrD,MAAM,OAAO,QAAQ,IAAI,KAAK,KAAK,MAAM,MAAM;AAC/C,YAAS,MAAM,WAAW,MAAM,IAAI,MAAM,eAAe,MAAM;AAC/D,OAAI,CAAC,OAAQ,QAAO,MAAM;AAC1B,eAAY,QAAQ;AACpB,gBAAa,GAAG,sBAAsB;QAEtC,QAAO,MAAM;EAGf,MAAM,aAAa,MAAM,eAAe,OAAO;AAC/C,MAAI,CAAC,WAAW,MACd,QAAO,EAAE,KAAK,uBAAuB,WAAW,OAAgB,EAAE,IAAI;AAGxE,MAAI,OAAO,OAAO,SAAS,SACzB,KAAI;GACF,MAAM,SAAS,MAAM,uBAAuB,QAAQ,WAAW,WAAW;AAC1E,SAAM,cAAc,OAAO,IAAI,SAAS;AACxC,UAAO;WACA,KAAK;AACZ,OAAI,KAAK;IAAE;IAAK,SAAS,OAAO;IAAI,EAAE,sBAAsB;AAC5D,UAAO,IAAI,SAAS,kBAAkB,EAAE,QAAQ,KAAK,CAAC;;AAI1D,MAAI,OAAO,OAAO,SAAS,SAAS;GAClC,MAAM,MAAM;IACV;IACA;IACA;IACA,aAAa;IACb,gBAAgB,IAAI,aAAa,WAAY,UAAqB;IACnE;GACD,MAAM,EAAE,aAAa,MAAM,mBAAmB,QAAQ,OAAO,EAAE,IAAI,KAAK,IAAI;AAC5E,UAAO;;AAGT,SAAO,MAAM;;;;AAKjB,SAAgB,uBACd,SACA,KACA,QACA,MACS;CACT,MAAM,SAAS,uBAAuB,QAAQ;AAC9C,KAAI,CAAC,OAAO,QAAS,QAAO;AAC5B,KAAI,CAAC,OAAO,MAAM,WAAW,CAAC,OAAO,MAAM,iBAAkB,QAAO;CAEpE,MAAM,QAAQ,kBAAkB,OAAO;AACvC,OAAM,aAAa,OAAO;CAE1B,MAAM,QAAQ,IAAI,QAAQ,QAAQ,IAAI,UAAU;CAChD,MAAM,QAAQ,sBAAsB,MAAM,OAAO,iBAAiB;CAClE,MAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,QAAQ,cAAc;CAEpE,IAAI,SAA8C;CAClD,IAAI,YAAY,IAAI;CACpB,IAAI,aAAa;AACjB,KAAI,MACF,UAAS,MAAM,mBAAmB,MAAM;UAC/B,IAAI,SAAS,WAAW,oBAAoB,EAAE;EACvD,MAAM,OAAO,IAAI,SAAS,MAAM,EAA2B;EAC3D,MAAM,QAAQ,KAAK,QAAQ,IAAI;EAC/B,MAAM,QAAQ,QAAQ,IAAI,OAAO,KAAK,MAAM,GAAG,MAAM;EACrD,MAAM,OAAO,QAAQ,IAAI,KAAK,KAAK,MAAM,MAAM;AAC/C,WAAS,MAAM,WAAW,MAAM,IAAI,MAAM,eAAe,MAAM;AAC/D,cAAY,QAAQ;AACpB,eAAa,GAAG,sBAAsB;;AAExC,KAAI,CAAC,OAAQ,QAAO;AAEpB,KAAI,CADe,MAAM,eAAe,OACzB,CAAC,OAAO;AACrB,MAAI;AACF,UAAO,MAAM,iDAAiD;UACxD;AAGR,SAAO,SAAS;AAChB,SAAO;;AAET,KAAI,OAAO,OAAO,SAAS,SAAS;AAClC,MAAI;AACF,UAAO,MAAM,wDAAwD;UAC/D;AAGR,SAAO,SAAS;AAChB,SAAO;;CAGT,MAAM,YAAY,IAAI,OAAO,iBAAiB,IAAI,QAAQ,YAAY,GAAG,IAAI;AAC7E,6BAA4B;EAC1B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;AACF,QAAO;;;AAIT,SAAgB,4BAA4B,KAAW,SAA+B;AACpF,KAAI,IAAI,0BAA0B,QAAQ,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type CreateSiteShareParams, type SiteShareConfig, type SiteShareRecord } from './site-share-types.js';
|
|
2
|
+
export declare class SiteShareStore {
|
|
3
|
+
private shares;
|
|
4
|
+
private byToken;
|
|
5
|
+
private bySubdomain;
|
|
6
|
+
private config;
|
|
7
|
+
private dirty;
|
|
8
|
+
private debounceTimer;
|
|
9
|
+
private cleanupTimer;
|
|
10
|
+
/** Optional cleanup hook invoked when a record is dropped (e.g. delete staging dir). */
|
|
11
|
+
private onCleanup;
|
|
12
|
+
constructor(config?: Partial<SiteShareConfig>);
|
|
13
|
+
updateConfig(config: Partial<SiteShareConfig>): void;
|
|
14
|
+
setCleanupHook(hook: (record: SiteShareRecord) => void): void;
|
|
15
|
+
getConfig(): SiteShareConfig;
|
|
16
|
+
create(params: CreateSiteShareParams & {
|
|
17
|
+
workspaceRoot: string | null;
|
|
18
|
+
gatewayTokenHash: string;
|
|
19
|
+
}): Promise<SiteShareRecord>;
|
|
20
|
+
private buildSource;
|
|
21
|
+
private resolveSubdomain;
|
|
22
|
+
getById(id: string): SiteShareRecord | null;
|
|
23
|
+
getByToken(token: string): SiteShareRecord | null;
|
|
24
|
+
getBySubdomain(subdomain: string): SiteShareRecord | null;
|
|
25
|
+
/**
|
|
26
|
+
* Look up a record by token first, then by subdomain. Subdomain label may
|
|
27
|
+
* itself be the share token (when user didn't pick a custom subdomain).
|
|
28
|
+
*/
|
|
29
|
+
resolveByHostLabel(label: string): SiteShareRecord | null;
|
|
30
|
+
getActiveShares(): SiteShareRecord[];
|
|
31
|
+
getAllShares(): SiteShareRecord[];
|
|
32
|
+
validateAccess(record: SiteShareRecord): {
|
|
33
|
+
valid: boolean;
|
|
34
|
+
reason?: string;
|
|
35
|
+
};
|
|
36
|
+
/** Increment counters when a request lands. Debounced persist. */
|
|
37
|
+
recordRequest(id: string, clientIp: string): void;
|
|
38
|
+
setThumbnailStatus(id: string, status: 'pending' | 'ready' | 'failed'): void;
|
|
39
|
+
revoke(id: string): boolean;
|
|
40
|
+
revokeMany(ids: string[]): number;
|
|
41
|
+
revokeExpired(): number;
|
|
42
|
+
update(id: string, patch: {
|
|
43
|
+
extendTtlMs?: number;
|
|
44
|
+
maxRequests?: number | null;
|
|
45
|
+
}): SiteShareRecord | null;
|
|
46
|
+
private load;
|
|
47
|
+
private persistSync;
|
|
48
|
+
private scheduleDebouncedPersist;
|
|
49
|
+
private startCleanupTimer;
|
|
50
|
+
shutdown(): void;
|
|
51
|
+
}
|
|
52
|
+
export declare function getSiteShareStore(config?: Partial<SiteShareConfig>): SiteShareStore;
|
|
53
|
+
export declare function resetSiteShareStoreForTests(): void;
|