@twin.org/api-server-fastify 0.0.1-next.9 → 0.0.2-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +299 -41
- package/dist/esm/index.mjs +299 -41
- package/dist/types/fastifySocketIo.d.ts +4 -0
- package/dist/types/fastifyWebServer.d.ts +8 -10
- package/dist/types/index.d.ts +2 -0
- package/dist/types/models/IFastifyWebServerConfig.d.ts +19 -0
- package/dist/types/models/IFastifyWebServerConstructorOptions.d.ts +19 -0
- package/docs/changelog.md +121 -1
- package/docs/reference/classes/FastifyWebServer.md +31 -19
- package/docs/reference/index.md +5 -0
- package/docs/reference/interfaces/IFastifyWebServerConfig.md +27 -0
- package/docs/reference/interfaces/IFastifyWebServerConstructorOptions.md +27 -0
- package/locales/en.json +4 -1
- package/package.json +10 -8
package/dist/cjs/index.cjs
CHANGED
|
@@ -3,10 +3,24 @@
|
|
|
3
3
|
var FastifyCompress = require('@fastify/compress');
|
|
4
4
|
var FastifyCors = require('@fastify/cors');
|
|
5
5
|
var apiModels = require('@twin.org/api-models');
|
|
6
|
+
var apiProcessors = require('@twin.org/api-processors');
|
|
6
7
|
var core = require('@twin.org/core');
|
|
7
8
|
var loggingModels = require('@twin.org/logging-models');
|
|
8
9
|
var web = require('@twin.org/web');
|
|
9
10
|
var Fastify = require('fastify');
|
|
11
|
+
var fp = require('fastify-plugin');
|
|
12
|
+
var socket_io = require('socket.io');
|
|
13
|
+
|
|
14
|
+
// This is a clone of fastify-socket.io which runs with recent fastify versions.
|
|
15
|
+
const fastifySocketIO = fp(async (fastify, opts) => {
|
|
16
|
+
const ioServer = new socket_io.Server(fastify.server, opts);
|
|
17
|
+
fastify.decorate("io", ioServer);
|
|
18
|
+
fastify.addHook("preClose", done => {
|
|
19
|
+
ioServer.disconnectSockets();
|
|
20
|
+
ioServer.close();
|
|
21
|
+
done();
|
|
22
|
+
});
|
|
23
|
+
}, { fastify: ">=5.x.x", name: "socket.io" });
|
|
10
24
|
|
|
11
25
|
// Copyright 2024 IOTA Stiftung.
|
|
12
26
|
// SPDX-License-Identifier: Apache-2.0.
|
|
@@ -48,23 +62,49 @@ class FastifyWebServer {
|
|
|
48
62
|
* @internal
|
|
49
63
|
*/
|
|
50
64
|
_fastify;
|
|
65
|
+
/**
|
|
66
|
+
* The options for the socket server.
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
_socketConfig;
|
|
51
70
|
/**
|
|
52
71
|
* Whether the server has been started.
|
|
53
72
|
* @internal
|
|
54
73
|
*/
|
|
55
74
|
_started;
|
|
75
|
+
/**
|
|
76
|
+
* The mime type processors.
|
|
77
|
+
* @internal
|
|
78
|
+
*/
|
|
79
|
+
_mimeTypeProcessors;
|
|
80
|
+
/**
|
|
81
|
+
* Include the stack with errors.
|
|
82
|
+
* @internal
|
|
83
|
+
*/
|
|
84
|
+
_includeErrorStack;
|
|
56
85
|
/**
|
|
57
86
|
* Create a new instance of FastifyWebServer.
|
|
58
87
|
* @param options The options for the server.
|
|
59
|
-
* @param options.loggingConnectorType The type of the logging connector to use, if undefined, no logging will happen.
|
|
60
|
-
* @param options.config Additional options for the Fastify server.
|
|
61
88
|
*/
|
|
62
89
|
constructor(options) {
|
|
63
90
|
this._loggingConnector = core.Is.stringValue(options?.loggingConnectorType)
|
|
64
91
|
? loggingModels.LoggingConnectorFactory.get(options.loggingConnectorType)
|
|
65
92
|
: undefined;
|
|
66
|
-
this._fastify = Fastify({
|
|
93
|
+
this._fastify = Fastify({
|
|
94
|
+
maxParamLength: 2000,
|
|
95
|
+
...options?.config?.web
|
|
96
|
+
});
|
|
97
|
+
this._socketConfig = {
|
|
98
|
+
path: "/socket",
|
|
99
|
+
...options?.config?.socket
|
|
100
|
+
};
|
|
67
101
|
this._started = false;
|
|
102
|
+
this._mimeTypeProcessors = options?.mimeTypeProcessors ?? [];
|
|
103
|
+
const hasJsonLd = this._mimeTypeProcessors.find(processor => processor.CLASS_NAME === "json-ld");
|
|
104
|
+
if (!hasJsonLd) {
|
|
105
|
+
this._mimeTypeProcessors.push(new apiProcessors.JsonLdMimeTypeProcessor());
|
|
106
|
+
}
|
|
107
|
+
this._includeErrorStack = options?.config?.includeErrorStack ?? false;
|
|
68
108
|
}
|
|
69
109
|
/**
|
|
70
110
|
* Get the web server instance.
|
|
@@ -75,14 +115,19 @@ class FastifyWebServer {
|
|
|
75
115
|
}
|
|
76
116
|
/**
|
|
77
117
|
* Build the server.
|
|
78
|
-
* @param restRouteProcessors The
|
|
118
|
+
* @param restRouteProcessors The processors for incoming requests over REST.
|
|
79
119
|
* @param restRoutes The REST routes.
|
|
120
|
+
* @param socketRouteProcessors The processors for incoming requests over Sockets.
|
|
121
|
+
* @param socketRoutes The socket routes.
|
|
80
122
|
* @param options Options for building the server.
|
|
81
123
|
* @returns Nothing.
|
|
82
124
|
*/
|
|
83
|
-
async build(restRouteProcessors, restRoutes, options) {
|
|
84
|
-
if (!core.Is.arrayValue(restRouteProcessors)) {
|
|
85
|
-
throw new core.GeneralError(this.CLASS_NAME, "
|
|
125
|
+
async build(restRouteProcessors, restRoutes, socketRouteProcessors, socketRoutes, options) {
|
|
126
|
+
if (core.Is.arrayValue(restRoutes) && !core.Is.arrayValue(restRouteProcessors)) {
|
|
127
|
+
throw new core.GeneralError(this.CLASS_NAME, "noRestProcessors");
|
|
128
|
+
}
|
|
129
|
+
if (core.Is.arrayValue(socketRoutes) && !core.Is.arrayValue(socketRouteProcessors)) {
|
|
130
|
+
throw new core.GeneralError(this.CLASS_NAME, "noSocketProcessors");
|
|
86
131
|
}
|
|
87
132
|
await this._loggingConnector?.log({
|
|
88
133
|
level: "info",
|
|
@@ -92,8 +137,25 @@ class FastifyWebServer {
|
|
|
92
137
|
});
|
|
93
138
|
this._options = options;
|
|
94
139
|
await this._fastify.register(FastifyCompress);
|
|
140
|
+
if (core.Is.arrayValue(socketRoutes)) {
|
|
141
|
+
await this._fastify.register(fastifySocketIO, this._socketConfig);
|
|
142
|
+
}
|
|
143
|
+
if (core.Is.arrayValue(this._mimeTypeProcessors)) {
|
|
144
|
+
for (const contentTypeHandler of this._mimeTypeProcessors) {
|
|
145
|
+
this._fastify.addContentTypeParser(contentTypeHandler.getTypes(), { parseAs: "buffer" }, (request, body, done) => {
|
|
146
|
+
// Fastify does not handle this method correctly if it is async
|
|
147
|
+
// so we have to use the callback method
|
|
148
|
+
contentTypeHandler
|
|
149
|
+
.handle(body)
|
|
150
|
+
// eslint-disable-next-line promise/prefer-await-to-then, promise/no-callback-in-promise
|
|
151
|
+
.then(processed => done(null, processed))
|
|
152
|
+
// eslint-disable-next-line promise/prefer-await-to-then, promise/no-callback-in-promise
|
|
153
|
+
.catch(err => done(core.BaseError.fromError(err)));
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
95
157
|
await this.initCors(options);
|
|
96
|
-
this._fastify.setNotFoundHandler({}, async (request, reply) => this.
|
|
158
|
+
this._fastify.setNotFoundHandler({}, async (request, reply) => this.handleRequestRest(restRouteProcessors ?? [], request, reply));
|
|
97
159
|
this._fastify.setErrorHandler(async (error, request, reply) => {
|
|
98
160
|
// If code property is set this is a fastify error
|
|
99
161
|
// otherwise it's from our framework
|
|
@@ -123,22 +185,8 @@ class FastifyWebServer {
|
|
|
123
185
|
error: err
|
|
124
186
|
});
|
|
125
187
|
});
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
let path = core.StringHelper.trimTrailingSlashes(restRoute.path);
|
|
129
|
-
if (!path.startsWith("/")) {
|
|
130
|
-
path = `/${path}`;
|
|
131
|
-
}
|
|
132
|
-
await this._loggingConnector?.log({
|
|
133
|
-
level: "info",
|
|
134
|
-
ts: Date.now(),
|
|
135
|
-
source: this.CLASS_NAME,
|
|
136
|
-
message: `${FastifyWebServer._CLASS_NAME_CAMEL_CASE}.restRouteAdded`,
|
|
137
|
-
data: { route: path, method: restRoute.method }
|
|
138
|
-
});
|
|
139
|
-
const method = restRoute.method.toLowerCase();
|
|
140
|
-
this._fastify[method](path, async (request, reply) => this.handleRequest(restRouteProcessors, request, reply, restRoute));
|
|
141
|
-
}
|
|
188
|
+
await this.addRoutesRest(restRouteProcessors, restRoutes);
|
|
189
|
+
await this.addRoutesSocket(socketRouteProcessors, socketRoutes);
|
|
142
190
|
}
|
|
143
191
|
/**
|
|
144
192
|
* Start the server.
|
|
@@ -203,14 +251,109 @@ class FastifyWebServer {
|
|
|
203
251
|
}
|
|
204
252
|
}
|
|
205
253
|
/**
|
|
206
|
-
*
|
|
254
|
+
* Add the REST routes to the server.
|
|
255
|
+
* @param restRouteProcessors The processors for the incoming requests.
|
|
256
|
+
* @param restRoutes The REST routes to add.
|
|
257
|
+
* @internal
|
|
258
|
+
*/
|
|
259
|
+
async addRoutesRest(restRouteProcessors, restRoutes) {
|
|
260
|
+
if (core.Is.arrayValue(restRouteProcessors) && core.Is.arrayValue(restRoutes)) {
|
|
261
|
+
for (const restRoute of restRoutes) {
|
|
262
|
+
let path = core.StringHelper.trimTrailingSlashes(restRoute.path);
|
|
263
|
+
if (!path.startsWith("/")) {
|
|
264
|
+
path = `/${path}`;
|
|
265
|
+
}
|
|
266
|
+
await this._loggingConnector?.log({
|
|
267
|
+
level: "info",
|
|
268
|
+
ts: Date.now(),
|
|
269
|
+
source: this.CLASS_NAME,
|
|
270
|
+
message: `${FastifyWebServer._CLASS_NAME_CAMEL_CASE}.restRouteAdded`,
|
|
271
|
+
data: { route: path, method: restRoute.method }
|
|
272
|
+
});
|
|
273
|
+
const method = restRoute.method.toLowerCase();
|
|
274
|
+
this._fastify[method](path, async (request, reply) => this.handleRequestRest(restRouteProcessors, request, reply, restRoute));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Add the socket routes to the server.
|
|
280
|
+
* @param socketRouteProcessors The processors for the incoming requests.
|
|
281
|
+
* @param socketRoutes The socket routes to add.
|
|
282
|
+
* @internal
|
|
283
|
+
*/
|
|
284
|
+
async addRoutesSocket(socketRouteProcessors, socketRoutes) {
|
|
285
|
+
if (core.Is.arrayValue(socketRouteProcessors) && core.Is.arrayValue(socketRoutes)) {
|
|
286
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
287
|
+
const io = this._fastify.io;
|
|
288
|
+
for (const socketRoute of socketRoutes) {
|
|
289
|
+
const path = core.StringHelper.trimLeadingSlashes(core.StringHelper.trimTrailingSlashes(socketRoute.path));
|
|
290
|
+
const pathParts = path.split("/");
|
|
291
|
+
await this._loggingConnector?.log({
|
|
292
|
+
level: "info",
|
|
293
|
+
ts: Date.now(),
|
|
294
|
+
source: this.CLASS_NAME,
|
|
295
|
+
message: `${FastifyWebServer._CLASS_NAME_CAMEL_CASE}.socketRouteAdded`,
|
|
296
|
+
data: { route: `/${path}` }
|
|
297
|
+
});
|
|
298
|
+
const socketNamespace = io.of(`/${pathParts[0]}`);
|
|
299
|
+
const topic = pathParts.slice(1).join("/");
|
|
300
|
+
socketNamespace.on("connection", async (socket) => {
|
|
301
|
+
const httpServerRequest = {
|
|
302
|
+
method: web.HttpMethod.GET,
|
|
303
|
+
url: socket.handshake.url,
|
|
304
|
+
query: socket.handshake.query,
|
|
305
|
+
headers: socket.handshake.headers
|
|
306
|
+
};
|
|
307
|
+
// Pass the connected information on to any processors
|
|
308
|
+
try {
|
|
309
|
+
const processorState = {
|
|
310
|
+
socketId: socket.id
|
|
311
|
+
};
|
|
312
|
+
for (const socketRouteProcessor of socketRouteProcessors) {
|
|
313
|
+
if (core.Is.function(socketRouteProcessor.connected)) {
|
|
314
|
+
await socketRouteProcessor.connected(httpServerRequest, socketRoute, processorState);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
catch (err) {
|
|
319
|
+
const { error, httpStatusCode } = apiModels.HttpErrorHelper.processError(err, this._includeErrorStack);
|
|
320
|
+
const response = {};
|
|
321
|
+
apiModels.HttpErrorHelper.buildResponse(response, error, httpStatusCode);
|
|
322
|
+
socket.emit(topic, response);
|
|
323
|
+
}
|
|
324
|
+
socket.on("disconnect", async () => {
|
|
325
|
+
try {
|
|
326
|
+
const processorState = {
|
|
327
|
+
socketId: socket.id
|
|
328
|
+
};
|
|
329
|
+
// The socket disconnected so notify any processors
|
|
330
|
+
for (const socketRouteProcessor of socketRouteProcessors) {
|
|
331
|
+
if (core.Is.function(socketRouteProcessor.disconnected)) {
|
|
332
|
+
await socketRouteProcessor.disconnected(httpServerRequest, socketRoute, processorState);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
// If something fails on a disconnect there is not much we can do with it
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
// Handle any incoming messages
|
|
341
|
+
socket.on(topic, async (data) => {
|
|
342
|
+
await this.handleRequestSocket(socketRouteProcessors, socketRoute, socket, `/${pathParts.join("/")}`, topic, data);
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Handle the incoming REST request.
|
|
207
350
|
* @param restRouteProcessors The hooks to process the incoming requests.
|
|
208
351
|
* @param request The incoming request.
|
|
209
352
|
* @param reply The outgoing response.
|
|
210
353
|
* @param restRoute The REST route to handle.
|
|
211
354
|
* @internal
|
|
212
355
|
*/
|
|
213
|
-
async
|
|
356
|
+
async handleRequestRest(restRouteProcessors, request, reply, restRoute) {
|
|
214
357
|
const httpServerRequest = {
|
|
215
358
|
method: request.method.toUpperCase(),
|
|
216
359
|
url: `${request.protocol}://${request.hostname}${request.url}`,
|
|
@@ -222,21 +365,7 @@ class FastifyWebServer {
|
|
|
222
365
|
const httpResponse = {};
|
|
223
366
|
const httpRequestIdentity = {};
|
|
224
367
|
const processorState = {};
|
|
225
|
-
|
|
226
|
-
if (core.Is.function(restRouteProcessor.pre)) {
|
|
227
|
-
await restRouteProcessor.pre(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
for (const restRouteProcessor of restRouteProcessors) {
|
|
231
|
-
if (core.Is.function(restRouteProcessor.process)) {
|
|
232
|
-
await restRouteProcessor.process(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
for (const restRouteProcessor of restRouteProcessors) {
|
|
236
|
-
if (core.Is.function(restRouteProcessor.post)) {
|
|
237
|
-
await restRouteProcessor.post(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
368
|
+
await this.runProcessorsRest(restRouteProcessors, restRoute, httpServerRequest, httpResponse, httpRequestIdentity, processorState);
|
|
240
369
|
if (!core.Is.empty(httpResponse.headers)) {
|
|
241
370
|
for (const header of Object.keys(httpResponse.headers)) {
|
|
242
371
|
reply.header(header, httpResponse.headers[header]);
|
|
@@ -246,6 +375,135 @@ class FastifyWebServer {
|
|
|
246
375
|
.status((httpResponse.statusCode ?? web.HttpStatusCode.ok))
|
|
247
376
|
.send(httpResponse.body);
|
|
248
377
|
}
|
|
378
|
+
/**
|
|
379
|
+
* Run the REST processors for the route.
|
|
380
|
+
* @param restRouteProcessors The processors to run.
|
|
381
|
+
* @param restRoute The route to process.
|
|
382
|
+
* @param httpServerRequest The incoming request.
|
|
383
|
+
* @param httpResponse The outgoing response.
|
|
384
|
+
* @param httpRequestIdentity The identity context for the request.
|
|
385
|
+
* @internal
|
|
386
|
+
*/
|
|
387
|
+
async runProcessorsRest(restRouteProcessors, restRoute, httpServerRequest, httpResponse, httpRequestIdentity, processorState) {
|
|
388
|
+
try {
|
|
389
|
+
for (const routeProcessor of restRouteProcessors) {
|
|
390
|
+
if (core.Is.function(routeProcessor.pre)) {
|
|
391
|
+
await routeProcessor.pre(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
for (const routeProcessor of restRouteProcessors) {
|
|
395
|
+
if (core.Is.function(routeProcessor.process)) {
|
|
396
|
+
await routeProcessor.process(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
for (const routeProcessor of restRouteProcessors) {
|
|
400
|
+
if (core.Is.function(routeProcessor.post)) {
|
|
401
|
+
await routeProcessor.post(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch (err) {
|
|
406
|
+
const { error, httpStatusCode } = apiModels.HttpErrorHelper.processError(err, this._includeErrorStack);
|
|
407
|
+
apiModels.HttpErrorHelper.buildResponse(httpResponse, error, httpStatusCode);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Handle the incoming socket request.
|
|
412
|
+
* @param socketRouteProcessors The hooks to process the incoming requests.
|
|
413
|
+
* @param socketRoute The socket route to handle.
|
|
414
|
+
* @param socket The socket to handle.
|
|
415
|
+
* @param fullPath The full path of the socket route.
|
|
416
|
+
* @param emitTopic The topic to emit the response on.
|
|
417
|
+
* @param data The incoming data.
|
|
418
|
+
* @internal
|
|
419
|
+
*/
|
|
420
|
+
async handleRequestSocket(socketRouteProcessors, socketRoute, socket, fullPath, emitTopic, request) {
|
|
421
|
+
const httpServerRequest = {
|
|
422
|
+
method: web.HttpMethod.GET,
|
|
423
|
+
url: fullPath,
|
|
424
|
+
query: socket.handshake.query,
|
|
425
|
+
headers: socket.handshake.headers,
|
|
426
|
+
body: request.body
|
|
427
|
+
};
|
|
428
|
+
const httpResponse = {};
|
|
429
|
+
const httpRequestIdentity = {};
|
|
430
|
+
const processorState = {
|
|
431
|
+
socketId: socket.id
|
|
432
|
+
};
|
|
433
|
+
delete httpServerRequest.query?.EIO;
|
|
434
|
+
delete httpServerRequest.query?.transport;
|
|
435
|
+
await this.runProcessorsSocket(socketRouteProcessors, socketRoute, httpServerRequest, httpResponse, httpRequestIdentity, processorState, emitTopic, async (topic, response) => {
|
|
436
|
+
await socket.emit(topic, response);
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Run the socket processors for the route.
|
|
441
|
+
* @param socketRouteProcessors The processors to run.
|
|
442
|
+
* @param socketRoute The route to process.
|
|
443
|
+
* @param httpServerRequest The incoming request.
|
|
444
|
+
* @param httpResponse The outgoing response.
|
|
445
|
+
* @param httpRequestIdentity The identity context for the request.
|
|
446
|
+
* @param processorState The state handed through the processors.
|
|
447
|
+
* @param requestTopic The topic of the request.
|
|
448
|
+
* @internal
|
|
449
|
+
*/
|
|
450
|
+
async runProcessorsSocket(socketRouteProcessors, socketRoute, httpServerRequest, httpResponse, httpRequestIdentity, processorState, requestTopic, responseEmitter) {
|
|
451
|
+
// Custom emit method which will also call the post processors
|
|
452
|
+
const postProcessEmit = async (topic, response, responseProcessorState) => {
|
|
453
|
+
await responseEmitter(topic, response);
|
|
454
|
+
try {
|
|
455
|
+
// The post processors are called after the response has been emitted
|
|
456
|
+
for (const postSocketRouteProcessor of socketRouteProcessors) {
|
|
457
|
+
if (core.Is.function(postSocketRouteProcessor.post)) {
|
|
458
|
+
await postSocketRouteProcessor.post(httpServerRequest, response, socketRoute, httpRequestIdentity, responseProcessorState);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
catch (err) {
|
|
463
|
+
this._loggingConnector?.log({
|
|
464
|
+
level: "error",
|
|
465
|
+
ts: Date.now(),
|
|
466
|
+
source: this.CLASS_NAME,
|
|
467
|
+
message: `${FastifyWebServer._CLASS_NAME_CAMEL_CASE}.postProcessorError`,
|
|
468
|
+
error: core.BaseError.fromError(err),
|
|
469
|
+
data: {
|
|
470
|
+
route: socketRoute.path
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
try {
|
|
476
|
+
for (const socketRouteProcessor of socketRouteProcessors) {
|
|
477
|
+
if (core.Is.function(socketRouteProcessor.pre)) {
|
|
478
|
+
await socketRouteProcessor.pre(httpServerRequest, httpResponse, socketRoute, httpRequestIdentity, processorState);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// We always call all the processors regardless of any response set by a previous processor.
|
|
482
|
+
// But if a pre processor sets a status code, we will emit the response manually, as the pre
|
|
483
|
+
// and post processors do not receive the emit method, they just populate the response object.
|
|
484
|
+
if (!core.Is.empty(httpResponse.statusCode)) {
|
|
485
|
+
await postProcessEmit(requestTopic, httpResponse, processorState);
|
|
486
|
+
}
|
|
487
|
+
for (const socketRouteProcessor of socketRouteProcessors) {
|
|
488
|
+
if (core.Is.function(socketRouteProcessor.process)) {
|
|
489
|
+
await socketRouteProcessor.process(httpServerRequest, httpResponse, socketRoute, httpRequestIdentity, processorState, async (topic, processResponse) => {
|
|
490
|
+
await postProcessEmit(topic, processResponse, processorState);
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
// If the processors set the status to any kind of error then we should emit this manually
|
|
495
|
+
if (core.Is.integer(httpResponse.statusCode) &&
|
|
496
|
+
httpResponse.statusCode >= web.HttpStatusCode.badRequest) {
|
|
497
|
+
await postProcessEmit(requestTopic, httpResponse, processorState);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
catch (err) {
|
|
501
|
+
// Emit any unhandled errors manually
|
|
502
|
+
const { error, httpStatusCode } = apiModels.HttpErrorHelper.processError(err, this._includeErrorStack);
|
|
503
|
+
apiModels.HttpErrorHelper.buildResponse(httpResponse, error, httpStatusCode);
|
|
504
|
+
await postProcessEmit(requestTopic, httpResponse, processorState);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
249
507
|
/**
|
|
250
508
|
* Initialize the cors options.
|
|
251
509
|
* @param options The web server options.
|
package/dist/esm/index.mjs
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import FastifyCompress from '@fastify/compress';
|
|
2
2
|
import FastifyCors from '@fastify/cors';
|
|
3
3
|
import { HttpErrorHelper } from '@twin.org/api-models';
|
|
4
|
+
import { JsonLdMimeTypeProcessor } from '@twin.org/api-processors';
|
|
4
5
|
import { StringHelper, Is, GeneralError, BaseError } from '@twin.org/core';
|
|
5
6
|
import { LoggingConnectorFactory } from '@twin.org/logging-models';
|
|
6
7
|
import { HttpStatusCode, HttpMethod, HeaderTypes } from '@twin.org/web';
|
|
7
8
|
import Fastify from 'fastify';
|
|
9
|
+
import fp from 'fastify-plugin';
|
|
10
|
+
import { Server } from 'socket.io';
|
|
11
|
+
|
|
12
|
+
// This is a clone of fastify-socket.io which runs with recent fastify versions.
|
|
13
|
+
const fastifySocketIO = fp(async (fastify, opts) => {
|
|
14
|
+
const ioServer = new Server(fastify.server, opts);
|
|
15
|
+
fastify.decorate("io", ioServer);
|
|
16
|
+
fastify.addHook("preClose", done => {
|
|
17
|
+
ioServer.disconnectSockets();
|
|
18
|
+
ioServer.close();
|
|
19
|
+
done();
|
|
20
|
+
});
|
|
21
|
+
}, { fastify: ">=5.x.x", name: "socket.io" });
|
|
8
22
|
|
|
9
23
|
// Copyright 2024 IOTA Stiftung.
|
|
10
24
|
// SPDX-License-Identifier: Apache-2.0.
|
|
@@ -46,23 +60,49 @@ class FastifyWebServer {
|
|
|
46
60
|
* @internal
|
|
47
61
|
*/
|
|
48
62
|
_fastify;
|
|
63
|
+
/**
|
|
64
|
+
* The options for the socket server.
|
|
65
|
+
* @internal
|
|
66
|
+
*/
|
|
67
|
+
_socketConfig;
|
|
49
68
|
/**
|
|
50
69
|
* Whether the server has been started.
|
|
51
70
|
* @internal
|
|
52
71
|
*/
|
|
53
72
|
_started;
|
|
73
|
+
/**
|
|
74
|
+
* The mime type processors.
|
|
75
|
+
* @internal
|
|
76
|
+
*/
|
|
77
|
+
_mimeTypeProcessors;
|
|
78
|
+
/**
|
|
79
|
+
* Include the stack with errors.
|
|
80
|
+
* @internal
|
|
81
|
+
*/
|
|
82
|
+
_includeErrorStack;
|
|
54
83
|
/**
|
|
55
84
|
* Create a new instance of FastifyWebServer.
|
|
56
85
|
* @param options The options for the server.
|
|
57
|
-
* @param options.loggingConnectorType The type of the logging connector to use, if undefined, no logging will happen.
|
|
58
|
-
* @param options.config Additional options for the Fastify server.
|
|
59
86
|
*/
|
|
60
87
|
constructor(options) {
|
|
61
88
|
this._loggingConnector = Is.stringValue(options?.loggingConnectorType)
|
|
62
89
|
? LoggingConnectorFactory.get(options.loggingConnectorType)
|
|
63
90
|
: undefined;
|
|
64
|
-
this._fastify = Fastify({
|
|
91
|
+
this._fastify = Fastify({
|
|
92
|
+
maxParamLength: 2000,
|
|
93
|
+
...options?.config?.web
|
|
94
|
+
});
|
|
95
|
+
this._socketConfig = {
|
|
96
|
+
path: "/socket",
|
|
97
|
+
...options?.config?.socket
|
|
98
|
+
};
|
|
65
99
|
this._started = false;
|
|
100
|
+
this._mimeTypeProcessors = options?.mimeTypeProcessors ?? [];
|
|
101
|
+
const hasJsonLd = this._mimeTypeProcessors.find(processor => processor.CLASS_NAME === "json-ld");
|
|
102
|
+
if (!hasJsonLd) {
|
|
103
|
+
this._mimeTypeProcessors.push(new JsonLdMimeTypeProcessor());
|
|
104
|
+
}
|
|
105
|
+
this._includeErrorStack = options?.config?.includeErrorStack ?? false;
|
|
66
106
|
}
|
|
67
107
|
/**
|
|
68
108
|
* Get the web server instance.
|
|
@@ -73,14 +113,19 @@ class FastifyWebServer {
|
|
|
73
113
|
}
|
|
74
114
|
/**
|
|
75
115
|
* Build the server.
|
|
76
|
-
* @param restRouteProcessors The
|
|
116
|
+
* @param restRouteProcessors The processors for incoming requests over REST.
|
|
77
117
|
* @param restRoutes The REST routes.
|
|
118
|
+
* @param socketRouteProcessors The processors for incoming requests over Sockets.
|
|
119
|
+
* @param socketRoutes The socket routes.
|
|
78
120
|
* @param options Options for building the server.
|
|
79
121
|
* @returns Nothing.
|
|
80
122
|
*/
|
|
81
|
-
async build(restRouteProcessors, restRoutes, options) {
|
|
82
|
-
if (!Is.arrayValue(restRouteProcessors)) {
|
|
83
|
-
throw new GeneralError(this.CLASS_NAME, "
|
|
123
|
+
async build(restRouteProcessors, restRoutes, socketRouteProcessors, socketRoutes, options) {
|
|
124
|
+
if (Is.arrayValue(restRoutes) && !Is.arrayValue(restRouteProcessors)) {
|
|
125
|
+
throw new GeneralError(this.CLASS_NAME, "noRestProcessors");
|
|
126
|
+
}
|
|
127
|
+
if (Is.arrayValue(socketRoutes) && !Is.arrayValue(socketRouteProcessors)) {
|
|
128
|
+
throw new GeneralError(this.CLASS_NAME, "noSocketProcessors");
|
|
84
129
|
}
|
|
85
130
|
await this._loggingConnector?.log({
|
|
86
131
|
level: "info",
|
|
@@ -90,8 +135,25 @@ class FastifyWebServer {
|
|
|
90
135
|
});
|
|
91
136
|
this._options = options;
|
|
92
137
|
await this._fastify.register(FastifyCompress);
|
|
138
|
+
if (Is.arrayValue(socketRoutes)) {
|
|
139
|
+
await this._fastify.register(fastifySocketIO, this._socketConfig);
|
|
140
|
+
}
|
|
141
|
+
if (Is.arrayValue(this._mimeTypeProcessors)) {
|
|
142
|
+
for (const contentTypeHandler of this._mimeTypeProcessors) {
|
|
143
|
+
this._fastify.addContentTypeParser(contentTypeHandler.getTypes(), { parseAs: "buffer" }, (request, body, done) => {
|
|
144
|
+
// Fastify does not handle this method correctly if it is async
|
|
145
|
+
// so we have to use the callback method
|
|
146
|
+
contentTypeHandler
|
|
147
|
+
.handle(body)
|
|
148
|
+
// eslint-disable-next-line promise/prefer-await-to-then, promise/no-callback-in-promise
|
|
149
|
+
.then(processed => done(null, processed))
|
|
150
|
+
// eslint-disable-next-line promise/prefer-await-to-then, promise/no-callback-in-promise
|
|
151
|
+
.catch(err => done(BaseError.fromError(err)));
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
93
155
|
await this.initCors(options);
|
|
94
|
-
this._fastify.setNotFoundHandler({}, async (request, reply) => this.
|
|
156
|
+
this._fastify.setNotFoundHandler({}, async (request, reply) => this.handleRequestRest(restRouteProcessors ?? [], request, reply));
|
|
95
157
|
this._fastify.setErrorHandler(async (error, request, reply) => {
|
|
96
158
|
// If code property is set this is a fastify error
|
|
97
159
|
// otherwise it's from our framework
|
|
@@ -121,22 +183,8 @@ class FastifyWebServer {
|
|
|
121
183
|
error: err
|
|
122
184
|
});
|
|
123
185
|
});
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
let path = StringHelper.trimTrailingSlashes(restRoute.path);
|
|
127
|
-
if (!path.startsWith("/")) {
|
|
128
|
-
path = `/${path}`;
|
|
129
|
-
}
|
|
130
|
-
await this._loggingConnector?.log({
|
|
131
|
-
level: "info",
|
|
132
|
-
ts: Date.now(),
|
|
133
|
-
source: this.CLASS_NAME,
|
|
134
|
-
message: `${FastifyWebServer._CLASS_NAME_CAMEL_CASE}.restRouteAdded`,
|
|
135
|
-
data: { route: path, method: restRoute.method }
|
|
136
|
-
});
|
|
137
|
-
const method = restRoute.method.toLowerCase();
|
|
138
|
-
this._fastify[method](path, async (request, reply) => this.handleRequest(restRouteProcessors, request, reply, restRoute));
|
|
139
|
-
}
|
|
186
|
+
await this.addRoutesRest(restRouteProcessors, restRoutes);
|
|
187
|
+
await this.addRoutesSocket(socketRouteProcessors, socketRoutes);
|
|
140
188
|
}
|
|
141
189
|
/**
|
|
142
190
|
* Start the server.
|
|
@@ -201,14 +249,109 @@ class FastifyWebServer {
|
|
|
201
249
|
}
|
|
202
250
|
}
|
|
203
251
|
/**
|
|
204
|
-
*
|
|
252
|
+
* Add the REST routes to the server.
|
|
253
|
+
* @param restRouteProcessors The processors for the incoming requests.
|
|
254
|
+
* @param restRoutes The REST routes to add.
|
|
255
|
+
* @internal
|
|
256
|
+
*/
|
|
257
|
+
async addRoutesRest(restRouteProcessors, restRoutes) {
|
|
258
|
+
if (Is.arrayValue(restRouteProcessors) && Is.arrayValue(restRoutes)) {
|
|
259
|
+
for (const restRoute of restRoutes) {
|
|
260
|
+
let path = StringHelper.trimTrailingSlashes(restRoute.path);
|
|
261
|
+
if (!path.startsWith("/")) {
|
|
262
|
+
path = `/${path}`;
|
|
263
|
+
}
|
|
264
|
+
await this._loggingConnector?.log({
|
|
265
|
+
level: "info",
|
|
266
|
+
ts: Date.now(),
|
|
267
|
+
source: this.CLASS_NAME,
|
|
268
|
+
message: `${FastifyWebServer._CLASS_NAME_CAMEL_CASE}.restRouteAdded`,
|
|
269
|
+
data: { route: path, method: restRoute.method }
|
|
270
|
+
});
|
|
271
|
+
const method = restRoute.method.toLowerCase();
|
|
272
|
+
this._fastify[method](path, async (request, reply) => this.handleRequestRest(restRouteProcessors, request, reply, restRoute));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Add the socket routes to the server.
|
|
278
|
+
* @param socketRouteProcessors The processors for the incoming requests.
|
|
279
|
+
* @param socketRoutes The socket routes to add.
|
|
280
|
+
* @internal
|
|
281
|
+
*/
|
|
282
|
+
async addRoutesSocket(socketRouteProcessors, socketRoutes) {
|
|
283
|
+
if (Is.arrayValue(socketRouteProcessors) && Is.arrayValue(socketRoutes)) {
|
|
284
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
285
|
+
const io = this._fastify.io;
|
|
286
|
+
for (const socketRoute of socketRoutes) {
|
|
287
|
+
const path = StringHelper.trimLeadingSlashes(StringHelper.trimTrailingSlashes(socketRoute.path));
|
|
288
|
+
const pathParts = path.split("/");
|
|
289
|
+
await this._loggingConnector?.log({
|
|
290
|
+
level: "info",
|
|
291
|
+
ts: Date.now(),
|
|
292
|
+
source: this.CLASS_NAME,
|
|
293
|
+
message: `${FastifyWebServer._CLASS_NAME_CAMEL_CASE}.socketRouteAdded`,
|
|
294
|
+
data: { route: `/${path}` }
|
|
295
|
+
});
|
|
296
|
+
const socketNamespace = io.of(`/${pathParts[0]}`);
|
|
297
|
+
const topic = pathParts.slice(1).join("/");
|
|
298
|
+
socketNamespace.on("connection", async (socket) => {
|
|
299
|
+
const httpServerRequest = {
|
|
300
|
+
method: HttpMethod.GET,
|
|
301
|
+
url: socket.handshake.url,
|
|
302
|
+
query: socket.handshake.query,
|
|
303
|
+
headers: socket.handshake.headers
|
|
304
|
+
};
|
|
305
|
+
// Pass the connected information on to any processors
|
|
306
|
+
try {
|
|
307
|
+
const processorState = {
|
|
308
|
+
socketId: socket.id
|
|
309
|
+
};
|
|
310
|
+
for (const socketRouteProcessor of socketRouteProcessors) {
|
|
311
|
+
if (Is.function(socketRouteProcessor.connected)) {
|
|
312
|
+
await socketRouteProcessor.connected(httpServerRequest, socketRoute, processorState);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
const { error, httpStatusCode } = HttpErrorHelper.processError(err, this._includeErrorStack);
|
|
318
|
+
const response = {};
|
|
319
|
+
HttpErrorHelper.buildResponse(response, error, httpStatusCode);
|
|
320
|
+
socket.emit(topic, response);
|
|
321
|
+
}
|
|
322
|
+
socket.on("disconnect", async () => {
|
|
323
|
+
try {
|
|
324
|
+
const processorState = {
|
|
325
|
+
socketId: socket.id
|
|
326
|
+
};
|
|
327
|
+
// The socket disconnected so notify any processors
|
|
328
|
+
for (const socketRouteProcessor of socketRouteProcessors) {
|
|
329
|
+
if (Is.function(socketRouteProcessor.disconnected)) {
|
|
330
|
+
await socketRouteProcessor.disconnected(httpServerRequest, socketRoute, processorState);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
// If something fails on a disconnect there is not much we can do with it
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
// Handle any incoming messages
|
|
339
|
+
socket.on(topic, async (data) => {
|
|
340
|
+
await this.handleRequestSocket(socketRouteProcessors, socketRoute, socket, `/${pathParts.join("/")}`, topic, data);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Handle the incoming REST request.
|
|
205
348
|
* @param restRouteProcessors The hooks to process the incoming requests.
|
|
206
349
|
* @param request The incoming request.
|
|
207
350
|
* @param reply The outgoing response.
|
|
208
351
|
* @param restRoute The REST route to handle.
|
|
209
352
|
* @internal
|
|
210
353
|
*/
|
|
211
|
-
async
|
|
354
|
+
async handleRequestRest(restRouteProcessors, request, reply, restRoute) {
|
|
212
355
|
const httpServerRequest = {
|
|
213
356
|
method: request.method.toUpperCase(),
|
|
214
357
|
url: `${request.protocol}://${request.hostname}${request.url}`,
|
|
@@ -220,21 +363,7 @@ class FastifyWebServer {
|
|
|
220
363
|
const httpResponse = {};
|
|
221
364
|
const httpRequestIdentity = {};
|
|
222
365
|
const processorState = {};
|
|
223
|
-
|
|
224
|
-
if (Is.function(restRouteProcessor.pre)) {
|
|
225
|
-
await restRouteProcessor.pre(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
for (const restRouteProcessor of restRouteProcessors) {
|
|
229
|
-
if (Is.function(restRouteProcessor.process)) {
|
|
230
|
-
await restRouteProcessor.process(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
for (const restRouteProcessor of restRouteProcessors) {
|
|
234
|
-
if (Is.function(restRouteProcessor.post)) {
|
|
235
|
-
await restRouteProcessor.post(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
366
|
+
await this.runProcessorsRest(restRouteProcessors, restRoute, httpServerRequest, httpResponse, httpRequestIdentity, processorState);
|
|
238
367
|
if (!Is.empty(httpResponse.headers)) {
|
|
239
368
|
for (const header of Object.keys(httpResponse.headers)) {
|
|
240
369
|
reply.header(header, httpResponse.headers[header]);
|
|
@@ -244,6 +373,135 @@ class FastifyWebServer {
|
|
|
244
373
|
.status((httpResponse.statusCode ?? HttpStatusCode.ok))
|
|
245
374
|
.send(httpResponse.body);
|
|
246
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* Run the REST processors for the route.
|
|
378
|
+
* @param restRouteProcessors The processors to run.
|
|
379
|
+
* @param restRoute The route to process.
|
|
380
|
+
* @param httpServerRequest The incoming request.
|
|
381
|
+
* @param httpResponse The outgoing response.
|
|
382
|
+
* @param httpRequestIdentity The identity context for the request.
|
|
383
|
+
* @internal
|
|
384
|
+
*/
|
|
385
|
+
async runProcessorsRest(restRouteProcessors, restRoute, httpServerRequest, httpResponse, httpRequestIdentity, processorState) {
|
|
386
|
+
try {
|
|
387
|
+
for (const routeProcessor of restRouteProcessors) {
|
|
388
|
+
if (Is.function(routeProcessor.pre)) {
|
|
389
|
+
await routeProcessor.pre(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
for (const routeProcessor of restRouteProcessors) {
|
|
393
|
+
if (Is.function(routeProcessor.process)) {
|
|
394
|
+
await routeProcessor.process(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
for (const routeProcessor of restRouteProcessors) {
|
|
398
|
+
if (Is.function(routeProcessor.post)) {
|
|
399
|
+
await routeProcessor.post(httpServerRequest, httpResponse, restRoute, httpRequestIdentity, processorState);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
catch (err) {
|
|
404
|
+
const { error, httpStatusCode } = HttpErrorHelper.processError(err, this._includeErrorStack);
|
|
405
|
+
HttpErrorHelper.buildResponse(httpResponse, error, httpStatusCode);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Handle the incoming socket request.
|
|
410
|
+
* @param socketRouteProcessors The hooks to process the incoming requests.
|
|
411
|
+
* @param socketRoute The socket route to handle.
|
|
412
|
+
* @param socket The socket to handle.
|
|
413
|
+
* @param fullPath The full path of the socket route.
|
|
414
|
+
* @param emitTopic The topic to emit the response on.
|
|
415
|
+
* @param data The incoming data.
|
|
416
|
+
* @internal
|
|
417
|
+
*/
|
|
418
|
+
async handleRequestSocket(socketRouteProcessors, socketRoute, socket, fullPath, emitTopic, request) {
|
|
419
|
+
const httpServerRequest = {
|
|
420
|
+
method: HttpMethod.GET,
|
|
421
|
+
url: fullPath,
|
|
422
|
+
query: socket.handshake.query,
|
|
423
|
+
headers: socket.handshake.headers,
|
|
424
|
+
body: request.body
|
|
425
|
+
};
|
|
426
|
+
const httpResponse = {};
|
|
427
|
+
const httpRequestIdentity = {};
|
|
428
|
+
const processorState = {
|
|
429
|
+
socketId: socket.id
|
|
430
|
+
};
|
|
431
|
+
delete httpServerRequest.query?.EIO;
|
|
432
|
+
delete httpServerRequest.query?.transport;
|
|
433
|
+
await this.runProcessorsSocket(socketRouteProcessors, socketRoute, httpServerRequest, httpResponse, httpRequestIdentity, processorState, emitTopic, async (topic, response) => {
|
|
434
|
+
await socket.emit(topic, response);
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Run the socket processors for the route.
|
|
439
|
+
* @param socketRouteProcessors The processors to run.
|
|
440
|
+
* @param socketRoute The route to process.
|
|
441
|
+
* @param httpServerRequest The incoming request.
|
|
442
|
+
* @param httpResponse The outgoing response.
|
|
443
|
+
* @param httpRequestIdentity The identity context for the request.
|
|
444
|
+
* @param processorState The state handed through the processors.
|
|
445
|
+
* @param requestTopic The topic of the request.
|
|
446
|
+
* @internal
|
|
447
|
+
*/
|
|
448
|
+
async runProcessorsSocket(socketRouteProcessors, socketRoute, httpServerRequest, httpResponse, httpRequestIdentity, processorState, requestTopic, responseEmitter) {
|
|
449
|
+
// Custom emit method which will also call the post processors
|
|
450
|
+
const postProcessEmit = async (topic, response, responseProcessorState) => {
|
|
451
|
+
await responseEmitter(topic, response);
|
|
452
|
+
try {
|
|
453
|
+
// The post processors are called after the response has been emitted
|
|
454
|
+
for (const postSocketRouteProcessor of socketRouteProcessors) {
|
|
455
|
+
if (Is.function(postSocketRouteProcessor.post)) {
|
|
456
|
+
await postSocketRouteProcessor.post(httpServerRequest, response, socketRoute, httpRequestIdentity, responseProcessorState);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
catch (err) {
|
|
461
|
+
this._loggingConnector?.log({
|
|
462
|
+
level: "error",
|
|
463
|
+
ts: Date.now(),
|
|
464
|
+
source: this.CLASS_NAME,
|
|
465
|
+
message: `${FastifyWebServer._CLASS_NAME_CAMEL_CASE}.postProcessorError`,
|
|
466
|
+
error: BaseError.fromError(err),
|
|
467
|
+
data: {
|
|
468
|
+
route: socketRoute.path
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
try {
|
|
474
|
+
for (const socketRouteProcessor of socketRouteProcessors) {
|
|
475
|
+
if (Is.function(socketRouteProcessor.pre)) {
|
|
476
|
+
await socketRouteProcessor.pre(httpServerRequest, httpResponse, socketRoute, httpRequestIdentity, processorState);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
// We always call all the processors regardless of any response set by a previous processor.
|
|
480
|
+
// But if a pre processor sets a status code, we will emit the response manually, as the pre
|
|
481
|
+
// and post processors do not receive the emit method, they just populate the response object.
|
|
482
|
+
if (!Is.empty(httpResponse.statusCode)) {
|
|
483
|
+
await postProcessEmit(requestTopic, httpResponse, processorState);
|
|
484
|
+
}
|
|
485
|
+
for (const socketRouteProcessor of socketRouteProcessors) {
|
|
486
|
+
if (Is.function(socketRouteProcessor.process)) {
|
|
487
|
+
await socketRouteProcessor.process(httpServerRequest, httpResponse, socketRoute, httpRequestIdentity, processorState, async (topic, processResponse) => {
|
|
488
|
+
await postProcessEmit(topic, processResponse, processorState);
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
// If the processors set the status to any kind of error then we should emit this manually
|
|
493
|
+
if (Is.integer(httpResponse.statusCode) &&
|
|
494
|
+
httpResponse.statusCode >= HttpStatusCode.badRequest) {
|
|
495
|
+
await postProcessEmit(requestTopic, httpResponse, processorState);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
catch (err) {
|
|
499
|
+
// Emit any unhandled errors manually
|
|
500
|
+
const { error, httpStatusCode } = HttpErrorHelper.processError(err, this._includeErrorStack);
|
|
501
|
+
HttpErrorHelper.buildResponse(httpResponse, error, httpStatusCode);
|
|
502
|
+
await postProcessEmit(requestTopic, httpResponse, processorState);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
247
505
|
/**
|
|
248
506
|
* Initialize the cors options.
|
|
249
507
|
* @param options The web server options.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import { type
|
|
1
|
+
import { type IRestRoute, type IRestRouteProcessor, type ISocketRoute, type ISocketRouteProcessor, type IWebServer, type IWebServerOptions } from "@twin.org/api-models";
|
|
2
|
+
import { type FastifyInstance } from "fastify";
|
|
3
|
+
import type { IFastifyWebServerConstructorOptions } from "./models/IFastifyWebServerConstructorOptions";
|
|
3
4
|
/**
|
|
4
5
|
* Implementation of the web server using Fastify.
|
|
5
6
|
*/
|
|
@@ -11,13 +12,8 @@ export declare class FastifyWebServer implements IWebServer<FastifyInstance> {
|
|
|
11
12
|
/**
|
|
12
13
|
* Create a new instance of FastifyWebServer.
|
|
13
14
|
* @param options The options for the server.
|
|
14
|
-
* @param options.loggingConnectorType The type of the logging connector to use, if undefined, no logging will happen.
|
|
15
|
-
* @param options.config Additional options for the Fastify server.
|
|
16
15
|
*/
|
|
17
|
-
constructor(options?:
|
|
18
|
-
loggingConnectorType?: string;
|
|
19
|
-
config?: FastifyServerOptions;
|
|
20
|
-
});
|
|
16
|
+
constructor(options?: IFastifyWebServerConstructorOptions);
|
|
21
17
|
/**
|
|
22
18
|
* Get the web server instance.
|
|
23
19
|
* @returns The web server instance.
|
|
@@ -25,12 +21,14 @@ export declare class FastifyWebServer implements IWebServer<FastifyInstance> {
|
|
|
25
21
|
getInstance(): FastifyInstance;
|
|
26
22
|
/**
|
|
27
23
|
* Build the server.
|
|
28
|
-
* @param restRouteProcessors The
|
|
24
|
+
* @param restRouteProcessors The processors for incoming requests over REST.
|
|
29
25
|
* @param restRoutes The REST routes.
|
|
26
|
+
* @param socketRouteProcessors The processors for incoming requests over Sockets.
|
|
27
|
+
* @param socketRoutes The socket routes.
|
|
30
28
|
* @param options Options for building the server.
|
|
31
29
|
* @returns Nothing.
|
|
32
30
|
*/
|
|
33
|
-
build(restRouteProcessors
|
|
31
|
+
build(restRouteProcessors?: IRestRouteProcessor[], restRoutes?: IRestRoute[], socketRouteProcessors?: ISocketRouteProcessor[], socketRoutes?: ISocketRoute[], options?: IWebServerOptions): Promise<void>;
|
|
34
32
|
/**
|
|
35
33
|
* Start the server.
|
|
36
34
|
* @returns Nothing.
|
package/dist/types/index.d.ts
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { FastifyServerOptions } from "fastify";
|
|
2
|
+
import type { ServerOptions } from "socket.io";
|
|
3
|
+
/**
|
|
4
|
+
* The configuration for the Fastify web server.
|
|
5
|
+
*/
|
|
6
|
+
export interface IFastifyWebServerConfig {
|
|
7
|
+
/**
|
|
8
|
+
* The web server options.
|
|
9
|
+
*/
|
|
10
|
+
web?: Partial<FastifyServerOptions>;
|
|
11
|
+
/**
|
|
12
|
+
* The socket server options.
|
|
13
|
+
*/
|
|
14
|
+
socket?: Partial<ServerOptions>;
|
|
15
|
+
/**
|
|
16
|
+
* Include the stack with errors.
|
|
17
|
+
*/
|
|
18
|
+
includeErrorStack?: boolean;
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { IMimeTypeProcessor } from "@twin.org/api-models";
|
|
2
|
+
import type { IFastifyWebServerConfig } from "./IFastifyWebServerConfig";
|
|
3
|
+
/**
|
|
4
|
+
* The options for the Fastify web server constructor.
|
|
5
|
+
*/
|
|
6
|
+
export interface IFastifyWebServerConstructorOptions {
|
|
7
|
+
/**
|
|
8
|
+
* The type of the logging connector to use, if undefined, no logging will happen.
|
|
9
|
+
*/
|
|
10
|
+
loggingConnectorType?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Additional configuration for the server.
|
|
13
|
+
*/
|
|
14
|
+
config?: IFastifyWebServerConfig;
|
|
15
|
+
/**
|
|
16
|
+
* Additional MIME type processors.
|
|
17
|
+
*/
|
|
18
|
+
mimeTypeProcessors?: IMimeTypeProcessor[];
|
|
19
|
+
}
|
package/docs/changelog.md
CHANGED
|
@@ -1,5 +1,125 @@
|
|
|
1
1
|
# @twin.org/api-server-fastify - Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## [0.0.2-next.1](https://github.com/twinfoundation/api/compare/api-server-fastify-v0.0.2-next.0...api-server-fastify-v0.0.2-next.1) (2025-07-08)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add json-ld mime type processor and auth admin component ([8861791](https://github.com/twinfoundation/api/commit/88617916e23bfbca023dbae1976fe421983a02ff))
|
|
9
|
+
* update dependencies ([1171dc4](https://github.com/twinfoundation/api/commit/1171dc416a9481737f6a640e3cf30145768f37e9))
|
|
10
|
+
* use shared store mechanism ([#19](https://github.com/twinfoundation/api/issues/19)) ([32116df](https://github.com/twinfoundation/api/commit/32116df3b4380a30137f5056f242a5c99afa2df9))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Dependencies
|
|
14
|
+
|
|
15
|
+
* The following workspace dependencies were updated
|
|
16
|
+
* dependencies
|
|
17
|
+
* @twin.org/api-core bumped from 0.0.2-next.0 to 0.0.2-next.1
|
|
18
|
+
* @twin.org/api-models bumped from 0.0.2-next.0 to 0.0.2-next.1
|
|
19
|
+
* @twin.org/api-processors bumped from 0.0.2-next.0 to 0.0.2-next.1
|
|
20
|
+
|
|
21
|
+
## 0.0.1 (2025-07-03)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Features
|
|
25
|
+
|
|
26
|
+
* release to production ([70ee2d5](https://github.com/twinfoundation/api/commit/70ee2d56a1dc9537d7c9c154d4cb78a235678a3a))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Dependencies
|
|
30
|
+
|
|
31
|
+
* The following workspace dependencies were updated
|
|
32
|
+
* dependencies
|
|
33
|
+
* @twin.org/api-models bumped from ^0.0.0 to ^0.0.1
|
|
34
|
+
* @twin.org/api-core bumped from ^0.0.0 to ^0.0.1
|
|
35
|
+
* devDependencies
|
|
36
|
+
* @twin.org/api-processors bumped from ^0.0.0 to ^0.0.1
|
|
37
|
+
|
|
38
|
+
## [0.0.1-next.36](https://github.com/twinfoundation/api/compare/api-server-fastify-v0.0.1-next.35...api-server-fastify-v0.0.1-next.36) (2025-06-17)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Miscellaneous Chores
|
|
42
|
+
|
|
43
|
+
* **api-server-fastify:** Synchronize repo versions
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
### Dependencies
|
|
47
|
+
|
|
48
|
+
* The following workspace dependencies were updated
|
|
49
|
+
* dependencies
|
|
50
|
+
* @twin.org/api-models bumped from 0.0.1-next.35 to 0.0.1-next.36
|
|
51
|
+
* @twin.org/api-core bumped from 0.0.1-next.35 to 0.0.1-next.36
|
|
52
|
+
* devDependencies
|
|
53
|
+
* @twin.org/api-processors bumped from 0.0.1-next.35 to 0.0.1-next.36
|
|
54
|
+
|
|
55
|
+
## [0.0.1-next.35](https://github.com/twinfoundation/api/compare/api-server-fastify-v0.0.1-next.34...api-server-fastify-v0.0.1-next.35) (2025-06-11)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
### Features
|
|
59
|
+
|
|
60
|
+
* update dependencies ([1171dc4](https://github.com/twinfoundation/api/commit/1171dc416a9481737f6a640e3cf30145768f37e9))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
### Dependencies
|
|
64
|
+
|
|
65
|
+
* The following workspace dependencies were updated
|
|
66
|
+
* dependencies
|
|
67
|
+
* @twin.org/api-models bumped from 0.0.1-next.34 to 0.0.1-next.35
|
|
68
|
+
* @twin.org/api-core bumped from 0.0.1-next.34 to 0.0.1-next.35
|
|
69
|
+
* devDependencies
|
|
70
|
+
* @twin.org/api-processors bumped from 0.0.1-next.34 to 0.0.1-next.35
|
|
71
|
+
|
|
72
|
+
## [0.0.1-next.34](https://github.com/twinfoundation/api/compare/api-server-fastify-v0.0.1-next.33...api-server-fastify-v0.0.1-next.34) (2025-05-27)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
### Miscellaneous Chores
|
|
76
|
+
|
|
77
|
+
* **api-server-fastify:** Synchronize repo versions
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
### Dependencies
|
|
81
|
+
|
|
82
|
+
* The following workspace dependencies were updated
|
|
83
|
+
* dependencies
|
|
84
|
+
* @twin.org/api-models bumped from 0.0.1-next.33 to 0.0.1-next.34
|
|
85
|
+
* @twin.org/api-core bumped from 0.0.1-next.33 to 0.0.1-next.34
|
|
86
|
+
* devDependencies
|
|
87
|
+
* @twin.org/api-processors bumped from 0.0.1-next.33 to 0.0.1-next.34
|
|
88
|
+
|
|
89
|
+
## [0.0.1-next.33](https://github.com/twinfoundation/api/compare/api-server-fastify-v0.0.1-next.32...api-server-fastify-v0.0.1-next.33) (2025-04-17)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
### Features
|
|
93
|
+
|
|
94
|
+
* use shared store mechanism ([#19](https://github.com/twinfoundation/api/issues/19)) ([32116df](https://github.com/twinfoundation/api/commit/32116df3b4380a30137f5056f242a5c99afa2df9))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
### Dependencies
|
|
98
|
+
|
|
99
|
+
* The following workspace dependencies were updated
|
|
100
|
+
* dependencies
|
|
101
|
+
* @twin.org/api-models bumped from 0.0.1-next.32 to 0.0.1-next.33
|
|
102
|
+
* @twin.org/api-core bumped from 0.0.1-next.32 to 0.0.1-next.33
|
|
103
|
+
* devDependencies
|
|
104
|
+
* @twin.org/api-processors bumped from 0.0.1-next.32 to 0.0.1-next.33
|
|
105
|
+
|
|
106
|
+
## [0.0.1-next.32](https://github.com/twinfoundation/api/compare/api-server-fastify-v0.0.1-next.31...api-server-fastify-v0.0.1-next.32) (2025-03-28)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
### Miscellaneous Chores
|
|
110
|
+
|
|
111
|
+
* **api-server-fastify:** Synchronize repo versions
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
### Dependencies
|
|
115
|
+
|
|
116
|
+
* The following workspace dependencies were updated
|
|
117
|
+
* dependencies
|
|
118
|
+
* @twin.org/api-models bumped from 0.0.1-next.31 to 0.0.1-next.32
|
|
119
|
+
* @twin.org/api-core bumped from 0.0.1-next.31 to 0.0.1-next.32
|
|
120
|
+
* devDependencies
|
|
121
|
+
* @twin.org/api-processors bumped from next to 0.0.1-next.32
|
|
122
|
+
|
|
123
|
+
## v0.0.1-next.31
|
|
4
124
|
|
|
5
125
|
- Initial Release
|
|
@@ -8,29 +8,23 @@ Implementation of the web server using Fastify.
|
|
|
8
8
|
|
|
9
9
|
## Constructors
|
|
10
10
|
|
|
11
|
-
###
|
|
11
|
+
### Constructor
|
|
12
12
|
|
|
13
|
-
> **new FastifyWebServer**(`options
|
|
13
|
+
> **new FastifyWebServer**(`options?`): `FastifyWebServer`
|
|
14
14
|
|
|
15
15
|
Create a new instance of FastifyWebServer.
|
|
16
16
|
|
|
17
17
|
#### Parameters
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
##### options?
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
• **options.loggingConnectorType?**: `string`
|
|
24
|
-
|
|
25
|
-
The type of the logging connector to use, if undefined, no logging will happen.
|
|
26
|
-
|
|
27
|
-
• **options.config?**: `FastifyServerOptions`
|
|
21
|
+
[`IFastifyWebServerConstructorOptions`](../interfaces/IFastifyWebServerConstructorOptions.md)
|
|
28
22
|
|
|
29
|
-
|
|
23
|
+
The options for the server.
|
|
30
24
|
|
|
31
25
|
#### Returns
|
|
32
26
|
|
|
33
|
-
|
|
27
|
+
`FastifyWebServer`
|
|
34
28
|
|
|
35
29
|
## Properties
|
|
36
30
|
|
|
@@ -44,13 +38,13 @@ Runtime name for the class.
|
|
|
44
38
|
|
|
45
39
|
### getInstance()
|
|
46
40
|
|
|
47
|
-
> **getInstance**(): `FastifyInstance
|
|
41
|
+
> **getInstance**(): `FastifyInstance`
|
|
48
42
|
|
|
49
43
|
Get the web server instance.
|
|
50
44
|
|
|
51
45
|
#### Returns
|
|
52
46
|
|
|
53
|
-
`FastifyInstance
|
|
47
|
+
`FastifyInstance`
|
|
54
48
|
|
|
55
49
|
The web server instance.
|
|
56
50
|
|
|
@@ -62,21 +56,39 @@ The web server instance.
|
|
|
62
56
|
|
|
63
57
|
### build()
|
|
64
58
|
|
|
65
|
-
> **build**(`restRouteProcessors
|
|
59
|
+
> **build**(`restRouteProcessors?`, `restRoutes?`, `socketRouteProcessors?`, `socketRoutes?`, `options?`): `Promise`\<`void`\>
|
|
66
60
|
|
|
67
61
|
Build the server.
|
|
68
62
|
|
|
69
63
|
#### Parameters
|
|
70
64
|
|
|
71
|
-
|
|
65
|
+
##### restRouteProcessors?
|
|
66
|
+
|
|
67
|
+
`IRestRouteProcessor`[]
|
|
68
|
+
|
|
69
|
+
The processors for incoming requests over REST.
|
|
72
70
|
|
|
73
|
-
|
|
71
|
+
##### restRoutes?
|
|
74
72
|
|
|
75
|
-
|
|
73
|
+
`IRestRoute`\<`any`, `any`\>[]
|
|
76
74
|
|
|
77
75
|
The REST routes.
|
|
78
76
|
|
|
79
|
-
|
|
77
|
+
##### socketRouteProcessors?
|
|
78
|
+
|
|
79
|
+
`ISocketRouteProcessor`[]
|
|
80
|
+
|
|
81
|
+
The processors for incoming requests over Sockets.
|
|
82
|
+
|
|
83
|
+
##### socketRoutes?
|
|
84
|
+
|
|
85
|
+
`ISocketRoute`\<`any`, `any`\>[]
|
|
86
|
+
|
|
87
|
+
The socket routes.
|
|
88
|
+
|
|
89
|
+
##### options?
|
|
90
|
+
|
|
91
|
+
`IWebServerOptions`
|
|
80
92
|
|
|
81
93
|
Options for building the server.
|
|
82
94
|
|
package/docs/reference/index.md
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Interface: IFastifyWebServerConfig
|
|
2
|
+
|
|
3
|
+
The configuration for the Fastify web server.
|
|
4
|
+
|
|
5
|
+
## Properties
|
|
6
|
+
|
|
7
|
+
### web?
|
|
8
|
+
|
|
9
|
+
> `optional` **web**: `Partial`\<`FastifyServerOptions`\>
|
|
10
|
+
|
|
11
|
+
The web server options.
|
|
12
|
+
|
|
13
|
+
***
|
|
14
|
+
|
|
15
|
+
### socket?
|
|
16
|
+
|
|
17
|
+
> `optional` **socket**: `Partial`\<`ServerOptions`\>
|
|
18
|
+
|
|
19
|
+
The socket server options.
|
|
20
|
+
|
|
21
|
+
***
|
|
22
|
+
|
|
23
|
+
### includeErrorStack?
|
|
24
|
+
|
|
25
|
+
> `optional` **includeErrorStack**: `boolean`
|
|
26
|
+
|
|
27
|
+
Include the stack with errors.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Interface: IFastifyWebServerConstructorOptions
|
|
2
|
+
|
|
3
|
+
The options for the Fastify web server constructor.
|
|
4
|
+
|
|
5
|
+
## Properties
|
|
6
|
+
|
|
7
|
+
### loggingConnectorType?
|
|
8
|
+
|
|
9
|
+
> `optional` **loggingConnectorType**: `string`
|
|
10
|
+
|
|
11
|
+
The type of the logging connector to use, if undefined, no logging will happen.
|
|
12
|
+
|
|
13
|
+
***
|
|
14
|
+
|
|
15
|
+
### config?
|
|
16
|
+
|
|
17
|
+
> `optional` **config**: [`IFastifyWebServerConfig`](IFastifyWebServerConfig.md)
|
|
18
|
+
|
|
19
|
+
Additional configuration for the server.
|
|
20
|
+
|
|
21
|
+
***
|
|
22
|
+
|
|
23
|
+
### mimeTypeProcessors?
|
|
24
|
+
|
|
25
|
+
> `optional` **mimeTypeProcessors**: `IMimeTypeProcessor`[]
|
|
26
|
+
|
|
27
|
+
Additional MIME type processors.
|
package/locales/en.json
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
"stopped": "The Web Server was stopped",
|
|
8
8
|
"badRequest": "The web server could not handle the request",
|
|
9
9
|
"restRouteAdded": "Added REST route \"{route}\" \"{method}\"",
|
|
10
|
-
"
|
|
10
|
+
"socketRouteAdded": "Added socket route \"{route}\"",
|
|
11
|
+
"noRestProcessors": "You must configure at least one REST processor",
|
|
12
|
+
"noSocketProcessors": "You must configure at least one socket processor",
|
|
13
|
+
"postProcessorError": "There was a failure after in a post processor for route \"{route}\""
|
|
11
14
|
}
|
|
12
15
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@twin.org/api-server-fastify",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2-next.1",
|
|
4
4
|
"description": "Use Fastify as the core web server for APIs",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -14,24 +14,26 @@
|
|
|
14
14
|
"node": ">=20.0.0"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@fastify/compress": "8.0.
|
|
18
|
-
"@fastify/cors": "
|
|
19
|
-
"@twin.org/api-core": "0.0.
|
|
20
|
-
"@twin.org/api-models": "0.0.
|
|
17
|
+
"@fastify/compress": "8.0.3",
|
|
18
|
+
"@fastify/cors": "11.0.1",
|
|
19
|
+
"@twin.org/api-core": "0.0.2-next.1",
|
|
20
|
+
"@twin.org/api-models": "0.0.2-next.1",
|
|
21
|
+
"@twin.org/api-processors": "0.0.2-next.1",
|
|
21
22
|
"@twin.org/core": "next",
|
|
22
23
|
"@twin.org/logging-models": "next",
|
|
23
24
|
"@twin.org/nameof": "next",
|
|
24
25
|
"@twin.org/web": "next",
|
|
25
|
-
"fastify": "5.
|
|
26
|
+
"fastify": "5.3.3",
|
|
27
|
+
"socket.io": "4.8.1"
|
|
26
28
|
},
|
|
27
29
|
"main": "./dist/cjs/index.cjs",
|
|
28
30
|
"module": "./dist/esm/index.mjs",
|
|
29
31
|
"types": "./dist/types/index.d.ts",
|
|
30
32
|
"exports": {
|
|
31
33
|
".": {
|
|
34
|
+
"types": "./dist/types/index.d.ts",
|
|
32
35
|
"require": "./dist/cjs/index.cjs",
|
|
33
|
-
"import": "./dist/esm/index.mjs"
|
|
34
|
-
"types": "./dist/types/index.d.ts"
|
|
36
|
+
"import": "./dist/esm/index.mjs"
|
|
35
37
|
}
|
|
36
38
|
},
|
|
37
39
|
"files": [
|