@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,245 @@
|
|
|
1
|
+
import type { Dialect } from '@enbox/dwn-sql-store';
|
|
2
|
+
import type { AdminWebhook, AdminWebhookInput } from './types.js';
|
|
3
|
+
|
|
4
|
+
import { createHmac } from 'crypto';
|
|
5
|
+
import { Kysely } from 'kysely';
|
|
6
|
+
import log from 'loglevel';
|
|
7
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Payload delivered to webhook endpoints.
|
|
11
|
+
*/
|
|
12
|
+
export type WebhookPayload = {
|
|
13
|
+
/** Unique delivery ID. */
|
|
14
|
+
id : string;
|
|
15
|
+
/** ISO-8601 timestamp. */
|
|
16
|
+
timestamp : string;
|
|
17
|
+
/** Event type (e.g. `tenant.suspend`, `quota.warning`). */
|
|
18
|
+
event : string;
|
|
19
|
+
/** Optional target (e.g. tenant DID). */
|
|
20
|
+
target? : string;
|
|
21
|
+
/** Additional event data. */
|
|
22
|
+
data? : Record<string, unknown>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Manages webhook registrations and event delivery.
|
|
27
|
+
*
|
|
28
|
+
* Webhooks are stored in a SQL table and fire asynchronously when matching
|
|
29
|
+
* events occur. Delivery includes retry logic and optional HMAC-SHA256 signatures.
|
|
30
|
+
*
|
|
31
|
+
* @see https://github.com/enboxorg/enbox/issues/395
|
|
32
|
+
*/
|
|
33
|
+
export class WebhookManager {
|
|
34
|
+
static readonly #tableName = 'adminWebhooks';
|
|
35
|
+
static readonly #maxRetries = 3;
|
|
36
|
+
static readonly #retryDelaysMs = [1000, 5000, 15000];
|
|
37
|
+
|
|
38
|
+
#db: Kysely<WebhookDatabase>;
|
|
39
|
+
|
|
40
|
+
private constructor(dialect: Dialect) {
|
|
41
|
+
this.#db = new Kysely<WebhookDatabase>({ dialect });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Creates and initializes a `WebhookManager` instance.
|
|
46
|
+
*/
|
|
47
|
+
public static async create(dialect: Dialect): Promise<WebhookManager> {
|
|
48
|
+
const manager = new WebhookManager(dialect);
|
|
49
|
+
await manager.#initialize();
|
|
50
|
+
return manager;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async #initialize(): Promise<void> {
|
|
54
|
+
await this.#db.schema
|
|
55
|
+
.createTable(WebhookManager.#tableName)
|
|
56
|
+
.ifNotExists()
|
|
57
|
+
.addColumn('id', 'text', (col) => col.primaryKey())
|
|
58
|
+
.addColumn('url', 'text', (col) => col.notNull())
|
|
59
|
+
.addColumn('events', 'text', (col) => col.notNull()) // JSON array
|
|
60
|
+
.addColumn('secret', 'text')
|
|
61
|
+
.addColumn('createdAt', 'text', (col) => col.notNull())
|
|
62
|
+
.execute();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// CRUD
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Registers a new webhook. Returns the created webhook (without secret echoed).
|
|
71
|
+
*/
|
|
72
|
+
public async register(input: AdminWebhookInput): Promise<AdminWebhook> {
|
|
73
|
+
const webhook: AdminWebhook = {
|
|
74
|
+
id : uuidv4(),
|
|
75
|
+
url : input.url,
|
|
76
|
+
events : input.events,
|
|
77
|
+
createdAt : new Date().toISOString(),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
await this.#db
|
|
81
|
+
.insertInto(WebhookManager.#tableName)
|
|
82
|
+
.values({
|
|
83
|
+
id : webhook.id,
|
|
84
|
+
url : webhook.url,
|
|
85
|
+
events : JSON.stringify(webhook.events),
|
|
86
|
+
secret : input.secret ?? null,
|
|
87
|
+
createdAt : webhook.createdAt,
|
|
88
|
+
})
|
|
89
|
+
.execute();
|
|
90
|
+
|
|
91
|
+
return webhook;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Lists all registered webhooks. Secrets are redacted.
|
|
96
|
+
*/
|
|
97
|
+
public async list(): Promise<AdminWebhook[]> {
|
|
98
|
+
const rows = await this.#db
|
|
99
|
+
.selectFrom(WebhookManager.#tableName)
|
|
100
|
+
.select(['id', 'url', 'events', 'secret', 'createdAt'])
|
|
101
|
+
.orderBy('createdAt', 'asc')
|
|
102
|
+
.execute();
|
|
103
|
+
|
|
104
|
+
return rows.map((row): AdminWebhook => ({
|
|
105
|
+
id : row.id,
|
|
106
|
+
url : row.url,
|
|
107
|
+
events : JSON.parse(row.events),
|
|
108
|
+
secret : row.secret ? '***' : undefined,
|
|
109
|
+
createdAt : row.createdAt,
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Deletes a webhook by ID. Returns `true` if found and deleted.
|
|
115
|
+
*/
|
|
116
|
+
public async delete(id: string): Promise<boolean> {
|
|
117
|
+
const result = await this.#db
|
|
118
|
+
.deleteFrom(WebhookManager.#tableName)
|
|
119
|
+
.where('id', '=', id)
|
|
120
|
+
.executeTakeFirstOrThrow();
|
|
121
|
+
|
|
122
|
+
return Number(result.numDeletedRows) > 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// Event dispatch
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Fires matching webhooks for a given event. Non-blocking — delivery happens
|
|
131
|
+
* asynchronously and errors are logged but never propagated.
|
|
132
|
+
*/
|
|
133
|
+
public fire(event: string, target?: string, data?: Record<string, unknown>): void {
|
|
134
|
+
// Fire-and-forget.
|
|
135
|
+
this.#dispatchAll(event, target, data).catch((err): void => {
|
|
136
|
+
log.error('Webhook dispatch error:', err);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async #dispatchAll(event: string, target?: string, data?: Record<string, unknown>): Promise<void> {
|
|
141
|
+
const rows = await this.#db
|
|
142
|
+
.selectFrom(WebhookManager.#tableName)
|
|
143
|
+
.select(['id', 'url', 'events', 'secret'])
|
|
144
|
+
.execute();
|
|
145
|
+
|
|
146
|
+
const payload: WebhookPayload = {
|
|
147
|
+
id : uuidv4(),
|
|
148
|
+
timestamp : new Date().toISOString(),
|
|
149
|
+
event,
|
|
150
|
+
target,
|
|
151
|
+
data,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const body = JSON.stringify(payload);
|
|
155
|
+
|
|
156
|
+
for (const row of rows) {
|
|
157
|
+
const patterns: string[] = JSON.parse(row.events);
|
|
158
|
+
if (this.#matchesEvent(event, patterns)) {
|
|
159
|
+
this.#deliver(row.url, body, row.secret).catch((err): void => {
|
|
160
|
+
log.warn(`Webhook delivery failed for ${row.url}:`, err);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Checks if an event matches any of the subscription patterns.
|
|
168
|
+
* Supports wildcard patterns like `tenant.*`.
|
|
169
|
+
*/
|
|
170
|
+
#matchesEvent(event: string, patterns: string[]): boolean {
|
|
171
|
+
for (const pattern of patterns) {
|
|
172
|
+
if (pattern === '*') {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
if (pattern === event) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
if (pattern.endsWith('.*')) {
|
|
179
|
+
const prefix = pattern.slice(0, -2);
|
|
180
|
+
if (event.startsWith(prefix + '.') || event === prefix) {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Delivers a webhook payload with retry logic and optional HMAC signature.
|
|
190
|
+
*/
|
|
191
|
+
async #deliver(url: string, body: string, secret: string | null): Promise<void> {
|
|
192
|
+
const headers: Record<string, string> = {
|
|
193
|
+
'content-type': 'application/json',
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
if (secret) {
|
|
197
|
+
const signature = createHmac('sha256', secret).update(body).digest('hex');
|
|
198
|
+
headers['x-webhook-signature'] = `sha256=${signature}`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
for (let attempt = 0; attempt <= WebhookManager.#maxRetries; attempt++) {
|
|
202
|
+
try {
|
|
203
|
+
const response = await fetch(url, { method: 'POST', headers, body, signal: AbortSignal.timeout(10_000) });
|
|
204
|
+
if (response.ok) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
log.warn(`Webhook ${url} returned ${response.status} (attempt ${attempt + 1})`);
|
|
208
|
+
} catch (err) {
|
|
209
|
+
log.warn(`Webhook ${url} fetch error (attempt ${attempt + 1}):`, err);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Wait before retry (skip wait on last attempt).
|
|
213
|
+
if (attempt < WebhookManager.#maxRetries) {
|
|
214
|
+
await new Promise((resolve): ReturnType<typeof setTimeout> =>
|
|
215
|
+
setTimeout(resolve, WebhookManager.#retryDelaysMs[attempt]),
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
log.error(`Webhook delivery to ${url} failed after ${WebhookManager.#maxRetries + 1} attempts`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Closes the underlying database connection.
|
|
225
|
+
*/
|
|
226
|
+
public async close(): Promise<void> {
|
|
227
|
+
await this.#db.destroy();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
// Kysely type definitions
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
|
|
235
|
+
interface WebhookRow {
|
|
236
|
+
id : string;
|
|
237
|
+
url : string;
|
|
238
|
+
events : string; // JSON-serialized string[]
|
|
239
|
+
secret : string | null;
|
|
240
|
+
createdAt : string;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
interface WebhookDatabase {
|
|
244
|
+
adminWebhooks : WebhookRow;
|
|
245
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
|
|
1
3
|
import bytes from 'bytes';
|
|
2
4
|
|
|
3
5
|
export type DwnServerConfig = typeof config;
|
|
@@ -5,11 +7,8 @@ export type DwnServerConfig = typeof config;
|
|
|
5
7
|
export const config = {
|
|
6
8
|
/**
|
|
7
9
|
* Used to populate the `server` property returned by the `/info` endpoint.
|
|
8
|
-
*
|
|
9
|
-
* If running using `npm` the `process.env.npm_package_name` variable exists and we use that,
|
|
10
|
-
* otherwise we fall back on the use defined `DWN_SERVER_PACKAGE_NAME` or `@enbox/dwn-server`.
|
|
11
10
|
*/
|
|
12
|
-
serverName: process.env.
|
|
11
|
+
serverName: process.env.DWN_SERVER_PACKAGE_NAME || '@enbox/dwn-server',
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* The base external URL of this DWN.
|
|
@@ -26,7 +25,7 @@ export const config = {
|
|
|
26
25
|
/**
|
|
27
26
|
* The URL of the TTL cache used by the DWN.
|
|
28
27
|
* NOTE: Used for session/state keeping, thus requires the cache to be commonly addressable by nodes in a cloud cluster environment.
|
|
29
|
-
*
|
|
28
|
+
*
|
|
30
29
|
* Currently only supports SQL databases, e.g.
|
|
31
30
|
* Postgres: 'postgres://root:dwn@localhost:5432/dwn'
|
|
32
31
|
* MySQL: 'mysql://root:dwn@localhost:3306/dwn'
|
|
@@ -37,35 +36,204 @@ export const config = {
|
|
|
37
36
|
* Used to populate the `version` and `sdkVersion` properties returned by the `/info` endpoint.
|
|
38
37
|
*
|
|
39
38
|
* The `version` and `sdkVersion` are pulled from `package.json` at runtime.
|
|
40
|
-
* If
|
|
41
|
-
* Otherwise we
|
|
42
|
-
|
|
39
|
+
* If `DWN_SERVER_PACKAGE_JSON` is set, we use that path.
|
|
40
|
+
* Otherwise we resort to the path within the docker server image, located at `/dwn-server/package.json`.
|
|
41
|
+
*/
|
|
42
|
+
packageJsonPath : process.env.DWN_SERVER_PACKAGE_JSON || '/dwn-server/package.json',
|
|
43
|
+
/**
|
|
44
|
+
* Maximum size of data that can be provided with a RecordsWrite.
|
|
45
|
+
* Request bodies up to this size are buffered fully into memory, so the
|
|
46
|
+
* default should be conservative enough to prevent memory exhaustion from
|
|
47
|
+
* concurrent large uploads. Operators can raise the limit via the
|
|
48
|
+
* `MAX_RECORD_DATA_SIZE` env var (e.g. `'1gb'`).
|
|
43
49
|
*/
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
maxRecordDataSize : bytes(process.env.MAX_RECORD_DATA_SIZE || '100mb'),
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Maximum number of unacknowledged subscription events the server will send
|
|
54
|
+
* per subscription before pausing delivery. Clients must send `rpc.ack` to
|
|
55
|
+
* advance the window. Configurable via `DWN_MAX_IN_FLIGHT` env var.
|
|
56
|
+
*/
|
|
57
|
+
maxInFlight: parseInt(process.env.DWN_MAX_IN_FLIGHT || '32'),
|
|
47
58
|
|
|
48
59
|
// whether to enable 'ws:'
|
|
49
60
|
webSocketSupport: { on: true, off: false }[process.env.DS_WEBSOCKET_SERVER] ?? true,
|
|
50
61
|
|
|
51
62
|
/**
|
|
52
|
-
* Path to DWN
|
|
63
|
+
* Path to DWN EventLog plugin to use. Default in-memory implementation will be used if left empty.
|
|
64
|
+
* Also accepts the legacy `DWN_EVENT_STREAM_PLUGIN_PATH` env var for backward compatibility.
|
|
53
65
|
*/
|
|
54
|
-
|
|
66
|
+
eventLogPluginPath: process.env.DWN_EVENT_LOG_PLUGIN_PATH || process.env.DWN_EVENT_STREAM_PLUGIN_PATH,
|
|
55
67
|
|
|
56
68
|
// where to store persistent data
|
|
57
|
-
messageStore: process.env.DWN_STORAGE_MESSAGES || process.env.DWN_STORAGE || 'level://data',
|
|
58
|
-
dataStore: process.env.DWN_STORAGE_DATA || process.env.DWN_STORAGE || 'level://data',
|
|
59
|
-
|
|
60
|
-
resumableTaskStore: process.env.DWN_STORAGE_RESUMABLE_TASKS || process.env.DWN_STORAGE || 'level://data',
|
|
69
|
+
messageStore : process.env.DWN_STORAGE_MESSAGES || process.env.DWN_STORAGE || 'level://data',
|
|
70
|
+
dataStore : process.env.DWN_STORAGE_DATA || process.env.DWN_STORAGE || 'level://data',
|
|
71
|
+
stateIndex : process.env.DWN_STORAGE_STATE_INDEX || process.env.DWN_STORAGE || 'level://data',
|
|
72
|
+
resumableTaskStore : process.env.DWN_STORAGE_RESUMABLE_TASKS || process.env.DWN_STORAGE || 'level://data',
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* PostgreSQL connection pool tuning. When multiple DWN stores share the same
|
|
76
|
+
* Postgres connection URL, a single shared pool is used instead of one pool
|
|
77
|
+
* per store (which would be 4 pools x 10 default connections = 40 connections).
|
|
78
|
+
*/
|
|
79
|
+
pgPoolMin : parseInt(process.env.DWN_PG_POOL_MIN || '5'),
|
|
80
|
+
pgPoolMax : parseInt(process.env.DWN_PG_POOL_MAX || '30'),
|
|
81
|
+
pgPoolIdleTimeout : parseInt(process.env.DWN_PG_POOL_IDLE_TIMEOUT || '30000'),
|
|
61
82
|
|
|
62
83
|
// tenant registration feature configuration
|
|
63
|
-
registrationStoreUrl: process.env.DWN_REGISTRATION_STORE_URL || process.env.DWN_STORAGE,
|
|
64
|
-
registrationProofOfWorkSeed: process.env.DWN_REGISTRATION_PROOF_OF_WORK_SEED,
|
|
65
|
-
registrationProofOfWorkEnabled: process.env.DWN_REGISTRATION_PROOF_OF_WORK_ENABLED === 'true',
|
|
66
|
-
registrationProofOfWorkInitialMaxHash: process.env.DWN_REGISTRATION_PROOF_OF_WORK_INITIAL_MAX_HASH,
|
|
67
|
-
termsOfServiceFilePath: process.env.DWN_TERMS_OF_SERVICE_FILE_PATH,
|
|
84
|
+
registrationStoreUrl : process.env.DWN_REGISTRATION_STORE_URL || process.env.DWN_STORAGE,
|
|
85
|
+
registrationProofOfWorkSeed : process.env.DWN_REGISTRATION_PROOF_OF_WORK_SEED,
|
|
86
|
+
registrationProofOfWorkEnabled : process.env.DWN_REGISTRATION_PROOF_OF_WORK_ENABLED === 'true',
|
|
87
|
+
registrationProofOfWorkInitialMaxHash : process.env.DWN_REGISTRATION_PROOF_OF_WORK_INITIAL_MAX_HASH,
|
|
88
|
+
termsOfServiceFilePath : process.env.DWN_TERMS_OF_SERVICE_FILE_PATH,
|
|
89
|
+
|
|
90
|
+
// Provider auth configuration for paid DWN registration
|
|
91
|
+
providerAuthEnabled : process.env.DWN_PROVIDER_AUTH_ENABLED === 'true',
|
|
92
|
+
providerAuthAuthorizeUrl : process.env.DWN_PROVIDER_AUTH_AUTHORIZE_URL,
|
|
93
|
+
providerAuthTokenUrl : process.env.DWN_PROVIDER_AUTH_TOKEN_URL,
|
|
94
|
+
providerAuthRefreshUrl : process.env.DWN_PROVIDER_AUTH_REFRESH_URL,
|
|
95
|
+
providerAuthManagementUrl : process.env.DWN_PROVIDER_AUTH_MANAGEMENT_URL,
|
|
96
|
+
providerAuthPluginPath : process.env.DWN_PROVIDER_AUTH_PLUGIN_PATH,
|
|
97
|
+
providerAuthJwtSecret : process.env.DWN_PROVIDER_AUTH_JWT_SECRET,
|
|
98
|
+
providerAuthJwtJwksUrl : process.env.DWN_PROVIDER_AUTH_JWT_JWKS_URL,
|
|
68
99
|
|
|
69
100
|
// log level - trace/debug/info/warn/error
|
|
70
101
|
logLevel: process.env.DWN_SERVER_LOG_LEVEL || 'INFO',
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Bearer token for the admin API. If unset (or empty), the admin API is disabled entirely.
|
|
105
|
+
* Can also be read from a file path via `DWN_ADMIN_TOKEN_FILE` (useful for Docker secrets).
|
|
106
|
+
*/
|
|
107
|
+
adminToken: process.env.DWN_ADMIN_TOKEN || (
|
|
108
|
+
process.env.DWN_ADMIN_TOKEN_FILE
|
|
109
|
+
? readAdminTokenFromFile(process.env.DWN_ADMIN_TOKEN_FILE)
|
|
110
|
+
: undefined
|
|
111
|
+
),
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Maximum number of recent DWN activity events retained in the in-memory
|
|
115
|
+
* ring buffer for the admin `/events` endpoint. Defaults to 10,000.
|
|
116
|
+
*/
|
|
117
|
+
adminActivityLogCapacity: parseInt(process.env.DWN_ADMIN_ACTIVITY_LOG_CAPACITY || '10000'),
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Interval (in seconds) at which Prometheus gauge metrics are updated from
|
|
121
|
+
* the admin store. Defaults to 30 seconds.
|
|
122
|
+
*/
|
|
123
|
+
adminMetricsUpdateIntervalSeconds: parseInt(process.env.DWN_ADMIN_METRICS_UPDATE_INTERVAL || '30'),
|
|
124
|
+
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// Per-tenant storage quotas
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Default maximum number of messages a tenant may store. 0 = unlimited (default).
|
|
131
|
+
* Per-tenant overrides are managed via the admin API.
|
|
132
|
+
*/
|
|
133
|
+
quotaMaxMessages: parseInt(process.env.DWN_QUOTA_MAX_MESSAGES || '0'),
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Default maximum data storage in bytes a tenant may use. 0 = unlimited (default).
|
|
137
|
+
* Per-tenant overrides are managed via the admin API.
|
|
138
|
+
*/
|
|
139
|
+
quotaMaxStorageBytes: parseInt(process.env.DWN_QUOTA_MAX_STORAGE_BYTES || '0'),
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// Audit log retention
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Maximum age of audit log entries in days. Entries older than this are purged.
|
|
147
|
+
* 0 = no age limit (default: 90 days).
|
|
148
|
+
*
|
|
149
|
+
* @see https://github.com/enboxorg/enbox/issues/394
|
|
150
|
+
*/
|
|
151
|
+
auditLogMaxAgeDays: parseInt(process.env.DWN_AUDIT_LOG_MAX_AGE_DAYS || '90'),
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Maximum number of audit log rows to retain. Oldest entries are purged when exceeded.
|
|
155
|
+
* 0 = no row limit (default: 100000).
|
|
156
|
+
*
|
|
157
|
+
* @see https://github.com/enboxorg/enbox/issues/394
|
|
158
|
+
*/
|
|
159
|
+
auditLogMaxRows: parseInt(process.env.DWN_AUDIT_LOG_MAX_ROWS || '100000'),
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// Rate limiting
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Maximum HTTP requests per second per IP address. Set to 0 to disable.
|
|
167
|
+
* Defaults to 30 req/s which is generous for normal usage while limiting abuse.
|
|
168
|
+
* Can be reconfigured at runtime via the admin `PATCH /config` endpoint.
|
|
169
|
+
*/
|
|
170
|
+
rateLimitRequestsPerSecond: parseInt(process.env.DWN_RATE_LIMIT_REQUESTS_PER_SECOND || '30'),
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Maximum burst size for per-IP rate limiting. Allows short spikes above the
|
|
174
|
+
* sustained rate without triggering 429s. Defaults to 50.
|
|
175
|
+
*/
|
|
176
|
+
rateLimitBurst: parseInt(process.env.DWN_RATE_LIMIT_BURST || '50'),
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Maximum DWN requests per second per tenant DID. Set to 0 to disable.
|
|
180
|
+
* Defaults to 20 req/s. Applies to both HTTP and WebSocket transports.
|
|
181
|
+
* Can be reconfigured at runtime via the admin `PATCH /config` endpoint.
|
|
182
|
+
*/
|
|
183
|
+
rateLimitTenantRequestsPerSecond: parseInt(process.env.DWN_RATE_LIMIT_TENANT_REQUESTS_PER_SECOND || '20'),
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Maximum burst size for per-tenant rate limiting. Defaults to 50.
|
|
187
|
+
*/
|
|
188
|
+
rateLimitTenantBurst: parseInt(process.env.DWN_RATE_LIMIT_TENANT_BURST || '50'),
|
|
189
|
+
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// Record delivery & endpoint forwarding
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Enable endpoint forwarding: when a RecordsWrite/RecordsDelete is processed,
|
|
196
|
+
* forward the original signed message to the tenant's other DWN service
|
|
197
|
+
* endpoints (discovered via DID resolution). Disabled by default.
|
|
198
|
+
*/
|
|
199
|
+
forwardingEnabled: process.env.DWN_FORWARDING_ENABLED === 'true',
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Enable protocol-aware record delivery: when a RecordsWrite/RecordsDelete is
|
|
203
|
+
* processed at a protocol path with `$delivery`, proactively deliver to
|
|
204
|
+
* participants' DWN endpoints. Disabled by default.
|
|
205
|
+
*/
|
|
206
|
+
deliveryEnabled: process.env.DWN_DELIVERY_ENABLED === 'true',
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Maximum number of concurrent outbound delivery/forwarding requests.
|
|
210
|
+
* Prevents unbounded parallelism when delivering to many providers.
|
|
211
|
+
* Defaults to 10.
|
|
212
|
+
*/
|
|
213
|
+
deliveryMaxConcurrency: parseInt(process.env.DWN_DELIVERY_MAX_CONCURRENCY || '10'),
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* TTL in seconds for caching DID document service endpoint resolution results.
|
|
217
|
+
* Avoids resolving the same DID document on every delivery. Defaults to 300 (5 min).
|
|
218
|
+
*/
|
|
219
|
+
deliveryEndpointCacheTtlSeconds: parseInt(process.env.DWN_DELIVERY_ENDPOINT_CACHE_TTL || '300'),
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* TTL in seconds for the recently-forwarded messageCid deduplication cache.
|
|
223
|
+
* Messages with CIDs in this cache are not forwarded again, reducing redundant
|
|
224
|
+
* outbound requests between peer endpoints. Defaults to 60.
|
|
225
|
+
*/
|
|
226
|
+
forwardingDeduplicationTtlSeconds: parseInt(process.env.DWN_FORWARDING_DEDUP_TTL || '60'),
|
|
71
227
|
};
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Reads the admin token from a file path, trimming whitespace.
|
|
231
|
+
* Returns `undefined` if the file cannot be read.
|
|
232
|
+
*/
|
|
233
|
+
function readAdminTokenFromFile(filePath: string): string | undefined {
|
|
234
|
+
try {
|
|
235
|
+
return readFileSync(filePath).toString().trim() || undefined;
|
|
236
|
+
} catch {
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -1,39 +1,89 @@
|
|
|
1
|
-
import type { Dwn } from
|
|
1
|
+
import type { Dwn } from '@enbox/dwn-sdk-js';
|
|
2
|
+
import type { ServerWebSocket } from 'bun';
|
|
2
3
|
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
4
|
+
import type { ActivityLog } from '../admin/activity-log.js';
|
|
5
|
+
import type { AdminConnectionSnapshot } from '../admin/types.js';
|
|
6
|
+
import type { AdminStore } from '../admin/admin-store.js';
|
|
7
|
+
import type { DwnServerConfig } from '../config.js';
|
|
8
|
+
import type { MessageProcessedHook } from '../message-processed-hook.js';
|
|
9
|
+
import type { RateLimiter } from '../rate-limiter.js';
|
|
10
|
+
import type { RegistrationStore } from '../registration/registration-store.js';
|
|
11
|
+
import type { WsData } from '../http-api.js';
|
|
5
12
|
|
|
6
|
-
import { SocketConnection } from
|
|
13
|
+
import { SocketConnection } from './socket-connection.js';
|
|
7
14
|
|
|
8
15
|
/**
|
|
9
16
|
* Interface for managing `WebSocket` connections as they arrive.
|
|
10
17
|
*/
|
|
11
18
|
export interface ConnectionManager {
|
|
12
|
-
/** connect handler
|
|
13
|
-
connect(socket:
|
|
19
|
+
/** connect handler invoked when a new WebSocket connection is established. */
|
|
20
|
+
connect(socket: ServerWebSocket<WsData>): Promise<void>;
|
|
14
21
|
/** closes all of the connections */
|
|
15
|
-
closeAll(): Promise<void
|
|
22
|
+
closeAll(): Promise<void>;
|
|
23
|
+
/** Returns the number of active connections. */
|
|
24
|
+
getConnectionCount(): number;
|
|
25
|
+
/** Returns the total number of active subscriptions across all connections. */
|
|
26
|
+
getSubscriptionCount(): number;
|
|
27
|
+
/** Returns serializable snapshots of all active connections. */
|
|
28
|
+
getConnectionSnapshots(): AdminConnectionSnapshot[];
|
|
16
29
|
}
|
|
17
30
|
|
|
18
31
|
/**
|
|
19
32
|
* A Simple In Memory ConnectionManager implementation.
|
|
20
|
-
* It uses a `Map<
|
|
33
|
+
* It uses a `Map<ServerWebSocket, SocketConnection>` to manage connections.
|
|
21
34
|
*/
|
|
22
35
|
export class InMemoryConnectionManager implements ConnectionManager {
|
|
23
|
-
constructor(
|
|
36
|
+
constructor(
|
|
37
|
+
private dwn: Dwn,
|
|
38
|
+
private connections: Map<ServerWebSocket<WsData>, SocketConnection> = new Map(),
|
|
39
|
+
private maxInFlight?: number,
|
|
40
|
+
private activityLog?: ActivityLog,
|
|
41
|
+
private adminStore?: AdminStore,
|
|
42
|
+
private registrationStore?: RegistrationStore,
|
|
43
|
+
private serverConfig?: DwnServerConfig,
|
|
44
|
+
private tenantRateLimiter?: RateLimiter,
|
|
45
|
+
private messageProcessedHooks?: MessageProcessedHook[],
|
|
46
|
+
) {}
|
|
24
47
|
|
|
25
|
-
async connect(socket:
|
|
26
|
-
const connection = new SocketConnection(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
48
|
+
async connect(socket: ServerWebSocket<WsData>): Promise<void> {
|
|
49
|
+
const connection = new SocketConnection(
|
|
50
|
+
socket, this.dwn, () => {
|
|
51
|
+
// this is the onClose handler to clean up any closed connections.
|
|
52
|
+
this.connections.delete(socket);
|
|
53
|
+
},
|
|
54
|
+
this.maxInFlight, this.activityLog,
|
|
55
|
+
this.adminStore, this.registrationStore, this.serverConfig, this.tenantRateLimiter, this.messageProcessedHooks,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Attach the connection to the ws.data so Bun's websocket handlers can delegate to it.
|
|
59
|
+
socket.data.connection = connection;
|
|
30
60
|
|
|
31
61
|
this.connections.set(socket, connection);
|
|
32
62
|
}
|
|
33
63
|
|
|
34
64
|
async closeAll(): Promise<void> {
|
|
35
|
-
const closePromises = [];
|
|
36
|
-
this.connections.forEach(connection => closePromises.push(connection.close()));
|
|
65
|
+
const closePromises: Promise<void>[] = [];
|
|
66
|
+
this.connections.forEach((connection) => closePromises.push(connection.close()));
|
|
37
67
|
await Promise.all(closePromises);
|
|
38
68
|
}
|
|
39
|
-
|
|
69
|
+
|
|
70
|
+
getConnectionCount(): number {
|
|
71
|
+
return this.connections.size;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getSubscriptionCount(): number {
|
|
75
|
+
let count = 0;
|
|
76
|
+
this.connections.forEach((conn) => {
|
|
77
|
+
count += conn.subscriptionCount;
|
|
78
|
+
});
|
|
79
|
+
return count;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getConnectionSnapshots(): AdminConnectionSnapshot[] {
|
|
83
|
+
const snapshots: AdminConnectionSnapshot[] = [];
|
|
84
|
+
this.connections.forEach((conn) => {
|
|
85
|
+
snapshots.push(conn.toSnapshot());
|
|
86
|
+
});
|
|
87
|
+
return snapshots;
|
|
88
|
+
}
|
|
89
|
+
}
|