quickblox 2.23.1-beta.5 → 2.24.0-beta.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.
package/quickblox.js CHANGED
@@ -30502,7 +30502,8 @@ function filterStats(result, track, outbound) {
30502
30502
  var chatUtils = require('./qbChatHelpers'),
30503
30503
  config = require('../../qbConfig'),
30504
30504
  Utils = require('../../qbUtils'),
30505
- StreamManagement = require('../../plugins/streamManagement');
30505
+ StreamManagement = require('../../plugins/streamManagement'),
30506
+ noticeConsts = require('./qbNoticeConsts');
30506
30507
 
30507
30508
  var unsupportedError = 'This function isn\'t supported outside of the browser (...yet)';
30508
30509
 
@@ -30522,6 +30523,508 @@ if (Utils.getEnv().browser) {
30522
30523
  XMPP = require('node-xmpp-client');
30523
30524
  }
30524
30525
 
30526
+ // ============================================================================
30527
+ // Notice Feature parsing helpers (CROS-1055, SDK 2.24.0)
30528
+ // ----------------------------------------------------------------------------
30529
+ // Module-private. Used by ChatProxy._onSystemMessageListener to detect/parse
30530
+ // urn:xmpp:notice:0 stanzas. Pure functions — no instance state, easy to test
30531
+ // indirectly via chatProxy._onSystemMessageListener(stanza) call paths.
30532
+ //
30533
+ // Server contract reference: QuickBlox Confluence "Notice Feature" page
30534
+ // (https://quickblox.atlassian.net/wiki/spaces/CHAT/pages/4126081033) and
30535
+ // Android tests in android-reference-2026-05-07/test/.
30536
+ // ============================================================================
30537
+
30538
+ var NOTICE_KNOWN_MODULE_IDS = {};
30539
+ NOTICE_KNOWN_MODULE_IDS[noticeConsts.NOTICE_MODULE_IDENTIFIER.UPDATED_MESSAGE] = true;
30540
+ NOTICE_KNOWN_MODULE_IDS[noticeConsts.NOTICE_MODULE_IDENTIFIER.DELETED_MESSAGE] = true;
30541
+ NOTICE_KNOWN_MODULE_IDS[noticeConsts.NOTICE_MODULE_IDENTIFIER.UPDATED_DIALOG] = true;
30542
+ NOTICE_KNOWN_MODULE_IDS[noticeConsts.NOTICE_MODULE_IDENTIFIER.DELETED_DIALOG] = true;
30543
+
30544
+ /**
30545
+ * Cross-env: get all direct child elements of `parent` whose tag name equals
30546
+ * `name`. Works for browser DOM, xmldom, and ltx (node-xmpp-client).
30547
+ *
30548
+ * @param {Element|Object} parent
30549
+ * @param {String} name
30550
+ * @return {Array<Element|Object>}
30551
+ */
30552
+ function _getChildElements(parent, name) {
30553
+ if (!parent) {
30554
+ return [];
30555
+ }
30556
+ var out = [];
30557
+
30558
+ // Browser DOM / xmldom: walk childNodes filtering by nodeType and tagName.
30559
+ if (parent.childNodes && parent.childNodes.length !== undefined && typeof parent.childNodes !== 'function') {
30560
+ for (var i = 0; i < parent.childNodes.length; i++) {
30561
+ var c = parent.childNodes[i];
30562
+ if (!c || c.nodeType !== 1 /* ELEMENT_NODE */) {
30563
+ continue;
30564
+ }
30565
+ var tag = c.tagName || c.nodeName || c.localName;
30566
+ if (tag === name) {
30567
+ out.push(c);
30568
+ }
30569
+ }
30570
+ if (out.length > 0 || parent.childNodes.length > 0) {
30571
+ // Used DOM path (even if no matches) — return what we have.
30572
+ return out;
30573
+ }
30574
+ }
30575
+
30576
+ // ltx: `parent.children` is a mixed array of strings (text) and Element objects with `.name`.
30577
+ if (parent.children && parent.children.length !== undefined) {
30578
+ for (var j = 0; j < parent.children.length; j++) {
30579
+ var ch = parent.children[j];
30580
+ if (ch && typeof ch === 'object' && ch.name === name) {
30581
+ out.push(ch);
30582
+ }
30583
+ }
30584
+ }
30585
+
30586
+ return out;
30587
+ }
30588
+
30589
+ /**
30590
+ * Cross-env text reader for an Element node. In browser / xmldom uses
30591
+ * `textContent`; in node-xmpp-client (ltx) uses `getText()` or descends
30592
+ * into `.children` (mixed string/element children). Empty string treated
30593
+ * as no text, returns ''. Returns '' if element has no text at all.
30594
+ *
30595
+ * This is needed because the existing `chatUtils.getElementText(parent, name)`
30596
+ * resolves the child by name and reads its text, but we sometimes already have
30597
+ * the element reference (from a previous lookup or namespace check) and need
30598
+ * to read text from THAT element, not search again from a parent.
30599
+ *
30600
+ * @param {Element|Object} el
30601
+ * @return {String}
30602
+ */
30603
+ function _readElementText(el) {
30604
+ if (!el) {
30605
+ return '';
30606
+ }
30607
+ // Browser DOM and xmldom both expose textContent.
30608
+ if (typeof el.textContent === 'string') {
30609
+ return el.textContent;
30610
+ }
30611
+ // ltx / node-xmpp-client Element: getText() returns the concatenated
30612
+ // text of all string children.
30613
+ if (typeof el.getText === 'function') {
30614
+ return el.getText();
30615
+ }
30616
+ // Last resort: walk .children if present.
30617
+ if (el.children && el.children.length !== undefined) {
30618
+ var out = '';
30619
+ for (var i = 0; i < el.children.length; i++) {
30620
+ var c = el.children[i];
30621
+ if (typeof c === 'string') {
30622
+ out += c;
30623
+ }
30624
+ }
30625
+ return out;
30626
+ }
30627
+ return '';
30628
+ }
30629
+
30630
+ /**
30631
+ * Returns the moduleIdentifier value if and only if its xmlns attribute equals
30632
+ * urn:xmpp:notice:0. Matching only by text without namespace check is unsafe
30633
+ * (existing SystemNotifications stanzas use the same element name).
30634
+ * @param {Element} extraParams
30635
+ * @return {String|null} known notice moduleIdentifier value or null.
30636
+ */
30637
+ function _getNoticeModuleIdentifier(extraParams) {
30638
+ if (!extraParams) {
30639
+ return null;
30640
+ }
30641
+
30642
+ var moduleIdEl = chatUtils.getElement(extraParams, 'moduleIdentifier');
30643
+ if (!moduleIdEl) {
30644
+ return null;
30645
+ }
30646
+
30647
+ // Namespace check: stanza is a Notice only when xmlns equals NOTICE_NAMESPACE.
30648
+ var xmlns = chatUtils.getAttr(moduleIdEl, 'xmlns');
30649
+ if (xmlns !== noticeConsts.NOTICE_NAMESPACE) {
30650
+ return null;
30651
+ }
30652
+
30653
+ var text = (_readElementText(moduleIdEl) || '').trim();
30654
+ if (!NOTICE_KNOWN_MODULE_IDS[text]) {
30655
+ return null;
30656
+ }
30657
+
30658
+ return text;
30659
+ }
30660
+
30661
+ /**
30662
+ * Convert a CSV string of numeric ids ("10,11,13007") to Array<Number>.
30663
+ * Empty or null input → empty array. Non-numeric items are dropped silently.
30664
+ */
30665
+ function _parseNumericIdsCsv(value) {
30666
+ if (value === null || value === undefined || value === '') {
30667
+ return [];
30668
+ }
30669
+ var parts = String(value).split(',');
30670
+ var out = [];
30671
+ for (var i = 0; i < parts.length; i++) {
30672
+ var n = parseInt(parts[i], 10);
30673
+ if (!isNaN(n)) {
30674
+ out.push(n);
30675
+ }
30676
+ }
30677
+ return out;
30678
+ }
30679
+
30680
+ /**
30681
+ * Parse <reactions><reaction>{name,count,<user_ids>...}...</reaction></reactions>
30682
+ * aggregate snapshot used in NoticeUpdatedMessage text-update stanzas.
30683
+ * Reference: Android QBReactionsPropertyParser.java.
30684
+ *
30685
+ * @param {Element} extraParams
30686
+ * @return {Array<{name, count, user_ids: number[]}>|null} or null if absent.
30687
+ */
30688
+ function _parseAggregateReactions(extraParams) {
30689
+ var reactionsEl = chatUtils.getElement(extraParams, 'reactions');
30690
+ if (!reactionsEl) {
30691
+ return null;
30692
+ }
30693
+
30694
+ var out = [];
30695
+ var reactionChildren = _getChildElements(reactionsEl, 'reaction');
30696
+ for (var i = 0; i < reactionChildren.length; i++) {
30697
+ var child = reactionChildren[i];
30698
+
30699
+ try {
30700
+ var name = chatUtils.getElementText(child, 'name');
30701
+ var countText = chatUtils.getElementText(child, 'count');
30702
+ var count = parseInt(countText, 10);
30703
+ if (isNaN(count)) {
30704
+ count = 0;
30705
+ }
30706
+
30707
+ var userIds = [];
30708
+ var userIdsEl = chatUtils.getElement(child, 'user_ids');
30709
+ if (userIdsEl) {
30710
+ var uidEls = _getChildElements(userIdsEl, 'user_id');
30711
+ for (var j = 0; j < uidEls.length; j++) {
30712
+ var uid = parseInt((_readElementText(uidEls[j]) || '').trim(), 10);
30713
+ if (!isNaN(uid)) {
30714
+ userIds.push(uid);
30715
+ }
30716
+ }
30717
+ }
30718
+
30719
+ out.push({ name: name, count: count, user_ids: userIds });
30720
+ } catch (err) {
30721
+ // Malformed reaction child — skip silently per AC#9 (no exceptions).
30722
+ }
30723
+ }
30724
+
30725
+ return out;
30726
+ }
30727
+
30728
+ /**
30729
+ * Parse singular <reaction>{name,user_id,action}</reaction> incremental
30730
+ * add/remove event used in NoticeUpdatedMessage stanzas. Reference: Android
30731
+ * QBReactionPropertyParser.java.
30732
+ *
30733
+ * @param {Element} extraParams
30734
+ * @return {Object|null} singular reaction descriptor or null if absent.
30735
+ */
30736
+ function _parseSingularReaction(extraParams) {
30737
+ var reactionEl = chatUtils.getElement(extraParams, 'reaction');
30738
+ if (!reactionEl) {
30739
+ return null;
30740
+ }
30741
+
30742
+ try {
30743
+ var name = chatUtils.getElementText(reactionEl, 'name');
30744
+ var userIdText = chatUtils.getElementText(reactionEl, 'user_id');
30745
+ var action = chatUtils.getElementText(reactionEl, 'action');
30746
+
30747
+ if (!action || (action !== 'add' && action !== 'remove')) {
30748
+ return null;
30749
+ }
30750
+
30751
+ var userId = parseInt(userIdText, 10);
30752
+ if (isNaN(userId)) {
30753
+ userId = userIdText;
30754
+ }
30755
+
30756
+ return {
30757
+ name: name,
30758
+ userId: userId,
30759
+ action: action
30760
+ };
30761
+ } catch (err) {
30762
+ return null;
30763
+ }
30764
+ }
30765
+
30766
+ /**
30767
+ * Parse custom_data element. Two server formats supported:
30768
+ * 1) Nested XML: <custom_data><class_name>...</class_name>...</custom_data>
30769
+ * 2) JSON string: <custom_data>{"class_name":"...",...}</custom_data>
30770
+ * Returns plain object or undefined if custom_data is absent.
30771
+ * Reference: Android QBDialogCustomDataNoticeParser.java.
30772
+ */
30773
+ function _parseCustomData(extraParams) {
30774
+ var customDataEl = chatUtils.getElement(extraParams, 'custom_data');
30775
+ if (!customDataEl) {
30776
+ return undefined;
30777
+ }
30778
+
30779
+ var rawText = (_readElementText(customDataEl) || '').trim();
30780
+ if (rawText && rawText.charAt(0) === '{') {
30781
+ // JSON form.
30782
+ try {
30783
+ return JSON.parse(rawText);
30784
+ } catch (err) {
30785
+ // Fall through to nested-XML parsing.
30786
+ }
30787
+ }
30788
+
30789
+ // Nested XML: walk element children, build flat map of name → text.
30790
+ // Cross-env: collect all element children regardless of name, then read
30791
+ // their tag and text. _getChildElements expects a name filter, but here
30792
+ // we want every child element, so do a one-off walk.
30793
+ var out = {};
30794
+ var hasChildEl = false;
30795
+
30796
+ // Browser DOM / xmldom path.
30797
+ if (customDataEl.childNodes && customDataEl.childNodes.length !== undefined && typeof customDataEl.childNodes !== 'function') {
30798
+ for (var i = 0; i < customDataEl.childNodes.length; i++) {
30799
+ var child = customDataEl.childNodes[i];
30800
+ if (!child || child.nodeType !== 1) {
30801
+ continue;
30802
+ }
30803
+ hasChildEl = true;
30804
+ var key = child.tagName || child.nodeName || child.localName;
30805
+ out[key] = (_readElementText(child) || '').trim();
30806
+ }
30807
+ }
30808
+
30809
+ // ltx fallback: walk .children for Element-like objects.
30810
+ if (!hasChildEl && customDataEl.children && customDataEl.children.length !== undefined) {
30811
+ for (var k = 0; k < customDataEl.children.length; k++) {
30812
+ var ch = customDataEl.children[k];
30813
+ if (ch && typeof ch === 'object' && ch.name) {
30814
+ hasChildEl = true;
30815
+ out[ch.name] = (_readElementText(ch) || '').trim();
30816
+ }
30817
+ }
30818
+ }
30819
+
30820
+ if (!hasChildEl) {
30821
+ return undefined;
30822
+ }
30823
+ return out;
30824
+ }
30825
+
30826
+ /**
30827
+ * Parse a Notice headline stanza into {type, payload} ready for routing.
30828
+ * Returns null if stanza is not a Notice (no urn:xmpp:notice:0 namespace, or
30829
+ * unknown moduleIdentifier value).
30830
+ *
30831
+ * @param {Element} stanza - <message type="headline"> element from Strophe / xmpp-client.
30832
+ * @return {{type: String, payload: Object}|null}
30833
+ */
30834
+ function _parseNoticeStanza(stanza) {
30835
+ if (!stanza) {
30836
+ return null;
30837
+ }
30838
+ var extraParams = chatUtils.getElement(stanza, 'extraParams');
30839
+ if (!extraParams) {
30840
+ return null;
30841
+ }
30842
+
30843
+ var type = _getNoticeModuleIdentifier(extraParams);
30844
+ if (!type) {
30845
+ return null;
30846
+ }
30847
+
30848
+ // Common flat fields used across most notice variants.
30849
+ var dialogId = chatUtils.getElementText(extraParams, 'dialog_id');
30850
+ var messageId = chatUtils.getElementText(extraParams, 'message_id');
30851
+ var dateSentText = chatUtils.getElementText(extraParams, 'date_sent');
30852
+ var dateSent = parseInt(dateSentText, 10);
30853
+ if (isNaN(dateSent)) {
30854
+ dateSent = undefined;
30855
+ }
30856
+
30857
+ var payload;
30858
+ var IDS = noticeConsts.NOTICE_MODULE_IDENTIFIER;
30859
+
30860
+ if (type === IDS.DELETED_MESSAGE) {
30861
+ payload = {
30862
+ dialogId: dialogId,
30863
+ messageId: messageId,
30864
+ dateSent: dateSent
30865
+ };
30866
+ } else if (type === IDS.DELETED_DIALOG) {
30867
+ payload = {
30868
+ dialogId: dialogId,
30869
+ dateSent: dateSent
30870
+ };
30871
+ } else if (type === IDS.UPDATED_MESSAGE) {
30872
+ // Two variants: singular <reaction> (incremental) vs aggregate <reactions> (text update with snapshot).
30873
+ var singular = _parseSingularReaction(extraParams);
30874
+ if (singular) {
30875
+ payload = {
30876
+ kind: 'reaction',
30877
+ event: {
30878
+ dialogId: dialogId,
30879
+ messageId: messageId,
30880
+ reactionName: singular.name,
30881
+ userId: singular.userId,
30882
+ action: singular.action,
30883
+ dateSent: dateSent
30884
+ }
30885
+ };
30886
+ } else {
30887
+ // Build a partial QBChatMessage object from extraParams.
30888
+ // We read the well-known fields explicitly (no dependency on parseExtraParams,
30889
+ // which has env-specific branches and can throw on non-standard stanza shapes).
30890
+ var msg = {};
30891
+ msg._id = messageId;
30892
+ msg.chat_dialog_id = dialogId;
30893
+ if (dateSent !== undefined) {
30894
+ msg.date_sent = dateSent;
30895
+ }
30896
+ var msgText = chatUtils.getElementText(extraParams, 'message');
30897
+ if (msgText) {
30898
+ msg.message = msgText;
30899
+ }
30900
+ var senderIdText = chatUtils.getElementText(extraParams, 'sender_id');
30901
+ var senderId = parseInt(senderIdText, 10);
30902
+ if (!isNaN(senderId)) { msg.sender_id = senderId; }
30903
+ var recipientIdText = chatUtils.getElementText(extraParams, 'recipient_id');
30904
+ var recipientId = parseInt(recipientIdText, 10);
30905
+ if (!isNaN(recipientId)) { msg.recipient_id = recipientId; }
30906
+ var readIds = _parseNumericIdsCsv(chatUtils.getElementText(extraParams, 'read_ids'));
30907
+ if (readIds.length > 0) { msg.read_ids = readIds; }
30908
+ var deliveredIds = _parseNumericIdsCsv(chatUtils.getElementText(extraParams, 'delivered_ids'));
30909
+ if (deliveredIds.length > 0) { msg.delivered_ids = deliveredIds; }
30910
+ // Aggregate reactions (if any).
30911
+ var aggregate = _parseAggregateReactions(extraParams);
30912
+ if (aggregate !== null && aggregate.length > 0) {
30913
+ msg.reactions = aggregate;
30914
+ }
30915
+ payload = {
30916
+ kind: 'message',
30917
+ dialogId: dialogId,
30918
+ message: msg
30919
+ };
30920
+ }
30921
+ } else if (type === IDS.UPDATED_DIALOG) {
30922
+ // Build a partial QBChatDialog object from extraParams (full server snapshot).
30923
+ var dlg = {};
30924
+ dlg._id = dialogId;
30925
+ var name = chatUtils.getElementText(extraParams, 'name');
30926
+ if (name !== undefined && name !== null && name !== '') {
30927
+ dlg.name = name;
30928
+ }
30929
+ var photo = chatUtils.getElementText(extraParams, 'photo');
30930
+ if (photo !== undefined && photo !== null && photo !== '') {
30931
+ dlg.photo = photo;
30932
+ }
30933
+ var typeText = chatUtils.getElementText(extraParams, 'type');
30934
+ var typeNum = parseInt(typeText, 10);
30935
+ if (!isNaN(typeNum)) {
30936
+ dlg.type = typeNum;
30937
+ }
30938
+ var occupants = _parseNumericIdsCsv(chatUtils.getElementText(extraParams, 'occupants_ids'));
30939
+ if (occupants.length > 0) {
30940
+ dlg.occupants_ids = occupants;
30941
+ }
30942
+ var admins = _parseNumericIdsCsv(chatUtils.getElementText(extraParams, 'admin_ids'));
30943
+ if (admins.length > 0) {
30944
+ dlg.admin_ids = admins;
30945
+ }
30946
+ var isJoinReqText = chatUtils.getElementText(extraParams, 'is_join_required');
30947
+ if (isJoinReqText !== undefined && isJoinReqText !== null && isJoinReqText !== '') {
30948
+ var isJoinReq = parseInt(isJoinReqText, 10);
30949
+ dlg.is_join_required = isNaN(isJoinReq) ? isJoinReqText : isJoinReq;
30950
+ }
30951
+ var roomJid = chatUtils.getElementText(extraParams, 'xmpp_room_jid');
30952
+ if (roomJid !== undefined && roomJid !== null && roomJid !== '') {
30953
+ dlg.xmpp_room_jid = roomJid;
30954
+ }
30955
+ var customData = _parseCustomData(extraParams);
30956
+ if (customData !== undefined) {
30957
+ dlg.custom_data = customData;
30958
+ }
30959
+ // Last message fields.
30960
+ var lmText = chatUtils.getElementText(extraParams, 'last_message');
30961
+ if (lmText) { dlg.last_message = lmText; }
30962
+ var lmId = chatUtils.getElementText(extraParams, 'last_message_id');
30963
+ if (lmId) { dlg.last_message_id = lmId; }
30964
+ var lmDsText = chatUtils.getElementText(extraParams, 'last_message_date_sent');
30965
+ var lmDs = parseInt(lmDsText, 10);
30966
+ if (!isNaN(lmDs)) { dlg.last_message_date_sent = lmDs; }
30967
+ var lmUidText = chatUtils.getElementText(extraParams, 'last_message_user_id');
30968
+ var lmUid = parseInt(lmUidText, 10);
30969
+ if (!isNaN(lmUid)) { dlg.last_message_user_id = lmUid; }
30970
+
30971
+ payload = {
30972
+ kind: 'dialog',
30973
+ dialog: dlg
30974
+ };
30975
+ } else {
30976
+ return null;
30977
+ }
30978
+
30979
+ return { type: type, payload: payload };
30980
+ }
30981
+
30982
+ /**
30983
+ * Route a parsed Notice event to the matching listener-property on chatProxy.
30984
+ * No-op if the listener is null/undefined. Listener exceptions are caught by
30985
+ * Utils.safeCallbackCall (existing SDK convention).
30986
+ *
30987
+ * @param {ChatProxy} chatProxy
30988
+ * @param {{type: String, payload: Object}} parsed
30989
+ */
30990
+ function _routeNoticeEvent(chatProxy, parsed) {
30991
+ if (!chatProxy || !parsed) {
30992
+ return;
30993
+ }
30994
+ var IDS = noticeConsts.NOTICE_MODULE_IDENTIFIER;
30995
+ var type = parsed.type;
30996
+ var p = parsed.payload || {};
30997
+
30998
+ if (type === IDS.DELETED_MESSAGE) {
30999
+ if (typeof chatProxy.onMessageDeletedListener === 'function') {
31000
+ Utils.safeCallbackCall(chatProxy.onMessageDeletedListener, p.dialogId, p.messageId, p.dateSent);
31001
+ }
31002
+ } else if (type === IDS.UPDATED_MESSAGE) {
31003
+ if (p.kind === 'reaction') {
31004
+ if (typeof chatProxy.onMessageReactionChangedListener === 'function') {
31005
+ Utils.safeCallbackCall(chatProxy.onMessageReactionChangedListener, p.event);
31006
+ }
31007
+ } else if (p.kind === 'message') {
31008
+ if (typeof chatProxy.onMessageUpdatedListener === 'function') {
31009
+ Utils.safeCallbackCall(chatProxy.onMessageUpdatedListener, p.dialogId, p.message);
31010
+ }
31011
+ }
31012
+ } else if (type === IDS.DELETED_DIALOG) {
31013
+ if (typeof chatProxy.onDialogDeletedListener === 'function') {
31014
+ Utils.safeCallbackCall(chatProxy.onDialogDeletedListener, p.dialogId, p.dateSent);
31015
+ }
31016
+ } else if (type === IDS.UPDATED_DIALOG) {
31017
+ if (typeof chatProxy.onDialogUpdatedListener === 'function') {
31018
+ Utils.safeCallbackCall(chatProxy.onDialogUpdatedListener, p.dialog);
31019
+ }
31020
+ }
31021
+ }
31022
+
31023
+ // ============================================================================
31024
+ // End of Notice Feature parsing helpers
31025
+ // ============================================================================
31026
+
31027
+
30525
31028
 
30526
31029
  function ChatProxy(service) {
30527
31030
  var self = this;
@@ -30598,6 +31101,23 @@ function ChatProxy(service) {
30598
31101
  this._sessionHasExpired = false;
30599
31102
  this._pings = {};
30600
31103
 
31104
+ /**
31105
+ * Notice Feature local state (CROS-1055, SDK 2.24.0).
31106
+ * Reflects the current XMPP session subscription only — reset to false on disconnect.
31107
+ * Consumers must call enableNotices() again after reconnect to re-subscribe.
31108
+ */
31109
+ this._isNoticesEnabled = false;
31110
+
31111
+ /**
31112
+ * Notice listener properties (CROS-1055). All initialized to null.
31113
+ * Single-listener pattern matching existing onMessageListener / onSystemMessageListener style.
31114
+ */
31115
+ this.onMessageDeletedListener = null;
31116
+ this.onMessageUpdatedListener = null;
31117
+ this.onMessageReactionChangedListener = null;
31118
+ this.onDialogDeletedListener = null;
31119
+ this.onDialogUpdatedListener = null;
31120
+
30601
31121
  // [QC-1550] XMPP connection is considered "verified" only after the first
30602
31122
  // successful pong response. Strophe emits Status.CONNECTED at the transport
30603
31123
  // layer (TCP/WebSocket handshake completed) before XMPP-level traffic is
@@ -31179,9 +31699,23 @@ function ChatProxy(service) {
31179
31699
  delay = chatUtils.getElement(stanza, 'delay'),
31180
31700
  moduleIdentifier = chatUtils.getElementText(extraParams, 'moduleIdentifier'),
31181
31701
  bodyContent = chatUtils.getElementText(stanza, 'body'),
31182
- extraParamsParsed = chatUtils.parseExtraParams(extraParams),
31702
+ extraParamsParsed,
31183
31703
  message;
31184
31704
 
31705
+ // CROS-1055: parseExtraParams may throw on edge-case stanza shapes
31706
+ // (e.g. test fixtures using xmldom which lacks .children). Catch
31707
+ // defensively so a parsing failure doesn't kill the whole headline
31708
+ // handler — Notice routing must still run.
31709
+ try {
31710
+ extraParamsParsed = chatUtils.parseExtraParams(extraParams);
31711
+ } catch (parseErr) {
31712
+ extraParamsParsed = {};
31713
+ Utils.QBLog('[QBChat]', '[parseExtraParams] failed: ' + (parseErr && parseErr.message ? parseErr.message : parseErr));
31714
+ }
31715
+ if (!extraParamsParsed) {
31716
+ extraParamsParsed = {};
31717
+ }
31718
+
31185
31719
  if (moduleIdentifier === 'SystemNotifications' && typeof self.onSystemMessageListener === 'function') {
31186
31720
  message = {
31187
31721
  id: messageId,
@@ -31193,6 +31727,20 @@ function ChatProxy(service) {
31193
31727
  Utils.safeCallbackCall(self.onSystemMessageListener, message);
31194
31728
  } else if (self.webrtcSignalingProcessor && !delay && moduleIdentifier === 'WebRTCVideoChat') {
31195
31729
  self.webrtcSignalingProcessor._onMessage(from, extraParams, delay, userId, extraParamsParsed.extension);
31730
+ } else {
31731
+ // Notice Feature (CROS-1055, SDK 2.24.0). Sibling branch — only triggers
31732
+ // when extraParams contains <moduleIdentifier xmlns="urn:xmpp:notice:0">.
31733
+ // Non-notice headline stanzas (e.g. legacy SystemNotifications without
31734
+ // matching xmlns) fall through to the parser which returns null.
31735
+ try {
31736
+ var noticeParsed = _parseNoticeStanza(stanza);
31737
+ if (noticeParsed) {
31738
+ _routeNoticeEvent(self, noticeParsed);
31739
+ }
31740
+ } catch (err) {
31741
+ // Per AC#9: never throw from headline handler.
31742
+ Utils.QBLog('[QBChat]', '[Notice] handler error: ' + (err && err.message ? err.message : err));
31743
+ }
31196
31744
  }
31197
31745
 
31198
31746
  /**
@@ -31758,6 +32306,7 @@ ChatProxy.prototype = {
31758
32306
  self._isConnectionVerified = true;
31759
32307
  self._isReconnectListenerPending = false;
31760
32308
 
32309
+ // TODO(2.25.0): auto-enable Notice Feature here (see qbChat.js enableNotices JSDoc).
31761
32310
  self.roster.get(function (contacts) {
31762
32311
  xmppClient.send(presence);
31763
32312
 
@@ -32391,6 +32940,10 @@ ChatProxy.prototype = {
32391
32940
  this._isLogout = true;
32392
32941
  this.helpers.setUserCurrentJid('');
32393
32942
 
32943
+ // Notice Feature (CROS-1055): reset local subscription flag on disconnect.
32944
+ // Consumers must call enableNotices() again after a new connection.
32945
+ this._isNoticesEnabled = false;
32946
+
32394
32947
  if (Utils.getEnv().browser) {
32395
32948
  this.connection.flush();
32396
32949
  this.connection.disconnect('call QB.chat.disconnect');//artik should add reason = 'disconnect from SDK'
@@ -32404,6 +32957,132 @@ ChatProxy.prototype = {
32404
32957
  }
32405
32958
  },
32406
32959
 
32960
+ /**
32961
+ * Subscribe the current XMPP session to Notice Feature stanzas
32962
+ * (urn:xmpp:notice:0). After a successful IQ result the server starts
32963
+ * delivering NoticeUpdatedMessage / NoticeDeletedMessage /
32964
+ * NoticeUpdatedDialog / NoticeDeletedDialog headline stanzas.
32965
+ *
32966
+ * The local enabled flag is reset to false on chat disconnect — consumers
32967
+ * must call enableNotices() again after a reconnect (intentional divergence
32968
+ * from the Android SDK, which auto-restores the subscription).
32969
+ *
32970
+ * TODO(2.25.0): make auto-enable SDK-internal — fire on initial connect and
32971
+ * on relogin after session-expired, skip on Stream-Management reconnect.
32972
+ * Spec & three-state matrix:
32973
+ * __ai-work-log/sdk-js/tasks/ad-hoc-2026-05-05-notice-reaction-admin-role/
32974
+ * backlog-2.25.0-auto-enable-notices.md
32975
+ * jira-stories/_jira-src-06-auto-enable-notices.md
32976
+ *
32977
+ * Do not call enableNotices again before the previous callback fires —
32978
+ * concurrent enable calls are not specified.
32979
+ *
32980
+ * @memberof QB.chat
32981
+ * @param {enableNoticesCallback} callback - Called with (error, result) on IQ result.
32982
+ * @since 2.24.0
32983
+ */
32984
+ enableNotices: function (callback) {
32985
+ /**
32986
+ * Callback for QB.chat.enableNotices().
32987
+ * @callback enableNoticesCallback
32988
+ * @param {Object|null} error - Error object on failure, null on success.
32989
+ * @param {Object} [result] - IQ result element on success.
32990
+ */
32991
+ this._sendNoticeIQ('enable', callback);
32992
+ },
32993
+
32994
+ /**
32995
+ * Unsubscribe the current XMPP session from Notice Feature stanzas.
32996
+ * Sends a <disable xmlns="urn:xmpp:notice:0"/> IQ. On success the local
32997
+ * enabled flag becomes false; the server stops delivering notice stanzas.
32998
+ *
32999
+ * @memberof QB.chat
33000
+ * @param {disableNoticesCallback} callback - Called with (error, result) on IQ result.
33001
+ * @since 2.24.0
33002
+ */
33003
+ disableNotices: function (callback) {
33004
+ /**
33005
+ * Callback for QB.chat.disableNotices().
33006
+ * @callback disableNoticesCallback
33007
+ * @param {Object|null} error - Error object on failure, null on success.
33008
+ * @param {Object} [result] - IQ result element on success.
33009
+ */
33010
+ this._sendNoticeIQ('disable', callback);
33011
+ },
33012
+
33013
+ /**
33014
+ * Returns the local Notice Feature subscription flag.
33015
+ * @memberof QB.chat
33016
+ * @return {Boolean} true after a successful enableNotices(), false otherwise.
33017
+ * Reset to false on chat disconnect; not synchronized with the server.
33018
+ * @since 2.24.0
33019
+ */
33020
+ isNoticesEnabled: function () {
33021
+ return this._isNoticesEnabled === true;
33022
+ },
33023
+
33024
+ /**
33025
+ * @private
33026
+ * Internal IQ helper used by enableNotices/disableNotices. Implements the
33027
+ * dual-env IQ pattern (browser/Strophe vs Node/NativeScript stanza-builder)
33028
+ * used elsewhere in the SDK (see RosterProxy.get for the canonical reference).
33029
+ *
33030
+ * @param {String} action - 'enable' or 'disable'.
33031
+ * @param {Function} callback
33032
+ */
33033
+ _sendNoticeIQ: function (action, callback) {
33034
+ var self = this;
33035
+ var iqParams = {
33036
+ type: 'set',
33037
+ from: self.helpers.getUserCurrentJid(),
33038
+ id: chatUtils.getUniqueId('notice_' + action)
33039
+ };
33040
+ var builder = Utils.getEnv().browser ? $iq : XMPP.Stanza;
33041
+ var iq = chatUtils.createStanza(builder, iqParams, 'iq');
33042
+
33043
+ // Append <enable xmlns="urn:xmpp:notice:0"/> or <disable xmlns="..."/>
33044
+ iq.c(action, { xmlns: noticeConsts.NOTICE_NAMESPACE }).up();
33045
+
33046
+ function _onSuccess(stanza) {
33047
+ self._isNoticesEnabled = (action === 'enable');
33048
+ if (typeof callback === 'function') {
33049
+ Utils.safeCallbackCall(callback, null, stanza);
33050
+ }
33051
+ }
33052
+
33053
+ function _onError(stanza) {
33054
+ // Per AC#3/AC#4: on error keep the flag unchanged (do NOT flip to true on enable error).
33055
+ if (typeof callback === 'function') {
33056
+ var err;
33057
+ try {
33058
+ err = stanza ? chatUtils.getErrorFromXMLNode(stanza) : null;
33059
+ } catch (e) {
33060
+ err = null;
33061
+ }
33062
+ if (!err) {
33063
+ err = { type: 'cancel', message: 'Notice IQ error' };
33064
+ }
33065
+ Utils.safeCallbackCall(callback, err);
33066
+ }
33067
+ }
33068
+
33069
+ if (Utils.getEnv().browser) {
33070
+ self.connection.sendIQ(iq, _onSuccess, _onError);
33071
+ } else {
33072
+ // Node/NativeScript path: register callback in nodeStanzasCallbacks map.
33073
+ self.nodeStanzasCallbacks[iqParams.id] = function (stanza) {
33074
+ // The map handler receives the result/error stanza. Inspect type to dispatch.
33075
+ var iqType = chatUtils.getAttr(stanza, 'type');
33076
+ if (iqType === 'result') {
33077
+ _onSuccess(stanza);
33078
+ } else {
33079
+ _onError(stanza);
33080
+ }
33081
+ };
33082
+ self.Client.send(iq);
33083
+ }
33084
+ },
33085
+
32407
33086
  addListener: function (params, callback) {
32408
33087
  Utils.QBLog('[Deprecated!]', 'Avoid using it, this feature will be removed in future version.');
32409
33088
 
@@ -33710,7 +34389,7 @@ Helpers.prototype = {
33710
34389
  * */
33711
34390
  module.exports = ChatProxy;
33712
34391
 
33713
- },{"../../plugins/streamManagement":150,"../../qbConfig":151,"../../qbStrophe":154,"../../qbUtils":155,"./qbChatHelpers":133,"nativescript-xmpp-client":undefined,"node-xmpp-client":54}],133:[function(require,module,exports){
34392
+ },{"../../plugins/streamManagement":151,"../../qbConfig":152,"../../qbStrophe":155,"../../qbUtils":156,"./qbChatHelpers":133,"./qbNoticeConsts":136,"nativescript-xmpp-client":undefined,"node-xmpp-client":54}],133:[function(require,module,exports){
33714
34393
  'use strict';
33715
34394
 
33716
34395
  var utils = require('../../qbUtils');
@@ -34039,7 +34718,7 @@ var qbChatHelpers = {
34039
34718
 
34040
34719
  module.exports = qbChatHelpers;
34041
34720
 
34042
- },{"../../qbConfig":151,"../../qbUtils":155}],134:[function(require,module,exports){
34721
+ },{"../../qbConfig":152,"../../qbUtils":156}],134:[function(require,module,exports){
34043
34722
  'use strict';
34044
34723
 
34045
34724
  var config = require('../../qbConfig'),
@@ -34081,9 +34760,11 @@ DialogProxy.prototype = {
34081
34760
  },
34082
34761
 
34083
34762
  /**
34084
- * Create new dialog({@link https://docs.quickblox.com/docs/js-chat-dialogs#create-dialog read more}).
34763
+ * Create new dialog({@link https://docs.quickblox.com/reference/create-dialog read more}).
34085
34764
  * @memberof QB.chat.dialog
34086
34765
  * @param {Object} params - Object of parameters.
34766
+ * @param {Number[]|String} [params.admin_ids] - IDs of dialog admins for public/group dialogs.
34767
+ * Ignored by the backend for private dialogs.
34087
34768
  * @param {createDialogCallback} callback - The callback function.
34088
34769
  * */
34089
34770
  create: function(params, callback) {
@@ -34098,6 +34779,10 @@ DialogProxy.prototype = {
34098
34779
  params.occupants_ids = params.occupants_ids.join(', ');
34099
34780
  }
34100
34781
 
34782
+ if (params && params.admin_ids && Utils.isArray(params.admin_ids)) {
34783
+ params.admin_ids = params.admin_ids.join(', ');
34784
+ }
34785
+
34101
34786
  if (params && params.is_join_required !== undefined && params.is_join_required !== null) {
34102
34787
  if (params.is_join_required !== 0 && params.is_join_required !== 1) {
34103
34788
  Utils.QBLog('[QBChat]', 'Warning: is_join_required must be 0 or 1, got: ' +
@@ -34114,10 +34799,13 @@ DialogProxy.prototype = {
34114
34799
  },
34115
34800
 
34116
34801
  /**
34117
- * Update group dialog({@link https://docs.quickblox.com/docs/js-chat-dialogs#update-dialog read more}).
34802
+ * Update group dialog({@link https://docs.quickblox.com/reference/update-dialog read more}).
34118
34803
  * @memberof QB.chat.dialog
34119
34804
  * @param {String} id - The dialog ID.
34120
34805
  * @param {Object} params - Object of parameters.
34806
+ * @param {Number[]|String} [params.admin_ids] - Full replacement list of dialog admins.
34807
+ * @param {Object} [params.push_all] - Incremental fields to append, including admin_ids.
34808
+ * @param {Object} [params.pull_all] - Incremental fields to remove, including admin_ids.
34121
34809
  * @param {updateDialogCallback} callback - The callback function.
34122
34810
  * */
34123
34811
  update: function(id, params, callback) {
@@ -34174,7 +34862,7 @@ DialogProxy.prototype = {
34174
34862
 
34175
34863
  module.exports = DialogProxy;
34176
34864
 
34177
- },{"../../qbConfig":151,"../../qbUtils":155}],135:[function(require,module,exports){
34865
+ },{"../../qbConfig":152,"../../qbUtils":156}],135:[function(require,module,exports){
34178
34866
  'use strict';
34179
34867
 
34180
34868
  var config = require('../../qbConfig'),
@@ -34194,6 +34882,9 @@ MessageProxy.prototype = {
34194
34882
  * Get a chat history({@link https://docs.quickblox.com/docs/js-chat-messaging#retrieve-chat-history read more}).
34195
34883
  * @memberof QB.chat.message
34196
34884
  * @param {Object} params - Object of parameters.
34885
+ * @param {Number} [params.include_reactions] - Include message reactions in the response.
34886
+ * Allowed values: 0 (default, no reactions data) or 1 (include reactions[] per message).
34887
+ * Reactions array shape: [{ name, count, user_ids: number[] }].
34197
34888
  * @param {listMessageCallback} callback - The callback function.
34198
34889
  * */
34199
34890
  list: function(params, callback) {
@@ -34291,6 +34982,129 @@ MessageProxy.prototype = {
34291
34982
  }
34292
34983
  },
34293
34984
 
34985
+ /**
34986
+ * Get a chat message by ID
34987
+ * ({@link https://docs.quickblox.com/reference/get-message-by-id read more}).
34988
+ * @memberof QB.chat.message
34989
+ * @param {String} id - The message id.
34990
+ * @param {Object} [params] - Optional query parameters.
34991
+ * @param {Number} [params.include_reactions] - Include message reactions in the response (0 or 1).
34992
+ * @param {getByIdMessageCallback} callback - The callback function.
34993
+ * @since 2.24.0
34994
+ * */
34995
+ getById: function(id, params_or_callback, callback) {
34996
+ /**
34997
+ * Callback for QB.chat.message.getById().
34998
+ * @param {Object} error - The error object.
34999
+ * @param {Object} message - The message object.
35000
+ * @callback getByIdMessageCallback
35001
+ * */
35002
+
35003
+ var ajaxParams = {
35004
+ url: Utils.getUrl(MESSAGES_API_URL, id)
35005
+ };
35006
+
35007
+ if (arguments.length === 2) {
35008
+ this.service.ajax(ajaxParams, params_or_callback);
35009
+ } else if (arguments.length === 3) {
35010
+ ajaxParams.data = params_or_callback;
35011
+
35012
+ this.service.ajax(ajaxParams, callback);
35013
+ }
35014
+ },
35015
+
35016
+ /**
35017
+ * Add reaction to a message.
35018
+ * @memberof QB.chat.message
35019
+ * @param {String} id - The message id.
35020
+ * @param {String} name - The reaction name.
35021
+ * @param {addReactionCallback} callback - The callback function.
35022
+ *
35023
+ * Server contract:
35024
+ * POST /chat/Message/{id}/reactions
35025
+ * Success: 201 Created with empty body.
35026
+ * Errors: 400 (invalid id/name or reaction limits), 404 (message not found), 422 (public dialog).
35027
+ *
35028
+ * Idempotency note: Re-adding the same reaction by the same user returns 201 without creating a duplicate reaction.
35029
+ * */
35030
+ addReaction: function(id, name, callback) {
35031
+ /**
35032
+ * Callback for QB.chat.message.addReaction().
35033
+ * @param {Object} error - The error object.
35034
+ * @param {Object|undefined} reaction - Empty response body on success.
35035
+ * @callback addReactionCallback
35036
+ * */
35037
+
35038
+ // Server returns 201 with empty body. Use dataType:'text' so qbProxy does
35039
+ // not try to JSON.parse the empty response (which throws in node-fetch).
35040
+ this.service.ajax({
35041
+ url: Utils.getUrl(MESSAGES_API_URL + '/' + id + '/reactions'),
35042
+ type: 'POST',
35043
+ contentType: 'application/json; charset=utf-8',
35044
+ isNeedStringify: true,
35045
+ dataType: 'text',
35046
+ data: {
35047
+ name: name
35048
+ }
35049
+ }, callback);
35050
+ },
35051
+
35052
+ /**
35053
+ * Remove reaction from a message.
35054
+ * @memberof QB.chat.message
35055
+ * @param {String} id - The message id.
35056
+ * @param {String} name - The reaction name.
35057
+ * @param {removeReactionCallback} callback - The callback function.
35058
+ *
35059
+ * Server contract:
35060
+ * DELETE /chat/Message/{id}/reactions
35061
+ * Success: 200 OK with empty body.
35062
+ * Errors: 400 (invalid id/name), 404 (message not found or reaction not found).
35063
+ * */
35064
+ removeReaction: function(id, name, callback) {
35065
+ /**
35066
+ * Callback for QB.chat.message.removeReaction().
35067
+ * @param {Object} error - The error object.
35068
+ * @param {String} response - Empty body.
35069
+ * @callback removeReactionCallback
35070
+ * */
35071
+
35072
+ this.service.ajax({
35073
+ url: Utils.getUrl(MESSAGES_API_URL + '/' + id + '/reactions'),
35074
+ type: 'DELETE',
35075
+ contentType: 'application/json; charset=utf-8',
35076
+ isNeedStringify: true,
35077
+ dataType: 'text',
35078
+ data: {
35079
+ name: name
35080
+ }
35081
+ }, callback);
35082
+ },
35083
+
35084
+ /**
35085
+ * Get reactions list for a message.
35086
+ * @memberof QB.chat.message
35087
+ * @param {String} id - The message id.
35088
+ * @param {listReactionsCallback} callback - The callback function.
35089
+ *
35090
+ * Server contract:
35091
+ * GET /chat/Message/{id}/reactions
35092
+ * Success: 200 OK, body { total_entries: Number, items: Array<{ name, count, user_ids[] }> }.
35093
+ * Errors: 400 (invalid id), 404 (message not found).
35094
+ * */
35095
+ listReactions: function(id, callback) {
35096
+ /**
35097
+ * Callback for QB.chat.message.listReactions().
35098
+ * @param {Object} error - The error object.
35099
+ * @param {Object} reactions - The reactions aggregate object.
35100
+ * @callback listReactionsCallback
35101
+ * */
35102
+
35103
+ this.service.ajax({
35104
+ url: Utils.getUrl(MESSAGES_API_URL + '/' + id + '/reactions')
35105
+ }, callback);
35106
+ },
35107
+
34294
35108
  /**
34295
35109
  * Get unread messages counter for one or group of dialogs({@link https://docs.quickblox.com/docs/js-chat-dialogs#get-number-of-unread-messages read more}).
34296
35110
  * @memberof QB.chat.message
@@ -34318,7 +35132,41 @@ MessageProxy.prototype = {
34318
35132
 
34319
35133
  module.exports = MessageProxy;
34320
35134
 
34321
- },{"../../qbConfig":151,"../../qbUtils":155}],136:[function(require,module,exports){
35135
+ },{"../../qbConfig":152,"../../qbUtils":156}],136:[function(require,module,exports){
35136
+ 'use strict';
35137
+
35138
+ /**
35139
+ * Shared constants for the Notice Feature (XMPP `urn:xmpp:notice:0`).
35140
+ *
35141
+ * Foundation module for SDK 2.24.0 — imported by Notice / Reaction / Admin Role
35142
+ * runtime modules in their respective feature branches. No runtime behavior here.
35143
+ *
35144
+ * Mirror of Android `com.quickblox.chat.notice.QBNoticeConsts`.
35145
+ */
35146
+
35147
+ /**
35148
+ * XMPP namespace for Notice IQ stanzas (`<enable>`, `<disable>`) and for the
35149
+ * `<moduleIdentifier>` element on inbound notice messages.
35150
+ */
35151
+ var NOTICE_NAMESPACE = 'urn:xmpp:notice:0';
35152
+
35153
+ /**
35154
+ * Discriminator strings carried in the `<moduleIdentifier>` element of
35155
+ * inbound notice stanzas. Used for routing in the headline-message handler.
35156
+ */
35157
+ var NOTICE_MODULE_IDENTIFIER = {
35158
+ UPDATED_MESSAGE: 'NoticeUpdatedMessage',
35159
+ DELETED_MESSAGE: 'NoticeDeletedMessage',
35160
+ UPDATED_DIALOG: 'NoticeUpdatedDialog',
35161
+ DELETED_DIALOG: 'NoticeDeletedDialog'
35162
+ };
35163
+
35164
+ module.exports = {
35165
+ NOTICE_NAMESPACE: NOTICE_NAMESPACE,
35166
+ NOTICE_MODULE_IDENTIFIER: NOTICE_MODULE_IDENTIFIER
35167
+ };
35168
+
35169
+ },{}],137:[function(require,module,exports){
34322
35170
  'use strict';
34323
35171
 
34324
35172
  var Utils = require('../qbUtils');
@@ -34672,7 +35520,7 @@ AIProxy.prototype = {
34672
35520
 
34673
35521
  module.exports = AIProxy;
34674
35522
 
34675
- },{"../qbUtils":155}],137:[function(require,module,exports){
35523
+ },{"../qbUtils":156}],138:[function(require,module,exports){
34676
35524
  'use strict';
34677
35525
 
34678
35526
  var Utils = require('../qbUtils');
@@ -34897,7 +35745,7 @@ function isFunction(func) {
34897
35745
  return !!(func && func.constructor && func.call && func.apply);
34898
35746
  }
34899
35747
 
34900
- },{"../qbConfig":151,"../qbUtils":155}],138:[function(require,module,exports){
35748
+ },{"../qbConfig":152,"../qbUtils":156}],139:[function(require,module,exports){
34901
35749
  'use strict';
34902
35750
 
34903
35751
  var config = require('../qbConfig'),
@@ -35048,7 +35896,7 @@ function signMessage(message, secret) {
35048
35896
  return cryptoSessionMsg;
35049
35897
  }
35050
35898
 
35051
- },{"../qbConfig":151,"../qbUtils":155,"crypto-js/hmac-sha1":24,"crypto-js/hmac-sha256":25}],139:[function(require,module,exports){
35899
+ },{"../qbConfig":152,"../qbUtils":156,"crypto-js/hmac-sha1":24,"crypto-js/hmac-sha256":25}],140:[function(require,module,exports){
35052
35900
  'use strict';
35053
35901
 
35054
35902
  /*
@@ -35451,7 +36299,7 @@ parseUri.options = {
35451
36299
  }
35452
36300
  };
35453
36301
 
35454
- },{"../qbConfig":151,"../qbUtils":155}],140:[function(require,module,exports){
36302
+ },{"../qbConfig":152,"../qbUtils":156}],141:[function(require,module,exports){
35455
36303
  'use strict';
35456
36304
 
35457
36305
  var config = require('../qbConfig');
@@ -35833,7 +36681,7 @@ DataProxy.prototype = {
35833
36681
 
35834
36682
  module.exports = DataProxy;
35835
36683
 
35836
- },{"../qbConfig":151,"../qbUtils":155}],141:[function(require,module,exports){
36684
+ },{"../qbConfig":152,"../qbUtils":156}],142:[function(require,module,exports){
35837
36685
  (function (Buffer){(function (){
35838
36686
  'use strict';
35839
36687
 
@@ -36072,7 +36920,7 @@ EventsProxy.prototype = {
36072
36920
  module.exports = PushNotificationsProxy;
36073
36921
 
36074
36922
  }).call(this)}).call(this,require("buffer").Buffer)
36075
- },{"../qbConfig":151,"../qbUtils":155,"buffer":21}],142:[function(require,module,exports){
36923
+ },{"../qbConfig":152,"../qbUtils":156,"buffer":21}],143:[function(require,module,exports){
36076
36924
  'use strict';
36077
36925
 
36078
36926
  /*
@@ -36385,7 +37233,7 @@ function generateOrder(obj) {
36385
37233
  return [obj.sort, type, obj.field].join(' ');
36386
37234
  }
36387
37235
 
36388
- },{"../qbConfig":151,"../qbUtils":155}],143:[function(require,module,exports){
37236
+ },{"../qbConfig":152,"../qbUtils":156}],144:[function(require,module,exports){
36389
37237
  'use strict';
36390
37238
 
36391
37239
  /**
@@ -36930,110 +37778,92 @@ function _getStats(peer, lastResults, successCallback, errorCallback) {
36930
37778
 
36931
37779
  peer.getStats(null).then(function (results) {
36932
37780
  results.forEach(function (result) {
36933
- _applyStatReport(statistic, result, lastResults);
37781
+ var item;
37782
+
37783
+ if (result.bytesReceived && result.type === 'inbound-rtp') {
37784
+ item = statistic.remote[result.mediaType];
37785
+ item.bitrate = _getBitratePerSecond(result, lastResults, false);
37786
+ item.bytesReceived = result.bytesReceived;
37787
+ item.packetsReceived = result.packetsReceived;
37788
+ item.timestamp = result.timestamp;
37789
+ if (result.mediaType === 'video' && result.framerateMean) {
37790
+ item.framesPerSecond = Math.round(result.framerateMean * 10) / 10;
37791
+ }
37792
+ } else if (result.bytesSent && result.type === 'outbound-rtp') {
37793
+ item = statistic.local[result.mediaType];
37794
+ item.bitrate = _getBitratePerSecond(result, lastResults, true);
37795
+ item.bytesSent = result.bytesSent;
37796
+ item.packetsSent = result.packetsSent;
37797
+ item.timestamp = result.timestamp;
37798
+ if (result.mediaType === 'video' && result.framerateMean) {
37799
+ item.framesPerSecond = Math.round(result.framerateMean * 10) / 10;
37800
+ }
37801
+ } else if (result.type === 'local-candidate') {
37802
+ item = statistic.local.candidate;
37803
+ if (result.candidateType === 'host' && result.mozLocalTransport === 'udp' && result.transport === 'udp') {
37804
+ item.protocol = result.transport;
37805
+ item.ip = result.ipAddress;
37806
+ item.port = result.portNumber;
37807
+ } else if (!Helpers.getVersionFirefox()) {
37808
+ item.protocol = result.protocol;
37809
+ item.ip = result.ip;
37810
+ item.port = result.port;
37811
+ }
37812
+ } else if (result.type === 'remote-candidate') {
37813
+ item = statistic.remote.candidate;
37814
+ item.protocol = result.protocol || result.transport;
37815
+ item.ip = result.ip || result.ipAddress;
37816
+ item.port = result.port || result.portNumber;
37817
+ } else if (result.type === 'track' && result.kind === 'video' && !Helpers.getVersionFirefox()) {
37818
+ if (result.remoteSource) {
37819
+ item = statistic.remote.video;
37820
+ item.frameHeight = result.frameHeight;
37821
+ item.frameWidth = result.frameWidth;
37822
+ item.framesPerSecond = _getFramesPerSecond(result, lastResults, false);
37823
+ } else {
37824
+ item = statistic.local.video;
37825
+ item.frameHeight = result.frameHeight;
37826
+ item.frameWidth = result.frameWidth;
37827
+ item.framesPerSecond = _getFramesPerSecond(result, lastResults, true);
37828
+ }
37829
+ }
36934
37830
  });
36935
37831
  successCallback(statistic, results);
36936
37832
  }, errorCallback);
36937
- }
36938
37833
 
36939
- function _applyStatReport(statistic, result, lastResults) {
36940
- var item;
36941
- // mediaType is deprecated in the W3C webrtc-stats spec and replaced by kind.
36942
- // Safari/WebKit omits mediaType on some reports and provides only kind, so we
36943
- // normalize once and use it for both the statistic lookup and the 'video' check.
36944
- var mediaType = result.mediaType || result.kind;
36945
-
36946
- if (result.bytesReceived && result.type === 'inbound-rtp') {
36947
- item = statistic.remote[mediaType];
36948
-
36949
- if (item) {
36950
- item.bitrate = _getBitratePerSecond(result, lastResults, false);
36951
- item.bytesReceived = result.bytesReceived;
36952
- item.packetsReceived = result.packetsReceived;
36953
- item.timestamp = result.timestamp;
36954
- if (mediaType === 'video' && result.framerateMean) {
36955
- item.framesPerSecond = Math.round(result.framerateMean * 10) / 10;
36956
- }
36957
- } else {
36958
- Helpers.traceWarning('_getStats: skipping inbound-rtp report with unknown mediaType/kind: ' + mediaType);
36959
- }
36960
- } else if (result.bytesSent && result.type === 'outbound-rtp') {
36961
- item = statistic.local[mediaType];
36962
-
36963
- if (item) {
36964
- item.bitrate = _getBitratePerSecond(result, lastResults, true);
36965
- item.bytesSent = result.bytesSent;
36966
- item.packetsSent = result.packetsSent;
36967
- item.timestamp = result.timestamp;
36968
- if (mediaType === 'video' && result.framerateMean) {
36969
- item.framesPerSecond = Math.round(result.framerateMean * 10) / 10;
36970
- }
37834
+ function _getBitratePerSecond(result, lastResults, isLocal) {
37835
+ var lastResult = lastResults && lastResults.get(result.id),
37836
+ seconds = lastResult ? ((result.timestamp - lastResult.timestamp) / 1000) : 5,
37837
+ kilo = 1024,
37838
+ bit = 8,
37839
+ bitrate;
37840
+
37841
+ if (!lastResult) {
37842
+ bitrate = 0;
37843
+ } else if (isLocal) {
37844
+ bitrate = bit * (result.bytesSent - lastResult.bytesSent) / (kilo * seconds);
36971
37845
  } else {
36972
- Helpers.traceWarning('_getStats: skipping outbound-rtp report with unknown mediaType/kind: ' + mediaType);
36973
- }
36974
- } else if (result.type === 'local-candidate') {
36975
- item = statistic.local.candidate;
36976
- if (result.candidateType === 'host' && result.mozLocalTransport === 'udp' && result.transport === 'udp') {
36977
- item.protocol = result.transport;
36978
- item.ip = result.ipAddress;
36979
- item.port = result.portNumber;
36980
- } else if (!Helpers.getVersionFirefox()) {
36981
- item.protocol = result.protocol;
36982
- item.ip = result.ip;
36983
- item.port = result.port;
36984
- }
36985
- } else if (result.type === 'remote-candidate') {
36986
- item = statistic.remote.candidate;
36987
- item.protocol = result.protocol || result.transport;
36988
- item.ip = result.ip || result.ipAddress;
36989
- item.port = result.port || result.portNumber;
36990
- } else if (result.type === 'track' && result.kind === 'video' && !Helpers.getVersionFirefox()) {
36991
- if (result.remoteSource) {
36992
- item = statistic.remote.video;
36993
- item.frameHeight = result.frameHeight;
36994
- item.frameWidth = result.frameWidth;
36995
- item.framesPerSecond = _getFramesPerSecond(result, lastResults, false);
36996
- } else {
36997
- item = statistic.local.video;
36998
- item.frameHeight = result.frameHeight;
36999
- item.frameWidth = result.frameWidth;
37000
- item.framesPerSecond = _getFramesPerSecond(result, lastResults, true);
37846
+ bitrate = bit * (result.bytesReceived - lastResult.bytesReceived) / (kilo * seconds);
37001
37847
  }
37002
- }
37003
- }
37004
37848
 
37005
- function _getBitratePerSecond(result, lastResults, isLocal) {
37006
- var lastResult = lastResults && lastResults.get(result.id),
37007
- seconds = lastResult ? ((result.timestamp - lastResult.timestamp) / 1000) : 5,
37008
- kilo = 1024,
37009
- bit = 8,
37010
- bitrate;
37011
-
37012
- if (!lastResult) {
37013
- bitrate = 0;
37014
- } else if (isLocal) {
37015
- bitrate = bit * (result.bytesSent - lastResult.bytesSent) / (kilo * seconds);
37016
- } else {
37017
- bitrate = bit * (result.bytesReceived - lastResult.bytesReceived) / (kilo * seconds);
37849
+ return Math.round(bitrate);
37018
37850
  }
37019
37851
 
37020
- return Math.round(bitrate);
37021
- }
37852
+ function _getFramesPerSecond(result, lastResults, isLocal) {
37853
+ var lastResult = lastResults && lastResults.get(result.id),
37854
+ seconds = lastResult ? ((result.timestamp - lastResult.timestamp) / 1000) : 5,
37855
+ framesPerSecond;
37022
37856
 
37023
- function _getFramesPerSecond(result, lastResults, isLocal) {
37024
- var lastResult = lastResults && lastResults.get(result.id),
37025
- seconds = lastResult ? ((result.timestamp - lastResult.timestamp) / 1000) : 5,
37026
- framesPerSecond;
37857
+ if (!lastResult) {
37858
+ framesPerSecond = 0;
37859
+ } else if (isLocal) {
37860
+ framesPerSecond = (result.framesSent - lastResult.framesSent) / seconds;
37861
+ } else {
37862
+ framesPerSecond = (result.framesReceived - lastResult.framesReceived) / seconds;
37863
+ }
37027
37864
 
37028
- if (!lastResult) {
37029
- framesPerSecond = 0;
37030
- } else if (isLocal) {
37031
- framesPerSecond = (result.framesSent - lastResult.framesSent) / seconds;
37032
- } else {
37033
- framesPerSecond = (result.framesReceived - lastResult.framesReceived) / seconds;
37865
+ return Math.round(framesPerSecond * 10) / 10;
37034
37866
  }
37035
-
37036
- return Math.round(framesPerSecond * 10) / 10;
37037
37867
  }
37038
37868
 
37039
37869
  // Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix|
@@ -37220,12 +38050,9 @@ function setMediaBitrate(sdp, media, bitrate) {
37220
38050
  return newLines.join('\n');
37221
38051
  }
37222
38052
 
37223
- // PRIVATE - exposed for unit tests only, not part of the public SDK contract.
37224
- qbRTCPeerConnection._applyStatReport = _applyStatReport;
37225
-
37226
38053
  module.exports = qbRTCPeerConnection;
37227
38054
 
37228
- },{"../../qbConfig":151,"./qbWebRTCHelpers":145}],144:[function(require,module,exports){
38055
+ },{"../../qbConfig":152,"./qbWebRTCHelpers":146}],145:[function(require,module,exports){
37229
38056
  'use strict';
37230
38057
 
37231
38058
  /**
@@ -37636,7 +38463,7 @@ function getOpponentsIdNASessions(sessions) {
37636
38463
  return opponents;
37637
38464
  }
37638
38465
 
37639
- },{"../../qbConfig":151,"../../qbUtils":155,"./qbRTCPeerConnection":143,"./qbWebRTCHelpers":145,"./qbWebRTCSession":146,"./qbWebRTCSignalingConstants":147,"./qbWebRTCSignalingProcessor":148,"./qbWebRTCSignalingProvider":149}],145:[function(require,module,exports){
38466
+ },{"../../qbConfig":152,"../../qbUtils":156,"./qbRTCPeerConnection":144,"./qbWebRTCHelpers":146,"./qbWebRTCSession":147,"./qbWebRTCSignalingConstants":148,"./qbWebRTCSignalingProcessor":149,"./qbWebRTCSignalingProvider":150}],146:[function(require,module,exports){
37640
38467
  'use strict';
37641
38468
 
37642
38469
  /**
@@ -37782,7 +38609,7 @@ var WebRTCHelpers = {
37782
38609
 
37783
38610
  module.exports = WebRTCHelpers;
37784
38611
 
37785
- },{"../../qbConfig":151}],146:[function(require,module,exports){
38612
+ },{"../../qbConfig":152}],147:[function(require,module,exports){
37786
38613
  'use strict';
37787
38614
 
37788
38615
  /**
@@ -39145,7 +39972,7 @@ function _prepareExtension(extension) {
39145
39972
 
39146
39973
  module.exports = WebRTCSession;
39147
39974
 
39148
- },{"../../qbConfig":151,"../../qbUtils":155,"./qbRTCPeerConnection":143,"./qbWebRTCHelpers":145,"./qbWebRTCSignalingConstants":147}],147:[function(require,module,exports){
39975
+ },{"../../qbConfig":152,"../../qbUtils":156,"./qbRTCPeerConnection":144,"./qbWebRTCHelpers":146,"./qbWebRTCSignalingConstants":148}],148:[function(require,module,exports){
39149
39976
  'use strict';
39150
39977
 
39151
39978
  /**
@@ -39168,7 +39995,7 @@ WebRTCSignalingConstants.SignalingType = {
39168
39995
 
39169
39996
  module.exports = WebRTCSignalingConstants;
39170
39997
 
39171
- },{}],148:[function(require,module,exports){
39998
+ },{}],149:[function(require,module,exports){
39172
39999
  'use strict';
39173
40000
 
39174
40001
  /**
@@ -39345,7 +40172,7 @@ function WebRTCSignalingProcessor(service, delegate) {
39345
40172
 
39346
40173
  module.exports = WebRTCSignalingProcessor;
39347
40174
 
39348
- },{"./qbWebRTCSignalingConstants":147,"strophe.js":104,"strophe.js/dist/strophe.umd.js":104}],149:[function(require,module,exports){
40175
+ },{"./qbWebRTCSignalingConstants":148,"strophe.js":104,"strophe.js/dist/strophe.umd.js":104}],150:[function(require,module,exports){
39349
40176
  'use strict';
39350
40177
 
39351
40178
  /** JSHint inline rules */
@@ -39471,7 +40298,7 @@ WebRTCSignalingProvider.prototype._JStoXML = function (title, obj, msg) {
39471
40298
 
39472
40299
  module.exports = WebRTCSignalingProvider;
39473
40300
 
39474
- },{"../../qbConfig":151,"../../qbUtils":155,"./qbWebRTCHelpers":145,"./qbWebRTCSignalingConstants":147,"strophe.js":104,"strophe.js/dist/strophe.umd.js":104}],150:[function(require,module,exports){
40301
+ },{"../../qbConfig":152,"../../qbUtils":156,"./qbWebRTCHelpers":146,"./qbWebRTCSignalingConstants":148,"strophe.js":104,"strophe.js/dist/strophe.umd.js":104}],151:[function(require,module,exports){
39475
40302
  'use strict';
39476
40303
 
39477
40304
  /**
@@ -39708,7 +40535,7 @@ StreamManagement.prototype._increaseReceivedStanzasCounter = function(){
39708
40535
 
39709
40536
  module.exports = StreamManagement;
39710
40537
 
39711
- },{"../modules/chat/qbChatHelpers":133,"../qbUtils":155}],151:[function(require,module,exports){
40538
+ },{"../modules/chat/qbChatHelpers":133,"../qbUtils":156}],152:[function(require,module,exports){
39712
40539
  'use strict';
39713
40540
 
39714
40541
  /*
@@ -39723,7 +40550,7 @@ module.exports = StreamManagement;
39723
40550
  */
39724
40551
 
39725
40552
  var config = {
39726
- version: '2.23.1-beta.5',
40553
+ version: '2.24.0-beta.1',
39727
40554
  buildNumber: '1179',
39728
40555
  creds: {
39729
40556
  'appId': 0,
@@ -39865,7 +40692,7 @@ config.updateSessionExpirationDate = function (tokenExpirationDate, headerHasTok
39865
40692
 
39866
40693
  module.exports = config;
39867
40694
 
39868
- },{}],152:[function(require,module,exports){
40695
+ },{}],153:[function(require,module,exports){
39869
40696
  'use strict';
39870
40697
 
39871
40698
  /*
@@ -40282,7 +41109,7 @@ QB.QuickBlox = QuickBlox;
40282
41109
 
40283
41110
  module.exports = QB;
40284
41111
 
40285
- },{"./modules/chat/qbChat":132,"./modules/chat/qbDialog":134,"./modules/chat/qbMessage":135,"./modules/qbAI":136,"./modules/qbAddressBook":137,"./modules/qbAuth":138,"./modules/qbContent":139,"./modules/qbData":140,"./modules/qbPushNotifications":141,"./modules/qbUsers":142,"./modules/webrtc/qbWebRTCClient":144,"./qbConfig":151,"./qbProxy":153,"./qbUtils":155,"webrtc-adapter":117}],153:[function(require,module,exports){
41112
+ },{"./modules/chat/qbChat":132,"./modules/chat/qbDialog":134,"./modules/chat/qbMessage":135,"./modules/qbAI":137,"./modules/qbAddressBook":138,"./modules/qbAuth":139,"./modules/qbContent":140,"./modules/qbData":141,"./modules/qbPushNotifications":142,"./modules/qbUsers":143,"./modules/webrtc/qbWebRTCClient":145,"./qbConfig":152,"./qbProxy":154,"./qbUtils":156,"webrtc-adapter":117}],154:[function(require,module,exports){
40286
41113
  'use strict';
40287
41114
 
40288
41115
  var config = require('./qbConfig');
@@ -40633,7 +41460,7 @@ ServiceProxy.prototype = {
40633
41460
 
40634
41461
  module.exports = ServiceProxy;
40635
41462
 
40636
- },{"./qbConfig":151,"./qbUtils":155,"form-data":32,"node-fetch":53}],154:[function(require,module,exports){
41463
+ },{"./qbConfig":152,"./qbUtils":156,"form-data":32,"node-fetch":53}],155:[function(require,module,exports){
40637
41464
  'use strict';
40638
41465
  /** JSHint inline rules */
40639
41466
  /* globals Strophe */
@@ -40741,7 +41568,7 @@ function Connection(onLogListenerCallback) {
40741
41568
 
40742
41569
  module.exports = Connection;
40743
41570
 
40744
- },{"./qbConfig":151,"./qbUtils":155,"strophe.js":104,"strophe.js/dist/strophe.umd.js":104}],155:[function(require,module,exports){
41571
+ },{"./qbConfig":152,"./qbUtils":156,"strophe.js":104,"strophe.js/dist/strophe.umd.js":104}],156:[function(require,module,exports){
40745
41572
  (function (global){(function (){
40746
41573
  /* eslint no-console: 2 */
40747
41574
 
@@ -41145,5 +41972,5 @@ var Utils = {
41145
41972
  module.exports = Utils;
41146
41973
 
41147
41974
  }).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
41148
- },{"./qbConfig":151,"fs":20,"os":81}]},{},[152])(152)
41975
+ },{"./qbConfig":152,"fs":20,"os":81}]},{},[153])(153)
41149
41976
  });