quickblox 2.23.0-beta.2 → 2.23.1-beta.3
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/package.json +1 -1
- package/quickblox.js +175 -14
- package/quickblox.min.js +1 -1
- package/src/modules/chat/qbChat.js +173 -12
- package/src/qbConfig.js +2 -2
|
@@ -101,6 +101,19 @@ function ChatProxy(service) {
|
|
|
101
101
|
this._checkExpiredSessionTimer = undefined;
|
|
102
102
|
this._sessionHasExpired = false;
|
|
103
103
|
this._pings = {};
|
|
104
|
+
|
|
105
|
+
// [QC-1550] XMPP connection is considered "verified" only after the first
|
|
106
|
+
// successful pong response. Strophe emits Status.CONNECTED at the transport
|
|
107
|
+
// layer (TCP/WebSocket handshake completed) before XMPP-level traffic is
|
|
108
|
+
// actually flowing — this creates a race window where the application thinks
|
|
109
|
+
// chat is alive but pings can still time out. Gates onReconnectListener and
|
|
110
|
+
// onDisconnectedListener to avoid firing them based on an unverified state.
|
|
111
|
+
this._isConnectionVerified = false;
|
|
112
|
+
// [QC-1550] On reconnect, onReconnectListener is deferred until the first
|
|
113
|
+
// successful pong. This flag marks that a reconnect is awaiting verification:
|
|
114
|
+
// the ping success callback reads it to decide whether to fire the deferred
|
|
115
|
+
// listener. Reset on listener fire, on ping failure, and on logout.
|
|
116
|
+
this._isReconnectListenerPending = false;
|
|
104
117
|
//
|
|
105
118
|
this.helpers = new Helpers();
|
|
106
119
|
//
|
|
@@ -904,12 +917,35 @@ ChatProxy.prototype = {
|
|
|
904
917
|
' error: ', error);
|
|
905
918
|
self._chatPingFailedCounter += 1;
|
|
906
919
|
if (self._chatPingFailedCounter >= config.chatPingMissLimit) {
|
|
907
|
-
|
|
920
|
+
// [QC-1550] onDisconnectedListener should only fire when the
|
|
921
|
+
// previous connection was actually verified. If the ping miss
|
|
922
|
+
// limit is reached while we were still waiting for the first
|
|
923
|
+
// pong after reconnect (verification never succeeded), the
|
|
924
|
+
// application was never told the chat was "alive" — so we
|
|
925
|
+
// don't fire a misleading "disconnected" event for a state
|
|
926
|
+
// the consumer never observed.
|
|
927
|
+
if (self.isConnected && self._isConnectionVerified &&
|
|
928
|
+
typeof self.onDisconnectedListener === 'function') {
|
|
908
929
|
Utils.safeCallbackCall(self.onDisconnectedListener);
|
|
909
930
|
}
|
|
931
|
+
// [QC-1550] Reset verification state and clear any pending
|
|
932
|
+
// reconnect listener — the connection has failed; the next
|
|
933
|
+
// CONNECTED + pong cycle will rebuild verification from
|
|
934
|
+
// scratch.
|
|
935
|
+
self._isConnectionVerified = false;
|
|
936
|
+
self._isReconnectListenerPending = false;
|
|
910
937
|
self.isConnected = false;
|
|
911
938
|
self._isConnecting = false;
|
|
912
939
|
self._chatPingFailedCounter = 0;
|
|
940
|
+
// [QC-1550] Stop this ping interval — it belongs to the dead
|
|
941
|
+
// connection. Without this, the timer keeps firing pings on
|
|
942
|
+
// the old XMPP session and racing with the new connection's
|
|
943
|
+
// ping cycle, which produced spurious pong success/failure
|
|
944
|
+
// interleaving in the field logs.
|
|
945
|
+
if (self._checkConnectionPingTimer !== undefined) {
|
|
946
|
+
clearInterval(self._checkConnectionPingTimer);
|
|
947
|
+
self._checkConnectionPingTimer = undefined;
|
|
948
|
+
}
|
|
913
949
|
self._establishConnection(params,'CONNECTED have SDK ping failed');
|
|
914
950
|
}
|
|
915
951
|
} else {
|
|
@@ -918,6 +954,32 @@ ChatProxy.prototype = {
|
|
|
918
954
|
'ok, at ', Utils.getCurrentTime(),
|
|
919
955
|
'_chatPingFailedCounter: ', self._chatPingFailedCounter);
|
|
920
956
|
self._chatPingFailedCounter = 0;
|
|
957
|
+
|
|
958
|
+
// [QC-1550] First pong after reconnect verifies XMPP is live.
|
|
959
|
+
// Fire the deferred onReconnectListener now (was postponed in
|
|
960
|
+
// _postConnectActions). Skip if logout was triggered between
|
|
961
|
+
// CONNECTED and pong — listener fire would leak past logout.
|
|
962
|
+
if (self._isReconnectListenerPending && !self._isLogout) {
|
|
963
|
+
self._isConnectionVerified = true;
|
|
964
|
+
self._isReconnectListenerPending = false;
|
|
965
|
+
Utils.QBLog('[QBChat]',
|
|
966
|
+
'[QC-1550] First pong success, firing deferred onReconnectListener at ',
|
|
967
|
+
Utils.getCurrentTime());
|
|
968
|
+
if (typeof self.onLogListener === 'function') {
|
|
969
|
+
Utils.safeCallbackCall(self.onLogListener,
|
|
970
|
+
'[QBChat] [QC-1550] First pong success, firing deferred onReconnectListener at ' +
|
|
971
|
+
chatUtils.getLocalTime());
|
|
972
|
+
}
|
|
973
|
+
if (typeof self.onReconnectListener === 'function') {
|
|
974
|
+
Utils.safeCallbackCall(self.onReconnectListener);
|
|
975
|
+
}
|
|
976
|
+
} else if (!self._isConnectionVerified && !self._isLogout) {
|
|
977
|
+
// Edge case: pong succeeded but pending flag was cleared
|
|
978
|
+
// (e.g. by ping failure path racing with this success).
|
|
979
|
+
// Mark verified anyway so onDisconnectedListener gating
|
|
980
|
+
// in SDK-4 works correctly going forward.
|
|
981
|
+
self._isConnectionVerified = true;
|
|
982
|
+
}
|
|
921
983
|
}
|
|
922
984
|
});
|
|
923
985
|
} catch (err) {
|
|
@@ -972,11 +1034,30 @@ ChatProxy.prototype = {
|
|
|
972
1034
|
'[QBChat]' + '[SDK v'+config.version+']' +' Status.DISCONNECTED at ' +
|
|
973
1035
|
chatUtils.getLocalTime()+ ' DISCONNECTED CONDITION: ' + condition);
|
|
974
1036
|
}
|
|
975
|
-
// fire 'onDisconnectedListener' only once
|
|
976
|
-
|
|
1037
|
+
// [QC-1550] fire 'onDisconnectedListener' only once AND only when the
|
|
1038
|
+
// previous connection was actually verified (first pong succeeded).
|
|
1039
|
+
// See onDisconnectedListener gating in SDK-4 for full reasoning — this
|
|
1040
|
+
// is the same rule applied to transport-level disconnects.
|
|
1041
|
+
if (self.isConnected && self._isConnectionVerified &&
|
|
1042
|
+
typeof self.onDisconnectedListener === 'function') {
|
|
977
1043
|
Utils.safeCallbackCall(self.onDisconnectedListener);
|
|
978
1044
|
}
|
|
979
1045
|
|
|
1046
|
+
// [QC-1550] Reset verification state. The next CONNECTED + pong cycle
|
|
1047
|
+
// will rebuild it. Pending reconnect listener (if any) is cancelled —
|
|
1048
|
+
// the new cycle will set a fresh one in _postConnectActions.
|
|
1049
|
+
self._isConnectionVerified = false;
|
|
1050
|
+
self._isReconnectListenerPending = false;
|
|
1051
|
+
|
|
1052
|
+
// [QC-1550] Stop ping timer of the dead connection. Without this, the
|
|
1053
|
+
// interval keeps invoking self.pingchat() on a stale Strophe connection
|
|
1054
|
+
// until the new CONNECTED arrives, producing spurious failure logs and
|
|
1055
|
+
// racing with the new connection's freshly-started ping cycle.
|
|
1056
|
+
if (self._checkConnectionPingTimer !== undefined) {
|
|
1057
|
+
clearInterval(self._checkConnectionPingTimer);
|
|
1058
|
+
self._checkConnectionPingTimer = undefined;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
980
1061
|
self.isConnected = false;
|
|
981
1062
|
self._isConnecting = false;
|
|
982
1063
|
self.connection.reset();
|
|
@@ -1134,8 +1215,21 @@ ChatProxy.prototype = {
|
|
|
1134
1215
|
* - save user's JID;
|
|
1135
1216
|
* - enable carbons;
|
|
1136
1217
|
* - get and storage the user's roster (if the initial connect);
|
|
1137
|
-
* - recover the joined rooms and
|
|
1218
|
+
* - recover the joined rooms and defer 'onReconnectListener' until the first
|
|
1219
|
+
* pong confirms XMPP-level traffic (if the reconnect);
|
|
1138
1220
|
* - send initial presence to the chat server.
|
|
1221
|
+
*
|
|
1222
|
+
* [QC-1550] On reconnect, Strophe emits Status.CONNECTED as soon as the
|
|
1223
|
+
* transport handshake completes (WebSocket/BOSH), but the XMPP layer may not
|
|
1224
|
+
* yet be processing traffic — pings can still time out for several seconds.
|
|
1225
|
+
* Firing onReconnectListener at this moment leads consumers (e.g. UI overlays)
|
|
1226
|
+
* to believe chat is fully restored, then a subsequent ping miss triggers a
|
|
1227
|
+
* false "Lost connection" state. The fix defers the listener until the first
|
|
1228
|
+
* pong succeeds (see ping success branch in Strophe.Status.CONNECTED handler).
|
|
1229
|
+
*
|
|
1230
|
+
* Fallback: when ping is disabled (config.pingLocalhostTimeInterval === 0)
|
|
1231
|
+
* there is no pong to wait for, so we fire onReconnectListener immediately
|
|
1232
|
+
* to preserve backward compatibility for consumers who opted out of pings.
|
|
1139
1233
|
*/
|
|
1140
1234
|
_postConnectActions: function (callback, isInitialConnect) {
|
|
1141
1235
|
Utils.QBLog('[QBChat]', 'Status.CONNECTED at ' + chatUtils.getLocalTime());
|
|
@@ -1161,6 +1255,13 @@ ChatProxy.prototype = {
|
|
|
1161
1255
|
self._enableCarbons();
|
|
1162
1256
|
|
|
1163
1257
|
if (isInitialConnect) {
|
|
1258
|
+
// [QC-1550] Initial connect: no reconnect overlay race possible
|
|
1259
|
+
// (user just logged in), so we mark the connection as verified
|
|
1260
|
+
// immediately. This also enables onDisconnectedListener gating in
|
|
1261
|
+
// the DISCONNECTED handler from this point on.
|
|
1262
|
+
self._isConnectionVerified = true;
|
|
1263
|
+
self._isReconnectListenerPending = false;
|
|
1264
|
+
|
|
1164
1265
|
self.roster.get(function (contacts) {
|
|
1165
1266
|
xmppClient.send(presence);
|
|
1166
1267
|
|
|
@@ -1195,8 +1296,30 @@ ChatProxy.prototype = {
|
|
|
1195
1296
|
Utils.QBLog('[QBChat]', '[MUC_REJOIN] All ' + rooms.length +
|
|
1196
1297
|
' join presences sent. Awaiting server confirmations...');
|
|
1197
1298
|
|
|
1198
|
-
|
|
1199
|
-
|
|
1299
|
+
// [QC-1550] Defer onReconnectListener until first successful pong.
|
|
1300
|
+
// The ping success callback (in Strophe.Status.CONNECTED handler)
|
|
1301
|
+
// reads _isReconnectListenerPending and fires the listener once XMPP
|
|
1302
|
+
// is verified. Connection remains unverified until that point.
|
|
1303
|
+
self._isConnectionVerified = false;
|
|
1304
|
+
|
|
1305
|
+
if (config.pingLocalhostTimeInterval === 0) {
|
|
1306
|
+
// Ping disabled — no pong to wait for; preserve legacy behavior.
|
|
1307
|
+
Utils.QBLog('[QBChat]',
|
|
1308
|
+
'[QC-1550] pingLocalhostTimeInterval=0, firing onReconnectListener immediately (ping disabled)');
|
|
1309
|
+
self._isConnectionVerified = true;
|
|
1310
|
+
self._isReconnectListenerPending = false;
|
|
1311
|
+
if (typeof self.onReconnectListener === 'function') {
|
|
1312
|
+
Utils.safeCallbackCall(self.onReconnectListener);
|
|
1313
|
+
}
|
|
1314
|
+
} else {
|
|
1315
|
+
self._isReconnectListenerPending = true;
|
|
1316
|
+
Utils.QBLog('[QBChat]',
|
|
1317
|
+
'[QC-1550] onReconnectListener deferred until first pong success');
|
|
1318
|
+
if (typeof self.onLogListener === 'function') {
|
|
1319
|
+
Utils.safeCallbackCall(self.onLogListener,
|
|
1320
|
+
'[QBChat] [QC-1550] onReconnectListener deferred at ' +
|
|
1321
|
+
chatUtils.getLocalTime() + ' — awaiting first pong');
|
|
1322
|
+
}
|
|
1200
1323
|
}
|
|
1201
1324
|
}
|
|
1202
1325
|
},
|
|
@@ -1245,7 +1368,9 @@ ChatProxy.prototype = {
|
|
|
1245
1368
|
' _isLogout=' + self._isLogout +
|
|
1246
1369
|
' timerExists=' + Boolean(self._checkConnectionTimer) +
|
|
1247
1370
|
' _isConnecting=' + self._isConnecting +
|
|
1248
|
-
' isConnected=' + self.isConnected
|
|
1371
|
+
' isConnected=' + self.isConnected +
|
|
1372
|
+
' verified=' + self._isConnectionVerified +
|
|
1373
|
+
' pending=' + self._isReconnectListenerPending);
|
|
1249
1374
|
if (typeof self.onLogListener === 'function') {
|
|
1250
1375
|
Utils.safeCallbackCall(self.onLogListener,
|
|
1251
1376
|
'[QBChat] [RECONNECT] _establishConnection called at ' +
|
|
@@ -1253,7 +1378,9 @@ ChatProxy.prototype = {
|
|
|
1253
1378
|
' _isLogout=' + self._isLogout +
|
|
1254
1379
|
' timerExists=' + Boolean(self._checkConnectionTimer) +
|
|
1255
1380
|
' _isConnecting=' + self._isConnecting +
|
|
1256
|
-
' isConnected=' + self.isConnected
|
|
1381
|
+
' isConnected=' + self.isConnected +
|
|
1382
|
+
' verified=' + self._isConnectionVerified +
|
|
1383
|
+
' pending=' + self._isReconnectListenerPending);
|
|
1257
1384
|
}
|
|
1258
1385
|
if (self._isLogout || self._checkConnectionTimer) {
|
|
1259
1386
|
Utils.QBLog('[QBChat]', '[RECONNECT] _establishConnection SKIPPED at ' +
|
|
@@ -1272,13 +1399,17 @@ ChatProxy.prototype = {
|
|
|
1272
1399
|
Utils.QBLog('[QBChat]', '[RECONNECT] _connect() at ' + chatUtils.getLocalTime() +
|
|
1273
1400
|
' isConnected=' + self.isConnected +
|
|
1274
1401
|
' _isConnecting=' + self._isConnecting +
|
|
1275
|
-
' _sessionHasExpired=' + self._sessionHasExpired
|
|
1402
|
+
' _sessionHasExpired=' + self._sessionHasExpired +
|
|
1403
|
+
' verified=' + self._isConnectionVerified +
|
|
1404
|
+
' pending=' + self._isReconnectListenerPending);
|
|
1276
1405
|
if (typeof self.onLogListener === 'function') {
|
|
1277
1406
|
Utils.safeCallbackCall(self.onLogListener,
|
|
1278
1407
|
'[QBChat] [RECONNECT] _connect() at ' + chatUtils.getLocalTime() +
|
|
1279
1408
|
' isConnected=' + self.isConnected +
|
|
1280
1409
|
' _isConnecting=' + self._isConnecting +
|
|
1281
|
-
' _sessionHasExpired=' + self._sessionHasExpired
|
|
1410
|
+
' _sessionHasExpired=' + self._sessionHasExpired +
|
|
1411
|
+
' verified=' + self._isConnectionVerified +
|
|
1412
|
+
' pending=' + self._isReconnectListenerPending);
|
|
1282
1413
|
}
|
|
1283
1414
|
if (!self.isConnected && !self._isConnecting && !self._sessionHasExpired) {
|
|
1284
1415
|
Utils.QBLog('[QBChat]', '[RECONNECT] executing connect() at ' + chatUtils.getLocalTime());
|
|
@@ -1292,16 +1423,24 @@ ChatProxy.prototype = {
|
|
|
1292
1423
|
// Stop retry timer only when actually connected or session expired (no point retrying).
|
|
1293
1424
|
// Do NOT stop timer when _isConnecting — the in-flight attempt may fail,
|
|
1294
1425
|
// and we need the timer to keep ticking for the next retry.
|
|
1426
|
+
// [QC-1550] Note: timer is stopped on isConnected, BUT verification
|
|
1427
|
+
// (_isConnectionVerified) may still be pending — the first pong has
|
|
1428
|
+
// not yet arrived. The retry loop terminates here; from this point
|
|
1429
|
+
// it is the ping interval that drives verification.
|
|
1295
1430
|
Utils.QBLog('[QBChat]', '[RECONNECT] timer stopped at ' + chatUtils.getLocalTime() +
|
|
1296
1431
|
' — isConnected=' + self.isConnected +
|
|
1297
|
-
' _sessionHasExpired=' + self._sessionHasExpired
|
|
1432
|
+
' _sessionHasExpired=' + self._sessionHasExpired +
|
|
1433
|
+
' verified=' + self._isConnectionVerified +
|
|
1434
|
+
' pending=' + self._isReconnectListenerPending);
|
|
1298
1435
|
clearInterval(self._checkConnectionTimer);
|
|
1299
1436
|
self._checkConnectionTimer = undefined;
|
|
1300
1437
|
if (typeof self.onLogListener === 'function') {
|
|
1301
1438
|
Utils.safeCallbackCall(self.onLogListener,
|
|
1302
1439
|
'[QBChat] [RECONNECT] timer stopped at ' + chatUtils.getLocalTime() +
|
|
1303
1440
|
' — isConnected=' + self.isConnected +
|
|
1304
|
-
' _sessionHasExpired=' + self._sessionHasExpired
|
|
1441
|
+
' _sessionHasExpired=' + self._sessionHasExpired +
|
|
1442
|
+
' verified=' + self._isConnectionVerified +
|
|
1443
|
+
' pending=' + self._isReconnectListenerPending);
|
|
1305
1444
|
}
|
|
1306
1445
|
} else {
|
|
1307
1446
|
// _isConnecting === true — another attempt is in flight, skip this tick
|
|
@@ -1338,6 +1477,21 @@ ChatProxy.prototype = {
|
|
|
1338
1477
|
}
|
|
1339
1478
|
clearInterval(this._checkConnectionTimer);
|
|
1340
1479
|
this._checkConnectionTimer = undefined;
|
|
1480
|
+
// [QC-1550] Stop ping timer of the previous connection. Without this,
|
|
1481
|
+
// the interval continues to invoke pingchat() on a stale Strophe
|
|
1482
|
+
// connection across the reconnect cycle, racing with the new
|
|
1483
|
+
// connection's freshly-started ping cycle once Status.CONNECTED arrives.
|
|
1484
|
+
if (this._checkConnectionPingTimer !== undefined) {
|
|
1485
|
+
clearInterval(this._checkConnectionPingTimer);
|
|
1486
|
+
this._checkConnectionPingTimer = undefined;
|
|
1487
|
+
}
|
|
1488
|
+
// [QC-1550] Reset XMPP verification state — the new CONNECTED + pong
|
|
1489
|
+
// cycle from _postConnectActions will rebuild it. Without this reset, a
|
|
1490
|
+
// stale _isConnectionVerified=true from the previous session would let
|
|
1491
|
+
// onDisconnectedListener fire on the next ping miss before the new
|
|
1492
|
+
// connection had a chance to verify itself.
|
|
1493
|
+
this._isConnectionVerified = false;
|
|
1494
|
+
this._isReconnectListenerPending = false;
|
|
1341
1495
|
this.muc.joinedRooms = {};
|
|
1342
1496
|
this.helpers.setUserCurrentJid('');
|
|
1343
1497
|
|
|
@@ -1731,6 +1885,13 @@ ChatProxy.prototype = {
|
|
|
1731
1885
|
this._checkConnectionTimer = undefined;
|
|
1732
1886
|
this._checkExpiredSessionTimer = undefined;
|
|
1733
1887
|
this.muc.joinedRooms = {};
|
|
1888
|
+
// [QC-1550] Reset XMPP verification state on explicit disconnect so the
|
|
1889
|
+
// next connect() starts from a clean slate. _isLogout guard below also
|
|
1890
|
+
// prevents firing of deferred listeners if a pong arrives in flight,
|
|
1891
|
+
// but resetting here keeps state consistent for consumers that call
|
|
1892
|
+
// disconnect() + connect() in sequence.
|
|
1893
|
+
this._isConnectionVerified = false;
|
|
1894
|
+
this._isReconnectListenerPending = false;
|
|
1734
1895
|
this._isLogout = true;
|
|
1735
1896
|
this.helpers.setUserCurrentJid('');
|
|
1736
1897
|
|