@resolveio/server-lib 22.1.18 → 22.1.20

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/server-app.js CHANGED
@@ -145,6 +145,10 @@ var ResolveIOMainServer = /** @class */ (function () {
145
145
  this._safeShutdown = false;
146
146
  this._dynamicAppGatewayEnabled = false;
147
147
  this._dynamicAppGatewayCache = new Map();
148
+ this._socketTier = '';
149
+ this._maxClientSockets = 0;
150
+ this._singleIpPerUser = false;
151
+ this._socketPolicyUpgradeUrl = '';
148
152
  this._clientHeartbeatIntervalMs = 20000;
149
153
  this._clientHeartbeatInitialDelayMs = 5000;
150
154
  this._clientHeartbeatBackpressureBytes = 5 * 1024 * 1024;
@@ -189,6 +193,17 @@ var ResolveIOMainServer = /** @class */ (function () {
189
193
  this._timerDebugLogLimit = this.resolveTimerDebugLogLimit();
190
194
  this._aiWorkerDebug = this.parseDebugFlag(process.env.AI_ASSISTANT_WORKER_DEBUG);
191
195
  this._dynamicAppGatewayEnabled = this.resolveDynamicAppGatewayEnabled();
196
+ this._socketTier = this.resolveSocketTier();
197
+ this._maxClientSockets = this.resolveMaxClientSockets(this._socketTier);
198
+ this._singleIpPerUser = this.resolveSingleIpPerUserPolicy(this._socketTier);
199
+ this._socketPolicyUpgradeUrl = this.resolveSocketPolicyUpgradeUrl();
200
+ if (this._maxClientSockets > 0 || this._singleIpPerUser) {
201
+ console.info(new Date(), '[Socket Policy] configured', {
202
+ tier: this._socketTier || 'none',
203
+ maxClientSockets: this._maxClientSockets,
204
+ singleIpPerUser: this._singleIpPerUser
205
+ });
206
+ }
192
207
  _a = this;
193
208
  return [4 /*yield*/, monitor_manager_1.MonitorManager.create()];
194
209
  case 1:
@@ -1137,36 +1152,44 @@ var ResolveIOMainServer = /** @class */ (function () {
1137
1152
  }
1138
1153
  else {
1139
1154
  jwt.verify(token, resolveio_server_app_1.ResolveIOServer.getServerConfig()['JWT_SECRET'], function (err, decoded) { return __awaiter(_this, void 0, void 0, function () {
1140
- var user, _a;
1155
+ var user, socketAdmission, _a;
1141
1156
  return __generator(this, function (_b) {
1142
1157
  switch (_b.label) {
1143
1158
  case 0:
1144
1159
  if (!err) return [3 /*break*/, 1];
1145
1160
  cb(false, 401, 'Unauthorized');
1146
- return [3 /*break*/, 5];
1161
+ return [3 /*break*/, 8];
1147
1162
  case 1:
1148
1163
  info.req['id_user'] = decoded['id_user'];
1149
1164
  _b.label = 2;
1150
1165
  case 2:
1151
- _b.trys.push([2, 4, , 5]);
1166
+ _b.trys.push([2, 7, , 8]);
1152
1167
  return [4 /*yield*/, user_collection_1.Users.findById(decoded['id_user'])];
1153
1168
  case 3:
1154
1169
  user = _b.sent();
1155
- if (user) {
1156
- info.req['user'] = user.fullname;
1157
- info.req['user_readonly'] = user.readonly || false;
1158
- info.req['doc_user'] = user;
1159
- cb(true);
1160
- }
1161
- else {
1162
- cb(false);
1163
- }
1164
- return [3 /*break*/, 5];
1170
+ if (!user) return [3 /*break*/, 5];
1171
+ return [4 /*yield*/, this.evaluateClientSocketAdmission(decoded['id_user'], info.req)];
1165
1172
  case 4:
1173
+ socketAdmission = _b.sent();
1174
+ if (!socketAdmission.allowed) {
1175
+ cb(false, socketAdmission.statusCode, socketAdmission.message || 'Socket connection rejected.');
1176
+ return [2 /*return*/];
1177
+ }
1178
+ info.req['user'] = user.fullname;
1179
+ info.req['user_readonly'] = user.readonly || false;
1180
+ info.req['doc_user'] = user;
1181
+ info.req['client_ip'] = socketAdmission.clientIp;
1182
+ cb(true);
1183
+ return [3 /*break*/, 6];
1184
+ case 5:
1185
+ cb(false);
1186
+ _b.label = 6;
1187
+ case 6: return [3 /*break*/, 8];
1188
+ case 7:
1166
1189
  _a = _b.sent();
1167
1190
  cb(false);
1168
- return [3 /*break*/, 5];
1169
- case 5: return [2 /*return*/];
1191
+ return [3 /*break*/, 8];
1192
+ case 8: return [2 /*return*/];
1170
1193
  }
1171
1194
  });
1172
1195
  }); });
@@ -1185,284 +1208,330 @@ var ResolveIOMainServer = /** @class */ (function () {
1185
1208
  this._serverHTTP.listen(this._portHTTP, host, function () {
1186
1209
  console.log('Running HTTP/WS server on port %s', _this._portHTTP);
1187
1210
  });
1188
- this._serverWSS.on('connection', function (ws, req) {
1189
- var _a, _b;
1190
- if (req.url && req.url.includes('workerToken=')) {
1191
- // It's a WORKER
1192
- var workerId_1 = (0, common_1.objectIdHexString)();
1193
- ws['id_worker'] = workerId_1;
1194
- var workerIndex = null;
1195
- var workerInstance = null;
1196
- ws['supportsBinary'] = true;
1197
- if (req.url) {
1198
- var rootUrl = resolveio_server_app_1.ResolveIOServer.getServerConfig()['ROOT_URL'] || 'http://localhost';
1199
- try {
1200
- var requestUrl = new url_1.URL(req.url, rootUrl);
1201
- workerIndex = requestUrl.searchParams.get('workerIndex');
1202
- workerInstance = requestUrl.searchParams.get('workerInstance');
1203
- }
1204
- catch (_c) {
1211
+ this._serverWSS.on('connection', function (ws, req) { return __awaiter(_this, void 0, void 0, function () {
1212
+ var workerId_1, workerIndex, workerInstance, rootUrl, requestUrl, workerIndexForLog, workerInstanceForLog, interval_1, lastComm_1, missedPongs_1, heartbeatIntervalMs, maxMissedPongs_1, maxSilenceMs_1, socketAdmission, socketPolicyError_1;
1213
+ var _this = this;
1214
+ var _a;
1215
+ return __generator(this, function (_b) {
1216
+ switch (_b.label) {
1217
+ case 0:
1218
+ if (!(req.url && req.url.includes('workerToken='))) return [3 /*break*/, 1];
1219
+ workerId_1 = (0, common_1.objectIdHexString)();
1220
+ ws['id_worker'] = workerId_1;
1205
1221
  workerIndex = null;
1206
1222
  workerInstance = null;
1207
- }
1208
- }
1209
- if (!workerIndex && req['workerIndex']) {
1210
- workerIndex = req['workerIndex'];
1211
- }
1212
- if (!workerInstance && req['workerInstance']) {
1213
- workerInstance = req['workerInstance'];
1214
- }
1215
- if (workerIndex !== null && workerIndex !== undefined) {
1216
- ws['workerIndex'] = workerIndex;
1217
- }
1218
- if (workerInstance !== null && workerInstance !== undefined) {
1219
- ws['workerInstance'] = workerInstance;
1220
- }
1221
- var workerIndexForLog = ws['workerIndex'] || 'UNKNOWN';
1222
- var workerInstanceForLog = ws['workerInstance'] || 'UNKNOWN';
1223
- console.log(new Date(), 'Worker Connected', workerIndexForLog, workerInstanceForLog);
1224
- _this._workerDispatcherManager.addWorker(ws);
1225
- var interval_1 = null;
1226
- var lastComm_1 = new Date();
1227
- var missedPongs_1 = 0;
1228
- var heartbeatIntervalMs = 30000;
1229
- var maxMissedPongs_1 = 2;
1230
- var maxSilenceMs_1 = heartbeatIntervalMs * (maxMissedPongs_1 + 1);
1231
- _this._workerDispatcherManager.sendWorkerPayload(ws, 'ping');
1232
- interval_1 = setInterval(function () {
1233
- var now = Date.now();
1234
- var last = lastComm_1 ? lastComm_1.getTime() : 0;
1235
- var silenceMs = last ? now - last : maxSilenceMs_1 + 1;
1236
- if (silenceMs > maxSilenceMs_1 || missedPongs_1 > maxMissedPongs_1) {
1237
- _this._workerDispatcherManager.disconnectWorker(ws['id_worker']);
1238
- ws.close();
1239
- return;
1240
- }
1241
- missedPongs_1 += 1;
1242
- _this._workerDispatcherManager.sendWorkerPayload(ws, 'ping');
1243
- }, heartbeatIntervalMs);
1244
- ws.on('message', function (message) {
1245
- lastComm_1 = new Date();
1246
- if (typeof message === 'string') {
1247
- if (message === 'ping') {
1248
- _this._workerDispatcherManager.sendWorkerPayload(ws, 'pong');
1249
- }
1250
- else if (message === 'pong') {
1251
- missedPongs_1 = 0;
1223
+ ws['supportsBinary'] = true;
1224
+ if (req.url) {
1225
+ rootUrl = resolveio_server_app_1.ResolveIOServer.getServerConfig()['ROOT_URL'] || 'http://localhost';
1226
+ try {
1227
+ requestUrl = new url_1.URL(req.url, rootUrl);
1228
+ workerIndex = requestUrl.searchParams.get('workerIndex');
1229
+ workerInstance = requestUrl.searchParams.get('workerInstance');
1230
+ }
1231
+ catch (_c) {
1232
+ workerIndex = null;
1233
+ workerInstance = null;
1234
+ }
1252
1235
  }
1253
- else {
1254
- _this._workerDispatcherManager.handleWorkerMessage(ws['id_worker'], message);
1236
+ if (!workerIndex && req['workerIndex']) {
1237
+ workerIndex = req['workerIndex'];
1255
1238
  }
1256
- return;
1257
- }
1258
- var buffer;
1259
- if (Buffer.isBuffer(message)) {
1260
- buffer = message;
1261
- }
1262
- else if (Array.isArray(message)) {
1263
- var chunks = message;
1264
- buffer = Buffer.concat(chunks);
1265
- }
1266
- else if (message instanceof ArrayBuffer) {
1267
- buffer = Buffer.from(message);
1268
- }
1269
- else if (ArrayBuffer.isView(message)) {
1270
- var view = message;
1271
- buffer = Buffer.from(view.buffer, view.byteOffset, view.byteLength);
1272
- }
1273
- else {
1274
- buffer = Buffer.from(message);
1275
- }
1276
- if (buffer.length === 4) {
1277
- var heartbeat = buffer.toString('utf8');
1278
- if (heartbeat === 'ping') {
1279
- _this._workerDispatcherManager.sendWorkerPayload(ws, 'pong');
1280
- return;
1239
+ if (!workerInstance && req['workerInstance']) {
1240
+ workerInstance = req['workerInstance'];
1281
1241
  }
1282
- else if (heartbeat === 'pong') {
1283
- missedPongs_1 = 0;
1284
- return;
1242
+ if (workerIndex !== null && workerIndex !== undefined) {
1243
+ ws['workerIndex'] = workerIndex;
1285
1244
  }
1286
- }
1287
- _this._workerDispatcherManager.handleWorkerMessage(ws['id_worker'], buffer);
1288
- });
1289
- ws.on('close', function () {
1290
- _this._workerDispatcherManager.disconnectWorker(ws['id_worker']);
1291
- console.log(new Date(), 'Worker disconnected:', workerId_1);
1292
- if (interval_1) {
1293
- clearInterval(interval_1);
1294
- }
1295
- });
1296
- ws.on('error', function (error) {
1297
- _this._workerDispatcherManager.disconnectWorker(ws['id_worker']);
1298
- console.error('Error on WS Worker', error);
1299
- ws.close();
1300
- });
1301
- }
1302
- else {
1303
- // Normal client
1304
- ws['id_socket'] = (0, common_1.objectIdHexString)();
1305
- ws['supportsBinary'] = true;
1306
- ws['id_user'] = req['id_user'];
1307
- ws['user'] = req['user'];
1308
- ws['user_readonly'] = req['user_readonly'];
1309
- ws['doc_user'] = req['doc_user'];
1310
- _this._websocketManager.addWebSocket(ws);
1311
- _this.logConnectDebug('WS client connected', {
1312
- id_socket: ws['id_socket'],
1313
- id_user: ws['id_user'],
1314
- user: ws['user'],
1315
- url: req === null || req === void 0 ? void 0 : req.url,
1316
- ip: (_a = req === null || req === void 0 ? void 0 : req.socket) === null || _a === void 0 ? void 0 : _a.remoteAddress,
1317
- origin: (_b = req === null || req === void 0 ? void 0 : req.headers) === null || _b === void 0 ? void 0 : _b.origin
1318
- });
1319
- setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
1320
- return __generator(this, function (_a) {
1321
- switch (_a.label) {
1322
- case 0: return [4 /*yield*/, this.triggerClientHeartbeat(ws)];
1323
- case 1:
1324
- _a.sent();
1325
- return [2 /*return*/];
1326
- }
1327
- });
1328
- }); }, _this._clientHeartbeatInitialDelayMs);
1329
- if (_this.LOGGER === 'DEBUG') {
1330
- console.log('Connection from user: ' + req['user']);
1331
- }
1332
- ws['isAlive'] = true;
1333
- ws['retryCnt'] = 0;
1334
- ws.on('pong', function () {
1335
- ws['isAlive'] = true;
1336
- ws['pongTime'] = new Date();
1337
- if (ws['pingTime']) {
1338
- ws['latency'] = moment.duration(moment(ws['pongTime']).diff(ws['pingTime'])).asMilliseconds();
1339
- _this._subscriptionManager.loggedInLatency(ws);
1340
- }
1341
- });
1342
- ws.on('message', function (message) { return __awaiter(_this, void 0, void 0, function () {
1343
- var socketData, usedBinary, bufferPayload, decodeResult, decodeResult, decodeResult, view, decodeResult, e_6, correlationId, context;
1344
- return __generator(this, function (_a) {
1345
- switch (_a.label) {
1346
- case 0:
1347
- this._debugMsgRecv += 1;
1348
- socketData = [];
1349
- usedBinary = false;
1350
- _a.label = 1;
1351
- case 1:
1352
- _a.trys.push([1, 2, , 4]);
1353
- if (typeof message === 'string') {
1354
- if (message === 'ping' || message === 'pong') {
1355
- socketData = message;
1356
- }
1357
- else {
1358
- socketData = JSON.parse(message, common_1.dateReviver);
1359
- }
1360
- }
1361
- else if (Buffer.isBuffer(message)) {
1362
- bufferPayload = message;
1363
- decodeResult = this.decodeBufferPayload(bufferPayload);
1364
- socketData = decodeResult.data;
1365
- usedBinary = decodeResult.usedBinary;
1245
+ if (workerInstance !== null && workerInstance !== undefined) {
1246
+ ws['workerInstance'] = workerInstance;
1247
+ }
1248
+ workerIndexForLog = ws['workerIndex'] || 'UNKNOWN';
1249
+ workerInstanceForLog = ws['workerInstance'] || 'UNKNOWN';
1250
+ console.log(new Date(), 'Worker Connected', workerIndexForLog, workerInstanceForLog);
1251
+ this._workerDispatcherManager.addWorker(ws);
1252
+ interval_1 = null;
1253
+ lastComm_1 = new Date();
1254
+ missedPongs_1 = 0;
1255
+ heartbeatIntervalMs = 30000;
1256
+ maxMissedPongs_1 = 2;
1257
+ maxSilenceMs_1 = heartbeatIntervalMs * (maxMissedPongs_1 + 1);
1258
+ this._workerDispatcherManager.sendWorkerPayload(ws, 'ping');
1259
+ interval_1 = setInterval(function () {
1260
+ var now = Date.now();
1261
+ var last = lastComm_1 ? lastComm_1.getTime() : 0;
1262
+ var silenceMs = last ? now - last : maxSilenceMs_1 + 1;
1263
+ if (silenceMs > maxSilenceMs_1 || missedPongs_1 > maxMissedPongs_1) {
1264
+ _this._workerDispatcherManager.disconnectWorker(ws['id_worker']);
1265
+ ws.close();
1266
+ return;
1267
+ }
1268
+ missedPongs_1 += 1;
1269
+ _this._workerDispatcherManager.sendWorkerPayload(ws, 'ping');
1270
+ }, heartbeatIntervalMs);
1271
+ ws.on('message', function (message) {
1272
+ lastComm_1 = new Date();
1273
+ if (typeof message === 'string') {
1274
+ if (message === 'ping') {
1275
+ _this._workerDispatcherManager.sendWorkerPayload(ws, 'pong');
1366
1276
  }
1367
- else if (Array.isArray(message)) {
1368
- bufferPayload = Buffer.concat(message);
1369
- decodeResult = this.decodeBufferPayload(bufferPayload);
1370
- socketData = decodeResult.data;
1371
- usedBinary = decodeResult.usedBinary;
1277
+ else if (message === 'pong') {
1278
+ missedPongs_1 = 0;
1372
1279
  }
1373
- else if (message instanceof ArrayBuffer) {
1374
- bufferPayload = Buffer.from(message);
1375
- decodeResult = this.decodeBufferPayload(bufferPayload);
1376
- socketData = decodeResult.data;
1377
- usedBinary = decodeResult.usedBinary;
1280
+ else {
1281
+ _this._workerDispatcherManager.handleWorkerMessage(ws['id_worker'], message);
1378
1282
  }
1379
- else if (ArrayBuffer.isView(message)) {
1380
- view = message;
1381
- bufferPayload = Buffer.from(view.buffer, view.byteOffset, view.byteLength);
1382
- decodeResult = this.decodeBufferPayload(bufferPayload);
1383
- socketData = decodeResult.data;
1384
- usedBinary = decodeResult.usedBinary;
1283
+ return;
1284
+ }
1285
+ var buffer;
1286
+ if (Buffer.isBuffer(message)) {
1287
+ buffer = message;
1288
+ }
1289
+ else if (Array.isArray(message)) {
1290
+ var chunks = message;
1291
+ buffer = Buffer.concat(chunks);
1292
+ }
1293
+ else if (message instanceof ArrayBuffer) {
1294
+ buffer = Buffer.from(message);
1295
+ }
1296
+ else if (ArrayBuffer.isView(message)) {
1297
+ var view = message;
1298
+ buffer = Buffer.from(view.buffer, view.byteOffset, view.byteLength);
1299
+ }
1300
+ else {
1301
+ buffer = Buffer.from(message);
1302
+ }
1303
+ if (buffer.length === 4) {
1304
+ var heartbeat = buffer.toString('utf8');
1305
+ if (heartbeat === 'ping') {
1306
+ _this._workerDispatcherManager.sendWorkerPayload(ws, 'pong');
1307
+ return;
1385
1308
  }
1386
- else {
1387
- throw new Error('Unsupported WebSocket message type: ' + typeof message);
1309
+ else if (heartbeat === 'pong') {
1310
+ missedPongs_1 = 0;
1311
+ return;
1388
1312
  }
1389
- return [3 /*break*/, 4];
1390
- case 2:
1391
- e_6 = _a.sent();
1392
- console.log('Error - WS message parse', e_6);
1393
- correlationId = (0, common_1.objectIdHexString)();
1394
- context = {
1395
- rawBinary: bufferPayload ? bufferPayload.toString('base64') : undefined,
1396
- rawMessage: typeof message === 'string' ? message : undefined,
1397
- error: e_6 instanceof Error ? { name: e_6.name, message: e_6.message, stack: e_6.stack } : e_6
1398
- };
1399
- return [4 /*yield*/, this.reportServerError('SERVER - JSON Parse Error - ' + resolveio_server_app_1.ResolveIOServer.getServerConfig()['CLIENT_NAME'], correlationId, context, { context: 'websocket-message-parse' }, 'error', e_6 instanceof Error ? e_6.stack : undefined)];
1400
- case 3:
1401
- _a.sent();
1402
- return [2 /*return*/];
1403
- case 4:
1404
- if (usedBinary) {
1405
- ws['supportsBinary'] = true;
1313
+ }
1314
+ _this._workerDispatcherManager.handleWorkerMessage(ws['id_worker'], buffer);
1315
+ });
1316
+ ws.on('close', function () {
1317
+ _this._workerDispatcherManager.disconnectWorker(ws['id_worker']);
1318
+ console.log(new Date(), 'Worker disconnected:', workerId_1);
1319
+ if (interval_1) {
1320
+ clearInterval(interval_1);
1321
+ }
1322
+ });
1323
+ ws.on('error', function (error) {
1324
+ _this._workerDispatcherManager.disconnectWorker(ws['id_worker']);
1325
+ console.error('Error on WS Worker', error);
1326
+ ws.close();
1327
+ });
1328
+ return [3 /*break*/, 6];
1329
+ case 1:
1330
+ // Normal client
1331
+ ws['id_socket'] = (0, common_1.objectIdHexString)();
1332
+ ws['supportsBinary'] = true;
1333
+ ws['id_user'] = req['id_user'];
1334
+ ws['user'] = req['user'];
1335
+ ws['user_readonly'] = req['user_readonly'];
1336
+ ws['doc_user'] = req['doc_user'];
1337
+ ws['client_ip'] = this.resolveClientIp(req);
1338
+ socketAdmission = void 0;
1339
+ _b.label = 2;
1340
+ case 2:
1341
+ _b.trys.push([2, 4, , 5]);
1342
+ return [4 /*yield*/, this.evaluateClientSocketAdmission(ws['id_user'], req)];
1343
+ case 3:
1344
+ socketAdmission = _b.sent();
1345
+ return [3 /*break*/, 5];
1346
+ case 4:
1347
+ socketPolicyError_1 = _b.sent();
1348
+ this.logConnectDebug('WS socket policy evaluation failed', {
1349
+ id_socket: ws['id_socket'],
1350
+ id_user: ws['id_user'],
1351
+ user: ws['user'],
1352
+ ip: ws['client_ip'],
1353
+ error: (socketPolicyError_1 === null || socketPolicyError_1 === void 0 ? void 0 : socketPolicyError_1.message) || socketPolicyError_1
1354
+ });
1355
+ try {
1356
+ ws.close(1011, 'Socket policy error');
1357
+ }
1358
+ catch (_d) { }
1359
+ return [2 /*return*/];
1360
+ case 5:
1361
+ if (!socketAdmission.allowed) {
1362
+ this.logConnectDebug('WS client rejected', {
1363
+ id_socket: ws['id_socket'],
1364
+ id_user: ws['id_user'],
1365
+ user: ws['user'],
1366
+ ip: ws['client_ip'],
1367
+ reason: socketAdmission.message
1368
+ });
1369
+ try {
1370
+ ws.close(1008, this.buildSocketLimitCloseReason());
1371
+ }
1372
+ catch (_e) { }
1373
+ return [2 /*return*/];
1374
+ }
1375
+ ws['client_ip'] = socketAdmission.clientIp || ws['client_ip'];
1376
+ this._websocketManager.addWebSocket(ws);
1377
+ this.logConnectDebug('WS client connected', {
1378
+ id_socket: ws['id_socket'],
1379
+ id_user: ws['id_user'],
1380
+ user: ws['user'],
1381
+ url: req === null || req === void 0 ? void 0 : req.url,
1382
+ ip: ws['client_ip'],
1383
+ origin: (_a = req === null || req === void 0 ? void 0 : req.headers) === null || _a === void 0 ? void 0 : _a.origin
1384
+ });
1385
+ setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
1386
+ return __generator(this, function (_a) {
1387
+ switch (_a.label) {
1388
+ case 0: return [4 /*yield*/, this.triggerClientHeartbeat(ws)];
1389
+ case 1:
1390
+ _a.sent();
1391
+ return [2 /*return*/];
1406
1392
  }
1407
- // call our existing processSocketMessage
1408
- return [4 /*yield*/, this.processSocketMessage(ws, socketData)];
1409
- case 5:
1410
- // call our existing processSocketMessage
1411
- _a.sent();
1412
- return [2 /*return*/];
1413
- }
1414
- });
1415
- }); })
1416
- .on('end', function () {
1417
- ws.close();
1418
- })
1419
- .on('error', function () {
1420
- ws.close();
1421
- })
1422
- .on('close', function () { return __awaiter(_this, void 0, void 0, function () {
1423
- return __generator(this, function (_a) {
1424
- switch (_a.label) {
1425
- case 0:
1426
- this.logConnectDebug('WS client closed', {
1427
- id_socket: ws['id_socket'],
1428
- id_user: ws['id_user'],
1429
- user: ws['user']
1430
- });
1431
- return [4 /*yield*/, this.unsubscribeWS(ws)];
1432
- case 1:
1433
- _a.sent();
1434
- return [2 /*return*/];
1435
- }
1436
- });
1437
- }); });
1438
- // Do not block message handler registration on DB write; this avoids losing
1439
- // very-early subscription messages sent immediately after websocket open.
1440
- setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
1441
- var error_5;
1442
- return __generator(this, function (_a) {
1443
- switch (_a.label) {
1444
- case 0:
1445
- _a.trys.push([0, 2, , 3]);
1446
- return [4 /*yield*/, this._subscriptionManager.createLoggedInUser(ws['id_socket'])];
1447
- case 1:
1448
- _a.sent();
1449
- return [3 /*break*/, 3];
1450
- case 2:
1451
- error_5 = _a.sent();
1452
- console.error(new Date(), 'Error creating logged-in user', ws['id_socket'], error_5);
1453
- this.logConnectDebug('Create logged-in user failed', {
1454
- id_socket: ws['id_socket'],
1455
- id_user: ws['id_user'],
1456
- user: ws['user'],
1457
- error: (error_5 === null || error_5 === void 0 ? void 0 : error_5.message) || error_5
1458
- });
1459
- return [3 /*break*/, 3];
1460
- case 3: return [2 /*return*/];
1393
+ });
1394
+ }); }, this._clientHeartbeatInitialDelayMs);
1395
+ if (this.LOGGER === 'DEBUG') {
1396
+ console.log('Connection from user: ' + req['user']);
1461
1397
  }
1462
- });
1463
- }); }, 0);
1464
- }
1465
- });
1398
+ ws['isAlive'] = true;
1399
+ ws['retryCnt'] = 0;
1400
+ ws.on('pong', function () {
1401
+ ws['isAlive'] = true;
1402
+ ws['pongTime'] = new Date();
1403
+ if (ws['pingTime']) {
1404
+ ws['latency'] = moment.duration(moment(ws['pongTime']).diff(ws['pingTime'])).asMilliseconds();
1405
+ _this._subscriptionManager.loggedInLatency(ws);
1406
+ }
1407
+ });
1408
+ ws.on('message', function (message) { return __awaiter(_this, void 0, void 0, function () {
1409
+ var socketData, usedBinary, bufferPayload, decodeResult, decodeResult, decodeResult, view, decodeResult, e_6, correlationId, context;
1410
+ return __generator(this, function (_a) {
1411
+ switch (_a.label) {
1412
+ case 0:
1413
+ this._debugMsgRecv += 1;
1414
+ socketData = [];
1415
+ usedBinary = false;
1416
+ _a.label = 1;
1417
+ case 1:
1418
+ _a.trys.push([1, 2, , 4]);
1419
+ if (typeof message === 'string') {
1420
+ if (message === 'ping' || message === 'pong') {
1421
+ socketData = message;
1422
+ }
1423
+ else {
1424
+ socketData = JSON.parse(message, common_1.dateReviver);
1425
+ }
1426
+ }
1427
+ else if (Buffer.isBuffer(message)) {
1428
+ bufferPayload = message;
1429
+ decodeResult = this.decodeBufferPayload(bufferPayload);
1430
+ socketData = decodeResult.data;
1431
+ usedBinary = decodeResult.usedBinary;
1432
+ }
1433
+ else if (Array.isArray(message)) {
1434
+ bufferPayload = Buffer.concat(message);
1435
+ decodeResult = this.decodeBufferPayload(bufferPayload);
1436
+ socketData = decodeResult.data;
1437
+ usedBinary = decodeResult.usedBinary;
1438
+ }
1439
+ else if (message instanceof ArrayBuffer) {
1440
+ bufferPayload = Buffer.from(message);
1441
+ decodeResult = this.decodeBufferPayload(bufferPayload);
1442
+ socketData = decodeResult.data;
1443
+ usedBinary = decodeResult.usedBinary;
1444
+ }
1445
+ else if (ArrayBuffer.isView(message)) {
1446
+ view = message;
1447
+ bufferPayload = Buffer.from(view.buffer, view.byteOffset, view.byteLength);
1448
+ decodeResult = this.decodeBufferPayload(bufferPayload);
1449
+ socketData = decodeResult.data;
1450
+ usedBinary = decodeResult.usedBinary;
1451
+ }
1452
+ else {
1453
+ throw new Error('Unsupported WebSocket message type: ' + typeof message);
1454
+ }
1455
+ return [3 /*break*/, 4];
1456
+ case 2:
1457
+ e_6 = _a.sent();
1458
+ console.log('Error - WS message parse', e_6);
1459
+ correlationId = (0, common_1.objectIdHexString)();
1460
+ context = {
1461
+ rawBinary: bufferPayload ? bufferPayload.toString('base64') : undefined,
1462
+ rawMessage: typeof message === 'string' ? message : undefined,
1463
+ error: e_6 instanceof Error ? { name: e_6.name, message: e_6.message, stack: e_6.stack } : e_6
1464
+ };
1465
+ return [4 /*yield*/, this.reportServerError('SERVER - JSON Parse Error - ' + resolveio_server_app_1.ResolveIOServer.getServerConfig()['CLIENT_NAME'], correlationId, context, { context: 'websocket-message-parse' }, 'error', e_6 instanceof Error ? e_6.stack : undefined)];
1466
+ case 3:
1467
+ _a.sent();
1468
+ return [2 /*return*/];
1469
+ case 4:
1470
+ if (usedBinary) {
1471
+ ws['supportsBinary'] = true;
1472
+ }
1473
+ // call our existing processSocketMessage
1474
+ return [4 /*yield*/, this.processSocketMessage(ws, socketData)];
1475
+ case 5:
1476
+ // call our existing processSocketMessage
1477
+ _a.sent();
1478
+ return [2 /*return*/];
1479
+ }
1480
+ });
1481
+ }); })
1482
+ .on('end', function () {
1483
+ ws.close();
1484
+ })
1485
+ .on('error', function () {
1486
+ ws.close();
1487
+ })
1488
+ .on('close', function () { return __awaiter(_this, void 0, void 0, function () {
1489
+ return __generator(this, function (_a) {
1490
+ switch (_a.label) {
1491
+ case 0:
1492
+ this.logConnectDebug('WS client closed', {
1493
+ id_socket: ws['id_socket'],
1494
+ id_user: ws['id_user'],
1495
+ user: ws['user']
1496
+ });
1497
+ return [4 /*yield*/, this.unsubscribeWS(ws)];
1498
+ case 1:
1499
+ _a.sent();
1500
+ return [2 /*return*/];
1501
+ }
1502
+ });
1503
+ }); });
1504
+ // Do not block message handler registration on DB write; this avoids losing
1505
+ // very-early subscription messages sent immediately after websocket open.
1506
+ setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
1507
+ var error_5;
1508
+ return __generator(this, function (_a) {
1509
+ switch (_a.label) {
1510
+ case 0:
1511
+ _a.trys.push([0, 2, , 3]);
1512
+ return [4 /*yield*/, this._subscriptionManager.createLoggedInUser(ws['id_socket'])];
1513
+ case 1:
1514
+ _a.sent();
1515
+ return [3 /*break*/, 3];
1516
+ case 2:
1517
+ error_5 = _a.sent();
1518
+ console.error(new Date(), 'Error creating logged-in user', ws['id_socket'], error_5);
1519
+ this.logConnectDebug('Create logged-in user failed', {
1520
+ id_socket: ws['id_socket'],
1521
+ id_user: ws['id_user'],
1522
+ user: ws['user'],
1523
+ error: (error_5 === null || error_5 === void 0 ? void 0 : error_5.message) || error_5
1524
+ });
1525
+ return [3 /*break*/, 3];
1526
+ case 3: return [2 /*return*/];
1527
+ }
1528
+ });
1529
+ }); }, 0);
1530
+ _b.label = 6;
1531
+ case 6: return [2 /*return*/];
1532
+ }
1533
+ });
1534
+ }); });
1466
1535
  // Keep alive timer
1467
1536
  setInterval(function () { return __awaiter(_this, void 0, void 0, function () {
1468
1537
  var _a, _b, ws, e_7_1;
@@ -1982,6 +2051,267 @@ var ResolveIOMainServer = /** @class */ (function () {
1982
2051
  }
1983
2052
  return false;
1984
2053
  };
2054
+ ResolveIOMainServer.prototype.parseOptionalBoolean = function (value) {
2055
+ if (value === null || value === undefined) {
2056
+ return null;
2057
+ }
2058
+ var normalized = "".concat(value).trim();
2059
+ if (!normalized) {
2060
+ return null;
2061
+ }
2062
+ return this.parseDebugFlag(normalized);
2063
+ };
2064
+ ResolveIOMainServer.prototype.parseNonNegativeInt = function (value, fallback) {
2065
+ var parsed = parseInt("".concat(value !== null && value !== void 0 ? value : ''), 10);
2066
+ if (Number.isNaN(parsed) || parsed < 0) {
2067
+ return fallback;
2068
+ }
2069
+ return parsed;
2070
+ };
2071
+ ResolveIOMainServer.prototype.resolveSocketTier = function () {
2072
+ var config = resolveio_server_app_1.ResolveIOServer.getServerConfig() || {};
2073
+ return "".concat(process.env.AI_CODER_PLAN_TIER || config['AI_CODER_PLAN_TIER'] || '').trim().toLowerCase();
2074
+ };
2075
+ ResolveIOMainServer.prototype.resolveSocketTierKeySuffix = function (planTier) {
2076
+ return "".concat(planTier || '')
2077
+ .trim()
2078
+ .toUpperCase()
2079
+ .replace(/[^A-Z0-9]+/g, '_');
2080
+ };
2081
+ ResolveIOMainServer.prototype.resolveMaxClientSockets = function (planTier) {
2082
+ var _a, _b, _c;
2083
+ var config = resolveio_server_app_1.ResolveIOServer.getServerConfig() || {};
2084
+ var explicitLimit = this.parseNonNegativeInt((_a = config['AI_CODER_MAX_SOCKETS']) !== null && _a !== void 0 ? _a : process.env.AI_CODER_MAX_SOCKETS, -1);
2085
+ if (explicitLimit >= 0) {
2086
+ return explicitLimit;
2087
+ }
2088
+ var normalizedTier = "".concat(planTier || '').trim().toLowerCase();
2089
+ var tierKeySuffix = this.resolveSocketTierKeySuffix(normalizedTier);
2090
+ if (tierKeySuffix) {
2091
+ var tierLimitKey = "AI_CODER_MAX_SOCKETS_".concat(tierKeySuffix);
2092
+ var tierLimit = this.parseNonNegativeInt((_b = config[tierLimitKey]) !== null && _b !== void 0 ? _b : process.env[tierLimitKey], -1);
2093
+ if (tierLimit >= 0) {
2094
+ return tierLimit;
2095
+ }
2096
+ }
2097
+ var maxUsers = this.parseNonNegativeInt((_c = config['AI_CODER_MAX_USERS']) !== null && _c !== void 0 ? _c : process.env.AI_CODER_MAX_USERS, -1);
2098
+ if (maxUsers > 0) {
2099
+ return maxUsers === 1 ? 1 : maxUsers * 2;
2100
+ }
2101
+ if (normalizedTier === 'tool') {
2102
+ return 1;
2103
+ }
2104
+ if (normalizedTier === 'small') {
2105
+ return 10;
2106
+ }
2107
+ if (normalizedTier === 'medium') {
2108
+ return 50;
2109
+ }
2110
+ if (normalizedTier === 'large') {
2111
+ return 200;
2112
+ }
2113
+ if (normalizedTier === 'enterprise') {
2114
+ return 0;
2115
+ }
2116
+ return 0;
2117
+ };
2118
+ ResolveIOMainServer.prototype.resolveSingleIpPerUserPolicy = function (planTier) {
2119
+ var _a, _b;
2120
+ var config = resolveio_server_app_1.ResolveIOServer.getServerConfig() || {};
2121
+ var normalizedTier = "".concat(planTier || '').trim().toLowerCase();
2122
+ var tierKeySuffix = this.resolveSocketTierKeySuffix(normalizedTier);
2123
+ if (tierKeySuffix) {
2124
+ var tierPolicyKey = "AI_CODER_SINGLE_IP_PER_USER_".concat(tierKeySuffix);
2125
+ var tierPolicy = this.parseOptionalBoolean((_a = config[tierPolicyKey]) !== null && _a !== void 0 ? _a : process.env[tierPolicyKey]);
2126
+ if (tierPolicy !== null) {
2127
+ return tierPolicy;
2128
+ }
2129
+ }
2130
+ var policy = this.parseOptionalBoolean((_b = config['AI_CODER_SINGLE_IP_PER_USER']) !== null && _b !== void 0 ? _b : process.env.AI_CODER_SINGLE_IP_PER_USER);
2131
+ if (policy !== null) {
2132
+ return policy;
2133
+ }
2134
+ return !!normalizedTier;
2135
+ };
2136
+ ResolveIOMainServer.prototype.resolveSocketPolicyUpgradeUrl = function () {
2137
+ var config = resolveio_server_app_1.ResolveIOServer.getServerConfig() || {};
2138
+ var direct = "".concat(config['AI_CODER_SOCKET_UPGRADE_URL'] || process.env.AI_CODER_SOCKET_UPGRADE_URL || '').trim();
2139
+ if (direct) {
2140
+ return direct.replace(/\/$/, '');
2141
+ }
2142
+ var dashboard = "".concat(config['AI_CODER_CLIENT_DASHBOARD_URL'] || process.env.AI_CODER_CLIENT_DASHBOARD_URL || '').trim();
2143
+ if (dashboard) {
2144
+ return dashboard.replace(/\/$/, '');
2145
+ }
2146
+ var root = "".concat(config['AI_CODER_ROOT_URL'] || process.env.AI_CODER_ROOT_URL || config['ROOT_URL'] || process.env.ROOT_URL || '').trim();
2147
+ if (!root) {
2148
+ return '';
2149
+ }
2150
+ return "".concat(root.replace(/\/$/, ''), "/dashboard/client");
2151
+ };
2152
+ ResolveIOMainServer.prototype.resolveSocketUpgradeUrlWithAppId = function () {
2153
+ var base = "".concat(this._socketPolicyUpgradeUrl || '').trim();
2154
+ if (!base) {
2155
+ return '';
2156
+ }
2157
+ var appId = "".concat(process.env.AI_CODER_APP_ID || '').trim();
2158
+ if (!appId) {
2159
+ return base;
2160
+ }
2161
+ var separator = base.includes('?') ? '&' : '?';
2162
+ return "".concat(base).concat(separator, "appId=").concat(encodeURIComponent(appId));
2163
+ };
2164
+ ResolveIOMainServer.prototype.normalizeIpAddress = function (value) {
2165
+ var ip = "".concat(value || '').trim();
2166
+ if (!ip) {
2167
+ return '';
2168
+ }
2169
+ if (ip.includes(',')) {
2170
+ ip = ip.split(',')[0].trim();
2171
+ }
2172
+ if (ip.startsWith('::ffff:')) {
2173
+ ip = ip.slice(7);
2174
+ }
2175
+ if (/^\d+\.\d+\.\d+\.\d+:\d+$/.test(ip)) {
2176
+ ip = ip.split(':')[0].trim();
2177
+ }
2178
+ if (ip === '::1') {
2179
+ return '127.0.0.1';
2180
+ }
2181
+ return ip.toLowerCase();
2182
+ };
2183
+ ResolveIOMainServer.prototype.resolveClientIp = function (req) {
2184
+ var _a, _b, _c, _d;
2185
+ var forwardedFor = this.normalizeHeaderValue((_a = req === null || req === void 0 ? void 0 : req.headers) === null || _a === void 0 ? void 0 : _a['x-forwarded-for']);
2186
+ if (forwardedFor) {
2187
+ return this.normalizeIpAddress(forwardedFor);
2188
+ }
2189
+ var realIp = this.normalizeHeaderValue((_b = req === null || req === void 0 ? void 0 : req.headers) === null || _b === void 0 ? void 0 : _b['x-real-ip']);
2190
+ if (realIp) {
2191
+ return this.normalizeIpAddress(realIp);
2192
+ }
2193
+ var socketIp = ((_c = req === null || req === void 0 ? void 0 : req.socket) === null || _c === void 0 ? void 0 : _c.remoteAddress) || ((_d = req === null || req === void 0 ? void 0 : req.connection) === null || _d === void 0 ? void 0 : _d.remoteAddress) || (req === null || req === void 0 ? void 0 : req.ip) || '';
2194
+ return this.normalizeIpAddress(socketIp);
2195
+ };
2196
+ ResolveIOMainServer.prototype.buildSocketLimitMessage = function (activeSockets) {
2197
+ var tierLabel = this._socketTier ? this._socketTier[0].toUpperCase() + this._socketTier.slice(1) : 'Current';
2198
+ var upgradeUrl = this.resolveSocketUpgradeUrlWithAppId();
2199
+ var base = "Socket connection limit reached (".concat(activeSockets, "/").concat(this._maxClientSockets, ") for the ").concat(tierLabel, " tier.");
2200
+ if (!upgradeUrl) {
2201
+ return "".concat(base, " Upgrade to increase capacity.");
2202
+ }
2203
+ return "".concat(base, " Upgrade at ").concat(upgradeUrl, ".");
2204
+ };
2205
+ ResolveIOMainServer.prototype.buildSocketLimitCloseReason = function () {
2206
+ return "Socket limit reached (".concat(this._maxClientSockets, ")");
2207
+ };
2208
+ ResolveIOMainServer.prototype.disconnectUserSocketsFromDifferentIps = function (idUser, incomingIp) {
2209
+ return __awaiter(this, void 0, void 0, function () {
2210
+ var normalizedUser, normalizedIncomingIp, userSockets, disconnected, userSockets_1, userSockets_1_1, existingSocket, existingIp, e_9_1;
2211
+ var e_9, _a;
2212
+ return __generator(this, function (_b) {
2213
+ switch (_b.label) {
2214
+ case 0:
2215
+ if (!this._websocketManager) {
2216
+ return [2 /*return*/, 0];
2217
+ }
2218
+ normalizedUser = "".concat(idUser || '').trim();
2219
+ normalizedIncomingIp = this.normalizeIpAddress(incomingIp);
2220
+ if (!normalizedUser || !normalizedIncomingIp) {
2221
+ return [2 /*return*/, 0];
2222
+ }
2223
+ userSockets = this._websocketManager.getUserWebSockets(normalizedUser);
2224
+ disconnected = 0;
2225
+ _b.label = 1;
2226
+ case 1:
2227
+ _b.trys.push([1, 6, 7, 8]);
2228
+ userSockets_1 = __values(userSockets), userSockets_1_1 = userSockets_1.next();
2229
+ _b.label = 2;
2230
+ case 2:
2231
+ if (!!userSockets_1_1.done) return [3 /*break*/, 5];
2232
+ existingSocket = userSockets_1_1.value;
2233
+ existingIp = this.normalizeIpAddress(existingSocket === null || existingSocket === void 0 ? void 0 : existingSocket['client_ip']);
2234
+ if (!existingIp || existingIp === normalizedIncomingIp) {
2235
+ return [3 /*break*/, 4];
2236
+ }
2237
+ this.logConnectDebug('WS single-ip enforcement disconnect', {
2238
+ id_socket: existingSocket === null || existingSocket === void 0 ? void 0 : existingSocket['id_socket'],
2239
+ id_user: normalizedUser,
2240
+ existingIp: existingIp,
2241
+ incomingIp: normalizedIncomingIp
2242
+ });
2243
+ return [4 /*yield*/, this.unsubscribeWS(existingSocket)];
2244
+ case 3:
2245
+ _b.sent();
2246
+ if (existingSocket.readyState === WebSocket.OPEN || existingSocket.readyState === WebSocket.CONNECTING) {
2247
+ try {
2248
+ existingSocket.close(1008, 'Signed in from another IP');
2249
+ }
2250
+ catch (_c) { }
2251
+ }
2252
+ disconnected += 1;
2253
+ _b.label = 4;
2254
+ case 4:
2255
+ userSockets_1_1 = userSockets_1.next();
2256
+ return [3 /*break*/, 2];
2257
+ case 5: return [3 /*break*/, 8];
2258
+ case 6:
2259
+ e_9_1 = _b.sent();
2260
+ e_9 = { error: e_9_1 };
2261
+ return [3 /*break*/, 8];
2262
+ case 7:
2263
+ try {
2264
+ if (userSockets_1_1 && !userSockets_1_1.done && (_a = userSockets_1.return)) _a.call(userSockets_1);
2265
+ }
2266
+ finally { if (e_9) throw e_9.error; }
2267
+ return [7 /*endfinally*/];
2268
+ case 8: return [2 /*return*/, disconnected];
2269
+ }
2270
+ });
2271
+ });
2272
+ };
2273
+ ResolveIOMainServer.prototype.evaluateClientSocketAdmission = function (idUser, req) {
2274
+ return __awaiter(this, void 0, void 0, function () {
2275
+ var normalizedUser, clientIp, activeSockets, message;
2276
+ return __generator(this, function (_a) {
2277
+ switch (_a.label) {
2278
+ case 0:
2279
+ normalizedUser = "".concat(idUser || '').trim();
2280
+ clientIp = this.resolveClientIp(req);
2281
+ if (!this._singleIpPerUser) return [3 /*break*/, 2];
2282
+ return [4 /*yield*/, this.disconnectUserSocketsFromDifferentIps(normalizedUser, clientIp)];
2283
+ case 1:
2284
+ _a.sent();
2285
+ _a.label = 2;
2286
+ case 2:
2287
+ if (this._maxClientSockets > 0 && this._websocketManager) {
2288
+ activeSockets = this._websocketManager.getActiveWebSocketCount();
2289
+ if (activeSockets >= this._maxClientSockets) {
2290
+ message = this.buildSocketLimitMessage(activeSockets);
2291
+ this.logConnectDebug('WS socket limit blocked', {
2292
+ id_user: normalizedUser,
2293
+ clientIp: clientIp,
2294
+ activeSockets: activeSockets,
2295
+ maxClientSockets: this._maxClientSockets
2296
+ });
2297
+ return [2 /*return*/, {
2298
+ allowed: false,
2299
+ statusCode: 429,
2300
+ message: message,
2301
+ clientIp: clientIp
2302
+ }];
2303
+ }
2304
+ }
2305
+ return [2 /*return*/, {
2306
+ allowed: true,
2307
+ statusCode: 200,
2308
+ message: '',
2309
+ clientIp: clientIp
2310
+ }];
2311
+ }
2312
+ });
2313
+ });
2314
+ };
1985
2315
  ResolveIOMainServer.prototype.logConnectDebug = function (message, details) {
1986
2316
  if (!this._wsConnectDebug) {
1987
2317
  return;