@enbox/dwn-server 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -13
- package/dist/esm/src/config.d.ts +2 -6
- package/dist/esm/src/config.d.ts.map +1 -1
- package/dist/esm/src/config.js +4 -8
- package/dist/esm/src/config.js.map +1 -1
- package/dist/esm/src/connection/connection-manager.d.ts +9 -9
- package/dist/esm/src/connection/connection-manager.d.ts.map +1 -1
- package/dist/esm/src/connection/connection-manager.js +5 -3
- package/dist/esm/src/connection/connection-manager.js.map +1 -1
- package/dist/esm/src/connection/socket-connection.d.ts +18 -17
- package/dist/esm/src/connection/socket-connection.d.ts.map +1 -1
- package/dist/esm/src/connection/socket-connection.js +26 -35
- package/dist/esm/src/connection/socket-connection.js.map +1 -1
- package/dist/esm/src/dwn-error.js.map +1 -1
- package/dist/esm/src/dwn-server.d.ts +5 -6
- package/dist/esm/src/dwn-server.d.ts.map +1 -1
- package/dist/esm/src/dwn-server.js +3 -14
- package/dist/esm/src/dwn-server.js.map +1 -1
- package/dist/esm/src/http-api.d.ts +9 -11
- package/dist/esm/src/http-api.d.ts.map +1 -1
- package/dist/esm/src/http-api.js +450 -375
- package/dist/esm/src/http-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 +3 -3
- package/dist/esm/src/json-rpc-handlers/dwn/process-message.js.map +1 -1
- package/dist/esm/src/json-rpc-handlers/subscription/close.d.ts.map +1 -1
- package/dist/esm/src/json-rpc-handlers/subscription/close.js.map +1 -1
- package/dist/esm/src/json-rpc-socket.d.ts +1 -1
- package/dist/esm/src/json-rpc-socket.d.ts.map +1 -1
- package/dist/esm/src/json-rpc-socket.js +1 -1
- package/dist/esm/src/json-rpc-socket.js.map +1 -1
- package/dist/esm/src/lib/json-rpc-router.d.ts +3 -4
- 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/json-rpc.d.ts.map +1 -1
- package/dist/esm/src/lib/json-rpc.js.map +1 -1
- package/dist/esm/src/main.js +0 -0
- package/dist/esm/src/metrics.d.ts +1 -1
- package/dist/esm/src/metrics.js.map +1 -1
- 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/registration-manager.d.ts +3 -3
- package/dist/esm/src/registration/registration-manager.d.ts.map +1 -1
- package/dist/esm/src/registration/registration-manager.js +6 -6
- package/dist/esm/src/registration/registration-manager.js.map +1 -1
- package/dist/esm/src/registration/registration-store.d.ts +1 -1
- package/dist/esm/src/registration/registration-store.d.ts.map +1 -1
- package/dist/esm/src/registration/registration-store.js.map +1 -1
- package/dist/esm/src/storage.d.ts +2 -2
- package/dist/esm/src/storage.d.ts.map +1 -1
- package/dist/esm/src/storage.js +5 -4
- 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 +3 -2
- 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 +2 -4
- package/dist/esm/src/ws-api.d.ts.map +1 -1
- package/dist/esm/src/ws-api.js +6 -17
- package/dist/esm/src/ws-api.js.map +1 -1
- package/dist/esm/tests/common-scenario-validator.d.ts.map +1 -1
- package/dist/esm/tests/common-scenario-validator.js +2 -3
- package/dist/esm/tests/common-scenario-validator.js.map +1 -1
- package/dist/esm/tests/connection/connection-manager.spec.js +11 -9
- package/dist/esm/tests/connection/connection-manager.spec.js.map +1 -1
- package/dist/esm/tests/connection/socket-connection.spec.js +40 -18
- package/dist/esm/tests/connection/socket-connection.spec.js.map +1 -1
- package/dist/esm/tests/cors/http-api.browser.js +1 -1
- package/dist/esm/tests/cors/http-api.browser.js.map +1 -1
- package/dist/esm/tests/dwn-process-message.spec.js +4 -4
- package/dist/esm/tests/dwn-process-message.spec.js.map +1 -1
- package/dist/esm/tests/dwn-server.spec.js +8 -9
- package/dist/esm/tests/dwn-server.spec.js.map +1 -1
- package/dist/esm/tests/http-api.spec.js +92 -85
- package/dist/esm/tests/http-api.spec.js.map +1 -1
- package/dist/esm/tests/json-rpc-socket.spec.js +11 -9
- package/dist/esm/tests/json-rpc-socket.spec.js.map +1 -1
- package/dist/esm/tests/plugins/data-store-sqlite.d.ts +2 -2
- package/dist/esm/tests/plugins/data-store-sqlite.js +2 -2
- package/dist/esm/tests/plugins/event-log-sqlite.d.ts +2 -2
- package/dist/esm/tests/plugins/event-log-sqlite.js +2 -2
- package/dist/esm/tests/plugins/event-stream-in-memory.d.ts +2 -2
- package/dist/esm/tests/plugins/event-stream-in-memory.d.ts.map +1 -1
- package/dist/esm/tests/plugins/event-stream-in-memory.js +1 -1
- package/dist/esm/tests/plugins/event-stream-in-memory.js.map +1 -1
- package/dist/esm/tests/plugins/message-store-sqlite.d.ts +2 -2
- package/dist/esm/tests/plugins/message-store-sqlite.d.ts.map +1 -1
- package/dist/esm/tests/plugins/message-store-sqlite.js +2 -2
- package/dist/esm/tests/plugins/message-store-sqlite.js.map +1 -1
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts +2 -2
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts.map +1 -1
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.js +2 -2
- package/dist/esm/tests/plugins/resumable-task-store-sqlite.js.map +1 -1
- package/dist/esm/tests/process-handler.spec.js +6 -6
- package/dist/esm/tests/process-handler.spec.js.map +1 -1
- package/dist/esm/tests/registration/proof-of-work-manager.spec.js +3 -4
- package/dist/esm/tests/registration/proof-of-work-manager.spec.js.map +1 -1
- package/dist/esm/tests/rpc-subscribe-close.spec.js +1 -1
- package/dist/esm/tests/rpc-subscribe-close.spec.js.map +1 -1
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js +11 -10
- package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js.map +1 -1
- package/dist/esm/tests/scenarios/registration.spec.js +16 -12
- package/dist/esm/tests/scenarios/registration.spec.js.map +1 -1
- package/dist/esm/tests/scenarios/web5-connect.spec.js +12 -8
- package/dist/esm/tests/scenarios/web5-connect.spec.js.map +1 -1
- package/dist/esm/tests/test-dwn.d.ts.map +1 -1
- package/dist/esm/tests/test-dwn.js +9 -15
- package/dist/esm/tests/test-dwn.js.map +1 -1
- package/dist/esm/tests/utils.d.ts +3 -6
- package/dist/esm/tests/utils.d.ts.map +1 -1
- package/dist/esm/tests/utils.js +9 -18
- package/dist/esm/tests/utils.js.map +1 -1
- package/dist/esm/tests/ws-api.spec.js +28 -23
- package/dist/esm/tests/ws-api.spec.js.map +1 -1
- package/package.json +25 -44
- package/src/config.ts +15 -19
- package/src/connection/connection-manager.ts +18 -12
- package/src/connection/socket-connection.ts +52 -57
- package/src/dwn-error.ts +2 -2
- package/src/dwn-server.ts +17 -30
- package/src/http-api.ts +499 -396
- package/src/json-rpc-handlers/dwn/process-message.ts +9 -10
- package/src/json-rpc-handlers/subscription/close.ts +4 -4
- package/src/json-rpc-socket.ts +3 -2
- package/src/lib/json-rpc-router.ts +5 -6
- package/src/lib/json-rpc.ts +6 -6
- package/src/metrics.ts +7 -7
- package/src/process-handlers.ts +5 -5
- package/src/registration/proof-of-work-manager.ts +11 -10
- package/src/registration/registration-manager.ts +23 -21
- package/src/registration/registration-store.ts +8 -7
- package/src/storage.ts +15 -13
- package/src/web5-connect/sql-ttl-cache.ts +5 -4
- package/src/web5-connect/web5-connect-server.ts +9 -8
- package/src/ws-api.ts +11 -26
- package/dist/cjs/index.js +0 -6811
- package/dist/cjs/package.json +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/src/lib/http-server-shutdown-handler.ts +0 -79
package/src/http-api.ts
CHANGED
|
@@ -1,42 +1,49 @@
|
|
|
1
1
|
import type { RecordsReadReply } from '@enbox/dwn-sdk-js';
|
|
2
|
-
import
|
|
2
|
+
import type { Server, ServerWebSocket } from 'bun';
|
|
3
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
4
|
import log from 'loglevel';
|
|
5
|
+
|
|
6
|
+
import { Convert } from '@enbox/common';
|
|
7
|
+
import { readFileSync } from 'fs';
|
|
10
8
|
import { register } from 'prom-client';
|
|
11
|
-
import responseTime from 'response-time';
|
|
12
9
|
import { v4 as uuidv4 } from 'uuid';
|
|
10
|
+
import { DataStream, DateSort, type Dwn, ProtocolsQuery, RecordsQuery, RecordsRead } from '@enbox/dwn-sdk-js';
|
|
13
11
|
|
|
14
|
-
import type { RequestContext } from './lib/json-rpc-router.js';
|
|
15
|
-
import type { JsonRpcRequest } from './lib/json-rpc.js';
|
|
16
12
|
|
|
17
13
|
import type { DwnServerConfig } from './config.js';
|
|
18
14
|
import type { DwnServerError } from './dwn-error.js';
|
|
15
|
+
import type { JsonRpcRequest } from './lib/json-rpc.js';
|
|
19
16
|
import type { RegistrationManager } from './registration/registration-manager.js';
|
|
17
|
+
import type { RequestContext } from './lib/json-rpc-router.js';
|
|
18
|
+
import type { SocketConnection } from './connection/socket-connection.js';
|
|
19
|
+
|
|
20
20
|
import { config } from './config.js';
|
|
21
21
|
import { jsonRpcRouter } from './json-rpc-api.js';
|
|
22
22
|
import { Web5ConnectServer } from './web5-connect/web5-connect-server.js';
|
|
23
23
|
import { createJsonRpcErrorResponse, JsonRpcErrorCodes } from './lib/json-rpc.js';
|
|
24
24
|
import { requestCounter, responseHistogram } from './metrics.js';
|
|
25
|
-
import { Convert } from '@enbox/common';
|
|
26
25
|
|
|
26
|
+
/** Data attached to each Bun WebSocket via `ws.data`. */
|
|
27
|
+
export interface WsData {
|
|
28
|
+
connection: SocketConnection;
|
|
29
|
+
}
|
|
27
30
|
|
|
28
31
|
export class HttpApi {
|
|
29
32
|
#config: DwnServerConfig;
|
|
30
33
|
#packageInfo: { version?: string, sdkVersion?: string, server: string };
|
|
31
|
-
#
|
|
32
|
-
#server: http.Server;
|
|
34
|
+
#server!: Server<WsData>;
|
|
33
35
|
web5ConnectServer: Web5ConnectServer;
|
|
34
36
|
registrationManager: RegistrationManager;
|
|
35
37
|
dwn: Dwn;
|
|
36
38
|
|
|
39
|
+
/** Called by WsApi/ConnectionManager when a new WS connection is established. */
|
|
40
|
+
onWebSocketConnection?: (ws: ServerWebSocket<WsData>) => void;
|
|
41
|
+
|
|
37
42
|
private constructor() { }
|
|
38
43
|
|
|
39
|
-
public static async create(
|
|
44
|
+
public static async create(
|
|
45
|
+
config: DwnServerConfig, dwn: Dwn, registrationManager?: RegistrationManager
|
|
46
|
+
): Promise<HttpApi> {
|
|
40
47
|
const httpApi = new HttpApi();
|
|
41
48
|
|
|
42
49
|
log.info(config);
|
|
@@ -44,498 +51,594 @@ export class HttpApi {
|
|
|
44
51
|
httpApi.#packageInfo = {
|
|
45
52
|
server: config.serverName,
|
|
46
53
|
};
|
|
47
|
-
|
|
54
|
+
|
|
48
55
|
try {
|
|
49
|
-
// We populate the `version` and `sdkVersion` properties from the `package.json` file.
|
|
50
56
|
const packageJson = JSON.parse(readFileSync(config.packageJsonPath).toString());
|
|
51
57
|
httpApi.#packageInfo.version = packageJson.version;
|
|
52
|
-
httpApi.#packageInfo.sdkVersion = packageJson.dependencies
|
|
58
|
+
httpApi.#packageInfo.sdkVersion = packageJson.dependencies
|
|
59
|
+
? packageJson.dependencies['@enbox/dwn-sdk-js']
|
|
60
|
+
: undefined;
|
|
53
61
|
} catch (error: any) {
|
|
54
62
|
log.info('could not read `package.json` for version info', error);
|
|
55
63
|
}
|
|
56
64
|
|
|
57
65
|
httpApi.#config = config;
|
|
58
|
-
httpApi.#api = express();
|
|
59
|
-
httpApi.#server = http.createServer(httpApi.#api);
|
|
60
66
|
httpApi.dwn = dwn;
|
|
61
67
|
|
|
62
68
|
if (registrationManager !== undefined) {
|
|
63
69
|
httpApi.registrationManager = registrationManager;
|
|
64
70
|
}
|
|
65
71
|
|
|
66
|
-
// create the Web5 Connect Server
|
|
67
72
|
httpApi.web5ConnectServer = await Web5ConnectServer.create({
|
|
68
|
-
baseUrl: config.baseUrl,
|
|
69
|
-
sqlTtlCacheUrl: config.ttlCacheUrl,
|
|
73
|
+
baseUrl : config.baseUrl,
|
|
74
|
+
sqlTtlCacheUrl : config.ttlCacheUrl,
|
|
70
75
|
});
|
|
71
76
|
|
|
72
|
-
httpApi.#setupMiddleware();
|
|
73
|
-
httpApi.#setupRoutes();
|
|
74
|
-
|
|
75
77
|
return httpApi;
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
get server():
|
|
80
|
+
get server(): Server<WsData> {
|
|
79
81
|
return this.#server;
|
|
80
82
|
}
|
|
81
83
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// HTTP request handler
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
async start(port: number): Promise<void> {
|
|
89
|
+
const self = this; // capture for closures
|
|
90
|
+
|
|
91
|
+
this.#server = Bun.serve<WsData>({
|
|
92
|
+
port,
|
|
93
|
+
|
|
94
|
+
async fetch(req: Request, server): Promise<Response | undefined> {
|
|
95
|
+
const startTime = performance.now();
|
|
96
|
+
const url = new URL(req.url);
|
|
97
|
+
const path = url.pathname;
|
|
98
|
+
const method = req.method;
|
|
99
|
+
|
|
100
|
+
// --- WebSocket upgrade ---
|
|
101
|
+
if (method === 'GET' && req.headers.get('upgrade') === 'websocket') {
|
|
102
|
+
const upgraded = server.upgrade(req, { data: { connection: null } });
|
|
103
|
+
if (upgraded) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
return new Response('WebSocket upgrade failed', { status: 400 });
|
|
107
|
+
}
|
|
85
108
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
109
|
+
// --- Route matching ---
|
|
110
|
+
let response: Response;
|
|
111
|
+
try {
|
|
112
|
+
response = await self.#route(req, url, path, method);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
log.error(`Unhandled error on ${method} ${path}:`, error);
|
|
115
|
+
response = new Response('Internal Server Error', { status: 500 });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// --- CORS headers ---
|
|
119
|
+
response.headers.set('access-control-allow-origin', '*');
|
|
120
|
+
response.headers.set('access-control-allow-methods', 'GET, POST, OPTIONS');
|
|
121
|
+
response.headers.set('access-control-allow-headers', '*');
|
|
122
|
+
response.headers.set('access-control-expose-headers', 'dwn-response');
|
|
123
|
+
|
|
124
|
+
// --- Response-time metrics ---
|
|
125
|
+
const elapsed = performance.now() - startTime;
|
|
126
|
+
const routeLabel = (method + (path === '/' ? '/jsonrpc' : path))
|
|
97
127
|
.toLowerCase()
|
|
98
128
|
.replace(/[:.]/g, '')
|
|
99
129
|
.replace(/\//g, '_');
|
|
130
|
+
responseHistogram.labels(routeLabel, String(response.status)).observe(elapsed);
|
|
131
|
+
log.info(method, decodeURI(path), response.status);
|
|
100
132
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
log.info(req.method, decodeURI(req.url), res.statusCode);
|
|
104
|
-
}),
|
|
105
|
-
);
|
|
106
|
-
}
|
|
133
|
+
return response;
|
|
134
|
+
},
|
|
107
135
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
136
|
+
websocket: {
|
|
137
|
+
open(ws: ServerWebSocket<WsData>): void {
|
|
138
|
+
if (self.onWebSocketConnection) {
|
|
139
|
+
self.onWebSocketConnection(ws);
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
message(ws: ServerWebSocket<WsData>, msg: string | Buffer): void {
|
|
143
|
+
const connection = ws.data?.connection;
|
|
144
|
+
if (connection) {
|
|
145
|
+
connection.message(typeof msg === 'string' ? Buffer.from(msg) : msg as Buffer);
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
close(ws: ServerWebSocket<WsData>): void {
|
|
149
|
+
const connection = ws.data?.connection;
|
|
150
|
+
if (connection) {
|
|
151
|
+
connection.close();
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
// Bun automatically responds to pings with pongs
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
}
|
|
114
158
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
159
|
+
async close(): Promise<void> {
|
|
160
|
+
if (this.#server) {
|
|
161
|
+
this.#server.stop(true); // close all connections immediately
|
|
162
|
+
}
|
|
163
|
+
}
|
|
119
164
|
|
|
120
|
-
|
|
121
|
-
|
|
165
|
+
// ---------------------------------------------------------------------------
|
|
166
|
+
// Router
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
122
168
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
}
|
|
169
|
+
async #route(req: Request, url: URL, path: string, method: string): Promise<Response> {
|
|
170
|
+
// --- CORS preflight ---
|
|
171
|
+
if (method === 'OPTIONS') {
|
|
172
|
+
return new Response(null, { status: 204 });
|
|
132
173
|
}
|
|
133
174
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return
|
|
137
|
-
}
|
|
175
|
+
// --- Static routes ---
|
|
176
|
+
if (method === 'GET' && path === '/health') {
|
|
177
|
+
return Response.json({ ok: true });
|
|
178
|
+
}
|
|
138
179
|
|
|
139
|
-
|
|
180
|
+
if (method === 'GET' && path === '/metrics') {
|
|
140
181
|
try {
|
|
141
|
-
|
|
142
|
-
|
|
182
|
+
const metricsBody = await register.metrics();
|
|
183
|
+
return new Response(metricsBody, {
|
|
184
|
+
headers: { 'content-type': register.contentType },
|
|
185
|
+
});
|
|
143
186
|
} catch (e) {
|
|
144
|
-
|
|
187
|
+
return new Response(String(e), { status: 500 });
|
|
145
188
|
}
|
|
146
|
-
}
|
|
189
|
+
}
|
|
147
190
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
191
|
+
if (method === 'GET' && path === '/') {
|
|
192
|
+
return new Response(
|
|
193
|
+
'please use am enbox client, for example: https://github.com/enboxorg/enbox ',
|
|
194
|
+
{ headers: { 'content-type': 'text/plain' } },
|
|
195
|
+
);
|
|
196
|
+
}
|
|
153
197
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
}
|
|
198
|
+
if (method === 'GET' && path === '/info') {
|
|
199
|
+
return this.#handleInfo();
|
|
200
|
+
}
|
|
163
201
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
queryOptions.filter.protocolPath = req.params[0].replace(leadTailSlashRegex, '');
|
|
202
|
+
// --- JSON-RPC POST ---
|
|
203
|
+
if (method === 'POST' && path === '/') {
|
|
204
|
+
return this.#handleJsonRpcPost(req);
|
|
205
|
+
}
|
|
169
206
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
207
|
+
// --- Registration routes ---
|
|
208
|
+
const registrationResponse = await this.#matchRegistrationRoutes(req, path, method);
|
|
209
|
+
if (registrationResponse) {
|
|
210
|
+
return registrationResponse;
|
|
211
|
+
}
|
|
175
212
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
-
})
|
|
213
|
+
// --- Web5 Connect routes ---
|
|
214
|
+
const connectResponse = await this.#matchWeb5ConnectRoutes(req, path, method);
|
|
215
|
+
if (connectResponse) {
|
|
216
|
+
return connectResponse;
|
|
217
|
+
}
|
|
198
218
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
219
|
+
// --- DID routes (parameterized) ---
|
|
220
|
+
return this.#matchDidRoutes(req, url, path);
|
|
221
|
+
}
|
|
202
222
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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);
|
|
223
|
+
// ---------------------------------------------------------------------------
|
|
224
|
+
// DID convenience routes
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
async #matchDidRoutes(req: Request, url: URL, path: string): Promise<Response> {
|
|
228
|
+
const leadTailSlashRegex = /^\/|\/$/g;
|
|
229
|
+
|
|
230
|
+
// /:did/read/protocols/:protocol/* (also matches trailing slash with empty path)
|
|
231
|
+
{
|
|
232
|
+
const match = path.match(/^\/([^/]+)\/read\/protocols\/([^/]+)\/(.*)$/);
|
|
233
|
+
if (match && req.method === 'GET') {
|
|
234
|
+
const [, did, protocolParam, protocolPathRaw] = match;
|
|
235
|
+
return this.#handleReadProtocolRecord(did, protocolParam, protocolPathRaw, url, leadTailSlashRegex);
|
|
225
236
|
}
|
|
226
|
-
}
|
|
237
|
+
}
|
|
227
238
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
239
|
+
// /:did/read/protocols/:protocol
|
|
240
|
+
{
|
|
241
|
+
const match = path.match(/^\/([^/]+)\/read\/protocols\/([^/]+)$/);
|
|
242
|
+
if (match && req.method === 'GET') {
|
|
243
|
+
const [, did, protocolParam] = match;
|
|
244
|
+
return this.#handleReadProtocol(did, protocolParam);
|
|
245
|
+
}
|
|
234
246
|
}
|
|
235
247
|
|
|
236
|
-
|
|
237
|
-
|
|
248
|
+
// /:did/read/records/:id OR /:did/records/:id
|
|
249
|
+
{
|
|
250
|
+
const match = path.match(/^\/([^/]+)\/(?:read\/)?records\/([^/]+)$/);
|
|
251
|
+
if (match && req.method === 'GET') {
|
|
252
|
+
const [, did, recordId] = match;
|
|
253
|
+
return this.#handleReadRecord(did, recordId);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
238
256
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
} else if (status.code === 401) {
|
|
246
|
-
return res.sendStatus(404);
|
|
247
|
-
} else {
|
|
248
|
-
return res.sendStatus(status.code);
|
|
257
|
+
// /:did/query/protocols
|
|
258
|
+
{
|
|
259
|
+
const match = path.match(/^\/([^/]+)\/query\/protocols$/);
|
|
260
|
+
if (match && req.method === 'GET') {
|
|
261
|
+
const [, did] = match;
|
|
262
|
+
return this.#handleQueryProtocols(did);
|
|
249
263
|
}
|
|
250
|
-
}
|
|
264
|
+
}
|
|
251
265
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
-
});
|
|
266
|
+
// /:did/query
|
|
267
|
+
{
|
|
268
|
+
const match = path.match(/^\/([^/]+)\/query$/);
|
|
269
|
+
if (match && req.method === 'GET') {
|
|
270
|
+
const [, did] = match;
|
|
271
|
+
return this.#handleQueryRecords(did, url);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
276
274
|
|
|
277
|
-
|
|
278
|
-
|
|
275
|
+
return new Response('Not Found', { status: 404 });
|
|
276
|
+
}
|
|
279
277
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
278
|
+
// ---------------------------------------------------------------------------
|
|
279
|
+
// Handlers
|
|
280
|
+
// ---------------------------------------------------------------------------
|
|
281
|
+
|
|
282
|
+
#handleInfo(): Response {
|
|
283
|
+
const registrationRequirements: string[] = [];
|
|
284
|
+
if (config.registrationProofOfWorkEnabled) {
|
|
285
|
+
registrationRequirements.push('proof-of-work-sha256-v0');
|
|
286
|
+
}
|
|
287
|
+
if (config.termsOfServiceFilePath !== undefined) {
|
|
288
|
+
registrationRequirements.push('terms-of-service');
|
|
289
|
+
}
|
|
287
290
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
291
|
+
return Response.json({
|
|
292
|
+
url : config.baseUrl,
|
|
293
|
+
server : this.#packageInfo.server,
|
|
294
|
+
maxFileSize : config.maxRecordDataSize,
|
|
295
|
+
registrationRequirements : registrationRequirements,
|
|
296
|
+
version : this.#packageInfo.version,
|
|
297
|
+
sdkVersion : this.#packageInfo.sdkVersion,
|
|
298
|
+
webSocketSupport : config.webSocketSupport,
|
|
292
299
|
});
|
|
300
|
+
}
|
|
293
301
|
|
|
294
|
-
|
|
295
|
-
|
|
302
|
+
async #handleJsonRpcPost(req: Request): Promise<Response> {
|
|
303
|
+
const dwnRpcRequestString = req.headers.get('dwn-request');
|
|
296
304
|
|
|
297
|
-
|
|
298
|
-
|
|
305
|
+
if (!dwnRpcRequestString) {
|
|
306
|
+
const reply = createJsonRpcErrorResponse(
|
|
307
|
+
uuidv4(), JsonRpcErrorCodes.BadRequest, 'request payload required.'
|
|
308
|
+
);
|
|
309
|
+
return Response.json(reply, { status: 400 });
|
|
310
|
+
}
|
|
299
311
|
|
|
300
|
-
|
|
301
|
-
|
|
312
|
+
let dwnRpcRequest: JsonRpcRequest;
|
|
313
|
+
try {
|
|
314
|
+
dwnRpcRequest = JSON.parse(dwnRpcRequestString);
|
|
315
|
+
} catch (e) {
|
|
316
|
+
const reply = createJsonRpcErrorResponse(
|
|
317
|
+
uuidv4(), JsonRpcErrorCodes.BadRequest, (e as Error).message
|
|
318
|
+
);
|
|
319
|
+
return Response.json(reply, { status: 400 });
|
|
320
|
+
}
|
|
302
321
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
322
|
+
// Read the request body into bytes first, then wrap in a fresh ReadableStream.
|
|
323
|
+
// Bun's native Request.body stream has an incompatible reader.releaseLock()
|
|
324
|
+
// that breaks DWN SDK's DataStream.toBytes(), so we materialise the body here.
|
|
325
|
+
const contentLength = req.headers.get('content-length');
|
|
326
|
+
const transferEncoding = req.headers.get('transfer-encoding');
|
|
327
|
+
let requestDataStream: ReadableStream<Uint8Array> | undefined;
|
|
328
|
+
if (parseInt(contentLength ?? '0') > 0 || transferEncoding !== null) {
|
|
329
|
+
const bodyBytes = new Uint8Array(await req.arrayBuffer());
|
|
330
|
+
requestDataStream = DataStream.fromBytes(bodyBytes);
|
|
331
|
+
}
|
|
308
332
|
|
|
309
|
-
|
|
310
|
-
|
|
333
|
+
const requestContext: RequestContext = {
|
|
334
|
+
dwn : this.dwn,
|
|
335
|
+
transport : 'http',
|
|
336
|
+
dataStream : requestDataStream,
|
|
337
|
+
};
|
|
338
|
+
const { jsonRpcResponse, dataStream: responseDataStream } =
|
|
339
|
+
await jsonRpcRouter.handle(dwnRpcRequest, requestContext);
|
|
311
340
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
}
|
|
341
|
+
if (jsonRpcResponse.error) {
|
|
342
|
+
requestCounter.inc({ method: dwnRpcRequest.method, error: 1 });
|
|
343
|
+
return Response.json(jsonRpcResponse, { status: 500 });
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
requestCounter.inc({
|
|
347
|
+
method : dwnRpcRequest.method,
|
|
348
|
+
status : jsonRpcResponse?.result?.reply?.status?.code || 0,
|
|
349
|
+
});
|
|
330
350
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
351
|
+
if (responseDataStream) {
|
|
352
|
+
return new Response(responseDataStream, {
|
|
353
|
+
headers: {
|
|
354
|
+
'content-type' : 'application/octet-stream',
|
|
355
|
+
'dwn-response' : JSON.stringify(jsonRpcResponse),
|
|
356
|
+
},
|
|
334
357
|
});
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
358
|
+
} else {
|
|
359
|
+
return Response.json(jsonRpcResponse);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
338
362
|
|
|
339
|
-
|
|
363
|
+
#readReplyToResponse(reply: RecordsReadReply): Response {
|
|
364
|
+
if (reply.status.code === 200) {
|
|
365
|
+
if (reply?.entry?.data) {
|
|
366
|
+
return new Response(reply.entry.data, {
|
|
367
|
+
headers: {
|
|
368
|
+
'content-type' : reply.entry.recordsWrite.descriptor.dataFormat,
|
|
369
|
+
'dwn-response' : JSON.stringify(reply),
|
|
370
|
+
},
|
|
371
|
+
});
|
|
340
372
|
} else {
|
|
341
|
-
return
|
|
373
|
+
return new Response(null, { status: 400 });
|
|
342
374
|
}
|
|
375
|
+
} else if (reply.status.code === 401) {
|
|
376
|
+
return new Response(null, { status: 404 });
|
|
377
|
+
} else {
|
|
378
|
+
return Response.json(reply, { status: reply.status.code });
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async #handleReadRecord(did: string, recordId: string): Promise<Response> {
|
|
383
|
+
const record = await RecordsRead.create({
|
|
384
|
+
filter: { recordId },
|
|
343
385
|
});
|
|
386
|
+
const reply = await this.dwn.processMessage(did, record.message);
|
|
387
|
+
return this.#readReplyToResponse(reply);
|
|
388
|
+
}
|
|
344
389
|
|
|
345
|
-
|
|
390
|
+
async #handleReadProtocolRecord(
|
|
391
|
+
did: string, protocolParam: string, protocolPathRaw: string,
|
|
392
|
+
url: URL, leadTailSlashRegex: RegExp
|
|
393
|
+
): Promise<Response> {
|
|
394
|
+
if (!protocolPathRaw || protocolPathRaw.replace(leadTailSlashRegex, '') === '') {
|
|
395
|
+
return new Response('protocol path is required', { status: 400 });
|
|
396
|
+
}
|
|
346
397
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
398
|
+
try {
|
|
399
|
+
const queryOptions: Record<string, any> = { filter: {} };
|
|
400
|
+
for (const [param, value] of url.searchParams) {
|
|
401
|
+
const keys = param.split('.');
|
|
402
|
+
const lastKey = keys.pop();
|
|
403
|
+
const nestObj = (obj: Record<string, any>, key: string): Record<string, any> =>
|
|
404
|
+
obj[key] = obj[key] || {};
|
|
405
|
+
const lastLevelObject = keys.reduce(nestObj, queryOptions);
|
|
406
|
+
lastLevelObject[lastKey!] = value;
|
|
355
407
|
}
|
|
356
408
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
409
|
+
const protocol = Convert.base64Url(protocolParam).toString();
|
|
410
|
+
queryOptions.filter.protocol = protocol;
|
|
411
|
+
queryOptions.filter.protocolPath = protocolPathRaw.replace(leadTailSlashRegex, '');
|
|
412
|
+
|
|
413
|
+
const query = await RecordsQuery.create({
|
|
414
|
+
filter : queryOptions.filter,
|
|
415
|
+
pagination : { limit: 1 },
|
|
416
|
+
dateSort : DateSort.PublishedDescending,
|
|
365
417
|
});
|
|
366
|
-
});
|
|
367
418
|
|
|
368
|
-
|
|
419
|
+
const { entries, status } = await this.dwn.processMessage(did, query.message);
|
|
420
|
+
|
|
421
|
+
if (status.code === 200) {
|
|
422
|
+
if (entries[0]) {
|
|
423
|
+
const record = await RecordsRead.create({
|
|
424
|
+
filter: { recordId: entries[0].recordId },
|
|
425
|
+
});
|
|
426
|
+
const reply = await this.dwn.processMessage(did, record.toJSON());
|
|
427
|
+
return this.#readReplyToResponse(reply);
|
|
428
|
+
} else {
|
|
429
|
+
return new Response(null, { status: 404 });
|
|
430
|
+
}
|
|
431
|
+
} else if (status.code === 401) {
|
|
432
|
+
return new Response(null, { status: 404 });
|
|
433
|
+
} else {
|
|
434
|
+
return new Response(null, { status: status.code });
|
|
435
|
+
}
|
|
436
|
+
} catch (error) {
|
|
437
|
+
log.error(`Error processing request: ${decodeURI(url.pathname)}`, error);
|
|
438
|
+
return new Response('Bad Request', { status: 400 });
|
|
439
|
+
}
|
|
369
440
|
}
|
|
370
441
|
|
|
371
|
-
#
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
442
|
+
async #handleReadProtocol(did: string, protocolParam: string): Promise<Response> {
|
|
443
|
+
try {
|
|
444
|
+
const protocol = Convert.base64Url(protocolParam).toString();
|
|
445
|
+
const query = await ProtocolsQuery.create({
|
|
446
|
+
filter: { protocol },
|
|
376
447
|
});
|
|
448
|
+
const { entries, status } = await this.dwn.processMessage(did, query.message);
|
|
449
|
+
if (status.code === 200) {
|
|
450
|
+
if (entries.length) {
|
|
451
|
+
return Response.json(entries[0], { status: status.code });
|
|
452
|
+
} else {
|
|
453
|
+
return new Response(null, { status: 404 });
|
|
454
|
+
}
|
|
455
|
+
} else if (status.code === 401) {
|
|
456
|
+
return new Response(null, { status: 404 });
|
|
457
|
+
} else {
|
|
458
|
+
return new Response(null, { status: status.code });
|
|
459
|
+
}
|
|
460
|
+
} catch (error) {
|
|
461
|
+
log.error(`Error processing request`, error);
|
|
462
|
+
return new Response('Bad Request', { status: 400 });
|
|
377
463
|
}
|
|
464
|
+
}
|
|
378
465
|
|
|
379
|
-
|
|
380
|
-
|
|
466
|
+
async #handleQueryProtocols(did: string): Promise<Response> {
|
|
467
|
+
const query = await ProtocolsQuery.create({});
|
|
468
|
+
const { entries, status } = await this.dwn.processMessage(did, query.message);
|
|
469
|
+
if (status.code === 200) {
|
|
470
|
+
return Response.json(entries, { status: status.code });
|
|
471
|
+
} else if (status.code === 401) {
|
|
472
|
+
return new Response(null, { status: 404 });
|
|
473
|
+
} else {
|
|
474
|
+
return new Response(null, { status: status.code });
|
|
381
475
|
}
|
|
476
|
+
}
|
|
382
477
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
478
|
+
async #handleQueryRecords(did: string, url: URL): Promise<Response> {
|
|
479
|
+
try {
|
|
480
|
+
const recordsQueryOptions: Record<string, any> = {};
|
|
481
|
+
for (const [param, value] of url.searchParams) {
|
|
482
|
+
const keys = param.split('.');
|
|
483
|
+
const lastKey = keys.pop();
|
|
484
|
+
const nestObj = (obj: Record<string, any>, key: string): Record<string, any> =>
|
|
485
|
+
obj[key] = obj[key] || {};
|
|
486
|
+
const lastLevelObject = keys.reduce(nestObj, recordsQueryOptions);
|
|
487
|
+
lastLevelObject[lastKey!] = value;
|
|
488
|
+
}
|
|
387
489
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
490
|
+
const recordsQuery = await RecordsQuery.create({
|
|
491
|
+
filter : recordsQueryOptions.filter,
|
|
492
|
+
pagination : recordsQueryOptions.pagination,
|
|
493
|
+
dateSort : recordsQueryOptions.dateSort,
|
|
494
|
+
});
|
|
393
495
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
log.info('Error handling registration request:', error);
|
|
398
|
-
res.status(500).json({ success: false });
|
|
399
|
-
}
|
|
400
|
-
}
|
|
496
|
+
const reply = await this.dwn.processMessage(did, recordsQuery.message);
|
|
497
|
+
return Response.json(reply, {
|
|
498
|
+
headers: { 'content-type': 'application/json' },
|
|
401
499
|
});
|
|
500
|
+
} catch (error) {
|
|
501
|
+
return Response.json(error, { status: 400 });
|
|
402
502
|
}
|
|
403
503
|
}
|
|
404
504
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
505
|
+
// ---------------------------------------------------------------------------
|
|
506
|
+
// Registration routes
|
|
507
|
+
// ---------------------------------------------------------------------------
|
|
508
|
+
|
|
509
|
+
async #matchRegistrationRoutes(
|
|
510
|
+
req: Request, path: string, method: string
|
|
511
|
+
): Promise<Response | null> {
|
|
512
|
+
if (method === 'GET' && path === '/registration/proof-of-work'
|
|
513
|
+
&& this.#config.registrationProofOfWorkEnabled) {
|
|
514
|
+
const proofOfWorkChallenge = this.registrationManager.getProofOfWorkChallenge();
|
|
515
|
+
return Response.json(proofOfWorkChallenge);
|
|
516
|
+
}
|
|
414
517
|
|
|
415
|
-
|
|
416
|
-
|
|
518
|
+
if (method === 'GET' && path === '/registration/terms-of-service'
|
|
519
|
+
&& this.#config.termsOfServiceFilePath !== undefined) {
|
|
520
|
+
return new Response(this.registrationManager.getTermsOfService());
|
|
521
|
+
}
|
|
417
522
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
code: 400,
|
|
423
|
-
message: "Bad Request: Missing 'request' parameter",
|
|
424
|
-
},
|
|
425
|
-
});
|
|
426
|
-
}
|
|
523
|
+
if (method === 'POST' && path === '/registration'
|
|
524
|
+
&& this.#config.registrationStoreUrl !== undefined) {
|
|
525
|
+
const requestBody = await req.json();
|
|
526
|
+
log.info('Registration request:', requestBody);
|
|
427
527
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
return
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
528
|
+
try {
|
|
529
|
+
await this.registrationManager.handleRegistrationRequest(requestBody);
|
|
530
|
+
return Response.json({ success: true }, { status: 200 });
|
|
531
|
+
} catch (error) {
|
|
532
|
+
const dwnServerError = error as DwnServerError;
|
|
533
|
+
if (dwnServerError.code !== undefined) {
|
|
534
|
+
return Response.json(dwnServerError, { status: 400 });
|
|
535
|
+
} else {
|
|
536
|
+
log.info('Error handling registration request:', error);
|
|
537
|
+
return Response.json({ success: false }, { status: 500 });
|
|
538
|
+
}
|
|
437
539
|
}
|
|
540
|
+
}
|
|
438
541
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
});
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
442
544
|
|
|
443
|
-
|
|
444
|
-
|
|
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}...`);
|
|
545
|
+
// ---------------------------------------------------------------------------
|
|
546
|
+
// Web5 Connect routes
|
|
547
|
+
// ---------------------------------------------------------------------------
|
|
448
548
|
|
|
449
|
-
|
|
450
|
-
|
|
549
|
+
async #matchWeb5ConnectRoutes(
|
|
550
|
+
req: Request, path: string, method: string
|
|
551
|
+
): Promise<Response | null> {
|
|
552
|
+
// POST /connect/par
|
|
553
|
+
if (method === 'POST' && path === '/connect/par') {
|
|
554
|
+
log.info('Storing Pushed Authorization Request (PAR) request...');
|
|
555
|
+
const body = await req.json();
|
|
451
556
|
|
|
452
|
-
if (!
|
|
453
|
-
|
|
557
|
+
if (!body.request) {
|
|
558
|
+
return Response.json({
|
|
454
559
|
ok : false,
|
|
455
|
-
status : { code:
|
|
456
|
-
});
|
|
457
|
-
} else {
|
|
458
|
-
res.set('Content-Type', 'application/jwt');
|
|
459
|
-
res.send(requestObjectJwt);
|
|
560
|
+
status : { code: 400, message: 'Bad Request: Missing \'request\' parameter' },
|
|
561
|
+
}, { status: 400 });
|
|
460
562
|
}
|
|
461
|
-
});
|
|
462
563
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
564
|
+
if (body?.request?.request_uri) {
|
|
565
|
+
return Response.json({
|
|
566
|
+
ok : false,
|
|
567
|
+
status : { code: 400, message: 'Bad Request: \'request_uri\' parameter is not allowed in PAR' },
|
|
568
|
+
}, { status: 400 });
|
|
569
|
+
}
|
|
468
570
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
571
|
+
const result = await this.web5ConnectServer.setWeb5ConnectRequest(body.request);
|
|
572
|
+
return Response.json(result, { status: 201 });
|
|
573
|
+
}
|
|
472
574
|
|
|
473
|
-
|
|
575
|
+
// GET /connect/authorize/:requestId.jwt
|
|
576
|
+
{
|
|
577
|
+
const match = path.match(/^\/connect\/authorize\/([^/]+)\.jwt$/);
|
|
578
|
+
if (match && method === 'GET') {
|
|
579
|
+
const requestId = match[1];
|
|
580
|
+
log.info(`Retrieving Web5 Connect Request object of ID: ${requestId}...`);
|
|
581
|
+
|
|
582
|
+
const requestObjectJwt = await this.web5ConnectServer.getWeb5ConnectRequest(requestId);
|
|
583
|
+
if (!requestObjectJwt) {
|
|
584
|
+
return Response.json({
|
|
585
|
+
ok : false,
|
|
586
|
+
status : { code: 404, message: 'Not Found' },
|
|
587
|
+
}, { status: 404 });
|
|
588
|
+
} else {
|
|
589
|
+
const body = typeof requestObjectJwt === 'string'
|
|
590
|
+
? requestObjectJwt
|
|
591
|
+
: JSON.stringify(requestObjectJwt);
|
|
592
|
+
return new Response(body, {
|
|
593
|
+
headers: { 'content-type': 'application/jwt' },
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
474
598
|
|
|
475
|
-
|
|
599
|
+
// POST /connect/callback
|
|
600
|
+
if (method === 'POST' && path === '/connect/callback') {
|
|
601
|
+
log.info('Storing Identity Provider (wallet) pushed response with ID token...');
|
|
602
|
+
const body = await req.json();
|
|
603
|
+
const idToken = body.id_token;
|
|
604
|
+
const state = body.state;
|
|
476
605
|
|
|
477
|
-
|
|
606
|
+
if (idToken !== undefined && state != undefined) {
|
|
607
|
+
await this.web5ConnectServer.setWeb5ConnectResponse(state, idToken);
|
|
608
|
+
return Response.json({
|
|
478
609
|
ok : true,
|
|
479
|
-
status : { code: 201, message: 'Created' }
|
|
480
|
-
});
|
|
481
|
-
|
|
610
|
+
status : { code: 201, message: 'Created' },
|
|
611
|
+
}, { status: 201 });
|
|
482
612
|
} else {
|
|
483
|
-
|
|
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({
|
|
613
|
+
return Response.json({
|
|
501
614
|
ok : false,
|
|
502
|
-
status : { code:
|
|
503
|
-
});
|
|
504
|
-
} else {
|
|
505
|
-
res.set('Content-Type', 'application/jwt');
|
|
506
|
-
res.send(idToken);
|
|
615
|
+
status : { code: 400, message: 'Bad Request' },
|
|
616
|
+
}, { status: 400 });
|
|
507
617
|
}
|
|
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
|
-
}
|
|
618
|
+
}
|
|
523
619
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
620
|
+
// GET /connect/token/:state.jwt
|
|
621
|
+
{
|
|
622
|
+
const match = path.match(/^\/connect\/token\/([^/]+)\.jwt$/);
|
|
623
|
+
if (match && method === 'GET') {
|
|
624
|
+
const state = match[1];
|
|
625
|
+
log.info(`Retrieving ID token for state: ${state}...`);
|
|
626
|
+
|
|
627
|
+
const idToken = await this.web5ConnectServer.getWeb5ConnectResponse(state);
|
|
628
|
+
if (!idToken) {
|
|
629
|
+
return Response.json({
|
|
630
|
+
ok : false,
|
|
631
|
+
status : { code: 404, message: 'Not Found' },
|
|
632
|
+
}, { status: 404 });
|
|
533
633
|
} else {
|
|
534
|
-
|
|
634
|
+
const body = typeof idToken === 'string' ? idToken : JSON.stringify(idToken);
|
|
635
|
+
return new Response(body, {
|
|
636
|
+
headers: { 'content-type': 'application/jwt' },
|
|
637
|
+
});
|
|
535
638
|
}
|
|
536
|
-
}
|
|
537
|
-
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
538
641
|
|
|
539
|
-
|
|
642
|
+
return null;
|
|
540
643
|
}
|
|
541
644
|
}
|