@enbox/dwn-server 0.0.2 → 0.0.4
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/LICENSE +3 -2
- package/README.md +115 -215
- package/dist/esm/src/admin/activity-log.d.ts +44 -0
- package/dist/esm/src/admin/activity-log.d.ts.map +1 -0
- package/dist/esm/src/admin/activity-log.js +85 -0
- package/dist/esm/src/admin/activity-log.js.map +1 -0
- package/dist/esm/src/admin/admin-api.d.ts +61 -0
- package/dist/esm/src/admin/admin-api.d.ts.map +1 -0
- package/dist/esm/src/admin/admin-api.js +1047 -0
- package/dist/esm/src/admin/admin-api.js.map +1 -0
- package/dist/esm/src/admin/admin-auth.d.ts +9 -0
- package/dist/esm/src/admin/admin-auth.d.ts.map +1 -0
- package/dist/esm/src/admin/admin-auth.js +45 -0
- package/dist/esm/src/admin/admin-auth.js.map +1 -0
- package/dist/esm/src/admin/admin-store.d.ts +111 -0
- package/dist/esm/src/admin/admin-store.d.ts.map +1 -0
- package/dist/esm/src/admin/admin-store.js +376 -0
- package/dist/esm/src/admin/admin-store.js.map +1 -0
- package/dist/esm/src/admin/audit-log.d.ts +94 -0
- package/dist/esm/src/admin/audit-log.d.ts.map +1 -0
- package/dist/esm/src/admin/audit-log.js +220 -0
- package/dist/esm/src/admin/audit-log.js.map +1 -0
- package/dist/esm/src/admin/index.d.ts +10 -0
- package/dist/esm/src/admin/index.d.ts.map +1 -0
- package/dist/esm/src/admin/index.js +7 -0
- package/dist/esm/src/admin/index.js.map +1 -0
- package/dist/esm/src/admin/types.d.ts +306 -0
- package/dist/esm/src/admin/types.d.ts.map +1 -0
- package/dist/esm/src/admin/types.js +2 -0
- package/dist/esm/src/admin/types.js.map +1 -0
- package/dist/esm/src/admin/webhook-manager.d.ts +55 -0
- package/dist/esm/src/admin/webhook-manager.d.ts.map +1 -0
- package/dist/esm/src/admin/webhook-manager.js +184 -0
- package/dist/esm/src/admin/webhook-manager.js.map +1 -0
- package/dist/esm/src/config.d.ts +124 -9
- package/dist/esm/src/config.d.ts.map +1 -1
- package/dist/esm/src/config.js +155 -13
- package/dist/esm/src/config.js.map +1 -1
- package/dist/esm/src/connection/connection-manager.d.ts +32 -9
- package/dist/esm/src/connection/connection-manager.d.ts.map +1 -1
- package/dist/esm/src/connection/connection-manager.js +38 -5
- package/dist/esm/src/connection/connection-manager.js.map +1 -1
- package/dist/esm/src/connection/flow-controller.d.ts +53 -0
- package/dist/esm/src/connection/flow-controller.d.ts.map +1 -0
- package/dist/esm/src/connection/flow-controller.js +101 -0
- package/dist/esm/src/connection/flow-controller.js.map +1 -0
- package/dist/esm/src/connection/socket-connection.d.ts +54 -18
- package/dist/esm/src/connection/socket-connection.d.ts.map +1 -1
- package/dist/esm/src/connection/socket-connection.js +102 -40
- package/dist/esm/src/connection/socket-connection.js.map +1 -1
- package/dist/esm/src/delivery-service.d.ts +43 -0
- package/dist/esm/src/delivery-service.d.ts.map +1 -0
- package/dist/esm/src/delivery-service.js +574 -0
- package/dist/esm/src/delivery-service.js.map +1 -0
- package/dist/esm/src/dwn-error.d.ts +10 -1
- package/dist/esm/src/dwn-error.d.ts.map +1 -1
- package/dist/esm/src/dwn-error.js +9 -0
- package/dist/esm/src/dwn-error.js.map +1 -1
- package/dist/esm/src/dwn-server.d.ts +13 -6
- package/dist/esm/src/dwn-server.d.ts.map +1 -1
- package/dist/esm/src/dwn-server.js +199 -24
- package/dist/esm/src/dwn-server.js.map +1 -1
- package/dist/esm/src/http-api.d.ts +28 -13
- package/dist/esm/src/http-api.d.ts.map +1 -1
- package/dist/esm/src/http-api.js +649 -374
- package/dist/esm/src/http-api.js.map +1 -1
- package/dist/esm/src/index.d.ts +6 -2
- package/dist/esm/src/index.d.ts.map +1 -1
- package/dist/esm/src/index.js +4 -1
- package/dist/esm/src/index.js.map +1 -1
- package/dist/esm/src/json-rpc-api.js +2 -1
- package/dist/esm/src/json-rpc-api.js.map +1 -1
- package/dist/esm/src/json-rpc-handlers/dwn/process-message.d.ts.map +1 -1
- package/dist/esm/src/json-rpc-handlers/dwn/process-message.js +109 -7
- package/dist/esm/src/json-rpc-handlers/dwn/process-message.js.map +1 -1
- package/dist/esm/src/json-rpc-handlers/subscription/ack.d.ts +20 -0
- package/dist/esm/src/json-rpc-handlers/subscription/ack.d.ts.map +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/ack.js +41 -0
- package/dist/esm/src/json-rpc-handlers/subscription/ack.js.map +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/close.d.ts.map +1 -1
- package/dist/esm/src/json-rpc-handlers/subscription/close.js +1 -1
- package/dist/esm/src/json-rpc-handlers/subscription/close.js.map +1 -1
- package/dist/esm/src/json-rpc-handlers/subscription/index.d.ts +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/index.d.ts.map +1 -1
- package/dist/esm/src/json-rpc-handlers/subscription/index.js +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/index.js.map +1 -1
- package/dist/esm/src/lib/json-rpc-router.d.ts +25 -8
- package/dist/esm/src/lib/json-rpc-router.d.ts.map +1 -1
- package/dist/esm/src/lib/json-rpc-router.js.map +1 -1
- package/dist/esm/src/lib/sql-utils.d.ts +6 -0
- package/dist/esm/src/lib/sql-utils.d.ts.map +1 -0
- package/dist/esm/src/lib/sql-utils.js +8 -0
- package/dist/esm/src/lib/sql-utils.js.map +1 -0
- package/dist/esm/src/main.js +0 -6
- package/dist/esm/src/main.js.map +1 -1
- package/dist/esm/src/message-processed-hook.d.ts +35 -0
- package/dist/esm/src/message-processed-hook.d.ts.map +1 -0
- package/dist/esm/src/message-processed-hook.js +2 -0
- package/dist/esm/src/message-processed-hook.js.map +1 -0
- package/dist/esm/src/metrics.d.ts +14 -2
- package/dist/esm/src/metrics.d.ts.map +1 -1
- package/dist/esm/src/metrics.js +41 -1
- package/dist/esm/src/metrics.js.map +1 -1
- package/dist/esm/src/plugins/event-log-nats.d.ts +25 -0
- package/dist/esm/src/plugins/event-log-nats.d.ts.map +1 -0
- package/dist/esm/src/plugins/event-log-nats.js +379 -0
- package/dist/esm/src/plugins/event-log-nats.js.map +1 -0
- package/dist/esm/src/rate-limiter.d.ts +60 -0
- package/dist/esm/src/rate-limiter.d.ts.map +1 -0
- package/dist/esm/src/rate-limiter.js +116 -0
- package/dist/esm/src/rate-limiter.js.map +1 -0
- package/dist/esm/src/registration/jwt-provider-auth-plugin.d.ts +53 -0
- package/dist/esm/src/registration/jwt-provider-auth-plugin.d.ts.map +1 -0
- package/dist/esm/src/registration/jwt-provider-auth-plugin.js +90 -0
- package/dist/esm/src/registration/jwt-provider-auth-plugin.js.map +1 -0
- package/dist/esm/src/registration/open-auth-handler.d.ts +37 -0
- package/dist/esm/src/registration/open-auth-handler.d.ts.map +1 -0
- package/dist/esm/src/registration/open-auth-handler.js +214 -0
- package/dist/esm/src/registration/open-auth-handler.js.map +1 -0
- package/dist/esm/src/registration/proof-of-work-manager.d.ts +1 -1
- package/dist/esm/src/registration/proof-of-work-manager.d.ts.map +1 -1
- package/dist/esm/src/registration/proof-of-work-manager.js +3 -3
- package/dist/esm/src/registration/proof-of-work-manager.js.map +1 -1
- package/dist/esm/src/registration/provider-auth-plugin.d.ts +46 -0
- package/dist/esm/src/registration/provider-auth-plugin.d.ts.map +1 -0
- package/dist/esm/src/registration/provider-auth-plugin.js +29 -0
- package/dist/esm/src/registration/provider-auth-plugin.js.map +1 -0
- package/dist/esm/src/registration/registration-manager.d.ts +28 -5
- package/dist/esm/src/registration/registration-manager.d.ts.map +1 -1
- package/dist/esm/src/registration/registration-manager.js +83 -12
- package/dist/esm/src/registration/registration-manager.js.map +1 -1
- package/dist/esm/src/registration/registration-store.d.ts +83 -3
- package/dist/esm/src/registration/registration-store.d.ts.map +1 -1
- package/dist/esm/src/registration/registration-store.js +248 -11
- package/dist/esm/src/registration/registration-store.js.map +1 -1
- package/dist/esm/src/storage.d.ts +5 -5
- package/dist/esm/src/storage.d.ts.map +1 -1
- package/dist/esm/src/storage.js +105 -24
- package/dist/esm/src/storage.js.map +1 -1
- package/dist/esm/src/web5-connect/sql-ttl-cache.d.ts.map +1 -1
- package/dist/esm/src/web5-connect/sql-ttl-cache.js +11 -3
- package/dist/esm/src/web5-connect/sql-ttl-cache.js.map +1 -1
- package/dist/esm/src/web5-connect/web5-connect-server.d.ts.map +1 -1
- package/dist/esm/src/web5-connect/web5-connect-server.js +2 -2
- package/dist/esm/src/web5-connect/web5-connect-server.js.map +1 -1
- package/dist/esm/src/ws-api.d.ts +18 -4
- package/dist/esm/src/ws-api.d.ts.map +1 -1
- package/dist/esm/src/ws-api.js +12 -16
- package/dist/esm/src/ws-api.js.map +1 -1
- package/package.json +34 -53
- package/src/admin/activity-log.ts +100 -0
- package/src/admin/admin-api.ts +1308 -0
- package/src/admin/admin-auth.ts +56 -0
- package/src/admin/admin-store.ts +515 -0
- package/src/admin/audit-log.ts +327 -0
- package/src/admin/index.ts +34 -0
- package/src/admin/types.ts +352 -0
- package/src/admin/webhook-manager.ts +245 -0
- package/src/config.ts +190 -22
- package/src/connection/connection-manager.ts +67 -17
- package/src/connection/flow-controller.ts +117 -0
- package/src/connection/socket-connection.ts +144 -67
- package/src/delivery-service.ts +740 -0
- package/src/dwn-error.ts +11 -2
- package/src/dwn-server.ts +254 -39
- package/src/http-api.ts +736 -392
- package/src/index.ts +13 -2
- package/src/json-rpc-api.ts +2 -1
- package/src/json-rpc-handlers/dwn/process-message.ts +149 -15
- package/src/json-rpc-handlers/subscription/ack.ts +63 -0
- package/src/json-rpc-handlers/subscription/close.ts +5 -9
- package/src/json-rpc-handlers/subscription/index.ts +1 -0
- package/src/lib/json-rpc-router.ts +26 -11
- package/src/lib/sql-utils.ts +7 -0
- package/src/main.ts +0 -8
- package/src/message-processed-hook.ts +33 -0
- package/src/metrics.ts +57 -8
- package/src/plugins/event-log-nats.ts +466 -0
- package/src/process-handlers.ts +5 -5
- package/src/rate-limiter.ts +143 -0
- package/src/registration/jwt-provider-auth-plugin.ts +119 -0
- package/src/registration/open-auth-handler.ts +263 -0
- package/src/registration/proof-of-work-manager.ts +11 -10
- package/src/registration/provider-auth-plugin.ts +84 -0
- package/src/registration/registration-manager.ts +129 -31
- package/src/registration/registration-store.ts +332 -22
- package/src/storage.ts +136 -40
- package/src/web5-connect/sql-ttl-cache.ts +12 -5
- package/src/web5-connect/web5-connect-server.ts +9 -8
- package/src/ws-api.ts +39 -26
- package/dist/cjs/index.js +0 -6811
- package/dist/cjs/package.json +0 -1
- package/dist/esm/src/json-rpc-socket.d.ts +0 -39
- package/dist/esm/src/json-rpc-socket.d.ts.map +0 -1
- package/dist/esm/src/json-rpc-socket.js +0 -125
- package/dist/esm/src/json-rpc-socket.js.map +0 -1
- package/dist/esm/src/lib/http-server-shutdown-handler.d.ts +0 -10
- package/dist/esm/src/lib/http-server-shutdown-handler.d.ts.map +0 -1
- package/dist/esm/src/lib/http-server-shutdown-handler.js +0 -65
- package/dist/esm/src/lib/http-server-shutdown-handler.js.map +0 -1
- package/dist/esm/src/lib/json-rpc.d.ts +0 -54
- package/dist/esm/src/lib/json-rpc.d.ts.map +0 -1
- package/dist/esm/src/lib/json-rpc.js +0 -60
- package/dist/esm/src/lib/json-rpc.js.map +0 -1
- package/dist/esm/src/registration/proof-of-work-types.d.ts +0 -8
- package/dist/esm/src/registration/proof-of-work-types.d.ts.map +0 -1
- package/dist/esm/src/registration/proof-of-work-types.js +0 -2
- package/dist/esm/src/registration/proof-of-work-types.js.map +0 -1
- package/dist/esm/src/registration/registration-types.d.ts +0 -18
- package/dist/esm/src/registration/registration-types.d.ts.map +0 -1
- package/dist/esm/src/registration/registration-types.js +0 -2
- package/dist/esm/src/registration/registration-types.js.map +0 -1
- package/dist/esm/tests/common-scenario-validator.d.ts +0 -11
- package/dist/esm/tests/common-scenario-validator.d.ts.map +0 -1
- package/dist/esm/tests/common-scenario-validator.js +0 -114
- package/dist/esm/tests/common-scenario-validator.js.map +0 -1
- package/dist/esm/tests/connection/connection-manager.spec.d.ts +0 -2
- package/dist/esm/tests/connection/connection-manager.spec.d.ts.map +0 -1
- package/dist/esm/tests/connection/connection-manager.spec.js +0 -47
- package/dist/esm/tests/connection/connection-manager.spec.js.map +0 -1
- package/dist/esm/tests/connection/socket-connection.spec.d.ts +0 -2
- package/dist/esm/tests/connection/socket-connection.spec.d.ts.map +0 -1
- package/dist/esm/tests/connection/socket-connection.spec.js +0 -125
- package/dist/esm/tests/connection/socket-connection.spec.js.map +0 -1
- package/dist/esm/tests/cors/http-api.browser.d.ts +0 -2
- package/dist/esm/tests/cors/http-api.browser.d.ts.map +0 -1
- package/dist/esm/tests/cors/http-api.browser.js +0 -60
- package/dist/esm/tests/cors/http-api.browser.js.map +0 -1
- package/dist/esm/tests/cors/ping.browser.d.ts +0 -2
- package/dist/esm/tests/cors/ping.browser.d.ts.map +0 -1
- package/dist/esm/tests/cors/ping.browser.js +0 -7
- package/dist/esm/tests/cors/ping.browser.js.map +0 -1
- package/dist/esm/tests/dwn-process-message.spec.d.ts +0 -2
- package/dist/esm/tests/dwn-process-message.spec.d.ts.map +0 -1
- package/dist/esm/tests/dwn-process-message.spec.js +0 -172
- package/dist/esm/tests/dwn-process-message.spec.js.map +0 -1
- package/dist/esm/tests/dwn-server.spec.d.ts +0 -2
- package/dist/esm/tests/dwn-server.spec.d.ts.map +0 -1
- package/dist/esm/tests/dwn-server.spec.js +0 -49
- package/dist/esm/tests/dwn-server.spec.js.map +0 -1
- package/dist/esm/tests/http-api.spec.d.ts +0 -2
- package/dist/esm/tests/http-api.spec.d.ts.map +0 -1
- package/dist/esm/tests/http-api.spec.js +0 -775
- package/dist/esm/tests/http-api.spec.js.map +0 -1
- package/dist/esm/tests/json-rpc-socket.spec.d.ts +0 -2
- package/dist/esm/tests/json-rpc-socket.spec.d.ts.map +0 -1
- package/dist/esm/tests/json-rpc-socket.spec.js +0 -225
- package/dist/esm/tests/json-rpc-socket.spec.js.map +0 -1
- package/dist/esm/tests/plugins/data-store-sqlite.d.ts +0 -17
- package/dist/esm/tests/plugins/data-store-sqlite.d.ts.map +0 -1
- package/dist/esm/tests/plugins/data-store-sqlite.js +0 -23
- package/dist/esm/tests/plugins/data-store-sqlite.js.map +0 -1
- package/dist/esm/tests/plugins/event-log-sqlite.d.ts +0 -17
- package/dist/esm/tests/plugins/event-log-sqlite.d.ts.map +0 -1
- package/dist/esm/tests/plugins/event-log-sqlite.js +0 -23
- package/dist/esm/tests/plugins/event-log-sqlite.js.map +0 -1
- package/dist/esm/tests/plugins/event-stream-in-memory.d.ts +0 -17
- package/dist/esm/tests/plugins/event-stream-in-memory.d.ts.map +0 -1
- package/dist/esm/tests/plugins/event-stream-in-memory.js +0 -21
- package/dist/esm/tests/plugins/event-stream-in-memory.js.map +0 -1
- package/dist/esm/tests/plugins/message-store-sqlite.d.ts +0 -17
- package/dist/esm/tests/plugins/message-store-sqlite.d.ts.map +0 -1
- package/dist/esm/tests/plugins/message-store-sqlite.js +0 -23
- package/dist/esm/tests/plugins/message-store-sqlite.js.map +0 -1
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts +0 -17
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts.map +0 -1
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.js +0 -23
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.js.map +0 -1
- package/dist/esm/tests/process-handler.spec.d.ts +0 -2
- package/dist/esm/tests/process-handler.spec.d.ts.map +0 -1
- package/dist/esm/tests/process-handler.spec.js +0 -60
- package/dist/esm/tests/process-handler.spec.js.map +0 -1
- package/dist/esm/tests/registration/proof-of-work-manager.spec.d.ts +0 -2
- package/dist/esm/tests/registration/proof-of-work-manager.spec.d.ts.map +0 -1
- package/dist/esm/tests/registration/proof-of-work-manager.spec.js +0 -157
- package/dist/esm/tests/registration/proof-of-work-manager.spec.js.map +0 -1
- package/dist/esm/tests/rpc-subscribe-close.spec.d.ts +0 -2
- package/dist/esm/tests/rpc-subscribe-close.spec.d.ts.map +0 -1
- package/dist/esm/tests/rpc-subscribe-close.spec.js +0 -81
- package/dist/esm/tests/rpc-subscribe-close.spec.js.map +0 -1
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.d.ts +0 -2
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.d.ts.map +0 -1
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js +0 -73
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js.map +0 -1
- package/dist/esm/tests/scenarios/registration.spec.d.ts +0 -2
- package/dist/esm/tests/scenarios/registration.spec.d.ts.map +0 -1
- package/dist/esm/tests/scenarios/registration.spec.js +0 -507
- package/dist/esm/tests/scenarios/registration.spec.js.map +0 -1
- package/dist/esm/tests/scenarios/web5-connect.spec.d.ts +0 -2
- package/dist/esm/tests/scenarios/web5-connect.spec.d.ts.map +0 -1
- package/dist/esm/tests/scenarios/web5-connect.spec.js +0 -137
- package/dist/esm/tests/scenarios/web5-connect.spec.js.map +0 -1
- package/dist/esm/tests/test-dwn.d.ts +0 -7
- package/dist/esm/tests/test-dwn.d.ts.map +0 -1
- package/dist/esm/tests/test-dwn.js +0 -34
- package/dist/esm/tests/test-dwn.js.map +0 -1
- package/dist/esm/tests/utils.d.ts +0 -46
- package/dist/esm/tests/utils.d.ts.map +0 -1
- package/dist/esm/tests/utils.js +0 -116
- package/dist/esm/tests/utils.js.map +0 -1
- package/dist/esm/tests/ws-api.spec.d.ts +0 -2
- package/dist/esm/tests/ws-api.spec.d.ts.map +0 -1
- package/dist/esm/tests/ws-api.spec.js +0 -327
- package/dist/esm/tests/ws-api.spec.js.map +0 -1
- package/src/json-rpc-socket.ts +0 -155
- package/src/lib/http-server-shutdown-handler.ts +0 -79
- package/src/lib/json-rpc.ts +0 -126
- package/src/registration/proof-of-work-types.ts +0 -7
- package/src/registration/registration-types.ts +0 -18
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { SubscriptionMessage } from '@enbox/dwn-sdk-js';
|
|
2
|
+
import type { JsonRpcId, JsonRpcSuccessResponse } from '@enbox/dwn-clients';
|
|
3
|
+
|
|
4
|
+
import log from 'loglevel';
|
|
5
|
+
|
|
6
|
+
import { createJsonRpcSuccessResponse } from '@enbox/dwn-clients';
|
|
7
|
+
|
|
8
|
+
/** Default maximum number of unacknowledged events before pausing delivery. */
|
|
9
|
+
export const DEFAULT_MAX_IN_FLIGHT = 32;
|
|
10
|
+
|
|
11
|
+
/** Maximum buffer size before the subscription is force-closed to prevent OOM. */
|
|
12
|
+
export const MAX_BUFFER_SIZE = 1000;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Per-subscription flow controller that enforces a sliding window of
|
|
16
|
+
* unacknowledged events. When the window is full, incoming events are
|
|
17
|
+
* buffered. When the client sends `rpc.ack` with a cursor, events up
|
|
18
|
+
* to that cursor are acknowledged and buffered events are flushed.
|
|
19
|
+
*
|
|
20
|
+
* If the buffer exceeds {@link MAX_BUFFER_SIZE}, the subscription is
|
|
21
|
+
* closed via the provided `onOverflow` callback to prevent unbounded
|
|
22
|
+
* memory growth.
|
|
23
|
+
*/
|
|
24
|
+
export class FlowController {
|
|
25
|
+
/** Ordered list of cursors for events that have been sent but not yet acknowledged. */
|
|
26
|
+
private unacked: string[] = [];
|
|
27
|
+
|
|
28
|
+
/** Buffer of events waiting to be sent once the window opens. */
|
|
29
|
+
private buffer: SubscriptionMessage[] = [];
|
|
30
|
+
|
|
31
|
+
/** Whether the controller has been closed due to overflow. */
|
|
32
|
+
private closed = false;
|
|
33
|
+
|
|
34
|
+
constructor(
|
|
35
|
+
private readonly subscriptionId: JsonRpcId,
|
|
36
|
+
private readonly maxInFlight: number,
|
|
37
|
+
private readonly send: (response: JsonRpcSuccessResponse) => void,
|
|
38
|
+
private readonly onOverflow: () => void,
|
|
39
|
+
) {}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Accept an incoming {@link SubscriptionMessage} from the EventLog listener.
|
|
43
|
+
* If the window has room, send immediately. Otherwise buffer.
|
|
44
|
+
*/
|
|
45
|
+
public push(message: SubscriptionMessage): void {
|
|
46
|
+
if (this.closed) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (this.unacked.length < this.maxInFlight) {
|
|
51
|
+
this.sendMessage(message);
|
|
52
|
+
} else {
|
|
53
|
+
this.buffer.push(message);
|
|
54
|
+
|
|
55
|
+
if (this.buffer.length > MAX_BUFFER_SIZE) {
|
|
56
|
+
log.warn(
|
|
57
|
+
`FlowController: buffer overflow for subscription ${String(this.subscriptionId)}, ` +
|
|
58
|
+
`closing subscription (buffer=${this.buffer.length}, unacked=${this.unacked.length})`
|
|
59
|
+
);
|
|
60
|
+
this.closed = true;
|
|
61
|
+
this.buffer = [];
|
|
62
|
+
this.unacked = [];
|
|
63
|
+
this.onOverflow();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Process an `rpc.ack` for this subscription. Acknowledges all events up
|
|
70
|
+
* to and including the given cursor, then flushes buffered events into the
|
|
71
|
+
* newly opened window slots.
|
|
72
|
+
*/
|
|
73
|
+
public ack(cursor: string): void {
|
|
74
|
+
if (this.closed) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const idx = this.unacked.lastIndexOf(cursor);
|
|
79
|
+
if (idx === -1) {
|
|
80
|
+
// Unknown cursor — could be a stale or duplicate ack. Ignore silently.
|
|
81
|
+
log.debug(`FlowController: unknown cursor in ack for subscription ${String(this.subscriptionId)}: ${cursor}`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Remove all entries up to and including the acked cursor.
|
|
86
|
+
this.unacked.splice(0, idx + 1);
|
|
87
|
+
|
|
88
|
+
// Flush buffered messages into the freed window slots.
|
|
89
|
+
while (this.buffer.length > 0 && this.unacked.length < this.maxInFlight) {
|
|
90
|
+
const buffered = this.buffer.shift()!;
|
|
91
|
+
this.sendMessage(buffered);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Returns the number of events currently in flight (sent but unacknowledged).
|
|
97
|
+
*/
|
|
98
|
+
public get inFlightCount(): number {
|
|
99
|
+
return this.unacked.length;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Returns the number of events currently buffered (waiting to be sent).
|
|
104
|
+
*/
|
|
105
|
+
public get bufferCount(): number {
|
|
106
|
+
return this.buffer.length;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Sends a single message over the wire and tracks its cursor.
|
|
111
|
+
*/
|
|
112
|
+
private sendMessage(message: SubscriptionMessage): void {
|
|
113
|
+
const response = createJsonRpcSuccessResponse(this.subscriptionId, { subscription: message });
|
|
114
|
+
this.send(response);
|
|
115
|
+
this.unacked.push(message.cursor);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -1,54 +1,80 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import {
|
|
1
|
+
import type { ActivityLog } from '../admin/activity-log.js';
|
|
2
|
+
import type { AdminConnectionSnapshot } from '../admin/types.js';
|
|
3
|
+
import type { AdminStore } from '../admin/admin-store.js';
|
|
4
|
+
import type { DwnServerConfig } from '../config.js';
|
|
5
|
+
import type { MessageProcessedHook } from '../message-processed-hook.js';
|
|
6
|
+
import type { RateLimiter } from '../rate-limiter.js';
|
|
7
|
+
import type { RegistrationStore } from '../registration/registration-store.js';
|
|
8
|
+
import type { RequestContext } from '../lib/json-rpc-router.js';
|
|
9
|
+
import type { ServerWebSocket } from 'bun';
|
|
10
|
+
import type { WsData } from '../http-api.js';
|
|
11
|
+
import type { Dwn, GenericMessage, SubscriptionMessage } from '@enbox/dwn-sdk-js';
|
|
12
|
+
import type { JsonRpcErrorResponse, JsonRpcId, JsonRpcRequest, JsonRpcResponse, JsonRpcSubscription } from '@enbox/dwn-clients';
|
|
3
13
|
|
|
4
|
-
import type { WebSocket } from "ws";
|
|
5
14
|
import log from 'loglevel';
|
|
6
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
7
|
-
|
|
8
|
-
import type { RequestContext } from "../lib/json-rpc-router.js";
|
|
9
|
-
import type { JsonRpcErrorResponse, JsonRpcId, JsonRpcRequest, JsonRpcResponse, JsonRpcSubscription } from "../lib/json-rpc.js";
|
|
10
15
|
|
|
11
|
-
import {
|
|
12
|
-
import { jsonRpcRouter } from
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
16
|
+
import { DwnMethodName } from '@enbox/dwn-sdk-js';
|
|
17
|
+
import { jsonRpcRouter } from '../json-rpc-api.js';
|
|
18
|
+
import { requestCounter } from '../metrics.js';
|
|
19
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
20
|
+
import { createJsonRpcErrorResponse, JsonRpcErrorCodes } from '@enbox/dwn-clients';
|
|
21
|
+
import { DEFAULT_MAX_IN_FLIGHT, FlowController } from './flow-controller.js';
|
|
22
|
+
import { DwnServerError, DwnServerErrorCode } from '../dwn-error.js';
|
|
15
23
|
|
|
16
24
|
const HEARTBEAT_INTERVAL = 30_000;
|
|
17
25
|
|
|
18
26
|
/**
|
|
19
27
|
* SocketConnection handles a WebSocket connection to a DWN using JSON RPC.
|
|
20
28
|
* It also manages references to the long running RPC subscriptions for the connection.
|
|
29
|
+
*
|
|
30
|
+
* With Bun's native WebSocket, the message/close/error events are dispatched by the
|
|
31
|
+
* Bun.serve() websocket handlers in http-api.ts, which delegate to the public `message()`
|
|
32
|
+
* and `close()` methods on this class.
|
|
21
33
|
*/
|
|
22
34
|
export class SocketConnection {
|
|
23
|
-
|
|
35
|
+
/** Unique identifier for this connection (for admin introspection). */
|
|
36
|
+
public readonly id: string = uuidv4();
|
|
37
|
+
|
|
38
|
+
/** Timestamp when the connection was established (for admin introspection). */
|
|
39
|
+
public readonly connectedAt: number = Date.now();
|
|
40
|
+
|
|
41
|
+
private heartbeatInterval: ReturnType<typeof setInterval>;
|
|
24
42
|
private subscriptions: Map<JsonRpcId, JsonRpcSubscription> = new Map();
|
|
43
|
+
private flowControllers: Map<JsonRpcId, FlowController> = new Map();
|
|
25
44
|
private isAlive: boolean;
|
|
26
45
|
|
|
27
46
|
constructor(
|
|
28
|
-
private socket:
|
|
47
|
+
private socket: ServerWebSocket<WsData>,
|
|
29
48
|
private dwn: Dwn,
|
|
30
|
-
private
|
|
49
|
+
private onCloseCallback?: () => void,
|
|
50
|
+
private maxInFlight: number = DEFAULT_MAX_IN_FLIGHT,
|
|
51
|
+
private activityLog?: ActivityLog,
|
|
52
|
+
private adminStore?: AdminStore,
|
|
53
|
+
private registrationStore?: RegistrationStore,
|
|
54
|
+
private serverConfig?: DwnServerConfig,
|
|
55
|
+
private tenantRateLimiter?: RateLimiter,
|
|
56
|
+
private messageProcessedHooks?: MessageProcessedHook[],
|
|
31
57
|
){
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
socket.on('error', this.error.bind(this));
|
|
35
|
-
socket.on('pong', this.pong.bind(this));
|
|
36
|
-
|
|
37
|
-
// Sometimes connections between client <-> server can get borked in such a way that
|
|
38
|
-
// leaves both unaware of the borkage. ping messages can be used as a means to verify
|
|
39
|
-
// that the remote endpoint is still responsive. Server will ping each socket every 30s
|
|
40
|
-
// if a pong hasn't received from a socket by the next ping, the server will terminate
|
|
41
|
-
// the socket connection
|
|
58
|
+
// Bun handles ping/pong automatically at the protocol level, but we still
|
|
59
|
+
// want an application-level heartbeat to detect dead connections.
|
|
42
60
|
this.isAlive = true;
|
|
43
61
|
this.heartbeatInterval = setInterval(() => {
|
|
44
62
|
if (this.isAlive === false) {
|
|
45
63
|
this.close();
|
|
64
|
+
return;
|
|
46
65
|
}
|
|
47
66
|
this.isAlive = false;
|
|
48
67
|
this.socket.ping();
|
|
49
68
|
}, HEARTBEAT_INTERVAL);
|
|
50
69
|
}
|
|
51
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Called when a pong is received (triggered by Bun's built-in ping/pong handling).
|
|
73
|
+
*/
|
|
74
|
+
pong(): void {
|
|
75
|
+
this.isAlive = true;
|
|
76
|
+
}
|
|
77
|
+
|
|
52
78
|
/**
|
|
53
79
|
* Checks to see if the incoming `JsonRpcId` is already in use for a subscription.
|
|
54
80
|
*/
|
|
@@ -65,7 +91,7 @@ export class SocketConnection {
|
|
|
65
91
|
throw new DwnServerError(
|
|
66
92
|
DwnServerErrorCode.ConnectionSubscriptionJsonRpcIdExists,
|
|
67
93
|
`the subscription with id ${subscription.id} already exists`
|
|
68
|
-
)
|
|
94
|
+
);
|
|
69
95
|
}
|
|
70
96
|
|
|
71
97
|
this.subscriptions.set(subscription.id, subscription);
|
|
@@ -81,12 +107,24 @@ export class SocketConnection {
|
|
|
81
107
|
throw new DwnServerError(
|
|
82
108
|
DwnServerErrorCode.ConnectionSubscriptionJsonRpcIdNotFound,
|
|
83
109
|
`the subscription with id ${id} was not found`
|
|
84
|
-
)
|
|
110
|
+
);
|
|
85
111
|
}
|
|
86
112
|
|
|
87
113
|
const connection = this.subscriptions.get(id);
|
|
88
114
|
await connection.close();
|
|
89
115
|
this.subscriptions.delete(id);
|
|
116
|
+
this.flowControllers.delete(id);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Acknowledges subscription events up to the given cursor, advancing the
|
|
121
|
+
* flow-control window for the subscription.
|
|
122
|
+
*/
|
|
123
|
+
ackSubscription(id: JsonRpcId, cursor: string): void {
|
|
124
|
+
const fc = this.flowControllers.get(id);
|
|
125
|
+
if (fc) {
|
|
126
|
+
fc.ack(cursor);
|
|
127
|
+
}
|
|
90
128
|
}
|
|
91
129
|
|
|
92
130
|
/**
|
|
@@ -95,10 +133,7 @@ export class SocketConnection {
|
|
|
95
133
|
async close(): Promise<void> {
|
|
96
134
|
clearInterval(this.heartbeatInterval);
|
|
97
135
|
|
|
98
|
-
|
|
99
|
-
this.socket.removeAllListeners();
|
|
100
|
-
|
|
101
|
-
const closePromises = [];
|
|
136
|
+
const closePromises: Promise<void>[] = [];
|
|
102
137
|
for (const [id, subscription] of this.subscriptions) {
|
|
103
138
|
closePromises.push(subscription.close());
|
|
104
139
|
this.subscriptions.delete(id);
|
|
@@ -107,56 +142,52 @@ export class SocketConnection {
|
|
|
107
142
|
// close all of the associated subscriptions
|
|
108
143
|
await Promise.all(closePromises);
|
|
109
144
|
|
|
145
|
+
// clear all flow controllers
|
|
146
|
+
this.flowControllers.clear();
|
|
147
|
+
|
|
110
148
|
// close the socket.
|
|
111
149
|
this.socket.close();
|
|
112
150
|
|
|
113
151
|
// if there was a close handler passed call it after the connection has been closed
|
|
114
|
-
if (this.
|
|
115
|
-
this.
|
|
152
|
+
if (this.onCloseCallback !== undefined) {
|
|
153
|
+
this.onCloseCallback();
|
|
116
154
|
}
|
|
117
155
|
}
|
|
118
156
|
|
|
119
|
-
/**
|
|
120
|
-
* Pong messages are automatically sent in response to ping messages as required by
|
|
121
|
-
* the websocket spec. So, no need to send explicit pongs.
|
|
122
|
-
*/
|
|
123
|
-
private pong(): void {
|
|
124
|
-
this.isAlive = true;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
157
|
/**
|
|
128
158
|
* Log the error and close the connection.
|
|
129
159
|
*/
|
|
130
|
-
|
|
160
|
+
async error(error: Error): Promise<void> {
|
|
131
161
|
log.error(`SocketConnection error, terminating connection`, error);
|
|
132
|
-
this.socket.
|
|
162
|
+
this.socket.close();
|
|
133
163
|
await this.close();
|
|
134
164
|
}
|
|
135
165
|
|
|
136
166
|
/**
|
|
137
167
|
* Handles a `JSON RPC 2.0` encoded message.
|
|
168
|
+
* This is called by Bun's websocket message handler via http-api.ts.
|
|
138
169
|
*/
|
|
139
|
-
|
|
170
|
+
async message(dataBuffer: Buffer): Promise<void> {
|
|
140
171
|
const requestData = dataBuffer.toString();
|
|
141
172
|
if (!requestData) {
|
|
142
173
|
return this.send(createJsonRpcErrorResponse(
|
|
143
174
|
uuidv4(),
|
|
144
175
|
JsonRpcErrorCodes.BadRequest,
|
|
145
176
|
'request payload required.'
|
|
146
|
-
))
|
|
177
|
+
));
|
|
147
178
|
}
|
|
148
179
|
|
|
149
180
|
let jsonRequest: JsonRpcRequest;
|
|
150
181
|
try {
|
|
151
182
|
jsonRequest = JSON.parse(requestData);
|
|
152
|
-
} catch(error) {
|
|
183
|
+
} catch (error) {
|
|
153
184
|
const errorResponse = createJsonRpcErrorResponse(
|
|
154
185
|
uuidv4(),
|
|
155
186
|
JsonRpcErrorCodes.BadRequest,
|
|
156
187
|
(error as Error).message
|
|
157
188
|
);
|
|
158
189
|
return this.send(errorResponse);
|
|
159
|
-
}
|
|
190
|
+
}
|
|
160
191
|
|
|
161
192
|
const requestContext = await this.buildRequestContext(jsonRequest);
|
|
162
193
|
const { jsonRpcResponse } = await jsonRpcRouter.handle(jsonRequest, requestContext);
|
|
@@ -164,55 +195,101 @@ export class SocketConnection {
|
|
|
164
195
|
requestCounter.inc({ method: jsonRequest.method, error: 1 });
|
|
165
196
|
} else {
|
|
166
197
|
requestCounter.inc({
|
|
167
|
-
method: jsonRequest.method,
|
|
168
|
-
status: jsonRpcResponse?.result?.reply?.status?.code || 0,
|
|
198
|
+
method : jsonRequest.method,
|
|
199
|
+
status : jsonRpcResponse?.result?.reply?.status?.code || 0,
|
|
169
200
|
});
|
|
170
201
|
}
|
|
171
202
|
this.send(jsonRpcResponse);
|
|
172
203
|
}
|
|
173
204
|
|
|
174
205
|
/**
|
|
175
|
-
*
|
|
206
|
+
* Returns the number of active subscriptions on this connection.
|
|
207
|
+
*/
|
|
208
|
+
get subscriptionCount(): number {
|
|
209
|
+
return this.subscriptions.size;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Returns a serializable snapshot of this connection for the admin inspector.
|
|
214
|
+
*/
|
|
215
|
+
toSnapshot(): AdminConnectionSnapshot {
|
|
216
|
+
const subscriptions = Array.from(this.flowControllers.entries()).map(
|
|
217
|
+
([id, fc]): AdminConnectionSnapshot['subscriptions'][number] => ({
|
|
218
|
+
id : id as string | number,
|
|
219
|
+
inflight : fc.inFlightCount,
|
|
220
|
+
buffered : fc.bufferCount,
|
|
221
|
+
}),
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
id : this.id,
|
|
226
|
+
connectedAt : new Date(this.connectedAt).toISOString(),
|
|
227
|
+
subscriptionCount : this.subscriptions.size,
|
|
228
|
+
subscriptions,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Sends a JSON encoded string through the WebSocket.
|
|
176
234
|
*/
|
|
177
235
|
private send(response: JsonRpcResponse | JsonRpcErrorResponse): void {
|
|
178
236
|
this.socket.send(JSON.stringify(response));
|
|
179
237
|
}
|
|
180
238
|
|
|
181
239
|
/**
|
|
182
|
-
* Creates a subscription handler
|
|
183
|
-
*
|
|
184
|
-
*
|
|
240
|
+
* Creates a flow-controlled subscription handler that enforces the
|
|
241
|
+
* `maxInFlight` window. Returns a `SubscriptionListener` to be passed
|
|
242
|
+
* to the EventLog, and stores the `FlowController` for later `rpc.ack`
|
|
243
|
+
* processing.
|
|
185
244
|
*/
|
|
186
|
-
private createSubscriptionHandler(id: JsonRpcId): (message:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
this.
|
|
190
|
-
|
|
245
|
+
private createSubscriptionHandler(id: JsonRpcId): (message: SubscriptionMessage) => void {
|
|
246
|
+
const fc = new FlowController(
|
|
247
|
+
id,
|
|
248
|
+
this.maxInFlight,
|
|
249
|
+
(response) => {
|
|
250
|
+
this.send(response);
|
|
251
|
+
},
|
|
252
|
+
() => {
|
|
253
|
+
// overflow: close the subscription to prevent OOM
|
|
254
|
+
this.closeSubscription(id).catch((err) => {
|
|
255
|
+
log.error(`FlowController: error closing subscription ${String(id)} on overflow`, err);
|
|
256
|
+
});
|
|
257
|
+
},
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
this.flowControllers.set(id, fc);
|
|
261
|
+
|
|
262
|
+
return (message) => {
|
|
263
|
+
fc.push(message);
|
|
264
|
+
};
|
|
191
265
|
}
|
|
192
266
|
|
|
193
267
|
/**
|
|
194
268
|
* Builds a `RequestContext` object to use with the `JSON RPC API`.
|
|
195
|
-
*
|
|
196
|
-
* Adds a `subscriptionHandler` for `Subscribe` messages.
|
|
197
269
|
*/
|
|
198
270
|
private async buildRequestContext(request: JsonRpcRequest): Promise<RequestContext> {
|
|
199
271
|
const { params, method, subscription } = request;
|
|
200
272
|
|
|
201
273
|
const requestContext: RequestContext = {
|
|
202
|
-
transport
|
|
203
|
-
dwn
|
|
204
|
-
socketConnection
|
|
205
|
-
|
|
274
|
+
transport : 'ws',
|
|
275
|
+
dwn : this.dwn,
|
|
276
|
+
socketConnection : this,
|
|
277
|
+
activityLog : this.activityLog,
|
|
278
|
+
adminStore : this.adminStore,
|
|
279
|
+
registrationStore : this.registrationStore,
|
|
280
|
+
config : this.serverConfig,
|
|
281
|
+
tenantRateLimiter : this.tenantRateLimiter,
|
|
282
|
+
messageProcessedHooks : this.messageProcessedHooks,
|
|
283
|
+
};
|
|
206
284
|
|
|
207
285
|
// methods that expect a long-running subscription begin with `rpc.subscribe.`
|
|
208
286
|
if (method.startsWith('rpc.subscribe.') && subscription) {
|
|
209
287
|
const { message } = params as { message?: GenericMessage };
|
|
210
288
|
if (message?.descriptor.method === DwnMethodName.Subscribe) {
|
|
211
|
-
const handlerFunc = this.createSubscriptionHandler(subscription.id);
|
|
212
289
|
requestContext.subscriptionRequest = {
|
|
213
|
-
id: subscription.id,
|
|
214
|
-
subscriptionHandler
|
|
215
|
-
}
|
|
290
|
+
id : subscription.id,
|
|
291
|
+
subscriptionHandler : this.createSubscriptionHandler(subscription.id),
|
|
292
|
+
};
|
|
216
293
|
}
|
|
217
294
|
}
|
|
218
295
|
|