/**
* @class
* @name WindowSocket
* @param {Window} [_window] specify other window to listen
*/
var WindowSocket = function (_window) {
if (!_window) _window = window;
var app = new ApplicationPrototype();
var _uniqIndex = 0;
var _uniqPrefix = new Date().valueOf().toString(36);
var _uniq = function () {
_uniqIndex++;
return 'cb-index' + ':' + _uniqPrefix + ':' + _uniqIndex.toString(36);
};
var _config = {
origins: [],
handler: null,
handlerError: null,
encoders: [],
clients: [],
reservedEvents: [
"message",
"connect",
"disconnect",
"message:raw",
"event"
]
};
var WindowSocketDataEncode = function (arg, client) {
switch (typeof(arg)) {
case "undefined":
return {
type: "undefined"
};
case "number":
return {
type: "number",
value: arg
};
case "bigint":
return {
type: "bigint",
value: arg
};
case "boolean":
return {
type: "boolean",
value: arg
};
case "string":
return {
type: "string",
value: arg
};
case "object":
if (arg === null || !arg) {
return {
type: "null"
};
} else if (Array.isArray(arg)) {
return {
type: "array",
value: arg.map(function (item) {
return WindowSocketDataEncode(item, client);
})
};
} else if (arg instanceof Error) {
return {
type: "error",
value: {
name : arg.name,
stack: arg.stack,
message: arg.message
}
};
} else if (arg instanceof ArrayBuffer) {
return {
type: "arraybuffer",
value: arg
};
} else {
var data = {}, prop;
for (prop in arg) {
data[prop] = WindowSocketDataEncode(arg[prop], client);
}
return {
type: "object",
value: data
};
}
break;
case "function":
var func = function () {
arg.apply({}, arguments);
};
func.__callId = func.__callId || _uniq();
func.__callDestroy = function () {
app.off("message:callback:" + func.__callId);
};
func.__callClient = client;
app.on("message:callback:" + func.__callId, function () {
func.apply(
{},
arguments
);
});
return {
type: "function",
callId: func.__callId
};
default:
console.warn('Unable to encode value');
return null;
}
};
var WindowSocketDataDecode = function (arg, client) {
if (!arg || typeof(arg) !== "object") {
return null;
}
switch (arg.type) {
case "undefined":
return undefined;
case "number":
case "bigint":
case "boolean":
case "string":
case "arraybuffer":
return arg.value;
case "null":
return null;
case "array":
return arg.value.map(function (item) {
return WindowSocketDataDecode(item, client);
});
case "object":
var prop, data = {};
for (prop in arg.value) {
data[prop] = WindowSocketDataDecode(arg.value[prop], client);
}
return data;
case "error":
var err = Error(arg.value.message);
err.name = arg.value.name;
err.stack = arg.value.stack;
return err;
case "function":
var func = function () {
func.__callClient.message({
type: "callback",
callId: arg.callId,
args: Array.prototype.slice.call(
arguments
)
});
};
func.__callClient = client;
func.__callId = arg.callId;
func.__callDestroy = function () {
func.__callClient.message({
type: "callback:destroy",
callId: arg.callId
});
};
return func;
}
};
/**
* @class
* @name WindowSocket.Client
* @param {MessageEvent} event
*/
app.Client = function (event) {
var client = new ApplicationPrototype();
client.bind(
/**
* @method source
* @memberof WindowSocket.Client
* @returns {MessageEventSource}
*/
function source() {
return event.source;
}
);
client.bind(
/**
* @method target
* @memberof WindowSocket.Client
* @returns {WindowProxy}
*/
function target() {
return event.target;
}
);
client.bind(
/**
* @method isSameSource
* @param {MessageEventSource} source
* @memberof WindowSocket.Client
* @returns {boolean}
*/
function isSameSource(source) {
return source === event.source;
}
);
client.bind(
/**
* @method isSameTarget
* @param {WindowProxy} target
* @memberof WindowSocket.Client
* @returns {boolean}
*/
function isSameTarget(target) {
return target === event.target;
}
);
client.bind(
/**
* @method message
* @param {...any[]} args
* @memberof WindowSocket.Client
* @returns {WindowSocket.Client}
*/
function message() {
client.source().postMessage(
WindowSocketDataEncode(
Array.prototype.slice.call(
arguments
),
client
),
"*"
);
return client;
}
);
client.bind(
/**
* @method fire
* @memberof WindowSocket.Client
* @param {string} eventName
* @param {...any} args
* @returns {WindowSocket.Client}
*/
function fire() {
var args = Array.prototype.slice.call(arguments);
client.message(
{
type: "event",
name: args.shift(),
args: args
}
);
return client;
}
);
client.bind(
/**
* @method disconnect
* @memberof WindowSocket.Client
*/
function disconnect() {
_config.clients = _config.clients.filter(
function (item) {
return item !== client;
}
);
}
);
return client;
};
app.bind(
/**
* @method origins
* @param {string} origin
* @returns {WindowSocket}
*/
/**
* @method origins
* @returns {string[]}
*/
function origins(origin) {
if (
typeof(origin) === "string" && origin
) {
_config.origins.push(origin);
return app;
} else {
return _config.origins;
}
}
);
app.bind(
/**
* @method stop
* @memberof WindowSocket
* @returns {WindowSocket}
*/
function stop() {
if (_config.handler) {
_window.removeEventListener("message", _config.handler);
_config.handler = null;
}
if (_config.handlerError) {
_window.removeEventListener("messageerror", _config.handlerError);
_config.handlerError = null;
}
return app;
}
);
app.bind(
/**
* @method start
* @memberof WindowSocket
* @returns {WindowSocket}
*/
function start() {
app.stop();
_config.handler = _window.addEventListener('message', function (event) {
/**
* @event message:raw
* @memberof WindowSocket
* @type {MessageEvent}
*/
app.emit('message:raw', [event]);
});
_config.handlerError = _window.addEventListener('messageerror', function (event) {
/**
* @event message:error
* @memberof WindowSocket
* @type {MessageEvent}
*/
app.emit('message:error', [event]);
});
return app;
}
);
app.on('message:raw', function (event) {
var client = app.validateSource(
event
);
if (
client
) {
var data = WindowSocketDataDecode(event.data, client);
console.log("DecodeData", event.data, data);
if (Array.isArray(data)) {
/**
* @event message
* @memberof WindowSocket
* @type {object}
*/
app.emit('message', data);
}
}
});
app.on("message", function (data) {
if (typeof(data) === "object" && data) {
switch (data.type) {
case "callback":
if (
data.callId
&&
typeof(data.callId) === "string"
&&
Array.isArray(
data.args
)
) {
app.emit(
"message:callback:" + data.callId, data.args
);
} else {
console.warn("Incorrect callback structure", data);
}
break;
case "callback:destroy":
if (
data.callId
&&
typeof(data.callId) === "string"
) {
app.off("message:callback:" + data.callId);
} else {
console.warn("Incorrect callback structure", data);
}
break;
case "event":
if (
typeof(data.name) === "string"
&&
data.name
&&
_config.reservedEvents.indexOf(data.name) === -1
&&
Array.isArray(data.args)
) {
app.emit(data.name, data.args);
} else {
console.warn("Incorrect Event Structure");
}
break;
}
}
});
app.bind(
/**
* @method broadcast
* @param {...any[]} args
* @memberof WindowSocket
* @returns {WindowSocket}
*/
function broadcast() {
var args = Array.prototype.slice.call(
arguments
);
_config.clients.forEach(function (client) {
client.message(args);
});
return app;
}
);
app.bind(
/**
* @method fire
* @param {...any[]} args
* @memberof WindowSocket
* @returns {WindowSocket}
*/
function fire() {
var args = Array.prototype.slice.call(arguments);
_config.clients.forEach(
function (client) {
client.fire.apply(
client,
args
);
}
);
}
);
app.bind(
/**
* Validate Message Event Source
* @method validateSource
* @memberof WindowSocket
* @param {MessageEvent} event
* @returns {WindowSocket.Client}
*/
function validateSource(event) {
// event.origin
// event.source
// event.data
// _config.origins
// _config.encoders
var client = _config.clients.find(
/**
* @param {WindowSocket.Client} client
*/
function (client) {
return !client.isSameSource(event.source);
}
);
if (client) {
return client;
}
client = new app.Client(event);
_config.clients.push(client);
/**
* @event connect
* @memberof WindowSocket
* @param {WindowSocket.Client}
*/
app.emit('connect', [client]);
return client;
}
);
app.bind(
/**
* get current encoders
* @method encoders
* @memberof WindowSocket
* @returns {WindowSocket.Encoder[]}
*/
/**
* update encoders
* @method encoders
* @memberof WindowSocket
* @param {WindowSocket.Encoder[]} encoders
* @returns {WindowSocket}
*/
function encoders(encoders) {
if (Array.isArray(encoders)) {
_config.encoders = encoders;
return app;
}
return _config.encoders;
}
);
app.bind(
/**
* @method clients
* @memberof WindowSocket
* @returns {WindowSocket.Client[]}
*/
function clients() {
return _config.clients.map(function (client) {
return client;
});
}
);
return app;
};
if (typeof(module) === "object" && module) {
module.exports = WindowSocket;
}