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