@resolveio/server-lib 22.1.17 → 22.1.19

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
@@ -76,6 +76,7 @@ exports.ResolveIOMainServer = void 0;
76
76
  var express = require("express");
77
77
  var xmlParser = require("express-xml-bodyparser");
78
78
  var http_1 = require("http");
79
+ var crypto = require("crypto");
79
80
  var fs = require("fs");
80
81
  var jwt = require("jsonwebtoken");
81
82
  var moment = require("moment-timezone");
@@ -142,9 +143,16 @@ var ResolveIOMainServer = /** @class */ (function () {
142
143
  this._isWorkersEnabled = false;
143
144
  this._isWorkerInstance = false;
144
145
  this._safeShutdown = false;
146
+ this._dynamicAppGatewayEnabled = false;
147
+ this._dynamicAppGatewayCache = new Map();
148
+ this._socketTier = '';
149
+ this._maxClientSockets = 0;
150
+ this._singleIpPerUser = false;
151
+ this._socketPolicyUpgradeUrl = '';
145
152
  this._clientHeartbeatIntervalMs = 20000;
146
153
  this._clientHeartbeatInitialDelayMs = 5000;
147
154
  this._clientHeartbeatBackpressureBytes = 5 * 1024 * 1024;
155
+ this._dynamicAppGatewayCacheMs = 30 * 1000;
148
156
  }
149
157
  ResolveIOMainServer.create = function () {
150
158
  return __awaiter(this, void 0, void 0, function () {
@@ -184,6 +192,18 @@ var ResolveIOMainServer = /** @class */ (function () {
184
192
  this._timerDebugSampleRate = this.resolveTimerDebugSampleRate();
185
193
  this._timerDebugLogLimit = this.resolveTimerDebugLogLimit();
186
194
  this._aiWorkerDebug = this.parseDebugFlag(process.env.AI_ASSISTANT_WORKER_DEBUG);
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
+ }
187
207
  _a = this;
188
208
  return [4 /*yield*/, monitor_manager_1.MonitorManager.create()];
189
209
  case 1:
@@ -566,6 +586,7 @@ var ResolveIOMainServer = /** @class */ (function () {
566
586
  if (this.LOGGER === 'DEBUG') {
567
587
  console.log('Setup cors');
568
588
  }
589
+ this.installDynamicAppGatewayMiddleware();
569
590
  // Set up http login route
570
591
  (0, auth_1.setupAuthRoutes)(this, this._app, resolveio_server_app_1.ResolveIOServer.getServerConfig());
571
592
  (0, health_1.setupHealthRoutes)(this._app);
@@ -577,6 +598,377 @@ var ResolveIOMainServer = /** @class */ (function () {
577
598
  console.log('Setup express routes');
578
599
  }
579
600
  };
601
+ ResolveIOMainServer.prototype.installDynamicAppGatewayMiddleware = function () {
602
+ var _this = this;
603
+ if (!this._dynamicAppGatewayEnabled) {
604
+ return;
605
+ }
606
+ this._app.use(function (req, res, next) { return __awaiter(_this, void 0, void 0, function () {
607
+ var appId, appDoc, allowedHosts, controlHosts, requestHost, originHeader, originHost, expectedToken, providedToken, error_4;
608
+ var _a, _b, _c;
609
+ return __generator(this, function (_d) {
610
+ switch (_d.label) {
611
+ case 0:
612
+ appId = this.extractDynamicAppGatewayId(req);
613
+ if (!appId) {
614
+ next();
615
+ return [2 /*return*/];
616
+ }
617
+ _d.label = 1;
618
+ case 1:
619
+ _d.trys.push([1, 3, , 4]);
620
+ return [4 /*yield*/, this.resolveCachedDynamicAppGatewayApp(appId)];
621
+ case 2:
622
+ appDoc = _d.sent();
623
+ if (!appDoc) {
624
+ res.status(404).send(JSON.stringify({
625
+ error: true,
626
+ result: 'App not found.'
627
+ }));
628
+ return [2 /*return*/];
629
+ }
630
+ allowedHosts = this.resolveDynamicAppAllowedHosts(appDoc);
631
+ controlHosts = this.resolveDynamicAppControlHosts();
632
+ requestHost = this.normalizeHostname(this.normalizeHeaderValue((_a = req.headers) === null || _a === void 0 ? void 0 : _a['x-forwarded-host'])
633
+ || this.normalizeHeaderValue((_b = req.headers) === null || _b === void 0 ? void 0 : _b.host));
634
+ originHeader = this.normalizeHeaderValue((_c = req.headers) === null || _c === void 0 ? void 0 : _c.origin);
635
+ originHost = this.resolveOriginHostname(originHeader);
636
+ if (requestHost && controlHosts.has(requestHost) && !this.isLocalHostname(requestHost)) {
637
+ res.status(403).send(JSON.stringify({
638
+ error: true,
639
+ result: 'App API must be requested from an app domain.'
640
+ }));
641
+ return [2 /*return*/];
642
+ }
643
+ if (requestHost && !allowedHosts.has(requestHost) && !this.isLocalHostname(requestHost)) {
644
+ res.status(403).send(JSON.stringify({
645
+ error: true,
646
+ result: 'Host not allowed for app API.'
647
+ }));
648
+ return [2 /*return*/];
649
+ }
650
+ if (originHost && controlHosts.has(originHost) && !this.isLocalHostname(originHost)) {
651
+ res.status(403).send(JSON.stringify({
652
+ error: true,
653
+ result: 'App API origin must be an app domain.'
654
+ }));
655
+ return [2 /*return*/];
656
+ }
657
+ if (originHost && !allowedHosts.has(originHost)) {
658
+ res.status(403).send(JSON.stringify({
659
+ error: true,
660
+ result: 'Origin not allowed for app API.'
661
+ }));
662
+ return [2 /*return*/];
663
+ }
664
+ if (originHeader) {
665
+ this.applyDynamicAppGatewayCorsHeaders(res, originHeader);
666
+ }
667
+ if (req.method === 'OPTIONS') {
668
+ res.status(204).end();
669
+ return [2 /*return*/];
670
+ }
671
+ expectedToken = this.normalizeHeaderValue(appDoc === null || appDoc === void 0 ? void 0 : appDoc.rio_token);
672
+ if (!expectedToken) {
673
+ res.status(500).send(JSON.stringify({
674
+ error: true,
675
+ result: 'App token is not configured.'
676
+ }));
677
+ return [2 /*return*/];
678
+ }
679
+ providedToken = this.resolveDynamicAppGatewayToken(req);
680
+ if (!providedToken || providedToken !== expectedToken) {
681
+ res.status(401).send(JSON.stringify({
682
+ error: true,
683
+ result: 'Invalid or missing RIO token.'
684
+ }));
685
+ return [2 /*return*/];
686
+ }
687
+ next();
688
+ return [3 /*break*/, 4];
689
+ case 3:
690
+ error_4 = _d.sent();
691
+ console.error(new Date(), '[DynamicAppGateway] middleware failure', error_4);
692
+ res.status(500).send(JSON.stringify({
693
+ error: true,
694
+ result: (error_4 === null || error_4 === void 0 ? void 0 : error_4.message) || 'Dynamic app gateway error.'
695
+ }));
696
+ return [3 /*break*/, 4];
697
+ case 4: return [2 /*return*/];
698
+ }
699
+ });
700
+ }); });
701
+ };
702
+ ResolveIOMainServer.prototype.resolveDynamicAppGatewayEnabled = function () {
703
+ var _a;
704
+ var config = resolveio_server_app_1.ResolveIOServer.getServerConfig() || {};
705
+ var raw = (_a = config['AI_CODER_DYNAMIC_APP_GATEWAY']) !== null && _a !== void 0 ? _a : process.env.AI_CODER_DYNAMIC_APP_GATEWAY;
706
+ if (raw !== undefined && raw !== null && "".concat(raw).trim() !== '') {
707
+ return this.parseDebugFlag(raw);
708
+ }
709
+ var urls = [
710
+ config['ROOT_URL'],
711
+ config['SEC_ROOT_URL'],
712
+ config['SERVER_URL'],
713
+ process.env.ROOT_URL,
714
+ process.env.SEC_ROOT_URL,
715
+ process.env.SERVER_URL
716
+ ]
717
+ .map(function (value) { return "".concat(value || '').trim().toLowerCase(); })
718
+ .filter(Boolean);
719
+ return urls.some(function (value) { return value.includes('aicoder'); });
720
+ };
721
+ ResolveIOMainServer.prototype.extractDynamicAppGatewayId = function (req) {
722
+ var rawPath = "".concat(req.originalUrl || req.url || req.path || '');
723
+ var normalizedPath = rawPath.split('?')[0];
724
+ if (!normalizedPath.startsWith('/api/apps/')) {
725
+ return '';
726
+ }
727
+ var parts = normalizedPath.split('/').filter(Boolean);
728
+ if (parts.length < 3) {
729
+ return '';
730
+ }
731
+ return "".concat(parts[2] || '').trim();
732
+ };
733
+ ResolveIOMainServer.prototype.resolveDynamicAppGatewayToken = function (req) {
734
+ var e_1, _a, e_2, _b, e_3, _c;
735
+ var _d, _e, _f, _g, _h;
736
+ var authHeader = this.normalizeHeaderValue(((_d = req.headers) === null || _d === void 0 ? void 0 : _d.authorization) || ((_e = req.headers) === null || _e === void 0 ? void 0 : _e.Authorization));
737
+ if (authHeader) {
738
+ var bearerMatch = authHeader.match(/^bearer\s+(.+)$/i);
739
+ if (bearerMatch && bearerMatch[1]) {
740
+ return bearerMatch[1].trim();
741
+ }
742
+ }
743
+ var headerCandidates = [
744
+ (_f = req.headers) === null || _f === void 0 ? void 0 : _f['x-rio-token'],
745
+ (_g = req.headers) === null || _g === void 0 ? void 0 : _g['x-app-token'],
746
+ (_h = req.headers) === null || _h === void 0 ? void 0 : _h['x-api-key']
747
+ ];
748
+ try {
749
+ for (var headerCandidates_1 = __values(headerCandidates), headerCandidates_1_1 = headerCandidates_1.next(); !headerCandidates_1_1.done; headerCandidates_1_1 = headerCandidates_1.next()) {
750
+ var candidate = headerCandidates_1_1.value;
751
+ var value = this.normalizeHeaderValue(candidate);
752
+ if (value) {
753
+ return value;
754
+ }
755
+ }
756
+ }
757
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
758
+ finally {
759
+ try {
760
+ if (headerCandidates_1_1 && !headerCandidates_1_1.done && (_a = headerCandidates_1.return)) _a.call(headerCandidates_1);
761
+ }
762
+ finally { if (e_1) throw e_1.error; }
763
+ }
764
+ var body = req.body || {};
765
+ var query = req.query || {};
766
+ var bodyCandidates = [body.rioToken, body.rio_token, body.apiKey, body.token];
767
+ try {
768
+ for (var bodyCandidates_1 = __values(bodyCandidates), bodyCandidates_1_1 = bodyCandidates_1.next(); !bodyCandidates_1_1.done; bodyCandidates_1_1 = bodyCandidates_1.next()) {
769
+ var candidate = bodyCandidates_1_1.value;
770
+ var value = this.normalizeHeaderValue(candidate);
771
+ if (value) {
772
+ return value;
773
+ }
774
+ }
775
+ }
776
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
777
+ finally {
778
+ try {
779
+ if (bodyCandidates_1_1 && !bodyCandidates_1_1.done && (_b = bodyCandidates_1.return)) _b.call(bodyCandidates_1);
780
+ }
781
+ finally { if (e_2) throw e_2.error; }
782
+ }
783
+ var queryCandidates = [query.rioToken, query.rio_token, query.apiKey, query.token];
784
+ try {
785
+ for (var queryCandidates_1 = __values(queryCandidates), queryCandidates_1_1 = queryCandidates_1.next(); !queryCandidates_1_1.done; queryCandidates_1_1 = queryCandidates_1.next()) {
786
+ var candidate = queryCandidates_1_1.value;
787
+ var value = this.normalizeHeaderValue(candidate);
788
+ if (value) {
789
+ return value;
790
+ }
791
+ }
792
+ }
793
+ catch (e_3_1) { e_3 = { error: e_3_1 }; }
794
+ finally {
795
+ try {
796
+ if (queryCandidates_1_1 && !queryCandidates_1_1.done && (_c = queryCandidates_1.return)) _c.call(queryCandidates_1);
797
+ }
798
+ finally { if (e_3) throw e_3.error; }
799
+ }
800
+ return '';
801
+ };
802
+ ResolveIOMainServer.prototype.normalizeHeaderValue = function (value) {
803
+ if (Array.isArray(value)) {
804
+ return value.map(function (entry) { return "".concat(entry || '').trim(); }).filter(Boolean).join(',');
805
+ }
806
+ return "".concat(value || '').trim();
807
+ };
808
+ ResolveIOMainServer.prototype.normalizeHostname = function (value) {
809
+ var raw = "".concat(value || '').trim();
810
+ if (!raw) {
811
+ return '';
812
+ }
813
+ var candidate = raw.split(',')[0].trim();
814
+ if (!candidate) {
815
+ return '';
816
+ }
817
+ try {
818
+ if (candidate.startsWith('http://') || candidate.startsWith('https://')) {
819
+ return new url_1.URL(candidate).hostname.toLowerCase();
820
+ }
821
+ if (candidate.includes('/')) {
822
+ return new url_1.URL("http://".concat(candidate)).hostname.toLowerCase();
823
+ }
824
+ }
825
+ catch (_a) { }
826
+ candidate = candidate.replace(/^\[/, '').replace(/\]$/, '');
827
+ var lastColon = candidate.lastIndexOf(':');
828
+ if (lastColon > -1 && candidate.indexOf(':') === lastColon) {
829
+ candidate = candidate.slice(0, lastColon);
830
+ }
831
+ return candidate.toLowerCase();
832
+ };
833
+ ResolveIOMainServer.prototype.resolveOriginHostname = function (origin) {
834
+ var raw = "".concat(origin || '').trim();
835
+ if (!raw) {
836
+ return '';
837
+ }
838
+ try {
839
+ return new url_1.URL(raw).hostname.toLowerCase();
840
+ }
841
+ catch (_a) {
842
+ return this.normalizeHostname(raw);
843
+ }
844
+ };
845
+ ResolveIOMainServer.prototype.isLocalHostname = function (hostname) {
846
+ var normalized = this.normalizeHostname(hostname);
847
+ return normalized === 'localhost' || normalized === '127.0.0.1' || normalized === '::1';
848
+ };
849
+ ResolveIOMainServer.prototype.resolveDynamicAppControlHosts = function () {
850
+ var e_4, _a;
851
+ var config = resolveio_server_app_1.ResolveIOServer.getServerConfig() || {};
852
+ var hosts = new Set();
853
+ var candidates = [
854
+ config['ROOT_URL'],
855
+ config['SEC_ROOT_URL'],
856
+ config['SERVER_URL'],
857
+ process.env.ROOT_URL,
858
+ process.env.SEC_ROOT_URL,
859
+ process.env.SERVER_URL,
860
+ process.env.AI_CODER_ROOT_URL,
861
+ process.env.AI_CODER_SEC_ROOT_URL,
862
+ process.env.AI_CODER_SERVER_URL
863
+ ];
864
+ try {
865
+ for (var candidates_1 = __values(candidates), candidates_1_1 = candidates_1.next(); !candidates_1_1.done; candidates_1_1 = candidates_1.next()) {
866
+ var candidate = candidates_1_1.value;
867
+ var normalized = this.normalizeHostname("".concat(candidate || ''));
868
+ if (normalized) {
869
+ hosts.add(normalized);
870
+ }
871
+ }
872
+ }
873
+ catch (e_4_1) { e_4 = { error: e_4_1 }; }
874
+ finally {
875
+ try {
876
+ if (candidates_1_1 && !candidates_1_1.done && (_a = candidates_1.return)) _a.call(candidates_1);
877
+ }
878
+ finally { if (e_4) throw e_4.error; }
879
+ }
880
+ return hosts;
881
+ };
882
+ ResolveIOMainServer.prototype.resolveDynamicAppAllowedHosts = function (appDoc) {
883
+ var e_5, _a;
884
+ var hosts = new Set();
885
+ var candidates = [
886
+ appDoc === null || appDoc === void 0 ? void 0 : appDoc.domain,
887
+ appDoc === null || appDoc === void 0 ? void 0 : appDoc.backend_domain
888
+ ];
889
+ if ((appDoc === null || appDoc === void 0 ? void 0 : appDoc.subdomain) && (appDoc === null || appDoc === void 0 ? void 0 : appDoc.domain_base)) {
890
+ candidates.push("".concat(appDoc.subdomain, ".").concat(appDoc.domain_base));
891
+ }
892
+ try {
893
+ for (var candidates_2 = __values(candidates), candidates_2_1 = candidates_2.next(); !candidates_2_1.done; candidates_2_1 = candidates_2.next()) {
894
+ var candidate = candidates_2_1.value;
895
+ var normalized = this.normalizeHostname("".concat(candidate || ''));
896
+ if (normalized) {
897
+ hosts.add(normalized);
898
+ }
899
+ }
900
+ }
901
+ catch (e_5_1) { e_5 = { error: e_5_1 }; }
902
+ finally {
903
+ try {
904
+ if (candidates_2_1 && !candidates_2_1.done && (_a = candidates_2.return)) _a.call(candidates_2);
905
+ }
906
+ finally { if (e_5) throw e_5.error; }
907
+ }
908
+ return hosts;
909
+ };
910
+ ResolveIOMainServer.prototype.applyDynamicAppGatewayCorsHeaders = function (res, origin) {
911
+ res.setHeader('Access-Control-Allow-Origin', origin);
912
+ res.setHeader('Vary', 'Origin');
913
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
914
+ res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, X-Rio-Token, X-API-Key');
915
+ res.setHeader('Access-Control-Allow-Credentials', 'true');
916
+ res.setHeader('Access-Control-Max-Age', '600');
917
+ };
918
+ ResolveIOMainServer.prototype.resolveCachedDynamicAppGatewayApp = function (appId) {
919
+ return __awaiter(this, void 0, void 0, function () {
920
+ var now, cached, db, appCollection, appDoc, generated;
921
+ return __generator(this, function (_a) {
922
+ switch (_a.label) {
923
+ case 0:
924
+ now = Date.now();
925
+ cached = this._dynamicAppGatewayCache.get(appId);
926
+ if (cached && cached.expiresAt > now) {
927
+ return [2 /*return*/, cached.app];
928
+ }
929
+ db = resolveio_server_app_1.ResolveIOServer.getMainDB();
930
+ if (!db) {
931
+ return [2 /*return*/, null];
932
+ }
933
+ appCollection = db.collection('ai-coder-apps');
934
+ return [4 /*yield*/, appCollection.findOne({ _id: appId }, {
935
+ projection: {
936
+ _id: 1,
937
+ domain: 1,
938
+ backend_domain: 1,
939
+ subdomain: 1,
940
+ domain_base: 1,
941
+ rio_token: 1
942
+ }
943
+ })];
944
+ case 1:
945
+ appDoc = _a.sent();
946
+ if (!appDoc) {
947
+ this._dynamicAppGatewayCache.delete(appId);
948
+ return [2 /*return*/, null];
949
+ }
950
+ if (!!this.normalizeHeaderValue(appDoc.rio_token)) return [3 /*break*/, 3];
951
+ generated = crypto.randomBytes(32).toString('hex');
952
+ return [4 /*yield*/, appCollection.updateOne({ _id: appId }, {
953
+ $set: {
954
+ rio_token: generated,
955
+ updatedAt: new Date()
956
+ }
957
+ })];
958
+ case 2:
959
+ _a.sent();
960
+ appDoc.rio_token = generated;
961
+ _a.label = 3;
962
+ case 3:
963
+ this._dynamicAppGatewayCache.set(appId, {
964
+ expiresAt: now + this._dynamicAppGatewayCacheMs,
965
+ app: appDoc
966
+ });
967
+ return [2 /*return*/, appDoc];
968
+ }
969
+ });
970
+ });
971
+ };
580
972
  ResolveIOMainServer.prototype.safeShutdown = function () {
581
973
  return __awaiter(this, void 0, void 0, function () {
582
974
  var _a;
@@ -760,36 +1152,44 @@ var ResolveIOMainServer = /** @class */ (function () {
760
1152
  }
761
1153
  else {
762
1154
  jwt.verify(token, resolveio_server_app_1.ResolveIOServer.getServerConfig()['JWT_SECRET'], function (err, decoded) { return __awaiter(_this, void 0, void 0, function () {
763
- var user, _a;
1155
+ var user, socketAdmission, _a;
764
1156
  return __generator(this, function (_b) {
765
1157
  switch (_b.label) {
766
1158
  case 0:
767
1159
  if (!err) return [3 /*break*/, 1];
768
1160
  cb(false, 401, 'Unauthorized');
769
- return [3 /*break*/, 5];
1161
+ return [3 /*break*/, 8];
770
1162
  case 1:
771
1163
  info.req['id_user'] = decoded['id_user'];
772
1164
  _b.label = 2;
773
1165
  case 2:
774
- _b.trys.push([2, 4, , 5]);
1166
+ _b.trys.push([2, 7, , 8]);
775
1167
  return [4 /*yield*/, user_collection_1.Users.findById(decoded['id_user'])];
776
1168
  case 3:
777
1169
  user = _b.sent();
778
- if (user) {
779
- info.req['user'] = user.fullname;
780
- info.req['user_readonly'] = user.readonly || false;
781
- info.req['doc_user'] = user;
782
- cb(true);
783
- }
784
- else {
785
- cb(false);
786
- }
787
- return [3 /*break*/, 5];
1170
+ if (!user) return [3 /*break*/, 5];
1171
+ return [4 /*yield*/, this.evaluateClientSocketAdmission(decoded['id_user'], info.req)];
788
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:
789
1189
  _a = _b.sent();
790
1190
  cb(false);
791
- return [3 /*break*/, 5];
792
- case 5: return [2 /*return*/];
1191
+ return [3 /*break*/, 8];
1192
+ case 8: return [2 /*return*/];
793
1193
  }
794
1194
  });
795
1195
  }); });
@@ -808,288 +1208,334 @@ var ResolveIOMainServer = /** @class */ (function () {
808
1208
  this._serverHTTP.listen(this._portHTTP, host, function () {
809
1209
  console.log('Running HTTP/WS server on port %s', _this._portHTTP);
810
1210
  });
811
- this._serverWSS.on('connection', function (ws, req) {
812
- var _a, _b;
813
- if (req.url && req.url.includes('workerToken=')) {
814
- // It's a WORKER
815
- var workerId_1 = (0, common_1.objectIdHexString)();
816
- ws['id_worker'] = workerId_1;
817
- var workerIndex = null;
818
- var workerInstance = null;
819
- ws['supportsBinary'] = true;
820
- if (req.url) {
821
- var rootUrl = resolveio_server_app_1.ResolveIOServer.getServerConfig()['ROOT_URL'] || 'http://localhost';
822
- try {
823
- var requestUrl = new url_1.URL(req.url, rootUrl);
824
- workerIndex = requestUrl.searchParams.get('workerIndex');
825
- workerInstance = requestUrl.searchParams.get('workerInstance');
826
- }
827
- 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;
828
1221
  workerIndex = null;
829
1222
  workerInstance = null;
830
- }
831
- }
832
- if (!workerIndex && req['workerIndex']) {
833
- workerIndex = req['workerIndex'];
834
- }
835
- if (!workerInstance && req['workerInstance']) {
836
- workerInstance = req['workerInstance'];
837
- }
838
- if (workerIndex !== null && workerIndex !== undefined) {
839
- ws['workerIndex'] = workerIndex;
840
- }
841
- if (workerInstance !== null && workerInstance !== undefined) {
842
- ws['workerInstance'] = workerInstance;
843
- }
844
- var workerIndexForLog = ws['workerIndex'] || 'UNKNOWN';
845
- var workerInstanceForLog = ws['workerInstance'] || 'UNKNOWN';
846
- console.log(new Date(), 'Worker Connected', workerIndexForLog, workerInstanceForLog);
847
- _this._workerDispatcherManager.addWorker(ws);
848
- var interval_1 = null;
849
- var lastComm_1 = new Date();
850
- var missedPongs_1 = 0;
851
- var heartbeatIntervalMs = 30000;
852
- var maxMissedPongs_1 = 2;
853
- var maxSilenceMs_1 = heartbeatIntervalMs * (maxMissedPongs_1 + 1);
854
- _this._workerDispatcherManager.sendWorkerPayload(ws, 'ping');
855
- interval_1 = setInterval(function () {
856
- var now = Date.now();
857
- var last = lastComm_1 ? lastComm_1.getTime() : 0;
858
- var silenceMs = last ? now - last : maxSilenceMs_1 + 1;
859
- if (silenceMs > maxSilenceMs_1 || missedPongs_1 > maxMissedPongs_1) {
860
- _this._workerDispatcherManager.disconnectWorker(ws['id_worker']);
861
- ws.close();
862
- return;
863
- }
864
- missedPongs_1 += 1;
865
- _this._workerDispatcherManager.sendWorkerPayload(ws, 'ping');
866
- }, heartbeatIntervalMs);
867
- ws.on('message', function (message) {
868
- lastComm_1 = new Date();
869
- if (typeof message === 'string') {
870
- if (message === 'ping') {
871
- _this._workerDispatcherManager.sendWorkerPayload(ws, 'pong');
872
- }
873
- else if (message === 'pong') {
874
- 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
+ }
875
1235
  }
876
- else {
877
- _this._workerDispatcherManager.handleWorkerMessage(ws['id_worker'], message);
1236
+ if (!workerIndex && req['workerIndex']) {
1237
+ workerIndex = req['workerIndex'];
878
1238
  }
879
- return;
880
- }
881
- var buffer;
882
- if (Buffer.isBuffer(message)) {
883
- buffer = message;
884
- }
885
- else if (Array.isArray(message)) {
886
- var chunks = message;
887
- buffer = Buffer.concat(chunks);
888
- }
889
- else if (message instanceof ArrayBuffer) {
890
- buffer = Buffer.from(message);
891
- }
892
- else if (ArrayBuffer.isView(message)) {
893
- var view = message;
894
- buffer = Buffer.from(view.buffer, view.byteOffset, view.byteLength);
895
- }
896
- else {
897
- buffer = Buffer.from(message);
898
- }
899
- if (buffer.length === 4) {
900
- var heartbeat = buffer.toString('utf8');
901
- if (heartbeat === 'ping') {
902
- _this._workerDispatcherManager.sendWorkerPayload(ws, 'pong');
903
- return;
1239
+ if (!workerInstance && req['workerInstance']) {
1240
+ workerInstance = req['workerInstance'];
904
1241
  }
905
- else if (heartbeat === 'pong') {
906
- missedPongs_1 = 0;
907
- return;
1242
+ if (workerIndex !== null && workerIndex !== undefined) {
1243
+ ws['workerIndex'] = workerIndex;
908
1244
  }
909
- }
910
- _this._workerDispatcherManager.handleWorkerMessage(ws['id_worker'], buffer);
911
- });
912
- ws.on('close', function () {
913
- _this._workerDispatcherManager.disconnectWorker(ws['id_worker']);
914
- console.log(new Date(), 'Worker disconnected:', workerId_1);
915
- if (interval_1) {
916
- clearInterval(interval_1);
917
- }
918
- });
919
- ws.on('error', function (error) {
920
- _this._workerDispatcherManager.disconnectWorker(ws['id_worker']);
921
- console.error('Error on WS Worker', error);
922
- ws.close();
923
- });
924
- }
925
- else {
926
- // Normal client
927
- ws['id_socket'] = (0, common_1.objectIdHexString)();
928
- ws['supportsBinary'] = true;
929
- ws['id_user'] = req['id_user'];
930
- ws['user'] = req['user'];
931
- ws['user_readonly'] = req['user_readonly'];
932
- ws['doc_user'] = req['doc_user'];
933
- _this._websocketManager.addWebSocket(ws);
934
- _this.logConnectDebug('WS client connected', {
935
- id_socket: ws['id_socket'],
936
- id_user: ws['id_user'],
937
- user: ws['user'],
938
- url: req === null || req === void 0 ? void 0 : req.url,
939
- ip: (_a = req === null || req === void 0 ? void 0 : req.socket) === null || _a === void 0 ? void 0 : _a.remoteAddress,
940
- origin: (_b = req === null || req === void 0 ? void 0 : req.headers) === null || _b === void 0 ? void 0 : _b.origin
941
- });
942
- setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
943
- return __generator(this, function (_a) {
944
- switch (_a.label) {
945
- case 0: return [4 /*yield*/, this.triggerClientHeartbeat(ws)];
946
- case 1:
947
- _a.sent();
948
- return [2 /*return*/];
949
- }
950
- });
951
- }); }, _this._clientHeartbeatInitialDelayMs);
952
- if (_this.LOGGER === 'DEBUG') {
953
- console.log('Connection from user: ' + req['user']);
954
- }
955
- ws['isAlive'] = true;
956
- ws['retryCnt'] = 0;
957
- ws.on('pong', function () {
958
- ws['isAlive'] = true;
959
- ws['pongTime'] = new Date();
960
- if (ws['pingTime']) {
961
- ws['latency'] = moment.duration(moment(ws['pongTime']).diff(ws['pingTime'])).asMilliseconds();
962
- _this._subscriptionManager.loggedInLatency(ws);
963
- }
964
- });
965
- ws.on('message', function (message) { return __awaiter(_this, void 0, void 0, function () {
966
- var socketData, usedBinary, bufferPayload, decodeResult, decodeResult, decodeResult, view, decodeResult, e_1, correlationId, context;
967
- return __generator(this, function (_a) {
968
- switch (_a.label) {
969
- case 0:
970
- this._debugMsgRecv += 1;
971
- socketData = [];
972
- usedBinary = false;
973
- _a.label = 1;
974
- case 1:
975
- _a.trys.push([1, 2, , 4]);
976
- if (typeof message === 'string') {
977
- if (message === 'ping' || message === 'pong') {
978
- socketData = message;
979
- }
980
- else {
981
- socketData = JSON.parse(message, common_1.dateReviver);
982
- }
983
- }
984
- else if (Buffer.isBuffer(message)) {
985
- bufferPayload = message;
986
- decodeResult = this.decodeBufferPayload(bufferPayload);
987
- socketData = decodeResult.data;
988
- 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');
989
1276
  }
990
- else if (Array.isArray(message)) {
991
- bufferPayload = Buffer.concat(message);
992
- decodeResult = this.decodeBufferPayload(bufferPayload);
993
- socketData = decodeResult.data;
994
- usedBinary = decodeResult.usedBinary;
1277
+ else if (message === 'pong') {
1278
+ missedPongs_1 = 0;
995
1279
  }
996
- else if (message instanceof ArrayBuffer) {
997
- bufferPayload = Buffer.from(message);
998
- decodeResult = this.decodeBufferPayload(bufferPayload);
999
- socketData = decodeResult.data;
1000
- usedBinary = decodeResult.usedBinary;
1280
+ else {
1281
+ _this._workerDispatcherManager.handleWorkerMessage(ws['id_worker'], message);
1001
1282
  }
1002
- else if (ArrayBuffer.isView(message)) {
1003
- view = message;
1004
- bufferPayload = Buffer.from(view.buffer, view.byteOffset, view.byteLength);
1005
- decodeResult = this.decodeBufferPayload(bufferPayload);
1006
- socketData = decodeResult.data;
1007
- 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;
1008
1308
  }
1009
- else {
1010
- throw new Error('Unsupported WebSocket message type: ' + typeof message);
1309
+ else if (heartbeat === 'pong') {
1310
+ missedPongs_1 = 0;
1311
+ return;
1011
1312
  }
1012
- return [3 /*break*/, 4];
1013
- case 2:
1014
- e_1 = _a.sent();
1015
- console.log('Error - WS message parse', e_1);
1016
- correlationId = (0, common_1.objectIdHexString)();
1017
- context = {
1018
- rawBinary: bufferPayload ? bufferPayload.toString('base64') : undefined,
1019
- rawMessage: typeof message === 'string' ? message : undefined,
1020
- error: e_1 instanceof Error ? { name: e_1.name, message: e_1.message, stack: e_1.stack } : e_1
1021
- };
1022
- 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_1 instanceof Error ? e_1.stack : undefined)];
1023
- case 3:
1024
- _a.sent();
1025
- return [2 /*return*/];
1026
- case 4:
1027
- if (usedBinary) {
1028
- 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*/];
1029
1392
  }
1030
- // call our existing processSocketMessage
1031
- return [4 /*yield*/, this.processSocketMessage(ws, socketData)];
1032
- case 5:
1033
- // call our existing processSocketMessage
1034
- _a.sent();
1035
- return [2 /*return*/];
1036
- }
1037
- });
1038
- }); })
1039
- .on('end', function () {
1040
- ws.close();
1041
- })
1042
- .on('error', function () {
1043
- ws.close();
1044
- })
1045
- .on('close', function () { return __awaiter(_this, void 0, void 0, function () {
1046
- return __generator(this, function (_a) {
1047
- switch (_a.label) {
1048
- case 0:
1049
- this.logConnectDebug('WS client closed', {
1050
- id_socket: ws['id_socket'],
1051
- id_user: ws['id_user'],
1052
- user: ws['user']
1053
- });
1054
- return [4 /*yield*/, this.unsubscribeWS(ws)];
1055
- case 1:
1056
- _a.sent();
1057
- return [2 /*return*/];
1058
- }
1059
- });
1060
- }); });
1061
- // Do not block message handler registration on DB write; this avoids losing
1062
- // very-early subscription messages sent immediately after websocket open.
1063
- setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
1064
- var error_4;
1065
- return __generator(this, function (_a) {
1066
- switch (_a.label) {
1067
- case 0:
1068
- _a.trys.push([0, 2, , 3]);
1069
- return [4 /*yield*/, this._subscriptionManager.createLoggedInUser(ws['id_socket'])];
1070
- case 1:
1071
- _a.sent();
1072
- return [3 /*break*/, 3];
1073
- case 2:
1074
- error_4 = _a.sent();
1075
- console.error(new Date(), 'Error creating logged-in user', ws['id_socket'], error_4);
1076
- this.logConnectDebug('Create logged-in user failed', {
1077
- id_socket: ws['id_socket'],
1078
- id_user: ws['id_user'],
1079
- user: ws['user'],
1080
- error: (error_4 === null || error_4 === void 0 ? void 0 : error_4.message) || error_4
1081
- });
1082
- return [3 /*break*/, 3];
1083
- case 3: return [2 /*return*/];
1393
+ });
1394
+ }); }, this._clientHeartbeatInitialDelayMs);
1395
+ if (this.LOGGER === 'DEBUG') {
1396
+ console.log('Connection from user: ' + req['user']);
1084
1397
  }
1085
- });
1086
- }); }, 0);
1087
- }
1088
- });
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
+ }); });
1089
1535
  // Keep alive timer
1090
1536
  setInterval(function () { return __awaiter(_this, void 0, void 0, function () {
1091
- var _a, _b, ws, e_2_1;
1092
- var e_2, _c;
1537
+ var _a, _b, ws, e_7_1;
1538
+ var e_7, _c;
1093
1539
  return __generator(this, function (_d) {
1094
1540
  switch (_d.label) {
1095
1541
  case 0:
@@ -1130,14 +1576,14 @@ var ResolveIOMainServer = /** @class */ (function () {
1130
1576
  return [3 /*break*/, 1];
1131
1577
  case 9: return [3 /*break*/, 12];
1132
1578
  case 10:
1133
- e_2_1 = _d.sent();
1134
- e_2 = { error: e_2_1 };
1579
+ e_7_1 = _d.sent();
1580
+ e_7 = { error: e_7_1 };
1135
1581
  return [3 /*break*/, 12];
1136
1582
  case 11:
1137
1583
  try {
1138
1584
  if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
1139
1585
  }
1140
- finally { if (e_2) throw e_2.error; }
1586
+ finally { if (e_7) throw e_7.error; }
1141
1587
  return [7 /*endfinally*/];
1142
1588
  case 12: return [2 /*return*/];
1143
1589
  }
@@ -1146,8 +1592,8 @@ var ResolveIOMainServer = /** @class */ (function () {
1146
1592
  };
1147
1593
  ResolveIOMainServer.prototype.processSocketMessage = function (ws, socketData) {
1148
1594
  return __awaiter(this, void 0, void 0, function () {
1149
- var socketData_1, socketData_1_1, message, e_3_1;
1150
- var e_3, _a;
1595
+ var socketData_1, socketData_1_1, message, e_8_1;
1596
+ var e_8, _a;
1151
1597
  return __generator(this, function (_b) {
1152
1598
  switch (_b.label) {
1153
1599
  case 0:
@@ -1191,14 +1637,14 @@ var ResolveIOMainServer = /** @class */ (function () {
1191
1637
  return [3 /*break*/, 2];
1192
1638
  case 5: return [3 /*break*/, 8];
1193
1639
  case 6:
1194
- e_3_1 = _b.sent();
1195
- e_3 = { error: e_3_1 };
1640
+ e_8_1 = _b.sent();
1641
+ e_8 = { error: e_8_1 };
1196
1642
  return [3 /*break*/, 8];
1197
1643
  case 7:
1198
1644
  try {
1199
1645
  if (socketData_1_1 && !socketData_1_1.done && (_a = socketData_1.return)) _a.call(socketData_1);
1200
1646
  }
1201
- finally { if (e_3) throw e_3.error; }
1647
+ finally { if (e_8) throw e_8.error; }
1202
1648
  return [7 /*endfinally*/];
1203
1649
  case 8: return [2 /*return*/];
1204
1650
  }
@@ -1605,6 +2051,267 @@ var ResolveIOMainServer = /** @class */ (function () {
1605
2051
  }
1606
2052
  return false;
1607
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
+ };
1608
2315
  ResolveIOMainServer.prototype.logConnectDebug = function (message, details) {
1609
2316
  if (!this._wsConnectDebug) {
1610
2317
  return;
@@ -1953,7 +2660,7 @@ var ResolveIOMainServer = /** @class */ (function () {
1953
2660
  });
1954
2661
  }
1955
2662
  setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
1956
- var error_5;
2663
+ var error_6;
1957
2664
  return __generator(this, function (_a) {
1958
2665
  switch (_a.label) {
1959
2666
  case 0:
@@ -1963,8 +2670,8 @@ var ResolveIOMainServer = /** @class */ (function () {
1963
2670
  _a.sent();
1964
2671
  return [3 /*break*/, 3];
1965
2672
  case 2:
1966
- error_5 = _a.sent();
1967
- console.error(new Date(), 'aiCoderTerminalRunCodex failed:', error_5);
2673
+ error_6 = _a.sent();
2674
+ console.error(new Date(), 'aiCoderTerminalRunCodex failed:', error_6);
1968
2675
  return [3 /*break*/, 3];
1969
2676
  case 3: return [2 /*return*/];
1970
2677
  }