@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.
Files changed (146) hide show
  1. package/README.md +13 -13
  2. package/dist/esm/src/config.d.ts +2 -6
  3. package/dist/esm/src/config.d.ts.map +1 -1
  4. package/dist/esm/src/config.js +4 -8
  5. package/dist/esm/src/config.js.map +1 -1
  6. package/dist/esm/src/connection/connection-manager.d.ts +9 -9
  7. package/dist/esm/src/connection/connection-manager.d.ts.map +1 -1
  8. package/dist/esm/src/connection/connection-manager.js +5 -3
  9. package/dist/esm/src/connection/connection-manager.js.map +1 -1
  10. package/dist/esm/src/connection/socket-connection.d.ts +18 -17
  11. package/dist/esm/src/connection/socket-connection.d.ts.map +1 -1
  12. package/dist/esm/src/connection/socket-connection.js +26 -35
  13. package/dist/esm/src/connection/socket-connection.js.map +1 -1
  14. package/dist/esm/src/dwn-error.js.map +1 -1
  15. package/dist/esm/src/dwn-server.d.ts +5 -6
  16. package/dist/esm/src/dwn-server.d.ts.map +1 -1
  17. package/dist/esm/src/dwn-server.js +3 -14
  18. package/dist/esm/src/dwn-server.js.map +1 -1
  19. package/dist/esm/src/http-api.d.ts +9 -11
  20. package/dist/esm/src/http-api.d.ts.map +1 -1
  21. package/dist/esm/src/http-api.js +450 -375
  22. package/dist/esm/src/http-api.js.map +1 -1
  23. package/dist/esm/src/json-rpc-handlers/dwn/process-message.d.ts.map +1 -1
  24. package/dist/esm/src/json-rpc-handlers/dwn/process-message.js +3 -3
  25. package/dist/esm/src/json-rpc-handlers/dwn/process-message.js.map +1 -1
  26. package/dist/esm/src/json-rpc-handlers/subscription/close.d.ts.map +1 -1
  27. package/dist/esm/src/json-rpc-handlers/subscription/close.js.map +1 -1
  28. package/dist/esm/src/json-rpc-socket.d.ts +1 -1
  29. package/dist/esm/src/json-rpc-socket.d.ts.map +1 -1
  30. package/dist/esm/src/json-rpc-socket.js +1 -1
  31. package/dist/esm/src/json-rpc-socket.js.map +1 -1
  32. package/dist/esm/src/lib/json-rpc-router.d.ts +3 -4
  33. package/dist/esm/src/lib/json-rpc-router.d.ts.map +1 -1
  34. package/dist/esm/src/lib/json-rpc-router.js.map +1 -1
  35. package/dist/esm/src/lib/json-rpc.d.ts.map +1 -1
  36. package/dist/esm/src/lib/json-rpc.js.map +1 -1
  37. package/dist/esm/src/main.js +0 -0
  38. package/dist/esm/src/metrics.d.ts +1 -1
  39. package/dist/esm/src/metrics.js.map +1 -1
  40. package/dist/esm/src/registration/proof-of-work-manager.d.ts +1 -1
  41. package/dist/esm/src/registration/proof-of-work-manager.d.ts.map +1 -1
  42. package/dist/esm/src/registration/proof-of-work-manager.js +3 -3
  43. package/dist/esm/src/registration/proof-of-work-manager.js.map +1 -1
  44. package/dist/esm/src/registration/registration-manager.d.ts +3 -3
  45. package/dist/esm/src/registration/registration-manager.d.ts.map +1 -1
  46. package/dist/esm/src/registration/registration-manager.js +6 -6
  47. package/dist/esm/src/registration/registration-manager.js.map +1 -1
  48. package/dist/esm/src/registration/registration-store.d.ts +1 -1
  49. package/dist/esm/src/registration/registration-store.d.ts.map +1 -1
  50. package/dist/esm/src/registration/registration-store.js.map +1 -1
  51. package/dist/esm/src/storage.d.ts +2 -2
  52. package/dist/esm/src/storage.d.ts.map +1 -1
  53. package/dist/esm/src/storage.js +5 -4
  54. package/dist/esm/src/storage.js.map +1 -1
  55. package/dist/esm/src/web5-connect/sql-ttl-cache.d.ts.map +1 -1
  56. package/dist/esm/src/web5-connect/sql-ttl-cache.js +3 -2
  57. package/dist/esm/src/web5-connect/sql-ttl-cache.js.map +1 -1
  58. package/dist/esm/src/web5-connect/web5-connect-server.d.ts.map +1 -1
  59. package/dist/esm/src/web5-connect/web5-connect-server.js +2 -2
  60. package/dist/esm/src/web5-connect/web5-connect-server.js.map +1 -1
  61. package/dist/esm/src/ws-api.d.ts +2 -4
  62. package/dist/esm/src/ws-api.d.ts.map +1 -1
  63. package/dist/esm/src/ws-api.js +6 -17
  64. package/dist/esm/src/ws-api.js.map +1 -1
  65. package/dist/esm/tests/common-scenario-validator.d.ts.map +1 -1
  66. package/dist/esm/tests/common-scenario-validator.js +2 -3
  67. package/dist/esm/tests/common-scenario-validator.js.map +1 -1
  68. package/dist/esm/tests/connection/connection-manager.spec.js +11 -9
  69. package/dist/esm/tests/connection/connection-manager.spec.js.map +1 -1
  70. package/dist/esm/tests/connection/socket-connection.spec.js +40 -18
  71. package/dist/esm/tests/connection/socket-connection.spec.js.map +1 -1
  72. package/dist/esm/tests/cors/http-api.browser.js +1 -1
  73. package/dist/esm/tests/cors/http-api.browser.js.map +1 -1
  74. package/dist/esm/tests/dwn-process-message.spec.js +4 -4
  75. package/dist/esm/tests/dwn-process-message.spec.js.map +1 -1
  76. package/dist/esm/tests/dwn-server.spec.js +8 -9
  77. package/dist/esm/tests/dwn-server.spec.js.map +1 -1
  78. package/dist/esm/tests/http-api.spec.js +92 -85
  79. package/dist/esm/tests/http-api.spec.js.map +1 -1
  80. package/dist/esm/tests/json-rpc-socket.spec.js +11 -9
  81. package/dist/esm/tests/json-rpc-socket.spec.js.map +1 -1
  82. package/dist/esm/tests/plugins/data-store-sqlite.d.ts +2 -2
  83. package/dist/esm/tests/plugins/data-store-sqlite.js +2 -2
  84. package/dist/esm/tests/plugins/event-log-sqlite.d.ts +2 -2
  85. package/dist/esm/tests/plugins/event-log-sqlite.js +2 -2
  86. package/dist/esm/tests/plugins/event-stream-in-memory.d.ts +2 -2
  87. package/dist/esm/tests/plugins/event-stream-in-memory.d.ts.map +1 -1
  88. package/dist/esm/tests/plugins/event-stream-in-memory.js +1 -1
  89. package/dist/esm/tests/plugins/event-stream-in-memory.js.map +1 -1
  90. package/dist/esm/tests/plugins/message-store-sqlite.d.ts +2 -2
  91. package/dist/esm/tests/plugins/message-store-sqlite.d.ts.map +1 -1
  92. package/dist/esm/tests/plugins/message-store-sqlite.js +2 -2
  93. package/dist/esm/tests/plugins/message-store-sqlite.js.map +1 -1
  94. package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts +2 -2
  95. package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts.map +1 -1
  96. package/dist/esm/tests/plugins/resumable-task-store-sqlite.js +2 -2
  97. package/dist/esm/tests/plugins/resumable-task-store-sqlite.js.map +1 -1
  98. package/dist/esm/tests/process-handler.spec.js +6 -6
  99. package/dist/esm/tests/process-handler.spec.js.map +1 -1
  100. package/dist/esm/tests/registration/proof-of-work-manager.spec.js +3 -4
  101. package/dist/esm/tests/registration/proof-of-work-manager.spec.js.map +1 -1
  102. package/dist/esm/tests/rpc-subscribe-close.spec.js +1 -1
  103. package/dist/esm/tests/rpc-subscribe-close.spec.js.map +1 -1
  104. package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js +11 -10
  105. package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js.map +1 -1
  106. package/dist/esm/tests/scenarios/registration.spec.js +16 -12
  107. package/dist/esm/tests/scenarios/registration.spec.js.map +1 -1
  108. package/dist/esm/tests/scenarios/web5-connect.spec.js +12 -8
  109. package/dist/esm/tests/scenarios/web5-connect.spec.js.map +1 -1
  110. package/dist/esm/tests/test-dwn.d.ts.map +1 -1
  111. package/dist/esm/tests/test-dwn.js +9 -15
  112. package/dist/esm/tests/test-dwn.js.map +1 -1
  113. package/dist/esm/tests/utils.d.ts +3 -6
  114. package/dist/esm/tests/utils.d.ts.map +1 -1
  115. package/dist/esm/tests/utils.js +9 -18
  116. package/dist/esm/tests/utils.js.map +1 -1
  117. package/dist/esm/tests/ws-api.spec.js +28 -23
  118. package/dist/esm/tests/ws-api.spec.js.map +1 -1
  119. package/package.json +25 -44
  120. package/src/config.ts +15 -19
  121. package/src/connection/connection-manager.ts +18 -12
  122. package/src/connection/socket-connection.ts +52 -57
  123. package/src/dwn-error.ts +2 -2
  124. package/src/dwn-server.ts +17 -30
  125. package/src/http-api.ts +499 -396
  126. package/src/json-rpc-handlers/dwn/process-message.ts +9 -10
  127. package/src/json-rpc-handlers/subscription/close.ts +4 -4
  128. package/src/json-rpc-socket.ts +3 -2
  129. package/src/lib/json-rpc-router.ts +5 -6
  130. package/src/lib/json-rpc.ts +6 -6
  131. package/src/metrics.ts +7 -7
  132. package/src/process-handlers.ts +5 -5
  133. package/src/registration/proof-of-work-manager.ts +11 -10
  134. package/src/registration/registration-manager.ts +23 -21
  135. package/src/registration/registration-store.ts +8 -7
  136. package/src/storage.ts +15 -13
  137. package/src/web5-connect/sql-ttl-cache.ts +5 -4
  138. package/src/web5-connect/web5-connect-server.ts +9 -8
  139. package/src/ws-api.ts +11 -26
  140. package/dist/cjs/index.js +0 -6811
  141. package/dist/cjs/package.json +0 -1
  142. package/dist/esm/src/lib/http-server-shutdown-handler.d.ts +0 -10
  143. package/dist/esm/src/lib/http-server-shutdown-handler.d.ts.map +0 -1
  144. package/dist/esm/src/lib/http-server-shutdown-handler.js +0 -65
  145. package/dist/esm/src/lib/http-server-shutdown-handler.js.map +0 -1
  146. 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 { type Dwn, DateSort, RecordsRead, RecordsQuery, ProtocolsQuery } from '@enbox/dwn-sdk-js';
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
- #api: Express;
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(config: DwnServerConfig, dwn: Dwn, registrationManager?: RegistrationManager): Promise<HttpApi> {
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 ? packageJson.dependencies['@enbox/dwn-sdk-js'] : undefined;
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(): http.Server {
80
+ get server(): Server<WsData> {
79
81
  return this.#server;
80
82
  }
81
83
 
82
- get api(): Express {
83
- return this.#api;
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
- #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)
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
- 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
- }
133
+ return response;
134
+ },
107
135
 
108
- /**
109
- * Configures the HTTP server's request handlers.
110
- */
111
- #setupRoutes(): void {
112
-
113
- const leadTailSlashRegex = /^\/|\/$/;
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
- function readReplyHandler(res, reply: RecordsReadReply): any {
116
- if (reply.status.code === 200) {
117
- if (reply?.entry?.data) {
118
- const stream = reply.entry.data;
159
+ async close(): Promise<void> {
160
+ if (this.#server) {
161
+ this.#server.stop(true); // close all connections immediately
162
+ }
163
+ }
119
164
 
120
- res.setHeader('content-type', reply.entry.recordsWrite.descriptor.dataFormat);
121
- res.setHeader('dwn-response', JSON.stringify(reply));
165
+ // ---------------------------------------------------------------------------
166
+ // Router
167
+ // ---------------------------------------------------------------------------
122
168
 
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
- }
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
- this.#api.get('/health', (_req, res) => {
135
- // return 200 ok
136
- return res.json({ ok: true });
137
- });
175
+ // --- Static routes ---
176
+ if (method === 'GET' && path === '/health') {
177
+ return Response.json({ ok: true });
178
+ }
138
179
 
139
- this.#api.get('/metrics', async (req, res) => {
180
+ if (method === 'GET' && path === '/metrics') {
140
181
  try {
141
- res.set('Content-Type', register.contentType);
142
- res.end(await register.metrics());
182
+ const metricsBody = await register.metrics();
183
+ return new Response(metricsBody, {
184
+ headers: { 'content-type': register.contentType },
185
+ });
143
186
  } catch (e) {
144
- res.status(500).end(e);
187
+ return new Response(String(e), { status: 500 });
145
188
  }
146
- });
189
+ }
147
190
 
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
- }
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
- // 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
- }
198
+ if (method === 'GET' && path === '/info') {
199
+ return this.#handleInfo();
200
+ }
163
201
 
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, '');
202
+ // --- JSON-RPC POST ---
203
+ if (method === 'POST' && path === '/') {
204
+ return this.#handleJsonRpcPost(req);
205
+ }
169
206
 
170
- const query = await RecordsQuery.create({
171
- filter: queryOptions.filter,
172
- pagination: { limit: 1 },
173
- dateSort: DateSort.PublishedDescending
174
- });
207
+ // --- Registration routes ---
208
+ const registrationResponse = await this.#matchRegistrationRoutes(req, path, method);
209
+ if (registrationResponse) {
210
+ return registrationResponse;
211
+ }
175
212
 
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
- })
213
+ // --- Web5 Connect routes ---
214
+ const connectResponse = await this.#matchWeb5ConnectRoutes(req, path, method);
215
+ if (connectResponse) {
216
+ return connectResponse;
217
+ }
198
218
 
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 {
219
+ // --- DID routes (parameterized) ---
220
+ return this.#matchDidRoutes(req, url, path);
221
+ }
202
222
 
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);
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
- 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);
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
- this.#api.get('/:did/read/records/:id', recordsReadHandler);
237
- this.#api.get('/:did/records/:id', recordsReadHandler);
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
- 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);
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
- 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
- });
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
- // should always return a 200 status code with a JSON response
278
- const reply = await this.dwn.processMessage(req.params.did, recordsQuery.message);
275
+ return new Response('Not Found', { status: 404 });
276
+ }
279
277
 
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
- });
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
- 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 ');
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
- this.#api.post('/', async (req: Request, res) => {
295
- const dwnRpcRequestString = req.headers['dwn-request'] as string;
302
+ async #handleJsonRpcPost(req: Request): Promise<Response> {
303
+ const dwnRpcRequestString = req.headers.get('dwn-request');
296
304
 
297
- if (!dwnRpcRequestString) {
298
- const reply = createJsonRpcErrorResponse(uuidv4(), JsonRpcErrorCodes.BadRequest, 'request payload required.');
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
- return res.status(400).json(reply);
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
- let dwnRpcRequest: JsonRpcRequest;
304
- try {
305
- dwnRpcRequest = JSON.parse(dwnRpcRequestString);
306
- } catch (e) {
307
- const reply = createJsonRpcErrorResponse(uuidv4(), JsonRpcErrorCodes.BadRequest, e.message);
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
- return res.status(400).json(reply);
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
- // 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
- }
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
- requestCounter.inc({
332
- method : dwnRpcRequest.method,
333
- status : jsonRpcResponse?.result?.reply?.status?.code || 0,
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
- if (responseDataStream) {
336
- res.setHeader('content-type', 'application/octet-stream');
337
- res.setHeader('dwn-response', JSON.stringify(jsonRpcResponse));
358
+ } else {
359
+ return Response.json(jsonRpcResponse);
360
+ }
361
+ }
338
362
 
339
- return responseDataStream.pipe(res);
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 res.json(jsonRpcResponse);
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
- this.#setupRegistrationRoutes();
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
- 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');
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
- 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,
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
- this.#setupWeb5ConnectServerRoutes();
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
- #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);
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
- if (this.#config.termsOfServiceFilePath !== undefined) {
380
- this.#api.get('/registration/terms-of-service', (_req: Request, res: Response) => res.send(this.registrationManager.getTermsOfService()));
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
- 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);
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
- try {
389
- await this.registrationManager.handleRegistrationRequest(requestBody);
390
- res.status(200).json({ success: true });
391
- } catch (error) {
392
- const dwnServerError = error as DwnServerError;
490
+ const recordsQuery = await RecordsQuery.create({
491
+ filter : recordsQueryOptions.filter,
492
+ pagination : recordsQueryOptions.pagination,
493
+ dateSort : recordsQueryOptions.dateSort,
494
+ });
393
495
 
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
- }
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
- #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...');
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
- // 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
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
- 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
- }
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
- // 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
- });
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
- const result = await this.web5ConnectServer.setWeb5ConnectRequest(req.body.request);
440
- res.status(201).json(result);
441
- });
542
+ return null;
543
+ }
442
544
 
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}...`);
545
+ // ---------------------------------------------------------------------------
546
+ // Web5 Connect routes
547
+ // ---------------------------------------------------------------------------
448
548
 
449
- // Look up the request object based on the requestId.
450
- const requestObjectJwt = await this.web5ConnectServer.getWeb5ConnectRequest(req.params.requestId);
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 (!requestObjectJwt) {
453
- res.status(404).json({
557
+ if (!body.request) {
558
+ return Response.json({
454
559
  ok : false,
455
- status : { code: 404, message: 'Not Found' }
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
- * 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...');
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
- // Store the ID token.
470
- const idToken = req.body.id_token;
471
- const state = req.body.state;
571
+ const result = await this.web5ConnectServer.setWeb5ConnectRequest(body.request);
572
+ return Response.json(result, { status: 201 });
573
+ }
472
574
 
473
- if (idToken !== undefined && state != undefined) {
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
- await this.web5ConnectServer.setWeb5ConnectResponse(state, idToken);
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
- res.status(201).json({
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
- 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({
613
+ return Response.json({
501
614
  ok : false,
502
- status : { code: 404, message: 'Not Found' }
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
- * 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);
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
- resolve();
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
- this.server.closeAllConnections();
642
+ return null;
540
643
  }
541
644
  }