rtc-quickconnect
Version:
Create a WebRTC connection in record time
1,911 lines (1,476 loc) • 397 kB
JavaScript
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.quickconnect = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/* jshint node: true */
/* global location */
'use strict';
var rtc = require('rtc-tools');
var mbus = require('mbus');
var detectPlugin = require('rtc-core/plugin');
var debug = rtc.logger('rtc-quickconnect');
var extend = require('cog/extend');
/**
# rtc-quickconnect
This is a high level helper module designed to help you get up
an running with WebRTC really, really quickly. By using this module you
are trading off some flexibility, so if you need a more flexible
configuration you should drill down into lower level components of the
[rtc.io](http://www.rtc.io) suite. In particular you should check out
[rtc](https://github.com/rtc-io/rtc).
## Example Usage
In the simplest case you simply call quickconnect with a single string
argument which tells quickconnect which server to use for signaling:
<<< examples/simple.js
<<< docs/events.md
<<< docs/examples.md
## Regarding Signalling and a Signalling Server
Signaling is an important part of setting up a WebRTC connection and for
our examples we use our own test instance of the
[rtc-switchboard](https://github.com/rtc-io/rtc-switchboard). For your
testing and development you are more than welcome to use this also, but
just be aware that we use this for our testing so it may go up and down
a little. If you need something more stable, why not consider deploying
an instance of the switchboard yourself - it's pretty easy :)
## Reference
```
quickconnect(signalhost, opts?) => rtc-sigaller instance (+ helpers)
```
### Valid Quick Connect Options
The options provided to the `rtc-quickconnect` module function influence the
behaviour of some of the underlying components used from the rtc.io suite.
Listed below are some of the commonly used options:
- `ns` (default: '')
An optional namespace for your signalling room. While quickconnect
will generate a unique hash for the room, this can be made to be more
unique by providing a namespace. Using a namespace means two demos
that have generated the same hash but use a different namespace will be
in different rooms.
- `room` (default: null) _added 0.6_
Rather than use the internal hash generation
(plus optional namespace) for room name generation, simply use this room
name instead. __NOTE:__ Use of the `room` option takes precendence over
`ns`.
- `debug` (default: false)
Write rtc.io suite debug output to the browser console.
- `expectedLocalStreams` (default: not specified) _added 3.0_
By providing a positive integer value for this option will mean that
the created quickconnect instance will wait until the specified number of
streams have been added to the quickconnect "template" before announcing
to the signaling server.
- `manualJoin` (default: `false`)
Set this value to `true` if you would prefer to call the `join` function
to connecting to the signalling server, rather than having that happen
automatically as soon as quickconnect is ready to.
#### Options for Peer Connection Creation
Options that are passed onto the
[rtc.createConnection](https://github.com/rtc-io/rtc#createconnectionopts-constraints)
function:
- `iceServers`
This provides a list of ice servers that can be used to help negotiate a
connection between peers.
#### Options for P2P negotiation
Under the hood, quickconnect uses the
[rtc/couple](https://github.com/rtc-io/rtc#rtccouple) logic, and the options
passed to quickconnect are also passed onto this function.
**/
module.exports = function(signalhost, opts) {
var hash = typeof location != 'undefined' && location.hash.slice(1);
var signaller = require('rtc-pluggable-signaller')(extend({
signaller: signalhost,
// use the primus endpoint as a fallback in case we are talking to an
// older switchboard instance
endpoints: ['/', '/primus']
}, opts));
var getPeerData = require('./lib/getpeerdata')(signaller.peers);
// init configurable vars
var ns = (opts || {}).ns || '';
var room = (opts || {}).room;
var debugging = (opts || {}).debug;
var allowJoin = !(opts || {}).manualJoin;
var profile = {};
var announced = false;
// initialise iceServers to undefined
// we will not announce until these have been properly initialised
var iceServers;
// collect the local streams
var localStreams = [];
// create the calls map
var calls = signaller.calls = require('./lib/calls')(signaller, opts);
// create the known data channels registry
var channels = {};
// save the plugins passed to the signaller
var plugins = signaller.plugins = (opts || {}).plugins || [];
var plugin = detectPlugin(plugins);
var pluginReady;
// check how many local streams have been expected (default: 0)
var expectedLocalStreams = parseInt((opts || {}).expectedLocalStreams, 10) || 0;
var announceTimer = 0;
var updateTimer = 0;
function checkReadyToAnnounce() {
clearTimeout(announceTimer);
// if we have already announced do nothing!
if (announced) {
return;
}
if (! allowJoin) {
return;
}
// if we have a plugin but it's not initialized we aren't ready
if (plugin && (! pluginReady)) {
return;
}
// if we have no iceServers we aren't ready
if (! iceServers) {
return;
}
// if we are waiting for a set number of streams, then wait until we have
// the required number
if (expectedLocalStreams && localStreams.length < expectedLocalStreams) {
return;
}
// announce ourselves to our new friend
announceTimer = setTimeout(function() {
var data = extend({ room: room }, profile);
// announce and emit the local announce event
signaller.announce(data);
announced = true;
}, 0);
}
function connect(id) {
var data = getPeerData(id);
var pc;
var monitor;
// if the room is not a match, abort
if (data.room !== room) {
return;
}
// end any call to this id so we know we are starting fresh
calls.end(id);
// create a peer connection
// iceServers that have been created using genice taking precendence
pc = rtc.createConnection(
extend({}, opts, { iceServers: iceServers }),
(opts || {}).constraints
);
signaller('peer:connect', data.id, pc, data);
// add this connection to the calls list
calls.create(data.id, pc);
// add the local streams
localStreams.forEach(function(stream) {
pc.addStream(stream);
});
// add the data channels
// do this differently based on whether the connection is a
// master or a slave connection
if (signaller.isMaster(data.id)) {
debug('is master, creating data channels: ', Object.keys(channels));
// create the channels
Object.keys(channels).forEach(function(label) {
gotPeerChannel(pc.createDataChannel(label, channels[label]), pc, data);
});
}
else {
pc.ondatachannel = function(evt) {
var channel = evt && evt.channel;
// if we have no channel, abort
if (! channel) {
return;
}
if (channels[channel.label] !== undefined) {
gotPeerChannel(channel, pc, getPeerData(id));
}
};
}
// couple the connections
debug('coupling ' + signaller.id + ' to ' + data.id);
monitor = rtc.couple(pc, id, signaller, extend({}, opts, {
logger: mbus('pc.' + id, signaller)
}));
signaller('peer:couple', id, pc, data, monitor);
// once active, trigger the peer connect event
monitor.once('connected', calls.start.bind(null, id, pc, data));
monitor.once('closed', calls.end.bind(null, id));
// if we are the master connnection, create the offer
// NOTE: this only really for the sake of politeness, as rtc couple
// implementation handles the slave attempting to create an offer
if (signaller.isMaster(id)) {
monitor.createOffer();
}
}
function getActiveCall(peerId) {
var call = calls.get(peerId);
if (! call) {
throw new Error('No active call for peer: ' + peerId);
}
return call;
}
function gotPeerChannel(channel, pc, data) {
var channelMonitor;
function channelReady() {
var call = calls.get(data.id);
var args = [ data.id, channel, data, pc ];
// decouple the channel.onopen listener
debug('reporting channel "' + channel.label + '" ready, have call: ' + (!!call));
clearInterval(channelMonitor);
channel.onopen = null;
// save the channel
if (call) {
call.channels.set(channel.label, channel);
}
// trigger the %channel.label%:open event
debug('triggering channel:opened events for channel: ' + channel.label);
// emit the plain channel:opened event
signaller.apply(signaller, ['channel:opened'].concat(args));
// emit the channel:opened:%label% eve
signaller.apply(
signaller,
['channel:opened:' + channel.label].concat(args)
);
}
debug('channel ' + channel.label + ' discovered for peer: ' + data.id);
if (channel.readyState === 'open') {
return channelReady();
}
debug('channel not ready, current state = ' + channel.readyState);
channel.onopen = channelReady;
// monitor the channel open (don't trust the channel open event just yet)
channelMonitor = setInterval(function() {
debug('checking channel state, current state = ' + channel.readyState);
if (channel.readyState === 'open') {
channelReady();
}
}, 500);
}
function initPlugin() {
return plugin && plugin.init(opts, function(err) {
if (err) {
return console.error('Could not initialize plugin: ', err);
}
pluginReady = true;
checkReadyToAnnounce();
});
}
function handleLocalAnnounce(data) {
// if we send an announce with an updated room then update our local room name
if (data && typeof data.room != 'undefined') {
room = data.room;
}
}
function handlePeerFilter(id, data) {
// only connect with the peer if we are ready
data.allow = data.allow && (localStreams.length >= expectedLocalStreams);
}
function handlePeerUpdate(data) {
var id = data && data.id;
var activeCall = id && calls.get(id);
// if we have received an update for a peer that has no active calls,
// then pass this onto the announce handler
if (id && (! activeCall)) {
debug('received peer update from peer ' + id + ', no active calls');
signaller.to(id).send('/reconnect');
return connect(id);
}
}
// if the room is not defined, then generate the room name
if (! room) {
// if the hash is not assigned, then create a random hash value
if (typeof location != 'undefined' && (! hash)) {
hash = location.hash = '' + (Math.pow(2, 53) * Math.random());
}
room = ns + '#' + hash;
}
if (debugging) {
rtc.logger.enable.apply(rtc.logger, Array.isArray(debug) ? debugging : ['*']);
}
signaller.on('peer:announce', function(data) {
connect(data.id);
});
signaller.on('peer:update', handlePeerUpdate);
signaller.on('message:reconnect', function(sender) {
connect(sender.id);
});
/**
### Quickconnect Broadcast and Data Channel Helper Functions
The following are functions that are patched into the `rtc-signaller`
instance that make working with and creating functional WebRTC applications
a lot simpler.
**/
/**
#### addStream
```
addStream(stream:MediaStream) => qc
```
Add the stream to active calls and also save the stream so that it
can be added to future calls.
**/
signaller.broadcast = signaller.addStream = function(stream) {
localStreams.push(stream);
// if we have any active calls, then add the stream
calls.values().forEach(function(data) {
data.pc.addStream(stream);
});
checkReadyToAnnounce();
return signaller;
};
/**
#### endCalls()
The `endCalls` function terminates all the active calls that have been
created in this quickconnect instance. Calling `endCalls` does not
kill the connection with the signalling server.
**/
signaller.endCalls = function() {
calls.keys().forEach(calls.end);
};
/**
#### close()
The `close` function provides a convenient way of closing all associated
peer connections. This function simply uses the `endCalls` function and
the underlying `leave` function of the signaller to do a "full cleanup"
of all connections.
**/
signaller.close = function() {
signaller.endCalls();
signaller.leave();
};
/**
#### createDataChannel(label, config)
Request that a data channel with the specified `label` is created on
the peer connection. When the data channel is open and available, an
event will be triggered using the label of the data channel.
For example, if a new data channel was requested using the following
call:
```js
var qc = quickconnect('https://switchboard.rtc.io/').createDataChannel('test');
```
Then when the data channel is ready for use, a `test:open` event would
be emitted by `qc`.
**/
signaller.createDataChannel = function(label, opts) {
// create a channel on all existing calls
calls.keys().forEach(function(peerId) {
var call = calls.get(peerId);
var dc;
// if we are the master connection, create the data channel
if (call && call.pc && signaller.isMaster(peerId)) {
dc = call.pc.createDataChannel(label, opts);
gotPeerChannel(dc, call.pc, getPeerData(peerId));
}
});
// save the data channel opts in the local channels dictionary
channels[label] = opts || null;
return signaller;
};
/**
#### join()
The `join` function is used when `manualJoin` is set to true when creating
a quickconnect instance. Call the `join` function once you are ready to
join the signalling server and initiate connections with other people.
**/
signaller.join = function() {
allowJoin = true;
checkReadyToAnnounce();
};
/**
#### `get(name)`
The `get` function returns the property value for the specified property name.
**/
signaller.get = function(name) {
return profile[name];
};
/**
#### `getLocalStreams()`
Return a copy of the local streams that have currently been configured
**/
signaller.getLocalStreams = function() {
return [].concat(localStreams);
};
/**
#### reactive()
Flag that this session will be a reactive connection.
**/
signaller.reactive = function() {
// add the reactive flag
opts = opts || {};
opts.reactive = true;
// chain
return signaller;
};
/**
#### removeStream
```
removeStream(stream:MediaStream)
```
Remove the specified stream from both the local streams that are to
be connected to new peers, and also from any active calls.
**/
signaller.removeStream = function(stream) {
var localIndex = localStreams.indexOf(stream);
// remove the stream from any active calls
calls.values().forEach(function(call) {
call.pc.removeStream(stream);
});
// remove the stream from the localStreams array
if (localIndex >= 0) {
localStreams.splice(localIndex, 1);
}
return signaller;
};
/**
#### requestChannel
```
requestChannel(targetId, label, callback)
```
This is a function that can be used to respond to remote peers supplying
a data channel as part of their configuration. As per the `receiveStream`
function this function will either fire the callback immediately if the
channel is already available, or once the channel has been discovered on
the call.
**/
signaller.requestChannel = function(targetId, label, callback) {
var call = getActiveCall(targetId);
var channel = call && call.channels.get(label);
// if we have then channel trigger the callback immediately
if (channel) {
callback(null, channel);
return signaller;
}
// if not, wait for it
signaller.once('channel:opened:' + label, function(id, dc) {
callback(null, dc);
});
return signaller;
};
/**
#### requestStream
```
requestStream(targetId, idx, callback)
```
Used to request a remote stream from a quickconnect instance. If the
stream is already available in the calls remote streams, then the callback
will be triggered immediately, otherwise this function will monitor
`stream:added` events and wait for a match.
In the case that an unknown target is requested, then an exception will
be thrown.
**/
signaller.requestStream = function(targetId, idx, callback) {
var call = getActiveCall(targetId);
var stream;
function waitForStream(peerId) {
if (peerId !== targetId) {
return;
}
// get the stream
stream = call.pc.getRemoteStreams()[idx];
// if we have the stream, then remove the listener and trigger the cb
if (stream) {
signaller.removeListener('stream:added', waitForStream);
callback(null, stream);
}
}
// look for the stream in the remote streams of the call
stream = call.pc.getRemoteStreams()[idx];
// if we found the stream then trigger the callback
if (stream) {
callback(null, stream);
return signaller;
}
// otherwise wait for the stream
signaller.on('stream:added', waitForStream);
return signaller;
};
/**
#### profile(data)
Update the profile data with the attached information, so when
the signaller announces it includes this data in addition to any
room and id information.
**/
signaller.profile = function(data) {
extend(profile, data);
// if we have already announced, then reannounce our profile to provide
// others a `peer:update` event
if (announced) {
clearTimeout(updateTimer);
updateTimer = setTimeout(function() {
signaller.announce(profile);
}, (opts || {}).updateDelay || 1000);
}
return signaller;
};
/**
#### waitForCall
```
waitForCall(targetId, callback)
```
Wait for a call from the specified targetId. If the call is already
active the callback will be fired immediately, otherwise we will wait
for a `call:started` event that matches the requested `targetId`
**/
signaller.waitForCall = function(targetId, callback) {
var call = calls.get(targetId);
if (call && call.active) {
callback(null, call.pc);
return signaller;
}
signaller.on('call:started', function handleNewCall(id) {
if (id === targetId) {
signaller.removeListener('call:started', handleNewCall);
callback(null, calls.get(id).pc);
}
});
};
// if we have an expected number of local streams, then use a filter to
// check if we should respond
if (expectedLocalStreams) {
signaller.on('peer:filter', handlePeerFilter);
}
// respond to local announce messages
signaller.on('local:announce', handleLocalAnnounce);
// handle ping messages
signaller.on('message:ping', calls.ping);
// use genice to find our iceServers
require('rtc-core/genice')(opts, function(err, servers) {
if (err) {
return console.error('could not find iceServers: ', err);
}
iceServers = servers;
checkReadyToAnnounce();
});
// if we plugin is active, then initialize it
if (plugin) {
initPlugin();
}
// pass the signaller on
return signaller;
};
},{"./lib/calls":2,"./lib/getpeerdata":3,"cog/extend":6,"mbus":12,"rtc-core/genice":14,"rtc-core/plugin":16,"rtc-pluggable-signaller":17,"rtc-tools":50}],2:[function(require,module,exports){
(function (process){
var rtc = require('rtc-tools');
var debug = rtc.logger('rtc-quickconnect');
var cleanup = require('rtc-tools/cleanup');
var getable = require('cog/getable');
module.exports = function(signaller, opts) {
var calls = getable({});
var getPeerData = require('./getpeerdata')(signaller.peers);
var heartbeat;
function create(id, pc) {
calls.set(id, {
active: false,
pc: pc,
channels: getable({}),
streams: [],
lastping: Date.now()
});
}
function createStreamAddHandler(id) {
return function(evt) {
debug('peer ' + id + ' added stream');
updateRemoteStreams(id);
receiveRemoteStream(id)(evt.stream);
};
}
function createStreamRemoveHandler(id) {
return function(evt) {
debug('peer ' + id + ' removed stream');
updateRemoteStreams(id);
signaller('stream:removed', id, evt.stream);
};
}
function end(id) {
var call = calls.get(id);
// if we have no data, then do nothing
if (! call) {
return;
}
// if we have no data, then return
call.channels.keys().forEach(function(label) {
var channel = call.channels.get(label);
var args = [id, channel, label];
// emit the plain channel:closed event
signaller.apply(signaller, ['channel:closed'].concat(args));
// emit the labelled version of the event
signaller.apply(signaller, ['channel:closed:' + label].concat(args));
// decouple the events
channel.onopen = null;
});
// trigger stream:removed events for each of the remotestreams in the pc
call.streams.forEach(function(stream) {
signaller('stream:removed', id, stream);
});
// delete the call data
calls.delete(id);
// if we have no more calls, disable the heartbeat
if (calls.keys().length === 0) {
resetHeartbeat();
}
// trigger the call:ended event
signaller('call:ended', id, call.pc);
// ensure the peer connection is properly cleaned up
cleanup(call.pc);
}
function ping(sender) {
var call = calls.get(sender && sender.id);
// set the last ping for the data
if (call) {
call.lastping = Date.now();
}
}
function receiveRemoteStream(id) {
return function(stream) {
signaller('stream:added', id, stream, getPeerData(id));
};
}
function resetHeartbeat() {
clearInterval(heartbeat);
heartbeat = 0;
}
function start(id, pc, data) {
var call = calls.get(id);
var streams = [].concat(pc.getRemoteStreams());
// flag the call as active
call.active = true;
call.streams = [].concat(pc.getRemoteStreams());
pc.onaddstream = createStreamAddHandler(id);
pc.onremovestream = createStreamRemoveHandler(id);
debug(signaller.id + ' - ' + id + ' call start: ' + streams.length + ' streams');
signaller('call:started', id, pc, data);
// configure the heartbeat timer
heartbeat = heartbeat || require('./heartbeat')(signaller, calls, opts);
// examine the existing remote streams after a short delay
process.nextTick(function() {
// iterate through any remote streams
streams.forEach(receiveRemoteStream(id));
});
}
function updateRemoteStreams(id) {
var call = calls.get(id);
if (call && call.pc) {
call.streams = [].concat(call.pc.getRemoteStreams());
}
}
calls.create = create;
calls.end = end;
calls.ping = ping;
calls.start = start;
return calls;
};
}).call(this,require('_process'))
},{"./getpeerdata":3,"./heartbeat":4,"_process":11,"cog/getable":7,"rtc-tools":50,"rtc-tools/cleanup":46}],3:[function(require,module,exports){
module.exports = function(peers) {
return function(id) {
var peer = peers.get(id);
return peer && peer.data;
};
};
},{}],4:[function(require,module,exports){
module.exports = function(signaller, calls, opts) {
var heartbeat = (opts || {}).heartbeat || 2500;
var heartbeatTimer = 0;
function send() {
var tickInactive = (Date.now() - (heartbeat * 4));
// iterate through our established calls
calls.keys().forEach(function(id) {
var call = calls.get(id);
// if the call ping is too old, end the call
if (call.lastping < tickInactive) {
return calls.end(id);
}
// send a ping message
signaller.to(id).send('/ping');
});
}
if (! heartbeat) {
return;
}
return setInterval(send, heartbeat);
};
},{}],5:[function(require,module,exports){
/* jshint node: true */
'use strict';
/**
## cog/defaults
```js
var defaults = require('cog/defaults');
```
### defaults(target, *)
Shallow copy object properties from the supplied source objects (*) into
the target object, returning the target object once completed. Do not,
however, overwrite existing keys with new values:
```js
defaults({ a: 1, b: 2 }, { c: 3 }, { d: 4 }, { b: 5 }));
```
See an example on [requirebin](http://requirebin.com/?gist=6079475).
**/
module.exports = function(target) {
// ensure we have a target
target = target || {};
// iterate through the sources and copy to the target
[].slice.call(arguments, 1).forEach(function(source) {
if (! source) {
return;
}
for (var prop in source) {
if (target[prop] === void 0) {
target[prop] = source[prop];
}
}
});
return target;
};
},{}],6:[function(require,module,exports){
/* jshint node: true */
'use strict';
/**
## cog/extend
```js
var extend = require('cog/extend');
```
### extend(target, *)
Shallow copy object properties from the supplied source objects (*) into
the target object, returning the target object once completed:
```js
extend({ a: 1, b: 2 }, { c: 3 }, { d: 4 }, { b: 5 }));
```
See an example on [requirebin](http://requirebin.com/?gist=6079475).
**/
module.exports = function(target) {
[].slice.call(arguments, 1).forEach(function(source) {
if (! source) {
return;
}
for (var prop in source) {
target[prop] = source[prop];
}
});
return target;
};
},{}],7:[function(require,module,exports){
/**
## cog/getable
Take an object and provide a wrapper that allows you to `get` and
`set` values on that object.
**/
module.exports = function(target) {
function get(key) {
return target[key];
}
function set(key, value) {
target[key] = value;
}
function remove(key) {
return delete target[key];
}
function keys() {
return Object.keys(target);
};
function values() {
return Object.keys(target).map(function(key) {
return target[key];
});
};
if (typeof target != 'object') {
return target;
}
return {
get: get,
set: set,
remove: remove,
delete: remove,
keys: keys,
values: values
};
};
},{}],8:[function(require,module,exports){
/* jshint node: true */
'use strict';
/**
## cog/jsonparse
```js
var jsonparse = require('cog/jsonparse');
```
### jsonparse(input)
This function will attempt to automatically detect stringified JSON, and
when detected will parse into JSON objects. The function looks for strings
that look and smell like stringified JSON, and if found attempts to
`JSON.parse` the input into a valid object.
**/
module.exports = function(input) {
var isString = typeof input == 'string' || (input instanceof String);
var reNumeric = /^\-?\d+\.?\d*$/;
var shouldParse ;
var firstChar;
var lastChar;
if ((! isString) || input.length < 2) {
if (isString && reNumeric.test(input)) {
return parseFloat(input);
}
return input;
}
// check for true or false
if (input === 'true' || input === 'false') {
return input === 'true';
}
// check for null
if (input === 'null') {
return null;
}
// get the first and last characters
firstChar = input.charAt(0);
lastChar = input.charAt(input.length - 1);
// determine whether we should JSON.parse the input
shouldParse =
(firstChar == '{' && lastChar == '}') ||
(firstChar == '[' && lastChar == ']') ||
(firstChar == '"' && lastChar == '"');
if (shouldParse) {
try {
return JSON.parse(input);
}
catch (e) {
// apparently it wasn't valid json, carry on with regular processing
}
}
return reNumeric.test(input) ? parseFloat(input) : input;
};
},{}],9:[function(require,module,exports){
/* jshint node: true */
'use strict';
/**
## cog/logger
```js
var logger = require('cog/logger');
```
Simple browser logging offering similar functionality to the
[debug](https://github.com/visionmedia/debug) module.
### Usage
Create your self a new logging instance and give it a name:
```js
var debug = logger('phil');
```
Now do some debugging:
```js
debug('hello');
```
At this stage, no log output will be generated because your logger is
currently disabled. Enable it:
```js
logger.enable('phil');
```
Now do some more logger:
```js
debug('Oh this is so much nicer :)');
// --> phil: Oh this is some much nicer :)
```
### Reference
**/
var active = [];
var unleashListeners = [];
var targets = [ console ];
/**
#### logger(name)
Create a new logging instance.
**/
var logger = module.exports = function(name) {
// initial enabled check
var enabled = checkActive();
function checkActive() {
return enabled = active.indexOf('*') >= 0 || active.indexOf(name) >= 0;
}
// register the check active with the listeners array
unleashListeners[unleashListeners.length] = checkActive;
// return the actual logging function
return function() {
var args = [].slice.call(arguments);
// if we have a string message
if (typeof args[0] == 'string' || (args[0] instanceof String)) {
args[0] = name + ': ' + args[0];
}
// if not enabled, bail
if (! enabled) {
return;
}
// log
targets.forEach(function(target) {
target.log.apply(target, args);
});
};
};
/**
#### logger.reset()
Reset logging (remove the default console logger, flag all loggers as
inactive, etc, etc.
**/
logger.reset = function() {
// reset targets and active states
targets = [];
active = [];
return logger.enable();
};
/**
#### logger.to(target)
Add a logging target. The logger must have a `log` method attached.
**/
logger.to = function(target) {
targets = targets.concat(target || []);
return logger;
};
/**
#### logger.enable(names*)
Enable logging via the named logging instances. To enable logging via all
instances, you can pass a wildcard:
```js
logger.enable('*');
```
__TODO:__ wildcard enablers
**/
logger.enable = function() {
// update the active
active = active.concat([].slice.call(arguments));
// trigger the unleash listeners
unleashListeners.forEach(function(listener) {
listener();
});
return logger;
};
},{}],10:[function(require,module,exports){
/* jshint node: true */
'use strict';
/**
## cog/throttle
```js
var throttle = require('cog/throttle');
```
### throttle(fn, delay, opts)
A cherry-pickable throttle function. Used to throttle `fn` to ensure
that it can be called at most once every `delay` milliseconds. Will
fire first event immediately, ensuring the next event fired will occur
at least `delay` milliseconds after the first, and so on.
**/
module.exports = function(fn, delay, opts) {
var lastExec = (opts || {}).leading !== false ? 0 : Date.now();
var trailing = (opts || {}).trailing;
var timer;
var queuedArgs;
var queuedScope;
// trailing defaults to true
trailing = trailing || trailing === undefined;
function invokeDefered() {
fn.apply(queuedScope, queuedArgs || []);
lastExec = Date.now();
}
return function() {
var tick = Date.now();
var elapsed = tick - lastExec;
// always clear the defered timer
clearTimeout(timer);
if (elapsed < delay) {
queuedArgs = [].slice.call(arguments, 0);
queuedScope = this;
return trailing && (timer = setTimeout(invokeDefered, delay - elapsed));
}
// call the function
lastExec = tick;
fn.apply(this, arguments);
};
};
},{}],11:[function(require,module,exports){
// shim for using process in browser
var process = module.exports = {};
var queue = [];
var draining = false;
var currentQueue;
var queueIndex = -1;
function cleanUpNextTick() {
draining = false;
if (currentQueue.length) {
queue = currentQueue.concat(queue);
} else {
queueIndex = -1;
}
if (queue.length) {
drainQueue();
}
}
function drainQueue() {
if (draining) {
return;
}
var timeout = setTimeout(cleanUpNextTick);
draining = true;
var len = queue.length;
while(len) {
currentQueue = queue;
queue = [];
while (++queueIndex < len) {
currentQueue[queueIndex].run();
}
queueIndex = -1;
len = queue.length;
}
currentQueue = null;
draining = false;
clearTimeout(timeout);
}
process.nextTick = function (fun) {
var args = new Array(arguments.length - 1);
if (arguments.length > 1) {
for (var i = 1; i < arguments.length; i++) {
args[i - 1] = arguments[i];
}
}
queue.push(new Item(fun, args));
if (queue.length === 1 && !draining) {
setTimeout(drainQueue, 0);
}
};
// v8 likes predictible objects
function Item(fun, array) {
this.fun = fun;
this.array = array;
}
Item.prototype.run = function () {
this.fun.apply(null, this.array);
};
process.title = 'browser';
process.browser = true;
process.env = {};
process.argv = [];
process.version = ''; // empty string to avoid regexp issues
process.versions = {};
function noop() {}
process.on = noop;
process.addListener = noop;
process.once = noop;
process.off = noop;
process.removeListener = noop;
process.removeAllListeners = noop;
process.emit = noop;
process.binding = function (name) {
throw new Error('process.binding is not supported');
};
// TODO(shtylman)
process.cwd = function () { return '/' };
process.chdir = function (dir) {
throw new Error('process.chdir is not supported');
};
process.umask = function() { return 0; };
},{}],12:[function(require,module,exports){
var reDelim = /[\.\:]/;
/**
# mbus
If Node's EventEmitter and Eve were to have a child, it might look something like this.
No wildcard support at this stage though...
## Example Usage
<<< docs/usage.md
## Reference
### `mbus(namespace?, parent?, scope?)`
Create a new message bus with `namespace` inheriting from the `parent`
mbus instance. If events from this message bus should be triggered with
a specific `this` scope, then specify it using the `scope` argument.
**/
var createBus = module.exports = function(namespace, parent, scope) {
var registry = {};
var feeds = [];
function bus(name) {
var args = [].slice.call(arguments, 1);
var delimited = normalize(name);
var handlers = registry[delimited] || [];
var results;
// send through the feeds
feeds.forEach(function(feed) {
feed({ name: delimited, args: args });
});
// run the registered handlers
results = [].concat(handlers).map(function(handler) {
return handler.apply(scope || this, args);
});
// run the parent handlers
if (bus.parent) {
results = results.concat(
bus.parent.apply(
scope || this,
[(namespace ? namespace + '.' : '') + delimited].concat(args)
)
);
}
return results;
}
/**
### `mbus#clear()`
Reset the handler registry, which essential deregisters all event listeners.
_Alias:_ `removeAllListeners`
**/
function clear(name) {
// if we have a name, reset handlers for that handler
if (name) {
delete registry[normalize(name)];
}
// otherwise, reset the entire handler registry
else {
registry = {};
}
}
/**
### `mbus#feed(handler)`
Attach a handler function that will see all events that are sent through
this bus in an "object stream" format that matches the following format:
```
{ name: 'event.name', args: [ 'event', 'args' ] }
```
The feed function returns a function that can be called to stop the feed
sending data.
**/
function feed(handler) {
function stop() {
feeds.splice(feeds.indexOf(handler), 1);
}
feeds.push(handler);
return stop;
}
function normalize(name) {
return (Array.isArray(name) ? name : name.split(reDelim)).join('.');
}
/**
### `mbus#off(name, handler)`
Deregister an event handler.
**/
function off(name, handler) {
var handlers = registry[normalize(name)] || [];
var idx = handlers ? handlers.indexOf(handler._actual || handler) : -1;
if (idx >= 0) {
handlers.splice(idx, 1);
}
}
/**
### `mbus#on(name, handler)`
Register an event handler for the event `name`.
**/
function on(name, handler) {
var handlers;
name = normalize(name);
handlers = registry[name];
if (handlers) {
handlers.push(handler);
}
else {
registry[name] = [ handler ];
}
return bus;
}
/**
### `mbus#once(name, handler)`
Register an event handler for the event `name` that will only
trigger once (i.e. the handler will be deregistered immediately after
being triggered the first time).
**/
function once(name, handler) {
function handleEvent() {
var result = handler.apply(this, arguments);
bus.off(name, handleEvent);
return result;
}
handler._actual = handleEvent;
return on(name, handleEvent);
}
if (typeof namespace == 'function') {
parent = namespace;
namespace = '';
}
namespace = normalize(namespace || '');
bus.clear = bus.removeAllListeners = clear;
bus.feed = feed;
bus.on = bus.addListener = on;
bus.once = once;
bus.off = bus.removeListener = off;
bus.parent = parent || (namespace && createBus());
return bus;
};
},{}],13:[function(require,module,exports){
/* jshint node: true */
/* global window: false */
/* global navigator: false */
'use strict';
var browser = require('detect-browser');
/**
### `rtc-core/detect`
A browser detection helper for accessing prefix-free versions of the various
WebRTC types.
### Example Usage
If you wanted to get the native `RTCPeerConnection` prototype in any browser
you could do the following:
```js
var detect = require('rtc-core/detect'); // also available in rtc/detect
var RTCPeerConnection = detect('RTCPeerConnection');
```
This would provide whatever the browser prefixed version of the
RTCPeerConnection is available (`webkitRTCPeerConnection`,
`mozRTCPeerConnection`, etc).
**/
var detect = module.exports = function(target, opts) {
var attach = (opts || {}).attach;
var prefixIdx;
var prefix;
var testName;
var hostObject = this || (typeof window != 'undefined' ? window : undefined);
// initialise to default prefixes
// (reverse order as we use a decrementing for loop)
var prefixes = ((opts || {}).prefixes || ['ms', 'o', 'moz', 'webkit']).concat('');
// if we have no host object, then abort
if (! hostObject) {
return;
}
// iterate through the prefixes and return the class if found in global
for (prefixIdx = prefixes.length; prefixIdx--; ) {
prefix = prefixes[prefixIdx];
// construct the test class name
// if we have a prefix ensure the target has an uppercase first character
// such that a test for getUserMedia would result in a
// search for webkitGetUserMedia
testName = prefix + (prefix ?
target.charAt(0).toUpperCase() + target.slice(1) :
target);
if (typeof hostObject[testName] != 'undefined') {
// update the last used prefix
detect.browser = detect.browser || prefix.toLowerCase();
if (attach) {
hostObject[target] = hostObject[testName];
}
return hostObject[testName];
}
}
};
// detect mozilla (yes, this feels dirty)
detect.moz = typeof navigator != 'undefined' && !!navigator.mozGetUserMedia;
// set the browser and browser version
detect.browser = browser.name;
detect.browserVersion = detect.version = browser.version;
},{"detect-browser":15}],14:[function(require,module,exports){
/**
### `rtc-core/genice`
Respond appropriately to options that are passed to packages like
`rtc-quickconnect` and trigger a `callback` (error first) with iceServer
values.
The function looks for either of the following keys in the options, in
the following order or precedence:
1. `ice` - this can either be an array of ice server values or a generator
function (in the same format as this function). If this key contains a
value then any servers specified in the `iceServers` key (2) will be
ignored.
2. `iceServers` - an array of ice server values.
**/
module.exports = function(opts, callback) {
var ice = (opts || {}).ice;
var iceServers = (opts || {}).iceServers;
if (typeof ice == 'function') {
return ice(opts, callback);
}
else if (Array.isArray(ice)) {
return callback(null, [].concat(ice));
}
callback(null, [].concat(iceServers || []));
};
},{}],15:[function(require,module,exports){
var browsers = [
[ 'chrome', /Chrom(?:e|ium)\/([0-9\.]+)(:?\s|$)/ ],
[ 'firefox', /Firefox\/([0-9\.]+)(?:\s|$)/ ],
[ 'opera', /Opera\/([0-9\.]+)(?:\s|$)/ ],
[ 'ie', /Trident\/7\.0.*rv\:([0-9\.]+)\).*Gecko$/ ],
[ 'ie', /MSIE\s([0-9\.]+);.*Trident\/[4-7].0/ ],
[ 'ie', /MSIE\s(7\.0)/ ],
[ 'bb10', /BB10;\sTouch.*Version\/([0-9\.]+)/ ],
[ 'android', /Android\s([0-9\.]+)/ ],
[ 'ios', /iPad\;\sCPU\sOS\s([0-9\._]+)/ ],
[ 'ios', /iPhone\;\sCPU\siPhone\sOS\s([0-9\._]+)/ ],
[ 'safari', /Safari\/([0-9\._]+)/ ]
];
var match = browsers.map(match).filter(isMatch)[0];
var parts = match && match[3].split(/[._]/).slice(0,3);
while (parts && parts.length < 3) {
parts.push('0');
}
// set the name and version
exports.name = match && match[0];
exports.version = parts && parts.join('.');
function match(pair) {
return pair.concat(pair[1].exec(navigator.userAgent));
}
function isMatch(pair) {
return !!pair[2];
}
},{}],16:[function(require,module,exports){
var detect = require('./detect');
var requiredFunctions = [
'init'
];
function isSupported(plugin) {
return plugin && typeof plugin.supported == 'function' && plugin.supported(detect);
}
function isValid(plugin) {
var supportedFunctions = requiredFunctions.filter(function(fn) {
return typeof plugin[fn] == 'function';
});
return supportedFunctions.length === requiredFunctions.length;
}
module.exports = function(plugins) {
return [].concat(plugins || []).filter(isSupported).filter(isValid)[0];
}
},{"./detect":13}],17:[function(require,module,exports){
/**
# rtc-pluggable-signaller
By using `rtc-pluggable-signaller` in your code, you provide the ability
for your package to customize which signalling client it uses (and
thus have significant control) over how signalling operates in your
environment.
## How it Works
The pluggable signaller looks in the provided `opts` for a `signaller`
attribute. If the value of this attribute is a string, then it is
assumed that you wish to use the default
[`rtc-signaller`](https://github.com/rtc-io/rtc-signaller) in your
package. If, however, it is not a string value then it will be passed
straight back as the signaller (assuming that you have provided an
object that is compliant with the rtc.io signalling API).
**/
module.exports = function(opts) {
var signaller = (opts || {}).signaller;
var messenger = (opts || {}).messenger || require('rtc-switchboard-messenger');
if (typeof signaller == 'string' || (signaller instanceof String)) {
return require('rtc-signaller')(messenger(signaller, opts), opts);
}
return signaller;
};
},{"rtc-signaller":18,"rtc-switchboard-messenger":37}],18:[function(require,module,exports){
/* jshint node: true */
'use strict';
var detect = require('rtc-core/detect');
var extend = require('cog/extend');
var mbus = require('mbus');
var getable = require('cog/getable');
var uuid = require('cuid');
var pull = require('pull-stream');
var pushable = require('pull-pushable');
var prepare = require('rtc-signal/prepare');
var createQueue = require('pull-pushable');
// ready state constants
var RS_DISCONNECTED = 0;
var RS_CONNECTING = 1;
var RS_CONNECTED = 2;
// initialise signaller metadata so we don't have to include the package.json
// TODO: make this checkable with some kind of prepublish script
var metadata = {
version: '6.2.1'
};
/**
# rtc-signaller
The `rtc-signaller` module provides a transportless signalling
mechanism for WebRTC.
## Purpose
<<< docs/purpose.md
## Getting Started
While the signaller is capable of communicating by a number of different
messengers (i.e. anything that can send and receive messages over a wire)
it comes with support for understanding how to connect to an
[rtc-switchboard](https://github.com/rtc-io/rtc-switchboard) out of the box.
The following code sample demonstrates how:
<<< examples/getting-started.js
<<< docs/events.md
<<< docs/signalflow-diagrams.md
<<< docs/identifying-participants.md
## Reference
The `rtc-signaller` module is designed to be used primarily in a functional
way and when called it creates a new signaller that will enable
you to communicate with other peers via your messaging network.
```js
// create a signaller from something that knows how to send messages
var signaller = require('rtc-signaller')(messenger);
```
As demonstrated in the getting started guide, you can also pass through
a string value instead of a messenger instance if you simply want to
connect to an existing `rtc-switchboard` instance.
**/
module.exports = function(messenger, opts) {
var autoconnect = (opts || {}).autoconnect;
var reconnect = (opts || {}).reconnect;
var queue = createQueue();
var connectionCount = 0;
// create the signaller
var signaller = require('rtc-signal/signaller')(opts, bufferMessage);
var announced = false;
var announceTimer = 0;
var readyState = RS_DISCONNECTED;
function bufferMessage(message) {
queue.push(message);
// if we are not connected (and should autoconnect), then attempt connection
if (readyState === RS_DISCONNECTED && (autoconnect === undefined || autoconnect)) {
connect();
}
}
function handleDisconnect() {
if (reconnect === undefined || reconnect) {
setTimeout(connect, 50);
}
}
/**
### `signaller.connect()`
Manually connect the signaller using the supplied messenger.
__NOTE:__ This should never have to be called if the default setting
for `autoconnect` is used.
**/
var connect = signaller.connect = function() {
// if we are already connecting then do nothing
if (readyState === RS_CONNECTING) {
return;
}
// initiate the messenger
readyState = RS_CONNECTING;
messenger(function(err, source, sink) {
if (err) {
readyState = RS_DISCONNECTED;
return signaller('error', err);
}
// increment the connection count
connectionCount += 1;
// flag as connected
readyState = RS_CONNECTED;
// pass messages to the processor
pull(
source,
// monitor disconnection
pull.through(null, function() {
queue = createQueue();
readyState = RS_DISCONNECTED;
signaller('disconnected');
}),
pull.drain(signaller._process)
);
// pass the queue to the sink
pull(queue, sink);
// handle disconnection
signaller.removeListener('disconnected', handleDisconnect);
signaller.on('disconnected', handleDisconnect);
// trigger the connected event
signaller('connected');
// if this is a reconnection, then reannounce
if (announced && connectionCount > 1) {
signaller._announce();
}
});
};
/**
### announce(data?)
The `announce` function of the signaller will pass an `/announce` message
through the messenger network. When no additional data is supplied to
this function then only the id of the signaller is sent to all active
members of the messenging network.
#### Joining Rooms
To join a room using an announce call you simply provide the name of the
room you wish to join as part of the data block that you annouce, for
example:
```js
signaller.announce({ room: 'testroom' });
```
Signalling servers (such as
[rtc-switchboard](https://github.com/rtc-io/rtc-switchboard)) will then
place your peer connection into a room with other peers that have also
announced in this room.
Once you have joined a room, the server will only deliver messages that
you `send` to other peers within that room.
#### Providing Additional Announce Data
There may be instances where you wish to send additional data as part of
your announce message in your applica