@plitzi/sdk-server 0.37.1 → 0.37.3
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/CHANGELOG.md +1296 -0
- package/README.md +126 -7
- package/dist/actions.d.ts +20 -8
- package/dist/actions.js +11 -4
- package/dist/adapters/cloudAdapters.js +4 -3
- package/dist/adapters/jsonAdapters.js +28 -6
- package/dist/auth.d.ts +7 -4
- package/dist/auth.js +8 -6
- package/dist/core/auth/api.d.ts +29 -1
- package/dist/core/auth/api.js +47 -13
- package/dist/core/auth/createAuth.d.ts +4 -0
- package/dist/core/auth/createAuth.js +3 -3
- package/dist/core/auth/credentials.d.ts +3 -0
- package/dist/core/auth/credentials.js +1 -1
- package/dist/core/auth/csrf.d.ts +25 -1
- package/dist/core/auth/csrf.js +29 -4
- package/dist/core/auth/renewal.d.ts +26 -0
- package/dist/core/auth/renewal.js +24 -0
- package/dist/core/auth/routes.js +8 -6
- package/dist/core/auth/session.d.ts +45 -0
- package/dist/core/auth/session.js +47 -1
- package/dist/core/auth/sessionActivity.d.ts +11 -0
- package/dist/core/auth/sessionActivity.js +14 -0
- package/dist/core/auth/throttle.d.ts +15 -0
- package/dist/core/auth/throttle.js +29 -1
- package/dist/core/auth/totp.d.ts +9 -1
- package/dist/core/auth/totp.js +11 -6
- package/dist/core/createServer.actions.test.d.ts +1 -0
- package/dist/core/createServer.d.ts +14 -1
- package/dist/core/createServer.functions.test.d.ts +1 -0
- package/dist/core/createServer.js +22 -12
- package/dist/core/fontStore.js +1 -1
- package/dist/core/handlers/authRouteHandlers.d.ts +1 -1
- package/dist/core/handlers/authRouteHandlers.js +57 -21
- package/dist/core/handlers/csrfMiddleware.d.ts +23 -0
- package/dist/core/handlers/csrfMiddleware.js +31 -7
- package/dist/core/handlers/csrfMiddleware.test.d.ts +1 -0
- package/dist/core/handlers/oauthRouteHandlers.js +5 -3
- package/dist/core/handlers/socialRouteHandlers.d.ts +1 -1
- package/dist/core/handlers/socialRouteHandlers.js +2 -1
- package/dist/core/http/dispatcher.d.ts +9 -1
- package/dist/core/http/dispatcher.js +38 -11
- package/dist/core/http/navigation.d.ts +5 -2
- package/dist/core/http/navigation.js +7 -4
- package/dist/core/http/socketResponse.d.ts +18 -0
- package/dist/core/http/socketResponse.js +59 -0
- package/dist/core/http/sse.d.ts +26 -0
- package/dist/core/http/sse.js +78 -0
- package/dist/core/http/stages/authRoutes.js +3 -1
- package/dist/core/http/stages/exchangeRoute.test.d.ts +1 -0
- package/dist/core/http/stages/middlewares.d.ts +1 -1
- package/dist/core/http/stages/middlewares.js +16 -10
- package/dist/core/http/stages/middlewares.test.d.ts +1 -0
- package/dist/core/http/stages/oauth.js +2 -2
- package/dist/core/http/stages/sessionRenewal.d.ts +16 -0
- package/dist/core/http/stages/sessionRenewal.js +81 -0
- package/dist/core/http/stages/sessionRenewal.test.d.ts +1 -0
- package/dist/core/http/stages/signInRoute.d.ts +2 -0
- package/dist/core/http/stages/signInRoute.js +139 -0
- package/dist/core/http/stages/signInRoute.test.d.ts +1 -0
- package/dist/core/http/types.d.ts +9 -0
- package/dist/core/http/webExchange.d.ts +18 -0
- package/dist/core/http/webExchange.js +53 -0
- package/dist/core/oauth/authorize.js +37 -13
- package/dist/core/oauth/consentPage.js +11 -0
- package/dist/core/oauth/issueContext.d.ts +14 -0
- package/dist/core/oauth/issueContext.js +32 -0
- package/dist/core/oauth/records.d.ts +7 -0
- package/dist/core/oauth/register.d.ts +2 -0
- package/dist/core/oauth/register.js +32 -11
- package/dist/core/oauth/register.test.d.ts +1 -0
- package/dist/core/oauth/token.js +21 -6
- package/dist/core/oauth/token.test.d.ts +1 -0
- package/dist/core/requestParser.d.ts +4 -0
- package/dist/core/requestParser.js +14 -7
- package/dist/core/sdkAssets.js +1 -1
- package/dist/core/server/baseServer.d.ts +14 -1
- package/dist/core/server/baseServer.js +43 -11
- package/dist/core/server/closeOnSignals.d.ts +28 -0
- package/dist/core/server/closeOnSignals.js +47 -0
- package/dist/core/server/closeOnSignals.test.d.ts +1 -0
- package/dist/core/server/drain.d.ts +18 -0
- package/dist/core/server/drain.js +62 -0
- package/dist/core/server/drain.test.d.ts +1 -0
- package/dist/core/server/fleet/channel.d.ts +103 -0
- package/dist/core/server/fleet/channel.js +191 -0
- package/dist/core/server/fleet/channel.test.d.ts +1 -0
- package/dist/core/server/fleet/fixtures/fleetServer.d.ts +1 -0
- package/dist/core/server/fleet/fleet.integration.test.d.ts +1 -0
- package/dist/core/server/fleet/link.d.ts +23 -0
- package/dist/core/server/fleet/link.js +70 -0
- package/dist/core/server/fleet/relay.d.ts +11 -0
- package/dist/core/server/fleet/relay.js +104 -0
- package/dist/core/server/fleet/relay.test.d.ts +1 -0
- package/dist/core/server/fleet/role.d.ts +46 -0
- package/dist/core/server/fleet/role.js +59 -0
- package/dist/core/server/fleet/role.test.d.ts +1 -0
- package/dist/core/server/fleet/stores.d.ts +7 -0
- package/dist/core/server/fleet/stores.js +19 -0
- package/dist/core/server/fleet/supervisor.d.ts +26 -0
- package/dist/core/server/fleet/supervisor.js +108 -0
- package/dist/core/server/fleet/supervisor.test.d.ts +1 -0
- package/dist/core/server/pageServer.js +36 -5
- package/dist/core/services/action.js +10 -2
- package/dist/core/services/functionRoutes.d.ts +9 -0
- package/dist/core/services/functionRoutes.js +47 -0
- package/dist/core/services/realtime.d.ts +8 -0
- package/dist/core/services/realtime.js +57 -0
- package/dist/core/services/registry.js +6 -2
- package/dist/core/staticFiles.js +12 -4
- package/dist/core/staticFiles.test.d.ts +1 -0
- package/dist/core/transports.d.ts +13 -3
- package/dist/core/transports.js +14 -5
- package/dist/functions-api.d.ts +281 -0
- package/dist/functions.d.ts +19 -0
- package/dist/functions.js +5 -0
- package/dist/functionsRunner.d.ts +15 -0
- package/dist/functionsRunner.js +6 -0
- package/dist/handlers.d.ts +2 -2
- package/dist/handlers.js +2 -2
- package/dist/helpers/atomicFile.d.ts +10 -0
- package/dist/helpers/atomicFile.js +33 -0
- package/dist/helpers/atomicFile.test.d.ts +1 -0
- package/dist/helpers/buildResponseHelpers.js +35 -11
- package/dist/helpers/buildServerInfo.js +4 -2
- package/dist/helpers/buildServerInfo.test.d.ts +1 -0
- package/dist/helpers/cache/CompressedFileCache.d.ts +18 -0
- package/dist/helpers/cache/CompressedFileCache.js +44 -0
- package/dist/helpers/cache/CompressedFileCache.test.d.ts +1 -0
- package/dist/helpers/cache/cacheManager.d.ts +1 -1
- package/dist/helpers/cache/cacheManager.js +2 -1
- package/dist/helpers/cache/cacheManager.test.d.ts +1 -0
- package/dist/helpers/cache/index.d.ts +1 -1
- package/dist/helpers/cache/keys.d.ts +15 -1
- package/dist/helpers/cache/keys.js +47 -5
- package/dist/helpers/cache/serverCaches.d.ts +7 -1
- package/dist/helpers/compress.d.ts +3 -1
- package/dist/helpers/compress.js +11 -3
- package/dist/helpers/hydrationPayload.d.ts +11 -0
- package/dist/helpers/hydrationPayload.js +51 -0
- package/dist/helpers/hydrationPayload.test.d.ts +1 -0
- package/dist/helpers/metrics.d.ts +1 -1
- package/dist/helpers/metrics.js +3 -2
- package/dist/helpers/outboundGuard.d.ts +14 -6
- package/dist/helpers/outboundGuard.js +132 -8
- package/dist/helpers/paintedCookie.d.ts +8 -0
- package/dist/helpers/paintedCookie.js +13 -0
- package/dist/helpers/reportReactBuild.d.ts +9 -0
- package/dist/helpers/reportReactBuild.js +18 -0
- package/dist/helpers/reportReactBuild.test.d.ts +1 -0
- package/dist/helpers/serverLog.d.ts +25 -1
- package/dist/helpers/serverLog.js +68 -3
- package/dist/helpers/serverLog.test.d.ts +1 -0
- package/dist/helpers/wsFrame.d.ts +3 -0
- package/dist/helpers/wsFrame.js +5 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +6 -2
- package/dist/kernel.d.ts +2 -1
- package/dist/kernel.js +3 -2
- package/dist/modules/actions/connectorWrite.js +2 -1
- package/dist/modules/actions/index.d.ts +41 -13
- package/dist/modules/actions/index.js +52 -9
- package/dist/modules/actions/jobs/failureStreak.d.ts +17 -0
- package/dist/modules/actions/jobs/failureStreak.js +47 -0
- package/dist/modules/actions/jobs/failureStreak.test.d.ts +1 -0
- package/dist/modules/actions/jobs/index.d.ts +33 -0
- package/dist/modules/actions/jobs/index.js +59 -0
- package/dist/modules/actions/jobs/memoryQueue.d.ts +33 -0
- package/dist/modules/actions/jobs/memoryQueue.js +194 -0
- package/dist/modules/actions/jobs/memoryStores.test.d.ts +1 -0
- package/dist/modules/actions/jobs/scheduler.d.ts +64 -0
- package/dist/modules/actions/jobs/scheduler.js +130 -0
- package/dist/modules/actions/jobs/scheduler.test.d.ts +1 -0
- package/dist/modules/actions/jobs/schedules.d.ts +23 -0
- package/dist/modules/actions/jobs/schedules.js +47 -0
- package/dist/modules/actions/jobs/storeClock.d.ts +16 -0
- package/dist/modules/actions/jobs/storeClock.js +32 -0
- package/dist/modules/actions/jobs/testing/jobQueueContract.d.ts +25 -0
- package/dist/modules/actions/jobs/worker.d.ts +64 -0
- package/dist/modules/actions/jobs/worker.js +267 -0
- package/dist/modules/actions/jobs/worker.test.d.ts +1 -0
- package/dist/modules/actions/moduleFor.js +6 -2
- package/dist/modules/actions/runtime/errors.d.ts +12 -0
- package/dist/modules/actions/runtime/errors.js +16 -1
- package/dist/modules/actions/runtime/kvChange.d.ts +27 -0
- package/dist/modules/actions/runtime/kvChange.js +23 -0
- package/dist/modules/actions/runtime/kvList.d.ts +51 -0
- package/dist/modules/actions/runtime/kvList.js +52 -0
- package/dist/modules/actions/runtime/kvStore.d.ts +8 -0
- package/dist/modules/actions/runtime/kvStore.js +46 -20
- package/dist/modules/actions/runtime/memoryKv.d.ts +3 -0
- package/dist/modules/actions/runtime/memoryKv.js +19 -1
- package/dist/modules/actions/runtime/namespaceKv.d.ts +4 -6
- package/dist/modules/actions/runtime/namespaceKv.js +15 -3
- package/dist/modules/actions/runtime/namespaceKv.test.d.ts +1 -0
- package/dist/modules/actions/runtime/precheck.js +5 -9
- package/dist/modules/actions/runtime/rateLimit.d.ts +19 -0
- package/dist/modules/actions/runtime/rateLimit.js +19 -0
- package/dist/modules/actions/runtime/redisKv.d.ts +27 -0
- package/dist/modules/actions/runtime/redisKv.integration.test.d.ts +1 -0
- package/dist/modules/actions/runtime/redisKv.js +46 -0
- package/dist/modules/actions/runtime/renderResolver.js +2 -2
- package/dist/modules/actions/runtime/report.js +2 -1
- package/dist/modules/actions/runtime/runAction.d.ts +10 -2
- package/dist/modules/actions/runtime/runAction.js +52 -116
- package/dist/modules/actions/runtime/runLogger.d.ts +5 -0
- package/dist/modules/actions/runtime/runLogger.js +8 -2
- package/dist/modules/actions/runtime/signing.d.ts +20 -0
- package/dist/modules/actions/runtime/signing.js +25 -0
- package/dist/modules/actions/runtime/signing.test.d.ts +1 -0
- package/dist/modules/actions/runtime/taskContext.d.ts +33 -0
- package/dist/modules/actions/runtime/taskContext.js +124 -0
- package/dist/modules/actions/tasks/builtins.js +5 -1
- package/dist/modules/actions/tasks/flow.js +81 -10
- package/dist/modules/actions/tasks/helpers.js +2 -2
- package/dist/modules/actions/tasks/http.js +2 -3
- package/dist/modules/actions/tasks/kv.js +39 -0
- package/dist/modules/actions/tasks/list.d.ts +2 -0
- package/dist/modules/actions/tasks/list.js +145 -0
- package/dist/modules/actions/tasks/realtime.d.ts +2 -0
- package/dist/modules/actions/tasks/realtime.js +104 -0
- package/dist/modules/actions/tasks/registry.d.ts +10 -1
- package/dist/modules/actions/tasks/registry.js +17 -6
- package/dist/modules/actions/transport/callHandler.js +6 -3
- package/dist/modules/actions/transport/catalogHandler.d.ts +5 -2
- package/dist/modules/actions/transport/catalogHandler.js +3 -2
- package/dist/modules/actions/transport/stream.d.ts +4 -5
- package/dist/modules/actions/transport/stream.js +12 -76
- package/dist/modules/actions/transport/webhookHandler.js +5 -4
- package/dist/modules/actions/types.d.ts +162 -4
- package/dist/modules/connectors/engine.js +3 -5
- package/dist/modules/functions/build.d.ts +22 -0
- package/dist/modules/functions/build.js +122 -0
- package/dist/modules/functions/build.test.d.ts +1 -0
- package/dist/modules/functions/capabilities.d.ts +22 -0
- package/dist/modules/functions/capabilities.js +228 -0
- package/dist/modules/functions/config.d.ts +34 -0
- package/dist/modules/functions/config.js +24 -0
- package/dist/modules/functions/context.d.ts +17 -0
- package/dist/modules/functions/context.js +88 -0
- package/dist/modules/functions/contract.d.ts +121 -0
- package/dist/modules/functions/contract.js +9 -0
- package/dist/modules/functions/driver.d.ts +36 -0
- package/dist/modules/functions/driver.js +150 -0
- package/dist/modules/functions/load.d.ts +15 -0
- package/dist/modules/functions/load.js +45 -0
- package/dist/modules/functions/load.test.d.ts +1 -0
- package/dist/modules/functions/local.d.ts +29 -0
- package/dist/modules/functions/local.js +61 -0
- package/dist/modules/functions/local.test.d.ts +1 -0
- package/dist/modules/functions/manifest.d.ts +24 -0
- package/dist/modules/functions/manifest.js +163 -0
- package/dist/modules/functions/manifest.test.d.ts +1 -0
- package/dist/modules/functions/native.d.ts +8 -0
- package/dist/modules/functions/native.js +21 -0
- package/dist/modules/functions/native.test.d.ts +1 -0
- package/dist/modules/functions/protocol.d.ts +216 -0
- package/dist/modules/functions/protocol.js +18 -0
- package/dist/modules/functions/routes.d.ts +36 -0
- package/dist/modules/functions/routes.js +181 -0
- package/dist/modules/functions/routes.test.d.ts +1 -0
- package/dist/modules/functions/runner/messages.d.ts +5 -0
- package/dist/modules/functions/runner/messages.js +188 -0
- package/dist/modules/functions/runner/remote.d.ts +19 -0
- package/dist/modules/functions/runner/remote.js +116 -0
- package/dist/modules/functions/runner/remote.test.d.ts +1 -0
- package/dist/modules/functions/runner/service.d.ts +32 -0
- package/dist/modules/functions/runner/service.js +209 -0
- package/dist/modules/functions/sandbox/guest.d.ts +45 -0
- package/dist/modules/functions/sandbox/guest.js +660 -0
- package/dist/modules/functions/sandbox/isolate.d.ts +37 -0
- package/dist/modules/functions/sandbox/isolate.js +347 -0
- package/dist/modules/functions/sandbox/isolate.test.d.ts +1 -0
- package/dist/modules/functions/sandbox/prelude.d.ts +2 -0
- package/dist/modules/functions/sandbox/prelude.js +49 -0
- package/dist/modules/functions/space.d.ts +40 -0
- package/dist/modules/functions/space.js +162 -0
- package/dist/modules/functions/testing/tasksOf.d.ts +4 -0
- package/dist/modules/functions/tryEntry.d.ts +9 -0
- package/dist/modules/functions/tryEntry.js +49 -0
- package/dist/modules/mongo/index.d.ts +4 -0
- package/dist/modules/mongo/jobQueue.d.ts +38 -0
- package/dist/modules/mongo/jobQueue.js +335 -0
- package/dist/modules/mongo/kv.d.ts +15 -0
- package/dist/modules/mongo/kv.js +129 -0
- package/dist/modules/mongo/stores.integration.test.d.ts +1 -0
- package/dist/modules/mysql/accounts.js +19 -9
- package/dist/modules/mysql/index.d.ts +2 -1
- package/dist/modules/mysql/jobs/index.d.ts +5 -0
- package/dist/modules/mysql/jobs/jobQueue.d.ts +40 -0
- package/dist/modules/mysql/jobs/jobQueue.js +375 -0
- package/dist/modules/mysql/jobs/kv.d.ts +18 -0
- package/dist/modules/mysql/jobs/kv.js +108 -0
- package/dist/modules/mysql/jobs/schema.d.ts +33 -0
- package/dist/modules/mysql/jobs/schema.js +113 -0
- package/dist/modules/mysql/jobs/stores.integration.test.d.ts +1 -0
- package/dist/modules/mysql/schema.js +25 -0
- package/dist/modules/mysql/sessions.d.ts +1 -11
- package/dist/modules/mysql/sessions.js +6 -1
- package/dist/modules/realtime/declarations.d.ts +11 -0
- package/dist/modules/realtime/declarations.js +26 -0
- package/dist/modules/realtime/grants.d.ts +23 -0
- package/dist/modules/realtime/grants.js +61 -0
- package/dist/modules/realtime/handlers.d.ts +74 -0
- package/dist/modules/realtime/handlers.js +224 -0
- package/dist/modules/realtime/hub.d.ts +62 -0
- package/dist/modules/realtime/hub.js +141 -0
- package/dist/modules/realtime/index.d.ts +51 -0
- package/dist/modules/realtime/index.js +62 -0
- package/dist/modules/realtime/memoryPubSub.d.ts +10 -0
- package/dist/modules/realtime/memoryPubSub.js +43 -0
- package/dist/modules/realtime/realtime.test.d.ts +1 -0
- package/dist/modules/realtime/redisPubSub.d.ts +24 -0
- package/dist/modules/realtime/redisPubSub.js +36 -0
- package/dist/modules/realtime/redisPubSub.test.d.ts +1 -0
- package/dist/modules/realtime/socket.d.ts +25 -0
- package/dist/modules/realtime/socket.js +122 -0
- package/dist/modules/realtime/socket.test.d.ts +1 -0
- package/dist/modules/rsc/handler.d.ts +16 -0
- package/dist/modules/rsc/handler.js +17 -10
- package/dist/modules/rsc/handler.test.d.ts +1 -0
- package/dist/modules/rsc/resolveRscData.js +2 -1
- package/dist/modules/runtime/bundle.d.ts +24 -0
- package/dist/modules/runtime/bundle.js +102 -0
- package/dist/modules/runtime/contract.d.ts +37 -0
- package/dist/modules/runtime/contract.js +21 -0
- package/dist/modules/runtime/entry.test.d.ts +1 -0
- package/dist/modules/runtime/host.d.ts +38 -0
- package/dist/modules/runtime/host.js +68 -0
- package/dist/modules/runtime/inside.d.ts +15 -0
- package/dist/modules/runtime/inside.js +47 -0
- package/dist/modules/runtime/inside.test.d.ts +1 -0
- package/dist/modules/runtime/runner.d.ts +11 -0
- package/dist/modules/runtime/runner.js +38 -0
- package/dist/modules/runtime/runtime.test.d.ts +1 -0
- package/dist/modules/runtime/stages.d.ts +50 -0
- package/dist/modules/runtime/stages.js +117 -0
- package/dist/modules/ssr/Component.d.ts +4 -5
- package/dist/modules/ssr/Component.js +2 -2
- package/dist/modules/ssr/handler.js +10 -2
- package/dist/modules/ssr/loadPluginComponents.js +2 -1
- package/dist/modules/ssr/prepareRender.js +39 -13
- package/dist/modules/ssr/preview.d.ts +3 -0
- package/dist/modules/ssr/preview.js +17 -7
- package/dist/modules/ssr/redirect.test.d.ts +1 -0
- package/dist/modules/ssr/registerExternalPlugins.d.ts +6 -1
- package/dist/modules/ssr/registerExternalPlugins.js +19 -16
- package/dist/modules/ssr/registerExternalPlugins.test.d.ts +1 -0
- package/dist/modules/ssr/reportMissingPlugins.d.ts +9 -0
- package/dist/modules/ssr/reportMissingPlugins.js +24 -0
- package/dist/modules/ssr/reportMissingPlugins.test.d.ts +1 -0
- package/dist/modules/ssr/streamBody.d.ts +2 -2
- package/dist/modules/ssr/streamBody.js +4 -1
- package/dist/modules/ssr/template.js +1 -1
- package/dist/modules/ssr/views/template.ejs +8 -5
- package/dist/mongo.d.ts +25 -0
- package/dist/mongo.js +3 -0
- package/dist/mysql.d.ts +11 -0
- package/dist/mysql.js +4 -1
- package/dist/oauth.js +2 -2
- package/dist/plugins/compile.js +9 -4
- package/dist/plugins/copy.js +3 -3
- package/dist/plugins/manager.d.ts +35 -3
- package/dist/plugins/manager.js +109 -38
- package/dist/runtime.d.ts +15 -0
- package/dist/runtime.js +7 -0
- package/package.json +52 -11
- package/skills/plitzi-authoring/SKILL.md +153 -263
- package/skills/plitzi-authoring/reference/accessibility.md +111 -0
- package/skills/plitzi-authoring/reference/authoring-errors.md +70 -0
- package/skills/plitzi-authoring/reference/data-and-visibility.md +180 -0
- package/skills/plitzi-authoring/reference/elements-and-styles.md +161 -0
- package/skills/plitzi-authoring/reference/flows.md +171 -0
- package/skills/plitzi-authoring/reference/layouts.md +140 -0
- package/skills/plitzi-authoring/reference/lists.md +74 -0
- package/skills/plitzi-authoring/reference/performance.md +67 -0
- package/skills/plitzi-authoring/reference/plugins.md +167 -0
- package/skills/plitzi-authoring/reference/review-checklist.md +67 -0
- package/skills/plitzi-authoring/reference/structure.md +83 -0
- package/skills/plitzi-authoring/reference/templates-and-export.md +54 -0
- package/skills/plitzi-authoring/reference/templates.md +109 -0
- package/skills/plitzi-authoring/reference/testing.md +119 -0
- package/skills/plitzi-authoring/reference/validation.md +73 -0
- package/dist/modules/actions/runtime/schedule.d.ts +0 -35
- package/dist/modules/actions/runtime/schedule.js +0 -76
- /package/dist/{modules/actions/runtime/schedule.test.d.ts → adapters/jsonAdapters.test.d.ts} +0 -0
|
@@ -24,6 +24,8 @@ var STYLES = `
|
|
|
24
24
|
img.logo { display: block; height: 32px; margin-bottom: 20px; }
|
|
25
25
|
h1 { margin: 0 0 6px; font-size: 20px; font-weight: 600; }
|
|
26
26
|
p.lede { margin: 0 0 24px; color: var(--muted); font-size: 14px; }
|
|
27
|
+
p.client { margin: -12px 0 24px; padding: 10px 12px; border: 1px solid var(--line); border-radius: 8px;
|
|
28
|
+
font-size: 14px; }
|
|
27
29
|
input:focus-visible, button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
28
30
|
ul.targets { list-style: none; margin: 0 0 20px; padding: 0; display: grid; gap: 8px; }
|
|
29
31
|
ul.targets label { display: flex; gap: 10px; align-items: flex-start; margin: 0; padding: 12px;
|
|
@@ -73,6 +75,14 @@ var anonymousFields = (view) => {
|
|
|
73
75
|
return `${view.guest ? guestButton(view.guest) : ""}${signIn}`;
|
|
74
76
|
};
|
|
75
77
|
/**
|
|
78
|
+
* Who is asking and where the grant goes. The name is whatever the client registered as, so it is never shown alone:
|
|
79
|
+
* the destination host is what a person can check against the app they just opened.
|
|
80
|
+
*/
|
|
81
|
+
var clientNotice = (client) => {
|
|
82
|
+
const destination = client.loopback ? "an app on this computer" : `<strong>${escapeHtml(client.redirectHost)}</strong>`;
|
|
83
|
+
return `<p class="client"><strong>${escapeHtml(client.name)}</strong> is asking for access. If you continue, it is sent to ${destination}.</p>`;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
76
86
|
* The grant screen — the ONE page this server renders, and it never asks who anybody is.
|
|
77
87
|
*
|
|
78
88
|
* It used to be two steps, the first of which took a username and a password. That is gone: signing in happens on
|
|
@@ -99,6 +109,7 @@ var renderConsentPage = (view) => {
|
|
|
99
109
|
${logo}
|
|
100
110
|
<h1>${escapeHtml(productName)}</h1>
|
|
101
111
|
<p class="lede">${lede}</p>
|
|
112
|
+
${clientNotice(view.client)}
|
|
102
113
|
${error}
|
|
103
114
|
<form method="post" action="${escapeHtml(view.action)}">
|
|
104
115
|
${hiddenFields(view.hidden)}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { OAuthConfig, OAuthIssueContext, SSRRequest } from '@plitzi/sdk-shared';
|
|
2
|
+
/**
|
|
3
|
+
* What `issueToken` is told about who it issues to, beyond the person: the application, as it registered, and the
|
|
4
|
+
* request the credential is issued on.
|
|
5
|
+
*
|
|
6
|
+
* The application is read from its registration rather than trusted from the request — a client names itself once,
|
|
7
|
+
* when it registers, and every credential its grants mint carries that name. A registration that has aged out of the
|
|
8
|
+
* store (a grant renewed for longer than the client record lives) is still an application, just an unnamed one.
|
|
9
|
+
*/
|
|
10
|
+
export declare const issueContextFor: (config: OAuthConfig, clientId: string, extra?: {
|
|
11
|
+
req?: SSRRequest;
|
|
12
|
+
replaces?: string;
|
|
13
|
+
redirectUri?: string;
|
|
14
|
+
}) => Promise<OAuthIssueContext>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { forwardedIp } from "../requestParser.js";
|
|
2
|
+
import { getClient } from "./records.js";
|
|
3
|
+
//#region src/core/oauth/issueContext.ts
|
|
4
|
+
/**
|
|
5
|
+
* What `issueToken` is told about who it issues to, beyond the person: the application, as it registered, and the
|
|
6
|
+
* request the credential is issued on.
|
|
7
|
+
*
|
|
8
|
+
* The application is read from its registration rather than trusted from the request — a client names itself once,
|
|
9
|
+
* when it registers, and every credential its grants mint carries that name. A registration that has aged out of the
|
|
10
|
+
* store (a grant renewed for longer than the client record lives) is still an application, just an unnamed one.
|
|
11
|
+
*/
|
|
12
|
+
var issueContextFor = async (config, clientId, extra = {}) => {
|
|
13
|
+
const client = await getClient(config.adapters.store, clientId);
|
|
14
|
+
const userAgent = extra.req?.headers["user-agent"];
|
|
15
|
+
const ip = extra.req ? extra.req.ip ?? forwardedIp(extra.req.headers) : "";
|
|
16
|
+
const request = {
|
|
17
|
+
...typeof userAgent === "string" && userAgent ? { userAgent } : {},
|
|
18
|
+
...ip ? { ip } : {}
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
client: {
|
|
22
|
+
clientId,
|
|
23
|
+
name: client?.clientName ?? "Unnamed application",
|
|
24
|
+
...client?.softwareId ? { softwareId: client.softwareId } : {}
|
|
25
|
+
},
|
|
26
|
+
...request.userAgent || request.ip ? { request } : {},
|
|
27
|
+
...extra.replaces ? { replaces: extra.replaces } : {},
|
|
28
|
+
...extra.redirectUri ? { redirectUri: extra.redirectUri } : {}
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
//#endregion
|
|
32
|
+
export { issueContextFor };
|
|
@@ -4,6 +4,8 @@ import { OAuthGrantTarget, OAuthStore, OAuthUser } from '@plitzi/sdk-shared';
|
|
|
4
4
|
export type ClientRecord = {
|
|
5
5
|
clientId: string;
|
|
6
6
|
clientName: string;
|
|
7
|
+
/** RFC 7591 `software_id`, when the client sent one. */
|
|
8
|
+
softwareId?: string;
|
|
7
9
|
redirectUris: string[];
|
|
8
10
|
/** When this registration was first handed out, so re-registering the same client keeps reporting its real age. */
|
|
9
11
|
issuedAt: number;
|
|
@@ -37,6 +39,11 @@ export type RefreshRecord = {
|
|
|
37
39
|
scope?: string;
|
|
38
40
|
user: OAuthUser;
|
|
39
41
|
target: OAuthGrantTarget;
|
|
42
|
+
/**
|
|
43
|
+
* What `issueToken` last minted for this grant. Handed back as `replaces` on the renewal, so the deployment can
|
|
44
|
+
* rotate that credential instead of adding another, and to `revokeToken` when the grant is revoked.
|
|
45
|
+
*/
|
|
46
|
+
credential?: string;
|
|
40
47
|
};
|
|
41
48
|
/** A bearer this server handed out. The token a client holds is an opaque handle minted here, and this record is
|
|
42
49
|
* what it stands for — including `credential`, the thing the deployment's own adapters understand, which never
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { OAuthConfig, SSRResponseHelpers } from '@plitzi/sdk-shared';
|
|
2
|
+
/** Plain http on the loopback interface — the exception RFC 8252 carves out for a native app listening on a port. */
|
|
3
|
+
export declare const isLoopbackRedirectUri: (value: string) => boolean;
|
|
2
4
|
/** RFC 7591 dynamic client registration. A remote host has no way to be configured into this server ahead of
|
|
3
5
|
* time, so it registers itself on first connect; the record it gets back is only ever used to pin the redirect
|
|
4
6
|
* target, since a public client authenticates with PKCE rather than with credentials. */
|
|
@@ -1,21 +1,38 @@
|
|
|
1
|
-
import { randomId } from "./pkce.js";
|
|
2
1
|
import { getClientByFingerprint, putClient, putClientFingerprint } from "./records.js";
|
|
2
|
+
import { randomId } from "./pkce.js";
|
|
3
3
|
import { sendErrorJson, sendJson } from "./respond.js";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
//#region src/core/oauth/register.ts
|
|
6
|
-
var
|
|
7
|
-
let url;
|
|
6
|
+
var parseUri = (value) => {
|
|
8
7
|
try {
|
|
9
|
-
|
|
8
|
+
return new URL(value);
|
|
10
9
|
} catch {
|
|
11
|
-
return
|
|
10
|
+
return;
|
|
12
11
|
}
|
|
13
|
-
if (url.protocol === "https:") return true;
|
|
14
|
-
return url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1");
|
|
15
12
|
};
|
|
13
|
+
/** Plain http on the loopback interface — the exception RFC 8252 carves out for a native app listening on a port. */
|
|
14
|
+
var isLoopbackRedirectUri = (value) => {
|
|
15
|
+
const url = parseUri(value);
|
|
16
|
+
return url?.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1");
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* A redirect target must be one the user's browser can be sent to safely: https anywhere, or loopback. A deployment
|
|
20
|
+
* whose clients are all native apps narrows that to loopback alone (see `OAuthConfig.loopbackRedirectsOnly`).
|
|
21
|
+
*/
|
|
22
|
+
var isUsableRedirectUri = (value, loopbackOnly) => isLoopbackRedirectUri(value) || !loopbackOnly && parseUri(value)?.protocol === "https:";
|
|
16
23
|
var stringList = (value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
17
24
|
var nowSeconds = () => Math.floor(Date.now() / 1e3);
|
|
18
25
|
var fingerprintOf = (clientName, redirectUris) => createHash("sha256").update(JSON.stringify([clientName, [...redirectUris].sort()])).digest("base64url");
|
|
26
|
+
/**
|
|
27
|
+
* What a client calls itself is shown to the person it acts for — on the grant screen and in their list of devices — so
|
|
28
|
+
* it is bounded: a name is a line of text, not a document a client can fill the screen with.
|
|
29
|
+
*/
|
|
30
|
+
var CLIENT_NAME_MAX = 120;
|
|
31
|
+
var SOFTWARE_ID_MAX = 64;
|
|
32
|
+
var textField = (value, max) => {
|
|
33
|
+
if (typeof value !== "string") return;
|
|
34
|
+
return value.trim().slice(0, max) || void 0;
|
|
35
|
+
};
|
|
19
36
|
/** RFC 7591 dynamic client registration. A remote host has no way to be configured into this server ahead of
|
|
20
37
|
* time, so it registers itself on first connect; the record it gets back is only ever used to pin the redirect
|
|
21
38
|
* target, since a public client authenticates with PKCE rather than with credentials. */
|
|
@@ -25,17 +42,20 @@ var handleRegister = async (config, res, body) => {
|
|
|
25
42
|
return;
|
|
26
43
|
}
|
|
27
44
|
const metadata = body;
|
|
28
|
-
const
|
|
45
|
+
const loopbackOnly = config.loopbackRedirectsOnly === true;
|
|
46
|
+
const redirectUris = stringList(metadata["redirect_uris"]).filter((uri) => isUsableRedirectUri(uri, loopbackOnly));
|
|
29
47
|
if (redirectUris.length === 0) {
|
|
30
|
-
sendErrorJson(res, 400, "invalid_request", "redirect_uris must list at least one https (or loopback) URI.");
|
|
48
|
+
sendErrorJson(res, 400, "invalid_request", loopbackOnly ? "redirect_uris must list at least one loopback URI (http://127.0.0.1:<port>/…)." : "redirect_uris must list at least one https (or loopback) URI.");
|
|
31
49
|
return;
|
|
32
50
|
}
|
|
33
|
-
const clientName =
|
|
51
|
+
const clientName = textField(metadata["client_name"], CLIENT_NAME_MAX) ?? "MCP client";
|
|
52
|
+
const softwareId = textField(metadata["software_id"], SOFTWARE_ID_MAX);
|
|
34
53
|
const { store } = config.adapters;
|
|
35
54
|
const fingerprint = fingerprintOf(clientName, redirectUris);
|
|
36
55
|
const client = await getClientByFingerprint(store, fingerprint) ?? {
|
|
37
56
|
clientId: randomId(),
|
|
38
57
|
clientName,
|
|
58
|
+
...softwareId ? { softwareId } : {},
|
|
39
59
|
redirectUris,
|
|
40
60
|
issuedAt: nowSeconds()
|
|
41
61
|
};
|
|
@@ -45,6 +65,7 @@ var handleRegister = async (config, res, body) => {
|
|
|
45
65
|
client_id: client.clientId,
|
|
46
66
|
client_id_issued_at: client.issuedAt,
|
|
47
67
|
client_name: clientName,
|
|
68
|
+
...client.softwareId ? { software_id: client.softwareId } : {},
|
|
48
69
|
redirect_uris: redirectUris,
|
|
49
70
|
grant_types: config.refreshTtlSeconds === 0 ? ["authorization_code"] : ["authorization_code", "refresh_token"],
|
|
50
71
|
response_types: ["code"],
|
|
@@ -52,4 +73,4 @@ var handleRegister = async (config, res, body) => {
|
|
|
52
73
|
});
|
|
53
74
|
};
|
|
54
75
|
//#endregion
|
|
55
|
-
export { handleRegister };
|
|
76
|
+
export { handleRegister, isLoopbackRedirectUri };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/core/oauth/token.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { dropAccess, dropCode, dropRefresh, getAccess, getCode, getRefresh, putAccess, putRefresh } from "./records.js";
|
|
2
|
+
import { issueContextFor } from "./issueContext.js";
|
|
1
3
|
import { scopesOf } from "./metadata.js";
|
|
2
4
|
import { field, optionalField } from "./params.js";
|
|
3
5
|
import { randomId, verifyChallenge } from "./pkce.js";
|
|
4
|
-
import { dropAccess, dropCode, dropRefresh, getCode, getRefresh, putAccess, putRefresh } from "./records.js";
|
|
5
6
|
import { sendErrorJson, sendJson } from "./respond.js";
|
|
6
7
|
//#region src/core/oauth/token.ts
|
|
7
8
|
var DEFAULT_REFRESH_TTL_SECONDS = 2592e3;
|
|
@@ -35,12 +36,21 @@ var sendTokens = async (config, res, credential, expiresInSeconds, grant) => {
|
|
|
35
36
|
const body = {
|
|
36
37
|
access_token: bearer,
|
|
37
38
|
token_type: "Bearer",
|
|
38
|
-
scope: scopeOf(config, grant.scope)
|
|
39
|
+
scope: scopeOf(config, grant.scope),
|
|
40
|
+
/**
|
|
41
|
+
* What the person chose on the grant screen — a space, the account. RFC 6749 §5.1 lets a token response carry
|
|
42
|
+
* parameters of its own, and this one is what a client that asked to CHOOSE needs back: without it, a native
|
|
43
|
+
* client granted "work in this space" would hold a credential and not know which space it was for.
|
|
44
|
+
*/
|
|
45
|
+
target: grant.target.value
|
|
39
46
|
};
|
|
40
47
|
if (expiresInSeconds !== void 0) body["expires_in"] = expiresInSeconds;
|
|
41
48
|
if (ttl > 0) {
|
|
42
49
|
const refreshToken = randomId();
|
|
43
|
-
await putRefresh(config.adapters.store, refreshToken,
|
|
50
|
+
await putRefresh(config.adapters.store, refreshToken, {
|
|
51
|
+
...grant,
|
|
52
|
+
credential
|
|
53
|
+
}, ttl);
|
|
44
54
|
body["refresh_token"] = refreshToken;
|
|
45
55
|
}
|
|
46
56
|
sendJson(res, 200, body);
|
|
@@ -89,7 +99,7 @@ var exchangeRefresh = async (config, res, params) => {
|
|
|
89
99
|
sendErrorJson(res, 400, "invalid_grant", "The refresh token was issued to another client.");
|
|
90
100
|
return;
|
|
91
101
|
}
|
|
92
|
-
const issued = await config.adapters.issueToken(record.user, record.target);
|
|
102
|
+
const issued = await config.adapters.issueToken(record.user, record.target, await issueContextFor(config, record.clientId, { replaces: record.credential }));
|
|
93
103
|
if (!issued) {
|
|
94
104
|
sendErrorJson(res, 400, "invalid_grant", "The account no longer has access to this resource.");
|
|
95
105
|
return;
|
|
@@ -107,8 +117,13 @@ var exchangeRefresh = async (config, res, params) => {
|
|
|
107
117
|
var handleRevoke = async (config, res, params) => {
|
|
108
118
|
const token = optionalField(params, "token");
|
|
109
119
|
if (token) {
|
|
110
|
-
|
|
111
|
-
await
|
|
120
|
+
const { store, revokeToken } = config.adapters;
|
|
121
|
+
const refresh = await getRefresh(store, token);
|
|
122
|
+
const access = await getAccess(store, token);
|
|
123
|
+
await dropRefresh(store, token);
|
|
124
|
+
await dropAccess(store, token);
|
|
125
|
+
const credential = refresh?.credential ?? (access ? access.credential ?? token : void 0);
|
|
126
|
+
if (credential && revokeToken) await revokeToken(credential);
|
|
112
127
|
}
|
|
113
128
|
res.setStatus(200);
|
|
114
129
|
res.end();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -9,4 +9,8 @@ export declare const requestOrigin: (req: SSRRequest) => string;
|
|
|
9
9
|
* the original client first. All three are plain headers a direct client can forge, so this is log/diagnostic
|
|
10
10
|
* material — never an authorisation input. Falls back to the socket peer, which no client controls. */
|
|
11
11
|
export declare const clientIp: (raw: IncomingMessage, req: SSRRequest) => string;
|
|
12
|
+
/** The proxies' account of the client address, without the socket — for a caller that has headers and nothing else. */
|
|
13
|
+
export declare const forwardedIp: (headers: Record<string, string | string[] | undefined>) => string;
|
|
14
|
+
/** A request's body as it arrived, byte for byte — what a binary upload to a function's route needs. */
|
|
15
|
+
export declare const readRawBytes: (raw: IncomingMessage) => Promise<Buffer>;
|
|
12
16
|
export declare const readRawBody: (raw: IncomingMessage) => Promise<string>;
|
|
@@ -18,8 +18,10 @@ var parseRequest = (raw) => {
|
|
|
18
18
|
const sp = new URLSearchParams(search.slice(1));
|
|
19
19
|
for (const [k, v] of sp.entries()) query[k] = v;
|
|
20
20
|
}
|
|
21
|
+
const method = (headers[":method"] ?? raw.method ?? "GET").toUpperCase();
|
|
22
|
+
const ip = forwardedIp(headers) || unmapIpv4(raw.socket.remoteAddress ?? "");
|
|
21
23
|
return {
|
|
22
|
-
method
|
|
24
|
+
method,
|
|
23
25
|
path,
|
|
24
26
|
search,
|
|
25
27
|
url: rawUrl,
|
|
@@ -27,6 +29,7 @@ var parseRequest = (raw) => {
|
|
|
27
29
|
protocol,
|
|
28
30
|
headers,
|
|
29
31
|
query,
|
|
32
|
+
...ip ? { ip } : {},
|
|
30
33
|
ctx: {}
|
|
31
34
|
};
|
|
32
35
|
};
|
|
@@ -43,12 +46,15 @@ var unmapIpv4 = (address) => address.startsWith("::ffff:") && address.includes("
|
|
|
43
46
|
* peer it accepted in `cf-connecting-ip`, the ingress in `x-real-ip`, and `x-forwarded-for` lists the chain with
|
|
44
47
|
* the original client first. All three are plain headers a direct client can forge, so this is log/diagnostic
|
|
45
48
|
* material — never an authorisation input. Falls back to the socket peer, which no client controls. */
|
|
46
|
-
var clientIp = (raw, req) =>
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
var clientIp = (raw, req) => forwardedIp(req.headers) || unmapIpv4(raw.socket.remoteAddress ?? "");
|
|
50
|
+
/** The proxies' account of the client address, without the socket — for a caller that has headers and nothing else. */
|
|
51
|
+
var forwardedIp = (headers) => {
|
|
52
|
+
const forwardedFor = headerValue(headers["x-forwarded-for"]).split(",")[0] ?? "";
|
|
53
|
+
return unmapIpv4(headerValue(headers["cf-connecting-ip"]) || headerValue(headers["x-real-ip"]) || forwardedFor.trim());
|
|
49
54
|
};
|
|
50
55
|
var MAX_BODY_BYTES = 1048576;
|
|
51
|
-
|
|
56
|
+
/** A request's body as it arrived, byte for byte — what a binary upload to a function's route needs. */
|
|
57
|
+
var readRawBytes = (raw) => new Promise((resolve, reject) => {
|
|
52
58
|
const chunks = [];
|
|
53
59
|
let size = 0;
|
|
54
60
|
raw.on("data", (chunk) => {
|
|
@@ -59,8 +65,9 @@ var readRawBody = (raw) => new Promise((resolve, reject) => {
|
|
|
59
65
|
}
|
|
60
66
|
chunks.push(chunk);
|
|
61
67
|
});
|
|
62
|
-
raw.on("end", () => resolve(Buffer.concat(chunks)
|
|
68
|
+
raw.on("end", () => resolve(Buffer.concat(chunks)));
|
|
63
69
|
raw.on("error", reject);
|
|
64
70
|
});
|
|
71
|
+
var readRawBody = async (raw) => (await readRawBytes(raw)).toString("utf8");
|
|
65
72
|
//#endregion
|
|
66
|
-
export { clientIp, parseRequest, readRawBody, requestOrigin };
|
|
73
|
+
export { clientIp, forwardedIp, parseRequest, readRawBody, readRawBytes, requestOrigin };
|
package/dist/core/sdkAssets.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { statSync } from "node:fs";
|
|
3
2
|
import path from "node:path";
|
|
3
|
+
import { statSync } from "node:fs";
|
|
4
4
|
//#region src/core/sdkAssets.ts
|
|
5
5
|
/** Where the rendered page is told to fetch the SDK bundle from. Emitted by the render, so it is also served. */
|
|
6
6
|
var SDK_ASSETS_PREFIX = "/sdk-assets";
|
|
@@ -4,6 +4,19 @@ export interface HttpServerParts {
|
|
|
4
4
|
label: string;
|
|
5
5
|
cache: CacheManager | null;
|
|
6
6
|
plugins: PluginRegistry;
|
|
7
|
-
|
|
7
|
+
/** Started once the transport is bound: background work that should not run in a server nobody listened on. */
|
|
8
|
+
onListen?: () => void;
|
|
9
|
+
/**
|
|
10
|
+
* The server has stopped taking connections and is shutting down: what holds connections open for good — realtime
|
|
11
|
+
* sockets and streams — lets them go now, rather than being cut when the grace runs out.
|
|
12
|
+
*/
|
|
13
|
+
onClosing?: () => void;
|
|
14
|
+
/** Awaited, so a server that holds work in flight — jobs, drains — can finish it before the sockets close. */
|
|
15
|
+
onDestroy?: () => void | Promise<void>;
|
|
16
|
+
/** What a plugin invalidated in another process of the fleet leaves to do here: forget it, in memory only. */
|
|
17
|
+
forgetPlugins?: (name?: string, version?: string) => void;
|
|
18
|
+
/** What the primary does once, before it starts the workers — work each of them would otherwise repeat, or race
|
|
19
|
+
* the others to do. */
|
|
20
|
+
beforeFork?: () => Promise<void>;
|
|
8
21
|
}
|
|
9
22
|
export declare const createHttpServer: (config: SSRServerConfig, makeHandlerForPort: (port: number) => Handler, parts: HttpServerParts) => SSRServer;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import { serverLog } from "../../helpers/serverLog.js";
|
|
2
|
+
import { fleetRole, resolveWorkers } from "./fleet/role.js";
|
|
3
|
+
import { channelName } from "./fleet/link.js";
|
|
1
4
|
import { buildTransport, protoLabel } from "../transports.js";
|
|
5
|
+
import { watchConnections } from "./drain.js";
|
|
6
|
+
import { relayCache, relayPlugins } from "./fleet/relay.js";
|
|
2
7
|
//#region src/core/server/baseServer.ts
|
|
3
8
|
/**
|
|
4
9
|
* What went wrong taking the port, and what to do about it.
|
|
@@ -19,13 +24,33 @@ var createHttpServer = (config, makeHandlerForPort, parts) => {
|
|
|
19
24
|
const version = config.httpVersion ?? (config.tls ? 2 : 1);
|
|
20
25
|
if (version >= 3 && !config.tls) throw new Error(`[${label}] httpVersion: ${version} requires a tls config with key and cert`);
|
|
21
26
|
let primary;
|
|
27
|
+
let connections;
|
|
22
28
|
let h3;
|
|
29
|
+
let fleet;
|
|
30
|
+
let closed = false;
|
|
31
|
+
const plan = resolveWorkers(config.workers);
|
|
32
|
+
const role = fleetRole(plan);
|
|
33
|
+
const channel = role === "single" ? void 0 : channelName(label);
|
|
23
34
|
return {
|
|
24
|
-
cache: parts.cache,
|
|
25
|
-
plugins: parts.plugins,
|
|
35
|
+
cache: channel && parts.cache ? relayCache(parts.cache, `${channel}.cache`) : parts.cache,
|
|
36
|
+
plugins: channel && parts.forgetPlugins ? relayPlugins(parts.plugins, parts.forgetPlugins, `${channel}.plugins`, label) : parts.plugins,
|
|
26
37
|
listen(port, host = "0.0.0.0") {
|
|
38
|
+
if (plan.requested !== void 0 && role !== "worker") serverLog.warn(label, `${plan.requested} workers asked for, ${plan.count} cores to run them on: starting ${plan.count}`);
|
|
39
|
+
if (role === "primary") {
|
|
40
|
+
(parts.beforeFork?.() ?? Promise.resolve()).catch((error) => serverLog.error(label, "preparing for the workers failed", error)).then(async () => {
|
|
41
|
+
const { startFleet } = await import("./fleet/supervisor.js");
|
|
42
|
+
if (closed) return;
|
|
43
|
+
fleet = startFleet(plan.count, label);
|
|
44
|
+
serverLog.info(label, `${plan.count} workers on ${host}:${port}`);
|
|
45
|
+
}).catch((error) => {
|
|
46
|
+
serverLog.error(label, "the workers could not start", error);
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
});
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
27
51
|
const handler = makeHandlerForPort(port);
|
|
28
52
|
({primary, h3} = buildTransport(config, handler, port, label));
|
|
53
|
+
connections = watchConnections(primary);
|
|
29
54
|
primary.on("error", (error) => {
|
|
30
55
|
if (config.onListenError) {
|
|
31
56
|
config.onListenError(error, {
|
|
@@ -35,28 +60,35 @@ var createHttpServer = (config, makeHandlerForPort, parts) => {
|
|
|
35
60
|
});
|
|
36
61
|
return;
|
|
37
62
|
}
|
|
38
|
-
|
|
63
|
+
serverLog.error(label, bindFailure(error, port, label));
|
|
39
64
|
process.exitCode = 1;
|
|
40
65
|
process.exit(1);
|
|
41
66
|
});
|
|
42
67
|
primary.listen(port, host, () => {
|
|
43
|
-
|
|
68
|
+
serverLog.info(label, `${protoLabel(version, !!config.tls)} - listening on ${host}:${port}`);
|
|
69
|
+
parts.onListen?.();
|
|
44
70
|
});
|
|
45
71
|
},
|
|
46
72
|
async close() {
|
|
47
|
-
|
|
48
|
-
|
|
73
|
+
closed = true;
|
|
74
|
+
await fleet?.stop();
|
|
75
|
+
fleet = void 0;
|
|
76
|
+
const draining = connections?.drain(label);
|
|
77
|
+
const quic = h3;
|
|
49
78
|
primary = void 0;
|
|
79
|
+
connections = void 0;
|
|
50
80
|
h3 = void 0;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
81
|
+
parts.onClosing?.();
|
|
82
|
+
await parts.onDestroy?.();
|
|
83
|
+
await Promise.all([draining, quic && new Promise((resolve, reject) => {
|
|
84
|
+
quic.close((error) => {
|
|
85
|
+
if (error && !("code" in error && error.code === "ERR_SERVER_NOT_RUNNING")) {
|
|
86
|
+
reject(error);
|
|
55
87
|
return;
|
|
56
88
|
}
|
|
57
89
|
resolve();
|
|
58
90
|
});
|
|
59
|
-
}))
|
|
91
|
+
})]);
|
|
60
92
|
}
|
|
61
93
|
};
|
|
62
94
|
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Anything that closes the way a server from `createServer` does. */
|
|
2
|
+
export type Closable = {
|
|
3
|
+
close: () => Promise<void>;
|
|
4
|
+
};
|
|
5
|
+
export type CloseOnSignalsOptions = {
|
|
6
|
+
/** Default `SIGTERM` (what an orchestrator sends before it replaces a replica) and `SIGINT` (^C). */
|
|
7
|
+
signals?: NodeJS.Signals[];
|
|
8
|
+
/** Runs once the server has closed: what the deployment opened itself — a pool, a client, a file — goes here. */
|
|
9
|
+
afterClose?: () => void | Promise<void>;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Closes the server when the process is told to stop, and only then lets the process exit.
|
|
13
|
+
*
|
|
14
|
+
* `close()` already does the waiting — the page server stops claiming jobs, finishes the ones it is running and lets
|
|
15
|
+
* go of its sockets — but nothing calls it when a deploy sends SIGTERM, and a process that simply exits leaves every
|
|
16
|
+
* running job to be retried by somebody else. This is that call, for a deployment that has no signal handling of its
|
|
17
|
+
* own. Opt in: a host that already manages its process's signals keeps doing so, and calls `close()` from there.
|
|
18
|
+
*
|
|
19
|
+
* ```ts
|
|
20
|
+
* const server = createServer(config);
|
|
21
|
+
* server.listen(3000);
|
|
22
|
+
* closeOnSignals(server, { afterClose: () => pool.end() });
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* A second signal while it is closing exits at once — the ^C a person presses when waiting is not what they want.
|
|
26
|
+
* Answers a function that removes the handlers again.
|
|
27
|
+
*/
|
|
28
|
+
export declare const closeOnSignals: (server: Closable, { signals, afterClose }?: CloseOnSignalsOptions) => (() => void);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { serverLog } from "../../helpers/serverLog.js";
|
|
2
|
+
//#region src/core/server/closeOnSignals.ts
|
|
3
|
+
/**
|
|
4
|
+
* Closes the server when the process is told to stop, and only then lets the process exit.
|
|
5
|
+
*
|
|
6
|
+
* `close()` already does the waiting — the page server stops claiming jobs, finishes the ones it is running and lets
|
|
7
|
+
* go of its sockets — but nothing calls it when a deploy sends SIGTERM, and a process that simply exits leaves every
|
|
8
|
+
* running job to be retried by somebody else. This is that call, for a deployment that has no signal handling of its
|
|
9
|
+
* own. Opt in: a host that already manages its process's signals keeps doing so, and calls `close()` from there.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* const server = createServer(config);
|
|
13
|
+
* server.listen(3000);
|
|
14
|
+
* closeOnSignals(server, { afterClose: () => pool.end() });
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* A second signal while it is closing exits at once — the ^C a person presses when waiting is not what they want.
|
|
18
|
+
* Answers a function that removes the handlers again.
|
|
19
|
+
*/
|
|
20
|
+
var closeOnSignals = (server, { signals = ["SIGTERM", "SIGINT"], afterClose } = {}) => {
|
|
21
|
+
let closing = false;
|
|
22
|
+
const onSignal = (signal) => {
|
|
23
|
+
if (closing) {
|
|
24
|
+
serverLog.warn("server", `${signal} again: exiting without waiting`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
closing = true;
|
|
29
|
+
serverLog.info("server", `${signal}: finishing what is running, then exiting`);
|
|
30
|
+
(async () => {
|
|
31
|
+
try {
|
|
32
|
+
await server.close();
|
|
33
|
+
await afterClose?.();
|
|
34
|
+
process.exit(0);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
serverLog.error("server", "could not close cleanly", error);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
})();
|
|
40
|
+
};
|
|
41
|
+
for (const signal of signals) process.on(signal, onSignal);
|
|
42
|
+
return () => {
|
|
43
|
+
for (const signal of signals) process.off(signal, onSignal);
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
//#endregion
|
|
47
|
+
export { closeOnSignals };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PrimaryServer } from '../transports';
|
|
2
|
+
/**
|
|
3
|
+
* How long a server shutting down lets what it is answering finish before it cuts it: a render, an upload, an action
|
|
4
|
+
* called a moment ago. Well under the 30 seconds a cluster gives a pod between SIGTERM and SIGKILL.
|
|
5
|
+
*/
|
|
6
|
+
export declare const SHUTDOWN_GRACE_MS = 10000;
|
|
7
|
+
/**
|
|
8
|
+
* The connections a server holds, watched from the moment it is built, so it can be shut down.
|
|
9
|
+
*
|
|
10
|
+
* Node's `server.close()` stops taking connections and then waits for every open one to end. A request ends; a
|
|
11
|
+
* WebSocket or an event stream does not — a board open in a tab, an agent's app listening — so a server with one open
|
|
12
|
+
* waited for ever, and a Ctrl+C or a SIGTERM hung until something killed it. `drain` stops taking connections, closes
|
|
13
|
+
* the idle ones, ends every event stream at once (a stream has nothing left to finish), gives what is still being
|
|
14
|
+
* answered `graceMs`, and then cuts what remains.
|
|
15
|
+
*/
|
|
16
|
+
export declare const watchConnections: (server: PrimaryServer) => {
|
|
17
|
+
drain: (label: string, graceMs?: number) => Promise<void>;
|
|
18
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { serverLog } from "../../helpers/serverLog.js";
|
|
2
|
+
//#region src/core/server/drain.ts
|
|
3
|
+
/**
|
|
4
|
+
* How long a server shutting down lets what it is answering finish before it cuts it: a render, an upload, an action
|
|
5
|
+
* called a moment ago. Well under the 30 seconds a cluster gives a pod between SIGTERM and SIGKILL.
|
|
6
|
+
*/
|
|
7
|
+
var SHUTDOWN_GRACE_MS = 1e4;
|
|
8
|
+
/**
|
|
9
|
+
* Whether a response is an event stream: said by its content type — or, since headers written with `writeHead(status,
|
|
10
|
+
* headers)` are not kept where `getHeader` looks, by what it answers: a GET that asked for one, as an EventSource and
|
|
11
|
+
* an MCP client's listening stream do.
|
|
12
|
+
*/
|
|
13
|
+
var isEventStream = (asking, answering) => {
|
|
14
|
+
const type = answering.getHeader("content-type");
|
|
15
|
+
const accept = asking.headers.accept;
|
|
16
|
+
return typeof type === "string" && type.startsWith("text/event-stream") || asking.method === "GET" && [accept].flat().some((value) => value?.includes("text/event-stream"));
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The connections a server holds, watched from the moment it is built, so it can be shut down.
|
|
20
|
+
*
|
|
21
|
+
* Node's `server.close()` stops taking connections and then waits for every open one to end. A request ends; a
|
|
22
|
+
* WebSocket or an event stream does not — a board open in a tab, an agent's app listening — so a server with one open
|
|
23
|
+
* waited for ever, and a Ctrl+C or a SIGTERM hung until something killed it. `drain` stops taking connections, closes
|
|
24
|
+
* the idle ones, ends every event stream at once (a stream has nothing left to finish), gives what is still being
|
|
25
|
+
* answered `graceMs`, and then cuts what remains.
|
|
26
|
+
*/
|
|
27
|
+
var watchConnections = (server) => {
|
|
28
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
29
|
+
const answering = /* @__PURE__ */ new Map();
|
|
30
|
+
server.on("connection", (socket) => {
|
|
31
|
+
sockets.add(socket);
|
|
32
|
+
socket.once("close", () => sockets.delete(socket));
|
|
33
|
+
});
|
|
34
|
+
server.on("request", (request, response) => {
|
|
35
|
+
answering.set(response, request);
|
|
36
|
+
response.once("close", () => answering.delete(response));
|
|
37
|
+
});
|
|
38
|
+
return { drain: (label, graceMs = SHUTDOWN_GRACE_MS) => new Promise((resolve, reject) => {
|
|
39
|
+
const cut = setTimeout(() => {
|
|
40
|
+
if (sockets.size) serverLog.warn(label, `${sockets.size} connection(s) still open after ${graceMs} ms: closing them`);
|
|
41
|
+
sockets.forEach((socket) => socket.destroy());
|
|
42
|
+
}, graceMs);
|
|
43
|
+
cut.unref();
|
|
44
|
+
server.close((error) => {
|
|
45
|
+
clearTimeout(cut);
|
|
46
|
+
if (error && !("code" in error && error.code === "ERR_SERVER_NOT_RUNNING")) {
|
|
47
|
+
reject(error);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
resolve();
|
|
51
|
+
});
|
|
52
|
+
const closeIdle = () => {
|
|
53
|
+
if ("closeIdleConnections" in server) server.closeIdleConnections();
|
|
54
|
+
};
|
|
55
|
+
closeIdle();
|
|
56
|
+
answering.forEach((request, response) => {
|
|
57
|
+
if (isEventStream(request, response)) response.end(closeIdle);
|
|
58
|
+
});
|
|
59
|
+
}) };
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
export { SHUTDOWN_GRACE_MS, watchConnections };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|