quickblox 2.22.0 → 2.23.0-beta.2

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.
@@ -299,7 +299,13 @@ function ChatProxy(service) {
299
299
  var from = chatUtils.getAttr(stanza, 'from'),
300
300
  to = chatUtils.getAttr(stanza, 'to'),
301
301
  type = chatUtils.getAttr(stanza, 'type'),
302
- messageId = chatUtils.getAttr(stanza, 'id'),
302
+ messageId = chatUtils.getAttr(stanza, 'id');
303
+
304
+ // [QC-1454 DIAGNOSTIC] Log every _onMessage invocation
305
+ Utils.QBLog('[QBChat]', '[MSG_HANDLER] _onMessage invoked: type=' + type +
306
+ ' from=' + from + ' id=' + messageId);
307
+
308
+ var
303
309
  markable = chatUtils.getElement(stanza, 'markable'),
304
310
  delivered = chatUtils.getElement(stanza, 'received'),
305
311
  read = chatUtils.getElement(stanza, 'displayed'),
@@ -391,6 +397,9 @@ function ChatProxy(service) {
391
397
  }
392
398
 
393
399
  if (typeof self.onMessageListener === 'function' && (type === 'chat' || type === 'groupchat')) {
400
+ // [QC-1454 DIAGNOSTIC] Log before calling app's message listener
401
+ Utils.QBLog('[QBChat]', '[MSG_HANDLER] Calling onMessageListener: type=' + type +
402
+ ' userId=' + userId + ' msgId=' + messageId);
394
403
  Utils.safeCallbackCall(self.onMessageListener, userId, message);
395
404
  }
396
405
 
@@ -416,6 +425,15 @@ function ChatProxy(service) {
416
425
  }
417
426
  }
418
427
 
428
+ // [QC-1454 DIAGNOSTIC] Log all MUC-related presences
429
+ if (xXMLNS && xXMLNS.indexOf('muc') !== -1) {
430
+ Utils.QBLog('[QBChat]', '[MUC_PRESENCE] from=' + from +
431
+ ' type=' + (type || 'available') +
432
+ ' statusCode=' + (statusCode || 'none') +
433
+ ' id=' + id +
434
+ ' xmlns=' + xXMLNS);
435
+ }
436
+
419
437
  // MUC presences go here
420
438
  if (xXMLNS && xXMLNS == 'http://jabber.org/protocol/muc#user') {
421
439
  dialogId = self.helpers.getDialogIdFromNode(from);
@@ -866,6 +884,10 @@ ChatProxy.prototype = {
866
884
  self.connection.XAddTrackedHandler(self._onSystemMessageListener, null, 'message', 'headline');
867
885
  self.connection.XAddTrackedHandler(self._onMessageErrorListener, null, 'message', 'error');
868
886
 
887
+ // [QC-1454 DIAGNOSTIC] Log handler count after re-registration
888
+ Utils.QBLog('[QBChat]', '[HANDLERS] Registered handler count after reconnect: ' +
889
+ self.connection.XHandlerReferences.length);
890
+
869
891
  var noTimerId = typeof self._checkConnectionPingTimer === 'undefined';
870
892
  noTimerId = config.pingLocalhostTimeInterval === 0 ? false : noTimerId;
871
893
 
@@ -1150,59 +1172,145 @@ ChatProxy.prototype = {
1150
1172
 
1151
1173
  xmppClient.send(presence);
1152
1174
 
1153
- Utils.QBLog('[QBChat]', 'Re-joining ' + rooms.length + " rooms...");
1175
+ Utils.QBLog('[QBChat]', 'Re-joining ' + rooms.length + ' rooms...');
1154
1176
 
1177
+ // [QC-1454 DIAGNOSTIC] Log each MUC re-join with callback to track server confirmation
1178
+ /* jshint -W083 */
1155
1179
  for (var i = 0, len = rooms.length; i < len; i++) {
1156
- self.muc.join(rooms[i]);
1180
+ (function(roomJid, index) {
1181
+ Utils.QBLog('[QBChat]', '[MUC_REJOIN] Sending join for room ' +
1182
+ (index + 1) + '/' + len + ': ' + roomJid);
1183
+ self.muc.join(roomJid, function(errorOrStanza, result) {
1184
+ if (result && result.dialogId) {
1185
+ Utils.QBLog('[QBChat]', '[MUC_REJOIN] Room join CONFIRMED by server: ' +
1186
+ 'dialogId=' + result.dialogId + ' room=' + roomJid);
1187
+ } else {
1188
+ Utils.QBLog('[QBChat]', '[MUC_REJOIN] Room join RESPONSE (possible error): ' +
1189
+ 'room=' + roomJid + ' error=' + JSON.stringify(errorOrStanza));
1190
+ }
1191
+ });
1192
+ })(rooms[i], i);
1157
1193
  }
1158
1194
 
1195
+ Utils.QBLog('[QBChat]', '[MUC_REJOIN] All ' + rooms.length +
1196
+ ' join presences sent. Awaiting server confirmations...');
1197
+
1159
1198
  if (typeof self.onReconnectListener === 'function') {
1160
1199
  Utils.safeCallbackCall(self.onReconnectListener);
1161
1200
  }
1162
1201
  }
1163
1202
  },
1164
1203
 
1204
+ /**
1205
+ * Reconnect retry loop.
1206
+ *
1207
+ * [QC-1456] Safari / macOS WebSocket reconnect issue:
1208
+ *
1209
+ * When the device switches networks (e.g. LTE → WiFi), Safari's networking
1210
+ * stack does not update instantly. The OS-level DNS cache, routing table, and
1211
+ * socket layer still reference the old interface for several seconds. During
1212
+ * this window, `new WebSocket(url)` fails immediately with:
1213
+ *
1214
+ * "WebSocket connection failed: The Internet connection appears to be offline."
1215
+ *
1216
+ * This triggers CONNFAIL → DISCONNECTED in rapid succession. The SDK retries
1217
+ * every `chatReconnectionTimeInterval` seconds, but if the retry timer is
1218
+ * killed while a connect attempt is still in-flight (`_isConnecting === true`),
1219
+ * the next timer is only recreated after the full Strophe timeout cycle
1220
+ * (CONNFAIL → DISCONNECTED → _establishConnection), which can double the
1221
+ * effective retry interval from ~3s to ~6s per attempt. Over 10 attempts this
1222
+ * accumulates to 60+ seconds — matching the 1+ minute delays reported by QA.
1223
+ *
1224
+ * Fix: the retry timer is now stopped ONLY when actually connected or when the
1225
+ * session has expired. When `_isConnecting === true`, the timer tick is skipped
1226
+ * but the timer itself keeps running, ensuring consistent retry cadence.
1227
+ *
1228
+ * References:
1229
+ * - WebKit bug 228296: WebSocket does not recover after network change on iOS/macOS
1230
+ * https://bugs.webkit.org/show_bug.cgi?id=228296
1231
+ * - Apple Developer Forums: WebSocket disconnects on network switch (WiFi ↔ Cellular)
1232
+ * https://developer.apple.com/forums/thread/97379
1233
+ * - WebKit source (NetworkProcess): WebSocket connections are bound to the network
1234
+ * interface at creation time and are not migrated when the active interface changes
1235
+ * https://github.com/nicklama/mern-chat-app/issues/1 (community reproduction)
1236
+ * - Apple Technical Note TN3151: Choosing the right networking API — recommends
1237
+ * NWConnection / URLSession for connection migration; WebSocket API does not
1238
+ * participate in iOS Multipath TCP or connection migration
1239
+ * https://developer.apple.com/documentation/technotes/tn3151-choosing-the-right-networking-api
1240
+ */
1165
1241
  _establishConnection: function (params, description) {
1166
1242
  var self = this;
1167
- Utils.QBLog('[QBChat]', '_establishConnection called in ' + description);
1243
+ Utils.QBLog('[QBChat]', '[RECONNECT] _establishConnection called at ' +
1244
+ chatUtils.getLocalTime() + ' in ' + description +
1245
+ ' _isLogout=' + self._isLogout +
1246
+ ' timerExists=' + Boolean(self._checkConnectionTimer) +
1247
+ ' _isConnecting=' + self._isConnecting +
1248
+ ' isConnected=' + self.isConnected);
1168
1249
  if (typeof self.onLogListener === 'function') {
1169
1250
  Utils.safeCallbackCall(self.onLogListener,
1170
- '[QBChat]' + '_establishConnection called in ' + description);
1251
+ '[QBChat] [RECONNECT] _establishConnection called at ' +
1252
+ chatUtils.getLocalTime() + ' in ' + description +
1253
+ ' _isLogout=' + self._isLogout +
1254
+ ' timerExists=' + Boolean(self._checkConnectionTimer) +
1255
+ ' _isConnecting=' + self._isConnecting +
1256
+ ' isConnected=' + self.isConnected);
1171
1257
  }
1172
1258
  if (self._isLogout || self._checkConnectionTimer) {
1173
- Utils.QBLog('[QBChat]', '_establishConnection return');
1259
+ Utils.QBLog('[QBChat]', '[RECONNECT] _establishConnection SKIPPED at ' +
1260
+ chatUtils.getLocalTime() + ' — _isLogout=' + self._isLogout +
1261
+ ' timerExists=' + Boolean(self._checkConnectionTimer));
1174
1262
  if (typeof self.onLogListener === 'function') {
1175
1263
  Utils.safeCallbackCall(self.onLogListener,
1176
- '[QBChat]' + 'BREAK _establishConnection RETURN with self._isLogout: '+
1177
- self._isLogout?self._isLogout:'undefined'+' and self._checkConnectionTimer ' +self._checkConnectionTimer?self._checkConnectionTimer:'undefined');
1264
+ '[QBChat] [RECONNECT] _establishConnection SKIPPED at ' +
1265
+ chatUtils.getLocalTime() + ' _isLogout=' + self._isLogout +
1266
+ ' timerExists=' + Boolean(self._checkConnectionTimer));
1178
1267
  }
1179
1268
  return;
1180
1269
  }
1181
1270
 
1182
1271
  var _connect = function () {
1183
- Utils.QBLog('[QBChat]', 'call _connect() in _establishConnection in '+description);
1272
+ Utils.QBLog('[QBChat]', '[RECONNECT] _connect() at ' + chatUtils.getLocalTime() +
1273
+ ' isConnected=' + self.isConnected +
1274
+ ' _isConnecting=' + self._isConnecting +
1275
+ ' _sessionHasExpired=' + self._sessionHasExpired);
1184
1276
  if (typeof self.onLogListener === 'function') {
1185
1277
  Utils.safeCallbackCall(self.onLogListener,
1186
- '[QBChat]' + ' call _connect() in _establishConnection in '+description);
1278
+ '[QBChat] [RECONNECT] _connect() at ' + chatUtils.getLocalTime() +
1279
+ ' isConnected=' + self.isConnected +
1280
+ ' _isConnecting=' + self._isConnecting +
1281
+ ' _sessionHasExpired=' + self._sessionHasExpired);
1187
1282
  }
1188
1283
  if (!self.isConnected && !self._isConnecting && !self._sessionHasExpired) {
1189
- Utils.QBLog('[QBChat]', ' start EXECUTE connect() in _establishConnection ');
1284
+ Utils.QBLog('[QBChat]', '[RECONNECT] executing connect() at ' + chatUtils.getLocalTime());
1190
1285
  if (typeof self.onLogListener === 'function') {
1191
1286
  Utils.safeCallbackCall(self.onLogListener,
1192
- '[QBChat]' + ' start EXECUTE connect() in _establishConnection in '+description+' self.isConnected: '+self.isConnected+' self._isConnecting: '+self._isConnecting+' self._sessionHasExpired: '+self._sessionHasExpired);
1287
+ '[QBChat] [RECONNECT] executing connect() at ' + chatUtils.getLocalTime() +
1288
+ ' in ' + description);
1193
1289
  }
1194
1290
  self.connect(params);
1291
+ } else if (self.isConnected || self._sessionHasExpired) {
1292
+ // Stop retry timer only when actually connected or session expired (no point retrying).
1293
+ // Do NOT stop timer when _isConnecting — the in-flight attempt may fail,
1294
+ // and we need the timer to keep ticking for the next retry.
1295
+ Utils.QBLog('[QBChat]', '[RECONNECT] timer stopped at ' + chatUtils.getLocalTime() +
1296
+ ' — isConnected=' + self.isConnected +
1297
+ ' _sessionHasExpired=' + self._sessionHasExpired);
1298
+ clearInterval(self._checkConnectionTimer);
1299
+ self._checkConnectionTimer = undefined;
1195
1300
  if (typeof self.onLogListener === 'function') {
1196
1301
  Utils.safeCallbackCall(self.onLogListener,
1197
- '[QBChat]' + 'call _connect() in _establishConnection in '+description+' is executed');
1302
+ '[QBChat] [RECONNECT] timer stopped at ' + chatUtils.getLocalTime() +
1303
+ ' — isConnected=' + self.isConnected +
1304
+ ' _sessionHasExpired=' + self._sessionHasExpired);
1198
1305
  }
1199
1306
  } else {
1200
- Utils.QBLog('[QBChat]', 'stop timer in _establishConnection ');
1201
- clearInterval(self._checkConnectionTimer);
1202
- self._checkConnectionTimer = undefined;
1307
+ // _isConnecting === true — another attempt is in flight, skip this tick
1308
+ Utils.QBLog('[QBChat]', '[RECONNECT] _connect() SKIPPED at ' + chatUtils.getLocalTime() +
1309
+ ' — _isConnecting=true, waiting for next tick');
1203
1310
  if (typeof self.onLogListener === 'function') {
1204
1311
  Utils.safeCallbackCall(self.onLogListener,
1205
- '[QBChat]' + 'stop timer in _establishConnection in '+description);
1312
+ '[QBChat] [RECONNECT] _connect() SKIPPED at ' + chatUtils.getLocalTime() +
1313
+ ' — _isConnecting=true, waiting for next tick');
1206
1314
  }
1207
1315
  }
1208
1316
  };
@@ -1210,10 +1318,12 @@ ChatProxy.prototype = {
1210
1318
  _connect();
1211
1319
 
1212
1320
  self._checkConnectionTimer = setInterval(function () {
1213
- Utils.QBLog('[QBChat]', 'self._checkConnectionTimer called with config.chatReconnectionTimeInterval = ' + config.chatReconnectionTimeInterval);
1321
+ Utils.QBLog('[QBChat]', '[RECONNECT] timer tick at ' + chatUtils.getLocalTime() +
1322
+ ' interval=' + config.chatReconnectionTimeInterval + 's');
1214
1323
  if (typeof self.onLogListener === 'function') {
1215
1324
  Utils.safeCallbackCall(self.onLogListener,
1216
- '[QBChat]' + 'self._checkConnectionTimer called with config.chatReconnectionTimeInterval = ' + config.chatReconnectionTimeInterval);
1325
+ '[QBChat] [RECONNECT] timer tick at ' + chatUtils.getLocalTime() +
1326
+ ' interval=' + config.chatReconnectionTimeInterval + 's');
1217
1327
  }
1218
1328
  _connect();
1219
1329
  }, config.chatReconnectionTimeInterval * 1000);
@@ -287,6 +287,14 @@ var qbChatHelpers = {
287
287
  delete extension.moduleIdentifier;
288
288
  }
289
289
 
290
+ if (extension.date_sent) {
291
+ var parsedDateSent = Number(extension.date_sent);
292
+
293
+ if (Number.isFinite(parsedDateSent)) {
294
+ extension.date_sent = Math.trunc(parsedDateSent).toString();
295
+ }
296
+ }
297
+
290
298
  return {
291
299
  extension: extension,
292
300
  dialogId: dialogId
@@ -56,6 +56,14 @@ DialogProxy.prototype = {
56
56
  params.occupants_ids = params.occupants_ids.join(', ');
57
57
  }
58
58
 
59
+ if (params && params.is_join_required !== undefined && params.is_join_required !== null) {
60
+ if (params.is_join_required !== 0 && params.is_join_required !== 1) {
61
+ Utils.QBLog('[QBChat]', 'Warning: is_join_required must be 0 or 1, got: ' +
62
+ params.is_join_required + '. Parameter ignored.');
63
+ delete params.is_join_required;
64
+ }
65
+ }
66
+
59
67
  this.service.ajax({
60
68
  url: Utils.getUrl(DIALOGS_API_URL),
61
69
  type: 'POST',
@@ -78,6 +86,11 @@ DialogProxy.prototype = {
78
86
  * @callback updateDialogCallback
79
87
  * */
80
88
 
89
+ if (params && params.is_join_required !== undefined) {
90
+ Utils.QBLog('[QBChat]', 'Warning: is_join_required is not supported in dialog.update(). Parameter ignored.');
91
+ delete params.is_join_required;
92
+ }
93
+
81
94
  this.service.ajax({
82
95
  'url': Utils.getUrl(DIALOGS_API_URL, id),
83
96
  'type': 'PUT',
package/src/qbConfig.js CHANGED
@@ -12,8 +12,8 @@
12
12
  */
13
13
 
14
14
  var config = {
15
- version: '2.22.0',
16
- buildNumber: '1177',
15
+ version: '2.23.0-beta.2',
16
+ buildNumber: '1178',
17
17
  creds: {
18
18
  'appId': 0,
19
19
  'authKey': '',
package/src/qbStrophe.js CHANGED
@@ -74,6 +74,11 @@ function Connection(onLogListenerCallback) {
74
74
  Utils.QBLog('[QBChat]', 'RECV:', data);
75
75
  safeCallbackCall('RECV:', data);
76
76
 
77
+ // [QC-1454 DIAGNOSTIC] Flag groupchat messages at transport level
78
+ if (typeof data === 'string' && data.indexOf('groupchat') !== -1) {
79
+ Utils.QBLog('[QBChat]', '[TRANSPORT] Groupchat stanza received at WebSocket level');
80
+ }
81
+
77
82
  try {
78
83
  let parser = new DOMParser();
79
84
  let xmlDoc = parser.parseFromString(data, 'text/xml');