@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
package/src/http-api.ts
ADDED
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
import type { RecordsReadReply } from '@enbox/dwn-sdk-js';
|
|
2
|
+
import { type Dwn, DateSort, RecordsRead, RecordsQuery, ProtocolsQuery } from '@enbox/dwn-sdk-js';
|
|
3
|
+
|
|
4
|
+
import cors from 'cors';
|
|
5
|
+
import type { Express, Request, Response } from 'express';
|
|
6
|
+
import express from 'express';
|
|
7
|
+
import { readFileSync } from 'fs';
|
|
8
|
+
import http from 'http';
|
|
9
|
+
import log from 'loglevel';
|
|
10
|
+
import { register } from 'prom-client';
|
|
11
|
+
import responseTime from 'response-time';
|
|
12
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
13
|
+
|
|
14
|
+
import type { RequestContext } from './lib/json-rpc-router.js';
|
|
15
|
+
import type { JsonRpcRequest } from './lib/json-rpc.js';
|
|
16
|
+
|
|
17
|
+
import type { DwnServerConfig } from './config.js';
|
|
18
|
+
import type { DwnServerError } from './dwn-error.js';
|
|
19
|
+
import type { RegistrationManager } from './registration/registration-manager.js';
|
|
20
|
+
import { config } from './config.js';
|
|
21
|
+
import { jsonRpcRouter } from './json-rpc-api.js';
|
|
22
|
+
import { Web5ConnectServer } from './web5-connect/web5-connect-server.js';
|
|
23
|
+
import { createJsonRpcErrorResponse, JsonRpcErrorCodes } from './lib/json-rpc.js';
|
|
24
|
+
import { requestCounter, responseHistogram } from './metrics.js';
|
|
25
|
+
import { Convert } from '@enbox/common';
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
export class HttpApi {
|
|
29
|
+
#config: DwnServerConfig;
|
|
30
|
+
#packageInfo: { version?: string, sdkVersion?: string, server: string };
|
|
31
|
+
#api: Express;
|
|
32
|
+
#server: http.Server;
|
|
33
|
+
web5ConnectServer: Web5ConnectServer;
|
|
34
|
+
registrationManager: RegistrationManager;
|
|
35
|
+
dwn: Dwn;
|
|
36
|
+
|
|
37
|
+
private constructor() { }
|
|
38
|
+
|
|
39
|
+
public static async create(config: DwnServerConfig, dwn: Dwn, registrationManager?: RegistrationManager): Promise<HttpApi> {
|
|
40
|
+
const httpApi = new HttpApi();
|
|
41
|
+
|
|
42
|
+
log.info(config);
|
|
43
|
+
|
|
44
|
+
httpApi.#packageInfo = {
|
|
45
|
+
server: config.serverName,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// We populate the `version` and `sdkVersion` properties from the `package.json` file.
|
|
50
|
+
const packageJson = JSON.parse(readFileSync(config.packageJsonPath).toString());
|
|
51
|
+
httpApi.#packageInfo.version = packageJson.version;
|
|
52
|
+
httpApi.#packageInfo.sdkVersion = packageJson.dependencies ? packageJson.dependencies['@enbox/dwn-sdk-js'] : undefined;
|
|
53
|
+
} catch (error: any) {
|
|
54
|
+
log.info('could not read `package.json` for version info', error);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
httpApi.#config = config;
|
|
58
|
+
httpApi.#api = express();
|
|
59
|
+
httpApi.#server = http.createServer(httpApi.#api);
|
|
60
|
+
httpApi.dwn = dwn;
|
|
61
|
+
|
|
62
|
+
if (registrationManager !== undefined) {
|
|
63
|
+
httpApi.registrationManager = registrationManager;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// create the Web5 Connect Server
|
|
67
|
+
httpApi.web5ConnectServer = await Web5ConnectServer.create({
|
|
68
|
+
baseUrl: config.baseUrl,
|
|
69
|
+
sqlTtlCacheUrl: config.ttlCacheUrl,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
httpApi.#setupMiddleware();
|
|
73
|
+
httpApi.#setupRoutes();
|
|
74
|
+
|
|
75
|
+
return httpApi;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
get server(): http.Server {
|
|
79
|
+
return this.#server;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
get api(): Express {
|
|
83
|
+
return this.#api;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#setupMiddleware(): void {
|
|
87
|
+
this.#api.use(cors({ exposedHeaders: 'dwn-response' }));
|
|
88
|
+
this.#api.use(express.json());
|
|
89
|
+
|
|
90
|
+
// We enable the formData middleware to handle multipart/form-data requests.
|
|
91
|
+
// This is necessary for the endpoints used by the Web5 Connect Server/OIDC flow.
|
|
92
|
+
this.#api.use(express.urlencoded({ extended: true }));
|
|
93
|
+
this.#api.use(
|
|
94
|
+
responseTime((req: Request, res: Response, time) => {
|
|
95
|
+
const url = req.url === '/' ? '/jsonrpc' : req.url;
|
|
96
|
+
const route = (req.method + url)
|
|
97
|
+
.toLowerCase()
|
|
98
|
+
.replace(/[:.]/g, '')
|
|
99
|
+
.replace(/\//g, '_');
|
|
100
|
+
|
|
101
|
+
const statusCode = res.statusCode.toString();
|
|
102
|
+
responseHistogram.labels(route, statusCode).observe(time);
|
|
103
|
+
log.info(req.method, decodeURI(req.url), res.statusCode);
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Configures the HTTP server's request handlers.
|
|
110
|
+
*/
|
|
111
|
+
#setupRoutes(): void {
|
|
112
|
+
|
|
113
|
+
const leadTailSlashRegex = /^\/|\/$/;
|
|
114
|
+
|
|
115
|
+
function readReplyHandler(res, reply: RecordsReadReply): any {
|
|
116
|
+
if (reply.status.code === 200) {
|
|
117
|
+
if (reply?.entry?.data) {
|
|
118
|
+
const stream = reply.entry.data;
|
|
119
|
+
|
|
120
|
+
res.setHeader('content-type', reply.entry.recordsWrite.descriptor.dataFormat);
|
|
121
|
+
res.setHeader('dwn-response', JSON.stringify(reply));
|
|
122
|
+
|
|
123
|
+
return stream.pipe(res);
|
|
124
|
+
} else {
|
|
125
|
+
return res.sendStatus(400);
|
|
126
|
+
}
|
|
127
|
+
} else if (reply.status.code === 401) {
|
|
128
|
+
return res.sendStatus(404);
|
|
129
|
+
} else {
|
|
130
|
+
return res.status(reply.status.code).send(reply);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.#api.get('/health', (_req, res) => {
|
|
135
|
+
// return 200 ok
|
|
136
|
+
return res.json({ ok: true });
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
this.#api.get('/metrics', async (req, res) => {
|
|
140
|
+
try {
|
|
141
|
+
res.set('Content-Type', register.contentType);
|
|
142
|
+
res.end(await register.metrics());
|
|
143
|
+
} catch (e) {
|
|
144
|
+
res.status(500).end(e);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Returns the data for the most recently published record under a given protocol path collection, if one is present
|
|
149
|
+
this.#api.get('/:did/read/protocols/:protocol/*', async (req, res) => {
|
|
150
|
+
if (!req.params[0]) {
|
|
151
|
+
return res.status(400).send('protocol path is required');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// wrap request in a try-catch block to handle any unexpected errors
|
|
155
|
+
try {
|
|
156
|
+
const queryOptions = { filter: {} } as any;
|
|
157
|
+
for (const param in req.query) {
|
|
158
|
+
const keys = param.split('.');
|
|
159
|
+
const lastKey = keys.pop();
|
|
160
|
+
const lastLevelObject = keys.reduce((obj, key) => obj[key] = obj[key] || {}, queryOptions)
|
|
161
|
+
lastLevelObject[lastKey] = req.query[param];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// the protocol path segment is base64url encoded, as the actual protocol is a URL
|
|
165
|
+
// we decode it here in order to filter for the correct protocol
|
|
166
|
+
const protocol = Convert.base64Url(req.params.protocol).toString()
|
|
167
|
+
queryOptions.filter.protocol = protocol;
|
|
168
|
+
queryOptions.filter.protocolPath = req.params[0].replace(leadTailSlashRegex, '');
|
|
169
|
+
|
|
170
|
+
const query = await RecordsQuery.create({
|
|
171
|
+
filter: queryOptions.filter,
|
|
172
|
+
pagination: { limit: 1 },
|
|
173
|
+
dateSort: DateSort.PublishedDescending
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const { entries, status } = await this.dwn.processMessage(req.params.did, query.message);
|
|
177
|
+
|
|
178
|
+
if (status.code === 200) {
|
|
179
|
+
if (entries[0]) {
|
|
180
|
+
const record = await RecordsRead.create({
|
|
181
|
+
filter: { recordId: entries[0].recordId },
|
|
182
|
+
});
|
|
183
|
+
const reply = await this.dwn.processMessage(req.params.did, record.toJSON());
|
|
184
|
+
return readReplyHandler(res, reply);
|
|
185
|
+
} else {
|
|
186
|
+
return res.sendStatus(404);
|
|
187
|
+
}
|
|
188
|
+
} else if (status.code === 401) {
|
|
189
|
+
return res.sendStatus(404);
|
|
190
|
+
} else {
|
|
191
|
+
return res.sendStatus(status.code);
|
|
192
|
+
}
|
|
193
|
+
} catch(error) {
|
|
194
|
+
log.error(`Error processing request: ${decodeURI(req.url)}`, error);
|
|
195
|
+
return res.sendStatus(400);
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
this.#api.get('/:did/read/protocols/:protocol', async (req, res) => {
|
|
200
|
+
// wrap request in a try-catch block to handle any unexpected errors
|
|
201
|
+
try {
|
|
202
|
+
|
|
203
|
+
// the protocol segment is base64url encoded, as the actual protocol is a URL
|
|
204
|
+
// we decode it here in order to filter for the correct protocol
|
|
205
|
+
const protocol = Convert.base64Url(req.params.protocol).toString()
|
|
206
|
+
const query = await ProtocolsQuery.create({
|
|
207
|
+
filter: { protocol }
|
|
208
|
+
});
|
|
209
|
+
const { entries, status } = await this.dwn.processMessage(req.params.did, query.message);
|
|
210
|
+
if (status.code === 200) {
|
|
211
|
+
if (entries.length) {
|
|
212
|
+
res.status(status.code);
|
|
213
|
+
res.json(entries[0]);
|
|
214
|
+
} else {
|
|
215
|
+
return res.sendStatus(404);
|
|
216
|
+
}
|
|
217
|
+
} else if (status.code === 401) {
|
|
218
|
+
return res.sendStatus(404);
|
|
219
|
+
} else {
|
|
220
|
+
return res.sendStatus(status.code);
|
|
221
|
+
}
|
|
222
|
+
} catch(error) {
|
|
223
|
+
log.error(`Error processing request: ${decodeURI(req.url)}`, error);
|
|
224
|
+
return res.sendStatus(400);
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
const recordsReadHandler = async (req, res): Promise<any> => {
|
|
229
|
+
const record = await RecordsRead.create({
|
|
230
|
+
filter: { recordId: req.params.id },
|
|
231
|
+
});
|
|
232
|
+
const reply = await this.dwn.processMessage(req.params.did, record.message);
|
|
233
|
+
return readReplyHandler(res, reply);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
this.#api.get('/:did/read/records/:id', recordsReadHandler);
|
|
237
|
+
this.#api.get('/:did/records/:id', recordsReadHandler);
|
|
238
|
+
|
|
239
|
+
this.#api.get('/:did/query/protocols', async (req, res) => {
|
|
240
|
+
const query = await ProtocolsQuery.create({});
|
|
241
|
+
const { entries, status } = await this.dwn.processMessage(req.params.did, query.message);
|
|
242
|
+
if (status.code === 200) {
|
|
243
|
+
res.status(status.code);
|
|
244
|
+
res.json(entries);
|
|
245
|
+
} else if (status.code === 401) {
|
|
246
|
+
return res.sendStatus(404);
|
|
247
|
+
} else {
|
|
248
|
+
return res.sendStatus(status.code);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
this.#api.get('/:did/query', async (req, res) => {
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
// builds a nested object from flat keys with dot notation which may share the same parent path
|
|
256
|
+
// e.g. "did:dht:123/query?filter.protocol=foo&filter.protocolPath=bar" becomes
|
|
257
|
+
// {
|
|
258
|
+
// filter: {
|
|
259
|
+
// protocol: 'foo',
|
|
260
|
+
// protocolPath: 'bar'
|
|
261
|
+
// }
|
|
262
|
+
// }
|
|
263
|
+
const recordsQueryOptions = {} as any;
|
|
264
|
+
for (const param in req.query) {
|
|
265
|
+
const keys = param.split('.');
|
|
266
|
+
const lastKey = keys.pop();
|
|
267
|
+
const lastLevelObject = keys.reduce((obj, key) => obj[key] = obj[key] || {}, recordsQueryOptions)
|
|
268
|
+
lastLevelObject[lastKey] = req.query[param];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const recordsQuery = await RecordsQuery.create({
|
|
272
|
+
filter: recordsQueryOptions.filter,
|
|
273
|
+
pagination: recordsQueryOptions.pagination,
|
|
274
|
+
dateSort: recordsQueryOptions.dateSort,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// should always return a 200 status code with a JSON response
|
|
278
|
+
const reply = await this.dwn.processMessage(req.params.did, recordsQuery.message);
|
|
279
|
+
|
|
280
|
+
res.setHeader('content-type', 'application/json');
|
|
281
|
+
return res.json(reply);
|
|
282
|
+
} catch (error) {
|
|
283
|
+
// error should only occur when we are unable to create the RecordsQuery message internally, making it a client error
|
|
284
|
+
return res.status(400).send(error);
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
this.#api.get('/', (_req, res) => {
|
|
289
|
+
// return a plain text string
|
|
290
|
+
res.setHeader('content-type', 'text/plain');
|
|
291
|
+
return res.send('please use a web5 client, for example: https://github.com/TBD54566975/web5-js ');
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
this.#api.post('/', async (req: Request, res) => {
|
|
295
|
+
const dwnRpcRequestString = req.headers['dwn-request'] as string;
|
|
296
|
+
|
|
297
|
+
if (!dwnRpcRequestString) {
|
|
298
|
+
const reply = createJsonRpcErrorResponse(uuidv4(), JsonRpcErrorCodes.BadRequest, 'request payload required.');
|
|
299
|
+
|
|
300
|
+
return res.status(400).json(reply);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
let dwnRpcRequest: JsonRpcRequest;
|
|
304
|
+
try {
|
|
305
|
+
dwnRpcRequest = JSON.parse(dwnRpcRequestString);
|
|
306
|
+
} catch (e) {
|
|
307
|
+
const reply = createJsonRpcErrorResponse(uuidv4(), JsonRpcErrorCodes.BadRequest, e.message);
|
|
308
|
+
|
|
309
|
+
return res.status(400).json(reply);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Check whether data was provided in the request body
|
|
313
|
+
const contentLength = req.headers['content-length'];
|
|
314
|
+
const transferEncoding = req.headers['transfer-encoding'];
|
|
315
|
+
const requestDataStream = parseInt(contentLength) > 0 || transferEncoding !== undefined ? req : undefined;
|
|
316
|
+
|
|
317
|
+
const requestContext: RequestContext = {
|
|
318
|
+
dwn : this.dwn,
|
|
319
|
+
transport : 'http',
|
|
320
|
+
dataStream : requestDataStream,
|
|
321
|
+
};
|
|
322
|
+
const { jsonRpcResponse, dataStream: responseDataStream } = await jsonRpcRouter.handle(dwnRpcRequest, requestContext as RequestContext);
|
|
323
|
+
|
|
324
|
+
// If the handler catches a thrown exception and returns a JSON RPC InternalError, return the equivalent
|
|
325
|
+
// HTTP 500 Internal Server Error with the response.
|
|
326
|
+
if (jsonRpcResponse.error) {
|
|
327
|
+
requestCounter.inc({ method: dwnRpcRequest.method, error: 1 });
|
|
328
|
+
return res.status(500).json(jsonRpcResponse);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
requestCounter.inc({
|
|
332
|
+
method : dwnRpcRequest.method,
|
|
333
|
+
status : jsonRpcResponse?.result?.reply?.status?.code || 0,
|
|
334
|
+
});
|
|
335
|
+
if (responseDataStream) {
|
|
336
|
+
res.setHeader('content-type', 'application/octet-stream');
|
|
337
|
+
res.setHeader('dwn-response', JSON.stringify(jsonRpcResponse));
|
|
338
|
+
|
|
339
|
+
return responseDataStream.pipe(res);
|
|
340
|
+
} else {
|
|
341
|
+
return res.json(jsonRpcResponse);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
this.#setupRegistrationRoutes();
|
|
346
|
+
|
|
347
|
+
this.#api.get('/info', (req, res) => {
|
|
348
|
+
res.setHeader('content-type', 'application/json');
|
|
349
|
+
const registrationRequirements: string[] = [];
|
|
350
|
+
if (config.registrationProofOfWorkEnabled) {
|
|
351
|
+
registrationRequirements.push('proof-of-work-sha256-v0');
|
|
352
|
+
}
|
|
353
|
+
if (config.termsOfServiceFilePath !== undefined) {
|
|
354
|
+
registrationRequirements.push('terms-of-service');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
res.json({
|
|
358
|
+
url : config.baseUrl,
|
|
359
|
+
server : this.#packageInfo.server,
|
|
360
|
+
maxFileSize : config.maxRecordDataSize,
|
|
361
|
+
registrationRequirements : registrationRequirements,
|
|
362
|
+
version : this.#packageInfo.version,
|
|
363
|
+
sdkVersion : this.#packageInfo.sdkVersion,
|
|
364
|
+
webSocketSupport : config.webSocketSupport,
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
this.#setupWeb5ConnectServerRoutes();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
#setupRegistrationRoutes(): void {
|
|
372
|
+
if (this.#config.registrationProofOfWorkEnabled) {
|
|
373
|
+
this.#api.get('/registration/proof-of-work', async (_req: Request, res: Response) => {
|
|
374
|
+
const proofOfWorkChallenge = this.registrationManager.getProofOfWorkChallenge();
|
|
375
|
+
res.json(proofOfWorkChallenge);
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (this.#config.termsOfServiceFilePath !== undefined) {
|
|
380
|
+
this.#api.get('/registration/terms-of-service', (_req: Request, res: Response) => res.send(this.registrationManager.getTermsOfService()));
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (this.#config.registrationStoreUrl !== undefined) {
|
|
384
|
+
this.#api.post('/registration', async (req: Request, res: Response) => {
|
|
385
|
+
const requestBody = req.body;
|
|
386
|
+
log.info('Registration request:', requestBody);
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
await this.registrationManager.handleRegistrationRequest(requestBody);
|
|
390
|
+
res.status(200).json({ success: true });
|
|
391
|
+
} catch (error) {
|
|
392
|
+
const dwnServerError = error as DwnServerError;
|
|
393
|
+
|
|
394
|
+
if (dwnServerError.code !== undefined) {
|
|
395
|
+
res.status(400).json(dwnServerError);
|
|
396
|
+
} else {
|
|
397
|
+
log.info('Error handling registration request:', error);
|
|
398
|
+
res.status(500).json({ success: false });
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
#setupWeb5ConnectServerRoutes(): void {
|
|
406
|
+
/**
|
|
407
|
+
* Endpoint allows a Client app (RP) to submit an Authorization Request.
|
|
408
|
+
* The Authorization Request is stored on the server, and a unique `request_uri` is returned to the Client app.
|
|
409
|
+
* The Client app can then provide this `request_uri` to the Provider app (wallet).
|
|
410
|
+
* The Provider app uses the `request_uri` to retrieve the stored Authorization Request.
|
|
411
|
+
*/
|
|
412
|
+
this.#api.post('/connect/par', async (req, res) => {
|
|
413
|
+
log.info('Storing Pushed Authorization Request (PAR) request...');
|
|
414
|
+
|
|
415
|
+
// TODO: Add validation for request too large HTTP 413: https://github.com/TBD54566975/dwn-server/issues/146
|
|
416
|
+
// TODO: Add validation for too many requests HTTP 429: https://github.com/TBD54566975/dwn-server/issues/147
|
|
417
|
+
|
|
418
|
+
if (!req.body.request) {
|
|
419
|
+
return res.status(400).json({
|
|
420
|
+
ok: false,
|
|
421
|
+
status: {
|
|
422
|
+
code: 400,
|
|
423
|
+
message: "Bad Request: Missing 'request' parameter",
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Validate that `request_uri` was NOT provided
|
|
429
|
+
if (req.body?.request?.request_uri) {
|
|
430
|
+
return res.status(400).json({
|
|
431
|
+
ok: false,
|
|
432
|
+
status: {
|
|
433
|
+
code: 400,
|
|
434
|
+
message: "Bad Request: 'request_uri' parameter is not allowed in PAR",
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const result = await this.web5ConnectServer.setWeb5ConnectRequest(req.body.request);
|
|
440
|
+
res.status(201).json(result);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Endpoint for the Provider to retrieve the Authorization Request from the request_uri
|
|
445
|
+
*/
|
|
446
|
+
this.#api.get('/connect/authorize/:requestId.jwt', async (req, res) => {
|
|
447
|
+
log.info(`Retrieving Web5 Connect Request object of ID: ${req.params.requestId}...`);
|
|
448
|
+
|
|
449
|
+
// Look up the request object based on the requestId.
|
|
450
|
+
const requestObjectJwt = await this.web5ConnectServer.getWeb5ConnectRequest(req.params.requestId);
|
|
451
|
+
|
|
452
|
+
if (!requestObjectJwt) {
|
|
453
|
+
res.status(404).json({
|
|
454
|
+
ok : false,
|
|
455
|
+
status : { code: 404, message: 'Not Found' }
|
|
456
|
+
});
|
|
457
|
+
} else {
|
|
458
|
+
res.set('Content-Type', 'application/jwt');
|
|
459
|
+
res.send(requestObjectJwt);
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Endpoint that the Provider sends the Authorization Response to
|
|
465
|
+
*/
|
|
466
|
+
this.#api.post('/connect/callback', async (req, res) => {
|
|
467
|
+
log.info('Storing Identity Provider (wallet) pushed response with ID token...');
|
|
468
|
+
|
|
469
|
+
// Store the ID token.
|
|
470
|
+
const idToken = req.body.id_token;
|
|
471
|
+
const state = req.body.state;
|
|
472
|
+
|
|
473
|
+
if (idToken !== undefined && state != undefined) {
|
|
474
|
+
|
|
475
|
+
await this.web5ConnectServer.setWeb5ConnectResponse(state, idToken);
|
|
476
|
+
|
|
477
|
+
res.status(201).json({
|
|
478
|
+
ok : true,
|
|
479
|
+
status : { code: 201, message: 'Created' }
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
} else {
|
|
483
|
+
res.status(400).json({
|
|
484
|
+
ok : false,
|
|
485
|
+
status : { code: 400, message: 'Bad Request' }
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Endpoint for the connecting Client to retrieve the Authorization Response
|
|
492
|
+
*/
|
|
493
|
+
this.#api.get('/connect/token/:state.jwt', async (req, res) => {
|
|
494
|
+
log.info(`Retrieving ID token for state: ${req.params.state}...`);
|
|
495
|
+
|
|
496
|
+
// Look up the ID token.
|
|
497
|
+
const idToken = await this.web5ConnectServer.getWeb5ConnectResponse(req.params.state);
|
|
498
|
+
|
|
499
|
+
if (!idToken) {
|
|
500
|
+
res.status(404).json({
|
|
501
|
+
ok : false,
|
|
502
|
+
status : { code: 404, message: 'Not Found' }
|
|
503
|
+
});
|
|
504
|
+
} else {
|
|
505
|
+
res.set('Content-Type', 'application/jwt');
|
|
506
|
+
res.send(idToken);
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Starts the HTTP API endpoint on the given port.
|
|
513
|
+
* @returns The HTTP server instance.
|
|
514
|
+
*/
|
|
515
|
+
async start(port: number): Promise<void> {
|
|
516
|
+
// promisify http.Server.listen() and await on it
|
|
517
|
+
await new Promise<void>((resolve) => {
|
|
518
|
+
this.#server.listen(port, () => {
|
|
519
|
+
resolve();
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Stops the HTTP API endpoint.
|
|
526
|
+
*/
|
|
527
|
+
async close(): Promise<void> {
|
|
528
|
+
// promisify http.Server.close() and await on it
|
|
529
|
+
await new Promise<void>((resolve, reject) => {
|
|
530
|
+
this.#server.close((err?: Error) => {
|
|
531
|
+
if (err) {
|
|
532
|
+
reject(err);
|
|
533
|
+
} else {
|
|
534
|
+
resolve();
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
this.server.closeAllConnections();
|
|
540
|
+
}
|
|
541
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { DwnServerConfig } from './config.js';
|
|
2
|
+
export { DwnServer, DwnServerOptions } from './dwn-server.js';
|
|
3
|
+
export { HttpApi } from './http-api.js';
|
|
4
|
+
export { jsonRpcRouter } from './json-rpc-api.js';
|
|
5
|
+
export { StoreType, BackendTypes, DwnStore } from './storage.js';
|
|
6
|
+
export { WsApi } from './ws-api.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { JsonRpcRouter } from './lib/json-rpc-router.js';
|
|
2
|
+
|
|
3
|
+
import { handleDwnProcessMessage } from './json-rpc-handlers/dwn/index.js';
|
|
4
|
+
import { handleSubscriptionsClose } from './json-rpc-handlers/subscription/index.js';
|
|
5
|
+
|
|
6
|
+
export const jsonRpcRouter = new JsonRpcRouter();
|
|
7
|
+
|
|
8
|
+
jsonRpcRouter.on('dwn.processMessage', handleDwnProcessMessage);
|
|
9
|
+
jsonRpcRouter.on('rpc.subscribe.dwn.processMessage', handleDwnProcessMessage);
|
|
10
|
+
|
|
11
|
+
jsonRpcRouter.on('rpc.subscribe.close', handleSubscriptionsClose);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './process-message.js';
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { GenericMessage } from '@enbox/dwn-sdk-js';
|
|
2
|
+
import { DwnInterfaceName, DwnMethodName } from '@enbox/dwn-sdk-js';
|
|
3
|
+
|
|
4
|
+
import type { Readable as IsomorphicReadable } from 'readable-stream';
|
|
5
|
+
import log from 'loglevel';
|
|
6
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
7
|
+
|
|
8
|
+
import type { JsonRpcSubscription } from '../../lib/json-rpc.js';
|
|
9
|
+
import type {
|
|
10
|
+
HandlerResponse,
|
|
11
|
+
JsonRpcHandler,
|
|
12
|
+
} from '../../lib/json-rpc-router.js';
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
createJsonRpcErrorResponse,
|
|
16
|
+
createJsonRpcSuccessResponse,
|
|
17
|
+
JsonRpcErrorCodes,
|
|
18
|
+
} from '../../lib/json-rpc.js';
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
export const handleDwnProcessMessage: JsonRpcHandler = async (
|
|
22
|
+
dwnRequest,
|
|
23
|
+
context,
|
|
24
|
+
) => {
|
|
25
|
+
const { dwn, dataStream, subscriptionRequest, socketConnection, transport } = context;
|
|
26
|
+
const { target, message } = dwnRequest.params as { target: string, message: GenericMessage };
|
|
27
|
+
const requestId = dwnRequest.id ?? uuidv4();
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// RecordsWrite is only supported on 'http' to support data stream for large data
|
|
31
|
+
// TODO: https://github.com/TBD54566975/dwn-server/issues/108
|
|
32
|
+
if (
|
|
33
|
+
transport !== 'http' &&
|
|
34
|
+
message.descriptor.interface === DwnInterfaceName.Records &&
|
|
35
|
+
message.descriptor.method === DwnMethodName.Write
|
|
36
|
+
) {
|
|
37
|
+
const jsonRpcResponse = createJsonRpcErrorResponse(
|
|
38
|
+
requestId,
|
|
39
|
+
JsonRpcErrorCodes.InvalidParams,
|
|
40
|
+
`RecordsWrite is not supported via ${context.transport}`
|
|
41
|
+
)
|
|
42
|
+
return { jsonRpcResponse };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// subscribe methods must come with a subscriptionRequest context
|
|
46
|
+
if (message.descriptor.method === DwnMethodName.Subscribe && subscriptionRequest === undefined) {
|
|
47
|
+
const jsonRpcResponse = createJsonRpcErrorResponse(
|
|
48
|
+
requestId,
|
|
49
|
+
JsonRpcErrorCodes.InvalidRequest,
|
|
50
|
+
`subscribe methods must contain a subscriptionRequest context`
|
|
51
|
+
);
|
|
52
|
+
return { jsonRpcResponse };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Subscribe methods are only supported on 'ws' (WebSockets)
|
|
56
|
+
if (transport !== 'ws' && subscriptionRequest !== undefined) {
|
|
57
|
+
const jsonRpcResponse = createJsonRpcErrorResponse(
|
|
58
|
+
requestId,
|
|
59
|
+
JsonRpcErrorCodes.InvalidParams,
|
|
60
|
+
`subscriptions are not supported via ${context.transport}`
|
|
61
|
+
)
|
|
62
|
+
return { jsonRpcResponse };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// if this is a subscription request, we first check if the connection has a subscription with this Id
|
|
66
|
+
// we do this ahead of time to prevent opening a subscription on the dwn only to close it after attempting to add it to the subscription manager
|
|
67
|
+
// otherwise the subscription manager would throw an error that the Id is already in use and we would close the open subscription on the DWN.
|
|
68
|
+
if (subscriptionRequest !== undefined && socketConnection?.hasSubscription(subscriptionRequest.id)) {
|
|
69
|
+
const jsonRpcResponse = createJsonRpcErrorResponse(
|
|
70
|
+
requestId,
|
|
71
|
+
JsonRpcErrorCodes.InvalidParams,
|
|
72
|
+
`the subscribe id: ${subscriptionRequest.id} is in use by an active subscription`
|
|
73
|
+
)
|
|
74
|
+
return { jsonRpcResponse };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const reply = await dwn.processMessage(target, message, {
|
|
78
|
+
dataStream: dataStream as IsomorphicReadable,
|
|
79
|
+
subscriptionHandler: subscriptionRequest?.subscriptionHandler,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
const { entry } = reply;
|
|
84
|
+
// RecordsRead or MessagesRead messages optionally return data as a stream to accommodate large amounts of data
|
|
85
|
+
// we remove the data stream from the reply that will be serialized and return it as a separate property in the response payload.
|
|
86
|
+
let recordDataStream: IsomorphicReadable;
|
|
87
|
+
if (entry !== undefined && entry.data !== undefined) {
|
|
88
|
+
recordDataStream = entry.data;
|
|
89
|
+
delete reply.entry.data; // not serializable via JSON
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (subscriptionRequest && reply.subscription) {
|
|
93
|
+
const { close } = reply.subscription;
|
|
94
|
+
// Subscribe messages return a close function to facilitate closing the subscription
|
|
95
|
+
// we add a reference to the close function for this subscription request to the socket connection.
|
|
96
|
+
// this will facilitate closing the subscription later.
|
|
97
|
+
const subscriptionReply: JsonRpcSubscription = {
|
|
98
|
+
id: subscriptionRequest.id,
|
|
99
|
+
close,
|
|
100
|
+
}
|
|
101
|
+
await socketConnection.addSubscription(subscriptionReply);
|
|
102
|
+
delete reply.subscription.close // delete the close method from the reply as it's not JSON serializable and has a held reference.
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const jsonRpcResponse = createJsonRpcSuccessResponse(requestId, { reply });
|
|
106
|
+
const responsePayload: HandlerResponse = { jsonRpcResponse };
|
|
107
|
+
if (recordDataStream) {
|
|
108
|
+
responsePayload.dataStream = recordDataStream;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return responsePayload;
|
|
112
|
+
} catch (error) {
|
|
113
|
+
const jsonRpcResponse = createJsonRpcErrorResponse(
|
|
114
|
+
requestId,
|
|
115
|
+
JsonRpcErrorCodes.InternalError,
|
|
116
|
+
error.message,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// log the unhandled error response
|
|
120
|
+
log.error('handleDwnProcessMessage error', jsonRpcResponse, dwnRequest, error);
|
|
121
|
+
return { jsonRpcResponse } as HandlerResponse;
|
|
122
|
+
}
|
|
123
|
+
};
|