Source: WindowSession.js

"use strict";

import {EventManager} from "./EventManager.js";
import {WindowMessage} from "./WindowMessage.js";

/**
 * Windows sesstion client is used to store the status of a inter window communication process.
 *
 * Stores information about the other window e.g. (identification, status etc).
 *
 * @class
 */
function WindowSession(manager)
{
	/**
	 * The manager attached to this session.
	 *
	 * @type {WindowManager}
	 */
	this.manager = manager;

	/**
	 * Window object of this client, is not always the actual window, but it is the window that contains (directly or indirectly) access to the destination.
	 *
	 * The window object is used as a point of communcation. Depending of security configurations the access may be restricted.
	 *
	 * @type {Object}
	 */
	this.window = null;

	/**
	 * URL of the other window.
	 *
	 * Only available for windows that were open direclty, for wich a URL is known.
	 *
	 * @type {String}
	 */
	this.url = "";

	/**
	 * UUID to identify the other window.
	 *
	 * @type {String}
	 */
	this.uuid = null;
	
	/**
	 * Textual type indicator of the other window, used to indetify the type of the other window.
	 *
	 * (e.g. "3D", "Main", etc)
	 *
	 * @type {String}
	 */
	this.type = null;
		
	/**
	 * Counter of messages exchanged between the sessions.
	 *
	 * Increased everytime a message is sent.
	 *
	 * @type {Number}
	 */
	this.counter = 0;

	/** 
	 * The session object that is used as gateway for this session.
	 *
	 * When directly connected to the other window has a null value.
	 *
	 * @type {WindowSession}
	 */
	this.gateway = null;

	/**
	 * Status of the communication.
	 *
	 * @type {String}
	 */
	this.status = WindowSession.WAITING;

	/**
	 * List of all messages received.
	 *
	 * Stored the original WindowMessage object contains information about the window of origin and authentication data.
	 * 
	 * @type {Array}
	 */
	this.received = [];

	/**
	 * Messages waiting to be sent.
	 * 
	 * When the status is set to WAITING the messages are held here until it gets to READY status.
	 *
	 * @type {Array}
	 */
	this.queue = [];

	/**
	 * Domains allowed for this session messages.
	 *
	 * @type {String}
	 */
	this.allowdomain = "*";

	/**
	 * On message callback, receives data and authentication as parameters.
	 *
	 * Called when a normal message is received from another window, onMessage(data, authentication).
	 *
	 * @type {Function}
	 */
	this.onMessage = null;

	/**
	 * On broadcast message callback, receives data and authentication as parameters.
	 *
	 * Called when a broadcast message arrives, onBroadcastMessage(data, authentication).
	 *
	 * @type {Function}
	 */
	this.onBroadcastMessage = null;

	/**
	 * On ready callback, called when the session enters the CLOSED state.
	 *
	 * This callback does not receive any parameter.
	 *
	 * @type {Function}
	 */
	this.onClosed = null;
	
	/**
	 * On ready callback, called when the session enters the READY state.
	 *
	 * This callback does not receive any parameter.
	 *
	 * @type {Function}
	 */
	this.onReady = null;
}

/**
 * WAITING status means that the window is open but the ready message was not received.
 *
 * While the window is waiting all messages are stored in a queue waiting for a ready message.
 *
 * @static
 * @attribute {Number}
 */
WindowSession.WAITING = 101;

/**
 * READY status means that the window is ready to receive data.
 *
 * The message gets READY when it receives an ready message from the peer window.
 *
 * Messages sent before reaching READY state are queued and send only after the session reaches this state.
 *
 * @static
 * @attribute {Number}
 */
WindowSession.READY = 102;

/**
 * CLOSED status means that the other window is not available.
 *
 * @static
 * @attribute {Number}
 */
WindowSession.CLOSED = 103;

/**
 * Set the status of the window.
 *
 * Ensures the correct order of the status being set.
 *
 * @param {Number} status
 */
WindowSession.prototype.setStatus = function(status)
{
	if(status <= this.status)
	{
		console.warn("TabTalk: Invalid status cannot go from " + this.status + " to " + status + ".");
		return;
	}

	this.status = status;

	if(this.status === WindowSession.READY)
	{
		if(this.queue.length > 0)
		{
			console.warn("TabTalk: Sending queued messages.", this.queue);
		}

		//Send all queued waiting message
		for(var i = 0; i < this.queue.length; i++)
		{
			var message = this.queue[i];
			message.destinationUUID = this.uuid;
			this.send(message);
		}

		this.queue = [];

		if(this.onReady !== null)
		{
			this.onReady();
		}
	}
	else if(this.status === WindowSession.CLOSED)
	{
		if(this.onClose != null)
		{
			this.onClose();
		}
	}
};

/**
 * Post data to another window, windows references are stored using their url for later usage.
 *
 * If the window is unknonwn the opener window is used if available.
 * 
 * @param {String} action Name of the action of this message.
 * @param {Object} data Data to be sent (Optional).
 * @param {String} authentication Authentication information (Optional).
 */
WindowSession.prototype.send = function(message)
{
	if(this.status === WindowSession.CLOSED)
	{
		console.warn("TabTalk: Session in is closed state, its not possible to send data.", this, message);
		return;
	}

	if(this.window !== null)
	{
		this.window.postMessage(message, this.allowdomain);
		console.log("TabTalk: Sent message.", message);
	}
	else if(this.gateway !== null)
	{
		console.log("TabTalk: Message passed to gateway.", message);
		this.gateway.send(message);
	}
	else
	{
		console.warn("TabTalk: Session has no window attached.");
		return;
	}
};

/**
 * Send a message to another window, if the session is in WAITING status the message will be queued to be send later.
 * 
 * @param {Object} data Data to be sent (Optional).
 * @param {String} authentication Authentication information (Optional).
 */
WindowSession.prototype.sendMessage = function(data, authentication)
{
	var message = new WindowMessage(this.counter++, WindowMessage.MESSAGE, this.manager.type, this.manager.uuid, this.type, this.uuid, data, authentication);

	if(this.status === WindowSession.WAITING)
	{
		this.queue.push(message);
		console.log("TabTalk: Still on waiting status message was queued.", message);
	}
	else
	{
		this.send(message);
	}
}

/**
 * Close this session, send a close message and is possible close the window.
 * 
 * @param {Boolean} closeWindow If set true the session window will be closed (if possible).
 */
WindowSession.prototype.close = function(closeWindow)
{
	var message = new WindowMessage(this.counter++, WindowMessage.CLOSED, this.manager.type, this.manager.uuid, this.type, this.uuid);

	this.send(message);
	
	if(closeWindow === true && this.window !== null)
	{
		this.window.close();
	}
};

/**
 * Send a message to indicate that this session is READY.
 *
 * Also sends the session metadata for the remote window.
 */
WindowSession.prototype.acknowledge = function()
{
	if(this.uuid !== null)
	{
		this.send(new WindowMessage(this.counter++, WindowMessage.READY, this.manager.type, this.manager.uuid, this.type, this.uuid));
	}
	else
	{
		this.send(new WindowMessage(this.counter++, WindowMessage.READY, this.manager.type, this.manager.uuid));
	}
};

/**
 * Used to indicate the connection intent to remote windows after a positive lookup response.
 *
 * Works similarly to the ready message.
 */
WindowSession.prototype.connect = function()
{
	this.send(new WindowMessage(this.counter++, WindowMessage.CONNECT, this.manager.type, this.manager.uuid, this.type, this.uuid));
};

/**
 * Wait for this window to be ready.
 *
 * When a window is ready it should send a message containing a action property set to WindowMessage.READY.
 *
 * @param {Function} onReady Callback called when the winow is ready.
 */
WindowSession.prototype.waitReady = function()
{
	var self = this;

	console.log("TabTalk: Waiting for a ready message.");

	var manager = new EventManager();
	manager.add(window, "message", function(event)
	{
		//Ready events can only come from direct messages 
		if(event.source !== self.window && self.window !== null)
		{
			console.log("TabTalk: Event from diferent window.", event);
			return;
		}

		var data = event.data;

		if(data.action === WindowMessage.READY)
		{
			console.log("TabTalk: Received ready message.", data, event);

			self.type = data.originType;
			self.uuid = data.originUUID;
			self.manager.sessions[self.uuid] = self;
			self.acknowledge();
			self.setStatus(WindowSession.READY);

			manager.destroy();
		}
		else
		{
			console.warn("TabTalk: Not a ready message, waiting for ready.", data, event);
		}
	});
	manager.create();
};

export {WindowSession};