PageSourceSearch

https://medassist-k.ru/bitrix/js/pull/client/pull.client.js?165769563570481

js medassist-k.ru collected 2026-10-03 23:35:05 UTC 70,481 bytes, 2,933 lines download raw bytes

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.