@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
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { JOIN_TYPE, LEAVE_TYPE, REVOKED_TYPE } from "@plitzi/sdk-shared/realtime";
|
|
2
|
+
//#region src/modules/realtime/hub.ts
|
|
3
|
+
/** A space's topic, as the adapter knows it: namespaced, so two spaces never hear each other. */
|
|
4
|
+
var adapterTopic = ({ spaceId, environment }, topic) => `${spaceId}/${environment}/${topic}`;
|
|
5
|
+
/**
|
|
6
|
+
* Said between the servers, never to a page: every connection opened with the grant it names — or, naming none, with
|
|
7
|
+
* any grant for the topic — is let go of it. On the topic's own adapter channel, so it reaches every replica holding
|
|
8
|
+
* the topic, and only them. A page cannot send it: the types a page or a flow may use have no `$` but `$presence`.
|
|
9
|
+
*/
|
|
10
|
+
var REVOKE_TYPE = "$revoke";
|
|
11
|
+
var revokedGrant = (data) => typeof data === "object" && data !== null && "grant" in data && typeof data.grant === "string" ? data.grant : void 0;
|
|
12
|
+
/** A message as the adapter carried it — written by this module, but read from a transport that is not. */
|
|
13
|
+
var isMessage = (value) => typeof value === "object" && value !== null && "topic" in value && typeof value.topic === "string" && "type" in value && typeof value.type === "string" && "from" in value && typeof value.from === "string" && "at" in value && typeof value.at === "number";
|
|
14
|
+
var parse = (raw) => {
|
|
15
|
+
try {
|
|
16
|
+
const value = JSON.parse(raw);
|
|
17
|
+
return isMessage(value) ? value : void 0;
|
|
18
|
+
} catch {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* This process's connections, and the ONE adapter subscription per topic they share.
|
|
24
|
+
*
|
|
25
|
+
* A hundred pages on one board are a hundred connections here and a single subscription to the adapter: a message
|
|
26
|
+
* arrives once and is written to each of them. The last one to leave a topic unsubscribes it.
|
|
27
|
+
*/
|
|
28
|
+
var createRealtimeHub = (pubsub) => {
|
|
29
|
+
const byToken = /* @__PURE__ */ new Map();
|
|
30
|
+
const local = /* @__PURE__ */ new Map();
|
|
31
|
+
const publish = (space, message) => pubsub.publish(adapterTopic(space, message.topic), JSON.stringify(message));
|
|
32
|
+
const join = async (connection, topic) => {
|
|
33
|
+
const key = adapterTopic(connection.space, topic);
|
|
34
|
+
let entry = local.get(key);
|
|
35
|
+
if (!entry) {
|
|
36
|
+
const connections = /* @__PURE__ */ new Set();
|
|
37
|
+
entry = {
|
|
38
|
+
connections,
|
|
39
|
+
release: pubsub.subscribe(key, (raw) => {
|
|
40
|
+
const message = parse(raw);
|
|
41
|
+
if (message?.type === REVOKE_TYPE) {
|
|
42
|
+
const grant = revokedGrant(message.data);
|
|
43
|
+
[...connections].filter((member) => {
|
|
44
|
+
const opened = member.grants.get(message.topic);
|
|
45
|
+
return opened !== void 0 && (grant === void 0 || opened === grant);
|
|
46
|
+
}).forEach((member) => void releaseTopic(member, message.topic));
|
|
47
|
+
} else if (message) connections.forEach((member) => member.send("message", message));
|
|
48
|
+
})
|
|
49
|
+
};
|
|
50
|
+
local.set(key, entry);
|
|
51
|
+
}
|
|
52
|
+
entry.connections.add(connection);
|
|
53
|
+
await entry.release;
|
|
54
|
+
};
|
|
55
|
+
const leave = async (connection, topic) => {
|
|
56
|
+
const key = adapterTopic(connection.space, topic);
|
|
57
|
+
const entry = local.get(key);
|
|
58
|
+
if (!entry) return;
|
|
59
|
+
entry.connections.delete(connection);
|
|
60
|
+
if (entry.connections.size === 0) {
|
|
61
|
+
local.delete(key);
|
|
62
|
+
await (await entry.release)();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
/** A stamped message from this connection: `from` and `user` are the server's, whatever was sent. */
|
|
66
|
+
const from = (connection, topic, type, data) => ({
|
|
67
|
+
topic,
|
|
68
|
+
type,
|
|
69
|
+
data,
|
|
70
|
+
from: connection.id,
|
|
71
|
+
...connection.user ? { user: connection.user } : {},
|
|
72
|
+
at: Date.now()
|
|
73
|
+
});
|
|
74
|
+
/**
|
|
75
|
+
* Lets `connection` go of `topic`: it is told, the others on a presence channel hear it leave, and it no longer
|
|
76
|
+
* hears the topic or may send on it.
|
|
77
|
+
*/
|
|
78
|
+
const releaseTopic = async (connection, topic) => {
|
|
79
|
+
connection.send("message", {
|
|
80
|
+
topic,
|
|
81
|
+
type: REVOKED_TYPE,
|
|
82
|
+
data: null,
|
|
83
|
+
from: "server",
|
|
84
|
+
at: Date.now()
|
|
85
|
+
});
|
|
86
|
+
connection.topics.delete(topic);
|
|
87
|
+
connection.grants.delete(topic);
|
|
88
|
+
await leave(connection, topic);
|
|
89
|
+
if (connection.announced.delete(topic)) await publish(connection.space, from(connection, topic, LEAVE_TYPE, null));
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
publish,
|
|
93
|
+
from,
|
|
94
|
+
find: (token) => byToken.get(token),
|
|
95
|
+
/**
|
|
96
|
+
* Lets go of `topic` every connection, on every replica, that opened it with `grant` — or with any grant, naming
|
|
97
|
+
* none. What stops a grant that is already in use; `grants.revoke` is what stops it being used again.
|
|
98
|
+
*/
|
|
99
|
+
revoke: (space, topic, grant) => publish(space, {
|
|
100
|
+
topic,
|
|
101
|
+
type: REVOKE_TYPE,
|
|
102
|
+
data: grant === void 0 ? null : { grant },
|
|
103
|
+
from: "server",
|
|
104
|
+
at: Date.now()
|
|
105
|
+
}),
|
|
106
|
+
/** Subscribes a new connection to its topics, and tells the members of each presence channel it arrived. */
|
|
107
|
+
connect: async (connection) => {
|
|
108
|
+
byToken.set(connection.token, connection);
|
|
109
|
+
for (const [topic, declaration] of connection.topics) {
|
|
110
|
+
await join(connection, topic);
|
|
111
|
+
if (declaration.presence) await publish(connection.space, from(connection, topic, JOIN_TYPE, null));
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
/** Unsubscribes a connection that went away, and tells the channels it announced itself on. */
|
|
115
|
+
disconnect: async (connection) => {
|
|
116
|
+
byToken.delete(connection.token);
|
|
117
|
+
for (const topic of connection.topics.keys()) {
|
|
118
|
+
if (connection.announced.has(topic)) await publish(connection.space, from(connection, topic, LEAVE_TYPE, null));
|
|
119
|
+
await leave(connection, topic);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
/**
|
|
123
|
+
* Every connection closed from here: the server is shutting down. An open socket or event stream is a request that
|
|
124
|
+
* never ends, so a server waiting for them to finish waited for ever — each is told to go, and goes the way it
|
|
125
|
+
* goes when its page leaves.
|
|
126
|
+
*/
|
|
127
|
+
closeAll: () => {
|
|
128
|
+
[...byToken.values()].forEach((connection) => connection.end());
|
|
129
|
+
},
|
|
130
|
+
/** How many connections this process holds — for tests and for a health endpoint. */
|
|
131
|
+
get connectionCount() {
|
|
132
|
+
return byToken.size;
|
|
133
|
+
},
|
|
134
|
+
/** How many topics this process holds a subscription for — for tests and for a health endpoint. */
|
|
135
|
+
get topicCount() {
|
|
136
|
+
return local.size;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
//#endregion
|
|
141
|
+
export { adapterTopic, createRealtimeHub };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ChannelResolver } from './declarations';
|
|
2
|
+
import { RealtimeGrants } from './grants';
|
|
3
|
+
import { RealtimeHub } from './hub';
|
|
4
|
+
import { RealtimeTransport, SSRServerConfig } from '@plitzi/sdk-shared';
|
|
5
|
+
export { createMemoryPubSub } from './memoryPubSub';
|
|
6
|
+
export { handleRealtimePublish, handleRealtimeSubscribe } from './handlers';
|
|
7
|
+
export { handleRealtimeSocket } from './socket';
|
|
8
|
+
export type RealtimeModule = {
|
|
9
|
+
hub: RealtimeHub;
|
|
10
|
+
path: string;
|
|
11
|
+
/** What pages are told to connect with. Both are served whatever this says. */
|
|
12
|
+
transport: RealtimeTransport;
|
|
13
|
+
/** Origins besides the server's own whose pages may open a socket. */
|
|
14
|
+
allowedOrigins: readonly string[];
|
|
15
|
+
resolveChannels: ChannelResolver;
|
|
16
|
+
/** The grants issued for topics of channels declared `grant: true`, and the check a subscription makes. */
|
|
17
|
+
grants: RealtimeGrants;
|
|
18
|
+
/** Closes every connection this process holds: the server is shutting down (`HttpServerParts.onClosing`). */
|
|
19
|
+
close: () => void;
|
|
20
|
+
/**
|
|
21
|
+
* Publishes from the server — what a flow's `realtime.publish` does. Checked against the space's channels like a
|
|
22
|
+
* page's publish, and sent `from: 'server'`: the one sender a page cannot be.
|
|
23
|
+
*/
|
|
24
|
+
publish: (space: {
|
|
25
|
+
spaceId: number;
|
|
26
|
+
environment: string;
|
|
27
|
+
}, topic: string, type: string, data: unknown) => Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* A grant for one topic of a channel declared `grant: true` — what a flow's `realtime.grant` hands a page, once it
|
|
30
|
+
* has decided the visitor may be there. Refused for a topic of any other channel: a grant it would never ask for
|
|
31
|
+
* is a mistake in the flow, not a key to hand out.
|
|
32
|
+
*/
|
|
33
|
+
grant: (space: {
|
|
34
|
+
spaceId: number;
|
|
35
|
+
environment: string;
|
|
36
|
+
}, topic: string, ttlSeconds?: number) => Promise<string>;
|
|
37
|
+
/**
|
|
38
|
+
* `grant` no longer opens `topic` — or, naming none, no grant issued for it until now does: nobody who holds one gets
|
|
39
|
+
* in again, and whoever is on the topic with one, on any replica, is let go of it (`$revoked`). A page kept out asks
|
|
40
|
+
* the action that lets it in for a new grant, if it still may.
|
|
41
|
+
*/
|
|
42
|
+
revoke: (space: {
|
|
43
|
+
spaceId: number;
|
|
44
|
+
environment: string;
|
|
45
|
+
}, topic: string, grant?: string) => Promise<void>;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* The server's realtime channels, built once per config — or `undefined` when they are off or cannot be: without the
|
|
49
|
+
* space's documents (`getOfflineData`) there is nothing to say which channels a space declares, so none is opened.
|
|
50
|
+
*/
|
|
51
|
+
export declare const realtimeModuleFor: (config: SSRServerConfig) => RealtimeModule | undefined;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { fleetStore } from "../../core/server/fleet/link.js";
|
|
2
|
+
import { KV_METHODS, createMemoryKv } from "../actions/runtime/memoryKv.js";
|
|
3
|
+
import { createRealtimeGrants } from "./grants.js";
|
|
4
|
+
import { createChannelResolver } from "./declarations.js";
|
|
5
|
+
import { createRealtimeHub } from "./hub.js";
|
|
6
|
+
import { createMemoryPubSub } from "./memoryPubSub.js";
|
|
7
|
+
import { handleRealtimePublish, handleRealtimeSubscribe } from "./handlers.js";
|
|
8
|
+
import { handleRealtimeSocket } from "./socket.js";
|
|
9
|
+
import { matchChannel } from "@plitzi/sdk-shared/realtime";
|
|
10
|
+
//#region src/modules/realtime/index.ts
|
|
11
|
+
/** A type a flow publishes: its own vocabulary — the `$` types are the channel's. */
|
|
12
|
+
var SERVER_TYPE = /^[A-Za-z0-9_.:-]{1,64}$/;
|
|
13
|
+
var modules = /* @__PURE__ */ new WeakMap();
|
|
14
|
+
/**
|
|
15
|
+
* The server's realtime channels, built once per config — or `undefined` when they are off or cannot be: without the
|
|
16
|
+
* space's documents (`getOfflineData`) there is nothing to say which channels a space declares, so none is opened.
|
|
17
|
+
*/
|
|
18
|
+
var realtimeModuleFor = (config) => {
|
|
19
|
+
const getOfflineData = config.adapters.getOfflineData;
|
|
20
|
+
if (config.realtime === false || !getOfflineData) return;
|
|
21
|
+
const existing = modules.get(config);
|
|
22
|
+
if (existing) return existing;
|
|
23
|
+
const hub = createRealtimeHub(config.realtime?.pubsub ?? createMemoryPubSub());
|
|
24
|
+
const resolveChannels = createChannelResolver(getOfflineData);
|
|
25
|
+
const grants = createRealtimeGrants(config.action?.kv ?? fleetStore("realtime.grants", KV_METHODS) ?? createMemoryKv());
|
|
26
|
+
const module = {
|
|
27
|
+
hub,
|
|
28
|
+
path: config.realtime?.path ?? "/_realtime",
|
|
29
|
+
transport: config.realtime?.transport ?? "sse",
|
|
30
|
+
allowedOrigins: config.realtime?.allowedOrigins ?? [],
|
|
31
|
+
resolveChannels,
|
|
32
|
+
grants,
|
|
33
|
+
close: () => hub.closeAll(),
|
|
34
|
+
publish: async (space, topic, type, data) => {
|
|
35
|
+
const channels = await resolveChannels(space.spaceId, space.environment);
|
|
36
|
+
if (!matchChannel(topic, channels)) throw new Error(`No channel of this space matches "${topic}": declare it in the space's \`channels\``);
|
|
37
|
+
if (!SERVER_TYPE.test(type)) throw new Error("A message type is 1-64 of A-Z a-z 0-9 _ . : -");
|
|
38
|
+
await hub.publish(space, {
|
|
39
|
+
topic,
|
|
40
|
+
type,
|
|
41
|
+
data,
|
|
42
|
+
from: "server",
|
|
43
|
+
at: Date.now()
|
|
44
|
+
});
|
|
45
|
+
},
|
|
46
|
+
grant: async (space, topic, ttlSeconds) => {
|
|
47
|
+
const match = matchChannel(topic, await resolveChannels(space.spaceId, space.environment));
|
|
48
|
+
if (!match) throw new Error(`No channel of this space matches "${topic}": declare it in the space's \`channels\``);
|
|
49
|
+
if (match.declaration.grant !== true) throw new Error(`"${match.pattern}" is open to anyone its access lets in, so it takes no grant: declare it \`grant: true\` to make its topics private`);
|
|
50
|
+
return grants.issue(space, topic, ttlSeconds);
|
|
51
|
+
},
|
|
52
|
+
revoke: async (space, topic, grant) => {
|
|
53
|
+
if (matchChannel(topic, await resolveChannels(space.spaceId, space.environment))?.declaration.grant !== true) throw new Error(`"${topic}" is not a topic of a private (\`grant: true\`) channel of this space: it has no grants to revoke`);
|
|
54
|
+
await grants.revoke(space, topic, grant);
|
|
55
|
+
await hub.revoke(space, topic, grant);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
modules.set(config, module);
|
|
59
|
+
return module;
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
export { handleRealtimePublish, handleRealtimeSocket, handleRealtimeSubscribe, realtimeModuleFor };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { PubSubAdapter } from '@plitzi/sdk-shared';
|
|
2
|
+
/**
|
|
3
|
+
* The default pub/sub: in this process, and across its workers.
|
|
4
|
+
*
|
|
5
|
+
* A page connected to one worker must hear what another worker published, so a publish is delivered here AND sent to
|
|
6
|
+
* the rest of the fleet as a broadcast, which each process delivers to its own listeners. Without workers the
|
|
7
|
+
* broadcast goes nowhere and this is a Map of listeners. A second REPLICA shares nothing with this one: a deployment
|
|
8
|
+
* that runs several hands the server an adapter over something shared (Redis, NATS…).
|
|
9
|
+
*/
|
|
10
|
+
export declare const createMemoryPubSub: () => PubSubAdapter;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { broadcast, channelName, onBroadcast } from "../../core/server/fleet/link.js";
|
|
2
|
+
//#region src/modules/realtime/memoryPubSub.ts
|
|
3
|
+
var isRelayed = (value) => typeof value === "object" && value !== null && "topic" in value && typeof value.topic === "string" && "message" in value && typeof value.message === "string";
|
|
4
|
+
/**
|
|
5
|
+
* The default pub/sub: in this process, and across its workers.
|
|
6
|
+
*
|
|
7
|
+
* A page connected to one worker must hear what another worker published, so a publish is delivered here AND sent to
|
|
8
|
+
* the rest of the fleet as a broadcast, which each process delivers to its own listeners. Without workers the
|
|
9
|
+
* broadcast goes nowhere and this is a Map of listeners. A second REPLICA shares nothing with this one: a deployment
|
|
10
|
+
* that runs several hands the server an adapter over something shared (Redis, NATS…).
|
|
11
|
+
*/
|
|
12
|
+
var createMemoryPubSub = () => {
|
|
13
|
+
const topics = /* @__PURE__ */ new Map();
|
|
14
|
+
const fleet = channelName("realtime.pubsub");
|
|
15
|
+
const deliver = (topic, message) => {
|
|
16
|
+
for (const listener of topics.get(topic) ?? []) listener(message);
|
|
17
|
+
};
|
|
18
|
+
onBroadcast(fleet, (payload) => {
|
|
19
|
+
if (isRelayed(payload)) deliver(payload.topic, payload.message);
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
publish: (topic, message) => {
|
|
23
|
+
deliver(topic, message);
|
|
24
|
+
broadcast(fleet, {
|
|
25
|
+
topic,
|
|
26
|
+
message
|
|
27
|
+
});
|
|
28
|
+
return Promise.resolve();
|
|
29
|
+
},
|
|
30
|
+
subscribe: (topic, listener) => {
|
|
31
|
+
const listeners = topics.get(topic) ?? /* @__PURE__ */ new Set();
|
|
32
|
+
listeners.add(listener);
|
|
33
|
+
topics.set(topic, listeners);
|
|
34
|
+
return Promise.resolve(() => {
|
|
35
|
+
listeners.delete(listener);
|
|
36
|
+
if (listeners.size === 0) topics.delete(topic);
|
|
37
|
+
return Promise.resolve();
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
//#endregion
|
|
43
|
+
export { createMemoryPubSub };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { PubSubAdapter } from '@plitzi/sdk-shared';
|
|
2
|
+
/**
|
|
3
|
+
* The two Redis connections pub/sub needs, by shape — `ioredis` fits as it is, and so does anything answering the same
|
|
4
|
+
* calls. A connection in subscriber mode can do nothing else, which is why it is a second one.
|
|
5
|
+
*/
|
|
6
|
+
export type RedisPubSubClients = {
|
|
7
|
+
publisher: {
|
|
8
|
+
publish: (channel: string, message: string) => Promise<unknown>;
|
|
9
|
+
};
|
|
10
|
+
subscriber: {
|
|
11
|
+
subscribe: (channel: string) => Promise<unknown>;
|
|
12
|
+
unsubscribe: (channel: string) => Promise<unknown>;
|
|
13
|
+
on: (event: 'message', listener: (channel: string, message: string) => void) => unknown;
|
|
14
|
+
};
|
|
15
|
+
/** Prefixed onto every channel, so the realtime topics cannot collide with anything else on the server. */
|
|
16
|
+
prefix?: string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Pub/sub over Redis: what a deployment of several replicas hands `createServer({ realtime: { pubsub } })`.
|
|
20
|
+
*
|
|
21
|
+
* One Redis subscription per topic per process, however many pages of that process listen to it: the listeners are
|
|
22
|
+
* kept here, and the channel is subscribed on the first and unsubscribed with the last.
|
|
23
|
+
*/
|
|
24
|
+
export declare const createRedisPubSub: ({ publisher, subscriber, prefix }: RedisPubSubClients) => PubSubAdapter;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//#region src/modules/realtime/redisPubSub.ts
|
|
2
|
+
/**
|
|
3
|
+
* Pub/sub over Redis: what a deployment of several replicas hands `createServer({ realtime: { pubsub } })`.
|
|
4
|
+
*
|
|
5
|
+
* One Redis subscription per topic per process, however many pages of that process listen to it: the listeners are
|
|
6
|
+
* kept here, and the channel is subscribed on the first and unsubscribed with the last.
|
|
7
|
+
*/
|
|
8
|
+
var createRedisPubSub = ({ publisher, subscriber, prefix = "plitzi:rt:" }) => {
|
|
9
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
10
|
+
subscriber.on("message", (channel, message) => {
|
|
11
|
+
if (!channel.startsWith(prefix)) return;
|
|
12
|
+
listeners.get(channel.slice(prefix.length))?.forEach((listener) => listener(message));
|
|
13
|
+
});
|
|
14
|
+
return {
|
|
15
|
+
publish: async (topic, message) => {
|
|
16
|
+
await publisher.publish(`${prefix}${topic}`, message);
|
|
17
|
+
},
|
|
18
|
+
subscribe: async (topic, listener) => {
|
|
19
|
+
const set = listeners.get(topic) ?? /* @__PURE__ */ new Set();
|
|
20
|
+
if (!set.size) {
|
|
21
|
+
listeners.set(topic, set);
|
|
22
|
+
await subscriber.subscribe(`${prefix}${topic}`);
|
|
23
|
+
}
|
|
24
|
+
set.add(listener);
|
|
25
|
+
return async () => {
|
|
26
|
+
set.delete(listener);
|
|
27
|
+
if (!set.size) {
|
|
28
|
+
listeners.delete(topic);
|
|
29
|
+
await subscriber.unsubscribe(`${prefix}${topic}`);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
36
|
+
export { createRedisPubSub };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ChannelResolver } from './declarations';
|
|
2
|
+
import { RealtimeGrants } from './grants';
|
|
3
|
+
import { RealtimeHub } from './hub';
|
|
4
|
+
import { UpgradeRequest } from '../../core/http/socketResponse';
|
|
5
|
+
import { SSRRequest, SSRResponseHelpers } from '@plitzi/sdk-shared';
|
|
6
|
+
import { IncomingMessage } from 'node:http';
|
|
7
|
+
export type SocketDeps = {
|
|
8
|
+
req: SSRRequest;
|
|
9
|
+
res: SSRResponseHelpers;
|
|
10
|
+
raw: IncomingMessage;
|
|
11
|
+
upgrade: UpgradeRequest;
|
|
12
|
+
hub: RealtimeHub;
|
|
13
|
+
resolveChannels: ChannelResolver;
|
|
14
|
+
grants: RealtimeGrants;
|
|
15
|
+
allowedOrigins: readonly string[];
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* A page's realtime connection as a WebSocket: the same topics, rules and messages as the Server-Sent Events one, in
|
|
19
|
+
* both directions over one socket. A publish is a frame, not a request — no headers, no second connection, no token
|
|
20
|
+
* to present: the socket is the credential. Its answer arrives as an `ack` carrying the publish's `id`.
|
|
21
|
+
*
|
|
22
|
+
* Refused the way the stream is — a `403` or a `422`, in HTTP, before anything switches — so a page falls back or
|
|
23
|
+
* reports it exactly as it would there.
|
|
24
|
+
*/
|
|
25
|
+
export declare const handleRealtimeSocket: ({ req, res, raw, upgrade, hub, resolveChannels, grants, allowedOrigins }: SocketDeps) => Promise<void>;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { admit, answer, connectionFor, publishFrom } from "./handlers.js";
|
|
2
|
+
import { requestOrigin } from "../../core/requestParser.js";
|
|
3
|
+
import { frameText } from "../../helpers/wsFrame.js";
|
|
4
|
+
//#region src/modules/realtime/socket.ts
|
|
5
|
+
/** The largest frame a page may send: well above the largest message a channel may declare, and bounded. */
|
|
6
|
+
var MAX_FRAME_BYTES = 262144;
|
|
7
|
+
/**
|
|
8
|
+
* What may wait in a socket's buffer for a page that stopped reading. Past it the page is gone in all but name, and
|
|
9
|
+
* holding everything it has not read is how one stalled tab grows a process.
|
|
10
|
+
*/
|
|
11
|
+
var MAX_BUFFERED_BYTES = 1048576;
|
|
12
|
+
/** How often the server asks a socket whether the page behind it is still there — and gives up on one that did not answer the last time. */
|
|
13
|
+
var PING_MS = 25e3;
|
|
14
|
+
var server;
|
|
15
|
+
/**
|
|
16
|
+
* Loaded the first time a page asks for a socket: a server whose pages never do — or that runs behind HTTP/2, where
|
|
17
|
+
* none can — never pays for the module.
|
|
18
|
+
*/
|
|
19
|
+
var socketServer = () => server ??= import("ws").then(({ WebSocketServer: Server }) => new Server({
|
|
20
|
+
noServer: true,
|
|
21
|
+
maxPayload: MAX_FRAME_BYTES
|
|
22
|
+
}));
|
|
23
|
+
/**
|
|
24
|
+
* A browser opens a WebSocket to any address, carrying that address's cookies, from any page — CORS does not apply to
|
|
25
|
+
* it. So a socket is refused unless the page that opened it is on this origin, or on one the deployment lists: without
|
|
26
|
+
* this, another site could open a channel restricted to signed-in visitors with a visitor's session and read it.
|
|
27
|
+
* A client that is not a browser sends no `Origin`, and has no visitor's cookies to borrow.
|
|
28
|
+
*/
|
|
29
|
+
var originAllowed = (raw, req, allowed) => {
|
|
30
|
+
const origin = raw.headers.origin;
|
|
31
|
+
return origin === void 0 || origin === requestOrigin(req) || allowed.includes(origin);
|
|
32
|
+
};
|
|
33
|
+
/** A frame's text, whichever of the shapes `ws` hands a frame over in. */
|
|
34
|
+
var parse = (frame) => {
|
|
35
|
+
try {
|
|
36
|
+
const value = JSON.parse(frameText(frame));
|
|
37
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
38
|
+
} catch {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
/** A publish's id, echoed on its `ack`: what the page matches the answer to. Short, and never anything else. */
|
|
43
|
+
var ackId = (id) => typeof id === "number" && Number.isInteger(id) || typeof id === "string" && id.length <= 64 ? id : void 0;
|
|
44
|
+
/**
|
|
45
|
+
* A page's realtime connection as a WebSocket: the same topics, rules and messages as the Server-Sent Events one, in
|
|
46
|
+
* both directions over one socket. A publish is a frame, not a request — no headers, no second connection, no token
|
|
47
|
+
* to present: the socket is the credential. Its answer arrives as an `ack` carrying the publish's `id`.
|
|
48
|
+
*
|
|
49
|
+
* Refused the way the stream is — a `403` or a `422`, in HTTP, before anything switches — so a page falls back or
|
|
50
|
+
* reports it exactly as it would there.
|
|
51
|
+
*/
|
|
52
|
+
var handleRealtimeSocket = async ({ req, res, raw, upgrade, hub, resolveChannels, grants, allowedOrigins }) => {
|
|
53
|
+
if (!originAllowed(raw, req, allowedOrigins)) {
|
|
54
|
+
answer(res, {
|
|
55
|
+
status: 403,
|
|
56
|
+
error: "A page on another site may not open this",
|
|
57
|
+
reason: "foreign"
|
|
58
|
+
});
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const admission = await admit(req, resolveChannels, grants);
|
|
62
|
+
if (!admission.ok) {
|
|
63
|
+
answer(res, admission.answer);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const sockets = await socketServer();
|
|
67
|
+
upgrade.taken = true;
|
|
68
|
+
res.setStatus(101);
|
|
69
|
+
sockets.handleUpgrade(raw, upgrade.socket, upgrade.head, (socket) => {
|
|
70
|
+
const send = (event, data) => {
|
|
71
|
+
if (socket.readyState !== socket.OPEN) return;
|
|
72
|
+
if (socket.bufferedAmount > MAX_BUFFERED_BYTES) {
|
|
73
|
+
socket.terminate();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
socket.send(JSON.stringify({
|
|
77
|
+
event,
|
|
78
|
+
data
|
|
79
|
+
}));
|
|
80
|
+
};
|
|
81
|
+
const connection = connectionFor(admission, send, () => socket.close(1001, "The server is going away"));
|
|
82
|
+
let alive = true;
|
|
83
|
+
const heartbeat = setInterval(() => {
|
|
84
|
+
if (!alive) {
|
|
85
|
+
socket.terminate();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
alive = false;
|
|
89
|
+
socket.ping();
|
|
90
|
+
}, PING_MS);
|
|
91
|
+
socket.on("pong", () => {
|
|
92
|
+
alive = true;
|
|
93
|
+
});
|
|
94
|
+
socket.on("message", (frame, binary) => {
|
|
95
|
+
alive = true;
|
|
96
|
+
const message = binary ? void 0 : parse(frame);
|
|
97
|
+
if (!message) return;
|
|
98
|
+
const id = ackId(message.id);
|
|
99
|
+
publishFrom(hub, connection, message).then((result) => {
|
|
100
|
+
if (id !== void 0) send("ack", {
|
|
101
|
+
id,
|
|
102
|
+
ok: result.status === 204,
|
|
103
|
+
status: result.status,
|
|
104
|
+
...result.reason ? { reason: result.reason } : {}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
socket.on("close", () => {
|
|
109
|
+
clearInterval(heartbeat);
|
|
110
|
+
hub.disconnect(connection);
|
|
111
|
+
});
|
|
112
|
+
socket.on("error", () => socket.terminate());
|
|
113
|
+
send("ready", {
|
|
114
|
+
connection: connection.id,
|
|
115
|
+
topics: [...admission.accepted.keys()],
|
|
116
|
+
refused: admission.refused
|
|
117
|
+
});
|
|
118
|
+
hub.connect(connection);
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
//#endregion
|
|
122
|
+
export { handleRealtimeSocket };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { TtlCache } from '../../helpers/cache';
|
|
2
2
|
import { PluginManager } from '../../plugins/manager';
|
|
3
3
|
import { SSRPageServerConfig, SSRRequest, SSRResponseHelpers } from '@plitzi/sdk-shared';
|
|
4
|
+
/**
|
|
5
|
+
* Rewrites the request to the page the browser is actually on.
|
|
6
|
+
*
|
|
7
|
+
* A refresh is issued against `/_rsc`, not against `/blog/my-post`, so resolving straight from `req.path` matches
|
|
8
|
+
* nothing in `schema.pages` and every client-side refresh silently returns no data. The SDK sends the visitor's
|
|
9
|
+
* location as `?location=`, and route params plus the page parameter are read from that instead.
|
|
10
|
+
*
|
|
11
|
+
* What a refresh asks for besides — the next page of a "load more", a search a provider was reloaded with — rides on
|
|
12
|
+
* the query string beside `location`, and is read over the location's own query. It used to be dropped with the rest
|
|
13
|
+
* of the endpoint's query, so a provider paged in place was answered its first page every time.
|
|
14
|
+
*
|
|
15
|
+
* Reflecting both is safe by construction: the location is matched against this space's own page list, and the rest
|
|
16
|
+
* is what the same visitor could have put in the page's own address; the cache key is the whole request, so no answer
|
|
17
|
+
* is served for a query it was not made for.
|
|
18
|
+
*/
|
|
19
|
+
export declare const withPageLocation: (req: SSRRequest) => SSRRequest;
|
|
4
20
|
/**
|
|
5
21
|
* Handles GET /_rsc requests.
|
|
6
22
|
*
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { serverLog } from "../../helpers/serverLog.js";
|
|
1
2
|
import { resolveDebugAuthorization } from "../../helpers/debugAuthorization.js";
|
|
2
3
|
import { DEFAULT_TTL_MS } from "../../helpers/cache/defaults.js";
|
|
3
4
|
import { buildRscCacheKey } from "../../helpers/cache/keys.js";
|
|
@@ -19,6 +20,8 @@ var meterRsc = async (req, config, spaceId, environment, revision, cached) => {
|
|
|
19
20
|
});
|
|
20
21
|
} catch {}
|
|
21
22
|
};
|
|
23
|
+
/** The endpoint's own parameters: what it is asked about, never input to what it resolves. */
|
|
24
|
+
var ENDPOINT_PARAMS = /* @__PURE__ */ new Set(["location", "ids"]);
|
|
22
25
|
/**
|
|
23
26
|
* Rewrites the request to the page the browser is actually on.
|
|
24
27
|
*
|
|
@@ -26,22 +29,26 @@ var meterRsc = async (req, config, spaceId, environment, revision, cached) => {
|
|
|
26
29
|
* nothing in `schema.pages` and every client-side refresh silently returns no data. The SDK sends the visitor's
|
|
27
30
|
* location as `?location=`, and route params plus the page parameter are read from that instead.
|
|
28
31
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
32
|
+
* What a refresh asks for besides — the next page of a "load more", a search a provider was reloaded with — rides on
|
|
33
|
+
* the query string beside `location`, and is read over the location's own query. It used to be dropped with the rest
|
|
34
|
+
* of the endpoint's query, so a provider paged in place was answered its first page every time.
|
|
35
|
+
*
|
|
36
|
+
* Reflecting both is safe by construction: the location is matched against this space's own page list, and the rest
|
|
37
|
+
* is what the same visitor could have put in the page's own address; the cache key is the whole request, so no answer
|
|
38
|
+
* is served for a query it was not made for.
|
|
31
39
|
*/
|
|
32
40
|
var withPageLocation = (req) => {
|
|
33
41
|
const location = req.query.location;
|
|
34
42
|
if (!location || !location.startsWith("/") || location.length > MAX_PAGE_LOCATION) return req;
|
|
35
43
|
const url = new URL(location, `${req.protocol}://${req.hostname}`);
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}, {});
|
|
44
|
+
const asked = Object.entries(req.query).filter(([key]) => !ENDPOINT_PARAMS.has(key));
|
|
45
|
+
const query = Object.fromEntries([...url.searchParams.entries(), ...asked]);
|
|
46
|
+
const search = new URLSearchParams(query).toString();
|
|
40
47
|
return {
|
|
41
48
|
...req,
|
|
42
49
|
path: url.pathname,
|
|
43
|
-
search:
|
|
44
|
-
url: `${url.pathname}${
|
|
50
|
+
search: search ? `?${search}` : "",
|
|
51
|
+
url: `${url.pathname}${search ? `?${search}` : ""}`,
|
|
45
52
|
query
|
|
46
53
|
};
|
|
47
54
|
};
|
|
@@ -112,7 +119,7 @@ var handleRsc = async (req, res, config, _pluginManager, cache) => {
|
|
|
112
119
|
loadOfflineData
|
|
113
120
|
});
|
|
114
121
|
} catch (err) {
|
|
115
|
-
|
|
122
|
+
serverLog.error("RSC", "getRscData error", err);
|
|
116
123
|
res.setStatus(500);
|
|
117
124
|
res.send(JSON.stringify({ error: "RSC data fetch failed" }));
|
|
118
125
|
return;
|
|
@@ -135,4 +142,4 @@ var handleRsc = async (req, res, config, _pluginManager, cache) => {
|
|
|
135
142
|
res.send(payloadStr);
|
|
136
143
|
};
|
|
137
144
|
//#endregion
|
|
138
|
-
export { handleRsc };
|
|
145
|
+
export { handleRsc, withPageLocation };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { serverLog } from "../../helpers/serverLog.js";
|
|
1
2
|
import { matchRscPage } from "./matchRscPage.js";
|
|
2
3
|
import { collectServerElements } from "@plitzi/sdk-shared/schema/serverElements";
|
|
3
4
|
//#region src/modules/rsc/resolveRscData.ts
|
|
@@ -52,7 +53,7 @@ var resolveRscData = async ({ schema, req, spaceId, environment, user, ids, reso
|
|
|
52
53
|
}), timeoutMs, `element ${element.id}`)
|
|
53
54
|
})))).reduce((acum, result, index) => {
|
|
54
55
|
if (result.status === "rejected") {
|
|
55
|
-
|
|
56
|
+
serverLog.error("RSC", `element ${targets[index].id} failed to resolve`, result.reason);
|
|
56
57
|
return acum;
|
|
57
58
|
}
|
|
58
59
|
if (result.value.data !== void 0) acum[result.value.id] = result.value.data;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { SpaceRuntime } from './contract';
|
|
2
|
+
/** A packed runtime at most this big: its code and every dependency, not its data. */
|
|
3
|
+
export declare const MAX_RUNTIME_BUNDLE_BYTES: number;
|
|
4
|
+
/** A bundle's id: what its bytes are, so the same code packed twice is one bundle. */
|
|
5
|
+
export declare const runtimeBundleId: (bytes: Uint8Array) => string;
|
|
6
|
+
/**
|
|
7
|
+
* A runtime's module packed to travel: bundled for Node with every dependency it imports — but the platform's packages
|
|
8
|
+
* and React, which the host provides, so there is one copy of each — and gzipped. `entry` is the module whose default
|
|
9
|
+
* export is `defineRuntime(…)`.
|
|
10
|
+
*/
|
|
11
|
+
export declare const packRuntime: (entry: string) => Promise<Uint8Array>;
|
|
12
|
+
/**
|
|
13
|
+
* What a packed runtime holds, read without running any of it — what a platform checks a bundle with before keeping it.
|
|
14
|
+
* Throws, saying why, for bytes that are not one.
|
|
15
|
+
*/
|
|
16
|
+
export declare const inspectRuntime: (bytes: Uint8Array) => {
|
|
17
|
+
entry: string;
|
|
18
|
+
files: string[];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* A packed runtime written into `dir` and imported: its module's default export. `dir` must be inside the host's own
|
|
22
|
+
* project, so the packages the bundle leaves to the host resolve to the host's — one copy of each.
|
|
23
|
+
*/
|
|
24
|
+
export declare const loadRuntime: (bytes: Uint8Array, dir: string) => Promise<SpaceRuntime>;
|