providers/core/core.rtcdatachannel.js
/*jslint sloppy:true, node:true */
/*globals Components, ArrayBuffer */
var util = require('../../src/util');
var eventNames = [
'onopen',
'onerror',
'onclose',
'onmessage'
];
var unAttachedChannels = {};
var pendingEvents = {};
var allocateChannel = function (dataChannel) {
var id = util.getId();
unAttachedChannels[id] = dataChannel;
pendingEvents[id] = [];
eventNames.forEach(function(eventName) {
// This listener will be overridden (re-set) after the constructor runs.
var handler = function(event) {
var currentHandler = dataChannel[eventName];
if (currentHandler === handler) {
pendingEvents[id].push(event);
} else if (typeof currentHandler === 'function') {
// If an event somehow runs on this event handler after it has been
// replaced, forward that event to the new event handler.
currentHandler(event);
} else {
throw new Error('No handler for ' + event.type + ' event');
}
};
dataChannel[eventName] = handler;
});
return id;
};
var RTCDataChannelAdapter = function (cap, dispatchEvents, id) {
this.dispatchEvent = dispatchEvents;
if (!unAttachedChannels[id]) {
console.warn('Invalid ID, creating acting on unattached DataChannel');
var Connection = require('./core.rtcpeerconnection').provider,
provider = new Connection();
id = provider.createDataChannel();
provider.close();
}
this.channel = unAttachedChannels[id];
delete unAttachedChannels[id];
// After the constructor returns, and the caller has a chance to register
// event listeners, fire all pending events, and then ensure that all
// subsequent events are handled immediately.
setTimeout(function() {
this.drainPendingEvents(id);
// This function must not be called until after the pending events are
// drained, to ensure that messages are delivered in order.
this.manageEvents(true);
}.bind(this), 0);
};
RTCDataChannelAdapter.prototype.drainPendingEvents = function(id) {
pendingEvents[id].forEach(function(event) {
this['on' + event.type](event);
}.bind(this));
delete pendingEvents[id];
};
// Attach or detach listeners for events against the connection.
RTCDataChannelAdapter.prototype.manageEvents = function (attach) {
eventNames.forEach(function (eventName) {
if (attach) {
this[eventName] = this[eventName].bind(this);
this.channel[eventName] = this[eventName];
} else {
delete this.channel[eventName];
}
}.bind(this));
};
RTCDataChannelAdapter.prototype.getLabel = function (callback) {
callback(this.channel.label);
};
RTCDataChannelAdapter.prototype.getOrdered = function (callback) {
callback(this.channel.ordered);
};
RTCDataChannelAdapter.prototype.getMaxPacketLifeTime = function (callback) {
callback(this.channel.maxPacketLifeTime);
};
RTCDataChannelAdapter.prototype.getMaxRetransmits = function (callback) {
callback(this.channel.maxRetransmits);
};
RTCDataChannelAdapter.prototype.getProtocol = function (callback) {
callback(this.channel.protocol);
};
RTCDataChannelAdapter.prototype.getNegotiated = function (callback) {
callback(this.channel.negotiated);
};
RTCDataChannelAdapter.prototype.getId = function (callback) {
callback(this.channel.id);
};
RTCDataChannelAdapter.prototype.getReadyState = function (callback) {
callback(this.channel.readyState);
};
RTCDataChannelAdapter.prototype.getBufferedAmount = function (callback) {
callback(this.channel.bufferedAmount);
};
RTCDataChannelAdapter.prototype.getBinaryType = function (callback) {
callback(this.channel.binaryType);
};
RTCDataChannelAdapter.prototype.setBinaryType = function (binaryType, callback) {
this.channel.binaryType = binaryType;
callback();
};
RTCDataChannelAdapter.prototype.send = function (text, callback) {
this.channel.send(text);
callback();
};
RTCDataChannelAdapter.prototype.sendBuffer = function (buffer, callback) {
this.channel.send(buffer);
callback();
};
RTCDataChannelAdapter.prototype.close = function (callback) {
if (!this.channel) {
return callback();
}
this.manageEvents(false);
this.channel.close();
callback();
};
RTCDataChannelAdapter.prototype.onopen = function (event) {
this.dispatchEvent('onopen', event.message);
};
RTCDataChannelAdapter.prototype.onerror = function (event) {
this.dispatchEvent('onerror', {
errcode: event.type,
message: event.message
});
};
RTCDataChannelAdapter.prototype.onclose = function (event) {
this.dispatchEvent('onclose', event.message);
};
RTCDataChannelAdapter.prototype.onmessage = function (event) {
if (typeof event.data === 'string') {
this.dispatchEvent('onmessage', {text: event.data});
} else if (this.channel.binaryType === 'arraybuffer' &&
typeof Components !== 'undefined' &&
!(event.data instanceof ArrayBuffer)) {
// In Firefox Addons, incoming array buffers are not always owned by the
// Addon context. The following line clones the object to take ownership.
// See: https://developer.mozilla.org/en-US/docs/Components.utils.cloneInto
var myData = Components.utils.cloneInto(event.data, {});
this.dispatchEvent('onmessage', {buffer: myData});
} else {
this.dispatchEvent('onmessage', {buffer: event.data});
}
};
exports.name = "core.rtcdatachannel";
exports.provider = RTCDataChannelAdapter;
exports.allocate = allocateChannel;