@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
|
@@ -196,6 +196,23 @@ var step3 = (t) => {
|
|
|
196
196
|
...addIndex(account, "account_pending_email_token", "(pending_email_token)")
|
|
197
197
|
];
|
|
198
198
|
};
|
|
199
|
+
/**
|
|
200
|
+
* What tells one device from another in the account's session list: the application holding it, when it is not a
|
|
201
|
+
* browser (`Plitzi CLI on carlos-mbp`, software `plitzi-cli`), and when it was last used — see `sessionActivity`.
|
|
202
|
+
*/
|
|
203
|
+
var step4 = (t) => {
|
|
204
|
+
const session = unquote(t.session);
|
|
205
|
+
return [
|
|
206
|
+
...addColumn(session, "app_name", "VARCHAR(120) NULL AFTER ip"),
|
|
207
|
+
...addColumn(session, "app_id", "VARCHAR(64) NULL AFTER app_name"),
|
|
208
|
+
...addColumn(session, "last_active_at", "BIGINT NULL AFTER created_at")
|
|
209
|
+
];
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* The time step of the last second-factor code accepted, so the same code cannot sign in twice within the window it
|
|
213
|
+
* stays valid for (RFC 6238 §5.2) — see `MfaRecord.lastUsedStep`.
|
|
214
|
+
*/
|
|
215
|
+
var step5 = (t) => [...addColumn(unquote(t.mfa), "last_used_step", "BIGINT NULL AFTER recovery_codes")];
|
|
199
216
|
/** Append only. A released version is never edited — the deployments that already ran it would never see the change. */
|
|
200
217
|
var STEPS = [
|
|
201
218
|
{
|
|
@@ -209,6 +226,14 @@ var STEPS = [
|
|
|
209
226
|
{
|
|
210
227
|
version: 3,
|
|
211
228
|
statements: step3
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
version: 4,
|
|
232
|
+
statements: step4
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
version: 5,
|
|
236
|
+
statements: step5
|
|
212
237
|
}
|
|
213
238
|
];
|
|
214
239
|
var SCHEMA_VERSION = STEPS[STEPS.length - 1].version;
|
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
import { Tables } from './config';
|
|
2
2
|
import { Queryable } from './query';
|
|
3
|
-
|
|
4
|
-
export interface SessionSummary {
|
|
5
|
-
id: number;
|
|
6
|
-
userAgent?: string;
|
|
7
|
-
ip?: string;
|
|
8
|
-
/** Unix seconds. */
|
|
9
|
-
createdAt: number;
|
|
10
|
-
expiresAt: number;
|
|
11
|
-
/** This is the session asking. A device list without it invites someone to revoke the one they are using. */
|
|
12
|
-
current: boolean;
|
|
13
|
-
}
|
|
3
|
+
import { SessionSummary } from '../../core/auth/api';
|
|
14
4
|
/**
|
|
15
5
|
* The sessions of one account, as a thing that can be looked at and taken away one at a time.
|
|
16
6
|
*
|
|
@@ -9,14 +9,19 @@ import { execute, selectRows } from "./query.js";
|
|
|
9
9
|
*/
|
|
10
10
|
var createSessionStore = (db, t) => {
|
|
11
11
|
const list = async (accountId, currentToken) => {
|
|
12
|
-
return (await selectRows(db, `SELECT id, token, user_agent, ip, created_at, expires_at
|
|
12
|
+
return (await selectRows(db, `SELECT id, token, user_agent, ip, app_name, app_id, created_at, last_active_at, expires_at
|
|
13
13
|
FROM ${t.session}
|
|
14
14
|
WHERE account_id = ?
|
|
15
15
|
ORDER BY created_at DESC`, [accountId])).map((row) => ({
|
|
16
16
|
id: row.id,
|
|
17
17
|
...row.user_agent ? { userAgent: row.user_agent } : {},
|
|
18
18
|
...row.ip ? { ip: row.ip } : {},
|
|
19
|
+
...row.app_name ? { app: {
|
|
20
|
+
name: row.app_name,
|
|
21
|
+
...row.app_id ? { softwareId: row.app_id } : {}
|
|
22
|
+
} } : {},
|
|
19
23
|
createdAt: Math.floor(row.created_at.getTime() / 1e3),
|
|
24
|
+
...row.last_active_at ? { lastActiveAt: row.last_active_at } : {},
|
|
20
25
|
expiresAt: row.expires_at,
|
|
21
26
|
current: currentToken !== void 0 && row.token === currentToken
|
|
22
27
|
}));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ChannelDeclarations, OfflineDataRaw } from '@plitzi/sdk-shared';
|
|
2
|
+
type GetOfflineData = (spaceId: number, environment: string, revision?: number) => Promise<OfflineDataRaw | undefined>;
|
|
3
|
+
/**
|
|
4
|
+
* The channels a space declares, read from its schema and remembered for a few seconds.
|
|
5
|
+
*
|
|
6
|
+
* A publish is checked against them on every message; reading the space's documents for each one would cost more
|
|
7
|
+
* than the message. A space that changes its channels is heard within the TTL.
|
|
8
|
+
*/
|
|
9
|
+
export declare const createChannelResolver: (getOfflineData: GetOfflineData) => (spaceId: number, environment: string, revision?: number) => Promise<ChannelDeclarations | undefined>;
|
|
10
|
+
export type ChannelResolver = ReturnType<typeof createChannelResolver>;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/modules/realtime/declarations.ts
|
|
2
|
+
/** How long a space's channels are remembered: a publish is frequent, and a space's settings change rarely. */
|
|
3
|
+
var TTL_MS = 5e3;
|
|
4
|
+
/**
|
|
5
|
+
* The channels a space declares, read from its schema and remembered for a few seconds.
|
|
6
|
+
*
|
|
7
|
+
* A publish is checked against them on every message; reading the space's documents for each one would cost more
|
|
8
|
+
* than the message. A space that changes its channels is heard within the TTL.
|
|
9
|
+
*/
|
|
10
|
+
var createChannelResolver = (getOfflineData) => {
|
|
11
|
+
const cache = /* @__PURE__ */ new Map();
|
|
12
|
+
return (spaceId, environment, revision) => {
|
|
13
|
+
const key = `${spaceId}:${environment}:${revision ?? ""}`;
|
|
14
|
+
const hit = cache.get(key);
|
|
15
|
+
if (hit && Date.now() - hit.at < TTL_MS) return hit.channels;
|
|
16
|
+
const channels = getOfflineData(spaceId, environment, revision).then((data) => data?.schema.settings.channels);
|
|
17
|
+
cache.set(key, {
|
|
18
|
+
at: Date.now(),
|
|
19
|
+
channels
|
|
20
|
+
});
|
|
21
|
+
channels.catch(() => cache.delete(key));
|
|
22
|
+
return channels;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
26
|
+
export { createChannelResolver };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { RealtimeSpace } from './hub';
|
|
2
|
+
import { ActionKvAdapter } from '../actions/types';
|
|
3
|
+
/** How long a grant opens its topic when the flow does not say: a day, which outlives any one visit. */
|
|
4
|
+
export declare const DEFAULT_GRANT_SECONDS = 86400;
|
|
5
|
+
/** The longest a grant may last: thirty days. A page that stays open longer asks its action for a new one. */
|
|
6
|
+
export declare const MAX_GRANT_SECONDS: number;
|
|
7
|
+
export type RealtimeGrants = {
|
|
8
|
+
/** A grant for `topic`, good for `ttlSeconds` — what `realtime.grant` hands the page. */
|
|
9
|
+
issue: (space: RealtimeSpace, topic: string, ttlSeconds?: number) => Promise<string>;
|
|
10
|
+
/** Whether `grant` was issued for exactly `topic` of this space, and has neither run out nor been revoked. */
|
|
11
|
+
opens: (space: RealtimeSpace, topic: string, grant: string) => Promise<boolean>;
|
|
12
|
+
/** `grant` no longer opens `topic` — or, with none named, no grant issued for it until now does. */
|
|
13
|
+
revoke: (space: RealtimeSpace, topic: string, grant?: string) => Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
export declare const grantSeconds: (ttlSeconds: number | undefined) => number;
|
|
16
|
+
/**
|
|
17
|
+
* The grants a server has issued, kept in the store its actions' `kv` uses — so a grant one replica issued opens the
|
|
18
|
+
* topic on whichever replica the page connects to, and nothing new has to be configured for it. A random token rather
|
|
19
|
+
* than a signature: there is no secret to share between replicas, and a grant is only ever as old as its lifetime.
|
|
20
|
+
*
|
|
21
|
+
* Keyed under `realtime-grant:`, a prefix no key a flow writes can reach (theirs are `kv:action:<space>:…`).
|
|
22
|
+
*/
|
|
23
|
+
export declare const createRealtimeGrants: (store: ActionKvAdapter) => RealtimeGrants;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
//#region src/modules/realtime/grants.ts
|
|
3
|
+
/** How long a grant opens its topic when the flow does not say: a day, which outlives any one visit. */
|
|
4
|
+
var DEFAULT_GRANT_SECONDS = 86400;
|
|
5
|
+
/** The longest a grant may last: thirty days. A page that stays open longer asks its action for a new one. */
|
|
6
|
+
var MAX_GRANT_SECONDS = 2592e3;
|
|
7
|
+
/** What a grant looks like on the wire: long enough to be unguessable, and nothing a query string has to escape. */
|
|
8
|
+
var TOKEN = /^[A-Za-z0-9_-]{24,64}$/;
|
|
9
|
+
var parseGrant = (raw) => {
|
|
10
|
+
try {
|
|
11
|
+
const value = JSON.parse(raw ?? "");
|
|
12
|
+
return typeof value === "object" && value !== null && "topic" in value && typeof value.topic === "string" && "generation" in value && typeof value.generation === "number" ? {
|
|
13
|
+
topic: value.topic,
|
|
14
|
+
generation: value.generation
|
|
15
|
+
} : void 0;
|
|
16
|
+
} catch {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var grantSeconds = (ttlSeconds) => ttlSeconds === void 0 || !Number.isFinite(ttlSeconds) ? DEFAULT_GRANT_SECONDS : Math.min(MAX_GRANT_SECONDS, Math.max(60, Math.floor(ttlSeconds)));
|
|
21
|
+
/**
|
|
22
|
+
* The grants a server has issued, kept in the store its actions' `kv` uses — so a grant one replica issued opens the
|
|
23
|
+
* topic on whichever replica the page connects to, and nothing new has to be configured for it. A random token rather
|
|
24
|
+
* than a signature: there is no secret to share between replicas, and a grant is only ever as old as its lifetime.
|
|
25
|
+
*
|
|
26
|
+
* Keyed under `realtime-grant:`, a prefix no key a flow writes can reach (theirs are `kv:action:<space>:…`).
|
|
27
|
+
*/
|
|
28
|
+
var createRealtimeGrants = (store) => {
|
|
29
|
+
const key = ({ spaceId, environment }, grant) => `realtime-grant:${String(spaceId)}:${environment}:${grant}`;
|
|
30
|
+
/**
|
|
31
|
+
* How many times every grant of a topic was revoked at once. A grant carries the number it was issued under, so
|
|
32
|
+
* raising it voids them all — the ones in use and the ones not used yet — without a list of them to walk.
|
|
33
|
+
*/
|
|
34
|
+
const generationKey = ({ spaceId, environment }, topic) => `realtime-grant-generation:${String(spaceId)}:${environment}:${topic}`;
|
|
35
|
+
const generationOf = async (space, topic) => Number(await store.get(generationKey(space, topic))) || 0;
|
|
36
|
+
return {
|
|
37
|
+
issue: async (space, topic, ttlSeconds) => {
|
|
38
|
+
const grant = randomBytes(18).toString("base64url");
|
|
39
|
+
const stored = {
|
|
40
|
+
topic,
|
|
41
|
+
generation: await generationOf(space, topic)
|
|
42
|
+
};
|
|
43
|
+
await store.set(key(space, grant), JSON.stringify(stored), grantSeconds(ttlSeconds));
|
|
44
|
+
return grant;
|
|
45
|
+
},
|
|
46
|
+
opens: async (space, topic, grant) => {
|
|
47
|
+
if (!TOKEN.test(grant)) return false;
|
|
48
|
+
const stored = parseGrant(await store.get(key(space, grant)));
|
|
49
|
+
return stored?.topic === topic && stored.generation === await generationOf(space, topic);
|
|
50
|
+
},
|
|
51
|
+
revoke: async (space, topic, grant) => {
|
|
52
|
+
if (grant === void 0) {
|
|
53
|
+
await store.increment(generationKey(space, topic), 1);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (TOKEN.test(grant) && parseGrant(await store.get(key(space, grant)))?.topic === topic) await store.delete(key(space, grant));
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
//#endregion
|
|
61
|
+
export { DEFAULT_GRANT_SECONDS, MAX_GRANT_SECONDS, createRealtimeGrants, grantSeconds };
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { ChannelResolver } from './declarations';
|
|
2
|
+
import { RealtimeGrants } from './grants';
|
|
3
|
+
import { RealtimeConnection, RealtimeHub, RealtimeSpace } from './hub';
|
|
4
|
+
import { RawResponse } from '../../helpers/buildResponseHelpers';
|
|
5
|
+
import { ChannelDeclaration, RealtimeSender, SSRRequest, SSRResponseHelpers } from '@plitzi/sdk-shared';
|
|
6
|
+
export type RealtimeRefusal = {
|
|
7
|
+
topic: string;
|
|
8
|
+
/** `ungranted`: the channel is declared `grant: true`, and no grant the page sent opens this topic. */
|
|
9
|
+
reason: 'undeclared' | 'unauthenticated' | 'forbidden' | 'ungranted';
|
|
10
|
+
};
|
|
11
|
+
/** An answer in HTTP terms, whichever transport carries it: a status, and what went wrong when something did. */
|
|
12
|
+
export type RealtimeAnswer = {
|
|
13
|
+
status: number;
|
|
14
|
+
error?: string;
|
|
15
|
+
reason?: string;
|
|
16
|
+
refused?: RealtimeRefusal[];
|
|
17
|
+
};
|
|
18
|
+
export declare const answer: (res: SSRResponseHelpers, { status, ...payload }: RealtimeAnswer) => void;
|
|
19
|
+
/** What a page may open, decided once for either transport. */
|
|
20
|
+
export type Admission = {
|
|
21
|
+
ok: false;
|
|
22
|
+
answer: RealtimeAnswer;
|
|
23
|
+
} | {
|
|
24
|
+
ok: true;
|
|
25
|
+
space: RealtimeSpace;
|
|
26
|
+
user?: RealtimeSender['user'];
|
|
27
|
+
accepted: Map<string, ChannelDeclaration>;
|
|
28
|
+
/** The grant each accepted topic of a `grant: true` channel was opened with. */
|
|
29
|
+
granted: Map<string, string>;
|
|
30
|
+
refused: RealtimeRefusal[];
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Which of the topics a page asked for (`?topics=a,b`) it may open: each checked against the channels the space
|
|
34
|
+
* declares and against the visitor's access. One that fails is reported, not a reason to refuse the rest; all failing
|
|
35
|
+
* is a `403`.
|
|
36
|
+
*/
|
|
37
|
+
export declare const admit: (req: SSRRequest, resolveChannels: ChannelResolver, grants: RealtimeGrants) => Promise<Admission>;
|
|
38
|
+
/** A new connection for what was admitted, speaking through `send`. */
|
|
39
|
+
export declare const connectionFor: (admission: Extract<Admission, {
|
|
40
|
+
ok: true;
|
|
41
|
+
}>, send: RealtimeConnection["send"], end: RealtimeConnection["end"]) => RealtimeConnection;
|
|
42
|
+
/**
|
|
43
|
+
* A page publishing, whichever way it arrived: only on a topic its connection subscribed to, only on a channel pages
|
|
44
|
+
* may send on, within its size and its rate — and stamped by the server with who sent it and when, whatever the
|
|
45
|
+
* message said.
|
|
46
|
+
*/
|
|
47
|
+
export declare const publishFrom: (hub: RealtimeHub, connection: RealtimeConnection, { topic, type, data }: {
|
|
48
|
+
topic?: unknown;
|
|
49
|
+
type?: unknown;
|
|
50
|
+
data?: unknown;
|
|
51
|
+
}) => Promise<RealtimeAnswer>;
|
|
52
|
+
export type SubscribeDeps = {
|
|
53
|
+
req: SSRRequest;
|
|
54
|
+
res: SSRResponseHelpers;
|
|
55
|
+
raw: RawResponse;
|
|
56
|
+
signal: AbortSignal;
|
|
57
|
+
hub: RealtimeHub;
|
|
58
|
+
resolveChannels: ChannelResolver;
|
|
59
|
+
grants: RealtimeGrants;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* `GET /_realtime?topics=a,b` (and `&grants=g1,g2` for the topics of a `grant: true` channel) — one Server-Sent Events connection for every topic a page listens to.
|
|
63
|
+
*
|
|
64
|
+
* The first event, `ready`, tells the page its public name (`from`) and the secret its publishes carry. Resolves when
|
|
65
|
+
* the page goes.
|
|
66
|
+
*/
|
|
67
|
+
export declare const handleRealtimeSubscribe: ({ req, res, raw, signal, hub, resolveChannels, grants }: SubscribeDeps) => Promise<void>;
|
|
68
|
+
export type PublishDeps = {
|
|
69
|
+
req: SSRRequest;
|
|
70
|
+
res: SSRResponseHelpers;
|
|
71
|
+
hub: RealtimeHub;
|
|
72
|
+
};
|
|
73
|
+
/** `POST /_realtime` `{ token, topic, type, data }` — a page on a Server-Sent Events connection, publishing. */
|
|
74
|
+
export declare const handleRealtimePublish: ({ req, res, hub }: PublishDeps) => Promise<void>;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import onAbort from "../../helpers/onAbort.js";
|
|
2
|
+
import { openEventStream } from "../../core/http/sse.js";
|
|
3
|
+
import { accessRefusal } from "@plitzi/sdk-shared/actions";
|
|
4
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
5
|
+
import { PRESENCE_TYPE, channelLimits, matchChannel } from "@plitzi/sdk-shared/realtime";
|
|
6
|
+
//#region src/modules/realtime/handlers.ts
|
|
7
|
+
/** Topics one connection may hold. A page shows a handful; past this it is somebody enumerating. */
|
|
8
|
+
var MAX_TOPICS = 8;
|
|
9
|
+
/** A type a page may send: its own vocabulary, never one of the channel's `$` types but `$presence`. */
|
|
10
|
+
var TYPE = /^[A-Za-z0-9_.:-]{1,64}$/;
|
|
11
|
+
/** How long a disconnected client waits before trying again — ours reconnects itself; this is for anyone else's. */
|
|
12
|
+
var RETRY_MS = 3e3;
|
|
13
|
+
var answer = (res, { status, ...payload }) => {
|
|
14
|
+
res.setStatus(status);
|
|
15
|
+
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
16
|
+
res.setHeader("Cache-Control", "no-store");
|
|
17
|
+
res.send(JSON.stringify(payload));
|
|
18
|
+
};
|
|
19
|
+
var listOf = (query, name) => [...new Set((Object.hasOwn(query, name) ? query[name] : "").split(",").map((item) => item.trim()).filter(Boolean))];
|
|
20
|
+
/** The one of the grants the page sent (`?grants=g1,g2`) that opens `topic` — or none. */
|
|
21
|
+
var grantFor = async (grants, space, topic, offered) => {
|
|
22
|
+
for (const grant of offered) if (await grants.opens(space, topic, grant)) return grant;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Which of the topics a page asked for (`?topics=a,b`) it may open: each checked against the channels the space
|
|
26
|
+
* declares and against the visitor's access. One that fails is reported, not a reason to refuse the rest; all failing
|
|
27
|
+
* is a `403`.
|
|
28
|
+
*/
|
|
29
|
+
var admit = async (req, resolveChannels, grants) => {
|
|
30
|
+
const { environment = "main", spaceId, revision } = req.ctx.spaceDeployment ?? {};
|
|
31
|
+
if (typeof spaceId !== "number") return {
|
|
32
|
+
ok: false,
|
|
33
|
+
answer: {
|
|
34
|
+
status: 404,
|
|
35
|
+
error: "No space is served here",
|
|
36
|
+
reason: "not_found"
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const requested = listOf(req.query, "topics");
|
|
40
|
+
const offered = listOf(req.query, "grants").slice(0, MAX_TOPICS);
|
|
41
|
+
if (requested.length === 0 || requested.length > MAX_TOPICS) return {
|
|
42
|
+
ok: false,
|
|
43
|
+
answer: {
|
|
44
|
+
status: 422,
|
|
45
|
+
error: `Name between 1 and ${MAX_TOPICS} topics: ?topics=a,b`,
|
|
46
|
+
reason: "invalid_input"
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const channels = await resolveChannels(spaceId, environment, revision);
|
|
50
|
+
const accepted = /* @__PURE__ */ new Map();
|
|
51
|
+
const granted = /* @__PURE__ */ new Map();
|
|
52
|
+
const refused = [];
|
|
53
|
+
const space = {
|
|
54
|
+
spaceId,
|
|
55
|
+
environment
|
|
56
|
+
};
|
|
57
|
+
for (const topic of requested) {
|
|
58
|
+
const match = matchChannel(topic, channels);
|
|
59
|
+
const refusal = match ? accessRefusal(match.declaration.access, req.ctx.user) : "undeclared";
|
|
60
|
+
const grant = match && !refusal && match.declaration.grant === true ? await grantFor(grants, space, topic, offered) : void 0;
|
|
61
|
+
if (!match || refusal) refused.push({
|
|
62
|
+
topic,
|
|
63
|
+
reason: refusal ?? "undeclared"
|
|
64
|
+
});
|
|
65
|
+
else if (match.declaration.grant === true && grant === void 0) refused.push({
|
|
66
|
+
topic,
|
|
67
|
+
reason: "ungranted"
|
|
68
|
+
});
|
|
69
|
+
else {
|
|
70
|
+
accepted.set(topic, match.declaration);
|
|
71
|
+
if (grant !== void 0) granted.set(topic, grant);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (accepted.size === 0) return {
|
|
75
|
+
ok: false,
|
|
76
|
+
answer: {
|
|
77
|
+
status: 403,
|
|
78
|
+
error: "None of these topics may be opened here",
|
|
79
|
+
reason: "forbidden",
|
|
80
|
+
refused
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
const { user } = req.ctx;
|
|
84
|
+
return {
|
|
85
|
+
ok: true,
|
|
86
|
+
space,
|
|
87
|
+
...user ? { user: {
|
|
88
|
+
id: user.id,
|
|
89
|
+
name: user.username
|
|
90
|
+
} } : {},
|
|
91
|
+
accepted,
|
|
92
|
+
granted,
|
|
93
|
+
refused
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
/** A new connection for what was admitted, speaking through `send`. */
|
|
97
|
+
var connectionFor = (admission, send, end) => ({
|
|
98
|
+
id: randomUUID(),
|
|
99
|
+
token: randomBytes(24).toString("base64url"),
|
|
100
|
+
space: admission.space,
|
|
101
|
+
...admission.user ? { user: admission.user } : {},
|
|
102
|
+
topics: admission.accepted,
|
|
103
|
+
grants: admission.granted,
|
|
104
|
+
announced: /* @__PURE__ */ new Set(),
|
|
105
|
+
sent: /* @__PURE__ */ new Map(),
|
|
106
|
+
send,
|
|
107
|
+
end
|
|
108
|
+
});
|
|
109
|
+
/** Whether this connection may send one more message on `topic` this second. */
|
|
110
|
+
var withinRate = (connection, topic, perSecond) => {
|
|
111
|
+
const second = Math.floor(Date.now() / 1e3);
|
|
112
|
+
const sent = connection.sent.get(topic);
|
|
113
|
+
const count = sent?.second === second ? sent.count + 1 : 1;
|
|
114
|
+
connection.sent.set(topic, {
|
|
115
|
+
second,
|
|
116
|
+
count
|
|
117
|
+
});
|
|
118
|
+
return count <= perSecond;
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* A page publishing, whichever way it arrived: only on a topic its connection subscribed to, only on a channel pages
|
|
122
|
+
* may send on, within its size and its rate — and stamped by the server with who sent it and when, whatever the
|
|
123
|
+
* message said.
|
|
124
|
+
*/
|
|
125
|
+
var publishFrom = async (hub, connection, { topic, type, data }) => {
|
|
126
|
+
const name = typeof topic === "string" ? topic : "";
|
|
127
|
+
const declaration = connection.topics.get(name);
|
|
128
|
+
if (!declaration) return {
|
|
129
|
+
status: 403,
|
|
130
|
+
error: `This connection did not subscribe to "${name}"`,
|
|
131
|
+
reason: "not_subscribed"
|
|
132
|
+
};
|
|
133
|
+
if (declaration.publish === "server") return {
|
|
134
|
+
status: 403,
|
|
135
|
+
error: `Only the server publishes on "${name}"`,
|
|
136
|
+
reason: "server_only"
|
|
137
|
+
};
|
|
138
|
+
const kind = typeof type === "string" ? type : "";
|
|
139
|
+
const presence = kind === PRESENCE_TYPE && declaration.presence === true;
|
|
140
|
+
if (!presence && !TYPE.test(kind)) return {
|
|
141
|
+
status: 422,
|
|
142
|
+
error: "A message type is 1-64 of A-Z a-z 0-9 _ . : -",
|
|
143
|
+
reason: "invalid_input"
|
|
144
|
+
};
|
|
145
|
+
const limits = channelLimits(declaration);
|
|
146
|
+
const value = data ?? null;
|
|
147
|
+
if (Buffer.byteLength(JSON.stringify(value)) > limits.maxMessageBytes) return {
|
|
148
|
+
status: 413,
|
|
149
|
+
error: `A message on "${name}" is at most ${limits.maxMessageBytes} bytes`,
|
|
150
|
+
reason: "too_large"
|
|
151
|
+
};
|
|
152
|
+
if (!withinRate(connection, name, limits.messagesPerSecond)) return {
|
|
153
|
+
status: 429,
|
|
154
|
+
error: `At most ${limits.messagesPerSecond} messages a second`,
|
|
155
|
+
reason: "rate_limited"
|
|
156
|
+
};
|
|
157
|
+
if (presence) connection.announced.add(name);
|
|
158
|
+
await hub.publish(connection.space, hub.from(connection, name, kind, value));
|
|
159
|
+
return { status: 204 };
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* `GET /_realtime?topics=a,b` (and `&grants=g1,g2` for the topics of a `grant: true` channel) — one Server-Sent Events connection for every topic a page listens to.
|
|
163
|
+
*
|
|
164
|
+
* The first event, `ready`, tells the page its public name (`from`) and the secret its publishes carry. Resolves when
|
|
165
|
+
* the page goes.
|
|
166
|
+
*/
|
|
167
|
+
var handleRealtimeSubscribe = async ({ req, res, raw, signal, hub, resolveChannels, grants }) => {
|
|
168
|
+
const admission = await admit(req, resolveChannels, grants);
|
|
169
|
+
if (!admission.ok) {
|
|
170
|
+
answer(res, admission.answer);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
let finish = () => void 0;
|
|
174
|
+
const gone = new Promise((resolve) => {
|
|
175
|
+
finish = resolve;
|
|
176
|
+
});
|
|
177
|
+
const stream = openEventStream(raw, {
|
|
178
|
+
onAbort: () => finish(),
|
|
179
|
+
retryMs: RETRY_MS
|
|
180
|
+
});
|
|
181
|
+
const connection = connectionFor(admission, stream.send, () => finish());
|
|
182
|
+
const release = onAbort(signal, () => finish());
|
|
183
|
+
stream.send("ready", {
|
|
184
|
+
connection: connection.id,
|
|
185
|
+
token: connection.token,
|
|
186
|
+
topics: [...admission.accepted.keys()],
|
|
187
|
+
refused: admission.refused
|
|
188
|
+
});
|
|
189
|
+
await hub.connect(connection);
|
|
190
|
+
await gone;
|
|
191
|
+
release();
|
|
192
|
+
stream.close();
|
|
193
|
+
await hub.disconnect(connection);
|
|
194
|
+
};
|
|
195
|
+
var parseBody = (body) => {
|
|
196
|
+
try {
|
|
197
|
+
const value = body ? JSON.parse(body) : void 0;
|
|
198
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
199
|
+
} catch {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
/** `POST /_realtime` `{ token, topic, type, data }` — a page on a Server-Sent Events connection, publishing. */
|
|
204
|
+
var handleRealtimePublish = async ({ req, res, hub }) => {
|
|
205
|
+
const body = parseBody(req.body);
|
|
206
|
+
const connection = typeof body?.token === "string" ? hub.find(body.token) : void 0;
|
|
207
|
+
if (!body || !connection) {
|
|
208
|
+
answer(res, {
|
|
209
|
+
status: 401,
|
|
210
|
+
error: "Not connected: open /_realtime first and publish with its token",
|
|
211
|
+
reason: "not_connected"
|
|
212
|
+
});
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const result = await publishFrom(hub, connection, body);
|
|
216
|
+
if (result.status === 204) {
|
|
217
|
+
res.setStatus(204);
|
|
218
|
+
res.send("");
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
answer(res, result);
|
|
222
|
+
};
|
|
223
|
+
//#endregion
|
|
224
|
+
export { admit, answer, connectionFor, handleRealtimePublish, handleRealtimeSubscribe, publishFrom };
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ChannelDeclaration, PubSubAdapter, RealtimeMessage, RealtimeSender } from '@plitzi/sdk-shared';
|
|
2
|
+
/** The space a connection belongs to: its topics are that space's and no other's. */
|
|
3
|
+
export type RealtimeSpace = {
|
|
4
|
+
spaceId: number;
|
|
5
|
+
environment: string;
|
|
6
|
+
};
|
|
7
|
+
/** One page, connected. */
|
|
8
|
+
export type RealtimeConnection = {
|
|
9
|
+
/** Its public name — the `from` of everything it sends. */
|
|
10
|
+
id: string;
|
|
11
|
+
/** Its secret: what a publish proves it comes from this connection with. Never sent to anyone else. */
|
|
12
|
+
token: string;
|
|
13
|
+
space: RealtimeSpace;
|
|
14
|
+
user?: RealtimeSender['user'];
|
|
15
|
+
/** The topics it subscribed to, with the channel each falls under. */
|
|
16
|
+
topics: Map<string, ChannelDeclaration>;
|
|
17
|
+
/** The grant each topic of a `grant: true` channel was opened with — what a revoke names. */
|
|
18
|
+
grants: Map<string, string>;
|
|
19
|
+
/** The topics it announced itself on, which hear `$leave` when it goes. */
|
|
20
|
+
announced: Set<string>;
|
|
21
|
+
/** Messages sent this second, by topic — the rate a channel allows is per connection, and a connection is here. */
|
|
22
|
+
sent: Map<string, {
|
|
23
|
+
second: number;
|
|
24
|
+
count: number;
|
|
25
|
+
}>;
|
|
26
|
+
send: (event: string, data: unknown) => void;
|
|
27
|
+
/** Closes it from the server's side — the server is going away. Its own `close` then lets it go as any other. */
|
|
28
|
+
end: () => void;
|
|
29
|
+
};
|
|
30
|
+
/** A space's topic, as the adapter knows it: namespaced, so two spaces never hear each other. */
|
|
31
|
+
export declare const adapterTopic: ({ spaceId, environment }: RealtimeSpace, topic: string) => string;
|
|
32
|
+
export type RealtimeHub = ReturnType<typeof createRealtimeHub>;
|
|
33
|
+
/**
|
|
34
|
+
* This process's connections, and the ONE adapter subscription per topic they share.
|
|
35
|
+
*
|
|
36
|
+
* A hundred pages on one board are a hundred connections here and a single subscription to the adapter: a message
|
|
37
|
+
* arrives once and is written to each of them. The last one to leave a topic unsubscribes it.
|
|
38
|
+
*/
|
|
39
|
+
export declare const createRealtimeHub: (pubsub: PubSubAdapter) => {
|
|
40
|
+
publish: (space: RealtimeSpace, message: RealtimeMessage) => Promise<void>;
|
|
41
|
+
from: (connection: RealtimeConnection, topic: string, type: string, data: unknown) => RealtimeMessage;
|
|
42
|
+
find: (token: string) => RealtimeConnection | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Lets go of `topic` every connection, on every replica, that opened it with `grant` — or with any grant, naming
|
|
45
|
+
* none. What stops a grant that is already in use; `grants.revoke` is what stops it being used again.
|
|
46
|
+
*/
|
|
47
|
+
revoke: (space: RealtimeSpace, topic: string, grant?: string) => Promise<void>;
|
|
48
|
+
/** Subscribes a new connection to its topics, and tells the members of each presence channel it arrived. */
|
|
49
|
+
connect: (connection: RealtimeConnection) => Promise<void>;
|
|
50
|
+
/** Unsubscribes a connection that went away, and tells the channels it announced itself on. */
|
|
51
|
+
disconnect: (connection: RealtimeConnection) => Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Every connection closed from here: the server is shutting down. An open socket or event stream is a request that
|
|
54
|
+
* never ends, so a server waiting for them to finish waited for ever — each is told to go, and goes the way it
|
|
55
|
+
* goes when its page leaves.
|
|
56
|
+
*/
|
|
57
|
+
closeAll: () => void;
|
|
58
|
+
/** How many connections this process holds — for tests and for a health endpoint. */
|
|
59
|
+
readonly connectionCount: number;
|
|
60
|
+
/** How many topics this process holds a subscription for — for tests and for a health endpoint. */
|
|
61
|
+
readonly topicCount: number;
|
|
62
|
+
};
|