@clairejs/server 3.15.2 → 3.16.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/.mocharc.json +3 -0
- package/README.md +5 -0
- package/dist/common/AbstractController.js +3 -0
- package/dist/common/ControllerMetadata.js +1 -0
- package/dist/common/FileOperation.js +6 -0
- package/dist/common/ServerModelMetadata.js +1 -0
- package/dist/common/Transactionable.js +17 -0
- package/dist/common/auth/AbstractPrincipalResolver.js +2 -0
- package/dist/common/auth/IPrincipal.js +1 -0
- package/dist/common/constants.js +7 -0
- package/dist/common/decorator.d.ts +2 -2
- package/dist/common/decorator.js +6 -0
- package/dist/common/request/EndpointMetadata.js +1 -0
- package/dist/common/request/HttpData.js +1 -0
- package/dist/common/request/HttpEndpoint.js +1 -0
- package/dist/common/request/JobData.js +1 -0
- package/dist/common/request/MountedEndpointInfo.js +1 -0
- package/dist/common/request/RequestOptions.js +1 -0
- package/dist/common/request/SocketData.js +1 -0
- package/dist/common/request/types.d.ts +1 -1
- package/dist/common/request/types.js +1 -0
- package/dist/controllers/FileManageController.js +90 -0
- package/dist/controllers/FileUploadController.js +64 -0
- package/dist/controllers/dto/system.js +14 -0
- package/dist/controllers/dto/upload.js +205 -0
- package/dist/http/auth/AbstractHttpAuthorizer.js +2 -0
- package/dist/http/common/HttpRequest.js +72 -0
- package/dist/http/common/HttpResponse.js +62 -0
- package/dist/http/controller/AbstractHttpController.js +21 -0
- package/dist/http/controller/AbstractHttpMiddleware.js +2 -0
- package/dist/http/controller/AbstractHttpRequestHandler.js +69 -0
- package/dist/http/controller/CrudHttpController.js +303 -0
- package/dist/http/controller/DefaultHttpRequestHandler.js +143 -0
- package/dist/http/decorators.d.ts +1 -1
- package/dist/http/decorators.js +86 -0
- package/dist/http/file-upload/AbstractFileUploadHandler.js +2 -0
- package/dist/http/file-upload/FileUploadHandler.js +41 -0
- package/dist/http/file-upload/types.d.ts +1 -1
- package/dist/http/file-upload/types.js +1 -0
- package/dist/http/repository/AbstractRepository.js +26 -0
- package/dist/http/repository/DtoRepository.d.ts +3 -3
- package/dist/http/repository/DtoRepository.js +204 -0
- package/dist/http/repository/ICrudRepository.js +1 -0
- package/dist/http/repository/ModelRepository.js +696 -0
- package/dist/http/security/AbstractAccessCondition.js +2 -0
- package/dist/http/security/access-conditions/FilterModelFieldAccessCondition.js +30 -0
- package/dist/http/security/access-conditions/MaximumQueryLimit.js +31 -0
- package/dist/http/security/cors.js +1 -0
- package/dist/http/utils.js +32 -0
- package/dist/index.js +75 -1
- package/dist/job/AbstractJobController.js +9 -0
- package/dist/job/AbstractJobRepository.js +2 -0
- package/dist/job/AbstractJobScheduler.js +48 -0
- package/dist/job/AwsJobScheduler.js +405 -0
- package/dist/job/LocalJobScheduler.js +273 -0
- package/dist/job/decorators.js +57 -0
- package/dist/job/interfaces.js +10 -0
- package/dist/logging/FileLogMedium.js +44 -0
- package/dist/services/AbstractFileService.js +28 -0
- package/dist/services/AbstractMailService.js +2 -0
- package/dist/services/AbstractService.js +3 -0
- package/dist/services/AbstractSmsService.js +2 -0
- package/dist/services/implementations/LocalFileService.js +42 -0
- package/dist/services/implementations/LocalMailService.js +27 -0
- package/dist/services/implementations/LocalSmsService.js +17 -0
- package/dist/services/implementations/S3FileService.js +107 -0
- package/dist/services/implementations/SesMailService.js +64 -0
- package/dist/socket/AbstractServerSocket.js +44 -0
- package/dist/socket/AbstractServerSocketManager.d.ts +1 -1
- package/dist/socket/AbstractServerSocketManager.js +348 -0
- package/dist/socket/AbstractSocketConnectionHandler.js +2 -0
- package/dist/socket/AbstractSocketController.d.ts +3 -3
- package/dist/socket/AbstractSocketController.js +12 -0
- package/dist/socket/AwsSocketManager.d.ts +2 -2
- package/dist/socket/AwsSocketManager.js +160 -0
- package/dist/socket/IServerSocket.js +1 -0
- package/dist/socket/LocalSocketManager.js +292 -0
- package/dist/system/ClaireServer.js +78 -0
- package/dist/system/ExpressWrapper.js +122 -0
- package/dist/system/LambdaWrapper.js +151 -0
- package/dist/system/ServerGlobalStore.js +1 -0
- package/dist/system/lamba-request-mapper.js +49 -0
- package/dist/system/locale/LocaleEntry.js +13 -0
- package/dist/system/locale/LocaleTranslation.js +47 -0
- package/dist/system/locale/decorators.js +14 -0
- package/package.json +13 -20
|
@@ -0,0 +1,348 @@
|
|
|
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
|
+
import { AccessConditionValueType, DataType, Errors, getObjectMetadata, getServiceProvider, MessageType, Register, SocketMethod, } from "@clairejs/core";
|
|
8
|
+
import { AbstractAccessCondition } from "../http/security/AbstractAccessCondition";
|
|
9
|
+
import { HttpRequest } from "../http/common/HttpRequest";
|
|
10
|
+
import { AbstractSocketConnectionHandler } from "./AbstractSocketConnectionHandler";
|
|
11
|
+
import { AbstractSocketController } from "./AbstractSocketController";
|
|
12
|
+
const SOCKET_ACTION_HEADER = "x-socket-action";
|
|
13
|
+
const SOCKET_ACTION_READ = "read";
|
|
14
|
+
const SOCKET_ACTION_WRITE = "write";
|
|
15
|
+
let SocketReadCondition = class SocketReadCondition extends AbstractAccessCondition {
|
|
16
|
+
async resolveConditionValue(request) {
|
|
17
|
+
return request.headers[SOCKET_ACTION_HEADER] === SOCKET_ACTION_READ;
|
|
18
|
+
}
|
|
19
|
+
async validate(conditionValue) {
|
|
20
|
+
return !!conditionValue;
|
|
21
|
+
}
|
|
22
|
+
getConditionMetadata() {
|
|
23
|
+
return {
|
|
24
|
+
name: "disable_write",
|
|
25
|
+
description: "Disable send message to channel",
|
|
26
|
+
valueType: AccessConditionValueType.BOOLEAN,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
SocketReadCondition = __decorate([
|
|
31
|
+
Register()
|
|
32
|
+
], SocketReadCondition);
|
|
33
|
+
let SocketWriteCondition = class SocketWriteCondition extends AbstractAccessCondition {
|
|
34
|
+
async resolveConditionValue(request) {
|
|
35
|
+
return request.headers[SOCKET_ACTION_HEADER] === SOCKET_ACTION_WRITE;
|
|
36
|
+
}
|
|
37
|
+
async validate(conditionValue) {
|
|
38
|
+
return !!conditionValue;
|
|
39
|
+
}
|
|
40
|
+
getConditionMetadata() {
|
|
41
|
+
return {
|
|
42
|
+
name: "disable_read",
|
|
43
|
+
description: "Disable receive message from channel",
|
|
44
|
+
valueType: AccessConditionValueType.BOOLEAN,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
SocketWriteCondition = __decorate([
|
|
49
|
+
Register()
|
|
50
|
+
], SocketWriteCondition);
|
|
51
|
+
export class AbstractServerSocketManager {
|
|
52
|
+
requireAuthentication;
|
|
53
|
+
logger;
|
|
54
|
+
redisClient;
|
|
55
|
+
principalResolver;
|
|
56
|
+
requestAuthorizer;
|
|
57
|
+
mountedEndpointInfo;
|
|
58
|
+
_connectionHandler = null;
|
|
59
|
+
constructor(requireAuthentication, logger, redisClient, principalResolver, requestAuthorizer) {
|
|
60
|
+
this.requireAuthentication = requireAuthentication;
|
|
61
|
+
this.logger = logger;
|
|
62
|
+
this.redisClient = redisClient;
|
|
63
|
+
this.principalResolver = principalResolver;
|
|
64
|
+
this.requestAuthorizer = requestAuthorizer;
|
|
65
|
+
}
|
|
66
|
+
getUniqueChannelPrefix() {
|
|
67
|
+
return `CHANNEL:`;
|
|
68
|
+
}
|
|
69
|
+
getSocketInfoHashKey() {
|
|
70
|
+
return `__SOCKET_INFO__`;
|
|
71
|
+
}
|
|
72
|
+
async getConnectionHandler() {
|
|
73
|
+
if (this._connectionHandler === null) {
|
|
74
|
+
const injector = getServiceProvider().getInjector();
|
|
75
|
+
this._connectionHandler = injector.resolveOptional(AbstractSocketConnectionHandler);
|
|
76
|
+
await injector.initInstances();
|
|
77
|
+
}
|
|
78
|
+
return this._connectionHandler;
|
|
79
|
+
}
|
|
80
|
+
formatBroadcastData(channel, data) {
|
|
81
|
+
const rawData = {
|
|
82
|
+
type: MessageType.PLAIN,
|
|
83
|
+
data: { channel, message: data },
|
|
84
|
+
};
|
|
85
|
+
return rawData;
|
|
86
|
+
}
|
|
87
|
+
getUniqueChannelName(channel) {
|
|
88
|
+
return `${this.getUniqueChannelPrefix()}${channel}`;
|
|
89
|
+
}
|
|
90
|
+
async getSocketIdsOfChannel(channel) {
|
|
91
|
+
const socketIds = await this.redisClient.smembers(this.getUniqueChannelName(channel));
|
|
92
|
+
return socketIds || [];
|
|
93
|
+
}
|
|
94
|
+
async addSocketToChannel(socketId, channelInfos) {
|
|
95
|
+
const socket = await this.getById(socketId);
|
|
96
|
+
if (socket) {
|
|
97
|
+
socket.addChannels(channelInfos);
|
|
98
|
+
await Promise.all(channelInfos.map((channelInfo) => this.redisClient.sadd(this.getUniqueChannelName(channelInfo.name), socketId)));
|
|
99
|
+
await socket.saveSocketInfo();
|
|
100
|
+
}
|
|
101
|
+
return socket;
|
|
102
|
+
}
|
|
103
|
+
async removeSocketFromChannel(socketId, channels, persist = true) {
|
|
104
|
+
if (!channels.length) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const socket = await this.getById(socketId);
|
|
108
|
+
if (socket) {
|
|
109
|
+
socket.removeChannels(channels);
|
|
110
|
+
await Promise.all(channels.map((channel) => this.redisClient.srem(this.getUniqueChannelName(channel), socketId)));
|
|
111
|
+
if (persist) {
|
|
112
|
+
await socket.saveSocketInfo();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async removeSocket(socketId, _physicRemove = true) {
|
|
117
|
+
const socket = await this.getById(socketId);
|
|
118
|
+
if (socket) {
|
|
119
|
+
this.logger.debug("Remove from all channels");
|
|
120
|
+
await this.removeSocketFromChannel(socketId, socket.getChannelsInfo().map((c) => c.name), false);
|
|
121
|
+
}
|
|
122
|
+
//-- remove from redis
|
|
123
|
+
this.logger.debug("Remove from redis");
|
|
124
|
+
await this.redisClient.hdel(this.getSocketInfoHashKey(), socketId);
|
|
125
|
+
}
|
|
126
|
+
async handle(socketData) {
|
|
127
|
+
this.logger.debug("Handle socket data", socketData);
|
|
128
|
+
//-- check and handle socket creation
|
|
129
|
+
const connectionHandler = await this.getConnectionHandler();
|
|
130
|
+
switch (socketData.method) {
|
|
131
|
+
case SocketMethod.CONNECT: {
|
|
132
|
+
this.logger.debug("Socket connection attempt", socketData);
|
|
133
|
+
//-- try resolve principal
|
|
134
|
+
const socketRequest = new HttpRequest({
|
|
135
|
+
method: SocketMethod.CONNECT,
|
|
136
|
+
query: socketData.data.queries,
|
|
137
|
+
pathName: "",
|
|
138
|
+
});
|
|
139
|
+
const principal = await this.principalResolver.resolvePrincipal(socketRequest);
|
|
140
|
+
if (!principal && this.requireAuthentication) {
|
|
141
|
+
throw Errors.ACCESS_DENIED("Authentication required");
|
|
142
|
+
}
|
|
143
|
+
//-- generate server socket
|
|
144
|
+
await this.addSocket({
|
|
145
|
+
id: socketData.socketId,
|
|
146
|
+
authInfo: principal,
|
|
147
|
+
channels: [],
|
|
148
|
+
createdAt: Date.now(),
|
|
149
|
+
}, socketData.data);
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case SocketMethod.DISCONNECT: {
|
|
153
|
+
this.logger.debug("Socket disconnect", socketData);
|
|
154
|
+
const socket = await this.getById(socketData.socketId);
|
|
155
|
+
if (socket) {
|
|
156
|
+
const mountedEndpoints = await this.getMountedEndpointInfo();
|
|
157
|
+
//-- notify all channels
|
|
158
|
+
this.logger.debug("Notify channels");
|
|
159
|
+
for (const channelInfo of socket.getChannelsInfo()) {
|
|
160
|
+
const currentEndpoint = mountedEndpoints.find((e) => e.endpointMetadata.url === channelInfo.name);
|
|
161
|
+
if (currentEndpoint) {
|
|
162
|
+
currentEndpoint.endpoint.controller.onChannelLeave(socket);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
this.logger.debug("Calling disconnection handler");
|
|
166
|
+
connectionHandler?.onSocketDisconnect(socket);
|
|
167
|
+
this.logger.debug("Removing socket");
|
|
168
|
+
await this.removeSocket(socket.getId(), false);
|
|
169
|
+
}
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
case SocketMethod.MESSAGE: {
|
|
173
|
+
this.logger.debug("Socket message", socketData);
|
|
174
|
+
const message = socketData.data;
|
|
175
|
+
let clientSocket = await this.getById(socketData.socketId);
|
|
176
|
+
if (!clientSocket) {
|
|
177
|
+
this.logger.debug("Socket not found", socketData.socketId);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
switch (message.type) {
|
|
181
|
+
case MessageType.READY:
|
|
182
|
+
//-- client socket ready
|
|
183
|
+
await clientSocket.sendRaw({
|
|
184
|
+
type: MessageType.READY,
|
|
185
|
+
data: "",
|
|
186
|
+
});
|
|
187
|
+
//-- call connection handler
|
|
188
|
+
await connectionHandler?.onSocketConnect(clientSocket);
|
|
189
|
+
break;
|
|
190
|
+
case MessageType.PING_PONG:
|
|
191
|
+
{
|
|
192
|
+
//-- send back pong
|
|
193
|
+
await clientSocket.sendRaw({
|
|
194
|
+
type: MessageType.PING_PONG,
|
|
195
|
+
data: message.data,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
break;
|
|
199
|
+
case MessageType.CHANNEL_JOIN:
|
|
200
|
+
{
|
|
201
|
+
//-- check channels join permission
|
|
202
|
+
const channels = message.data;
|
|
203
|
+
const channelsInfo = [];
|
|
204
|
+
const endpoints = [];
|
|
205
|
+
for (const channel of channels) {
|
|
206
|
+
let canSend = false;
|
|
207
|
+
let canReceived = false;
|
|
208
|
+
const mountedEndpoints = await this.getMountedEndpointInfo();
|
|
209
|
+
const currentEndpoint = mountedEndpoints.find((e) => e.endpointMetadata.url === channel);
|
|
210
|
+
if (!currentEndpoint) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const request = new HttpRequest({
|
|
215
|
+
method: SocketMethod.MESSAGE,
|
|
216
|
+
pathName: channel,
|
|
217
|
+
headers: { [SOCKET_ACTION_HEADER]: SOCKET_ACTION_READ },
|
|
218
|
+
}, currentEndpoint.endpointMetadata);
|
|
219
|
+
await this.requestAuthorizer.authorize(request, currentEndpoint);
|
|
220
|
+
canReceived = true;
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
this.logger.debug("Error in read authorize", err);
|
|
224
|
+
}
|
|
225
|
+
try {
|
|
226
|
+
const request = new HttpRequest({
|
|
227
|
+
method: SocketMethod.MESSAGE,
|
|
228
|
+
pathName: channel,
|
|
229
|
+
headers: { [SOCKET_ACTION_HEADER]: SOCKET_ACTION_WRITE },
|
|
230
|
+
}, currentEndpoint.endpointMetadata);
|
|
231
|
+
await this.requestAuthorizer.authorize(request, currentEndpoint);
|
|
232
|
+
canSend = true;
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
this.logger.debug("Error in write authorize", err);
|
|
236
|
+
}
|
|
237
|
+
if (canSend || canReceived) {
|
|
238
|
+
this.logger.debug("Adding channel info", channel, canSend, canReceived);
|
|
239
|
+
channelsInfo.push({
|
|
240
|
+
name: channel,
|
|
241
|
+
clientToServerAllowed: canSend,
|
|
242
|
+
serverToClientAllowed: canReceived,
|
|
243
|
+
});
|
|
244
|
+
endpoints.push(currentEndpoint);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
//-- join socket to channels
|
|
248
|
+
clientSocket = await this.addSocketToChannel(clientSocket.getId(), channelsInfo);
|
|
249
|
+
if (!clientSocket) {
|
|
250
|
+
this.logger.debug("Socket not found after addSocketToChannel");
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
//-- send back
|
|
254
|
+
await Promise.all([
|
|
255
|
+
clientSocket.sendRaw({
|
|
256
|
+
type: MessageType.CHANNEL_JOIN,
|
|
257
|
+
data: channelsInfo.map((c) => c.name),
|
|
258
|
+
}),
|
|
259
|
+
...endpoints.map((endpoint) => endpoint.endpoint.controller.onChannelJoin(clientSocket)),
|
|
260
|
+
]);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
break;
|
|
264
|
+
case MessageType.CHANNEL_LEAVE:
|
|
265
|
+
{
|
|
266
|
+
const channels = message.data;
|
|
267
|
+
//-- remove channels from socket
|
|
268
|
+
const mountedEndpoints = await this.getMountedEndpointInfo();
|
|
269
|
+
Promise.all(channels.map((channel) => () => {
|
|
270
|
+
const currentEndpoint = mountedEndpoints.find((e) => e.endpointMetadata.url === channel);
|
|
271
|
+
if (!currentEndpoint) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
return currentEndpoint.endpoint.controller.onChannelLeave(clientSocket);
|
|
275
|
+
}));
|
|
276
|
+
//-- remove socket from channels
|
|
277
|
+
await this.removeSocketFromChannel(clientSocket.getId(), channels);
|
|
278
|
+
}
|
|
279
|
+
break;
|
|
280
|
+
case MessageType.PLAIN:
|
|
281
|
+
{
|
|
282
|
+
const channel = message.data.channel;
|
|
283
|
+
const data = message.data.message;
|
|
284
|
+
//-- check if socket is allowed to send
|
|
285
|
+
if (channel) {
|
|
286
|
+
//-- check if socket is allowed to send to channel
|
|
287
|
+
const allowed = clientSocket
|
|
288
|
+
.getChannelsInfo()
|
|
289
|
+
.find((info) => info.name === channel && info.clientToServerAllowed);
|
|
290
|
+
if (allowed) {
|
|
291
|
+
const mountedEndpoints = await this.getMountedEndpointInfo();
|
|
292
|
+
const currentEndpoint = mountedEndpoints.find((e) => e.endpointMetadata.url === channel);
|
|
293
|
+
if (currentEndpoint) {
|
|
294
|
+
const result = await currentEndpoint.endpoint.controller.onMessage(clientSocket, data);
|
|
295
|
+
if (result !== false) {
|
|
296
|
+
//-- broadcast
|
|
297
|
+
this.logger.debug("broadcasting to channel", channel, data);
|
|
298
|
+
this.broadcastToChannel(channel, data);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
await connectionHandler?.onMessage(clientSocket, data);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
break;
|
|
308
|
+
default:
|
|
309
|
+
//-- invalid message format ignore
|
|
310
|
+
this.logger.debug("Invalid message format", message);
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
default:
|
|
317
|
+
return undefined;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
async getMountedEndpointInfo() {
|
|
321
|
+
if (!this.mountedEndpointInfo) {
|
|
322
|
+
const injector = getServiceProvider().getInjector();
|
|
323
|
+
const socketController = injector.resolveMultiple(AbstractSocketController);
|
|
324
|
+
await injector.initInstances();
|
|
325
|
+
this.mountedEndpointInfo = socketController.map((controller) => {
|
|
326
|
+
const controllerMetadata = getObjectMetadata(controller.constructor);
|
|
327
|
+
return {
|
|
328
|
+
endpoint: {
|
|
329
|
+
httpMethod: SocketMethod.MESSAGE,
|
|
330
|
+
mount: controller.getChannelName(),
|
|
331
|
+
controller: controller,
|
|
332
|
+
handlerFunctionName: controller.onMessage.name,
|
|
333
|
+
},
|
|
334
|
+
endpointMetadata: {
|
|
335
|
+
httpMethod: SocketMethod.MESSAGE,
|
|
336
|
+
description: "Send / Receive message to / from channel",
|
|
337
|
+
permissionGroup: controllerMetadata.permissionGroup,
|
|
338
|
+
dataType: DataType.OBJECT,
|
|
339
|
+
url: controller.getChannelName(),
|
|
340
|
+
name: controller.getChannelName(),
|
|
341
|
+
accessConditions: [SocketReadCondition, SocketWriteCondition],
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
return this.mountedEndpointInfo;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
@@ -2,13 +2,13 @@ import { AbstractController } from "../common/AbstractController";
|
|
|
2
2
|
import { IServerSocket } from "./IServerSocket";
|
|
3
3
|
export declare abstract class AbstractSocketController extends AbstractController {
|
|
4
4
|
abstract getChannelName(): string;
|
|
5
|
-
onChannelJoin(
|
|
6
|
-
onChannelLeave(
|
|
5
|
+
onChannelJoin(_socket: IServerSocket): Promise<void>;
|
|
6
|
+
onChannelLeave(_socket: IServerSocket): Promise<void>;
|
|
7
7
|
/**
|
|
8
8
|
* Handle the message sent to this channel
|
|
9
9
|
* @param socket the socket sending message
|
|
10
10
|
* @param data the data payload being sent
|
|
11
11
|
* @returns return false to prevent message broadcast
|
|
12
12
|
*/
|
|
13
|
-
onMessage(
|
|
13
|
+
onMessage(_socket: IServerSocket, _message: any): Promise<boolean | void>;
|
|
14
14
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AbstractController } from "../common/AbstractController";
|
|
2
|
+
export class AbstractSocketController extends AbstractController {
|
|
3
|
+
async onChannelJoin(_socket) { }
|
|
4
|
+
async onChannelLeave(_socket) { }
|
|
5
|
+
/**
|
|
6
|
+
* Handle the message sent to this channel
|
|
7
|
+
* @param socket the socket sending message
|
|
8
|
+
* @param data the data payload being sent
|
|
9
|
+
* @returns return false to prevent message broadcast
|
|
10
|
+
*/
|
|
11
|
+
async onMessage(_socket, _message) { }
|
|
12
|
+
}
|
|
@@ -21,10 +21,10 @@ export declare class AwsSocketManager extends AbstractServerSocketManager implem
|
|
|
21
21
|
exit(): void;
|
|
22
22
|
removeSocketFromRedis(socketId: string): Promise<void>;
|
|
23
23
|
physicSend(socketId: string, data: any): Promise<void>;
|
|
24
|
-
configure(
|
|
24
|
+
configure(_data?: any): void;
|
|
25
25
|
getSocketsByChannel(channel: string): Promise<IServerSocket[]>;
|
|
26
26
|
broadcastToChannel(channel: string, data: any): Promise<void>;
|
|
27
|
-
addSocket(socketInfo: ServerSocketInfo,
|
|
27
|
+
addSocket(socketInfo: ServerSocketInfo, _data: any): Promise<AbstractServerSocket>;
|
|
28
28
|
removeSocket(socketId: string, physicRemove?: boolean): Promise<void>;
|
|
29
29
|
private getSocketBySerialized;
|
|
30
30
|
getById(socketId: string): Promise<AbstractServerSocket | undefined>;
|
|
@@ -0,0 +1,160 @@
|
|
|
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 } from "@clairejs/core";
|
|
11
|
+
import aws from "aws-sdk";
|
|
12
|
+
import Redis from "ioredis";
|
|
13
|
+
import { AbstractHttpRequestHandler } from "../http/controller/AbstractHttpRequestHandler";
|
|
14
|
+
import { AbstractServerSocketManager } from "./AbstractServerSocketManager";
|
|
15
|
+
import { AbstractServerSocket } from "./AbstractServerSocket";
|
|
16
|
+
import { AbstractRequestAuthorizer } from "../http/auth/AbstractHttpAuthorizer";
|
|
17
|
+
import { AbstractPrincipalResolver } from "../common/auth/AbstractPrincipalResolver";
|
|
18
|
+
class ApiGatewaySocket extends AbstractServerSocket {
|
|
19
|
+
socketManager;
|
|
20
|
+
socketInfo;
|
|
21
|
+
apiGateway;
|
|
22
|
+
socketNamespace;
|
|
23
|
+
redisClient;
|
|
24
|
+
constructor(socketManager, socketInfo, apiGateway, socketNamespace, redisClient) {
|
|
25
|
+
super(socketInfo);
|
|
26
|
+
this.socketManager = socketManager;
|
|
27
|
+
this.socketInfo = socketInfo;
|
|
28
|
+
this.apiGateway = apiGateway;
|
|
29
|
+
this.socketNamespace = socketNamespace;
|
|
30
|
+
this.redisClient = redisClient;
|
|
31
|
+
}
|
|
32
|
+
async saveSocketInfo() {
|
|
33
|
+
await this.redisClient.hset(this.socketNamespace, this.socketInfo.id, JSON.stringify(this.socketInfo));
|
|
34
|
+
}
|
|
35
|
+
async physicSend(data) {
|
|
36
|
+
await this.socketManager.physicSend(this.getId(), data);
|
|
37
|
+
}
|
|
38
|
+
async disconnect(err) {
|
|
39
|
+
if (err) {
|
|
40
|
+
await this.physicSend(err);
|
|
41
|
+
}
|
|
42
|
+
await this.socketManager.removeSocket(this.getId());
|
|
43
|
+
}
|
|
44
|
+
serialize() {
|
|
45
|
+
return JSON.stringify(this.socketInfo);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
let AwsSocketManager = class AwsSocketManager extends AbstractServerSocketManager {
|
|
49
|
+
authenticationRequired;
|
|
50
|
+
region;
|
|
51
|
+
socketDomainUrl;
|
|
52
|
+
redis;
|
|
53
|
+
httpRequestHandler;
|
|
54
|
+
principalResolver;
|
|
55
|
+
requestAuthorizer;
|
|
56
|
+
logger;
|
|
57
|
+
apiGatewayManagement;
|
|
58
|
+
constructor(authenticationRequired, region, socketDomainUrl, redis, httpRequestHandler, principalResolver, requestAuthorizer, logger) {
|
|
59
|
+
super(authenticationRequired, logger, redis, principalResolver, requestAuthorizer);
|
|
60
|
+
this.authenticationRequired = authenticationRequired;
|
|
61
|
+
this.region = region;
|
|
62
|
+
this.socketDomainUrl = socketDomainUrl;
|
|
63
|
+
this.redis = redis;
|
|
64
|
+
this.httpRequestHandler = httpRequestHandler;
|
|
65
|
+
this.principalResolver = principalResolver;
|
|
66
|
+
this.requestAuthorizer = requestAuthorizer;
|
|
67
|
+
this.logger = logger;
|
|
68
|
+
this.apiGatewayManagement = new aws.ApiGatewayManagementApi({
|
|
69
|
+
region,
|
|
70
|
+
apiVersion: "2018-11-29",
|
|
71
|
+
endpoint: socketDomainUrl,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
async init() {
|
|
75
|
+
this.logger.info("AwsSocketManager init");
|
|
76
|
+
}
|
|
77
|
+
exit() {
|
|
78
|
+
this.redisClient.disconnect();
|
|
79
|
+
this.logger.info("AwsSocketManager exit");
|
|
80
|
+
}
|
|
81
|
+
async removeSocketFromRedis(socketId) {
|
|
82
|
+
await this.redisClient.hdel(this.getSocketInfoHashKey(), socketId);
|
|
83
|
+
}
|
|
84
|
+
async physicSend(socketId, data) {
|
|
85
|
+
try {
|
|
86
|
+
this.logger.debug(`Socket ${socketId} sending`, data);
|
|
87
|
+
await this.apiGatewayManagement
|
|
88
|
+
.postToConnection({ ConnectionId: socketId, Data: JSON.stringify(data) })
|
|
89
|
+
.promise();
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
this.logger.debug(`Socket ${socketId} post error`, err);
|
|
93
|
+
//-- socket is gone
|
|
94
|
+
if (err.statusCode === 410) {
|
|
95
|
+
//-- can not send, remove staled socket
|
|
96
|
+
this.logger.debug(`Disconnect & remove stale socket ${socketId}`);
|
|
97
|
+
await this.removeSocket(socketId, false);
|
|
98
|
+
this.logger.debug(`Socket removed ${socketId}`);
|
|
99
|
+
}
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
configure(_data) {
|
|
104
|
+
this.logger.debug("Aws socket manager configure");
|
|
105
|
+
}
|
|
106
|
+
async getSocketsByChannel(channel) {
|
|
107
|
+
const socketIds = await this.getSocketIdsOfChannel(channel);
|
|
108
|
+
if (!socketIds.length) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
const data = await this.redisClient.hmget(this.getSocketInfoHashKey(), ...socketIds);
|
|
112
|
+
return data.filter((serialized) => !!serialized).map((s) => this.getSocketBySerialized(s));
|
|
113
|
+
}
|
|
114
|
+
async broadcastToChannel(channel, data) {
|
|
115
|
+
//-- get all channels
|
|
116
|
+
const socketIds = await this.getSocketIdsOfChannel(channel);
|
|
117
|
+
if (socketIds.length) {
|
|
118
|
+
const formattedData = this.formatBroadcastData(channel, data);
|
|
119
|
+
socketIds.map((socketId) => this.physicSend(socketId, formattedData).catch((err) => this.logger.error(`Cannot send to socket ${socketId}`, err)));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async addSocket(socketInfo, _data) {
|
|
123
|
+
const socket = new ApiGatewaySocket(this, socketInfo, this.apiGatewayManagement, this.getSocketInfoHashKey(), this.redisClient);
|
|
124
|
+
await socket.saveSocketInfo();
|
|
125
|
+
return socket;
|
|
126
|
+
}
|
|
127
|
+
async removeSocket(socketId, physicRemove = true) {
|
|
128
|
+
this.logger.debug(`Aws socket manager remove socket: ${socketId}, ${physicRemove}`);
|
|
129
|
+
await super.removeSocket(socketId, physicRemove);
|
|
130
|
+
this.logger.debug("Super remove ok");
|
|
131
|
+
if (physicRemove) {
|
|
132
|
+
this.apiGatewayManagement
|
|
133
|
+
.deleteConnection({ ConnectionId: socketId })
|
|
134
|
+
.promise()
|
|
135
|
+
.catch((err) => this.logger.error("Cannot physic remove aws socket", err));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
getSocketBySerialized(serialized) {
|
|
139
|
+
return new ApiGatewaySocket(this, JSON.parse(serialized), this.apiGatewayManagement, this.getSocketInfoHashKey(), this.redisClient);
|
|
140
|
+
}
|
|
141
|
+
async getById(socketId) {
|
|
142
|
+
const serialized = await this.redisClient.hget(this.getSocketInfoHashKey(), socketId);
|
|
143
|
+
if (!serialized) {
|
|
144
|
+
this.logger.error("Cannot find socket with id: " + socketId);
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
this.logger.debug("Serialized aws socket", serialized);
|
|
148
|
+
return this.getSocketBySerialized(serialized);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
AwsSocketManager = __decorate([
|
|
152
|
+
Initable(),
|
|
153
|
+
LogContext(),
|
|
154
|
+
__metadata("design:paramtypes", [Boolean, String, String, Redis,
|
|
155
|
+
AbstractHttpRequestHandler,
|
|
156
|
+
AbstractPrincipalResolver,
|
|
157
|
+
AbstractRequestAuthorizer,
|
|
158
|
+
AbstractLogger])
|
|
159
|
+
], AwsSocketManager);
|
|
160
|
+
export { AwsSocketManager };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|