1;(function() 2{ 3 /** 4 * Bitrix Push & Pull 5 * Pull client 6 * 7 * @package bitrix 8 * @subpackage pull 9 * @copyright 2001-2019 Bitrix 10 */ 11 12 /****************** ATTENTION ******************************* 13 * Please do not use Bitrix CoreJS in this class. 14 * This class can be called on a page without Bitrix Framework 15 *************************************************************/ 16 17 if (!window.BX) 18 { 19 window.BX = {}; 20 } 21 else if (window.BX.PullClient) 22 { 23 return; 24 } 25 else if (!window.BX.RestClient) 26 { 27 return; 28 } 29 30 var BX = window.BX; 31 var protobuf = window.protobuf; 32 33 var REVISION = 19; // api revision - check module/pull/include.php 34 var LONG_POLLING_TIMEOUT = 60; 35 var RESTORE_WEBSOCKET_TIMEOUT = 30 * 60; 36 var CONFIG_TTL = 24 * 60 * 60; 37 var CONFIG_CHECK_INTERVAL = 60000; 38 var MAX_IDS_TO_STORE = 10; 39 40 var LS_SESSION = "bx-pull-session"; 41 var LS_SESSION_CACHE_TIME = 20; 42 43 var ConnectionType = { 44 WebSocket: 'webSocket', 45 LongPolling: 'longPolling' 46 }; 47 48 var PullStatus = { 49 Online: 'online', 50 Offline: 'offline', 51 Connecting: 'connect' 52 }; 53 54 var SenderType = { 55 Unknown: 0, 56 Client: 1, 57 Backend: 2 58 }; 59 60 var SubscriptionType = { 61 Server: 'server', 62 Client: 'client', 63 Online: 'online', 64 Status: 'status', 65 Revision: 'revision' 66 }; 67 68 var CloseReasons = { 69 NORMAL_CLOSURE : 1000, 70 SERVER_DIE : 1001, 71 CONFIG_REPLACED : 3000, 72 CHANNEL_EXPIRED : 3001, 73 SERVER_RESTARTED : 3002, 74 CONFIG_EXPIRED : 3003, 75 MANUAL : 3004, 76 }; 77 78 var SystemCommands = { 79 CHANNEL_EXPIRE: 'CHANNEL_EXPIRE', 80 CONFIG_EXPIRE: 'CONFIG_EXPIRE', 81 SERVER_RESTART:'SERVER_RESTART' 82 }; 83 84 var ServerMode = { 85 Shared: 'shared', 86 Personal: 'personal' 87 }; 88 89 // Protobuf message models 90 var Response = protobuf.roots['push-server']['Response']; 91 var ResponseBatch = protobuf.roots['push-server']['ResponseBatch']; 92 var Request = protobuf.roots['push-server']['Request']; 93 var RequestBatch = protobuf.roots['push-server']['RequestBatch']; 94 var IncomingMessagesRequest = protobuf.roots['push-server']['IncomingMessagesRequest']; 95 var IncomingMessage = protobuf.roots['push-server']['IncomingMessage']; 96 var Receiver = protobuf.roots['push-server']['Receiver']; 97 98 var Pull = function (params) 99 { 100 params = params || {}; 101 102 if (params.restApplication) 103 { 104 if (typeof params.configGetMethod === 'undefined') 105 { 106 params.configGetMethod = 'pull.application.config.get'; 107 } 108 if (typeof params.skipCheckRevision === 'undefined') 109 { 110 params.skipCheckRevision = true; 111 } 112 if (typeof params.restApplication === 'string') 113 { 114 params.siteId = params.restApplication; 115 } 116 117 params.serverEnabled = true; 118 } 119 120 var self = this; 121 122 this.context = 'master'; 123 124 this.guestMode = params.guestMode? params.guestMode: (typeof BX.message !== 'undefined' && BX.message.pull_guest_mode? BX.message.pull_guest_mode === 'Y': false); 125 this.guestUserId = params.guestUserId? params.guestUserId: (typeof BX.message !== 'undefined' && BX.message.pull_guest_user_id? parseInt(BX.message.pull_guest_user_id, 10): 0); 126 if(this.guestMode && this.guestUserId) 127 { 128 this.userId = this.guestUserId; 129 } 130 else 131 { 132 this.userId = params.userId? params.userId: (typeof BX.message !== 'undefined' && BX.message.USER_ID? BX.message.USER_ID: 0); 133 } 134 135 this.siteId = params.siteId? params.siteId: (typeof BX.message !== 'undefined' && BX.message.SITE_ID? BX.message.SITE_ID: 'none'); 136 this.restClient = typeof params.restClient !== "undefined"? params.restClient: new BX.RestClient(this.getRestClientOptions()); 137 138 this.enabled = typeof params.serverEnabled !== 'undefined'? (params.serverEnabled === 'Y' || params.serverEnabled === true): (typeof BX.message !== 'undefined' && BX.message.pull_server_enabled === 'Y'); 139 this.unloading = false; 140 this.starting = false; 141 this.debug = false; 142 this.connectionAttempt = 0; 143 this.connectionType = ''; 144 this.reconnectTimeout = null; 145 this.restoreWebSocketTimeout = null; 146 147 this.configGetMethod = typeof params.configGetMethod !== 'string'? 'pull.config.get': params.configGetMethod; 148 this.getPublicListMethod = typeof params.getPublicListMethod !== 'string'? 'pull.channel.public.list': params.getPublicListMethod; 149 150 this.skipStorageInit = params.skipStorageInit === true; 151 152 this.skipCheckRevision = params.skipCheckRevision === true; 153 154 this._subscribers = {}; 155 156 this.watchTagsQueue = {}; 157 this.watchUpdateInterval = 1740000; 158 this.watchForceUpdateInterval = 5000; 159 160 if (typeof params.configTimestamp !== 'undefined') 161 { 162 this.configTimestamp = params.configTimestamp; 163 } 164 else if (typeof BX.message !== 'undefined' && BX.message.pull_config_timestamp) 165 { 166 this.configTimestamp = BX.message.pull_config_timestamp; 167 } 168 else 169 { 170 this.configTimestamp = 0; 171 } 172 173 this.session = { 174 mid : null, 175 tag : null, 176 time : null, 177 history: {}, 178 lastMessageIds: [], 179 messageCount: 0 180 }; 181 182 this._connectors = { 183 webSocket: null, 184 longPolling: null 185 }; 186 187 Object.defineProperty(this, "connector", { 188 get: function() 189 { 190 return self._connectors[self.connectionType]; 191 } 192 }); 193 194 this.isSecure = document.location.href.indexOf('https') === 0; 195 this.config = null; 196 197 this.storage = null; 198 199 if(this.userId && !this.skipStorageInit) 200 { 201 this.storage = new StorageManager({ 202 userId: this.userId, 203 siteId: this.siteId 204 }); 205 } 206 207 this.sharedConfig = new SharedConfig({
208 onWebSocketBlockChanged: this.onWebSocketBlockChanged.bind(this), 209 storage: this.storage 210 }); 211 this.channelManager = new ChannelManager({ 212 restClient: this.restClient, 213 getPublicListMethod: this.getPublicListMethod 214 }); 215 216 this.notificationPopup = null; 217 218 // timers 219 this.checkInterval = null; 220 this.offlineTimeout = null; 221 222 // manual stop workaround 223 this.isManualDisconnect = false; 224 225 this.loggingEnabled = this.sharedConfig.isLoggingEnabled(); 226 }; 227 228 /** 229 * Creates a subscription to incoming messages. 230 * 231 * @param {Object} params 232 * @param {string} [params.type] Subscription type (for possible values see SubscriptionType). 233 * @param {string} [params.moduleId] Name of the module. 234 * @param {Function} params.callback Function, that will be called for incoming messages. 235 * @returns {Function} - Unsubscribe callback function 236 */ 237 Pull.prototype.subscribe = function(params) 238 { 239 /** 240 * After modify this method, copy to follow scripts: 241 * mobile/install/mobileapp/mobile/extensions/bitrix/pull/client/events/extension.js 242 * mobile/install/js/mobile/pull/client/src/client.js 243 */ 244 245 if (!params) 246 { 247 console.error(Utils.getDateForLog() + ': Pull.subscribe: params for subscribe function is invalid. '); 248 return function(){} 249 } 250 251 if (!Utils.isPlainObject(params)) 252 { 253 return this.attachCommandHandler(params); 254 } 255 256 params = params || {}; 257 params.type = params.type || SubscriptionType.Server; 258 params.command = params.command || null; 259 260 if (params.type == SubscriptionType.Server || params.type == SubscriptionType.Client) 261 { 262 if (typeof (this._subscribers[params.type]) === 'undefined') 263 { 264 this._subscribers[params.type] = {}; 265 } 266 if (typeof (this._subscribers[params.type][params.moduleId]) === 'undefined') 267 { 268 this._subscribers[params.type][params.moduleId] = { 269 'callbacks': [], 270 'commands': {}, 271 }; 272 } 273 274 if (params.command) 275 { 276 if (typeof (this._subscribers[params.type][params.moduleId]['commands'][params.command]) === 'undefined') 277 { 278 this._subscribers[params.type][params.moduleId]['commands'][params.command] = []; 279 } 280 281 this._subscribers[params.type][params.moduleId]['commands'][params.command].push(params.callback); 282 283 return function () { 284 this._subscribers[params.type][params.moduleId]['commands'][params.command] = this._subscribers[params.type][params.moduleId]['commands'][params.command].filter(function(element) { 285 return element !== params.callback; 286 }); 287 }.bind(this); 288 } 289 else 290 { 291 this._subscribers[params.type][params.moduleId]['callbacks'].push(params.callback); 292 293 return function () { 294 this._subscribers[params.type][params.moduleId]['callbacks'] = this._subscribers[params.type][params.moduleId]['callbacks'].filter(function(element) { 295 return element !== params.callback; 296 }); 297 }.bind(this); 298 } 299 } 300 else 301 { 302 if (typeof (this._subscribers[params.type]) === 'undefined') 303 { 304 this._subscribers[params.type] = []; 305 } 306 307 this._subscribers[params.type].push(params.callback); 308 309 return function () { 310 this._subscribers[params.type] = this._subscribers[params.type].filter(function(element) { 311 return element !== params.callback; 312 }); 313 }.bind(this); 314 } 315 }; 316 317 Pull.prototype.attachCommandHandler = function(handler) 318 { 319 /** 320 * After modify this method, copy to follow scripts: 321 * mobile/install/mobileapp/mobile/extensions/bitrix/pull/client/events/extension.js 322 */ 323 if (typeof handler.getModuleId !== 'function' || typeof handler.getModuleId() !== 'string') 324 {
325 console.error(Utils.getDateForLog() + ': Pull.attachCommandHandler: result of handler.getModuleId() is not a string.'); 326 return function(){} 327 } 328 329 var type = SubscriptionType.Server; 330 if (typeof handler.getSubscriptionType === 'function') 331 { 332 type = handler.getSubscriptionType(); 333 } 334 335 return this.subscribe({ 336 type: type, 337 moduleId: handler.getModuleId(), 338 callback: function(data) 339 { 340 var method = null; 341 342 if (typeof handler.getMap === 'function') 343 { 344 var mapping = handler.getMap(); 345 if (mapping && typeof mapping === 'object') 346 { 347 if (typeof mapping[data.command] === 'function') 348 { 349 method = mapping[data.command].bind(handler) 350 } 351 else if (typeof mapping[data.command] === 'string' && typeof handler[mapping[data.command]] === 'function') 352 { 353 method = handler[mapping[data.command]].bind(handler); 354 } 355 } 356 } 357 358 if (!method) 359 { 360 var methodName = 'handle'+data.command.charAt(0).toUpperCase() + data.command.slice(1); 361 if (typeof handler[methodName] === 'function') 362 { 363 method = handler[methodName].bind(handler); 364 } 365 } 366 367 if (method) 368 { 369 if (this.debug && this.context !== 'master') 370 { 371 console.warn(Utils.getDateForLog() + ': Pull.attachCommandHandler: receive command', data); 372 } 373 method(data.params, data.extra, data.command); 374 } 375 }.bind(this) 376 }); 377 }; 378 379 /** 380 * 381 * @param params {Object} 382 * @returns {boolean} 383 */ 384 Pull.prototype.emit = function(params) 385 { 386 /** 387 * After modify this method, copy to follow scripts: 388 * mobile/install/mobileapp/mobile/extensions/bitrix/pull/client/events/extension.js 389 * mobile/install/js/mobile/pull/client/src/client.js 390 */ 391 params = params || {}; 392 393 if (params.type == SubscriptionType.Server || params.type == SubscriptionType.Client) 394 { 395 if (typeof (this._subscribers[params.type]) === 'undefined') 396 { 397 this._subscribers[params.type] = {}; 398 } 399 if (typeof (this._subscribers[params.type][params.moduleId]) === 'undefined') 400 { 401 this._subscribers[params.type][params.moduleId] = { 402 'callbacks': [], 403 'commands': {}, 404 }; 405 } 406 407 if (this._subscribers[params.type][params.moduleId]['callbacks'].length > 0) 408 { 409 this._subscribers[params.type][params.moduleId]['callbacks'].forEach(function(callback){ 410 callback(params.data, {type: params.type, moduleId: params.moduleId}); 411 }); 412 } 413 414 if ( 415 this._subscribers[params.type][params.moduleId]['commands'][params.data.command] 416 && this._subscribers[params.type][params.moduleId]['commands'][params.data.command].length > 0) 417 { 418 this._subscribers[params.type][params.moduleId]['commands'][params.data.command].forEach(function(callback){ 419 callback(params.data.params, params.data.extra, params.data.command, {type: params.type, moduleId: params.moduleId}); 420 }); 421 } 422 423 return true; 424 } 425 else 426 { 427 if (typeof (this._subscribers[params.type]) === 'undefined') 428 { 429 this._subscribers[params.type] = []; 430 } 431 432 if (this._subscribers[params.type].length <= 0) 433 { 434 return true; 435 } 436 437 this._subscribers[params.type].forEach(function(callback){ 438 callback(params.data, {type: params.type}); 439 }); 440 441 return true; 442 } 443 }; 444 445 Pull.prototype.init = function() 446 { 447 this._connectors.webSocket = new WebSocketConnector({ 448 parent: this, 449 onOpen: this.onWebSocketOpen.bind(this), 450 onMessage: this.parseResponse.bind(this), 451 onDisconnect: this.onWebSocketDisconnect.bind(this), 452 onError: this.onWebSocketError.bind(this) 453 }); 454
455 this._connectors.longPolling = new LongPollingConnector({ 456 parent: this, 457 onOpen: this.onLongPollingOpen.bind(this), 458 onMessage: this.parseResponse.bind(this), 459 onDisconnect: this.onLongPollingDisconnect.bind(this), 460 onError: this.onLongPollingError.bind(this) 461 }); 462 463 this.connectionType = this.isWebSocketAllowed() ? ConnectionType.WebSocket : ConnectionType.LongPolling; 464 465 window.addEventListener("beforeunload", this.onBeforeUnload.bind(this)); 466 window.addEventListener("offline", this.onOffline.bind(this)); 467 window.addEventListener("online", this.onOnline.bind(this)); 468 469 if(BX && BX.addCustomEvent) 470 { 471 BX.addCustomEvent("BXLinkOpened", this.connect.bind(this)); 472 } 473 474 if (BX && BX.desktop) 475 { 476 BX.addCustomEvent("onDesktopReload", function() { 477 this.session.mid = null; 478 this.session.tag = null; 479 this.session.time = null; 480 }.bind(this)); 481 482 BX.desktop.addCustomEvent("BXLoginSuccess", function() { 483 this.restart(1000, "Desktop login"); 484 }.bind(this)); 485 } 486 }; 487 488 Pull.prototype.start = function(config) 489 { 490 var allowConfigCaching = true; 491 492 if(this.starting || this.isConnected()) 493 { 494 return; 495 } 496 497 if(!this.userId && typeof(BX.message) !== 'undefined' && BX.message.USER_ID) 498 { 499 this.userId = BX.message.USER_ID; 500 if(!this.storage) 501 { 502 this.storage = new StorageManager({ 503 userId: this.userId, 504 siteId: this.siteId 505 }); 506 } 507 } 508 if(this.siteId === 'none' && typeof(BX.message) !== 'undefined' && BX.message.SITE_ID) 509 { 510 this.siteId = BX.message.SITE_ID; 511 } 512 513 var result = new BX.Promise(); 514 515 var skipReconnectToLastSession = false; 516 if (Utils.isPlainObject(config)) 517 { 518 if (typeof config.skipReconnectToLastSession !== 'undefined') 519 { 520 skipReconnectToLastSession = !!config.skipReconnectToLastSession; 521 delete config.skipReconnectToLastSession; 522 } 523 this.config = config; 524 allowConfigCaching = false; 525 } 526 527 if (!this.enabled) 528 { 529 result.reject({ 530 ex: { error: 'PULL_DISABLED', error_description: 'Push & Pull server is disabled'} 531 }); 532 return result; 533 } 534 535 var self = this; 536 var now = (new Date()).getTime(); 537 var oldSession; 538 if(!skipReconnectToLastSession && this.storage) 539 { 540 oldSession = this.storage.get(LS_SESSION); 541 } 542 if(Utils.isPlainObject(oldSession) && oldSession.hasOwnProperty('ttl') && oldSession.ttl >= now) 543 { 544 this.session.mid = oldSession.mid; 545 } 546 547 this.starting = true; 548 this.loadConfig().catch(function(error) 549 { 550 self.starting = false; 551 self.sendPullStatus(PullStatus.Offline); 552 self.stopCheckConfig(); 553 console.error(Utils.getDateForLog() + ': Pull: could not read push-server config. ', error); 554 result.reject(error); 555 }).then(function(config) 556 { 557 self.setConfig(config, allowConfigCaching); 558 self.init(); 559 self.connect(); 560 self.updateWatch(); 561 self.startCheckConfig(); 562 result.resolve(true); 563 }); 564 565 return result; 566 }; 567 568 Pull.prototype.getRestClientOptions = function() 569 { 570 var result = {}; 571 572 if(this.guestMode && this.guestUserId !== 0) 573 { 574 result.queryParams = { 575 pull_guest_id: this.guestUserId 576 } 577 } 578 return result; 579 }; 580 581 Pull.prototype.setLastMessageId = function(lastMessageId) 582 { 583 this.session.mid = lastMessageId; 584 }; 585 586 /** 587 * 588 * @param {object[]} publicIds 589 * @param {integer} publicIds.user_id 590 * @param {string} publicIds.public_id 591 * @param {string} publicIds.signature 592 * @param {Date} publicIds.start 593 * @param {Date} publicIds.end 594 */ 595 Pull.prototype.setPublicIds = function(publicIds) 596 { 597 return this.channelManager.setPublicIds(publicIds); 598 }; 599 600 /** 601 * Send single message to the specified users. 602 * 603 * @param {integer[]} users User ids of the message receivers. 604 * @param {string} moduleId Name of the module to receive message, 605 * @param {string} command Command name. 606 * @param {object} params Command parameters. 607 * @param {integer} [expiry] Message expiry time in seconds. 608 * @return void 609 */ 610 Pull.prototype.sendMessage = function(users, moduleId, command, params, expiry) 611 { 612 return this.sendMessageBatch([{ 613 users: users, 614 moduleId: moduleId, 615 command: command, 616 params: params, 617 expiry: expiry 618 }]); 619 }; 620 621 /** 622 * Send single message to the specified public channels. 623 * 624 * @param {string[]} publicChannels Public ids of the channels to receive message. 625 * @param {string}
625 moduleId Name of the module to receive message, 626 * @param {string} command Command name. 627 * @param {object} params Command parameters. 628 * @param {integer} [expiry] Message expiry time in seconds. 629 * @return void 630 */ 631 Pull.prototype.sendMessageToChannels = function(publicChannels, moduleId, command, params, expiry) 632 { 633 return this.sendMessageBatch([{ 634 publicChannels: publicChannels, 635 moduleId: moduleId, 636 command: command, 637 params: params, 638 expiry: expiry 639 }]); 640 } 641 642 /** 643 * Sends batch of messages to the multiple public channels. 644 * 645 * @param {object[]} messageBatch Array of messages to send. 646 * @param {int[]} messageBatch.users User ids the message receivers. 647 * @param {string[]|object[]} messageBatch.publicChannels Public ids of the channels to send messages. 648 * @param {string} messageBatch.moduleId Name of the module to receive message, 649 * @param {string} messageBatch.command Command name. 650 * @param {object} messageBatch.params Command parameters. 651 * @param {integer} [messageBatch.expiry] Message expiry time in seconds. 652 * @return void 653 */ 654 Pull.prototype.sendMessageBatch = function(messageBatch) 655 { 656 if(!this.isPublishingEnabled()) 657 { 658 console.error('Client publishing is not supported or is disabled'); 659 return false; 660 } 661 662 var userIds = {}; 663 for(var i = 0; i < messageBatch.length; i++) 664 { 665 if (messageBatch[i].users) 666 { 667 for(var j = 0; j < messageBatch[i].users.length; j++) 668 { 669 userIds[messageBatch[i].users[j]] = true; 670 } 671 } 672 } 673 674 this.channelManager.getPublicIds(Object.keys(userIds)).then(function(publicIds) 675 { 676 return this.connector.send(this.encodeMessageBatch(messageBatch, publicIds)); 677 }.bind(this)) 678 }; 679 680 Pull.prototype.encodeMessageBatch = function(messageBatch, publicIds) 681 { 682 var messages = []; 683 messageBatch.forEach(function(messageFields) 684 { 685 var messageBody = { 686 module_id: messageFields.moduleId, 687 command: messageFields.command, 688 params: messageFields.params 689 }; 690 691 var receivers; 692 if (messageFields.users) 693 { 694 receivers = this.createMessageReceivers(messageFields.users, publicIds); 695 } 696 else 697 { 698 receivers = []; 699 } 700 701 if (messageFields.publicChannels) 702 { 703 if (!BX.type.isArray(messageFields.publicChannels)) 704 { 705 throw new Error('messageFields.publicChannels must be an array'); 706 } 707 messageFields.publicChannels.forEach(function(publicChannel) 708 { 709 var publicId; 710 var signature; 711 if (typeof(publicChannel) === 'string' && publicChannel.includes('.')) 712 { 713 var fields = publicChannel.toString().split('.'); 714 publicId = fields[0]; 715 signature = fields[1]; 716 } 717 else if (typeof(publicChannel) === 'object' && ('publicId' in publicChannel) && ('signature' in publicChannel)) 718 { 719 publicId = publicChannel.publicId; 720 signature = publicChannel.signature; 721 } 722 else 723 { 724 throw new Error('Public channel MUST be either a string, formatted like "{publicId}.{signature}" or an object with fields \'publicId\' and \'signature\''); 725 } 726 727 receivers.push(Receiver.create({ 728 id: this.encodeId(publicId), 729 signature: this.encodeId(signature) 730 })) 731 }.bind(this)) 732 } 733 734 var message = IncomingMessage.create({ 735 receivers: receivers, 736 body: JSON.stringify(messageBody), 737 expiry: messageFields.expiry || 0 738 }); 739 messages.push(message); 740 }, this); 741 742 var requestBatch = RequestBatch.create({ 743 requests: [{ 744 incomingMessages: { 745 messages: messages 746 } 747 }] 748 }); 749 750 return RequestBatch.encode(requestBatch).finish(); 751 }; 752 753 Pull.prototype.createMessageReceivers = function(users, publicIds) 754 { 755 var result = []; 756 for(var i = 0; i < users.length; i++) 757 { 758 var userId = users[i]; 759 if(!publicIds[userId] || !publicIds[userId].publicId) 760 { 761 throw new Error('Could not determine public id for user ' + userId); 762 } 763 764 result.push(Receiver.create({ 765 id: this.encodeId(publicIds[userId].publicId), 766 signature: this.encodeId(publicIds[userId].signature) 767 })) 768 } 769 return result; 770 }; 771 772 Pull.prototype.restart = function(disconnectCode, disconnectReason) 773 { 774 var self = this; 775 this.disconnect(disconnectCode, disconnectReason); 776 if(this.storage) 777 { 778 this.storage.remove('bx-pull-config'); 779 } 780 this.config = null; 781
782 this.loadConfig().catch(function(error) 783 { 784 console.error(Utils.getDateForLog() + ': Pull: could not read push-server config', error); 785 self.sendPullStatus(PullStatus.Offline); 786 787 clearTimeout(self.reconnectTimeout); 788 if(error.status == 401 || error.status == 403) 789 { 790 self.stopCheckConfig(); 791 792 if(BX && BX.onCustomEvent) 793 { 794 BX.onCustomEvent(window, 'onPullError', ['AUTHORIZE_ERROR']); 795 } 796 } 797 }).then(function(config) 798 { 799 self.setConfig(config, true); 800 self.connect(); 801 self.updateWatch(); 802 self.startCheckConfig(); 803 }); 804 }; 805 806 Pull.prototype.loadConfig = function () 807 { 808 var result = new BX.Promise(); 809 if (!this.config) 810 { 811 this.config = { 812 api: {}, 813 channels: {}, 814 publicChannels: {}, 815 server: { timeShift: 0 }, 816 clientId: null 817 }; 818 819 var config; 820 if(this.storage) 821 { 822 config = this.storage.get('bx-pull-config'); 823 } 824 if(this.isConfigActual(config) && this.checkRevision(config.api.revision_web)) 825 { 826 result.resolve(config); 827 return result; 828 } 829 else if (this.storage) 830 { 831 this.storage.remove('bx-pull-config') 832 } 833 } 834 else if(this.isConfigActual(this.config) && this.checkRevision(this.config.api.revision_web)) 835 { 836 result.resolve(this.config); 837 return result; 838 } 839 else 840 { 841 this.config = { 842 api: {}, 843 channels: {}, 844 publicChannels: {}, 845 server: { timeShift: 0 }, 846 clientId: null 847 }; 848 } 849 850 this.restClient.callMethod(this.configGetMethod, {'CACHE': 'N'}).then(function(response) { 851 var timeShift = 0; 852 var data = response.data(); 853 timeShift = Math.floor((Utils.getTimestamp() - new Date(data.serverTime).getTime())/1000); 854 delete data.serverTime; 855 856 var config = Object.assign({}, data); 857 config.server.timeShift = timeShift; 858 859 result.resolve(config) 860 }).catch(function(response) 861 { 862 var error = response.error(); 863 if(error.getError().error == "AUTHORIZE_ERROR" || error.getError().error == "WRONG_AUTH_TYPE") 864 { 865 error.status = 403; 866 } 867 result.reject(error); 868 }); 869 870 return result; 871 }; 872 873 Pull.prototype.isConfigActual = function(config) 874 { 875 if(!Utils.isPlainObject(config)) 876 { 877 return false; 878 } 879 880 if(config.server.config_timestamp < this.configTimestamp) 881 { 882 return false; 883 } 884 885 var now = new Date(); 886 887 var channelCount = Object.keys(config.channels).length; 888 if(channelCount === 0) 889 { 890 return false; 891 } 892 893 for(var channelType in config.channels) 894 { 895 if (!config.channels.hasOwnProperty(channelType)) 896 { 897 continue; 898 } 899 900 var channel = config.channels[channelType]; 901 var channelEnd = new Date(channel.end); 902 903 if(channelEnd < now) 904 { 905 return false; 906 } 907 } 908 909 return true; 910 }; 911 912 Pull.prototype.startCheckConfig = function() 913 { 914 if(this.checkInterval) 915 { 916 clearInterval(this.checkInterval); 917 } 918 919 this.checkInterval = setInterval(this.checkConfig.bind(this), CONFIG_CHECK_INTERVAL) 920 }; 921 922 Pull.prototype.stopCheckConfig = function() 923 { 924 if(this.checkInterval) 925 { 926 clearInterval(this.checkInterval); 927 } 928 this.checkInterval = null; 929 }; 930 931 Pull.prototype.checkConfig = function() 932 { 933 if(this.isConfigActual(this.config)) 934 { 935 if(!this.checkRevision(this.config.api.revision_web)) 936 { 937 return false; 938 } 939 } 940 else 941 { 942 this.logToConsole("Stale config detected. Restarting"); 943 this.restart(CloseReasons.CONFIG_EXPIRED, "Config update required"); 944 } 945 }; 946 947 Pull.prototype.setConfig = function(config, allowCaching) 948 { 949 for (var key in config) 950 { 951 if(config.hasOwnProperty(key) && this.config.hasOwnProperty(key)) 952 { 953 this.config[key] = config[key]; 954 } 955 } 956 957 if (config.publicChannels) 958 { 959 this.setPublicIds(Utils.objectValues(config.publicChannels)); 960 } 961 962 if(this.storage && allowCaching) 963 { 964 try 965 { 966 this.storage.set('bx-pull-config', config); 967 } 968 catch (e) 969 { 970 // try to delete the key "history" (landing site change history, see http://jabber.bx/view.php?id=136492) 971 if (localStorage && localStorage.removeItem) 972 { 973 localStorage.removeItem('history'); 974 } 975 console.error(Utils.getDateForLog() + " Pull: Could not cache config in local storage. Error: ", e); 976 } 977 } 978 }; 979 980 Pull.prototype.isWebSocketSupported = function() 981 { 982 return typeof(window.WebSocket) !== "undefined"; 983 }; 984 985 Pull.prototype.isWebSocketAllowed = function() 986 { 987 if(this.sharedConfig.isWebSocketBlocked()) 988 { 989 return false;
990 } 991 992 return this.isWebSocketEnabled(); 993 }; 994 995 Pull.prototype.isWebSocketEnabled = function() 996 { 997 if(!this.isWebSocketSupported()) 998 { 999 return false; 1000 } 1001 1002 return (this.config && this.config.server && this.config.server.websocket_enabled === true); 1003 }; 1004 1005 Pull.prototype.isPublishingSupported = function () 1006 { 1007 return this.getServerVersion() > 3; 1008 }; 1009 1010 Pull.prototype.isPublishingEnabled = function () 1011 { 1012 if(!this.isPublishingSupported()) 1013 { 1014 return false; 1015 } 1016 1017 return (this.config && this.config.server && this.config.server.publish_enabled === true); 1018 }; 1019 1020 Pull.prototype.isProtobufSupported = function() 1021 { 1022 return (this.getServerVersion() > 3 && !Utils.browser.IsIe()); 1023 }; 1024 1025 Pull.prototype.isSharedMode = function() 1026 { 1027 return (this.getServerMode() == ServerMode.Shared) 1028 }; 1029 1030 Pull.prototype.disconnect = function(disconnectCode, disconnectReason) 1031 { 1032 if(this.connector) 1033 { 1034 this.isManualDisconnect = true; 1035 this.connector.disconnect(disconnectCode, disconnectReason); 1036 } 1037 }; 1038 1039 Pull.prototype.stop = function(disconnectCode, disconnectReason) 1040 { 1041 this.disconnect(disconnectCode, disconnectReason); 1042 this.stopCheckConfig(); 1043 }; 1044 1045 Pull.prototype.reconnect = function(disconnectCode, disconnectReason, delay) 1046 { 1047 this.disconnect(disconnectCode, disconnectReason); 1048 1049 delay = delay || 1; 1050 this.scheduleReconnect(delay); 1051 }; 1052 1053 Pull.prototype.restoreWebSocketConnection = function() 1054 { 1055 if(this.connectionType == ConnectionType.WebSocket) 1056 { 1057 return true; 1058 } 1059 1060 this._connectors.webSocket.connect(); 1061 }; 1062 1063 Pull.prototype.scheduleReconnect = function(connectionDelay) 1064 { 1065 if(!this.enabled) 1066 return false; 1067 1068 if(!connectionDelay) 1069 { 1070 if(this.connectionAttempt > 3 && this.connectionType === ConnectionType.WebSocket && !this.sharedConfig.isLongPollingBlocked()) 1071 { 1072 // Websocket seems to be closed by network filter. Trying to fallback to long polling 1073 this.sharedConfig.setWebSocketBlocked(true); 1074 this.connectionType = ConnectionType.LongPolling; 1075 this.connectionAttempt = 1; 1076 connectionDelay = 1; 1077 } 1078 else 1079 { 1080 connectionDelay = this.getConnectionAttemptDelay(this.connectionAttempt); 1081 } 1082 } 1083 if(this.reconnectTimeout) 1084 { 1085 clearTimeout(this.reconnectTimeout); 1086 } 1087 1088 this.logToConsole('Pull: scheduling reconnection in ' + connectionDelay + ' seconds; attempt # ' + this.connectionAttempt); 1089 1090 this.reconnectTimeout = setTimeout(this.connect.bind(this), connectionDelay * 1000); 1091 }; 1092 1093 Pull.prototype.scheduleRestoreWebSocketConnection = function() 1094 { 1095 this.logToConsole('Pull: scheduling restoration of websocket connection in ' + RESTORE_WEBSOCKET_TIMEOUT + ' seconds'); 1096 1097 var self = this; 1098 if(this.restoreWebSocketTimeout) 1099 { 1100 return; 1101 } 1102 1103 this.restoreWebSocketTimeout = setTimeout(function() 1104 { 1105 self.restoreWebSocketTimeout = 0; 1106 self.restoreWebSocketConnection(); 1107 }, RESTORE_WEBSOCKET_TIMEOUT * 1000); 1108 }; 1109 1110 Pull.prototype.connect = function() 1111 { 1112 if(!this.enabled || this.connector.connected) 1113 { 1114 return false; 1115 } 1116 1117 if(this.reconnectTimeout) 1118 { 1119 clearTimeout(this.reconnectTimeout); 1120 } 1121 1122 this.sendPullStatus(PullStatus.Connecting); 1123 this.connectionAttempt++; 1124 this.connector.connect(); 1125 }; 1126 1127 Pull.prototype.parseResponse = function (response) 1128 { 1129 var events = this.extractMessages(response); 1130 var messages = []; 1131 if (events.length === 0) 1132 { 1133 this.session.mid = null; 1134 return; 1135 } 1136 1137 for (var i = 0; i < events.length; i++) 1138 { 1139 var event = events[i]; 1140 if (event.mid && this.session.lastMessageIds.includes(event.mid)) 1141 { 1142 console.warn("Duplicate message " + event.mid + " skipped"); 1143 continue; 1144 } 1145 1146 this.session.mid = event.mid || null; 1147 this.session.tag = event.tag || null; 1148 this.session.time = event.time || null; 1149 if (event.mid) 1150 { 1151 this.session.lastMessageIds.push(event.mid); 1152 } 1153 messages.push(event.text); 1154 1155 if (!this.session.history[event.text.module_id]) 1156 { 1157 this.session.history[event.text.module_id] = {}; 1158 } 1159 if (!this.session.history[event.text.module_id][event.text.command]) 1160 { 1161 this.session.history[event.text.module_id][event.text.command] = 0; 1162 } 1163 this.session.history[event.text.module_id][event.text.command]++; 1164 this.session.messageCount++; 1165 } 1166 1167 if (this.session.lastMessageIds.length > MAX_IDS_TO_STORE) 1168 { 1169 this.session.lastMessageIds = this.session.lastMessageIds.slice( - MAX_IDS_TO_STORE); 1170 } 1171 this.broadcastMessages(messages); 1172 }; 1173 1174 Pull.prototype.extractMessages = function (pullEvent) 1175 { 1176 if(pullEvent instanceof ArrayBuffer) 1177 { 1178 return this.extractProtobufMessages(pullEvent); 1179 } 1180 else if(Utils.isNotEmptyString(pullEvent)) 1181 { 1182 return this.extractPlainTextMessages(pullEvent) 1183 } 1184 }; 1185 1186 Pull.prototype.extractProtobufMessages = function(pullEvent) 1187 { 1188 var result = []; 1189 try 1190 { 1191 var responseBatch = ResponseBatch.decode(new Uint8Array(pullEvent)); 1192 for (var i = 0; i < responseBatch.responses.length; i++) 1193 { 1194 var response = responseBatch.responses[i]; 1195 if (response.command != "outgoingMessages") 1196 { 1197 continue; 1198 } 1199 1200 var messages = responseBatch.responses[i].outgoingMessages.messages; 1201 for (var m = 0; m < messages.length; m++) 1202 { 1203 var message = messages[m]; 1204 var messageFields; 1205 try 1206 { 1207 messageFields = JSON.parse(message.body) 1208 } 1209 catch (e) 1210 { 1211 console.error(Utils.getDateForLog() + ": Pull: Could not parse message body", e); 1212 continue; 1213 } 1214 1215 if(!messageFields.extra) 1216 { 1217 messageFields.extra = {} 1218 } 1219 messageFields.extra.sender = { 1220 type: message.sender.type 1221 }; 1222 1223 if(message.sender.id instanceof Uint8Array) 1224 { 1225 messageFields.extra.sender.id = this.decodeId(message.sender.id) 1226 } 1227 1228 var compatibleMessage = { 1229 mid: this.decodeId(message.id), 1230 text: messageFields 1231 }; 1232 1233 result.push(compatibleMessage); 1234 } 1235 } 1236 } 1237 catch(e) 1238 {
1239 console.error(Utils.getDateForLog() + ": Pull: Could not parse message", e) 1240 } 1241 return result; 1242 }; 1243 1244 Pull.prototype.extractPlainTextMessages = function(pullEvent) 1245 { 1246 var result = []; 1247 var dataArray = pullEvent.match(/#!NGINXNMS!#(.*?)#!NGINXNME!#/gm); 1248 if (dataArray === null) 1249 { 1250 text = "\n========= PULL ERROR ===========\n"+ 1251 "Error type: parseResponse error parsing message\n"+ 1252 "\n"+ 1253 "Data string: " + pullEvent + "\n"+ 1254 "================================\n\n"; 1255 console.warn(text); 1256 return result; 1257 } 1258 for (var i = 0; i < dataArray.length; i++) 1259 { 1260 dataArray[i] = dataArray[i].substring(12, dataArray[i].length - 12); 1261 if (dataArray[i].length <= 0) 1262 { 1263 continue; 1264 } 1265 1266 try 1267 { 1268 var data = JSON.parse(dataArray[i]) 1269 } 1270 catch(e) 1271 { 1272 continue; 1273 } 1274 1275 result.push(data); 1276 } 1277 return result; 1278 }; 1279 1280 /** 1281 * Converts message id from byte[] to string 1282 * @param {Uint8Array} encodedId 1283 * @return {string} 1284 */ 1285 Pull.prototype.decodeId = function(encodedId) 1286 { 1287 if(!(encodedId instanceof Uint8Array)) 1288 { 1289 throw new Error("encodedId should be an instance of Uint8Array"); 1290 } 1291 1292 var result = ""; 1293 for (var i = 0; i < encodedId.length; i++) 1294 { 1295 var hexByte = encodedId[i].toString(16); 1296 if (hexByte.length === 1) 1297 { 1298 result += '0'; 1299 } 1300 result += hexByte; 1301 } 1302 return result; 1303 }; 1304 1305 /** 1306 * Converts message id from hex-encoded string to byte[] 1307 * @param {string} id Hex-encoded string. 1308 * @return {Uint8Array} 1309 */ 1310 Pull.prototype.encodeId = function(id) 1311 { 1312 if (!id) 1313 { 1314 return new Uint8Array(); 1315 } 1316 1317 var result = []; 1318 for (var i = 0; i < id.length; i += 2) 1319 { 1320 result.push(parseInt(id.substr(i, 2), 16)); 1321 } 1322 1323 return new Uint8Array(result); 1324 }; 1325 1326 Pull.prototype.broadcastMessages = function (messages) 1327 { 1328 messages.forEach(function (message) 1329 { 1330 var moduleId = message.module_id = message.module_id.toLowerCase(); 1331 var command = message.command; 1332 1333 if(!message.extra) 1334 { 1335 message.extra = {}; 1336 } 1337 1338 if(message.extra.server_time_unix) 1339 { 1340 message.extra.server_time_ago = ((Utils.getTimestamp() - (message.extra.server_time_unix * 1000)) / 1000)-(this.config.server.timeShift? this.config.server.timeShift: 0); 1341 message.extra.server_time_ago = message.extra.server_time_ago > 0 ? message.extra.server_time_ago : 0; 1342 } 1343 1344 this.logMessage(message); 1345 try 1346 { 1347 if(message.extra.sender && message.extra.sender.type === SenderType.Client) 1348 { 1349 if (typeof BX.onCustomEvent !== 'undefined') 1350 { 1351 BX.onCustomEvent(window, 'onPullClientEvent-' + moduleId, [command, message.params, message.extra], true); 1352 BX.onCustomEvent(window, 'onPullClientEvent', [moduleId, command, message.params, message.extra], true); 1353 } 1354 1355 this.emit({ 1356 type: SubscriptionType.Client, 1357 moduleId: moduleId, 1358 data: { 1359 command: command, 1360 params: Utils.clone(message.params), 1361 extra: Utils.clone(message.extra) 1362 } 1363 }); 1364 } 1365 else if (moduleId === 'pull') 1366 { 1367 this.handleInternalPullEvent(command, message); 1368 } 1369 else if (moduleId == 'online') 1370 { 1371 if (message.extra.server_time_ago < 240) 1372 { 1373 if (typeof BX.onCustomEvent !== 'undefined') 1374 { 1375 BX.onCustomEvent(window, 'onPullOnlineEvent', [command, message.params, message.extra], true); 1376 } 1377 1378 this.emit({ 1379 type: SubscriptionType.Online, 1380 data: { 1381 command: command, 1382 params: Utils.clone(message.params), 1383 extra: Utils.clone(message.extra) 1384 } 1385 }); 1386 } 1387 } 1388 else 1389 { 1390 if (typeof BX.onCustomEvent !== 'undefined') 1391 { 1392 BX.onCustomEvent(window, 'onPullEvent-' + moduleId, [command, message.params, message.extra], true); 1393 BX.onCustomEvent(window, 'onPullEvent', [moduleId, command, message.params, message.extra], true); 1394 } 1395 1396 this.emit({ 1397 type: SubscriptionType.Server, 1398 moduleId: moduleId, 1399 data: { 1400 command: command, 1401 params: Utils.clone(message.params), 1402 extra: Utils.clone(message.extra) 1403 } 1404 }); 1405 } 1406 } 1407 catch(e) 1408 { 1409 if (typeof(console) == 'object') 1410 { 1411 console.warn( 1412 "\n========= PULL ERROR ===========\n"+ 1413 "Error type: broadcastMessages execute error\n"+ 1414 "Error event: ", e, "\n"+ 1415 "Message: ", message, "\n"+ 1416 "================================\n" 1417 ); 1418 if (typeof BX.debug !== 'undefined') 1419 { 1420 BX.debug(e); 1421 } 1422 } 1423 } 1424 1425 if(message.extra && message.extra.revision_web) 1426 { 1427 this.checkRevision(message.extra.revision_web); 1428 } 1429 }, this); 1430 }; 1431 1432 Pull.prototype.logToConsole = function(message) 1433 { 1434 if(this.loggingEnabled) 1435 {
1436 console.log(Utils.getDateForLog() + ': ' + message); 1437 } 1438 }; 1439 1440 Pull.prototype.logMessage = function(message) 1441 { 1442 if(!this.debug) 1443 { 1444 return; 1445 } 1446 1447 if(message.extra.sender && message.extra.sender.type === SenderType.Client) 1448 { 1449 console.info('onPullClientEvent-' + message.module_id, message.command, message.params, message.extra); 1450 } 1451 else if (message.moduleId == 'online') 1452 { 1453 console.info('onPullOnlineEvent', message.command, message.params, message.extra); 1454 } 1455 else 1456 { 1457 console.info('onPullEvent', message.module_id, message.command, message.params, message.extra); 1458 } 1459 }; 1460 1461 Pull.prototype.onLongPollingOpen = function() 1462 { 1463 this.unloading = false; 1464 this.starting = false; 1465 this.connectionAttempt = 0; 1466 this.isManualDisconnect = false; 1467 this.sendPullStatus(PullStatus.Online); 1468 1469 if(this.offlineTimeout) 1470 { 1471 clearTimeout(this.offlineTimeout); 1472 this.offlineTimeout = null; 1473 } 1474 1475 this.logToConsole('Pull: Long polling connection with push-server opened'); 1476 if(this.isWebSocketEnabled()) 1477 { 1478 this.scheduleRestoreWebSocketConnection(); 1479 } 1480 }; 1481 1482 Pull.prototype.onWebSocketBlockChanged = function(e) 1483 { 1484 var isWebSocketBlocked = e.isWebSocketBlocked; 1485 1486 if(isWebSocketBlocked && this.connectionType === ConnectionType.WebSocket && !this.isConnected()) 1487 { 1488 clearTimeout(this.reconnectTimeout); 1489 1490 this.connectionAttempt = 0; 1491 this.connectionType = ConnectionType.LongPolling; 1492 this.scheduleReconnect(1); 1493 } 1494 else if(!isWebSocketBlocked && this.connectionType === ConnectionType.LongPolling) 1495 { 1496 clearTimeout(this.reconnectTimeout); 1497 clearTimeout(this.restoreWebSocketTimeout); 1498 1499 this.connectionAttempt = 0; 1500 this.connectionType = ConnectionType.WebSocket; 1501 this.scheduleReconnect(1); 1502 } 1503 }; 1504 1505 Pull.prototype.onWebSocketOpen = function() 1506 { 1507 this.unloading = false; 1508 this.starting = false; 1509 this.connectionAttempt = 0; 1510 this.isManualDisconnect = false; 1511 this.sendPullStatus(PullStatus.Online); 1512 this.sharedConfig.setWebSocketBlocked(false); 1513 1514 // to prevent fallback to long polling in case of networking problems 1515 this.sharedConfig.setLongPollingBlocked(true); 1516 1517 if(this.connectionType == ConnectionType.LongPolling) 1518 { 1519 this.connectionType = ConnectionType.WebSocket; 1520 this._connectors.longPolling.disconnect(); 1521 } 1522 1523 if(this.offlineTimeout) 1524 { 1525 clearTimeout(this.offlineTimeout); 1526 this.offlineTimeout = null; 1527 } 1528 if (this.restoreWebSocketTimeout) 1529 { 1530 clearTimeout(this.restoreWebSocketTimeout); 1531 this.restoreWebSocketTimeout = null; 1532 } 1533 this.logToConsole('Pull: Websocket connection with push-server opened'); 1534 }; 1535 1536 Pull.prototype.onWebSocketDisconnect = function(e) 1537 { 1538 if(this.connectionType === ConnectionType.WebSocket) 1539 { 1540 if(e.code != CloseReasons.CONFIG_EXPIRED && e.code != CloseReasons.CHANNEL_EXPIRED && e.code != CloseReasons.CONFIG_REPLACED) 1541 { 1542 this.sendPullStatus(PullStatus.Offline); 1543 } 1544 else 1545 { 1546 this.offlineTimeout = setTimeout(function() 1547 { 1548 this.sendPullStatus(PullStatus.Offline); 1549 }.bind(this), 5000) 1550 } 1551 } 1552 1553 if(!e) 1554 { 1555 e = {}; 1556 } 1557 1558 this.logToConsole('Pull: Websocket connection with push-server closed. Code: ' + e.code + ', reason: ' + e.reason); 1559 if(!this.isManualDisconnect) 1560 { 1561 this.scheduleReconnect(); 1562 } 1563 1564 // to prevent fallback to long polling in case of networking problems 1565 this.sharedConfig.setLongPollingBlocked(true); 1566 this.isManualDisconnect = false; 1567 }; 1568 1569 Pull.prototype.onWebSocketError = function(e) 1570 { 1571 this.starting = false; 1572 if(this.connectionType === ConnectionType.WebSocket) 1573 { 1574 this.sendPullStatus(PullStatus.Offline); 1575 } 1576 1577 console.error(Utils.getDateForLog() + ": Pull: WebSocket connection error", e); 1578 this.scheduleReconnect(); 1579 }; 1580 1581 Pull.prototype.onLongPollingDisconnect = function(e) 1582 { 1583 if(this.connectionType === ConnectionType.LongPolling) 1584 { 1585 if(e.code != CloseReasons.CONFIG_EXPIRED && e.code != CloseReasons.CHANNEL_EXPIRED && e.code != CloseReasons.CONFIG_REPLACED) 1586 { 1587 this.sendPullStatus(PullStatus.Offline); 1588 } 1589 else 1590 { 1591 this.offlineTimeout = setTimeout(function() 1592 { 1593 this.sendPullStatus(PullStatus.Offline); 1594 }.bind(this), 5500) 1595 } 1596 } 1597 1598 if(!e) 1599 { 1600 e = {}; 1601 } 1602 1603 this.logToConsole('Pull: Long polling connection with push-server closed. Code: ' + e.code + ', reason: ' + e.reason); 1604 if(!this.isManualDisconnect) 1605 { 1606 this.scheduleReconnect(); 1607 } 1608 this.isManualDisconnect = false;
1609 }; 1610 1611 Pull.prototype.onLongPollingError = function(e) 1612 { 1613 this.starting = false; 1614 if(this.connectionType === ConnectionType.LongPolling) 1615 { 1616 this.sendPullStatus(PullStatus.Offline); 1617 } 1618 console.error(Utils.getDateForLog() + ': Pull: Long polling connection error', e); 1619 this.scheduleReconnect(); 1620 }; 1621 1622 Pull.prototype.isConnected = function() 1623 { 1624 return this.connector ? this.connector.connected : false; 1625 }; 1626 1627 Pull.prototype.onBeforeUnload = function() 1628 { 1629 this.unloading = true; 1630 1631 var session = Utils.clone(this.session); 1632 session.ttl = (new Date()).getTime() + LS_SESSION_CACHE_TIME * 1000; 1633 if(this.storage) 1634 { 1635 try 1636 { 1637 this.storage.set(LS_SESSION, JSON.stringify(session), LS_SESSION_CACHE_TIME); 1638 } 1639 catch (e) 1640 { 1641 console.error(Utils.getDateForLog() + " Pull: Could not save session info in local storage. Error: ", e); 1642 } 1643 } 1644 1645 this.scheduleReconnect(15); 1646 }; 1647 1648 Pull.prototype.onOffline = function() 1649 { 1650 this.disconnect("1000", "offline"); 1651 }; 1652 1653 Pull.prototype.onOnline = function() 1654 { 1655 this.connect(); 1656 }; 1657 1658 Pull.prototype.handleInternalPullEvent = function(command, message) 1659 { 1660 switch (command.toUpperCase()) 1661 { 1662 case SystemCommands.CHANNEL_EXPIRE: 1663 { 1664 if (message.params.action == 'reconnect') 1665 { 1666 this.config.channels[message.params.channel.type] = message.params.new_channel; 1667 this.logToConsole("Pull: new config for " + message.params.channel.type + " channel set:\n", this.config.channels[message.params.channel.type]); 1668 1669 this.reconnect(CloseReasons.CONFIG_REPLACED, "config was replaced"); 1670 } 1671 else 1672 { 1673 this.restart(CloseReasons.CHANNEL_EXPIRED, "channel expired"); 1674 } 1675 break; 1676 } 1677 case SystemCommands.CONFIG_EXPIRE: 1678 { 1679 this.restart(CloseReasons.CONFIG_EXPIRED, "config expired"); 1680 break; 1681 } 1682 case SystemCommands.SERVER_RESTART: 1683 { 1684 this.reconnect(CloseReasons.SERVER_RESTARTED, "server was restarted", 15); 1685 break; 1686 } 1687 default:// 1688 } 1689 }; 1690 1691 Pull.prototype.checkRevision = function(serverRevision) 1692 { 1693 if (this.skipCheckRevision) 1694 { 1695 return true; 1696 } 1697 1698 serverRevision = parseInt(serverRevision); 1699 if (serverRevision > 0 && serverRevision != REVISION) 1700 { 1701 this.enabled = false; 1702 if (typeof BX.message !== 'undefined') 1703 { 1704 this.showNotification(BX.message('PULL_OLD_REVISION')); 1705 } 1706 this.disconnect(CloseReasons.NORMAL_CLOSURE, 'check_revision'); 1707 1708 if (typeof BX.onCustomEvent !== 'undefined') 1709 { 1710 BX.onCustomEvent(window, 'onPullRevisionUp', [serverRevision, REVISION]); 1711 } 1712 1713 this.emit({ 1714 type: SubscriptionType.Revision, 1715 data: { 1716 server: serverRevision, 1717 client: REVISION 1718 } 1719 }); 1720 1721 this.logToConsole("Pull revision changed from " + REVISION + " to " + serverRevision + ". Reload required"); 1722 1723 return false; 1724 } 1725 return true; 1726 }; 1727 1728 Pull.prototype.showNotification = function(text) 1729 { 1730 var self = this; 1731 if (this.notificationPopup || typeof BX.PopupWindow === 'undefined') 1732 return; 1733 1734 this.notificationPopup = new BX.PopupWindow('bx-notifier-popup-confirm', null, { 1735 zIndex: 200, 1736 autoHide: false, 1737 closeByEsc: false, 1738 overlay: true, 1739 content : BX.create("div", { 1740 props: {className: "bx-messenger-confirm"}, 1741 html: text 1742 }), 1743 buttons: [ 1744 new BX.PopupWindowButton({ 1745 text: BX.message('JS_CORE_WINDOW_CLOSE'), 1746 className: "popup-window-button-decline", 1747 events: { 1748 click: function(e) 1749 { 1750 self.notificationPopup.close(); 1751 } 1752 } 1753 }) 1754 ], 1755 events: { 1756 onPopupClose: function() 1757 { 1758 this.destroy() 1759 }, 1760 onPopupDestroy: function() 1761 { 1762 self.notificationPopup = null; 1763 } 1764 } 1765 }); 1766 this.notificationPopup.show(); 1767 }; 1768 1769 Pull.prototype.getRevision = function() 1770 { 1771 return (this.config && this.config.api) ? this.config.api.revision_web : null; 1772 }; 1773 1774 Pull.prototype.getServerVersion = function() 1775 { 1776 return (this.config && this.config.server) ? this.config.server.version : 0; 1777 }; 1778 1779 Pull.prototype.getServerMode = function() 1780 { 1781 return (this.config && this.config.server) ? this.config.server.mode : null; 1782 }; 1783 1784 Pull.prototype.getConfig = function() 1785 { 1786 return this.config; 1787 }; 1788 1789 Pull.prototype.getDebugInfo = function() 1790 { 1791 if (!console || !console.info || !JSON || !JSON.stringify) 1792 return false;
1793 1794 var configDump; 1795 1796 if(this.config && this.config.channels && this.config.channels.private) 1797 { 1798 configDump = "ChannelID: " + this.config.channels.private.id + "\n" + 1799 "ChannelDie: " + this.config.channels.private.end + "\n" + 1800 ("shared" in this.config.channels ? "ChannelDieShared: " + this.config.channels.shared.end : ""); 1801 } 1802 else 1803 { 1804 configDump = "Config error: config is not loaded"; 1805 } 1806 1807 var watchTagsDump = JSON.stringify(this.watchTagsQueue); 1808 var text = "\n========= PULL DEBUG ===========\n"+ 1809 "UserId: " + this.userId + " " + (this.userId > 0 ? '': '(guest)') + "\n" + 1810 (this.guestMode && this.guestUserId !== 0? "Guest userId: " + this.guestUserId + "\n":"") + 1811 "Browser online: " + (navigator.onLine ? 'Y' : 'N') + "\n" + 1812 "Connect: " + (this.isConnected() ? 'Y': 'N') + "\n" + 1813 "Server type: " + (this.isSharedMode() ? 'cloud' : 'local') + "\n" + 1814 "WebSocket support: " + (this.isWebSocketSupported() ? 'Y': 'N') + "\n" + 1815 "WebSocket connect: " + (this._connectors.webSocket && this._connectors.webSocket.connected ? 'Y': 'N') + "\n"+ 1816 "WebSocket mode: " + (this._connectors.webSocket && this._connectors.webSocket.socket ? (this._connectors.webSocket.socket.url.search("binaryMode=true") != -1 ? "protobuf" : "text") : '-') + "\n"+ 1817 1818 "Try connect: " + (this.reconnectTimeout? 'Y': 'N') + "\n" + 1819 "Try number: " + (this.connectionAttempt) + "\n" + 1820 "\n"+ 1821 "Path: " + (this.connector ? this.connector.path : '-') + "\n" + 1822 configDump + "\n" + 1823 "\n"+ 1824 "Last message: " + (this.session.mid > 0? this.session.mid : '-') + "\n" + 1825 "Session history: " + JSON.stringify(this.session.history) + "\n" + 1826 "Watch tags: " + (watchTagsDump == '{}'? '-' : watchTagsDump) + "\n"+ 1827 "================================\n"; 1828 1829 return console.info(text); 1830 }; 1831 1832 Pull.prototype.enableLogging = function(loggingFlag) 1833 { 1834 if(loggingFlag === undefined) 1835 { 1836 loggingFlag = true; 1837 } 1838 loggingFlag = loggingFlag === true; 1839 1840 this.sharedConfig.setLoggingEnabled(loggingFlag); 1841 this.loggingEnabled = loggingFlag; 1842 }; 1843 1844 Pull.prototype.capturePullEvent = function(debugFlag) 1845 { 1846 if(debugFlag === undefined) 1847 { 1848 debugFlag = true; 1849 } 1850 1851 this.debug = debugFlag; 1852 }; 1853 1854 Pull.prototype.getConnectionPath = function(connectionType) 1855 { 1856 var path; 1857 1858 switch(connectionType) 1859 { 1860 case ConnectionType.WebSocket: 1861 path = this.isSecure? this.config.server.websocket_secure: this.config.server.websocket; 1862 break; 1863 case ConnectionType.LongPolling: 1864 path = this.isSecure? this.config.server.long_pooling_secure: this.config.server.long_polling; 1865 break; 1866 default: 1867 throw new Error("Unknown connection type " + connectionType); 1868 } 1869 1870 if(!Utils.isNotEmptyString(path)) 1871 { 1872 return false; 1873 } 1874 1875 var channels = []; 1876 ['private', 'shared'].forEach(function(type) 1877 { 1878 if (typeof this.config.channels[type] !== 'undefined') 1879 { 1880 channels.push(this.config.channels[type].id); 1881 } 1882 }, this); 1883 1884 if(channels.length === 0) 1885 { 1886 return false; 1887 } 1888 1889 var params = { 1890 CHANNEL_ID: channels.join('/') 1891 }; 1892 1893 if(this.isProtobufSupported()) 1894 { 1895 params.binaryMode = 'true'; 1896 } 1897 if (this.isSharedMode()) 1898 { 1899 if(!this.config.clientId) 1900 { 1901 throw new Error("Push-server is in shared mode, but clientId is not set"); 1902 } 1903 params.clientId = this.config.clientId; 1904 } 1905 if (this.session.mid) 1906 { 1907 params.mid = this.session.mid; 1908 } 1909 if (this.session.tag) 1910 { 1911 params.tag = this.session.tag; 1912 } 1913 if (this.session.time) 1914 { 1915 params.time = this.session.time; 1916 } 1917 params.revision = REVISION; 1918 1919 return path + '?' + Utils.buildQueryString(params); 1920 }; 1921 1922 Pull.prototype.getPublicationPath = function() 1923 { 1924 var path = this.isSecure? this.config.server.publish_secure: this.config.server.publish; 1925 if(!path) 1926 { 1927 return ''; 1928 } 1929 1930 var channels = []; 1931 for (var type in this.config.channels) 1932 { 1933 if (!this.config.channels.hasOwnProperty(type)) 1934 { 1935 continue; 1936 } 1937 channels.push(this.config.channels[type].id); 1938 } 1939 1940 var params = { 1941 CHANNEL_ID: channels.join('/') 1942 }; 1943 1944 return path + '?' + Utils.buildQueryString(params); 1945 }; 1946 1947 /** 1948 * Returns reconnect delay in seconds 1949 * @param attemptNumber 1950 * @return {number} 1951 */ 1952 Pull.prototype.getConnectionAttemptDelay = function(attemptNumber) 1953 { 1954 var result; 1955 if(attemptNumber < 1) 1956 { 1957 result = 0.5; 1958 } 1959 else if(attemptNumber < 3) 1960 { 1961 result = 15; 1962 } 1963 else if(attemptNumber < 5) 1964 { 1965 result = 45; 1966 } 1967 else if (attemptNumber < 10) 1968 { 1969 result = 600; 1970 } 1971 else 1972 { 1973 result = 3600; 1974 } 1975 1976 return result + (result * Math.random() * 0.2); 1977 }; 1978 1979 Pull.prototype.sendPullStatus = function(status) 1980 { 1981 if(this.unloading) 1982 { 1983 return; 1984 } 1985 1986 if (typeof BX.onCustomEvent !== 'undefined') 1987 { 1988 BX.onCustomEvent(window, 'onPullStatus', [status]); 1989 } 1990
1991 this.emit({ 1992 type: SubscriptionType.Status, 1993 data: { 1994 status: status 1995 } 1996 }); 1997 }; 1998 1999 Pull.prototype.extendWatch = function (tag, force) 2000 { 2001 if (!tag || this.watchTagsQueue[tag]) 2002 { 2003 return false; 2004 } 2005 2006 this.watchTagsQueue[tag] = true; 2007 if (force) 2008 { 2009 this.updateWatch(force); 2010 } 2011 }; 2012 2013 Pull.prototype.updateWatch = function (force) 2014 { 2015 clearTimeout(this.watchUpdateTimeout); 2016 this.watchUpdateTimeout = setTimeout(function () 2017 { 2018 var watchTags = Object.keys(this.watchTagsQueue); 2019 if (watchTags.length > 0) 2020 { 2021 this.restClient.callMethod('pull.watch.extend', {tags: watchTags}, function(result) 2022 { 2023 if(result.error()) 2024 { 2025 this.updateWatch(); 2026 2027 return false; 2028 } 2029 2030 var updatedTags = result.data(); 2031 2032 for (var tagId in updatedTags) 2033 { 2034 if (updatedTags.hasOwnProperty(tagId) && !updatedTags[tagId]) 2035 { 2036 this.clearWatch(tagId); 2037 } 2038 } 2039 this.updateWatch(); 2040 2041 }.bind(this)) 2042 } 2043 else 2044 { 2045 this.updateWatch(); 2046 } 2047 }.bind(this), force ? this.watchForceUpdateInterval : this.watchUpdateInterval); 2048 }; 2049 2050 Pull.prototype.clearWatch = function (tagId) 2051 { 2052 delete this.watchTagsQueue[tagId]; 2053 }; 2054 2055 // old functions, not used anymore. 2056 Pull.prototype.setPrivateVar = function(){}; 2057 Pull.prototype.returnPrivateVar = function(){}; 2058 Pull.prototype.expireConfig = function(){}; 2059 Pull.prototype.updateChannelID = function(){}; 2060 Pull.prototype.tryConnect = function(){}; 2061 Pull.prototype.tryConnectDelay = function(){}; 2062 Pull.prototype.tryConnectSet = function(){}; 2063 Pull.prototype.updateState = function(){}; 2064 Pull.prototype.setUpdateStateStepCount = function(){}; 2065 Pull.prototype.supportWebSocket = function() 2066 { 2067 return this.isWebSocketSupported(); 2068 }; 2069 Pull.prototype.isWebSoketConnected = function() 2070 { 2071 return this.isConnected() && this.connectionType == ConnectionType.WebSocket; 2072 }; 2073 Pull.prototype.getPullServerStatus = function(){return this.isConnected()}; 2074 Pull.prototype.closeConfirm = function() 2075 { 2076 if (this.notificationPopup) 2077 { 2078 this.notificationPopup.destroy(); 2079 } 2080 }; 2081 2082 var SharedConfig = function(params) 2083 { 2084 params = params || {}; 2085 this.storage = params.storage || new StorageManager(); 2086 2087 this.ttl = 24 * 60 * 60; 2088 2089 this.lsKeys = { 2090 websocketBlocked: 'bx-pull-websocket-blocked', 2091 longPollingBlocked: 'bx-pull-longpolling-blocked', 2092 loggingEnabled: 'bx-pull-logging-enabled' 2093 }; 2094 2095 this.callbacks = { 2096 onWebSocketBlockChanged: (Utils.isFunction(params.onWebSocketBlockChanged) ? params.onWebSocketBlockChanged : function(){}) 2097 }; 2098 2099 if (this.storage) 2100 { 2101 window.addEventListener('storage', this.onLocalStorageSet.bind(this)); 2102 } 2103 }; 2104 2105 SharedConfig.prototype.onLocalStorageSet = function(params) 2106 { 2107 if( 2108 this.storage.compareKey(params.key, this.lsKeys.websocketBlocked) 2109 && params.newValue != params.oldValue 2110 ) 2111 { 2112 this.callbacks.onWebSocketBlockChanged({ 2113 isWebSocketBlocked: this.isWebSocketBlocked() 2114 }) 2115 } 2116 }; 2117 2118 SharedConfig.prototype.isWebSocketBlocked = function() 2119 { 2120 if (!this.storage) 2121 { 2122 return false; 2123 } 2124 2125 return this.storage.get(this.lsKeys.websocketBlocked, 0) > Utils.getTimestamp(); 2126 }; 2127 2128 SharedConfig.prototype.setWebSocketBlocked = function(isWebSocketBlocked) 2129 { 2130 if (!this.storage) 2131 { 2132 return false; 2133 } 2134 2135 try 2136 { 2137 this.storage.set(this.lsKeys.websocketBlocked, (isWebSocketBlocked ? Utils.getTimestamp()+this.ttl : 0)); 2138 } 2139 catch (e) 2140 {
2141 console.error(Utils.getDateForLog() + " Pull: Could not save WS_blocked flag in local storage. Error: ", e); 2142 } 2143 }; 2144 2145 SharedConfig.prototype.isLongPollingBlocked = function() 2146 { 2147 if (!this.storage) 2148 { 2149 return false; 2150 } 2151 2152 return this.storage.get(this.lsKeys.longPollingBlocked, 0) > Utils.getTimestamp(); 2153 }; 2154 2155 SharedConfig.prototype.setLongPollingBlocked = function(isLongPollingBlocked) 2156 { 2157 if (!this.storage) 2158 { 2159 return false; 2160 } 2161 2162 try 2163 { 2164 this.storage.set(this.lsKeys.longPollingBlocked, (isLongPollingBlocked ? Utils.getTimestamp()+this.ttl : 0)); 2165 } 2166 catch (e) 2167 { 2168 console.error(Utils.getDateForLog() + " Pull: Could not save LP_blocked flag in local storage. Error: ", e); 2169 } 2170 }; 2171 2172 SharedConfig.prototype.isLoggingEnabled = function() 2173 { 2174 if (!this.storage) 2175 { 2176 return false; 2177 } 2178 2179 return this.storage.get(this.lsKeys.loggingEnabled, 0) > Utils.getTimestamp(); 2180 }; 2181 2182 SharedConfig.prototype.setLoggingEnabled = function(isLoggingEnabled) 2183 { 2184 if (!this.storage) 2185 { 2186 return false; 2187 } 2188 2189 try 2190 { 2191 this.storage.set(this.lsKeys.loggingEnabled, (isLoggingEnabled ? Utils.getTimestamp()+this.ttl : 0)); 2192 } 2193 catch (e) 2194 {
2195 console.error("LocalStorage error: ", e); 2196 return false; 2197 } 2198 }; 2199 2200 var ObjectExtend = function(child, parent) 2201 { 2202 var f = function() {}; 2203 f.prototype = parent.prototype; 2204 2205 child.prototype = new f(); 2206 child.prototype.constructor = child; 2207 2208 child.superclass = parent.prototype; 2209 if(parent.prototype.constructor == Object.prototype.constructor) 2210 { 2211 parent.prototype.constructor = parent; 2212 } 2213 }; 2214 2215 var AbstractConnector = function(config) 2216 { 2217 this.parent = config.parent; 2218 this.callbacks = { 2219 onOpen: Utils.isFunction(config.onOpen) ? config.onOpen : function() {}, 2220 onDisconnect: Utils.isFunction(config.onDisconnect) ? config.onDisconnect : function() {}, 2221 onError: Utils.isFunction(config.onError) ? config.onError : function() {}, 2222 onMessage: Utils.isFunction(config.onMessage) ? config.onMessage : function() {} 2223 }; 2224 2225 this._connected = false; 2226 this.connectionType = ""; 2227 2228 this.disconnectCode = ''; 2229 this.disconnectReason = ''; 2230 2231 Object.defineProperty(this, "connected", { 2232 get: function() 2233 { 2234 return this._connected 2235 }, 2236 set: function(connected) 2237 { 2238 if(connected == this._connected) 2239 return; 2240 2241 this._connected = connected; 2242 2243 if(this._connected) 2244 { 2245 this.callbacks.onOpen(); 2246 } 2247 else 2248 { 2249 this.callbacks.onDisconnect({ 2250 code: this.disconnectCode, 2251 reason: this.disconnectReason 2252 }); 2253 } 2254 } 2255 }); 2256 2257 Object.defineProperty(this, "path", { 2258 get: function() 2259 { 2260 return this.parent.getConnectionPath(this.connectionType); 2261 } 2262 }) 2263 }; 2264 2265 var WebSocketConnector = function(config) 2266 { 2267 WebSocketConnector.superclass.constructor.apply(this, arguments); 2268 this.connectionType = ConnectionType.WebSocket; 2269 this.socket = null; 2270 2271 this.onSocketOpenHandler = this.onSocketOpen.bind(this); 2272 this.onSocketCloseHandler = this.onSocketClose.bind(this); 2273 this.onSocketErrorHandler = this.onSocketError.bind(this); 2274 this.onSocketMessageHandler = this.onSocketMessage.bind(this); 2275 }; 2276 2277 ObjectExtend(WebSocketConnector, AbstractConnector); 2278 2279 WebSocketConnector.prototype.connect = function() 2280 { 2281 if(this.socket) 2282 { 2283 if(this.socket.readyState === 1) 2284 { 2285 // already connected 2286 return true; 2287 } 2288 else 2289 { 2290 this.socket.removeEventListener('open', this.onSocketOpenHandler); 2291 this.socket.removeEventListener('close', this.onSocketCloseHandler); 2292 this.socket.removeEventListener('error', this.onSocketErrorHandler); 2293 this.socket.removeEventListener('message', this.onSocketMessageHandler); 2294 2295 this.socket.close(); 2296 this.socket = null; 2297 } 2298 } 2299 2300 this.createSocket(); 2301 }; 2302 2303 WebSocketConnector.prototype.disconnect = function(code, message) 2304 { 2305 if (this.socket !== null) 2306 { 2307 this.socket.removeEventListener('open', this.onSocketOpenHandler); 2308 this.socket.removeEventListener('close', this.onSocketCloseHandler); 2309 this.socket.removeEventListener('error', this.onSocketErrorHandler); 2310 this.socket.removeEventListener('message', this.onSocketMessageHandler); 2311 2312 this.socket.close(code, message); 2313 } 2314 this.socket = null; 2315 this.disconnectCode = code; 2316 this.disconnectReason = message; 2317 this.connected = false; 2318 }; 2319 2320 WebSocketConnector.prototype.createSocket = function() 2321 { 2322 if(this.socket) 2323 { 2324 throw new Error("Socket already exists"); 2325 } 2326 2327 if(!this.path) 2328 { 2329 throw new Error("Websocket connection path is not defined"); 2330 } 2331 2332 this.socket = new WebSocket(this.path); 2333 this.socket.binaryType = 'arraybuffer'; 2334 2335 this.socket.addEventListener('open', this.onSocketOpenHandler); 2336 this.socket.addEventListener('close', this.onSocketCloseHandler); 2337 this.socket.addEventListener('error', this.onSocketErrorHandler); 2338 this.socket.addEventListener('message', this.onSocketMessageHandler); 2339 }; 2340 2341 /** 2342 * Sends some data to the server via websocket connection. 2343 * @param {ArrayBuffer} buffer Data to send. 2344 * @return {boolean} 2345 */ 2346 WebSocketConnector.prototype.send = function(buffer) 2347 { 2348 if(!this.socket || this.socket.readyState !== 1) 2349 { 2350 console.error(Utils.getDateForLog() + ": Pull: WebSocket is not connected"); 2351 return false; 2352 } 2353 2354 this.socket.send(buffer); 2355 }; 2356 2357 WebSocketConnector.prototype.onSocketOpen = function() 2358 { 2359 this.connected = true; 2360 }; 2361 2362 WebSocketConnector.prototype.onSocketClose = function(e) 2363 { 2364 this.socket = null; 2365 this.disconnectCode = e.code; 2366 this.disconnectReason = e.reason; 2367 this.connected = false;
2368 }; 2369 2370 WebSocketConnector.prototype.onSocketError = function(e) 2371 { 2372 this.callbacks.onError(e); 2373 }; 2374 2375 WebSocketConnector.prototype.onSocketMessage = function(e) 2376 { 2377 this.callbacks.onMessage(e.data); 2378 }; 2379 2380 WebSocketConnector.prototype.destroy = function() 2381 { 2382 if(this.socket) 2383 { 2384 this.socket.close(); 2385 this.socket = null; 2386 } 2387 }; 2388 2389 var LongPollingConnector = function(config) 2390 { 2391 LongPollingConnector.superclass.constructor.apply(this, arguments); 2392 2393 this.active = false; 2394 this.connectionType = ConnectionType.LongPolling; 2395 this.requestTimeout = null; 2396 this.failureTimeout = null; 2397 this.xhr = this.createXhr(); 2398 this.requestAborted = false; 2399 }; 2400 2401 ObjectExtend(LongPollingConnector, AbstractConnector); 2402 2403 LongPollingConnector.prototype.createXhr = function() 2404 { 2405 var result = new XMLHttpRequest(); 2406 if(this.parent.isProtobufSupported()) 2407 { 2408 result.responseType = "arraybuffer"; 2409 } 2410 result.addEventListener("readystatechange", this.onXhrReadyStateChange.bind(this)); 2411 return result; 2412 }; 2413 2414 LongPollingConnector.prototype.connect = function() 2415 { 2416 this.active = true; 2417 this.performRequest(); 2418 }; 2419
2420 LongPollingConnector.prototype.disconnect = function(code, reason) 2421 { 2422 this.active = false; 2423 2424 if(this.failureTimeout) 2425 { 2426 clearTimeout(this.failureTimeout); 2427 this.failureTimeout = null; 2428 } 2429 if(this.requestTimeout) 2430 { 2431 clearTimeout(this.requestTimeout); 2432 this.requestTimeout = null; 2433 } 2434 2435 if(this.xhr) 2436 { 2437 this.requestAborted = true; 2438 this.xhr.abort(); 2439 } 2440 2441 this.disconnectCode = code; 2442 this.disconnectReason = reason; 2443 this.connected = false; 2444 }; 2445 2446 LongPollingConnector.prototype.performRequest = function() 2447 { 2448 var self = this; 2449 if(!this.active) 2450 return; 2451 2452 if(!this.path) 2453 { 2454 throw new Error("Long polling connection path is not defined"); 2455 } 2456 if(this.xhr.readyState !== 0 && this.xhr.readyState !== 4) 2457 { 2458 return; 2459 } 2460 2461 clearTimeout(this.failureTimeout); 2462 clearTimeout(this.requestTimeout); 2463 2464 this.failureTimeout = setTimeout(function() 2465 { 2466 self.connected = true; 2467 }, 5000); 2468 2469 this.requestTimeout = setTimeout(this.onRequestTimeout.bind(this), LONG_POLLING_TIMEOUT * 1000); 2470 2471 this.xhr.open("GET", this.path); 2472 this.xhr.send(); 2473 }; 2474
2475 LongPollingConnector.prototype.onRequestTimeout = function() 2476 { 2477 this.requestAborted = true; 2478 this.xhr.abort(); 2479 this.performRequest(); 2480 }; 2481 2482 LongPollingConnector.prototype.onXhrReadyStateChange = function (e) 2483 { 2484 if (this.xhr.readyState === 4) 2485 { 2486 if(!this.requestAborted || this.xhr.status == 200) 2487 { 2488 this.onResponse(this.xhr.response); 2489 } 2490 this.requestAborted = false; 2491 } 2492 }; 2493 2494 /** 2495 * Sends some data to the server via http request. 2496 * @param {ArrayBuffer} buffer Data to send. 2497 * @return {bool} 2498 */ 2499 LongPollingConnector.prototype.send = function(buffer) 2500 { 2501 var path = this.parent.getPublicationPath(); 2502 if(!path) 2503 { 2504 console.error(Utils.getDateForLog() + ": Pull: publication path is empty"); 2505 return false; 2506 } 2507 2508 var xhr = new XMLHttpRequest(); 2509 xhr.open("POST", path); 2510 xhr.send(buffer); 2511 }; 2512 2513 LongPollingConnector.prototype.onResponse = function(response) 2514 { 2515 if(this.failureTimeout) 2516 { 2517 clearTimeout(this.failureTimeout); 2518 this.failureTimeout = 0; 2519 } 2520 if(this.requestTimeout) 2521 { 2522 clearTimeout(this.requestTimeout); 2523 this.requestTimeout = 0; 2524 } 2525 2526 if(this.xhr.status == 200) 2527 { 2528 this.connected = true; 2529 if(Utils.isNotEmptyString(response) || (response instanceof ArrayBuffer)) 2530 { 2531 this.callbacks.onMessage(response); 2532 } 2533 else 2534 { 2535 this.parent.session.mid = null; 2536 } 2537 this.performRequest(); 2538 } 2539 else if(this.xhr.status == 304) 2540 { 2541 this.connected = true; 2542 if (this.xhr.getResponseHeader("Expires") === "Thu, 01 Jan 1973 11:11:01 GMT") 2543 { 2544 var lastMessageId = this.xhr.getResponseHeader("Last-Message-Id"); 2545 if (Utils.isNotEmptyString(lastMessageId)) 2546 { 2547 this.parent.setLastMessageId(lastMessageId); 2548 } 2549 } 2550 this.performRequest(); 2551 } 2552 else 2553 { 2554 this.callbacks.onError('Could not connect to the server'); 2555 this.connected = false; 2556 } 2557 }; 2558 2559 var ChannelManager = function (params) 2560 { 2561 this.publicIds = {}; 2562 2563 this.restClient = typeof params.restClient !== "undefined"? params.restClient: BX.rest; 2564 2565 this.getPublicListMethod = params.getPublicListMethod; 2566 }; 2567 2568 /** 2569 * 2570 * @param {Array} users Array of user ids. 2571 * @return {BX.Promise} 2572 */ 2573 ChannelManager.prototype.getPublicIds = function(users) 2574 { 2575 var promise = new BX.Promise(); 2576 var result = {}; 2577 var now = new Date(); 2578 var unknownUsers = []; 2579 2580 for(var i = 0; i < users.length; i++) 2581 { 2582 var userId = users[i]; 2583 if(this.publicIds[userId] && this.publicIds[userId]['end'] > now) 2584 { 2585 result[userId] = this.publicIds[userId]; 2586 } 2587 else 2588 { 2589 unknownUsers.push(userId); 2590 } 2591 } 2592 2593 if(unknownUsers.length === 0) 2594 { 2595 promise.resolve(result); 2596 return promise; 2597 } 2598 2599 this.restClient.callMethod(this.getPublicListMethod, {users: unknownUsers}).then(function(response) 2600 { 2601 if(response.error()) 2602 { 2603 promise.resolve({}); 2604 return promise; 2605 } 2606 2607 var data = response.data(); 2608 2609 this.setPublicIds(Utils.objectValues(data)); 2610 unknownUsers.forEach(function(userId) { 2611 result[userId] = this.publicIds[userId]; 2612 }, this); 2613 2614 promise.resolve(result); 2615 2616 }.bind(this)); 2617 2618 return promise; 2619 }; 2620 2621 /** 2622 * 2623 * @param {object[]} publicIds 2624 * @param {integer} publicIds.user_id 2625 * @param {string} publicIds.public_id 2626 * @param {string} publicIds.signature 2627 * @param {Date} publicIds.start 2628 * @param {Date} publicIds.end 2629 */ 2630 ChannelManager.prototype.setPublicIds = function(publicIds) 2631 { 2632 for(var i = 0; i < publicIds.length; i++) 2633 { 2634 var publicIdDescriptor = publicIds[i]; 2635 var userId = publicIdDescriptor.user_id; 2636 this.publicIds[userId] = { 2637 userId: userId, 2638 publicId: publicIdDescriptor.public_id, 2639 signature: publicIdDescriptor.signature, 2640 start: new Date(publicIdDescriptor.start), 2641 end: new Date(publicIdDescriptor.end) 2642 } 2643 } 2644 }; 2645 2646 2647 var StorageManager = function (params) 2648 { 2649 params = params || {}; 2650 2651 this.userId = params.userId? params.userId: (typeof BX.message !== 'undefined' && BX.message.USER_ID? BX.message.USER_ID: 0); 2652 this.siteId = params.siteId? params.siteId: (typeof BX.message !== 'undefined' && BX.message.SITE_ID? BX.message.SITE_ID: 'none'); 2653 }; 2654 2655 StorageManager.prototype.set = function(name, value) 2656 { 2657 if (typeof window.localStorage === 'undefined') 2658 { 2659 return false;
2660 } 2661 if (typeof value != 'string') 2662 { 2663 if (value) 2664 { 2665 value = JSON.stringify(value); 2666 } 2667 } 2668 return window.localStorage.setItem(this.getKey(name), value) 2669 }; 2670 2671 2672 StorageManager.prototype.get = function(name, defaultValue) 2673 { 2674 if (typeof window.localStorage === 'undefined') 2675 { 2676 return defaultValue || null; 2677 } 2678 2679 var result = window.localStorage.getItem(this.getKey(name)); 2680 if (result === null) 2681 { 2682 return defaultValue || null; 2683 } 2684 2685 return JSON.parse(result); 2686 }; 2687 2688 StorageManager.prototype.remove = function(name) 2689 { 2690 if (typeof window.localStorage === 'undefined') 2691 { 2692 return false; 2693 } 2694 return window.localStorage.removeItem(this.getKey(name)); 2695 }; 2696 2697 StorageManager.prototype.getKey = function (name) 2698 { 2699 return 'bx-pull-' + this.userId + '-' + this.siteId + '-' + name; 2700 }; 2701 2702 StorageManager.prototype.compareKey = function (eventKey, userKey) 2703 { 2704 return eventKey === this.getKey(userKey); 2705 }; 2706 2707 var Utils = { 2708 browser: { 2709 IsChrome: function() 2710 { 2711 return navigator.userAgent.toLowerCase().indexOf('chrome') != -1; 2712 }, 2713 IsFirefox: function() 2714 { 2715 return navigator.userAgent.toLowerCase().indexOf('firefox') != -1; 2716 }, 2717 IsIe: function () 2718 { 2719 return navigator.userAgent.match(/(Trident\/|MSIE\/)/) !== null; 2720 } 2721 }, 2722 getTimestamp: function() 2723 { 2724 return (new Date()).getTime(); 2725 }, 2726 /** 2727 * Reduces errors array to single string. 2728 * @param {array} errors 2729 * @return {string} 2730 */ 2731 errorsToString: function(errors) 2732 { 2733 if(!this.isArray(errors)) 2734 { 2735 return ""; 2736 } 2737 else 2738 { 2739 return errors.reduce(function(result, currentValue) 2740 { 2741 if(result != "") 2742 { 2743 result += "; "; 2744 } 2745 return result + currentValue.code + ": " + currentValue.message; 2746 }, ""); 2747 } 2748 }, 2749 isString: function(item) { 2750 return item === '' ? true : (item ? (typeof (item) == "string" || item instanceof String) : false); 2751 }, 2752 isArray: function(item) { 2753 return item && Object.prototype.toString.call(item) == "[object Array]"; 2754 }, 2755 isFunction: function(item) { 2756 return item === null ? false : (typeof (item) == "function" || item instanceof Function); 2757 }, 2758 isDomNode: function(item) { 2759 return item && typeof (item) == "object" && "nodeType" in item; 2760 }, 2761 isDate: function(item) { 2762 return item && Object.prototype.toString.call(item) == "[object Date]"; 2763 }, 2764 isPlainObject: function(item) 2765 { 2766 if(!item || typeof(item) !== "object" || item.nodeType) 2767 { 2768 return false; 2769 } 2770 2771 var hasProp = Object.prototype.hasOwnProperty; 2772 try 2773 { 2774 if (item.constructor && !hasProp.call(item, "constructor") && !hasProp.call(item.constructor.prototype, "isPrototypeOf") ) 2775 { 2776 return false; 2777 } 2778 } 2779 catch (e) 2780 { 2781 return false; 2782 } 2783 2784 var key; 2785 for (key in item) 2786 { 2787 } 2788 return typeof(key) === "undefined" || hasProp.call(item, key); 2789 }, 2790 isNotEmptyString: function(item) { 2791 return this.isString(item) ? item.length > 0 : false; 2792 }, 2793 buildQueryString: function(params) 2794 { 2795 var result = ''; 2796 for (var key in params) 2797 { 2798 if (!params.hasOwnProperty(key)) 2799 { 2800 continue; 2801 } 2802 var value = params[key]; 2803 if(Utils.isArray(value)) 2804 { 2805 value.forEach(function(valueElement, index) 2806 { 2807 result += encodeURIComponent(key + "[" + index + "]") + "=" + encodeURIComponent(valueElement) + "&"; 2808 }); 2809 } 2810 else 2811 { 2812 result += encodeURIComponent(key) + "=" + encodeURIComponent(value) + "&"; 2813 } 2814 } 2815 2816 if(result.length > 0) 2817 { 2818 result = result.substr(0, result.length - 1); 2819 } 2820 return result; 2821 }, 2822 objectValues: function values(obj) 2823 { 2824 var result = []; 2825 for (var key in obj) 2826 { 2827 if(obj.hasOwnProperty(key) && obj.propertyIsEnumerable(key)) 2828 { 2829 result.push(obj[key]); 2830 } 2831 } 2832 return result; 2833 }, 2834 clone: function(obj, bCopyObj) 2835 { 2836 var _obj, i, l; 2837 if (bCopyObj !== false) 2838 bCopyObj = true; 2839 2840 if (obj === null) 2841 return null; 2842 2843 if (this.isDomNode(obj)) 2844 { 2845 _obj = obj.cloneNode(bCopyObj); 2846 } 2847 else if (typeof obj == 'object') 2848 { 2849 if (this.isArray(obj)) 2850 { 2851 _obj = []; 2852 for (i=0,l=obj.length;i<l;i++) 2853 { 2854 if (typeof obj[i] == "object" && bCopyObj) 2855 _obj[i] = this.clone(obj[i], bCopyObj); 2856 else 2857 _obj[i] = obj[i]; 2858 } 2859 } 2860 else 2861 { 2862 _obj = {}; 2863 if (obj.constructor) 2864 { 2865 if (this.isDate(obj)) 2866 _obj = new Date(obj); 2867 else 2868 _obj = new obj.constructor(); 2869 } 2870 2871 for (i in obj) 2872 { 2873 if (!obj.hasOwnProperty(i)) 2874 { 2875 continue; 2876 } 2877 if (typeof obj[i] == "object" && bCopyObj) 2878 _obj[i] = this.clone(obj[i], bCopyObj); 2879 else 2880 _obj[i] = obj[i]; 2881 } 2882 } 2883 2884 } 2885 else 2886 {
2887 _obj = obj; 2888 } 2889 2890 return _obj; 2891 }, 2892 2893 getDateForLog: function() 2894 { 2895 var d = new Date(); 2896 2897 return d.getFullYear() + "-" + Utils.lpad(d.getMonth(), 2, '0') + "-" + Utils.lpad(d.getDate(), 2, '0') + " " + Utils.lpad(d.getHours(), 2, '0') + ":" + Utils.lpad(d.getMinutes(), 2, '0'); 2898 }, 2899 2900 lpad: function(str, length, chr) 2901 { 2902 str = str.toString(); 2903 chr = chr || ' '; 2904 2905 if(str.length > length) 2906 { 2907 return str; 2908 } 2909 2910 var result = ''; 2911 for(var i = 0; i < length - str.length; i++) 2912 { 2913 result += chr; 2914 } 2915 2916 return result + str; 2917 } 2918 }; 2919 2920 if ( 2921 typeof BX.namespace !== 'undefined' 2922 && typeof BX.PULL === 'undefined' 2923 ) 2924 { 2925 BX.PULL = new Pull(); 2926 } 2927 2928 BX.PullClient = Pull; 2929 BX.PullClient.PullStatus = PullStatus; 2930 BX.PullClient.SubscriptionType = SubscriptionType; 2931 BX.PullClient.CloseReasons = CloseReasons; 2932 BX.PullClient.StorageManager = StorageManager; 2933})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.