Your IP : 18.224.32.173


Current Path : /var/www/axolotl/data/www/arhangelsk.axolotls.ru/bitrix/js/im/call/
Upload File :
Current File : /var/www/axolotl/data/www/arhangelsk.axolotls.ru/bitrix/js/im/call/engine.js

;(function()
{
	BX.namespace('BX.Call');

	BX.Call.State = {
		Incoming: 'Incoming'
	};

	BX.Call.UserState = {
		Idle: 'Idle',
		Busy: 'Busy',
		Calling: 'Calling',
		Unavailable: 'Unavailable',
		Declined: 'Declined',
		Ready: 'Ready',
		Connecting: 'Connecting',
		Connected: 'Connected',
		Failed: 'Failed'
	};

	BX.Call.Type = {
		Instant: 1,
		Permanent: 2
	};

	BX.Call.Provider = {
		Plain: 'Plain',
		Voximplant: 'Voximplant',
		Janus: 'Janus'
	};

	BX.Call.StreamTag = {
		Main: 'main',
		Screen: 'screen'
	};

	BX.Call.Direction = {
		Incoming: 'Incoming',
		Outgoing: 'Outgoing'
	};

	BX.Call.Quality = {
		VeryHigh: "very_high",
		High: "high",
		Medium: "medium",
		Low: "low",
		VeryLow: "very_low"
	};

	BX.Call.Event = {
		onUserInvited: 'onUserInvited',
		onUserStateChanged: 'onUserStateChanged',
		onUserMicrophoneState: 'onUserMicrophoneState',
		onUserVoiceStarted: 'onUserVoiceStarted',
		onUserVoiceStopped: 'onUserVoiceStopped',
		onLocalMediaReceived: 'onLocalMediaReceived',
		onLocalMediaStopped: 'onLocalMediaStopped',
		onDeviceListUpdated: 'onDeviceListUpdated',
		onRTCStatsReceived: 'onRTCStatsReceived',
		onCallFailure: 'onCallFailure',
		onStreamReceived: 'onStreamReceived',
		onStreamRemoved: 'onStreamRemoved',
		onJoin: 'onJoin',
		onLeave: 'onLeave',
		onDestroy: 'onDestroy',
	};

	var ajaxActions = {
		createCall: 'im.call.create',
		createChildCall: 'im.call.createChildCall',
		getPublicChannels: 'pull.channel.public.list',
		getCall: 'im.call.get'
	};

	var __allowConstructing = false;

	BX.Call.Engine = function()
	{
		this.debugFlag = false;
		if(!__allowConstructing)
		{
			throw new Error('Do not use this constructor directly, use BX.Call.Engine.getInstance instead');
		}
		this.calls = {};
		this.userId = Number(BX.message('USER_ID'));

		this.unknownCalls = {};

		this.restClient = null;

		this.init();
	};

	/**
	 * @return {BX.Call.Engine}
	 */
	BX.Call.Engine.getInstance = function()
	{
		return BX.CallEngine;
	};

	BX.Call.Engine.prototype.init = function()
	{
		BX.addCustomEvent("onPullEvent-im", this.__onPullEvent.bind(this));
		BX.addCustomEvent("onPullClientEvent-im", this.__onPullClientEvent.bind(this));
	};

	BX.Call.Engine.prototype.getCurrentUserId = function()
	{
		return this.userId;
	};

	BX.Call.Engine.prototype.setRestClient = function(restClient)
	{
		this.restClient = restClient;
	};

	BX.Call.Engine.prototype.getRestClient = function()
	{
		return this.restClient || BX.rest;
	};

	/**
	 * @param {Object} config
	 * @param {int} config.type
	 * @param {string} config.provider
	 * @param {string} config.entityType
	 * @param {string} config.entityId
	 * @param {string} config.provider
	 * @param {boolean} config.joinExisting
	 * @return Promise<BX.Call.AbstractCall>
	 */
	BX.Call.Engine.prototype.createCall = function(config)
	{
		var self = this;

		return new Promise(function(resolve, reject)
		{
			var callType = config.type || BX.Call.Type.Instant;
			var callProvider = config.provider || self.getDefaultProvider();

			if (config.joinExisting)
			{
				for(var callId in self.calls)
				{
					if(self.calls.hasOwnProperty(callId))
					{
						var call = self.calls[callId];
						if(call.provider == config.provider && call.associatedEntity.type == config.entityType && call.associatedEntity.id == config.entityId)
						{
							self.log(callId, "Found existing call, attaching to it");
							return resolve({
								call: call,
								isNew: false
							});
						}
					}
				}
			}

			var callParameters = {
				type: callType,
				provider: callProvider,
				entityType: config.entityType,
				entityId: config.entityId,
				joinExisting: !!config.joinExisting,
				userIds: BX.type.isArray(config.userIds) ? config.userIds : []
			};

			self.getRestClient().callMethod(ajaxActions.createCall, callParameters).then(function(response)
			{
				if(response.error())
				{
					var error = response.error().getError();
					return reject({
						code: error.error,
						message: error.error_description
					});
				}

				var createCallResponse = response.data();
				if(createCallResponse.userData)
				{
					BX.MessengerCommon.updateUserData(createCallResponse.userData);
				}
				if(createCallResponse.publicChannels)
				{
					BX.PULL.setPublicIds(Object.values(createCallResponse.publicChannels))
				}
				var callFields = createCallResponse.call;
				if (self.calls[callFields['ID']])
				{
					if(self.calls[callFields['ID']] instanceof CallStub)
					{
						self.calls[callFields['ID']].destroy();
					}
					else
					{
						console.error("Call " + callFields['ID'] + " already exists");
						return resolve ({
							call: self.calls[callFields['ID']],
							isNew: false
						});
					}
				}

				var callFabric = self.__getCallFabric(callFields['PROVIDER']);
				var call = callFabric.createCall({
					id: parseInt(callFields['ID'], 10),
					instanceId: self.getUuidv4(),
					direction: BX.Call.Direction.Outgoing,
					users: createCallResponse.users,
					videoEnabled: (config.videoEnabled == true),
					enableMicAutoParameters: (config.enableMicAutoParameters !== false),
					associatedEntity: callFields.ASSOCIATED_ENTITY,
					events: {
						onDestroy: self.__onCallDestroy.bind(self)
					},
					debug: config.debug === true
				});


				self.calls[callFields['ID']] = call;

				if(createCallResponse.isNew)
				{
					self.log(call.id, "Creating new call");
				}
				else
				{
					self.log(call.id, "Server returned existing call, attaching to it");
				}

				BX.onCustomEvent(window, "CallEvents::callCreated", [{
					call: call
				}]);

				resolve({
					call: call,
					isNew: createCallResponse.isNew
				});
			}).catch(function(error)
			{
				if (BX.type.isFunction(error.error))
				{
					error = error.error().getError();
				}
				reject({
					code: error.error,
					message: error.error_description
				})
			})
		});

	};

	BX.Call.Engine.prototype.createChildCall = function(parentId, newProvider, newUsers)
	{
		var self = this;
		return new Promise(function(resolve, reject)
		{
			if(!self.calls[parentId])
			{
				return reject('Parent call is not found');
			}

			var parentCall = self.calls[parentId];

			var callParameters = {
				parentId: parentId,
				newProvider: newProvider,
				newUsers: newUsers
			};

			self.getRestClient().callMethod(ajaxActions.createChildCall, callParameters, function(response)
			{
				var createCallResponse = response.data();
				var callFields = createCallResponse.call;
				var callFabric = self.__getCallFabric(callFields['PROVIDER']);

				var call = callFabric.createCall({
					id: parseInt(callFields['ID'], 10),
					instanceId: self.getUuidv4(),
					parentId: callFields['PARENT_ID'],
					direction: BX.Call.Direction.Outgoing,
					users: createCallResponse.users,
					videoEnabled: parentCall.isVideoEnabled(),
					enableMicAutoParameters: parentCall.enableMicAutoParameters !== false,
					associatedEntity: callFields.ASSOCIATED_ENTITY,
					events: {
						onDestroy: self.__onCallDestroy.bind(self)
					}
				});

				self.calls[callFields['ID']] = call;
				BX.onCustomEvent(window, "CallEvents::callCreated", [{
					call: call
				}]);

				resolve({
					call: call,
					isNew: createCallResponse.isNew
				});
			});
		});
	};
	
	BX.Call.Engine.prototype._instantiateCall = function(callFields, users)
	{
		if(this.calls[callFields['ID']])
		{
			console.error("Call " + callFields['ID'] + " already exists");
			return this.calls[callFields['ID']];
		}

		var callFabric = this.__getCallFabric(callFields['PROVIDER']);
		var call = callFabric.createCall({
			id: parseInt(callFields['ID'], 10),
			instanceId: this.getUuidv4(),
			initiatorId: parseInt(callFields['INITIATOR_ID'], 10),
			parentId: callFields['PARENT_ID'],
			direction: callFields['INITIATOR_ID'] == this.userId ? BX.Call.Direction.Outgoing : BX.Call.Direction.Incoming,
			users: users,
			associatedEntity: callFields.ASSOCIATED_ENTITY,

			events: {
				onDestroy: this.__onCallDestroy.bind(this)
			}
		});

		this.calls[callFields['ID']] = call;

		BX.onCustomEvent(window, "CallEvents::callCreated", [{
			call: call
		}]);

		return call;
	};

	BX.Call.Engine.prototype.getCallWithId = function(id)
	{
		var self = this;
		return new Promise(function(resolve, reject)
		{
			if(self.calls[id])
			{
				return resolve({
					call: self.calls[id],
					isNew: false
				});
			}

			self.getRestClient().callMethod(ajaxActions.getCall, {callId: id}).then(function(answer)
			{
				var data = answer.data();
				resolve({
					call: self._instantiateCall(data.call, data.users),
					isNew: false
				})
			}).catch(function (error)
			{
				if (BX.type.isFunction(error.error))
				{
					error = error.error().getError();
				}
				reject({
					code: error.error,
					message: error.error_description
				})
			})
		})
	};

	BX.Call.Engine.prototype.getUuidv4 = function()
	{
		return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
			var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
			return v.toString(16);
		});
	};

	BX.Call.Engine.prototype.__onPullEvent = function(command, params, extra)
	{
		var handlers = {
			'Call::incoming': this.__onPullIncomingCall.bind(this),
		};

		if(command.substr(0, 6) === 'Call::' && params.publicIds)
		{
			BX.PULL.setPublicIds(Object.values(params.publicIds));
		}

		if(handlers[command])
		{
			handlers[command].call(this, params, extra);
		}
		else if(command.substr(0, 6) === 'Call::' && (params['call'] || params['callId']))
		{
			var callId = params['call'] ? params['call']['ID'] : params['callId'];
			if(this.calls[callId])
			{
				this.calls[callId].__onPullEvent(command, params, extra);
			}
			else if(command === 'Call::ping')
			{
				this.__onUnknownCallPing(params, extra).then(function(result)
				{
					if(result && this.calls[callId])
					{
						this.calls[callId].__onPullEvent(command, params, extra);
					}
				}.bind(this));
			}
		}
	};

	BX.Call.Engine.prototype.__onPullClientEvent = function(command, params, extra)
	{
		if(command.substr(0, 6) === 'Call::' && params['callId'])
		{
			var callId = params['callId'];
			if(this.calls[callId])
			{
				this.calls[callId].__onPullEvent(command, params, extra);
			}
			else if (command === 'Call::ping')
			{
				this.__onUnknownCallPing(params, extra).then(function(result)
				{
					if(result && this.calls[callId])
					{
						this.calls[callId].__onPullEvent(command, params, extra);
					}
				}.bind(this));
			}
		}
	};

	BX.Call.Engine.prototype.__onPullIncomingCall = function(params, extra)
	{
		if(extra.server_time_ago > 30)
		{
			console.error("Call was started too long time ago");
			return;
		}

		var callFields = params.call;
		var callId = parseInt(callFields.ID, 10);
		var call;

		if(params.publicIds)
		{
			BX.PULL.setPublicIds(Object.values(params.publicIds));
		}

		if(params.userData)
		{
			BX.MessengerCommon.updateUserData(params.userData);
		}

		if(this.calls[callId])
		{
			call = this.calls[callId];
		}
		else
		{
			var callFabric = this.__getCallFabric(callFields.PROVIDER);
			call = callFabric.createCall({
				id: callId,
				instanceId: this.getUuidv4(),
				parentId: callFields.PARENT_ID || null,
				callFromMobile: params.isMobile === true,
				direction: BX.Call.Direction.Incoming,
				users: params.users,
				initiatorId: params.senderId,
				associatedEntity: callFields.ASSOCIATED_ENTITY,
				events: {
					onDestroy: this.__onCallDestroy.bind(this)
				}
			});

			this.calls[callId] = call;

			BX.onCustomEvent(window, "CallEvents::callCreated", [{
				call: call
			}]);
		}

		call.addInvitedUsers(params.invitedUsers);
		if(call)
		{
			BX.onCustomEvent(window, "CallEvents::incomingCall", [{
				call: call,
				video: params.video === true,
				isMobile: params.isMobile === true
			}]);
		}
		this.log(call.id, "Incoming call " + call.id);
	};

	BX.Call.Engine.prototype.__onUnknownCallPing = function(params, extra)
	{
		return new Promise(function(resolve, reject)
		{
			var callId = Number(params.callId);
			if(extra.server_time_ago > 10)
			{
				this.log(callId, "Error: Ping was sent too long time ago");
				return resolve(false);
			}
			if(!window.BXIM || !window.BXIM.init)
			{
				return resolve(false);
			}

			if(this.unknownCalls[callId])
			{
				return resolve(false);
			}

			this.unknownCalls[callId] = true;

			if(params.userData)
			{
				BX.MessengerCommon.updateUserData(params.userData);
			}

			this.getCallWithId(callId).then(function(result)
			{
				this.unknownCalls[callId] = false;
				resolve(true);
			}.bind(this)).catch(function(error)
			{
				this.unknownCalls[callId] = false;
				this.log(callId, "Error: Could not instantiate call", error);
				resolve(false);
			}.bind(this));
		}.bind(this));
	};

	BX.Call.Engine.prototype.__onCallDestroy = function(e)
	{
		var callId = e.call.id;
		this.calls[callId] = new CallStub({
			callId: callId,
			onDelete: function()
			{
				if(this.calls[callId])
				{
					delete this.calls[callId];
				}
			}.bind(this)
		});

		BX.onCustomEvent(window, "CallEvents::callDestroyed", [{
			callId: e.call.id
		}]);
	};

	BX.Call.Engine.prototype.getDefaultProvider = function()
	{
		return BX.Call.Provider.Plain;
	};

	BX.Call.Engine.prototype.__getCallFabric = function(providerType)
	{
		if(providerType == BX.Call.Provider.Plain)
		{
			return BX.Call.PlainCallFabric;
		}
		else if(providerType == BX.Call.Provider.Voximplant)
		{
			return BX.Call.VoximplantCallFabric;
		}
		else if(providerType == BX.Call.Provider.Janus)
		{
			return BX.Call.JanusCallFabric;
		}

		throw new Error("Unknown call provider type " + providerType);
	};

	BX.Call.Engine.prototype.debug = function(debugFlag)
	{
		this.debugFlag = typeof(debugFlag) === 'undefined' ? true:  !!debugFlag;

		return this.debugFlag;
	};

	BX.Call.Engine.prototype.log = function()
	{
		var text = BX.Call.Util.getDateForLog();

		var callId = typeof(arguments[0]) === "number" ? arguments[0] : 0;

		for (var i = callId > 0 ? 1 : 0; i < arguments.length; i++)
		{
			if(arguments[i] instanceof Error)
			{
				text = arguments[i].message + "\n" + arguments[i].stack
			}
			else
			{
				try
				{
					text = text+' | '+(typeof(arguments[i]) == 'object'? JSON.stringify(arguments[i]): arguments[i]);
				}
				catch (e)
				{
					text = text+' | (circular structure)';
				}
			}
		}
		if (BX.desktop && BX.desktop.ready())
		{
			BX.desktop.log(BX.message('USER_ID')+'.video.log', text.substr(3));
		}
		if (this.debugFlag)
		{
			if (console)
			{
				var a = ['Call log [' + BX.Call.Util.getTimeForLog() + ']: '];
				console.log.apply(this, a.concat(Array.prototype.slice.call(arguments)));
			}
		}

		if(BX.MessengerDebug && callId)
		{
			BX.MessengerDebug.addLog(callId, text);
		}
	};

	BX.Call.Engine.prototype.getAllowedVideoQuality = function(participantsCount)
	{
		if(participantsCount < 5)
		{
			return BX.Call.Quality.VeryHigh
		}
		else if(participantsCount < 8)
		{
			return BX.Call.Quality.High
		}
		else if(participantsCount < 16)
		{
			return BX.Call.Quality.Medium
		}
		else if(participantsCount < 24)
		{
			return BX.Call.Quality.Low
		}
		else
		{
			return BX.Call.Quality.VeryLow
		}
	};

	BX.Call.PlainCallFabric =
	{
		createCall: function(config)
		{
			return new BX.Call.PlainCall(config);
		}
	};

	BX.Call.VoximplantCallFabric =
	{
		createCall: function(config)
		{
			return new BX.Call.VoximplantCall(config);
		}
	};

	BX.Call.JanusCallFabric =
	{
		createCall: function(config)
		{
			return new BX.Call.JanusCall(config);
		}
	};

	var CallStub = function(config)
	{
		this.callId = config.callId;
		this.lifetime = config.lifetime || 120;
		this.callbacks = {
			onDelete: BX.type.isFunction(config.onDelete) ? config.onDelete : BX.DoNothing
		};

		this.deleteTimeout = setTimeout(function()
		{
			this.callbacks.onDelete({
				callId: this.callId
			})
		}.bind(this), this.lifetime * 1000);
	};

	CallStub.prototype.__onPullEvent = function(command, params, extra)
	{
		// do nothing
	};

	CallStub.prototype.isAnyoneParticipating = function()
	{
		return false;
	};

	CallStub.prototype.addEventListener = function()
	{
		return false;
	};

	CallStub.prototype.removeEventListener = function()
	{
		return false;
	};

	CallStub.prototype.destroy = function()
	{
		clearTimeout(this.deleteTimeout);
		this.callbacks.onDelete = BX.DoNothing;
	};

	__allowConstructing = true;
	BX.CallEngine = new BX.Call.Engine();
	__allowConstructing = false;

})();