blob: e50e1aaedb2933fa81afaa20e3675c1a88189d49 [file] [log] [blame]
/**
* Socket implementation that uses flash SocketPool class as a backend.
*
* @author Dave Longley
*
* Copyright (c) 2010-2013 Digital Bazaar, Inc.
*/
(function() {
/* ########## Begin module implementation ########## */
function initModule(forge) {
// define net namespace
var net = forge.net = forge.net || {};
// map of flash ID to socket pool
net.socketPools = {};
/**
* Creates a flash socket pool.
*
* @param options:
* flashId: the dom ID for the flash object element.
* policyPort: the default policy port for sockets, 0 to use the
* flash default.
* policyUrl: the default policy file URL for sockets (if provided
* used instead of a policy port).
* msie: true if the browser is msie, false if not.
*
* @return the created socket pool.
*/
net.createSocketPool = function(options) {
// set default
options.msie = options.msie || false;
// initialize the flash interface
var spId = options.flashId;
var api = document.getElementById(spId);
api.init({marshallExceptions: !options.msie});
// create socket pool entry
var sp = {
// ID of the socket pool
id: spId,
// flash interface
flashApi: api,
// map of socket ID to sockets
sockets: {},
// default policy port
policyPort: options.policyPort || 0,
// default policy URL
policyUrl: options.policyUrl || null
};
net.socketPools[spId] = sp;
// create event handler, subscribe to flash events
if(options.msie === true) {
sp.handler = function(e) {
if(e.id in sp.sockets) {
// get handler function
var f;
switch(e.type) {
case 'connect':
f = 'connected';
break;
case 'close':
f = 'closed';
break;
case 'socketData':
f = 'data';
break;
default:
f = 'error';
break;
}
/* IE calls javascript on the thread of the external object
that triggered the event (in this case flash) ... which will
either run concurrently with other javascript or pre-empt any
running javascript in the middle of its execution (BAD!) ...
calling setTimeout() will schedule the javascript to run on
the javascript thread and solve this EVIL problem. */
setTimeout(function(){sp.sockets[e.id][f](e);}, 0);
}
};
} else {
sp.handler = function(e) {
if(e.id in sp.sockets) {
// get handler function
var f;
switch(e.type) {
case 'connect':
f = 'connected';
break;
case 'close':
f = 'closed';
break;
case 'socketData':
f = 'data';
break;
default:
f = 'error';
break;
}
sp.sockets[e.id][f](e);
}
};
}
var handler = 'forge.net.socketPools[\'' + spId + '\'].handler';
api.subscribe('connect', handler);
api.subscribe('close', handler);
api.subscribe('socketData', handler);
api.subscribe('ioError', handler);
api.subscribe('securityError', handler);
/**
* Destroys a socket pool. The socket pool still needs to be cleaned
* up via net.cleanup().
*/
sp.destroy = function() {
delete net.socketPools[options.flashId];
for(var id in sp.sockets) {
sp.sockets[id].destroy();
}
sp.sockets = {};
api.cleanup();
};
/**
* Creates a new socket.
*
* @param options:
* connected: function(event) called when the socket connects.
* closed: function(event) called when the socket closes.
* data: function(event) called when socket data has arrived,
* it can be read from the socket using receive().
* error: function(event) called when a socket error occurs.
*/
sp.createSocket = function(options) {
// default to empty options
options = options || {};
// create flash socket
var id = api.create();
// create javascript socket wrapper
var socket = {
id: id,
// set handlers
connected: options.connected || function(e){},
closed: options.closed || function(e){},
data: options.data || function(e){},
error: options.error || function(e){}
};
/**
* Destroys this socket.
*/
socket.destroy = function() {
api.destroy(id);
delete sp.sockets[id];
};
/**
* Connects this socket.
*
* @param options:
* host: the host to connect to.
* port: the port to connect to.
* policyPort: the policy port to use (if non-default), 0 to
* use the flash default.
* policyUrl: the policy file URL to use (instead of port).
*/
socket.connect = function(options) {
// give precedence to policy URL over policy port
// if no policy URL and passed port isn't 0, use default port,
// otherwise use 0 for the port
var policyUrl = options.policyUrl || null;
var policyPort = 0;
if(policyUrl === null && options.policyPort !== 0) {
policyPort = options.policyPort || sp.policyPort;
}
api.connect(id, options.host, options.port, policyPort, policyUrl);
};
/**
* Closes this socket.
*/
socket.close = function() {
api.close(id);
socket.closed({
id: socket.id,
type: 'close',
bytesAvailable: 0
});
};
/**
* Determines if the socket is connected or not.
*
* @return true if connected, false if not.
*/
socket.isConnected = function() {
return api.isConnected(id);
};
/**
* Writes bytes to this socket.
*
* @param bytes the bytes (as a string) to write.
*
* @return true on success, false on failure.
*/
socket.send = function(bytes) {
return api.send(id, forge.util.encode64(bytes));
};
/**
* Reads bytes from this socket (non-blocking). Fewer than the number
* of bytes requested may be read if enough bytes are not available.
*
* This method should be called from the data handler if there are
* enough bytes available. To see how many bytes are available, check
* the 'bytesAvailable' property on the event in the data handler or
* call the bytesAvailable() function on the socket. If the browser is
* msie, then the bytesAvailable() function should be used to avoid
* race conditions. Otherwise, using the property on the data handler's
* event may be quicker.
*
* @param count the maximum number of bytes to read.
*
* @return the bytes read (as a string) or null on error.
*/
socket.receive = function(count) {
var rval = api.receive(id, count).rval;
return (rval === null) ? null : forge.util.decode64(rval);
};
/**
* Gets the number of bytes available for receiving on the socket.
*
* @return the number of bytes available for receiving.
*/
socket.bytesAvailable = function() {
return api.getBytesAvailable(id);
};
// store and return socket
sp.sockets[id] = socket;
return socket;
};
return sp;
};
/**
* Destroys a flash socket pool.
*
* @param options:
* flashId: the dom ID for the flash object element.
*/
net.destroySocketPool = function(options) {
if(options.flashId in net.socketPools) {
var sp = net.socketPools[options.flashId];
sp.destroy();
}
};
/**
* Creates a new socket.
*
* @param options:
* flashId: the dom ID for the flash object element.
* connected: function(event) called when the socket connects.
* closed: function(event) called when the socket closes.
* data: function(event) called when socket data has arrived, it
* can be read from the socket using receive().
* error: function(event) called when a socket error occurs.
*
* @return the created socket.
*/
net.createSocket = function(options) {
var socket = null;
if(options.flashId in net.socketPools) {
// get related socket pool
var sp = net.socketPools[options.flashId];
socket = sp.createSocket(options);
}
return socket;
};
} // end module implementation
/* ########## Begin module wrapper ########## */
var name = 'net';
if(typeof define !== 'function') {
// NodeJS -> AMD
if(typeof module === 'object' && module.exports) {
var nodeJS = true;
define = function(ids, factory) {
factory(require, module);
};
} else {
// <script>
if(typeof forge === 'undefined') {
forge = {};
}
return initModule(forge);
}
}
// AMD
var deps;
var defineFunc = function(require, module) {
module.exports = function(forge) {
var mods = deps.map(function(dep) {
return require(dep);
}).concat(initModule);
// handle circular dependencies
forge = forge || {};
forge.defined = forge.defined || {};
if(forge.defined[name]) {
return forge[name];
}
forge.defined[name] = true;
for(var i = 0; i < mods.length; ++i) {
mods[i](forge);
}
return forge[name];
};
};
var tmpDefine = define;
define = function(ids, factory) {
deps = (typeof ids === 'string') ? factory.slice(2) : ids.slice(2);
if(nodeJS) {
delete define;
return tmpDefine.apply(null, Array.prototype.slice.call(arguments, 0));
}
define = tmpDefine;
return define.apply(null, Array.prototype.slice.call(arguments, 0));
};
define(['require', 'module'], function() {
defineFunc.apply(null, Array.prototype.slice.call(arguments, 0));
});
})();