You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
288 lines
8.1 KiB
288 lines
8.1 KiB
2 weeks ago
|
/**
|
||
|
* Socket implementation that uses flash SocketPool class as a backend.
|
||
|
*
|
||
|
* @author Dave Longley
|
||
|
*
|
||
|
* Copyright (c) 2010-2013 Digital Bazaar, Inc.
|
||
|
*/
|
||
|
var forge = require('./forge');
|
||
|
require('./util');
|
||
|
|
||
|
// define net namespace
|
||
|
var net = module.exports = 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;
|
||
|
};
|