skybridge 0.0.0-dev.f840c22 → 0.0.0-dev.f864975
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 +123 -120
- package/dist/cli/build-helpers.d.ts +7 -0
- package/dist/cli/build-helpers.js +82 -0
- package/dist/cli/build-helpers.js.map +1 -0
- package/dist/cli/build-helpers.test.js +64 -0
- package/dist/cli/build-helpers.test.js.map +1 -0
- package/dist/cli/detect-port.d.ts +2 -2
- package/dist/cli/detect-port.js +9 -20
- package/dist/cli/detect-port.js.map +1 -1
- package/dist/cli/header.js +1 -1
- package/dist/cli/header.js.map +1 -1
- package/dist/cli/resolve-views-dir.d.ts +1 -0
- package/dist/cli/resolve-views-dir.js +17 -0
- package/dist/cli/resolve-views-dir.js.map +1 -0
- package/dist/cli/run-command.js.map +1 -1
- package/dist/cli/telemetry.js.map +1 -1
- package/dist/cli/tunnel-control-server.d.ts +9 -0
- package/dist/cli/tunnel-control-server.js +31 -0
- package/dist/cli/tunnel-control-server.js.map +1 -0
- package/dist/cli/tunnel-control-server.test.js +39 -0
- package/dist/cli/tunnel-control-server.test.js.map +1 -0
- package/dist/cli/tunnel-handler.d.ts +3 -0
- package/dist/cli/tunnel-handler.js +48 -0
- package/dist/cli/tunnel-handler.js.map +1 -0
- package/dist/cli/tunnel-handler.test.js +105 -0
- package/dist/cli/tunnel-handler.test.js.map +1 -0
- package/dist/cli/tunnel.d.ts +57 -0
- package/dist/cli/tunnel.js +154 -0
- package/dist/cli/tunnel.js.map +1 -0
- package/dist/cli/tunnel.test.d.ts +1 -0
- package/dist/cli/tunnel.test.js +190 -0
- package/dist/cli/tunnel.test.js.map +1 -0
- package/dist/cli/types.d.ts +5 -0
- package/dist/cli/types.js +2 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/cli/use-execute-steps.js.map +1 -1
- package/dist/cli/use-messages.d.ts +3 -0
- package/dist/cli/use-messages.js +11 -0
- package/dist/cli/use-messages.js.map +1 -0
- package/dist/cli/use-nodemon.d.ts +2 -7
- package/dist/cli/use-nodemon.js +18 -21
- package/dist/cli/use-nodemon.js.map +1 -1
- package/dist/cli/use-open-browser.d.ts +1 -0
- package/dist/cli/use-open-browser.js +44 -0
- package/dist/cli/use-open-browser.js.map +1 -0
- package/dist/cli/use-tunnel.d.ts +14 -0
- package/dist/cli/use-tunnel.js +131 -0
- package/dist/cli/use-tunnel.js.map +1 -0
- package/dist/cli/use-typescript-check.d.ts +1 -0
- package/dist/cli/use-typescript-check.js +42 -7
- package/dist/cli/use-typescript-check.js.map +1 -1
- package/dist/commands/build.d.ts +0 -1
- package/dist/commands/build.js +52 -8
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/create.d.ts +9 -0
- package/dist/commands/create.js +30 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/dev.d.ts +3 -0
- package/dist/commands/dev.js +63 -3
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/start.js +13 -10
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/telemetry/disable.js.map +1 -1
- package/dist/commands/telemetry/enable.js.map +1 -1
- package/dist/commands/telemetry/status.js.map +1 -1
- package/dist/server/asset-base-url-transform-plugin.d.ts +6 -6
- package/dist/server/asset-base-url-transform-plugin.js +25 -11
- package/dist/server/asset-base-url-transform-plugin.js.map +1 -1
- package/dist/server/asset-base-url-transform-plugin.test.js +92 -14
- package/dist/server/asset-base-url-transform-plugin.test.js.map +1 -1
- package/dist/server/auth/discovery.d.ts +11 -0
- package/dist/server/auth/discovery.js +38 -0
- package/dist/server/auth/discovery.js.map +1 -0
- package/dist/server/auth/discovery.test.d.ts +1 -0
- package/dist/server/auth/discovery.test.js +81 -0
- package/dist/server/auth/discovery.test.js.map +1 -0
- package/dist/server/auth/index.d.ts +18 -0
- package/dist/server/auth/index.js +2 -0
- package/dist/server/auth/index.js.map +1 -0
- package/dist/server/auth/providers/custom.d.ts +14 -0
- package/dist/server/auth/providers/custom.js +28 -0
- package/dist/server/auth/providers/custom.js.map +1 -0
- package/dist/server/auth/providers/custom.test.d.ts +1 -0
- package/dist/server/auth/providers/custom.test.js +113 -0
- package/dist/server/auth/providers/custom.test.js.map +1 -0
- package/dist/server/auth/providers/descope.d.ts +13 -0
- package/dist/server/auth/providers/descope.js +14 -0
- package/dist/server/auth/providers/descope.js.map +1 -0
- package/dist/server/auth/providers/descope.test.d.ts +1 -0
- package/dist/server/auth/providers/descope.test.js +35 -0
- package/dist/server/auth/providers/descope.test.js.map +1 -0
- package/dist/server/auth/providers/stytch.d.ts +11 -0
- package/dist/server/auth/providers/stytch.js +17 -0
- package/dist/server/auth/providers/stytch.js.map +1 -0
- package/dist/server/auth/providers/stytch.test.d.ts +1 -0
- package/dist/server/auth/providers/stytch.test.js +33 -0
- package/dist/server/auth/providers/stytch.test.js.map +1 -0
- package/dist/server/auth/providers/workos.d.ts +10 -0
- package/dist/server/auth/providers/workos.js +16 -0
- package/dist/server/auth/providers/workos.js.map +1 -0
- package/dist/server/auth/providers/workos.test.d.ts +1 -0
- package/dist/server/auth/providers/workos.test.js +41 -0
- package/dist/server/auth/providers/workos.test.js.map +1 -0
- package/dist/server/auth/setup.d.ts +4 -0
- package/dist/server/auth/setup.js +51 -0
- package/dist/server/auth/setup.js.map +1 -0
- package/dist/server/auth/setup.test.d.ts +1 -0
- package/dist/server/auth/setup.test.js +185 -0
- package/dist/server/auth/setup.test.js.map +1 -0
- package/dist/server/auth/verify.d.ts +11 -0
- package/dist/server/auth/verify.js +37 -0
- package/dist/server/auth/verify.js.map +1 -0
- package/dist/server/auth/verify.test.d.ts +1 -0
- package/dist/server/auth/verify.test.js +87 -0
- package/dist/server/auth/verify.test.js.map +1 -0
- package/dist/server/auth.d.ts +20 -0
- package/dist/server/auth.js +28 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/build-manifest.test.d.ts +1 -0
- package/dist/server/build-manifest.test.js +27 -0
- package/dist/server/build-manifest.test.js.map +1 -0
- package/dist/server/content-helpers.d.ts +67 -0
- package/dist/server/content-helpers.js +79 -0
- package/dist/server/content-helpers.js.map +1 -0
- package/dist/server/content-helpers.test.d.ts +1 -0
- package/dist/server/content-helpers.test.js +70 -0
- package/dist/server/content-helpers.test.js.map +1 -0
- package/dist/server/express.d.ts +4 -4
- package/dist/server/express.js +51 -22
- package/dist/server/express.js.map +1 -1
- package/dist/server/express.test.js +433 -22
- package/dist/server/express.test.js.map +1 -1
- package/dist/server/file-ref.d.ts +28 -0
- package/dist/server/file-ref.js +27 -0
- package/dist/server/file-ref.js.map +1 -0
- package/dist/server/index.d.ts +11 -3
- package/dist/server/index.js +9 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/inferUtilityTypes.d.ts +6 -6
- package/dist/server/inferUtilityTypes.js.map +1 -1
- package/dist/server/metric.d.ts +14 -0
- package/dist/server/metric.js +62 -0
- package/dist/server/metric.js.map +1 -0
- package/dist/server/middleware.d.ts +16 -3
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/middleware.test-d.js.map +1 -1
- package/dist/server/middleware.test.js +115 -5
- package/dist/server/middleware.test.js.map +1 -1
- package/dist/server/requestOrigin.d.ts +7 -0
- package/dist/server/requestOrigin.js +25 -0
- package/dist/server/requestOrigin.js.map +1 -0
- package/dist/server/server.d.ts +327 -71
- package/dist/server/server.js +491 -125
- package/dist/server/server.js.map +1 -1
- package/dist/server/templateHelper.d.ts +5 -7
- package/dist/server/templateHelper.js +3 -22
- package/dist/server/templateHelper.js.map +1 -1
- package/dist/server/templates.generated.d.ts +4 -0
- package/dist/server/templates.generated.js +47 -0
- package/dist/server/templates.generated.js.map +1 -0
- package/dist/server/tunnel-proxy-router.d.ts +7 -0
- package/dist/server/tunnel-proxy-router.js +110 -0
- package/dist/server/tunnel-proxy-router.js.map +1 -0
- package/dist/server/tunnel-proxy-router.test.d.ts +1 -0
- package/dist/server/tunnel-proxy-router.test.js +229 -0
- package/dist/server/tunnel-proxy-router.test.js.map +1 -0
- package/dist/server/view-resource-resolution.test.d.ts +6 -0
- package/dist/server/view-resource-resolution.test.js +88 -0
- package/dist/server/view-resource-resolution.test.js.map +1 -0
- package/dist/server/viewsDevServer.d.ts +14 -0
- package/dist/server/viewsDevServer.js +45 -0
- package/dist/server/viewsDevServer.js.map +1 -0
- package/dist/test/utils.d.ts +13 -21
- package/dist/test/utils.js +42 -37
- package/dist/test/utils.js.map +1 -1
- package/dist/test/view.test.d.ts +1 -0
- package/dist/test/view.test.js +568 -0
- package/dist/test/view.test.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/dist/web/bridges/apps-sdk/adaptor.d.ts +11 -4
- package/dist/web/bridges/apps-sdk/adaptor.js +59 -17
- package/dist/web/bridges/apps-sdk/adaptor.js.map +1 -1
- package/dist/web/bridges/apps-sdk/bridge.d.ts +1 -0
- package/dist/web/bridges/apps-sdk/bridge.js +1 -0
- package/dist/web/bridges/apps-sdk/bridge.js.map +1 -1
- package/dist/web/bridges/apps-sdk/index.js.map +1 -1
- package/dist/web/bridges/apps-sdk/types.d.ts +18 -6
- package/dist/web/bridges/apps-sdk/types.js.map +1 -1
- package/dist/web/bridges/apps-sdk/use-apps-sdk-context.d.ts +11 -0
- package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js +11 -0
- package/dist/web/bridges/apps-sdk/use-apps-sdk-context.js.map +1 -1
- package/dist/web/bridges/get-adaptor.d.ts +7 -0
- package/dist/web/bridges/get-adaptor.js +7 -0
- package/dist/web/bridges/get-adaptor.js.map +1 -1
- package/dist/web/bridges/index.js.map +1 -1
- package/dist/web/bridges/mcp-app/adaptor.d.ts +21 -6
- package/dist/web/bridges/mcp-app/adaptor.js +127 -28
- package/dist/web/bridges/mcp-app/adaptor.js.map +1 -1
- package/dist/web/bridges/mcp-app/bridge.d.ts +4 -2
- package/dist/web/bridges/mcp-app/bridge.js +23 -1
- package/dist/web/bridges/mcp-app/bridge.js.map +1 -1
- package/dist/web/bridges/mcp-app/index.js.map +1 -1
- package/dist/web/bridges/mcp-app/types.js.map +1 -1
- package/dist/web/bridges/mcp-app/use-mcp-app-context.d.ts +12 -0
- package/dist/web/bridges/mcp-app/use-mcp-app-context.js +12 -0
- package/dist/web/bridges/mcp-app/use-mcp-app-context.js.map +1 -1
- package/dist/web/bridges/mcp-app/use-mcp-app-context.test.js.map +1 -1
- package/dist/web/bridges/mcp-app/view-tools.test.d.ts +1 -0
- package/dist/web/bridges/mcp-app/view-tools.test.js +144 -0
- package/dist/web/bridges/mcp-app/view-tools.test.js.map +1 -0
- package/dist/web/bridges/types.d.ts +113 -11
- package/dist/web/bridges/types.js.map +1 -1
- package/dist/web/bridges/use-host-context.d.ts +5 -0
- package/dist/web/bridges/use-host-context.js +5 -0
- package/dist/web/bridges/use-host-context.js.map +1 -1
- package/dist/web/components/modal-provider.js +3 -5
- package/dist/web/components/modal-provider.js.map +1 -1
- package/dist/web/create-store.d.ts +26 -0
- package/dist/web/create-store.js +43 -3
- package/dist/web/create-store.js.map +1 -1
- package/dist/web/create-store.test.js +14 -16
- package/dist/web/create-store.test.js.map +1 -1
- package/dist/web/data-llm.d.ts +34 -1
- package/dist/web/data-llm.js +31 -3
- package/dist/web/data-llm.js.map +1 -1
- package/dist/web/data-llm.test.js +22 -22
- package/dist/web/data-llm.test.js.map +1 -1
- package/dist/web/generate-helpers.d.ts +22 -18
- package/dist/web/generate-helpers.js +22 -18
- package/dist/web/generate-helpers.js.map +1 -1
- package/dist/web/generate-helpers.test-d.js +30 -28
- package/dist/web/generate-helpers.test-d.js.map +1 -1
- package/dist/web/generate-helpers.test.js.map +1 -1
- package/dist/web/helpers/state.d.ts +2 -2
- package/dist/web/helpers/state.js +11 -11
- package/dist/web/helpers/state.js.map +1 -1
- package/dist/web/helpers/state.test.js +9 -9
- package/dist/web/helpers/state.test.js.map +1 -1
- package/dist/web/hooks/index.d.ts +5 -1
- package/dist/web/hooks/index.js +5 -1
- package/dist/web/hooks/index.js.map +1 -1
- package/dist/web/hooks/test/utils.d.ts +6 -2
- package/dist/web/hooks/test/utils.js +13 -2
- package/dist/web/hooks/test/utils.js.map +1 -1
- package/dist/web/hooks/use-call-tool.d.ts +45 -0
- package/dist/web/hooks/use-call-tool.js +28 -0
- package/dist/web/hooks/use-call-tool.js.map +1 -1
- package/dist/web/hooks/use-call-tool.test-d.js.map +1 -1
- package/dist/web/hooks/use-call-tool.test.js +27 -6
- package/dist/web/hooks/use-call-tool.test.js.map +1 -1
- package/dist/web/hooks/use-display-mode.d.ts +20 -0
- package/dist/web/hooks/use-display-mode.js +20 -0
- package/dist/web/hooks/use-display-mode.js.map +1 -1
- package/dist/web/hooks/use-display-mode.test-d.js.map +1 -1
- package/dist/web/hooks/use-display-mode.test.js.map +1 -1
- package/dist/web/hooks/use-download.d.ts +5 -0
- package/dist/web/hooks/use-download.js +8 -0
- package/dist/web/hooks/use-download.js.map +1 -0
- package/dist/web/hooks/use-download.test.d.ts +1 -0
- package/dist/web/hooks/use-download.test.js +95 -0
- package/dist/web/hooks/use-download.test.js.map +1 -0
- package/dist/web/hooks/use-files.d.ts +34 -1
- package/dist/web/hooks/use-files.js +33 -0
- package/dist/web/hooks/use-files.js.map +1 -1
- package/dist/web/hooks/use-files.test.js +22 -2
- package/dist/web/hooks/use-files.test.js.map +1 -1
- package/dist/web/hooks/use-layout.d.ts +2 -0
- package/dist/web/hooks/use-layout.js +2 -0
- package/dist/web/hooks/use-layout.js.map +1 -1
- package/dist/web/hooks/use-layout.test.js.map +1 -1
- package/dist/web/hooks/use-open-external.d.ts +17 -0
- package/dist/web/hooks/use-open-external.js +16 -0
- package/dist/web/hooks/use-open-external.js.map +1 -1
- package/dist/web/hooks/use-open-external.test.js.map +1 -1
- package/dist/web/hooks/use-register-view-tool.d.ts +38 -0
- package/dist/web/hooks/use-register-view-tool.js +50 -0
- package/dist/web/hooks/use-register-view-tool.js.map +1 -0
- package/dist/web/hooks/use-request-close.d.ts +16 -0
- package/dist/web/hooks/use-request-close.js +21 -0
- package/dist/web/hooks/use-request-close.js.map +1 -0
- package/dist/web/hooks/use-request-close.test.d.ts +1 -0
- package/dist/web/hooks/use-request-close.test.js +52 -0
- package/dist/web/hooks/use-request-close.test.js.map +1 -0
- package/dist/web/hooks/use-request-modal.d.ts +16 -1
- package/dist/web/hooks/use-request-modal.js +19 -4
- package/dist/web/hooks/use-request-modal.js.map +1 -1
- package/dist/web/hooks/use-request-modal.test.js +5 -1
- package/dist/web/hooks/use-request-modal.test.js.map +1 -1
- package/dist/web/hooks/use-request-size.d.ts +20 -0
- package/dist/web/hooks/use-request-size.js +24 -0
- package/dist/web/hooks/use-request-size.js.map +1 -0
- package/dist/web/hooks/use-request-size.test.d.ts +1 -0
- package/dist/web/hooks/use-request-size.test.js +65 -0
- package/dist/web/hooks/use-request-size.test.js.map +1 -0
- package/dist/web/hooks/use-send-follow-up-message.d.ts +19 -1
- package/dist/web/hooks/use-send-follow-up-message.js +19 -2
- package/dist/web/hooks/use-send-follow-up-message.js.map +1 -1
- package/dist/web/hooks/use-set-open-in-app-url.d.ts +17 -0
- package/dist/web/hooks/use-set-open-in-app-url.js +17 -0
- package/dist/web/hooks/use-set-open-in-app-url.js.map +1 -1
- package/dist/web/hooks/use-set-open-in-app-url.test.js.map +1 -1
- package/dist/web/hooks/use-tool-info.d.ts +53 -2
- package/dist/web/hooks/use-tool-info.js +30 -7
- package/dist/web/hooks/use-tool-info.js.map +1 -1
- package/dist/web/hooks/use-tool-info.test-d.js +11 -29
- package/dist/web/hooks/use-tool-info.test-d.js.map +1 -1
- package/dist/web/hooks/use-tool-info.test.js +5 -5
- package/dist/web/hooks/use-tool-info.test.js.map +1 -1
- package/dist/web/hooks/use-user.d.ts +2 -0
- package/dist/web/hooks/use-user.js +20 -2
- package/dist/web/hooks/use-user.js.map +1 -1
- package/dist/web/hooks/use-user.test.js +28 -0
- package/dist/web/hooks/use-user.test.js.map +1 -1
- package/dist/web/hooks/use-view-state.d.ts +25 -0
- package/dist/web/hooks/use-view-state.js +32 -0
- package/dist/web/hooks/use-view-state.js.map +1 -0
- package/dist/web/hooks/use-view-state.test.d.ts +1 -0
- package/dist/web/hooks/use-view-state.test.js +177 -0
- package/dist/web/hooks/use-view-state.test.js.map +1 -0
- package/dist/web/index.d.ts +1 -2
- package/dist/web/index.js +1 -2
- package/dist/web/index.js.map +1 -1
- package/dist/web/mount-view.d.ts +20 -0
- package/dist/web/{mount-widget.js → mount-view.js} +21 -2
- package/dist/web/mount-view.js.map +1 -0
- package/dist/web/plugin/data-llm.test.js.map +1 -1
- package/dist/web/plugin/plugin.d.ts +32 -1
- package/dist/web/plugin/plugin.js +160 -25
- package/dist/web/plugin/plugin.js.map +1 -1
- package/dist/web/plugin/scan-views.d.ts +16 -0
- package/dist/web/plugin/scan-views.js +88 -0
- package/dist/web/plugin/scan-views.js.map +1 -0
- package/dist/web/plugin/scan-views.test.d.ts +1 -0
- package/dist/web/plugin/scan-views.test.js +99 -0
- package/dist/web/plugin/scan-views.test.js.map +1 -0
- package/dist/web/plugin/transform-data-llm.js +1 -1
- package/dist/web/plugin/transform-data-llm.js.map +1 -1
- package/dist/web/plugin/transform-data-llm.test.js.map +1 -1
- package/dist/web/plugin/validate-view.d.ts +1 -0
- package/dist/web/plugin/validate-view.js +9 -0
- package/dist/web/plugin/validate-view.js.map +1 -0
- package/dist/web/plugin/validate-view.test.d.ts +1 -0
- package/dist/web/plugin/validate-view.test.js +24 -0
- package/dist/web/plugin/validate-view.test.js.map +1 -0
- package/dist/web/proxy.js.map +1 -1
- package/dist/web/types.d.ts +4 -0
- package/dist/web/types.js.map +1 -1
- package/package.json +34 -17
- package/tsconfig.base.json +5 -0
- package/dist/server/templates/development.hbs +0 -12
- package/dist/server/templates/production.hbs +0 -6
- package/dist/server/widgetsDevServer.d.ts +0 -13
- package/dist/server/widgetsDevServer.js +0 -57
- package/dist/server/widgetsDevServer.js.map +0 -1
- package/dist/test/widget.test.js +0 -263
- package/dist/test/widget.test.js.map +0 -1
- package/dist/web/hooks/use-widget-state.d.ts +0 -4
- package/dist/web/hooks/use-widget-state.js +0 -32
- package/dist/web/hooks/use-widget-state.js.map +0 -1
- package/dist/web/hooks/use-widget-state.test.js +0 -64
- package/dist/web/hooks/use-widget-state.test.js.map +0 -1
- package/dist/web/mount-widget.d.ts +0 -1
- package/dist/web/mount-widget.js.map +0 -1
- package/dist/web/plugin/validate-widget.d.ts +0 -5
- package/dist/web/plugin/validate-widget.js +0 -27
- package/dist/web/plugin/validate-widget.js.map +0 -1
- package/dist/web/plugin/validate-widget.test.js +0 -42
- package/dist/web/plugin/validate-widget.test.js.map +0 -1
- /package/dist/{test/widget.test.d.ts → cli/build-helpers.test.d.ts} +0 -0
- /package/dist/{web/hooks/use-widget-state.test.d.ts → cli/tunnel-control-server.test.d.ts} +0 -0
- /package/dist/{web/plugin/validate-widget.test.d.ts → cli/tunnel-handler.test.d.ts} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-base-url-transform-plugin.js","sourceRoot":"","sources":["../../src/server/asset-base-url-transform-plugin.ts"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"asset-base-url-transform-plugin.js","sourceRoot":"","sources":["../../src/server/asset-base-url-transform-plugin.ts"],"names":[],"mappings":"AAEA,0EAA0E;AAC1E,6EAA6E;AAC7E,uFAAuF;AACvF,MAAM,YAAY,GAChB,6DAA6D,CAAC;AAEhE,MAAM,UAAU,YAAY,CAAC,EAAU;IACrC,OAAO,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC/B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAChD,MAAM,kBAAkB,GACtB,4GAA4G,CAAC;IAE/G,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE;QACpE,OAAO,0CAA0C,SAAS,GAAG,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,2BAA2B;IACzC,OAAO;QACL,IAAI,EAAE,0BAA0B;QAChC,SAAS,CAAC,IAAI,EAAE,EAAE;YAChB,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,OAAO,IAAI,CAAC;YACd,CAAC;YAED,uEAAuE;YACvE,oEAAoE;YACpE,mEAAmE;YACnE,mEAAmE;YACnE,4DAA4D;YAC5D,IAAI,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC;gBACrB,OAAO,IAAI,CAAC;YACd,CAAC;YAED,MAAM,eAAe,GAAG,qBAAqB,CAAC,IAAI,CAAC,CAAC;YAEpD,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;gBAC7B,OAAO,IAAI,CAAC;YACd,CAAC;YAED,OAAO;gBACL,IAAI,EAAE,eAAe;gBACrB,GAAG,EAAE,IAAI;aACV,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { Plugin } from \"vite\";\n\n// Mirrors Vite's own `isCSSRequest`: matches the css family of extensions\n// either at the end of the id or right before the query string. Catches both\n// plain `.css` requests and SFC style blocks (e.g. `Foo.vue?vue&type=style&lang.css`).\nconst CSS_LANGS_RE =\n /\\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\\?)/;\n\nexport function isCssRequest(id: string): boolean {\n return CSS_LANGS_RE.test(id);\n}\n\n/**\n * Transforms asset import paths to resolve at runtime via `window.skybridge.serverUrl`,\n * so they work both locally and behind tunnels.\n */\nexport function assetBaseUrlTransform(code: string): string {\n const assetStringPattern =\n /(?<!\\bfrom\\s)(?<!https?:\\/\\/)([\"'`])(\\/[^\"'`]+\\.(svg|png|jpeg|jpg|gif|webp|mp3|mp4|woff|woff2|ttf|eot))\\1/g;\n\n code = code.replace(assetStringPattern, (_match, _quote, assetPath) => {\n return `(window.skybridge?.serverUrl ?? \"\") + \"${assetPath}\"`;\n });\n\n return code;\n}\n\n/**\n * Vite plugin that transforms asset import paths to resolve at runtime via `window.skybridge.serverUrl`.\n */\nexport function assetBaseUrlTransformPlugin(): Plugin {\n return {\n name: \"asset-base-url-transform\",\n transform(code, id) {\n if (!code) {\n return null;\n }\n\n // Vite serves CSS modules as JS that embeds the stylesheet as a string\n // literal. Rewriting `url(\"/foo.woff2\")` inside that string to a JS\n // concatenation expression produces invalid CSS once it lands in a\n // <style> tag. CSS asset URLs are already handled at build time by\n // `experimental.renderBuiltUrl`, so skip CSS requests here.\n if (isCssRequest(id)) {\n return null;\n }\n\n const transformedCode = assetBaseUrlTransform(code);\n\n if (transformedCode === code) {\n return null;\n }\n\n return {\n code: transformedCode,\n map: null,\n };\n },\n };\n}\n"]}
|
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { assetBaseUrlTransform } from "./asset-base-url-transform-plugin.js";
|
|
2
|
+
import { assetBaseUrlTransform, assetBaseUrlTransformPlugin, isCssRequest, } from "./asset-base-url-transform-plugin.js";
|
|
3
3
|
describe("assetBaseUrlTransform", () => {
|
|
4
|
-
|
|
5
|
-
it("should transform asset paths in single, double, and backtick quotes", () => {
|
|
4
|
+
it("should transform asset paths to use window.skybridge.serverUrl", () => {
|
|
6
5
|
const cases = [
|
|
7
6
|
{
|
|
8
7
|
desc: "single-quoted",
|
|
9
8
|
code: `const image = '/assets/logo.png';`,
|
|
10
|
-
expected: `const image =
|
|
9
|
+
expected: `const image = (window.skybridge?.serverUrl ?? "") + "/assets/logo.png";`,
|
|
11
10
|
},
|
|
12
11
|
{
|
|
13
12
|
desc: "double-quoted",
|
|
14
13
|
code: `const image = "/assets/logo.png";`,
|
|
15
|
-
expected: `const image = "
|
|
14
|
+
expected: `const image = (window.skybridge?.serverUrl ?? "") + "/assets/logo.png";`,
|
|
16
15
|
},
|
|
17
16
|
{
|
|
18
17
|
desc: "backtick-quoted",
|
|
19
18
|
code: "const image = `/assets/logo.png`;",
|
|
20
|
-
expected: `const image =
|
|
19
|
+
expected: `const image = (window.skybridge?.serverUrl ?? "") + "/assets/logo.png";`,
|
|
21
20
|
},
|
|
22
21
|
];
|
|
23
22
|
for (const { code, expected } of cases) {
|
|
24
|
-
const result = assetBaseUrlTransform(code
|
|
23
|
+
const result = assetBaseUrlTransform(code);
|
|
25
24
|
expect(result).toBe(expected);
|
|
26
25
|
}
|
|
27
26
|
});
|
|
@@ -31,10 +30,10 @@ describe("assetBaseUrlTransform", () => {
|
|
|
31
30
|
const icon = '/assets/icon.svg';
|
|
32
31
|
const font = '/assets/font.woff2';
|
|
33
32
|
`;
|
|
34
|
-
const result = assetBaseUrlTransform(code
|
|
35
|
-
expect(result).toContain(
|
|
36
|
-
expect(result).toContain(
|
|
37
|
-
expect(result).toContain(
|
|
33
|
+
const result = assetBaseUrlTransform(code);
|
|
34
|
+
expect(result).toContain(`(window.skybridge?.serverUrl ?? "") + "/assets/logo.png"`);
|
|
35
|
+
expect(result).toContain(`(window.skybridge?.serverUrl ?? "") + "/assets/icon.svg"`);
|
|
36
|
+
expect(result).toContain(`(window.skybridge?.serverUrl ?? "") + "/assets/font.woff2"`);
|
|
38
37
|
});
|
|
39
38
|
it("should not transform already absolute URLs", () => {
|
|
40
39
|
const code = `
|
|
@@ -42,15 +41,94 @@ describe("assetBaseUrlTransform", () => {
|
|
|
42
41
|
const http = 'http://example.com/image.png';
|
|
43
42
|
const https = 'https://example.com/image.png';
|
|
44
43
|
`;
|
|
45
|
-
const result = assetBaseUrlTransform(code
|
|
46
|
-
expect(result).toContain(
|
|
44
|
+
const result = assetBaseUrlTransform(code);
|
|
45
|
+
expect(result).toContain(`(window.skybridge?.serverUrl ?? "") + "/assets/logo.png"`);
|
|
47
46
|
expect(result).toContain("http://example.com/image.png");
|
|
48
47
|
expect(result).toContain("https://example.com/image.png");
|
|
49
48
|
});
|
|
50
49
|
it("should not transform code without asset paths", () => {
|
|
51
50
|
const code = `const text = "Hello World";`;
|
|
52
|
-
const result = assetBaseUrlTransform(code
|
|
51
|
+
const result = assetBaseUrlTransform(code);
|
|
53
52
|
expect(result).toBe(code);
|
|
54
53
|
});
|
|
54
|
+
it("should not transform asset paths inside static `import ... from` clauses", () => {
|
|
55
|
+
// Reproducer for #713: a dep does `import * as sprite from './icons.svg'`,
|
|
56
|
+
// Vite resolves the relative path to absolute, then this transform used
|
|
57
|
+
// to rewrite the resolved string — producing invalid JS like
|
|
58
|
+
// `import * as sprite from (expr) + "..."` that crashes vite:import-analysis.
|
|
59
|
+
const cases = [
|
|
60
|
+
`import * as sprite from "/Users/me/proj/node_modules/pkg/icons.svg";`,
|
|
61
|
+
`import sprite from '/assets/icons.svg';`,
|
|
62
|
+
`import sprite from "/assets/icons.svg";`,
|
|
63
|
+
`export { default } from "/assets/icons.svg";`,
|
|
64
|
+
`export * from '/assets/sprites.svg';`,
|
|
65
|
+
];
|
|
66
|
+
for (const code of cases) {
|
|
67
|
+
expect(assetBaseUrlTransform(code)).toBe(code);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
it("should still transform value-position asset paths in files that also have unrelated imports", () => {
|
|
71
|
+
const code = [
|
|
72
|
+
`import { foo } from "./foo.js";`,
|
|
73
|
+
`import * as sprite from "/assets/sprite.svg";`,
|
|
74
|
+
`const logo = "/assets/logo.png";`,
|
|
75
|
+
].join("\n");
|
|
76
|
+
const result = assetBaseUrlTransform(code);
|
|
77
|
+
// Imports untouched
|
|
78
|
+
expect(result).toContain(`from "./foo.js"`);
|
|
79
|
+
expect(result).toContain(`from "/assets/sprite.svg"`);
|
|
80
|
+
// Value-position rewritten
|
|
81
|
+
expect(result).toContain(`const logo = (window.skybridge?.serverUrl ?? "") + "/assets/logo.png";`);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
describe("isCssRequest", () => {
|
|
85
|
+
it("returns true for CSS-family extensions", () => {
|
|
86
|
+
expect(isCssRequest("/src/styles.css")).toBe(true);
|
|
87
|
+
expect(isCssRequest("/src/styles.module.css")).toBe(true);
|
|
88
|
+
expect(isCssRequest("/src/styles.scss")).toBe(true);
|
|
89
|
+
expect(isCssRequest("/src/styles.sass")).toBe(true);
|
|
90
|
+
expect(isCssRequest("/src/styles.less")).toBe(true);
|
|
91
|
+
expect(isCssRequest("/src/styles.styl")).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
it("returns true for CSS modules with Vite query strings", () => {
|
|
94
|
+
expect(isCssRequest("/src/styles.css?direct")).toBe(true);
|
|
95
|
+
expect(isCssRequest("/src/styles.css?inline")).toBe(true);
|
|
96
|
+
expect(isCssRequest("/src/styles.css?used")).toBe(true);
|
|
97
|
+
expect(isCssRequest("/src/Foo.vue?vue&type=style&lang.css")).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
it("returns false for non-CSS modules", () => {
|
|
100
|
+
expect(isCssRequest("/src/index.tsx")).toBe(false);
|
|
101
|
+
expect(isCssRequest("/src/utils.ts")).toBe(false);
|
|
102
|
+
expect(isCssRequest("/src/Logo.svg")).toBe(false);
|
|
103
|
+
expect(isCssRequest("/src/notes.cssx")).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe("assetBaseUrlTransformPlugin", () => {
|
|
107
|
+
function runTransform(id, code) {
|
|
108
|
+
const plugin = assetBaseUrlTransformPlugin();
|
|
109
|
+
const hook = plugin.transform;
|
|
110
|
+
if (!hook) {
|
|
111
|
+
throw new Error("plugin.transform is not defined");
|
|
112
|
+
}
|
|
113
|
+
const handler = typeof hook === "function" ? hook : hook.handler;
|
|
114
|
+
return handler.call(
|
|
115
|
+
// biome-ignore lint/suspicious/noExplicitAny: vitest harness for plugin hook
|
|
116
|
+
{}, code, id, { moduleType: "js" });
|
|
117
|
+
}
|
|
118
|
+
it("rewrites asset paths in JS modules", () => {
|
|
119
|
+
const result = runTransform("/src/widget.tsx", `const logo = "/assets/logo.png";`);
|
|
120
|
+
expect(result?.code).toContain(`(window.skybridge?.serverUrl ?? "") + "/assets/logo.png"`);
|
|
121
|
+
});
|
|
122
|
+
// Reproducer for #697: CSS imports are served as JS modules with the
|
|
123
|
+
// stylesheet embedded as a string. Rewriting url("/foo.woff2") inside that
|
|
124
|
+
// string would produce invalid CSS once the styles are injected.
|
|
125
|
+
it("does not rewrite asset paths when transforming a CSS module", () => {
|
|
126
|
+
const cssCode = `__vite__updateStyle("style-id", "@font-face { src: url(\\"/fonts/Brand.woff2\\") format(\\"woff2\\"); }");`;
|
|
127
|
+
expect(runTransform("/src/fonts.css", cssCode)).toBeNull();
|
|
128
|
+
expect(runTransform("/src/fonts.css?direct", cssCode)).toBeNull();
|
|
129
|
+
expect(runTransform("/src/fonts.css?inline", cssCode)).toBeNull();
|
|
130
|
+
expect(runTransform("/src/styles.scss", cssCode)).toBeNull();
|
|
131
|
+
expect(runTransform("/src/Foo.vue?vue&type=style&lang.css", cssCode)).toBeNull();
|
|
132
|
+
});
|
|
55
133
|
});
|
|
56
134
|
//# sourceMappingURL=asset-base-url-transform-plugin.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-base-url-transform-plugin.test.js","sourceRoot":"","sources":["../../src/server/asset-base-url-transform-plugin.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAE7E,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,MAAM,eAAe,GAAG,uBAAuB,CAAC;IAEhD,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,MAAM,KAAK,GAAG;YACZ;gBACE,IAAI,EAAE,eAAe;gBACrB,IAAI,EAAE,mCAAmC;gBACzC,QAAQ,EAAE,kBAAkB,eAAe,oBAAoB;aAChE;YACD;gBACE,IAAI,EAAE,eAAe;gBACrB,IAAI,EAAE,mCAAmC;gBACzC,QAAQ,EAAE,kBAAkB,eAAe,oBAAoB;aAChE;YACD;gBACE,IAAI,EAAE,iBAAiB;gBACvB,IAAI,EAAE,mCAAmC;gBACzC,QAAQ,EAAE,mBAAmB,eAAe,qBAAqB;aAClE;SACF,CAAC;QAEF,KAAK,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,KAAK,EAAE,CAAC;YACvC,MAAM,MAAM,GAAG,qBAAqB,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;YAC5D,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAChC,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,IAAI,GAAG;;;;KAIZ,CAAC;QACF,MAAM,MAAM,GAAG,qBAAqB,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;QAE5D,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,GAAG,eAAe,kBAAkB,CAAC,CAAC;QAC/D,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,GAAG,eAAe,kBAAkB,CAAC,CAAC;QAC/D,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,GAAG,eAAe,oBAAoB,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,IAAI,GAAG;;;;KAIZ,CAAC;QACF,MAAM,MAAM,GAAG,qBAAqB,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;QAE5D,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,GAAG,eAAe,kBAAkB,CAAC,CAAC;QAC/D,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,8BAA8B,CAAC,CAAC;QACzD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,+BAA+B,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,IAAI,GAAG,6BAA6B,CAAC;QAC3C,MAAM,MAAM,GAAG,qBAAqB,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;QAE5D,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"asset-base-url-transform-plugin.test.js","sourceRoot":"","sources":["../../src/server/asset-base-url-transform-plugin.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport {\n assetBaseUrlTransform,\n assetBaseUrlTransformPlugin,\n isCssRequest,\n} from \"./asset-base-url-transform-plugin.js\";\n\ndescribe(\"assetBaseUrlTransform\", () => {\n it(\"should transform asset paths to use window.skybridge.serverUrl\", () => {\n const cases = [\n {\n desc: \"single-quoted\",\n code: `const image = '/assets/logo.png';`,\n expected: `const image = (window.skybridge?.serverUrl ?? \"\") + \"/assets/logo.png\";`,\n },\n {\n desc: \"double-quoted\",\n code: `const image = \"/assets/logo.png\";`,\n expected: `const image = (window.skybridge?.serverUrl ?? \"\") + \"/assets/logo.png\";`,\n },\n {\n desc: \"backtick-quoted\",\n code: \"const image = `/assets/logo.png`;\",\n expected: `const image = (window.skybridge?.serverUrl ?? \"\") + \"/assets/logo.png\";`,\n },\n ];\n\n for (const { code, expected } of cases) {\n const result = assetBaseUrlTransform(code);\n expect(result).toBe(expected);\n }\n });\n\n it(\"should transform multiple asset paths\", () => {\n const code = `\n const logo = '/assets/logo.png';\n const icon = '/assets/icon.svg';\n const font = '/assets/font.woff2';\n `;\n const result = assetBaseUrlTransform(code);\n\n expect(result).toContain(\n `(window.skybridge?.serverUrl ?? \"\") + \"/assets/logo.png\"`,\n );\n expect(result).toContain(\n `(window.skybridge?.serverUrl ?? \"\") + \"/assets/icon.svg\"`,\n );\n expect(result).toContain(\n `(window.skybridge?.serverUrl ?? \"\") + \"/assets/font.woff2\"`,\n );\n });\n\n it(\"should not transform already absolute URLs\", () => {\n const code = `\n const local = '/assets/logo.png';\n const http = 'http://example.com/image.png';\n const https = 'https://example.com/image.png';\n `;\n const result = assetBaseUrlTransform(code);\n\n expect(result).toContain(\n `(window.skybridge?.serverUrl ?? \"\") + \"/assets/logo.png\"`,\n );\n expect(result).toContain(\"http://example.com/image.png\");\n expect(result).toContain(\"https://example.com/image.png\");\n });\n\n it(\"should not transform code without asset paths\", () => {\n const code = `const text = \"Hello World\";`;\n const result = assetBaseUrlTransform(code);\n\n expect(result).toBe(code);\n });\n\n it(\"should not transform asset paths inside static `import ... from` clauses\", () => {\n // Reproducer for #713: a dep does `import * as sprite from './icons.svg'`,\n // Vite resolves the relative path to absolute, then this transform used\n // to rewrite the resolved string — producing invalid JS like\n // `import * as sprite from (expr) + \"...\"` that crashes vite:import-analysis.\n const cases = [\n `import * as sprite from \"/Users/me/proj/node_modules/pkg/icons.svg\";`,\n `import sprite from '/assets/icons.svg';`,\n `import sprite from \"/assets/icons.svg\";`,\n `export { default } from \"/assets/icons.svg\";`,\n `export * from '/assets/sprites.svg';`,\n ];\n\n for (const code of cases) {\n expect(assetBaseUrlTransform(code)).toBe(code);\n }\n });\n\n it(\"should still transform value-position asset paths in files that also have unrelated imports\", () => {\n const code = [\n `import { foo } from \"./foo.js\";`,\n `import * as sprite from \"/assets/sprite.svg\";`,\n `const logo = \"/assets/logo.png\";`,\n ].join(\"\\n\");\n const result = assetBaseUrlTransform(code);\n\n // Imports untouched\n expect(result).toContain(`from \"./foo.js\"`);\n expect(result).toContain(`from \"/assets/sprite.svg\"`);\n // Value-position rewritten\n expect(result).toContain(\n `const logo = (window.skybridge?.serverUrl ?? \"\") + \"/assets/logo.png\";`,\n );\n });\n});\n\ndescribe(\"isCssRequest\", () => {\n it(\"returns true for CSS-family extensions\", () => {\n expect(isCssRequest(\"/src/styles.css\")).toBe(true);\n expect(isCssRequest(\"/src/styles.module.css\")).toBe(true);\n expect(isCssRequest(\"/src/styles.scss\")).toBe(true);\n expect(isCssRequest(\"/src/styles.sass\")).toBe(true);\n expect(isCssRequest(\"/src/styles.less\")).toBe(true);\n expect(isCssRequest(\"/src/styles.styl\")).toBe(true);\n });\n\n it(\"returns true for CSS modules with Vite query strings\", () => {\n expect(isCssRequest(\"/src/styles.css?direct\")).toBe(true);\n expect(isCssRequest(\"/src/styles.css?inline\")).toBe(true);\n expect(isCssRequest(\"/src/styles.css?used\")).toBe(true);\n expect(isCssRequest(\"/src/Foo.vue?vue&type=style&lang.css\")).toBe(true);\n });\n\n it(\"returns false for non-CSS modules\", () => {\n expect(isCssRequest(\"/src/index.tsx\")).toBe(false);\n expect(isCssRequest(\"/src/utils.ts\")).toBe(false);\n expect(isCssRequest(\"/src/Logo.svg\")).toBe(false);\n expect(isCssRequest(\"/src/notes.cssx\")).toBe(false);\n });\n});\n\ndescribe(\"assetBaseUrlTransformPlugin\", () => {\n type TransformResult = { code: string; map: null } | null;\n\n function runTransform(id: string, code: string): TransformResult {\n const plugin = assetBaseUrlTransformPlugin();\n const hook = plugin.transform;\n if (!hook) {\n throw new Error(\"plugin.transform is not defined\");\n }\n const handler = typeof hook === \"function\" ? hook : hook.handler;\n return handler.call(\n // biome-ignore lint/suspicious/noExplicitAny: vitest harness for plugin hook\n {} as any,\n code,\n id,\n { moduleType: \"js\" },\n ) as TransformResult;\n }\n\n it(\"rewrites asset paths in JS modules\", () => {\n const result = runTransform(\n \"/src/widget.tsx\",\n `const logo = \"/assets/logo.png\";`,\n );\n expect(result?.code).toContain(\n `(window.skybridge?.serverUrl ?? \"\") + \"/assets/logo.png\"`,\n );\n });\n\n // Reproducer for #697: CSS imports are served as JS modules with the\n // stylesheet embedded as a string. Rewriting url(\"/foo.woff2\") inside that\n // string would produce invalid CSS once the styles are injected.\n it(\"does not rewrite asset paths when transforming a CSS module\", () => {\n const cssCode = `__vite__updateStyle(\"style-id\", \"@font-face { src: url(\\\\\"/fonts/Brand.woff2\\\\\") format(\\\\\"woff2\\\\\"); }\");`;\n\n expect(runTransform(\"/src/fonts.css\", cssCode)).toBeNull();\n expect(runTransform(\"/src/fonts.css?direct\", cssCode)).toBeNull();\n expect(runTransform(\"/src/fonts.css?inline\", cssCode)).toBeNull();\n expect(runTransform(\"/src/styles.scss\", cssCode)).toBeNull();\n expect(\n runTransform(\"/src/Foo.vue?vue&type=style&lang.css\", cssCode),\n ).toBeNull();\n });\n});\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
2
|
+
/**
|
|
3
|
+
* Discovered AS metadata + `jwks_uri` — the SDK's `OAuthMetadata` omits it (its
|
|
4
|
+
* OAuth client never verifies tokens), but we need it to verify token signatures
|
|
5
|
+
* via JWKS, so we re-type the field for typed access.
|
|
6
|
+
*/
|
|
7
|
+
export type DiscoveredMetadata = OAuthMetadata & {
|
|
8
|
+
jwks_uri?: string;
|
|
9
|
+
};
|
|
10
|
+
/** Fetches and validates an authorization server's discovery document. */
|
|
11
|
+
export declare function discoverAuthorizationServer(issuer: string): Promise<DiscoveredMetadata>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { OAuthMetadataSchema, } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
2
|
+
const WELL_KNOWN = [
|
|
3
|
+
"/.well-known/openid-configuration",
|
|
4
|
+
"/.well-known/oauth-authorization-server",
|
|
5
|
+
];
|
|
6
|
+
const DISCOVERY_TIMEOUT_MS = 5000;
|
|
7
|
+
/** Fetches and validates an authorization server's discovery document. */
|
|
8
|
+
export async function discoverAuthorizationServer(issuer) {
|
|
9
|
+
const base = issuer.replace(/\/$/, "");
|
|
10
|
+
const errors = [];
|
|
11
|
+
for (const path of WELL_KNOWN) {
|
|
12
|
+
const url = `${base}${path}`;
|
|
13
|
+
// Any failure (network/timeout, non-2xx, non-JSON, invalid metadata, issuer
|
|
14
|
+
// mismatch) records a per-URL reason and falls through to the next path.
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch(url, {
|
|
17
|
+
signal: AbortSignal.timeout(DISCOVERY_TIMEOUT_MS),
|
|
18
|
+
});
|
|
19
|
+
if (!res.ok) {
|
|
20
|
+
throw new Error(`HTTP ${res.status}`);
|
|
21
|
+
}
|
|
22
|
+
const parsed = OAuthMetadataSchema.safeParse(await res.json());
|
|
23
|
+
if (!parsed.success) {
|
|
24
|
+
throw new Error(`invalid metadata (${parsed.error.message})`);
|
|
25
|
+
}
|
|
26
|
+
// RFC 8414 §3.3: issuer must match the fetch origin (slash-insensitive).
|
|
27
|
+
if (parsed.data.issuer.replace(/\/$/, "") !== base) {
|
|
28
|
+
throw new Error(`issuer mismatch: ${parsed.data.issuer}`);
|
|
29
|
+
}
|
|
30
|
+
return parsed.data;
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
errors.push(`${url}: ${err instanceof Error ? err.message : String(err)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
throw new Error(`OAuth discovery failed for ${issuer}: ${errors.join("; ")}`);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../src/server/auth/discovery.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,mBAAmB,GACpB,MAAM,0CAA0C,CAAC;AASlD,MAAM,UAAU,GAAG;IACjB,mCAAmC;IACnC,yCAAyC;CAC1C,CAAC;AAEF,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAElC,0EAA0E;AAC1E,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,MAAc;IAEd,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACvC,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;QAC7B,4EAA4E;QAC5E,yEAAyE;QACzE,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAC3B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,oBAAoB,CAAC;aAClD,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YACxC,CAAC;YACD,MAAM,MAAM,GAAG,mBAAmB,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;YAC/D,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,MAAM,IAAI,KAAK,CAAC,qBAAqB,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC;YAChE,CAAC;YACD,yEAAyE;YACzE,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC;gBACnD,MAAM,IAAI,KAAK,CAAC,oBAAoB,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;YAC5D,CAAC;YACD,OAAO,MAAM,CAAC,IAA0B,CAAC;QAC3C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CACT,GAAG,GAAG,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAC9D,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,8BAA8B,MAAM,KAAK,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAChF,CAAC","sourcesContent":["import {\n type OAuthMetadata,\n OAuthMetadataSchema,\n} from \"@modelcontextprotocol/sdk/shared/auth.js\";\n\n/**\n * Discovered AS metadata + `jwks_uri` — the SDK's `OAuthMetadata` omits it (its\n * OAuth client never verifies tokens), but we need it to verify token signatures\n * via JWKS, so we re-type the field for typed access.\n */\nexport type DiscoveredMetadata = OAuthMetadata & { jwks_uri?: string };\n\nconst WELL_KNOWN = [\n \"/.well-known/openid-configuration\",\n \"/.well-known/oauth-authorization-server\",\n];\n\nconst DISCOVERY_TIMEOUT_MS = 5000;\n\n/** Fetches and validates an authorization server's discovery document. */\nexport async function discoverAuthorizationServer(\n issuer: string,\n): Promise<DiscoveredMetadata> {\n const base = issuer.replace(/\\/$/, \"\");\n const errors: string[] = [];\n\n for (const path of WELL_KNOWN) {\n const url = `${base}${path}`;\n // Any failure (network/timeout, non-2xx, non-JSON, invalid metadata, issuer\n // mismatch) records a per-URL reason and falls through to the next path.\n try {\n const res = await fetch(url, {\n signal: AbortSignal.timeout(DISCOVERY_TIMEOUT_MS),\n });\n if (!res.ok) {\n throw new Error(`HTTP ${res.status}`);\n }\n const parsed = OAuthMetadataSchema.safeParse(await res.json());\n if (!parsed.success) {\n throw new Error(`invalid metadata (${parsed.error.message})`);\n }\n // RFC 8414 §3.3: issuer must match the fetch origin (slash-insensitive).\n if (parsed.data.issuer.replace(/\\/$/, \"\") !== base) {\n throw new Error(`issuer mismatch: ${parsed.data.issuer}`);\n }\n return parsed.data as DiscoveredMetadata;\n } catch (err) {\n errors.push(\n `${url}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n throw new Error(`OAuth discovery failed for ${issuer}: ${errors.join(\"; \")}`);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
import http from "node:http";
|
|
3
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
4
|
+
import { discoverAuthorizationServer } from "./discovery.js";
|
|
5
|
+
let server;
|
|
6
|
+
afterEach(() => server?.close());
|
|
7
|
+
function doc(origin, extra = {}) {
|
|
8
|
+
return {
|
|
9
|
+
issuer: origin,
|
|
10
|
+
authorization_endpoint: `${origin}/authorize`,
|
|
11
|
+
token_endpoint: `${origin}/token`,
|
|
12
|
+
registration_endpoint: `${origin}/register`,
|
|
13
|
+
response_types_supported: ["code"],
|
|
14
|
+
scopes_supported: ["openid", "email"],
|
|
15
|
+
jwks_uri: `${origin}/jwks`,
|
|
16
|
+
...extra,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
// Serves JSON bodies built from the live origin at their well-known paths;
|
|
20
|
+
// returns the origin URL.
|
|
21
|
+
async function serve(build) {
|
|
22
|
+
let origin = "";
|
|
23
|
+
const srv = http.createServer((req, res) => {
|
|
24
|
+
const body = build(origin)[req.url ?? ""];
|
|
25
|
+
if (body === undefined) {
|
|
26
|
+
res.writeHead(404).end();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
res.setHeader("content-type", "application/json");
|
|
30
|
+
res.end(JSON.stringify(body));
|
|
31
|
+
});
|
|
32
|
+
await new Promise((resolve) => srv.listen(0, resolve));
|
|
33
|
+
server = srv;
|
|
34
|
+
origin = `http://localhost:${srv.address().port}`;
|
|
35
|
+
return origin;
|
|
36
|
+
}
|
|
37
|
+
describe("discoverAuthorizationServer", () => {
|
|
38
|
+
it("fetches and validates openid-configuration", async () => {
|
|
39
|
+
const base = await serve((o) => ({
|
|
40
|
+
"/.well-known/openid-configuration": doc(o),
|
|
41
|
+
}));
|
|
42
|
+
const meta = await discoverAuthorizationServer(base);
|
|
43
|
+
expect(meta.registration_endpoint).toBe(`${base}/register`);
|
|
44
|
+
expect(meta.jwks_uri).toBe(`${base}/jwks`);
|
|
45
|
+
});
|
|
46
|
+
it("accepts a canonically slash-terminated issuer", async () => {
|
|
47
|
+
const base = await serve((o) => ({
|
|
48
|
+
"/.well-known/openid-configuration": doc(o, { issuer: `${o}/` }),
|
|
49
|
+
}));
|
|
50
|
+
const meta = await discoverAuthorizationServer(base);
|
|
51
|
+
expect(meta.registration_endpoint).toBe(`${base}/register`);
|
|
52
|
+
});
|
|
53
|
+
it("falls back to oauth-authorization-server when oidc is 404", async () => {
|
|
54
|
+
const base = await serve((o) => ({
|
|
55
|
+
"/.well-known/oauth-authorization-server": doc(o),
|
|
56
|
+
}));
|
|
57
|
+
const meta = await discoverAuthorizationServer(base);
|
|
58
|
+
expect(meta.registration_endpoint).toBe(`${base}/register`);
|
|
59
|
+
});
|
|
60
|
+
it("throws when no well-known doc is reachable", async () => {
|
|
61
|
+
const base = await serve(() => ({}));
|
|
62
|
+
await expect(discoverAuthorizationServer(base)).rejects.toThrow(/discovery failed/i);
|
|
63
|
+
});
|
|
64
|
+
it("falls through to oauth-authorization-server when oidc doc is schema-invalid", async () => {
|
|
65
|
+
const base = await serve((o) => ({
|
|
66
|
+
"/.well-known/openid-configuration": doc(o, {
|
|
67
|
+
response_types_supported: undefined,
|
|
68
|
+
}),
|
|
69
|
+
"/.well-known/oauth-authorization-server": doc(o),
|
|
70
|
+
}));
|
|
71
|
+
const meta = await discoverAuthorizationServer(base);
|
|
72
|
+
expect(meta.registration_endpoint).toBe(`${base}/register`);
|
|
73
|
+
});
|
|
74
|
+
it("rejects a doc whose issuer does not match the fetch origin", async () => {
|
|
75
|
+
const base = await serve(() => ({
|
|
76
|
+
"/.well-known/openid-configuration": doc("https://evil.test"),
|
|
77
|
+
}));
|
|
78
|
+
await expect(discoverAuthorizationServer(base)).rejects.toThrow(/discovery failed/i);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
//# sourceMappingURL=discovery.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.test.js","sourceRoot":"","sources":["../../../src/server/auth/discovery.test.ts"],"names":[],"mappings":"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","sourcesContent":["// @vitest-environment node\nimport http from \"node:http\";\nimport { afterEach, describe, expect, it } from \"vitest\";\nimport { discoverAuthorizationServer } from \"./discovery.js\";\n\nlet server: http.Server | undefined;\nafterEach(() => server?.close());\n\nfunction doc(origin: string, extra: Record<string, unknown> = {}) {\n return {\n issuer: origin,\n authorization_endpoint: `${origin}/authorize`,\n token_endpoint: `${origin}/token`,\n registration_endpoint: `${origin}/register`,\n response_types_supported: [\"code\"],\n scopes_supported: [\"openid\", \"email\"],\n jwks_uri: `${origin}/jwks`,\n ...extra,\n };\n}\n\n// Serves JSON bodies built from the live origin at their well-known paths;\n// returns the origin URL.\nasync function serve(build: (origin: string) => Record<string, unknown>) {\n let origin = \"\";\n const srv = http.createServer((req, res) => {\n const body = build(origin)[req.url ?? \"\"];\n if (body === undefined) {\n res.writeHead(404).end();\n return;\n }\n res.setHeader(\"content-type\", \"application/json\");\n res.end(JSON.stringify(body));\n });\n await new Promise<void>((resolve) => srv.listen(0, resolve));\n server = srv;\n origin = `http://localhost:${(srv.address() as { port: number }).port}`;\n return origin;\n}\n\ndescribe(\"discoverAuthorizationServer\", () => {\n it(\"fetches and validates openid-configuration\", async () => {\n const base = await serve((o) => ({\n \"/.well-known/openid-configuration\": doc(o),\n }));\n const meta = await discoverAuthorizationServer(base);\n expect(meta.registration_endpoint).toBe(`${base}/register`);\n expect(meta.jwks_uri).toBe(`${base}/jwks`);\n });\n\n it(\"accepts a canonically slash-terminated issuer\", async () => {\n const base = await serve((o) => ({\n \"/.well-known/openid-configuration\": doc(o, { issuer: `${o}/` }),\n }));\n const meta = await discoverAuthorizationServer(base);\n expect(meta.registration_endpoint).toBe(`${base}/register`);\n });\n\n it(\"falls back to oauth-authorization-server when oidc is 404\", async () => {\n const base = await serve((o) => ({\n \"/.well-known/oauth-authorization-server\": doc(o),\n }));\n const meta = await discoverAuthorizationServer(base);\n expect(meta.registration_endpoint).toBe(`${base}/register`);\n });\n\n it(\"throws when no well-known doc is reachable\", async () => {\n const base = await serve(() => ({}));\n await expect(discoverAuthorizationServer(base)).rejects.toThrow(\n /discovery failed/i,\n );\n });\n\n it(\"falls through to oauth-authorization-server when oidc doc is schema-invalid\", async () => {\n const base = await serve((o) => ({\n \"/.well-known/openid-configuration\": doc(o, {\n response_types_supported: undefined,\n }),\n \"/.well-known/oauth-authorization-server\": doc(o),\n }));\n const meta = await discoverAuthorizationServer(base);\n expect(meta.registration_endpoint).toBe(`${base}/register`);\n });\n\n it(\"rejects a doc whose issuer does not match the fetch origin\", async () => {\n const base = await serve(() => ({\n \"/.well-known/openid-configuration\": doc(\"https://evil.test\"),\n }));\n await expect(discoverAuthorizationServer(base)).rejects.toThrow(\n /discovery failed/i,\n );\n });\n});\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
2
|
+
import type { JwksVerifyConfig } from "./verify.js";
|
|
3
|
+
/** Resource-server OAuth config for `SkybridgeServerOptions.oauth`. */
|
|
4
|
+
export type OAuthConfig = {
|
|
5
|
+
/**
|
|
6
|
+
* Public URL of this server; sets `resourceServerUrl` and the
|
|
7
|
+
* `resource_metadata` URL. When omitted, it is inferred per request from
|
|
8
|
+
* `x-forwarded-host`/`origin`/`host` headers.
|
|
9
|
+
*/
|
|
10
|
+
baseUrl?: string;
|
|
11
|
+
/** AS metadata served at `/.well-known/oauth-authorization-server`. */
|
|
12
|
+
oauthMetadata: OAuthMetadata;
|
|
13
|
+
verify: JwksVerifyConfig;
|
|
14
|
+
/** Scopes advertised in protected-resource metadata. */
|
|
15
|
+
scopesSupported?: string[];
|
|
16
|
+
/** Server-wide required-scope floor. */
|
|
17
|
+
requiredScopes?: string[];
|
|
18
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/server/auth/index.ts"],"names":[],"mappings":"","sourcesContent":["import type { OAuthMetadata } from \"@modelcontextprotocol/sdk/shared/auth.js\";\nimport type { JwksVerifyConfig } from \"./verify.js\";\n\n/** Resource-server OAuth config for `SkybridgeServerOptions.oauth`. */\nexport type OAuthConfig = {\n /**\n * Public URL of this server; sets `resourceServerUrl` and the\n * `resource_metadata` URL. When omitted, it is inferred per request from\n * `x-forwarded-host`/`origin`/`host` headers.\n */\n baseUrl?: string;\n /** AS metadata served at `/.well-known/oauth-authorization-server`. */\n oauthMetadata: OAuthMetadata;\n verify: JwksVerifyConfig;\n /** Scopes advertised in protected-resource metadata. */\n scopesSupported?: string[];\n /** Server-wide required-scope floor. */\n requiredScopes?: string[];\n};\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
|
|
2
|
+
import type { OAuthConfig } from "../index.js";
|
|
3
|
+
/** Options accepted by {@link customProvider} and the branded providers. */
|
|
4
|
+
export type CustomProviderOptions = {
|
|
5
|
+
issuer: string;
|
|
6
|
+
audience: string;
|
|
7
|
+
/** Omit to let the server infer the resource origin from request headers. */
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
scopes?: string[];
|
|
10
|
+
requiredScopes?: string[];
|
|
11
|
+
metadataOverrides?: Omit<Partial<OAuthMetadata>, "issuer">;
|
|
12
|
+
};
|
|
13
|
+
/** Builds a complete {@link OAuthConfig} for a DCR-compatible IdP via discovery. */
|
|
14
|
+
export declare function customProvider(opts: CustomProviderOptions): Promise<OAuthConfig>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { discoverAuthorizationServer, } from "../discovery.js";
|
|
2
|
+
/** Builds a complete {@link OAuthConfig} for a DCR-compatible IdP via discovery. */
|
|
3
|
+
export async function customProvider(opts) {
|
|
4
|
+
const discovered = await discoverAuthorizationServer(opts.issuer);
|
|
5
|
+
// The IdP's own discovery must advertise DCR and a JWKS; overrides can't fake it.
|
|
6
|
+
if (!discovered.registration_endpoint) {
|
|
7
|
+
throw new Error(`${opts.issuer} is not DCR-compatible (no registration_endpoint); customProvider only supports DCR IdPs.`);
|
|
8
|
+
}
|
|
9
|
+
if (!discovered.jwks_uri) {
|
|
10
|
+
throw new Error(`${opts.issuer} discovery has no jwks_uri; JWKS verification requires it.`);
|
|
11
|
+
}
|
|
12
|
+
// Overrides adjust only advertised metadata (e.g. proxied endpoints). The
|
|
13
|
+
// trust anchor (issuer, jwks_uri) always comes from validated discovery.
|
|
14
|
+
const { issuer: _issuer, jwks_uri: _jwks, ...overrides } = (opts.metadataOverrides ?? {});
|
|
15
|
+
const oauthMetadata = { ...discovered, ...overrides };
|
|
16
|
+
return {
|
|
17
|
+
baseUrl: opts.baseUrl,
|
|
18
|
+
oauthMetadata,
|
|
19
|
+
verify: {
|
|
20
|
+
issuer: discovered.issuer,
|
|
21
|
+
audience: opts.audience,
|
|
22
|
+
jwksUri: discovered.jwks_uri,
|
|
23
|
+
},
|
|
24
|
+
scopesSupported: opts.scopes ?? oauthMetadata.scopes_supported,
|
|
25
|
+
requiredScopes: opts.requiredScopes,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=custom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom.js","sourceRoot":"","sources":["../../../../src/server/auth/providers/custom.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,2BAA2B,GAC5B,MAAM,iBAAiB,CAAC;AAczB,oFAAoF;AACpF,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAA2B;IAE3B,MAAM,UAAU,GAAG,MAAM,2BAA2B,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAElE,kFAAkF;IAClF,IAAI,CAAC,UAAU,CAAC,qBAAqB,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,CAAC,MAAM,2FAA2F,CAC1G,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,CAAC,MAAM,4DAA4D,CAC3E,CAAC;IACJ,CAAC;IAED,0EAA0E;IAC1E,yEAAyE;IACzE,MAAM,EACJ,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,KAAK,EACf,GAAG,SAAS,EACb,GAAG,CAAC,IAAI,CAAC,iBAAiB,IAAI,EAAE,CAAgC,CAAC;IAClE,MAAM,aAAa,GAAuB,EAAE,GAAG,UAAU,EAAE,GAAG,SAAS,EAAE,CAAC;IAE1E,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,aAAa;QACb,MAAM,EAAE;YACN,MAAM,EAAE,UAAU,CAAC,MAAM;YACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,OAAO,EAAE,UAAU,CAAC,QAAQ;SAC7B;QACD,eAAe,EAAE,IAAI,CAAC,MAAM,IAAI,aAAa,CAAC,gBAAgB;QAC9D,cAAc,EAAE,IAAI,CAAC,cAAc;KACpC,CAAC;AACJ,CAAC","sourcesContent":["import type { OAuthMetadata } from \"@modelcontextprotocol/sdk/shared/auth.js\";\nimport {\n type DiscoveredMetadata,\n discoverAuthorizationServer,\n} from \"../discovery.js\";\nimport type { OAuthConfig } from \"../index.js\";\n\n/** Options accepted by {@link customProvider} and the branded providers. */\nexport type CustomProviderOptions = {\n issuer: string;\n audience: string;\n /** Omit to let the server infer the resource origin from request headers. */\n baseUrl?: string;\n scopes?: string[];\n requiredScopes?: string[];\n metadataOverrides?: Omit<Partial<OAuthMetadata>, \"issuer\">;\n};\n\n/** Builds a complete {@link OAuthConfig} for a DCR-compatible IdP via discovery. */\nexport async function customProvider(\n opts: CustomProviderOptions,\n): Promise<OAuthConfig> {\n const discovered = await discoverAuthorizationServer(opts.issuer);\n\n // The IdP's own discovery must advertise DCR and a JWKS; overrides can't fake it.\n if (!discovered.registration_endpoint) {\n throw new Error(\n `${opts.issuer} is not DCR-compatible (no registration_endpoint); customProvider only supports DCR IdPs.`,\n );\n }\n if (!discovered.jwks_uri) {\n throw new Error(\n `${opts.issuer} discovery has no jwks_uri; JWKS verification requires it.`,\n );\n }\n\n // Overrides adjust only advertised metadata (e.g. proxied endpoints). The\n // trust anchor (issuer, jwks_uri) always comes from validated discovery.\n const {\n issuer: _issuer,\n jwks_uri: _jwks,\n ...overrides\n } = (opts.metadataOverrides ?? {}) as Partial<DiscoveredMetadata>;\n const oauthMetadata: DiscoveredMetadata = { ...discovered, ...overrides };\n\n return {\n baseUrl: opts.baseUrl,\n oauthMetadata,\n verify: {\n issuer: discovered.issuer,\n audience: opts.audience,\n jwksUri: discovered.jwks_uri,\n },\n scopesSupported: opts.scopes ?? oauthMetadata.scopes_supported,\n requiredScopes: opts.requiredScopes,\n };\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
import http from "node:http";
|
|
3
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
4
|
+
import { customProvider } from "./custom.js";
|
|
5
|
+
let server;
|
|
6
|
+
afterEach(() => server?.close());
|
|
7
|
+
function doc(origin, extra = {}) {
|
|
8
|
+
return {
|
|
9
|
+
issuer: origin,
|
|
10
|
+
authorization_endpoint: `${origin}/authorize`,
|
|
11
|
+
token_endpoint: `${origin}/token`,
|
|
12
|
+
registration_endpoint: `${origin}/register`,
|
|
13
|
+
response_types_supported: ["code"],
|
|
14
|
+
scopes_supported: ["openid", "email", "profile"],
|
|
15
|
+
jwks_uri: `${origin}/jwks`,
|
|
16
|
+
...extra,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
// Serves the discovery doc (built from the live origin) at the OIDC well-known path.
|
|
20
|
+
async function serveDiscovery(extra = {}) {
|
|
21
|
+
let origin = "";
|
|
22
|
+
const srv = http.createServer((req, res) => {
|
|
23
|
+
if (req.url !== "/.well-known/openid-configuration") {
|
|
24
|
+
res.writeHead(404).end();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
res.setHeader("content-type", "application/json");
|
|
28
|
+
res.end(JSON.stringify(doc(origin, extra)));
|
|
29
|
+
});
|
|
30
|
+
await new Promise((resolve) => srv.listen(0, resolve));
|
|
31
|
+
server = srv;
|
|
32
|
+
origin = `http://localhost:${srv.address().port}`;
|
|
33
|
+
return origin;
|
|
34
|
+
}
|
|
35
|
+
describe("customProvider", () => {
|
|
36
|
+
it("builds an OAuthConfig from discovery", async () => {
|
|
37
|
+
const base = await serveDiscovery();
|
|
38
|
+
const config = await customProvider({
|
|
39
|
+
issuer: base,
|
|
40
|
+
audience: "my-api",
|
|
41
|
+
baseUrl: "https://app.example.test",
|
|
42
|
+
scopes: ["openid"],
|
|
43
|
+
});
|
|
44
|
+
expect(config.baseUrl).toBe("https://app.example.test");
|
|
45
|
+
expect(config.verify).toEqual({
|
|
46
|
+
issuer: base,
|
|
47
|
+
audience: "my-api",
|
|
48
|
+
jwksUri: `${base}/jwks`,
|
|
49
|
+
});
|
|
50
|
+
expect(config.scopesSupported).toEqual(["openid"]);
|
|
51
|
+
expect(config.oauthMetadata.registration_endpoint).toBe(`${base}/register`);
|
|
52
|
+
});
|
|
53
|
+
it("defaults scopesSupported to the discovered scopes", async () => {
|
|
54
|
+
const base = await serveDiscovery();
|
|
55
|
+
const config = await customProvider({
|
|
56
|
+
issuer: base,
|
|
57
|
+
audience: "a",
|
|
58
|
+
baseUrl: "https://app.example.test",
|
|
59
|
+
});
|
|
60
|
+
expect(config.scopesSupported).toEqual(["openid", "email", "profile"]);
|
|
61
|
+
});
|
|
62
|
+
it("allows omitting baseUrl (server infers it from headers)", async () => {
|
|
63
|
+
const base = await serveDiscovery();
|
|
64
|
+
const config = await customProvider({ issuer: base, audience: "a" });
|
|
65
|
+
expect(config.baseUrl).toBeUndefined();
|
|
66
|
+
});
|
|
67
|
+
it("ignores a runtime issuer override (keeps the discovered trust anchor)", async () => {
|
|
68
|
+
const base = await serveDiscovery();
|
|
69
|
+
const config = await customProvider({
|
|
70
|
+
issuer: base,
|
|
71
|
+
audience: "a",
|
|
72
|
+
metadataOverrides: { issuer: "https://evil.test" },
|
|
73
|
+
});
|
|
74
|
+
expect(config.verify.issuer).toBe(base);
|
|
75
|
+
expect(config.oauthMetadata.issuer).toBe(base);
|
|
76
|
+
});
|
|
77
|
+
it("ignores a runtime jwks_uri override (keeps the discovered signing keys)", async () => {
|
|
78
|
+
const base = await serveDiscovery();
|
|
79
|
+
const config = await customProvider({
|
|
80
|
+
issuer: base,
|
|
81
|
+
audience: "a",
|
|
82
|
+
metadataOverrides: { jwks_uri: "https://evil.test/keys" },
|
|
83
|
+
});
|
|
84
|
+
expect(config.verify.jwksUri).toBe(`${base}/jwks`);
|
|
85
|
+
});
|
|
86
|
+
it("rejects a non-DCR IdP (no registration_endpoint)", async () => {
|
|
87
|
+
const base = await serveDiscovery({ registration_endpoint: undefined });
|
|
88
|
+
await expect(customProvider({
|
|
89
|
+
issuer: base,
|
|
90
|
+
audience: "a",
|
|
91
|
+
baseUrl: "https://app.example.test",
|
|
92
|
+
})).rejects.toThrow(/not DCR-compatible/);
|
|
93
|
+
});
|
|
94
|
+
it("rejects a non-DCR IdP even if an override supplies registration_endpoint", async () => {
|
|
95
|
+
const base = await serveDiscovery({ registration_endpoint: undefined });
|
|
96
|
+
await expect(customProvider({
|
|
97
|
+
issuer: base,
|
|
98
|
+
audience: "a",
|
|
99
|
+
metadataOverrides: { registration_endpoint: `${base}/register` },
|
|
100
|
+
})).rejects.toThrow(/not DCR-compatible/);
|
|
101
|
+
});
|
|
102
|
+
it("applies metadataOverrides over discovered values", async () => {
|
|
103
|
+
const base = await serveDiscovery();
|
|
104
|
+
const config = await customProvider({
|
|
105
|
+
issuer: base,
|
|
106
|
+
audience: "a",
|
|
107
|
+
baseUrl: "https://app.example.test",
|
|
108
|
+
metadataOverrides: { token_endpoint: "https://override/token" },
|
|
109
|
+
});
|
|
110
|
+
expect(config.oauthMetadata.token_endpoint).toBe("https://override/token");
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
//# sourceMappingURL=custom.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom.test.js","sourceRoot":"","sources":["../../../../src/server/auth/providers/custom.test.ts"],"names":[],"mappings":"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","sourcesContent":["// @vitest-environment node\nimport http from \"node:http\";\nimport { afterEach, describe, expect, it } from \"vitest\";\nimport { customProvider } from \"./custom.js\";\n\nlet server: http.Server | undefined;\nafterEach(() => server?.close());\n\nfunction doc(origin: string, extra: Record<string, unknown> = {}) {\n return {\n issuer: origin,\n authorization_endpoint: `${origin}/authorize`,\n token_endpoint: `${origin}/token`,\n registration_endpoint: `${origin}/register`,\n response_types_supported: [\"code\"],\n scopes_supported: [\"openid\", \"email\", \"profile\"],\n jwks_uri: `${origin}/jwks`,\n ...extra,\n };\n}\n\n// Serves the discovery doc (built from the live origin) at the OIDC well-known path.\nasync function serveDiscovery(extra: Record<string, unknown> = {}) {\n let origin = \"\";\n const srv = http.createServer((req, res) => {\n if (req.url !== \"/.well-known/openid-configuration\") {\n res.writeHead(404).end();\n return;\n }\n res.setHeader(\"content-type\", \"application/json\");\n res.end(JSON.stringify(doc(origin, extra)));\n });\n await new Promise<void>((resolve) => srv.listen(0, resolve));\n server = srv;\n origin = `http://localhost:${(srv.address() as { port: number }).port}`;\n return origin;\n}\n\ndescribe(\"customProvider\", () => {\n it(\"builds an OAuthConfig from discovery\", async () => {\n const base = await serveDiscovery();\n\n const config = await customProvider({\n issuer: base,\n audience: \"my-api\",\n baseUrl: \"https://app.example.test\",\n scopes: [\"openid\"],\n });\n\n expect(config.baseUrl).toBe(\"https://app.example.test\");\n expect(config.verify).toEqual({\n issuer: base,\n audience: \"my-api\",\n jwksUri: `${base}/jwks`,\n });\n expect(config.scopesSupported).toEqual([\"openid\"]);\n expect(config.oauthMetadata.registration_endpoint).toBe(`${base}/register`);\n });\n\n it(\"defaults scopesSupported to the discovered scopes\", async () => {\n const base = await serveDiscovery();\n const config = await customProvider({\n issuer: base,\n audience: \"a\",\n baseUrl: \"https://app.example.test\",\n });\n expect(config.scopesSupported).toEqual([\"openid\", \"email\", \"profile\"]);\n });\n\n it(\"allows omitting baseUrl (server infers it from headers)\", async () => {\n const base = await serveDiscovery();\n const config = await customProvider({ issuer: base, audience: \"a\" });\n expect(config.baseUrl).toBeUndefined();\n });\n\n it(\"ignores a runtime issuer override (keeps the discovered trust anchor)\", async () => {\n const base = await serveDiscovery();\n const config = await customProvider({\n issuer: base,\n audience: \"a\",\n metadataOverrides: { issuer: \"https://evil.test\" } as never,\n });\n expect(config.verify.issuer).toBe(base);\n expect(config.oauthMetadata.issuer).toBe(base);\n });\n\n it(\"ignores a runtime jwks_uri override (keeps the discovered signing keys)\", async () => {\n const base = await serveDiscovery();\n const config = await customProvider({\n issuer: base,\n audience: \"a\",\n metadataOverrides: { jwks_uri: \"https://evil.test/keys\" } as never,\n });\n expect(config.verify.jwksUri).toBe(`${base}/jwks`);\n });\n\n it(\"rejects a non-DCR IdP (no registration_endpoint)\", async () => {\n const base = await serveDiscovery({ registration_endpoint: undefined });\n await expect(\n customProvider({\n issuer: base,\n audience: \"a\",\n baseUrl: \"https://app.example.test\",\n }),\n ).rejects.toThrow(/not DCR-compatible/);\n });\n\n it(\"rejects a non-DCR IdP even if an override supplies registration_endpoint\", async () => {\n const base = await serveDiscovery({ registration_endpoint: undefined });\n await expect(\n customProvider({\n issuer: base,\n audience: \"a\",\n metadataOverrides: { registration_endpoint: `${base}/register` },\n }),\n ).rejects.toThrow(/not DCR-compatible/);\n });\n\n it(\"applies metadataOverrides over discovered values\", async () => {\n const base = await serveDiscovery();\n const config = await customProvider({\n issuer: base,\n audience: \"a\",\n baseUrl: \"https://app.example.test\",\n metadataOverrides: { token_endpoint: \"https://override/token\" },\n });\n expect(config.oauthMetadata.token_endpoint).toBe(\"https://override/token\");\n });\n});\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { OAuthConfig } from "../index.js";
|
|
2
|
+
import { type CustomProviderOptions } from "./custom.js";
|
|
3
|
+
/**
|
|
4
|
+
* OAuth provider for a Descope MCP Server. `mcpServerId` is the MCP Server's
|
|
5
|
+
* ID (the `MS`-prefixed Resource ID from the console's MCP Servers section),
|
|
6
|
+
* not a generic Inbound App or client ID. Requires DCR enabled on the MCP
|
|
7
|
+
* Server. `audience` is the MCP server URL. Custom-domain Descope tenants
|
|
8
|
+
* should use `customProvider({ issuer })` directly with the full discovery URL.
|
|
9
|
+
*/
|
|
10
|
+
export declare function descopeProvider(opts: {
|
|
11
|
+
projectId: string;
|
|
12
|
+
mcpServerId: string;
|
|
13
|
+
} & Omit<CustomProviderOptions, "issuer">): Promise<OAuthConfig>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { customProvider } from "./custom.js";
|
|
2
|
+
/**
|
|
3
|
+
* OAuth provider for a Descope MCP Server. `mcpServerId` is the MCP Server's
|
|
4
|
+
* ID (the `MS`-prefixed Resource ID from the console's MCP Servers section),
|
|
5
|
+
* not a generic Inbound App or client ID. Requires DCR enabled on the MCP
|
|
6
|
+
* Server. `audience` is the MCP server URL. Custom-domain Descope tenants
|
|
7
|
+
* should use `customProvider({ issuer })` directly with the full discovery URL.
|
|
8
|
+
*/
|
|
9
|
+
export function descopeProvider(opts) {
|
|
10
|
+
const { projectId, mcpServerId, ...rest } = opts;
|
|
11
|
+
const issuer = `https://api.descope.com/v1/apps/agentic/${projectId}/${mcpServerId}`;
|
|
12
|
+
return customProvider({ issuer, ...rest });
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=descope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"descope.js","sourceRoot":"","sources":["../../../../src/server/auth/providers/descope.ts"],"names":[],"mappings":"AACA,OAAO,EAA8B,cAAc,EAAE,MAAM,aAAa,CAAC;AAEzE;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,IAGC;IAED,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;IACjD,MAAM,MAAM,GAAG,2CAA2C,SAAS,IAAI,WAAW,EAAE,CAAC;IACrF,OAAO,cAAc,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;AAC7C,CAAC","sourcesContent":["import type { OAuthConfig } from \"../index.js\";\nimport { type CustomProviderOptions, customProvider } from \"./custom.js\";\n\n/**\n * OAuth provider for a Descope MCP Server. `mcpServerId` is the MCP Server's\n * ID (the `MS`-prefixed Resource ID from the console's MCP Servers section),\n * not a generic Inbound App or client ID. Requires DCR enabled on the MCP\n * Server. `audience` is the MCP server URL. Custom-domain Descope tenants\n * should use `customProvider({ issuer })` directly with the full discovery URL.\n */\nexport function descopeProvider(\n opts: { projectId: string; mcpServerId: string } & Omit<\n CustomProviderOptions,\n \"issuer\"\n >,\n): Promise<OAuthConfig> {\n const { projectId, mcpServerId, ...rest } = opts;\n const issuer = `https://api.descope.com/v1/apps/agentic/${projectId}/${mcpServerId}`;\n return customProvider({ issuer, ...rest });\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|