@clairejs/server 3.15.2 → 3.16.0

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 (85) hide show
  1. package/.mocharc.json +3 -0
  2. package/dist/common/AbstractController.js +3 -0
  3. package/dist/common/ControllerMetadata.js +1 -0
  4. package/dist/common/FileOperation.js +6 -0
  5. package/dist/common/ServerModelMetadata.js +1 -0
  6. package/dist/common/Transactionable.js +17 -0
  7. package/dist/common/auth/AbstractPrincipalResolver.js +2 -0
  8. package/dist/common/auth/IPrincipal.js +1 -0
  9. package/dist/common/constants.js +7 -0
  10. package/dist/common/decorator.d.ts +2 -2
  11. package/dist/common/decorator.js +6 -0
  12. package/dist/common/request/EndpointMetadata.js +1 -0
  13. package/dist/common/request/HttpData.js +1 -0
  14. package/dist/common/request/HttpEndpoint.js +1 -0
  15. package/dist/common/request/JobData.js +1 -0
  16. package/dist/common/request/MountedEndpointInfo.js +1 -0
  17. package/dist/common/request/RequestOptions.js +1 -0
  18. package/dist/common/request/SocketData.js +1 -0
  19. package/dist/common/request/types.d.ts +1 -1
  20. package/dist/common/request/types.js +1 -0
  21. package/dist/controllers/FileManageController.js +90 -0
  22. package/dist/controllers/FileUploadController.js +64 -0
  23. package/dist/controllers/dto/system.js +14 -0
  24. package/dist/controllers/dto/upload.js +205 -0
  25. package/dist/http/auth/AbstractHttpAuthorizer.js +2 -0
  26. package/dist/http/common/HttpRequest.js +72 -0
  27. package/dist/http/common/HttpResponse.js +62 -0
  28. package/dist/http/controller/AbstractHttpController.js +21 -0
  29. package/dist/http/controller/AbstractHttpMiddleware.js +2 -0
  30. package/dist/http/controller/AbstractHttpRequestHandler.js +69 -0
  31. package/dist/http/controller/CrudHttpController.js +302 -0
  32. package/dist/http/controller/DefaultHttpRequestHandler.js +143 -0
  33. package/dist/http/decorators.d.ts +1 -1
  34. package/dist/http/decorators.js +86 -0
  35. package/dist/http/file-upload/AbstractFileUploadHandler.js +2 -0
  36. package/dist/http/file-upload/FileUploadHandler.js +41 -0
  37. package/dist/http/file-upload/types.d.ts +1 -1
  38. package/dist/http/file-upload/types.js +1 -0
  39. package/dist/http/repository/AbstractRepository.js +26 -0
  40. package/dist/http/repository/DtoRepository.d.ts +3 -3
  41. package/dist/http/repository/DtoRepository.js +204 -0
  42. package/dist/http/repository/ICrudRepository.js +1 -0
  43. package/dist/http/repository/ModelRepository.js +696 -0
  44. package/dist/http/security/AbstractAccessCondition.js +2 -0
  45. package/dist/http/security/access-conditions/FilterModelFieldAccessCondition.js +30 -0
  46. package/dist/http/security/access-conditions/MaximumQueryLimit.js +31 -0
  47. package/dist/http/security/cors.js +1 -0
  48. package/dist/http/utils.js +32 -0
  49. package/dist/index.js +75 -1
  50. package/dist/job/AbstractJobController.js +9 -0
  51. package/dist/job/AbstractJobRepository.js +2 -0
  52. package/dist/job/AbstractJobScheduler.js +48 -0
  53. package/dist/job/AwsJobScheduler.js +405 -0
  54. package/dist/job/LocalJobScheduler.js +273 -0
  55. package/dist/job/decorators.js +57 -0
  56. package/dist/job/interfaces.js +10 -0
  57. package/dist/logging/FileLogMedium.js +44 -0
  58. package/dist/services/AbstractFileService.js +28 -0
  59. package/dist/services/AbstractMailService.js +2 -0
  60. package/dist/services/AbstractService.js +3 -0
  61. package/dist/services/AbstractSmsService.js +2 -0
  62. package/dist/services/implementations/LocalFileService.js +42 -0
  63. package/dist/services/implementations/LocalMailService.js +27 -0
  64. package/dist/services/implementations/LocalSmsService.js +17 -0
  65. package/dist/services/implementations/S3FileService.js +107 -0
  66. package/dist/services/implementations/SesMailService.js +64 -0
  67. package/dist/socket/AbstractServerSocket.js +44 -0
  68. package/dist/socket/AbstractServerSocketManager.d.ts +1 -1
  69. package/dist/socket/AbstractServerSocketManager.js +348 -0
  70. package/dist/socket/AbstractSocketConnectionHandler.js +2 -0
  71. package/dist/socket/AbstractSocketController.d.ts +3 -3
  72. package/dist/socket/AbstractSocketController.js +12 -0
  73. package/dist/socket/AwsSocketManager.d.ts +2 -2
  74. package/dist/socket/AwsSocketManager.js +160 -0
  75. package/dist/socket/IServerSocket.js +1 -0
  76. package/dist/socket/LocalSocketManager.js +292 -0
  77. package/dist/system/ClaireServer.js +78 -0
  78. package/dist/system/ExpressWrapper.js +122 -0
  79. package/dist/system/LambdaWrapper.js +151 -0
  80. package/dist/system/ServerGlobalStore.js +1 -0
  81. package/dist/system/lamba-request-mapper.js +49 -0
  82. package/dist/system/locale/LocaleEntry.js +13 -0
  83. package/dist/system/locale/LocaleTranslation.js +47 -0
  84. package/dist/system/locale/decorators.js +14 -0
  85. package/package.json +13 -20
@@ -0,0 +1,292 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { AbstractLogger, Initable, LogContext, MessageType, SocketMethod } from "@clairejs/core";
11
+ import WebSocket from "ws";
12
+ import parseurl from "parseurl";
13
+ import queryString from "query-string";
14
+ import Redis from "ioredis";
15
+ import { AbstractServerSocketManager } from "./AbstractServerSocketManager";
16
+ import { AbstractServerSocket } from "./AbstractServerSocket";
17
+ import { AbstractRequestAuthorizer } from "../http/auth/AbstractHttpAuthorizer";
18
+ import { AbstractPrincipalResolver } from "../common/auth/AbstractPrincipalResolver";
19
+ var RedisSocketCommand;
20
+ (function (RedisSocketCommand) {
21
+ RedisSocketCommand["SEND"] = "SEND";
22
+ RedisSocketCommand["CLOSE"] = "CLOSE";
23
+ RedisSocketCommand["SAVE"] = "SAVE";
24
+ })(RedisSocketCommand || (RedisSocketCommand = {}));
25
+ class RedisSocket extends AbstractServerSocket {
26
+ socketChannelName;
27
+ redisClient;
28
+ socketInfo;
29
+ constructor(socketChannelName, redisClient, socketInfo) {
30
+ super(socketInfo);
31
+ this.socketChannelName = socketChannelName;
32
+ this.redisClient = redisClient;
33
+ this.socketInfo = socketInfo;
34
+ }
35
+ async physicSend(data) {
36
+ await this.redisClient.publish(this.socketChannelName, JSON.stringify({
37
+ type: RedisSocketCommand.SEND,
38
+ data: data,
39
+ }));
40
+ }
41
+ async disconnect(err) {
42
+ await this.redisClient.publish(this.socketChannelName, JSON.stringify({
43
+ type: RedisSocketCommand.CLOSE,
44
+ data: err,
45
+ }));
46
+ }
47
+ async saveSocketInfo() {
48
+ await this.redisClient.publish(this.socketChannelName, JSON.stringify({
49
+ type: RedisSocketCommand.SAVE,
50
+ data: this.socketInfo,
51
+ }));
52
+ }
53
+ }
54
+ class ExpressSocket extends AbstractServerSocket {
55
+ socketManager;
56
+ socket;
57
+ socketInfo;
58
+ infoSaver;
59
+ constructor(socketManager, socket, socketInfo, infoSaver) {
60
+ super(socketInfo);
61
+ this.socketManager = socketManager;
62
+ this.socket = socket;
63
+ this.socketInfo = socketInfo;
64
+ this.infoSaver = infoSaver;
65
+ }
66
+ async saveSocketInfo() {
67
+ //-- save to redis
68
+ await this.infoSaver(this.socketInfo);
69
+ }
70
+ async physicSend(data) {
71
+ this.socket.send(JSON.stringify(data));
72
+ }
73
+ async disconnect(err) {
74
+ this.socket.close(1000, err && JSON.stringify(err));
75
+ await this.socketManager.removeSocket(this.getId());
76
+ }
77
+ }
78
+ let LocalSocketManager = class LocalSocketManager extends AbstractServerSocketManager {
79
+ authenticationRequired;
80
+ subClient;
81
+ channelSubClient;
82
+ sendClient;
83
+ logger;
84
+ principalResolver;
85
+ requestAuthorizer;
86
+ allSockets = [];
87
+ subscribedChannels = [];
88
+ constructor(authenticationRequired, subClient, //-- use to subscribe to the socket-self channels
89
+ channelSubClient, //-- use to subscribe to channels
90
+ sendClient, //-- use to publish data
91
+ logger, principalResolver, requestAuthorizer) {
92
+ super(authenticationRequired, logger, sendClient, principalResolver, requestAuthorizer);
93
+ this.authenticationRequired = authenticationRequired;
94
+ this.subClient = subClient;
95
+ this.channelSubClient = channelSubClient;
96
+ this.sendClient = sendClient;
97
+ this.logger = logger;
98
+ this.principalResolver = principalResolver;
99
+ this.requestAuthorizer = requestAuthorizer;
100
+ }
101
+ async init() {
102
+ this.logger.info("LocalSocketManager init");
103
+ this.channelSubClient.on("message", (channel, message) => {
104
+ this.logger.debug("channelSubClient: ", message, channel);
105
+ this.channelMessageListenner(message, channel);
106
+ });
107
+ this.subClient.on("message", (channel, message) => {
108
+ this.logger.debug("subClient: ", message, channel);
109
+ this.socketMessageListener(message, channel);
110
+ });
111
+ }
112
+ exit() {
113
+ this.logger.info("LocalSocketManager exit");
114
+ }
115
+ channelMessageListenner(message, channel) {
116
+ this.logger.debug("Receiving message from channel", channel, message);
117
+ const socketMessage = JSON.parse(message);
118
+ //-- only broadcast plain message
119
+ if (socketMessage.type === MessageType.PLAIN) {
120
+ this.allSockets.forEach((socket) => {
121
+ //-- socket.send will check for necessary permission
122
+ socket.send(socketMessage.data.message, socketMessage.data.channel);
123
+ });
124
+ }
125
+ }
126
+ socketMessageListener(message, channel) {
127
+ this.logger.debug("Receiving message from remote socket channel", channel, message);
128
+ const socketId = channel.substring(this.getSocketChannelKeyPrefix().length);
129
+ //-- find the socket
130
+ const socket = this.allSockets.find((s) => s.getId() === socketId);
131
+ if (socket) {
132
+ //-- handle message
133
+ const command = JSON.parse(message);
134
+ switch (command.type) {
135
+ case RedisSocketCommand.SEND:
136
+ socket.physicSend(command.data);
137
+ break;
138
+ case RedisSocketCommand.SAVE:
139
+ socket.socketInfo.channels = command.data;
140
+ socket.saveSocketInfo();
141
+ break;
142
+ case RedisSocketCommand.CLOSE:
143
+ socket.disconnect(command.data);
144
+ break;
145
+ default:
146
+ //-- ignore
147
+ this.logger.debug("Invalid remote command", command);
148
+ break;
149
+ }
150
+ }
151
+ }
152
+ getUniqueDataChannelName(channel) {
153
+ return `CHANNEL_DATA:${channel}`;
154
+ }
155
+ getSocketChannelKeyPrefix() {
156
+ return "SOCKET_CHANNEL:";
157
+ }
158
+ getSocketChannelKey(socketId) {
159
+ return `${this.getSocketChannelKeyPrefix()}${socketId}`;
160
+ }
161
+ async broadcastToChannel(channel, data) {
162
+ const formattedData = this.formatBroadcastData(channel, data);
163
+ await this.redisClient.publish(this.getUniqueDataChannelName(channel), JSON.stringify(formattedData));
164
+ }
165
+ async addSocketToChannel(socketId, channelInfos) {
166
+ const socket = await super.addSocketToChannel(socketId, channelInfos);
167
+ const notYetSubscribed = channelInfos.map((c) => c.name).filter((c) => !this.subscribedChannels.includes(c));
168
+ if (notYetSubscribed.length) {
169
+ await Promise.all(notYetSubscribed.map((channelName) => {
170
+ return this.channelSubClient.subscribe(this.getUniqueDataChannelName(channelName));
171
+ }));
172
+ this.subscribedChannels.push(...notYetSubscribed);
173
+ }
174
+ return socket;
175
+ }
176
+ async removeSocketFromChannel(socketId, channels, persist = true) {
177
+ await super.removeSocketFromChannel(socketId, channels, persist);
178
+ await Promise.all(channels.map((channel) => this.channelSubClient.unsubscribe(this.getUniqueChannelName(channel))));
179
+ this.subscribedChannels = this.subscribedChannels.filter((c) => !channels.includes(c));
180
+ }
181
+ async addSocket(socketInfo, { socket }) {
182
+ const expressSocket = new ExpressSocket(this, socket, socketInfo, async (socketInfo) => {
183
+ await this.redisClient.hset(this.getSocketInfoHashKey(), socketInfo.id, JSON.stringify(socketInfo));
184
+ });
185
+ //-- create a channel on redis for cross-instance communication
186
+ const channelKey = this.getSocketChannelKey(socketInfo.id);
187
+ //-- subscribe to that channel
188
+ await this.subClient.subscribe(channelKey);
189
+ this.allSockets.push(expressSocket);
190
+ return expressSocket;
191
+ }
192
+ async removeSocket(socketId, physicRemove = true) {
193
+ await super.removeSocket(socketId, physicRemove);
194
+ //-- unsubscribe from that channel
195
+ const channelKey = this.getSocketChannelKey(socketId);
196
+ await this.subClient.unsubscribe(channelKey);
197
+ //-- filter out socket
198
+ this.allSockets = this.allSockets.filter((s) => s.getId() !== socketId);
199
+ }
200
+ async getById(socketId) {
201
+ let socket = this.allSockets.find((s) => s.getId() === socketId);
202
+ if (!socket) {
203
+ //-- check redis
204
+ const socketInfo = await new Promise((resolve) => this.redisClient
205
+ .hget(this.getSocketInfoHashKey(), socketId)
206
+ .then((data) => resolve(data ? JSON.parse(data) : undefined))
207
+ .catch((err) => {
208
+ this.logger.error(err);
209
+ resolve(undefined);
210
+ }));
211
+ //-- if found then create redis socket
212
+ if (socketInfo) {
213
+ socket = new RedisSocket(this.getSocketChannelKey(socketId), this.sendClient, socketInfo);
214
+ }
215
+ }
216
+ return socket;
217
+ }
218
+ async getSocketsByChannel(channel) {
219
+ //-- get socket ids
220
+ const ids = await this.getSocketIdsOfChannel(channel);
221
+ const sockets = await Promise.all(ids.map((id) => this.getById(id)));
222
+ return sockets.filter((s) => !!s);
223
+ }
224
+ configure(httpServer) {
225
+ if (!httpServer) {
226
+ return;
227
+ }
228
+ this.logger.debug("Local socket manager configure");
229
+ const socketServer = new WebSocket.Server({ server: httpServer });
230
+ socketServer.on("connection", (socket, req) => {
231
+ const socketId = req.headers["sec-websocket-key"];
232
+ let socketReady = false;
233
+ let bufferMessages = [];
234
+ //-- call handle connection
235
+ const result = parseurl({ url: req.url });
236
+ const query = queryString.parse(decodeURIComponent(result.search || ""));
237
+ this.handle({
238
+ method: SocketMethod.CONNECT,
239
+ socketId: socketId,
240
+ data: { queries: query, socket },
241
+ })
242
+ .then(() => {
243
+ socketReady = true;
244
+ for (const m of bufferMessages) {
245
+ this.handle(m).catch((err) => {
246
+ this.logger.error(err);
247
+ });
248
+ }
249
+ })
250
+ .catch((err) => {
251
+ //-- free up buffered messages
252
+ bufferMessages = [];
253
+ //-- close with code 1000
254
+ socket.close(1000, `${err.name}:${err.message}`);
255
+ });
256
+ socket.onmessage = (event) => {
257
+ const data = {
258
+ method: SocketMethod.MESSAGE,
259
+ socketId: socketId,
260
+ data: event.data && JSON.parse(event.data),
261
+ };
262
+ if (socketReady) {
263
+ this.handle(data).catch((err) => {
264
+ this.logger.error(err);
265
+ });
266
+ }
267
+ else {
268
+ bufferMessages.push(data);
269
+ }
270
+ };
271
+ socket.onclose = () => {
272
+ this.handle({
273
+ method: SocketMethod.DISCONNECT,
274
+ socketId: socketId,
275
+ }).catch((err) => {
276
+ this.logger.error(err);
277
+ });
278
+ };
279
+ });
280
+ }
281
+ };
282
+ LocalSocketManager = __decorate([
283
+ Initable(),
284
+ LogContext(),
285
+ __metadata("design:paramtypes", [Boolean, Redis,
286
+ Redis,
287
+ Redis,
288
+ AbstractLogger,
289
+ AbstractPrincipalResolver,
290
+ AbstractRequestAuthorizer])
291
+ ], LocalSocketManager);
292
+ export { LocalSocketManager };
@@ -0,0 +1,78 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { AbstractLogger, ClaireApp, LogContext, getGlobalStore } from "@clairejs/core";
11
+ import { ExitCode } from "../common/constants";
12
+ import { AbstractHttpRequestHandler } from "../http/controller/AbstractHttpRequestHandler";
13
+ import { AbstractServerSocketManager } from "../socket/AbstractServerSocketManager";
14
+ import { getEndpointId } from "../http/utils";
15
+ let ClaireServer = class ClaireServer extends ClaireApp {
16
+ logger;
17
+ httpRequestHandler;
18
+ socketManager;
19
+ booted = false;
20
+ constructor(logger, httpRequestHandler, socketManager) {
21
+ super();
22
+ this.logger = logger;
23
+ this.httpRequestHandler = httpRequestHandler;
24
+ this.socketManager = socketManager;
25
+ }
26
+ async init() {
27
+ if (this.booted) {
28
+ return;
29
+ }
30
+ const mountedEndpointInfo = [];
31
+ if (this.httpRequestHandler) {
32
+ const infos = await this.httpRequestHandler.getMountedEndpointInfo();
33
+ mountedEndpointInfo.push(...infos);
34
+ }
35
+ if (this.socketManager) {
36
+ const infos = await this.socketManager.getMountedEndpointInfo();
37
+ mountedEndpointInfo.push(...infos);
38
+ }
39
+ //-- save mounted endpoint info in server store to prevent circular dependency when resolving http request handler in controller
40
+ for (const endpoint of mountedEndpointInfo) {
41
+ this.logger.info(`Mounting: ${getEndpointId(endpoint.endpoint)}`);
42
+ }
43
+ getGlobalStore().mountedEndpointInfo = mountedEndpointInfo;
44
+ this.logger.debug("Claire server initing");
45
+ //-- handle exceptions
46
+ this.logger.debug(`Setting up exception handlers`);
47
+ process.on("SIGTERM", () => {
48
+ this.logger.warn("SIGTERM interrupt signal");
49
+ return this.stop(ExitCode.SIGTERM_INTERUPTION);
50
+ });
51
+ process.on("SIGINT", () => {
52
+ this.logger.warn("SIGINT interrupt signal");
53
+ return this.stop(ExitCode.SIGTERM_INTERUPTION);
54
+ });
55
+ process.on("uncaughtException", (err) => {
56
+ this.logger.error("uncaughtException", err.name, err.stack);
57
+ });
58
+ process.on("unhandledRejection", (err) => {
59
+ this.logger.error("unhandledRejection", err.name, err.stack);
60
+ });
61
+ this.booted = true;
62
+ }
63
+ exit() {
64
+ super.exit();
65
+ }
66
+ stop(code) {
67
+ this.logger.debug("Server is shutting down");
68
+ this.exit();
69
+ process.exit(code);
70
+ }
71
+ };
72
+ ClaireServer = __decorate([
73
+ LogContext(),
74
+ __metadata("design:paramtypes", [AbstractLogger,
75
+ AbstractHttpRequestHandler,
76
+ AbstractServerSocketManager])
77
+ ], ClaireServer);
78
+ export { ClaireServer };
@@ -0,0 +1,122 @@
1
+ import { Errors, getServiceProvider, SocketMethod } from "@clairejs/core";
2
+ import express from "express";
3
+ import http from "http";
4
+ import cookieParser from "cookie-parser";
5
+ import expressFileUpload from "express-fileupload";
6
+ import cors from "cors";
7
+ import { AbstractServerSocketManager } from "../socket/AbstractServerSocketManager";
8
+ import { AbstractHttpRequestHandler } from "../http/controller/AbstractHttpRequestHandler";
9
+ export class ExpressWrapper {
10
+ server;
11
+ config;
12
+ injector = getServiceProvider().getInjector();
13
+ socketManager;
14
+ httpRequestHandler;
15
+ httpServer;
16
+ constructor(server, config) {
17
+ this.server = server;
18
+ this.config = config;
19
+ this.socketManager = this.injector.resolveOptional(AbstractServerSocketManager);
20
+ this.httpRequestHandler = this.injector.resolveOptional(AbstractHttpRequestHandler);
21
+ }
22
+ close() {
23
+ this.httpServer?.close();
24
+ this.server.exit();
25
+ }
26
+ async listen(listenPort) {
27
+ await this.server.init();
28
+ await this.injector.initInstances();
29
+ const app = express();
30
+ //-- setup http listener
31
+ const httpServer = http.createServer(app);
32
+ this.socketManager?.configure(httpServer);
33
+ if (this.httpRequestHandler?.corsConfig) {
34
+ app.use(cors({
35
+ allowedHeaders: this.httpRequestHandler.corsConfig.headers,
36
+ credentials: this.httpRequestHandler.corsConfig.credentials,
37
+ origin: this.httpRequestHandler.corsConfig.origins,
38
+ methods: this.httpRequestHandler.corsConfig.methods,
39
+ }));
40
+ }
41
+ const requestLimitBytes = (this.config?.requestSetting?.maxBodySizeKB || 10240) * 1024;
42
+ app.use(cookieParser());
43
+ app.use(express.json({ limit: requestLimitBytes }));
44
+ app.use(express.urlencoded({ extended: false, limit: requestLimitBytes }));
45
+ app.use(expressFileUpload());
46
+ app.use((req, _res, next) => {
47
+ if (req.files) {
48
+ if (!req.body) {
49
+ req.body = {};
50
+ }
51
+ Object.keys(req.files).forEach((file) => {
52
+ req.body[file] = req.files[file];
53
+ });
54
+ }
55
+ next();
56
+ });
57
+ app.use(express.raw({
58
+ limit: requestLimitBytes,
59
+ type: (req) => !["application/json", "application/x-www-form-urlencoded"].find((type) => req.headers["content-type"]?.includes(type)),
60
+ }));
61
+ app.all("*", (req, res) => {
62
+ (async () => {
63
+ const isAwsGatewaySocket = !!req.headers["x-amzn-apigateway-api-id"];
64
+ if (isAwsGatewaySocket) {
65
+ if (!this.socketManager) {
66
+ throw Errors.SYSTEM_ERROR("Socket manager not found");
67
+ }
68
+ const socketData = {
69
+ socketId: req.body.connectionId,
70
+ data: req.body.data,
71
+ method: req.body.type.toLowerCase(),
72
+ };
73
+ //-- if connect method then send url for authentication parsing
74
+ if (socketData.method === SocketMethod.CONNECT) {
75
+ //-- parse querystring, the query string has format of: "{p1=v1, p2=v2}"
76
+ const queryString = req.body.data.querystring;
77
+ const queries = queryString
78
+ .substring(1, queryString.length - 1)
79
+ .split(",")
80
+ .map((pair) => pair.trim().split("="))
81
+ .reduce((collector, pair) => Object.assign(collector, { [pair[0]]: pair[1] }), {});
82
+ socketData.data = { queries };
83
+ }
84
+ await this.socketManager.handle(socketData);
85
+ //-- send success response
86
+ res.status(200).send();
87
+ }
88
+ else {
89
+ if (!this.httpRequestHandler) {
90
+ throw Errors.SYSTEM_ERROR("Http request handler not found");
91
+ }
92
+ const httpData = {
93
+ clientIP: req.ip || req.headers["x-forwarded-for"],
94
+ fullPath: req.url,
95
+ method: req.method.toLowerCase(),
96
+ headers: req.headers,
97
+ body: req.body,
98
+ cookies: req.cookies,
99
+ };
100
+ //-- handle options
101
+ const result = await this.httpRequestHandler.handle(httpData);
102
+ if (result.headers) {
103
+ Object.keys(result.headers).forEach((key) => {
104
+ res.set(key, result.headers[key]);
105
+ });
106
+ }
107
+ if (result.cookies) {
108
+ Object.keys(result.cookies).forEach((key) => {
109
+ res.cookie(key, result.cookies[key].value, result.cookies[key].options || {});
110
+ });
111
+ }
112
+ res.status(result.code || 200).send(result.value);
113
+ }
114
+ })().catch((err) => {
115
+ res.status(err.code || 500).json({ name: err.name, message: err.message, code: err.code || 500 });
116
+ });
117
+ });
118
+ return new Promise((resolve, reject) => {
119
+ this.httpServer = httpServer.listen(listenPort, resolve).on("error", reject);
120
+ });
121
+ }
122
+ }
@@ -0,0 +1,151 @@
1
+ import { getServiceProvider, HttpMethod, SocketMethod, Errors } from "@clairejs/core";
2
+ import { AbstractHttpRequestHandler } from "../http/controller/AbstractHttpRequestHandler";
3
+ import { AwsJobScheduler } from "../job/AwsJobScheduler";
4
+ import { CRON_REQUEST_METHOD, INTERVAL_REQUEST_METHOD } from "../job/interfaces";
5
+ import { AbstractServerSocketManager } from "../socket/AbstractServerSocketManager";
6
+ const convertCorsToHeaders = (origin, cors) => {
7
+ const headers = {};
8
+ if (cors?.credentials) {
9
+ headers["Access-Control-Allow-Credentials"] = "true";
10
+ }
11
+ if (cors?.headers) {
12
+ headers["Access-Control-Allow-Headers"] =
13
+ typeof cors.headers === "string" ? cors.headers : cors.headers.join(", ");
14
+ }
15
+ if (cors?.methods) {
16
+ headers["Access-Control-Allow-Methods"] =
17
+ typeof cors.methods === "string" ? cors.methods : cors.methods.join(", ");
18
+ }
19
+ if (cors?.origins) {
20
+ headers["Access-Control-Allow-Origin"] =
21
+ typeof cors.origins === "string"
22
+ ? cors.origins
23
+ : cors.origins.find((org) => origin.match(org))
24
+ ? origin
25
+ : "";
26
+ }
27
+ return headers;
28
+ };
29
+ const toApiGatewayFormat = (response) => ({
30
+ statusCode: response.code || 200,
31
+ body: response.value && JSON.stringify(response.value),
32
+ headers: response.headers,
33
+ cookies: Object.keys(response.cookies || {}).map((key) => {
34
+ const cookieValue = response.cookies[key];
35
+ let base = `${key}=${cookieValue.value}`;
36
+ if (cookieValue.options?.maxAge) {
37
+ base += `; MaxAge=${cookieValue.options.maxAge}`;
38
+ }
39
+ if (cookieValue.options?.expires) {
40
+ base += `; Expires=${cookieValue.options.expires.toUTCString()}`;
41
+ }
42
+ if (cookieValue.options?.httpOnly) {
43
+ base += `; HttpOnly`;
44
+ }
45
+ if (cookieValue.options?.path) {
46
+ base += `; Path=${cookieValue.options?.path}`;
47
+ }
48
+ if (cookieValue.options?.domain) {
49
+ base += `; Domain=${cookieValue.options?.domain}`;
50
+ }
51
+ if (cookieValue.options?.secure) {
52
+ base += `; Secure`;
53
+ }
54
+ if (cookieValue.options?.sameSite) {
55
+ if (typeof cookieValue.options?.sameSite === "boolean") {
56
+ base += `; SameSite`;
57
+ }
58
+ else {
59
+ base += `; SameSite=${cookieValue.options?.sameSite}`;
60
+ }
61
+ }
62
+ return base;
63
+ }),
64
+ });
65
+ export class LambdaWrapper {
66
+ serverFactory;
67
+ requestMapper;
68
+ config;
69
+ injector = getServiceProvider().getInjector();
70
+ httpRequestHandler;
71
+ socketManager;
72
+ jobScheduler;
73
+ _server;
74
+ constructor(serverFactory, requestMapper, config) {
75
+ this.serverFactory = serverFactory;
76
+ this.requestMapper = requestMapper;
77
+ this.config = config;
78
+ this.handler = this.handler.bind(this);
79
+ this.socketManager = this.injector.resolveOptional(AbstractServerSocketManager);
80
+ this.httpRequestHandler = this.injector.resolveOptional(AbstractHttpRequestHandler);
81
+ this.jobScheduler = this.injector.resolveOptional(AwsJobScheduler);
82
+ }
83
+ async bootServer() {
84
+ if (!this._server) {
85
+ this._server = await this.serverFactory();
86
+ await this.injector.initInstances();
87
+ this.socketManager?.configure();
88
+ await this._server.init();
89
+ }
90
+ }
91
+ async handler(event) {
92
+ await this.bootServer();
93
+ //-- handle http otherwise
94
+ const requestOptions = this.requestMapper(event);
95
+ if (!requestOptions) {
96
+ throw Errors.SYSTEM_ERROR("Cannot resolve event");
97
+ }
98
+ const corsHeaders = convertCorsToHeaders(requestOptions.headers?.origin || "", this.httpRequestHandler?.corsConfig);
99
+ if (requestOptions.method === HttpMethod.OPTIONS) {
100
+ return toApiGatewayFormat({ code: 200, headers: corsHeaders, cookies: {} });
101
+ }
102
+ if (requestOptions.method === INTERVAL_REQUEST_METHOD) {
103
+ await this.jobScheduler?.handleInterval(requestOptions.body);
104
+ return toApiGatewayFormat({ code: 200, headers: corsHeaders, cookies: {} });
105
+ }
106
+ if (requestOptions.method === CRON_REQUEST_METHOD) {
107
+ await this.jobScheduler?.handleCron(requestOptions.body);
108
+ return toApiGatewayFormat({ code: 200, headers: corsHeaders, cookies: {} });
109
+ }
110
+ try {
111
+ if (Object.values(HttpMethod).includes(requestOptions.method)) {
112
+ if (!this.httpRequestHandler) {
113
+ throw Errors.SYSTEM_ERROR("Http request handler not found");
114
+ }
115
+ const httpData = {
116
+ clientIP: requestOptions.clientIP,
117
+ fullPath: requestOptions.rawPath,
118
+ method: requestOptions.method,
119
+ headers: requestOptions.headers,
120
+ cookies: requestOptions.cookies,
121
+ body: requestOptions.body,
122
+ };
123
+ const response = await this.httpRequestHandler.handle(httpData);
124
+ return toApiGatewayFormat({ ...response, headers: { ...corsHeaders, ...response.headers } });
125
+ }
126
+ else if (Object.values(SocketMethod).includes(requestOptions.method)) {
127
+ if (!this.socketManager) {
128
+ throw Errors.SYSTEM_ERROR("Socket manager not found");
129
+ }
130
+ const socketData = {
131
+ socketId: requestOptions.rawPath,
132
+ method: requestOptions.method,
133
+ data: requestOptions.body,
134
+ };
135
+ await this.socketManager.handle(socketData);
136
+ }
137
+ else {
138
+ throw Errors.HTTP_REQUEST_ERROR("Not allow request method: " + requestOptions.method);
139
+ }
140
+ return toApiGatewayFormat({ code: 200, headers: corsHeaders, cookies: {} });
141
+ }
142
+ catch (err) {
143
+ return toApiGatewayFormat({
144
+ code: err.code || 500,
145
+ value: { name: err.name, message: err.message, code: err.code || 500 },
146
+ headers: corsHeaders,
147
+ cookies: {},
148
+ });
149
+ }
150
+ }
151
+ }
@@ -0,0 +1 @@
1
+ export {};