@enbox/dwn-server 0.0.1
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 +201 -0
- package/README.md +353 -0
- package/dist/cjs/index.js +6811 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/src/config.d.ts +55 -0
- package/dist/esm/src/config.d.ts.map +1 -0
- package/dist/esm/src/config.js +60 -0
- package/dist/esm/src/config.js.map +1 -0
- package/dist/esm/src/connection/connection-manager.d.ts +25 -0
- package/dist/esm/src/connection/connection-manager.d.ts.map +1 -0
- package/dist/esm/src/connection/connection-manager.js +26 -0
- package/dist/esm/src/connection/connection-manager.js.map +1 -0
- package/dist/esm/src/connection/socket-connection.d.ts +65 -0
- package/dist/esm/src/connection/socket-connection.d.ts.map +1 -0
- package/dist/esm/src/connection/socket-connection.js +180 -0
- package/dist/esm/src/connection/socket-connection.js.map +1 -0
- package/dist/esm/src/dwn-error.d.ts +29 -0
- package/dist/esm/src/dwn-error.d.ts.map +1 -0
- package/dist/esm/src/dwn-error.js +36 -0
- package/dist/esm/src/dwn-error.js.map +1 -0
- package/dist/esm/src/dwn-server.d.ts +60 -0
- package/dist/esm/src/dwn-server.d.ts.map +1 -0
- package/dist/esm/src/dwn-server.js +130 -0
- package/dist/esm/src/dwn-server.js.map +1 -0
- package/dist/esm/src/http-api.d.ts +26 -0
- package/dist/esm/src/http-api.d.ts.map +1 -0
- package/dist/esm/src/http-api.js +474 -0
- package/dist/esm/src/http-api.js.map +1 -0
- package/dist/esm/src/index.d.ts +7 -0
- package/dist/esm/src/index.d.ts.map +1 -0
- package/dist/esm/src/index.js +6 -0
- package/dist/esm/src/index.js.map +1 -0
- package/dist/esm/src/json-rpc-api.d.ts +3 -0
- package/dist/esm/src/json-rpc-api.d.ts.map +1 -0
- package/dist/esm/src/json-rpc-api.js +8 -0
- package/dist/esm/src/json-rpc-api.js.map +1 -0
- package/dist/esm/src/json-rpc-handlers/dwn/index.d.ts +2 -0
- package/dist/esm/src/json-rpc-handlers/dwn/index.d.ts.map +1 -0
- package/dist/esm/src/json-rpc-handlers/dwn/index.js +2 -0
- package/dist/esm/src/json-rpc-handlers/dwn/index.js.map +1 -0
- package/dist/esm/src/json-rpc-handlers/dwn/process-message.d.ts +3 -0
- package/dist/esm/src/json-rpc-handlers/dwn/process-message.d.ts.map +1 -0
- package/dist/esm/src/json-rpc-handlers/dwn/process-message.js +73 -0
- package/dist/esm/src/json-rpc-handlers/dwn/process-message.js.map +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/close.d.ts +10 -0
- package/dist/esm/src/json-rpc-handlers/subscription/close.d.ts.map +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/close.js +39 -0
- package/dist/esm/src/json-rpc-handlers/subscription/close.js.map +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/index.d.ts +2 -0
- package/dist/esm/src/json-rpc-handlers/subscription/index.d.ts.map +1 -0
- package/dist/esm/src/json-rpc-handlers/subscription/index.js +2 -0
- package/dist/esm/src/json-rpc-handlers/subscription/index.js.map +1 -0
- package/dist/esm/src/json-rpc-socket.d.ts +39 -0
- package/dist/esm/src/json-rpc-socket.d.ts.map +1 -0
- package/dist/esm/src/json-rpc-socket.js +125 -0
- package/dist/esm/src/json-rpc-socket.js.map +1 -0
- package/dist/esm/src/lib/http-server-shutdown-handler.d.ts +10 -0
- package/dist/esm/src/lib/http-server-shutdown-handler.d.ts.map +1 -0
- package/dist/esm/src/lib/http-server-shutdown-handler.js +65 -0
- package/dist/esm/src/lib/http-server-shutdown-handler.js.map +1 -0
- package/dist/esm/src/lib/json-rpc-router.d.ts +30 -0
- package/dist/esm/src/lib/json-rpc-router.d.ts.map +1 -0
- package/dist/esm/src/lib/json-rpc-router.js +14 -0
- package/dist/esm/src/lib/json-rpc-router.js.map +1 -0
- package/dist/esm/src/lib/json-rpc.d.ts +54 -0
- package/dist/esm/src/lib/json-rpc.d.ts.map +1 -0
- package/dist/esm/src/lib/json-rpc.js +60 -0
- package/dist/esm/src/lib/json-rpc.js.map +1 -0
- package/dist/esm/src/main.d.ts +3 -0
- package/dist/esm/src/main.d.ts.map +1 -0
- package/dist/esm/src/main.js +11 -0
- package/dist/esm/src/main.js.map +1 -0
- package/dist/esm/src/metrics.d.ts +4 -0
- package/dist/esm/src/metrics.d.ts.map +1 -0
- package/dist/esm/src/metrics.js +13 -0
- package/dist/esm/src/metrics.js.map +1 -0
- package/dist/esm/src/plugin-loader.d.ts +10 -0
- package/dist/esm/src/plugin-loader.d.ts.map +1 -0
- package/dist/esm/src/plugin-loader.js +19 -0
- package/dist/esm/src/plugin-loader.js.map +1 -0
- package/dist/esm/src/process-handlers.d.ts +11 -0
- package/dist/esm/src/process-handlers.d.ts.map +1 -0
- package/dist/esm/src/process-handlers.js +40 -0
- package/dist/esm/src/process-handlers.js.map +1 -0
- package/dist/esm/src/registration/proof-of-work-manager.d.ts +93 -0
- package/dist/esm/src/registration/proof-of-work-manager.d.ts.map +1 -0
- package/dist/esm/src/registration/proof-of-work-manager.js +243 -0
- package/dist/esm/src/registration/proof-of-work-manager.js.map +1 -0
- package/dist/esm/src/registration/proof-of-work-types.d.ts +8 -0
- package/dist/esm/src/registration/proof-of-work-types.d.ts.map +1 -0
- package/dist/esm/src/registration/proof-of-work-types.js +2 -0
- package/dist/esm/src/registration/proof-of-work-types.js.map +1 -0
- package/dist/esm/src/registration/proof-of-work.d.ts +41 -0
- package/dist/esm/src/registration/proof-of-work.d.ts.map +1 -0
- package/dist/esm/src/registration/proof-of-work.js +69 -0
- package/dist/esm/src/registration/proof-of-work.js.map +1 -0
- package/dist/esm/src/registration/registration-manager.d.ts +55 -0
- package/dist/esm/src/registration/registration-manager.d.ts.map +1 -0
- package/dist/esm/src/registration/registration-manager.js +121 -0
- package/dist/esm/src/registration/registration-manager.js.map +1 -0
- package/dist/esm/src/registration/registration-store.d.ts +24 -0
- package/dist/esm/src/registration/registration-store.d.ts.map +1 -0
- package/dist/esm/src/registration/registration-store.js +56 -0
- package/dist/esm/src/registration/registration-store.js.map +1 -0
- package/dist/esm/src/registration/registration-types.d.ts +18 -0
- package/dist/esm/src/registration/registration-types.d.ts.map +1 -0
- package/dist/esm/src/registration/registration-types.js +2 -0
- package/dist/esm/src/registration/registration-types.js.map +1 -0
- package/dist/esm/src/storage.d.ts +24 -0
- package/dist/esm/src/storage.d.ts.map +1 -0
- package/dist/esm/src/storage.js +146 -0
- package/dist/esm/src/storage.js.map +1 -0
- package/dist/esm/src/web5-connect/sql-ttl-cache.d.ts +39 -0
- package/dist/esm/src/web5-connect/sql-ttl-cache.d.ts.map +1 -0
- package/dist/esm/src/web5-connect/sql-ttl-cache.js +106 -0
- package/dist/esm/src/web5-connect/sql-ttl-cache.js.map +1 -0
- package/dist/esm/src/web5-connect/web5-connect-server.d.ts +58 -0
- package/dist/esm/src/web5-connect/web5-connect-server.d.ts.map +1 -0
- package/dist/esm/src/web5-connect/web5-connect-server.js +77 -0
- package/dist/esm/src/web5-connect/web5-connect-server.js.map +1 -0
- package/dist/esm/src/ws-api.d.ts +13 -0
- package/dist/esm/src/ws-api.d.ts.map +1 -0
- package/dist/esm/src/ws-api.js +31 -0
- package/dist/esm/src/ws-api.js.map +1 -0
- package/dist/esm/tests/common-scenario-validator.d.ts +11 -0
- package/dist/esm/tests/common-scenario-validator.d.ts.map +1 -0
- package/dist/esm/tests/common-scenario-validator.js +114 -0
- package/dist/esm/tests/common-scenario-validator.js.map +1 -0
- package/dist/esm/tests/connection/connection-manager.spec.d.ts +2 -0
- package/dist/esm/tests/connection/connection-manager.spec.d.ts.map +1 -0
- package/dist/esm/tests/connection/connection-manager.spec.js +47 -0
- package/dist/esm/tests/connection/connection-manager.spec.js.map +1 -0
- package/dist/esm/tests/connection/socket-connection.spec.d.ts +2 -0
- package/dist/esm/tests/connection/socket-connection.spec.d.ts.map +1 -0
- package/dist/esm/tests/connection/socket-connection.spec.js +125 -0
- package/dist/esm/tests/connection/socket-connection.spec.js.map +1 -0
- package/dist/esm/tests/cors/http-api.browser.d.ts +2 -0
- package/dist/esm/tests/cors/http-api.browser.d.ts.map +1 -0
- package/dist/esm/tests/cors/http-api.browser.js +60 -0
- package/dist/esm/tests/cors/http-api.browser.js.map +1 -0
- package/dist/esm/tests/cors/ping.browser.d.ts +2 -0
- package/dist/esm/tests/cors/ping.browser.d.ts.map +1 -0
- package/dist/esm/tests/cors/ping.browser.js +7 -0
- package/dist/esm/tests/cors/ping.browser.js.map +1 -0
- package/dist/esm/tests/dwn-process-message.spec.d.ts +2 -0
- package/dist/esm/tests/dwn-process-message.spec.d.ts.map +1 -0
- package/dist/esm/tests/dwn-process-message.spec.js +172 -0
- package/dist/esm/tests/dwn-process-message.spec.js.map +1 -0
- package/dist/esm/tests/dwn-server.spec.d.ts +2 -0
- package/dist/esm/tests/dwn-server.spec.d.ts.map +1 -0
- package/dist/esm/tests/dwn-server.spec.js +49 -0
- package/dist/esm/tests/dwn-server.spec.js.map +1 -0
- package/dist/esm/tests/http-api.spec.d.ts +2 -0
- package/dist/esm/tests/http-api.spec.d.ts.map +1 -0
- package/dist/esm/tests/http-api.spec.js +775 -0
- package/dist/esm/tests/http-api.spec.js.map +1 -0
- package/dist/esm/tests/json-rpc-socket.spec.d.ts +2 -0
- package/dist/esm/tests/json-rpc-socket.spec.d.ts.map +1 -0
- package/dist/esm/tests/json-rpc-socket.spec.js +225 -0
- package/dist/esm/tests/json-rpc-socket.spec.js.map +1 -0
- package/dist/esm/tests/plugins/data-store-sqlite.d.ts +17 -0
- package/dist/esm/tests/plugins/data-store-sqlite.d.ts.map +1 -0
- package/dist/esm/tests/plugins/data-store-sqlite.js +23 -0
- package/dist/esm/tests/plugins/data-store-sqlite.js.map +1 -0
- package/dist/esm/tests/plugins/event-log-sqlite.d.ts +17 -0
- package/dist/esm/tests/plugins/event-log-sqlite.d.ts.map +1 -0
- package/dist/esm/tests/plugins/event-log-sqlite.js +23 -0
- package/dist/esm/tests/plugins/event-log-sqlite.js.map +1 -0
- package/dist/esm/tests/plugins/event-stream-in-memory.d.ts +17 -0
- package/dist/esm/tests/plugins/event-stream-in-memory.d.ts.map +1 -0
- package/dist/esm/tests/plugins/event-stream-in-memory.js +21 -0
- package/dist/esm/tests/plugins/event-stream-in-memory.js.map +1 -0
- package/dist/esm/tests/plugins/message-store-sqlite.d.ts +17 -0
- package/dist/esm/tests/plugins/message-store-sqlite.d.ts.map +1 -0
- package/dist/esm/tests/plugins/message-store-sqlite.js +23 -0
- package/dist/esm/tests/plugins/message-store-sqlite.js.map +1 -0
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts +17 -0
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts.map +1 -0
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.js +23 -0
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.js.map +1 -0
- package/dist/esm/tests/process-handler.spec.d.ts +2 -0
- package/dist/esm/tests/process-handler.spec.d.ts.map +1 -0
- package/dist/esm/tests/process-handler.spec.js +60 -0
- package/dist/esm/tests/process-handler.spec.js.map +1 -0
- package/dist/esm/tests/registration/proof-of-work-manager.spec.d.ts +2 -0
- package/dist/esm/tests/registration/proof-of-work-manager.spec.d.ts.map +1 -0
- package/dist/esm/tests/registration/proof-of-work-manager.spec.js +157 -0
- package/dist/esm/tests/registration/proof-of-work-manager.spec.js.map +1 -0
- package/dist/esm/tests/rpc-subscribe-close.spec.d.ts +2 -0
- package/dist/esm/tests/rpc-subscribe-close.spec.d.ts.map +1 -0
- package/dist/esm/tests/rpc-subscribe-close.spec.js +81 -0
- package/dist/esm/tests/rpc-subscribe-close.spec.js.map +1 -0
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.d.ts +2 -0
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.d.ts.map +1 -0
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js +73 -0
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js.map +1 -0
- package/dist/esm/tests/scenarios/registration.spec.d.ts +2 -0
- package/dist/esm/tests/scenarios/registration.spec.d.ts.map +1 -0
- package/dist/esm/tests/scenarios/registration.spec.js +507 -0
- package/dist/esm/tests/scenarios/registration.spec.js.map +1 -0
- package/dist/esm/tests/scenarios/web5-connect.spec.d.ts +2 -0
- package/dist/esm/tests/scenarios/web5-connect.spec.d.ts.map +1 -0
- package/dist/esm/tests/scenarios/web5-connect.spec.js +137 -0
- package/dist/esm/tests/scenarios/web5-connect.spec.js.map +1 -0
- package/dist/esm/tests/test-dwn.d.ts +7 -0
- package/dist/esm/tests/test-dwn.d.ts.map +1 -0
- package/dist/esm/tests/test-dwn.js +34 -0
- package/dist/esm/tests/test-dwn.js.map +1 -0
- package/dist/esm/tests/utils.d.ts +46 -0
- package/dist/esm/tests/utils.d.ts.map +1 -0
- package/dist/esm/tests/utils.js +116 -0
- package/dist/esm/tests/utils.js.map +1 -0
- package/dist/esm/tests/ws-api.spec.d.ts +2 -0
- package/dist/esm/tests/ws-api.spec.d.ts.map +1 -0
- package/dist/esm/tests/ws-api.spec.js +327 -0
- package/dist/esm/tests/ws-api.spec.js.map +1 -0
- package/package.json +119 -0
- package/src/config.ts +71 -0
- package/src/connection/connection-manager.ts +39 -0
- package/src/connection/socket-connection.ts +221 -0
- package/src/dwn-error.ts +38 -0
- package/src/dwn-server.ts +178 -0
- package/src/http-api.ts +541 -0
- package/src/index.ts +6 -0
- package/src/json-rpc-api.ts +11 -0
- package/src/json-rpc-handlers/dwn/index.ts +1 -0
- package/src/json-rpc-handlers/dwn/process-message.ts +123 -0
- package/src/json-rpc-handlers/subscription/close.ts +59 -0
- package/src/json-rpc-handlers/subscription/index.ts +1 -0
- package/src/json-rpc-socket.ts +155 -0
- package/src/lib/http-server-shutdown-handler.ts +79 -0
- package/src/lib/json-rpc-router.ts +52 -0
- package/src/lib/json-rpc.ts +126 -0
- package/src/main.ts +14 -0
- package/src/metrics.ts +14 -0
- package/src/plugin-loader.ts +17 -0
- package/src/process-handlers.ts +65 -0
- package/src/registration/proof-of-work-manager.ts +317 -0
- package/src/registration/proof-of-work-types.ts +7 -0
- package/src/registration/proof-of-work.ts +100 -0
- package/src/registration/registration-manager.ts +153 -0
- package/src/registration/registration-store.ts +79 -0
- package/src/registration/registration-types.ts +18 -0
- package/src/storage.ts +213 -0
- package/src/web5-connect/sql-ttl-cache.ts +137 -0
- package/src/web5-connect/web5-connect-server.ts +122 -0
- package/src/ws-api.ts +45 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import type { Dwn, GenericMessage, MessageEvent } from "@enbox/dwn-sdk-js";
|
|
2
|
+
import { DwnMethodName } from "@enbox/dwn-sdk-js";
|
|
3
|
+
|
|
4
|
+
import type { WebSocket } from "ws";
|
|
5
|
+
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
|
+
|
|
11
|
+
import { requestCounter } from "../metrics.js";
|
|
12
|
+
import { jsonRpcRouter } from "../json-rpc-api.js";
|
|
13
|
+
import { JsonRpcErrorCodes, createJsonRpcErrorResponse, createJsonRpcSuccessResponse } from "../lib/json-rpc.js";
|
|
14
|
+
import { DwnServerError, DwnServerErrorCode } from "../dwn-error.js";
|
|
15
|
+
|
|
16
|
+
const HEARTBEAT_INTERVAL = 30_000;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* SocketConnection handles a WebSocket connection to a DWN using JSON RPC.
|
|
20
|
+
* It also manages references to the long running RPC subscriptions for the connection.
|
|
21
|
+
*/
|
|
22
|
+
export class SocketConnection {
|
|
23
|
+
private heartbeatInterval: NodeJS.Timer;
|
|
24
|
+
private subscriptions: Map<JsonRpcId, JsonRpcSubscription> = new Map();
|
|
25
|
+
private isAlive: boolean;
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
private socket: WebSocket,
|
|
29
|
+
private dwn: Dwn,
|
|
30
|
+
private onClose?: () => void
|
|
31
|
+
){
|
|
32
|
+
socket.on('message', this.message.bind(this));
|
|
33
|
+
socket.on('close', this.close.bind(this));
|
|
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
|
|
42
|
+
this.isAlive = true;
|
|
43
|
+
this.heartbeatInterval = setInterval(() => {
|
|
44
|
+
if (this.isAlive === false) {
|
|
45
|
+
this.close();
|
|
46
|
+
}
|
|
47
|
+
this.isAlive = false;
|
|
48
|
+
this.socket.ping();
|
|
49
|
+
}, HEARTBEAT_INTERVAL);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Checks to see if the incoming `JsonRpcId` is already in use for a subscription.
|
|
54
|
+
*/
|
|
55
|
+
hasSubscription(id: JsonRpcId): boolean {
|
|
56
|
+
return this.subscriptions.has(id);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Adds a reference for the JSON RPC Subscription to this connection.
|
|
61
|
+
* Used for cleanup if the connection is closed.
|
|
62
|
+
*/
|
|
63
|
+
async addSubscription(subscription: JsonRpcSubscription): Promise<void> {
|
|
64
|
+
if (this.subscriptions.has(subscription.id)) {
|
|
65
|
+
throw new DwnServerError(
|
|
66
|
+
DwnServerErrorCode.ConnectionSubscriptionJsonRpcIdExists,
|
|
67
|
+
`the subscription with id ${subscription.id} already exists`
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.subscriptions.set(subscription.id, subscription);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Closes and removes the reference for a given subscription from this connection.
|
|
76
|
+
*
|
|
77
|
+
* @param id the `JsonRpcId` of the JSON RPC subscription request.
|
|
78
|
+
*/
|
|
79
|
+
async closeSubscription(id: JsonRpcId): Promise<void> {
|
|
80
|
+
if (!this.subscriptions.has(id)) {
|
|
81
|
+
throw new DwnServerError(
|
|
82
|
+
DwnServerErrorCode.ConnectionSubscriptionJsonRpcIdNotFound,
|
|
83
|
+
`the subscription with id ${id} was not found`
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const connection = this.subscriptions.get(id);
|
|
88
|
+
await connection.close();
|
|
89
|
+
this.subscriptions.delete(id);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Closes the existing connection and cleans up any listeners or subscriptions.
|
|
94
|
+
*/
|
|
95
|
+
async close(): Promise<void> {
|
|
96
|
+
clearInterval(this.heartbeatInterval);
|
|
97
|
+
|
|
98
|
+
// clean up all socket event listeners
|
|
99
|
+
this.socket.removeAllListeners();
|
|
100
|
+
|
|
101
|
+
const closePromises = [];
|
|
102
|
+
for (const [id, subscription] of this.subscriptions) {
|
|
103
|
+
closePromises.push(subscription.close());
|
|
104
|
+
this.subscriptions.delete(id);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// close all of the associated subscriptions
|
|
108
|
+
await Promise.all(closePromises);
|
|
109
|
+
|
|
110
|
+
// close the socket.
|
|
111
|
+
this.socket.close();
|
|
112
|
+
|
|
113
|
+
// if there was a close handler passed call it after the connection has been closed
|
|
114
|
+
if (this.onClose !== undefined) {
|
|
115
|
+
this.onClose();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
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
|
+
/**
|
|
128
|
+
* Log the error and close the connection.
|
|
129
|
+
*/
|
|
130
|
+
private async error(error:Error): Promise<void>{
|
|
131
|
+
log.error(`SocketConnection error, terminating connection`, error);
|
|
132
|
+
this.socket.terminate();
|
|
133
|
+
await this.close();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Handles a `JSON RPC 2.0` encoded message.
|
|
138
|
+
*/
|
|
139
|
+
private async message(dataBuffer: Buffer): Promise<void> {
|
|
140
|
+
const requestData = dataBuffer.toString();
|
|
141
|
+
if (!requestData) {
|
|
142
|
+
return this.send(createJsonRpcErrorResponse(
|
|
143
|
+
uuidv4(),
|
|
144
|
+
JsonRpcErrorCodes.BadRequest,
|
|
145
|
+
'request payload required.'
|
|
146
|
+
))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let jsonRequest: JsonRpcRequest;
|
|
150
|
+
try {
|
|
151
|
+
jsonRequest = JSON.parse(requestData);
|
|
152
|
+
} catch(error) {
|
|
153
|
+
const errorResponse = createJsonRpcErrorResponse(
|
|
154
|
+
uuidv4(),
|
|
155
|
+
JsonRpcErrorCodes.BadRequest,
|
|
156
|
+
(error as Error).message
|
|
157
|
+
);
|
|
158
|
+
return this.send(errorResponse);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const requestContext = await this.buildRequestContext(jsonRequest);
|
|
162
|
+
const { jsonRpcResponse } = await jsonRpcRouter.handle(jsonRequest, requestContext);
|
|
163
|
+
if (jsonRpcResponse.error) {
|
|
164
|
+
requestCounter.inc({ method: jsonRequest.method, error: 1 });
|
|
165
|
+
} else {
|
|
166
|
+
requestCounter.inc({
|
|
167
|
+
method: jsonRequest.method,
|
|
168
|
+
status: jsonRpcResponse?.result?.reply?.status?.code || 0,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
this.send(jsonRpcResponse);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Sends a JSON encoded Buffer through the Websocket.
|
|
176
|
+
*/
|
|
177
|
+
private send(response: JsonRpcResponse | JsonRpcErrorResponse): void {
|
|
178
|
+
this.socket.send(JSON.stringify(response));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Creates a subscription handler to send messages matching the subscription requested.
|
|
183
|
+
*
|
|
184
|
+
* Wraps the incoming `message` in a `JSON RPC Success Response` using the original subscription`JSON RPC Id` to send through the WebSocket.
|
|
185
|
+
*/
|
|
186
|
+
private createSubscriptionHandler(id: JsonRpcId): (message: MessageEvent) => void {
|
|
187
|
+
return (event) => {
|
|
188
|
+
const response = createJsonRpcSuccessResponse(id, { event });
|
|
189
|
+
this.send(response);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Builds a `RequestContext` object to use with the `JSON RPC API`.
|
|
195
|
+
*
|
|
196
|
+
* Adds a `subscriptionHandler` for `Subscribe` messages.
|
|
197
|
+
*/
|
|
198
|
+
private async buildRequestContext(request: JsonRpcRequest): Promise<RequestContext> {
|
|
199
|
+
const { params, method, subscription } = request;
|
|
200
|
+
|
|
201
|
+
const requestContext: RequestContext = {
|
|
202
|
+
transport : 'ws',
|
|
203
|
+
dwn : this.dwn,
|
|
204
|
+
socketConnection : this,
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// methods that expect a long-running subscription begin with `rpc.subscribe.`
|
|
208
|
+
if (method.startsWith('rpc.subscribe.') && subscription) {
|
|
209
|
+
const { message } = params as { message?: GenericMessage };
|
|
210
|
+
if (message?.descriptor.method === DwnMethodName.Subscribe) {
|
|
211
|
+
const handlerFunc = this.createSubscriptionHandler(subscription.id);
|
|
212
|
+
requestContext.subscriptionRequest = {
|
|
213
|
+
id: subscription.id,
|
|
214
|
+
subscriptionHandler: (message): void => handlerFunc(message),
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return requestContext;
|
|
220
|
+
}
|
|
221
|
+
}
|
package/src/dwn-error.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A class that represents a DWN Server error.
|
|
3
|
+
*/
|
|
4
|
+
export class DwnServerError extends Error {
|
|
5
|
+
constructor(
|
|
6
|
+
public code: string,
|
|
7
|
+
message: string,
|
|
8
|
+
) {
|
|
9
|
+
super(`${code}: ${message}`);
|
|
10
|
+
|
|
11
|
+
this.name = 'DwnServerError';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Called by `JSON.stringify(...)` automatically.
|
|
16
|
+
*/
|
|
17
|
+
public toJSON(): { code: string, message: string } {
|
|
18
|
+
return {
|
|
19
|
+
code: this.code,
|
|
20
|
+
message: this.message,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* DWN Server error codes.
|
|
27
|
+
*/
|
|
28
|
+
export enum DwnServerErrorCode {
|
|
29
|
+
ConnectionSubscriptionJsonRpcIdExists = 'ConnectionSubscriptionJsonRpcIdExists',
|
|
30
|
+
ConnectionSubscriptionJsonRpcIdNotFound = 'ConnectionSubscriptionJsonRpcIdNotFound',
|
|
31
|
+
ProofOfWorkInsufficientSolutionNonce = 'ProofOfWorkInsufficientSolutionNonce',
|
|
32
|
+
ProofOfWorkInvalidOrExpiredChallenge = 'ProofOfWorkInvalidOrExpiredChallenge',
|
|
33
|
+
ProofOfWorkManagerInvalidChallengeNonce = 'ProofOfWorkManagerInvalidChallengeNonce',
|
|
34
|
+
ProofOfWorkManagerInvalidResponseNonceFormat = 'ProofOfWorkManagerInvalidResponseNonceFormat',
|
|
35
|
+
ProofOfWorkManagerResponseNonceReused = 'ProofOfWorkManagerResponseNonceReused',
|
|
36
|
+
RegistrationManagerInvalidOrOutdatedTermsOfServiceHash = 'RegistrationManagerInvalidOrOutdatedTermsOfServiceHash',
|
|
37
|
+
TenantRegistrationOutdatedTermsOfService = 'TenantRegistrationOutdatedTermsOfService',
|
|
38
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { DidResolver } from '@enbox/dids';
|
|
2
|
+
import type { EventStream } from '@enbox/dwn-sdk-js';
|
|
3
|
+
import type { ProcessHandlers } from './process-handlers.js';
|
|
4
|
+
import type { Server } from 'http';
|
|
5
|
+
import type { WebSocketServer } from 'ws';
|
|
6
|
+
import type { DwnServerConfig } from './config.js';
|
|
7
|
+
|
|
8
|
+
import log from 'loglevel';
|
|
9
|
+
import prefix from 'loglevel-plugin-prefix';
|
|
10
|
+
import { config as defaultConfig } from './config.js';
|
|
11
|
+
import { getDwnConfig } from './storage.js';
|
|
12
|
+
import { HttpServerShutdownHandler } from './lib/http-server-shutdown-handler.js';
|
|
13
|
+
import { HttpApi } from './http-api.js';
|
|
14
|
+
import { PluginLoader } from './plugin-loader.js';
|
|
15
|
+
import { RegistrationManager } from './registration/registration-manager.js';
|
|
16
|
+
import { WsApi } from './ws-api.js';
|
|
17
|
+
import { Dwn, EventEmitterStream } from '@enbox/dwn-sdk-js';
|
|
18
|
+
import { removeProcessHandlers, setProcessHandlers } from './process-handlers.js';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Options for the DwnServer constructor.
|
|
22
|
+
* This is different to DwnServerConfig in that the DwnServerConfig defines configuration that come from environment variables so (more) user facing.
|
|
23
|
+
* Where as DwnServerOptions wraps DwnServerConfig with additional overrides that can be used for testing.
|
|
24
|
+
*/
|
|
25
|
+
export type DwnServerOptions = {
|
|
26
|
+
/**
|
|
27
|
+
* A custom DID resolver to use in the DWN.
|
|
28
|
+
* Mainly for testing purposes. Ignored if `dwn` is provided.
|
|
29
|
+
*/
|
|
30
|
+
didResolver?: DidResolver;
|
|
31
|
+
dwn?: Dwn;
|
|
32
|
+
config?: DwnServerConfig;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* State of the DwnServer, either Stopped or Started, to help short-circuit start and stop logic.
|
|
37
|
+
*/
|
|
38
|
+
enum DwnServerState {
|
|
39
|
+
Stopped,
|
|
40
|
+
Started
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class DwnServer {
|
|
44
|
+
serverState = DwnServerState.Stopped;
|
|
45
|
+
processHandlers: ProcessHandlers;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A custom DID resolver to use in the DWN.
|
|
49
|
+
* Mainly for testing purposes. Ignored if `dwn` is provided.
|
|
50
|
+
*/
|
|
51
|
+
didResolver?: DidResolver;
|
|
52
|
+
dwn?: Dwn;
|
|
53
|
+
config: DwnServerConfig;
|
|
54
|
+
#httpServerShutdownHandler: HttpServerShutdownHandler;
|
|
55
|
+
#httpApi: HttpApi;
|
|
56
|
+
#wsApi: WsApi;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param options.dwn - Dwn instance to use as an override. Registration endpoint will not be enabled if this is provided.
|
|
60
|
+
*/
|
|
61
|
+
constructor(options: DwnServerOptions = {}) {
|
|
62
|
+
this.config = options.config ?? defaultConfig;
|
|
63
|
+
|
|
64
|
+
this.didResolver = options.didResolver;
|
|
65
|
+
this.dwn = options.dwn;
|
|
66
|
+
|
|
67
|
+
log.setLevel(this.config.logLevel as log.LogLevelDesc);
|
|
68
|
+
|
|
69
|
+
prefix.reg(log);
|
|
70
|
+
prefix.apply(log);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Starts the DWN server.
|
|
75
|
+
*/
|
|
76
|
+
async start(): Promise<void> {
|
|
77
|
+
if (this.serverState === DwnServerState.Started) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
await this.#setupServer();
|
|
82
|
+
this.processHandlers = setProcessHandlers(this);
|
|
83
|
+
this.serverState = DwnServerState.Started;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Function to setup the servers (HTTP and WebSocket)
|
|
88
|
+
* The DWN creation is secondary and only happens if it hasn't already been done.
|
|
89
|
+
*/
|
|
90
|
+
async #setupServer(): Promise<void> {
|
|
91
|
+
|
|
92
|
+
let registrationManager: RegistrationManager;
|
|
93
|
+
if (!this.dwn) {
|
|
94
|
+
// undefined registrationStoreUrl is used as a signal that there is no need for tenant registration, DWN is open for all.
|
|
95
|
+
registrationManager = await RegistrationManager.create({
|
|
96
|
+
registrationStoreUrl: this.config.registrationStoreUrl,
|
|
97
|
+
termsOfServiceFilePath: this.config.termsOfServiceFilePath,
|
|
98
|
+
proofOfWorkChallengeNonceSeed: this.config.registrationProofOfWorkSeed,
|
|
99
|
+
proofOfWorkInitialMaximumAllowedHash: this.config.registrationProofOfWorkInitialMaxHash,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
let eventStream: EventStream | undefined;
|
|
103
|
+
if (this.config.webSocketSupport) {
|
|
104
|
+
// If Even Stream plugin is not specified, use `EventEmitterStream` implementation as default.
|
|
105
|
+
if (this.config.eventStreamPluginPath === undefined || this.config.eventStreamPluginPath === '') {
|
|
106
|
+
eventStream = new EventEmitterStream();
|
|
107
|
+
} else {
|
|
108
|
+
eventStream = await PluginLoader.loadPlugin<EventStream>(this.config.eventStreamPluginPath);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const dwnConfig = await getDwnConfig(this.config, {
|
|
114
|
+
didResolver: this.didResolver,
|
|
115
|
+
tenantGate: registrationManager,
|
|
116
|
+
eventStream,
|
|
117
|
+
})
|
|
118
|
+
this.dwn = await Dwn.create(dwnConfig);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.#httpApi = await HttpApi.create(this.config, this.dwn, registrationManager);
|
|
122
|
+
|
|
123
|
+
await this.#httpApi.start(this.config.port);
|
|
124
|
+
log.info(`HttpServer listening on port ${this.config.port}`);
|
|
125
|
+
|
|
126
|
+
this.#httpServerShutdownHandler = new HttpServerShutdownHandler(
|
|
127
|
+
this.#httpApi.server,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (this.config.webSocketSupport) {
|
|
131
|
+
this.#wsApi = new WsApi(this.#httpApi.server, this.dwn);
|
|
132
|
+
this.#wsApi.start();
|
|
133
|
+
log.info('WebSocketServer ready...');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Stops the DWN server.
|
|
139
|
+
*/
|
|
140
|
+
async stop(): Promise<void> {
|
|
141
|
+
if (this.serverState === DwnServerState.Stopped) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
await this.dwn.close();
|
|
146
|
+
await this.#httpApi.close();
|
|
147
|
+
|
|
148
|
+
// close WebSocket server if it was initialized
|
|
149
|
+
if (this.#wsApi !== undefined) {
|
|
150
|
+
await this.#wsApi.close();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
await new Promise<void>((resolve) => {
|
|
154
|
+
this.#httpServerShutdownHandler.stop(() => {
|
|
155
|
+
resolve();
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
removeProcessHandlers(this.processHandlers);
|
|
160
|
+
|
|
161
|
+
this.serverState = DwnServerState.Stopped;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
get httpServer(): Server {
|
|
165
|
+
return this.#httpApi.server;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
get wsServer(): WebSocketServer | undefined {
|
|
169
|
+
return this.#wsApi?.server;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Gets the RegistrationManager for testing purposes.
|
|
174
|
+
*/
|
|
175
|
+
get registrationManager(): RegistrationManager {
|
|
176
|
+
return this.#httpApi.registrationManager;
|
|
177
|
+
}
|
|
178
|
+
}
|