quickblox 2.24.0 → 2.24.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,13 @@ var chatUtils = require('./qbChatHelpers'),
11
11
 
12
12
  var unsupportedError = 'This function isn\'t supported outside of the browser (...yet)';
13
13
 
14
+ /**
15
+ * [CROS-1061] How long to wait for the server to answer a Notice enable/disable
16
+ * IQ before reporting an error to the caller. Without a bound the callback is
17
+ * never invoked when the server stays silent.
18
+ */
19
+ var NOTICE_IQ_TIMEOUT_MS = 10000;
20
+
14
21
  var XMPP;
15
22
 
16
23
  /** create StropheJS or NodeXMPP connection object */
@@ -44,6 +51,7 @@ NOTICE_KNOWN_MODULE_IDS[noticeConsts.NOTICE_MODULE_IDENTIFIER.UPDATED_MESSAGE] =
44
51
  NOTICE_KNOWN_MODULE_IDS[noticeConsts.NOTICE_MODULE_IDENTIFIER.DELETED_MESSAGE] = true;
45
52
  NOTICE_KNOWN_MODULE_IDS[noticeConsts.NOTICE_MODULE_IDENTIFIER.UPDATED_DIALOG] = true;
46
53
  NOTICE_KNOWN_MODULE_IDS[noticeConsts.NOTICE_MODULE_IDENTIFIER.DELETED_DIALOG] = true;
54
+ NOTICE_KNOWN_MODULE_IDS[noticeConsts.NOTICE_MODULE_IDENTIFIER.CREATED_DIALOG] = true;
47
55
 
48
56
  /**
49
57
  * Cross-env: get all direct child elements of `parent` whose tag name equals
@@ -327,6 +335,91 @@ function _parseCustomData(extraParams) {
327
335
  return out;
328
336
  }
329
337
 
338
+ /**
339
+ * Build a partial QBChatDialog object from a dialog-notice <extraParams>.
340
+ * Shared by NoticeUpdatedDialog and NoticeCreatedDialog (SR-2952) — the server
341
+ * sends a full dialog snapshot in both cases. Fields with null/empty values are
342
+ * omitted by the server, so every read is guarded.
343
+ *
344
+ * CreatedDialog additionally carries created_at/updated_at/user_id (creator) and
345
+ * has no last_message* fields (dialog just created); UpdatedDialog carries
346
+ * last_message* and (per server contract) may also carry created_at/updated_at/
347
+ * user_id. Reading them here for both is safe — absent fields are simply skipped.
348
+ *
349
+ * @param {Element} extraParams
350
+ * @param {String} dialogId - already-resolved dialog_id text.
351
+ * @return {Object} partial dialog snapshot.
352
+ */
353
+ function _parseDialogSnapshot(extraParams, dialogId) {
354
+ var dlg = {};
355
+ dlg._id = dialogId;
356
+
357
+ var name = chatUtils.getElementText(extraParams, 'name');
358
+ if (name !== undefined && name !== null && name !== '') {
359
+ dlg.name = name;
360
+ }
361
+ var photo = chatUtils.getElementText(extraParams, 'photo');
362
+ if (photo !== undefined && photo !== null && photo !== '') {
363
+ dlg.photo = photo;
364
+ }
365
+ var typeText = chatUtils.getElementText(extraParams, 'type');
366
+ var typeNum = parseInt(typeText, 10);
367
+ if (!isNaN(typeNum)) {
368
+ dlg.type = typeNum;
369
+ }
370
+ var occupants = _parseNumericIdsCsv(chatUtils.getElementText(extraParams, 'occupants_ids'));
371
+ if (occupants.length > 0) {
372
+ dlg.occupants_ids = occupants;
373
+ }
374
+ var admins = _parseNumericIdsCsv(chatUtils.getElementText(extraParams, 'admin_ids'));
375
+ if (admins.length > 0) {
376
+ dlg.admin_ids = admins;
377
+ }
378
+ var isJoinReqText = chatUtils.getElementText(extraParams, 'is_join_required');
379
+ if (isJoinReqText !== undefined && isJoinReqText !== null && isJoinReqText !== '') {
380
+ var isJoinReq = parseInt(isJoinReqText, 10);
381
+ dlg.is_join_required = isNaN(isJoinReq) ? isJoinReqText : isJoinReq;
382
+ }
383
+ var roomJid = chatUtils.getElementText(extraParams, 'xmpp_room_jid');
384
+ if (roomJid !== undefined && roomJid !== null && roomJid !== '') {
385
+ // Server may pretty-print xmpp_room_jid with surrounding whitespace.
386
+ dlg.xmpp_room_jid = roomJid.trim();
387
+ }
388
+ var customData = _parseCustomData(extraParams);
389
+ if (customData !== undefined) {
390
+ dlg.custom_data = customData;
391
+ }
392
+ // Creation / modification metadata (present on CreatedDialog; also valid on
393
+ // UpdatedDialog per server contract). Kept as ISO-8601 strings verbatim.
394
+ var createdAt = chatUtils.getElementText(extraParams, 'created_at');
395
+ if (createdAt !== undefined && createdAt !== null && createdAt !== '') {
396
+ dlg.created_at = createdAt;
397
+ }
398
+ var updatedAt = chatUtils.getElementText(extraParams, 'updated_at');
399
+ if (updatedAt !== undefined && updatedAt !== null && updatedAt !== '') {
400
+ dlg.updated_at = updatedAt;
401
+ }
402
+ var userIdText = chatUtils.getElementText(extraParams, 'user_id');
403
+ var userId = parseInt(userIdText, 10);
404
+ if (!isNaN(userId)) {
405
+ dlg.user_id = userId;
406
+ }
407
+ // Last message fields (present on UpdatedDialog; absent on a freshly
408
+ // CreatedDialog — the guards below simply skip them then).
409
+ var lmText = chatUtils.getElementText(extraParams, 'last_message');
410
+ if (lmText) { dlg.last_message = lmText; }
411
+ var lmId = chatUtils.getElementText(extraParams, 'last_message_id');
412
+ if (lmId) { dlg.last_message_id = lmId; }
413
+ var lmDsText = chatUtils.getElementText(extraParams, 'last_message_date_sent');
414
+ var lmDs = parseInt(lmDsText, 10);
415
+ if (!isNaN(lmDs)) { dlg.last_message_date_sent = lmDs; }
416
+ var lmUidText = chatUtils.getElementText(extraParams, 'last_message_user_id');
417
+ var lmUid = parseInt(lmUidText, 10);
418
+ if (!isNaN(lmUid)) { dlg.last_message_user_id = lmUid; }
419
+
420
+ return dlg;
421
+ }
422
+
330
423
  /**
331
424
  * Parse a Notice headline stanza into {type, payload} ready for routing.
332
425
  * Returns null if stanza is not a Notice (no urn:xmpp:notice:0 namespace, or
@@ -422,59 +515,12 @@ function _parseNoticeStanza(stanza) {
422
515
  message: msg
423
516
  };
424
517
  }
425
- } else if (type === IDS.UPDATED_DIALOG) {
426
- // Build a partial QBChatDialog object from extraParams (full server snapshot).
427
- var dlg = {};
428
- dlg._id = dialogId;
429
- var name = chatUtils.getElementText(extraParams, 'name');
430
- if (name !== undefined && name !== null && name !== '') {
431
- dlg.name = name;
432
- }
433
- var photo = chatUtils.getElementText(extraParams, 'photo');
434
- if (photo !== undefined && photo !== null && photo !== '') {
435
- dlg.photo = photo;
436
- }
437
- var typeText = chatUtils.getElementText(extraParams, 'type');
438
- var typeNum = parseInt(typeText, 10);
439
- if (!isNaN(typeNum)) {
440
- dlg.type = typeNum;
441
- }
442
- var occupants = _parseNumericIdsCsv(chatUtils.getElementText(extraParams, 'occupants_ids'));
443
- if (occupants.length > 0) {
444
- dlg.occupants_ids = occupants;
445
- }
446
- var admins = _parseNumericIdsCsv(chatUtils.getElementText(extraParams, 'admin_ids'));
447
- if (admins.length > 0) {
448
- dlg.admin_ids = admins;
449
- }
450
- var isJoinReqText = chatUtils.getElementText(extraParams, 'is_join_required');
451
- if (isJoinReqText !== undefined && isJoinReqText !== null && isJoinReqText !== '') {
452
- var isJoinReq = parseInt(isJoinReqText, 10);
453
- dlg.is_join_required = isNaN(isJoinReq) ? isJoinReqText : isJoinReq;
454
- }
455
- var roomJid = chatUtils.getElementText(extraParams, 'xmpp_room_jid');
456
- if (roomJid !== undefined && roomJid !== null && roomJid !== '') {
457
- dlg.xmpp_room_jid = roomJid;
458
- }
459
- var customData = _parseCustomData(extraParams);
460
- if (customData !== undefined) {
461
- dlg.custom_data = customData;
462
- }
463
- // Last message fields.
464
- var lmText = chatUtils.getElementText(extraParams, 'last_message');
465
- if (lmText) { dlg.last_message = lmText; }
466
- var lmId = chatUtils.getElementText(extraParams, 'last_message_id');
467
- if (lmId) { dlg.last_message_id = lmId; }
468
- var lmDsText = chatUtils.getElementText(extraParams, 'last_message_date_sent');
469
- var lmDs = parseInt(lmDsText, 10);
470
- if (!isNaN(lmDs)) { dlg.last_message_date_sent = lmDs; }
471
- var lmUidText = chatUtils.getElementText(extraParams, 'last_message_user_id');
472
- var lmUid = parseInt(lmUidText, 10);
473
- if (!isNaN(lmUid)) { dlg.last_message_user_id = lmUid; }
474
-
518
+ } else if (type === IDS.UPDATED_DIALOG || type === IDS.CREATED_DIALOG) {
519
+ // Full server dialog snapshot. UpdatedDialog and CreatedDialog (SR-2952)
520
+ // share the same shape; they differ only by moduleIdentifier (routing).
475
521
  payload = {
476
522
  kind: 'dialog',
477
- dialog: dlg
523
+ dialog: _parseDialogSnapshot(extraParams, dialogId)
478
524
  };
479
525
  } else {
480
526
  return null;
@@ -521,6 +567,10 @@ function _routeNoticeEvent(chatProxy, parsed) {
521
567
  if (typeof chatProxy.onDialogUpdatedListener === 'function') {
522
568
  Utils.safeCallbackCall(chatProxy.onDialogUpdatedListener, p.dialog);
523
569
  }
570
+ } else if (type === IDS.CREATED_DIALOG) {
571
+ if (typeof chatProxy.onDialogCreatedListener === 'function') {
572
+ Utils.safeCallbackCall(chatProxy.onDialogCreatedListener, p.dialog);
573
+ }
524
574
  }
525
575
  }
526
576
 
@@ -621,6 +671,15 @@ function ChatProxy(service) {
621
671
  this.onMessageReactionChangedListener = null;
622
672
  this.onDialogDeletedListener = null;
623
673
  this.onDialogUpdatedListener = null;
674
+ /**
675
+ * NoticeCreatedDialog listener (SR-2952). Fired when a Group or Private
676
+ * dialog is created and the current user has the Notice feature enabled.
677
+ * Signature: onDialogCreatedListener(dialog). The dialog is a partial
678
+ * snapshot from the creation notice — it carries created_at/updated_at/
679
+ * user_id (creator) but no last_message* fields (the dialog is brand new),
680
+ * and for a private dialog no xmpp_room_jid. Not fired for Public dialogs.
681
+ */
682
+ this.onDialogCreatedListener = null;
624
683
 
625
684
  // [QC-1550] XMPP connection is considered "verified" only after the first
626
685
  // successful pong response. Strophe emits Status.CONNECTED at the transport
@@ -1800,6 +1859,22 @@ ChatProxy.prototype = {
1800
1859
  self._isConnecting = false;
1801
1860
  self._sessionHasExpired = false;
1802
1861
 
1862
+ // [CROS-1061] A new XMPP session never carries the previous session's
1863
+ // Notice subscription: the server binds <enable xmlns="urn:xmpp:notice:0"/>
1864
+ // to the stream, so it is gone once the stream is gone. Without this
1865
+ // reset the flag stays `true` after a transport reconnect while no
1866
+ // notices are delivered any more, and isNoticesEnabled() misleads the
1867
+ // application into skipping the re-enable (verified against a live
1868
+ // server: socket break -> reconnect -> flag true, zero notice stanzas;
1869
+ // clearing the flag and re-sending <enable> restored delivery).
1870
+ //
1871
+ // Placed in the shared preamble, above the isInitialConnect branch, and
1872
+ // above the onReconnectListener call below: with
1873
+ // config.pingLocalhostTimeInterval === 0 that listener fires
1874
+ // synchronously from this function, and a consumer re-enabling notices
1875
+ // from it must not observe a stale `true`.
1876
+ self._isNoticesEnabled = false;
1877
+
1803
1878
  self._enableCarbons();
1804
1879
 
1805
1880
  if (isInitialConnect) {
@@ -1810,7 +1885,6 @@ ChatProxy.prototype = {
1810
1885
  self._isConnectionVerified = true;
1811
1886
  self._isReconnectListenerPending = false;
1812
1887
 
1813
- // TODO(2.25.0): auto-enable Notice Feature here (see qbChat.js enableNotices JSDoc).
1814
1888
  self.roster.get(function (contacts) {
1815
1889
  xmppClient.send(presence);
1816
1890
 
@@ -1818,7 +1892,24 @@ ChatProxy.prototype = {
1818
1892
  callback(self.roster.contacts);
1819
1893
  });
1820
1894
  } else {
1895
+ // [CROS-1061] Union of the rooms still tracked on this instance and
1896
+ // the ones an explicit reconnect() had to drop. Consumed once —
1897
+ // clearing it here keeps a later session (or another user after
1898
+ // logout) from resurrecting a stale list.
1899
+ //
1900
+ // Safe to build the join stanzas at this point: setUserCurrentJid()
1901
+ // above has already installed the new JID, and muc.join() derives
1902
+ // the presence `from` out of it. Moving this block above that call
1903
+ // would send from="".
1821
1904
  var rooms = Object.keys(self.muc.joinedRooms);
1905
+ var remembered = self.muc._roomsToRejoin || [];
1906
+
1907
+ for (var r = 0; r < remembered.length; r++) {
1908
+ if (rooms.indexOf(remembered[r]) === -1) {
1909
+ rooms.push(remembered[r]);
1910
+ }
1911
+ }
1912
+ self.muc._roomsToRejoin = [];
1822
1913
 
1823
1914
  xmppClient.send(presence);
1824
1915
 
@@ -2041,6 +2132,11 @@ ChatProxy.prototype = {
2041
2132
  // connection had a chance to verify itself.
2042
2133
  this._isConnectionVerified = false;
2043
2134
  this._isReconnectListenerPending = false;
2135
+ // [CROS-1061] Remember the rooms before dropping them, so the re-join
2136
+ // loop in _postConnectActions has something to restore. Without this the
2137
+ // list is lost and group dialogs stop receiving message-level notices
2138
+ // until the page is reloaded.
2139
+ this.muc._roomsToRejoin = Object.keys(this.muc.joinedRooms);
2044
2140
  this.muc.joinedRooms = {};
2045
2141
  this.helpers.setUserCurrentJid('');
2046
2142
 
@@ -2434,6 +2530,9 @@ ChatProxy.prototype = {
2434
2530
  this._checkConnectionTimer = undefined;
2435
2531
  this._checkExpiredSessionTimer = undefined;
2436
2532
  this.muc.joinedRooms = {};
2533
+ // [CROS-1061] Never carry rooms across a logout: re-joining the previous
2534
+ // user's rooms from a new JID is answered with 403/407 by the server.
2535
+ this.muc._roomsToRejoin = [];
2437
2536
  // [QC-1550] Reset XMPP verification state on explicit disconnect so the
2438
2537
  // next connect() starts from a clean slate. _isLogout guard below also
2439
2538
  // prevents firing of deferred listeners if a pong arrives in flight,
@@ -2465,19 +2564,29 @@ ChatProxy.prototype = {
2465
2564
  * Subscribe the current XMPP session to Notice Feature stanzas
2466
2565
  * (urn:xmpp:notice:0). After a successful IQ result the server starts
2467
2566
  * delivering NoticeUpdatedMessage / NoticeDeletedMessage /
2468
- * NoticeUpdatedDialog / NoticeDeletedDialog headline stanzas.
2567
+ * NoticeCreatedDialog / NoticeUpdatedDialog / NoticeDeletedDialog
2568
+ * headline stanzas.
2469
2569
  *
2470
- * The local enabled flag is reset to false on chat disconnect consumers
2471
- * must call enableNotices() again after a reconnect (intentional divergence
2472
- * from the Android SDK, which auto-restores the subscription).
2570
+ * The subscription belongs to the XMPP session, so it does not survive a
2571
+ * reconnect: the local enabled flag is reset to false whenever a new session
2572
+ * is established (initial connect and every reconnect alike) as well as on
2573
+ * disconnect. Consumers must call enableNotices() again after a reconnect —
2574
+ * an intentional divergence from the Android SDK, which auto-restores the
2575
+ * subscription. Reacting to onReconnectListener is the supported way to do
2576
+ * that.
2473
2577
  *
2474
- * TODO(2.25.0): make auto-enable SDK-internal fire on initial connect and
2475
- * on relogin after session-expired, skip on Stream-Management reconnect.
2476
- * Spec & three-state matrix are tracked in the internal 2.25.0 backlog
2477
- * (auto-enable notices).
2578
+ * Concurrent calls are safe: each IQ carries its own unique id, responses
2579
+ * are dispatched per id, and the flag is set independently for each result.
2580
+ * Calling enableNotices() again before the previous callback fires is
2581
+ * therefore allowed — for example when a second reconnect arrives while the
2582
+ * first enable is still in flight.
2478
2583
  *
2479
- * Do not call enableNotices again before the previous callback fires
2480
- * concurrent enable calls are not specified.
2584
+ * If the server does not answer, the callback receives an error after a
2585
+ * timeout instead of never being called.
2586
+ *
2587
+ * TODO(2.25.0): make auto-enable SDK-internal — fire on initial connect and
2588
+ * on every reconnect. Spec & three-state matrix are tracked in the internal
2589
+ * 2.25.0 backlog (auto-enable notices).
2481
2590
  *
2482
2591
  * @memberof QB.chat
2483
2592
  * @param {enableNoticesCallback} callback - Called with (error, result) on IQ result.
@@ -2516,7 +2625,10 @@ ChatProxy.prototype = {
2516
2625
  * Returns the local Notice Feature subscription flag.
2517
2626
  * @memberof QB.chat
2518
2627
  * @return {Boolean} true after a successful enableNotices(), false otherwise.
2519
- * Reset to false on chat disconnect; not synchronized with the server.
2628
+ * Reset to false on disconnect and whenever a new XMPP session is
2629
+ * established, so after a reconnect it reports false until the consumer
2630
+ * enables notices again. Not synchronized with the server: it reflects
2631
+ * what this client last requested, not the server's own state.
2520
2632
  * @since 2.24.0
2521
2633
  */
2522
2634
  isNoticesEnabled: function () {
@@ -2569,10 +2681,34 @@ ChatProxy.prototype = {
2569
2681
  }
2570
2682
 
2571
2683
  if (Utils.getEnv().browser) {
2572
- self.connection.sendIQ(iq, _onSuccess, _onError);
2684
+ // [CROS-1061] Without the 4th argument Strophe never times the IQ out,
2685
+ // so a server that stays silent leaves the caller without any callback
2686
+ // at all — the application spins forever. On timeout Strophe calls
2687
+ // _onError(null), which reports a 'cancel' error and leaves the flag
2688
+ // untouched.
2689
+ //
2690
+ // Note: this covers "server does not answer". It does NOT cover
2691
+ // "socket died", because the timeout is an addTimedHandler and
2692
+ // connection.reset() (called from the DISCONNECTED branch) clears
2693
+ // timedHandlers. That case is covered by the consumer-side timeout.
2694
+ self.connection.sendIQ(iq, _onSuccess, _onError, NOTICE_IQ_TIMEOUT_MS);
2573
2695
  } else {
2574
2696
  // Node/NativeScript path: register callback in nodeStanzasCallbacks map.
2697
+ // This path does not go through sendIQ, so the timeout is ours to run.
2698
+ var noticeIqTimer = setTimeout(function () {
2699
+ // Drop the pending entry first: _onIQ only dispatches while the
2700
+ // entry exists, so a late answer after this point is ignored
2701
+ // instead of invoking the callback a second time.
2702
+ delete self.nodeStanzasCallbacks[iqParams.id];
2703
+ _onError(null);
2704
+ }, NOTICE_IQ_TIMEOUT_MS);
2705
+
2575
2706
  self.nodeStanzasCallbacks[iqParams.id] = function (stanza) {
2707
+ // Must be first: otherwise a timely answer still leaves the timer
2708
+ // armed, and it would fire later and call the callback a second
2709
+ // time with an error.
2710
+ clearTimeout(noticeIqTimer);
2711
+
2576
2712
  // The map handler receives the result/error stanza. Inspect type to dispatch.
2577
2713
  var iqType = chatUtils.getAttr(stanza, 'type');
2578
2714
  if (iqType === 'result') {
@@ -2901,6 +3037,20 @@ function MucProxy(options) {
2901
3037
  this.nodeStanzasCallbacks = options.nodeStanzasCallbacks;
2902
3038
  //
2903
3039
  this.joinedRooms = {};
3040
+
3041
+ /**
3042
+ * [CROS-1061] Rooms carried across an explicit QB.chat.reconnect().
3043
+ *
3044
+ * reconnect() has to clear joinedRooms — the new session holds no presence
3045
+ * yet — but until now it dropped the list without keeping a copy, so the
3046
+ * re-join loop in _postConnectActions found nothing to restore and group
3047
+ * dialogs silently stopped receiving message-level notices.
3048
+ *
3049
+ * Written only by reconnect(), read and cleared once by _postConnectActions,
3050
+ * and cleared by disconnect() so one user's rooms can never be re-joined
3051
+ * from another user's JID.
3052
+ */
3053
+ this._roomsToRejoin = [];
2904
3054
  }
2905
3055
 
2906
3056
  MucProxy.prototype = {
@@ -23,7 +23,8 @@ var NOTICE_MODULE_IDENTIFIER = {
23
23
  UPDATED_MESSAGE: 'NoticeUpdatedMessage',
24
24
  DELETED_MESSAGE: 'NoticeDeletedMessage',
25
25
  UPDATED_DIALOG: 'NoticeUpdatedDialog',
26
- DELETED_DIALOG: 'NoticeDeletedDialog'
26
+ DELETED_DIALOG: 'NoticeDeletedDialog',
27
+ CREATED_DIALOG: 'NoticeCreatedDialog'
27
28
  };
28
29
 
29
30
  module.exports = {
package/src/qbConfig.js CHANGED
@@ -12,8 +12,8 @@
12
12
  */
13
13
 
14
14
  var config = {
15
- version: '2.24.0',
16
- buildNumber: '1180',
15
+ version: '2.24.1-alpha.1',
16
+ buildNumber: '1181',
17
17
  creds: {
18
18
  'appId': 0,
19
19
  'authKey': '',