diff --git a/openvidu-hello-world/web/app.js b/openvidu-hello-world/web/app.js
index a32820a2..4636fc70 100644
--- a/openvidu-hello-world/web/app.js
+++ b/openvidu-hello-world/web/app.js
@@ -1,73 +1,28 @@
-/* OPENVIDU METHODS */
-
var OV;
var session;
function joinSession() {
var sessionId = document.getElementById("sessionId").value;
- var userName = document.getElementById("userName").value;
- // --- 1) Get an OpenVidu object and init a session with a sessionId ---
-
- // Init OpenVidu object
OV = new OpenVidu();
-
- // We will join the video-call "sessionId". As there's no server, this parameter must start with the URL of
- // OpenVidu Server (with secure websocket protocol: "wss://") and must include the OpenVidu secret at the end
session = OV.initSession("wss://" + location.hostname + ":8443/" + sessionId + '?secret=MY_SECRET');
-
- // --- 2) Specify the actions when events take place ---
-
- // On every new Stream received...
session.on('streamCreated', function (event) {
-
- // Subscribe to the Stream to receive it. A video will be appended to element with id 'subscriber'
var subscriber = session.subscribe(event.stream, 'subscriber');
-
- // When the video has been appended to DOM...
- subscriber.on('videoElementCreated', function (event) {
- // Add a new HTML element for the user's nickname
- appendUserData(event.element, subscriber.stream.connection);
- });
});
- // On every Stream destroyed...
- session.on('streamDestroyed', function (event) {
- // Delete the HTML element with the user's nickname
- removeUserData(event.stream.connection);
- });
+ session.connect(null, function (error) {
-
- // --- 3) Connect to the session ---
-
- // First param irrelevant if your app has no server-side. Second param will be received by every user
- // in Stream.connection.data property, which will be appended to DOM as the user's nickname
- session.connect(null, '{"clientData": "' + userName + '"}', function (error) {
-
- // If the connection is successful, initialize a publisher and publish to the session
if (!error) {
-
- // --- 4) Get your own camera stream with the desired resolution ---
-
- // Your video will be appended to element with id 'publisher'
- var publisher = OV.initPublisher('publisher', {
- audio: true,
- video: true,
- quality: 'MEDIUM'
- });
-
- // --- 5) Publish your stream ---
-
+ var publisher = OV.initPublisher('publisher');
session.publish(publisher);
-
} else {
console.log('There was an error connecting to the session:', error.code, error.message);
}
+
});
- // Show Session page
document.getElementById('session-header').innerText = sessionId;
document.getElementById('join').style.display = 'none';
document.getElementById('session').style.display = 'block';
@@ -75,59 +30,15 @@ function joinSession() {
return false;
}
-
function leaveSession() {
- // --- 6) Leave the session by calling 'disconnect' method over the Session object ---
-
session.disconnect();
- // Removing all HTML elements with the user's nicknames
- removeAllUserData();
-
- // Show Join Session page
document.getElementById('join').style.display = 'block';
document.getElementById('session').style.display = 'none';
}
-/* OPENVIDU METHODS */
-
-
-
-/* APPLICATION SPECIFIC METHODS */
-
-window.addEventListener('load', function () {
- generateParticipantInfo();
-});
window.onbeforeunload = function () {
session.disconnect()
-};
-
-function generateParticipantInfo() {
- document.getElementById("sessionId").value = "SessionA";
- document.getElementById("userName").value = "Participant" + Math.floor(Math.random() * 100);
-}
-
-function appendUserData(videoElement, connection) {
- var clientDataJSON = JSON.parse(connection.data);
- var dataNode = document.createElement('p');
- dataNode.className = "data-node";
- dataNode.id = "data-" + connection.connectionId;
- dataNode.innerHTML = clientDataJSON.clientData;
- videoElement.parentNode.insertBefore(dataNode, videoElement.nextSibling);
-}
-
-function removeUserData(connection) {
- var dataNode = document.getElementById("data-" + connection.connectionId);
- dataNode.parentNode.removeChild(dataNode);
-}
-
-function removeAllUserData() {
- var nicknameElements = document.getElementsByClassName('data-node');
- while (nicknameElements[0]) {
- nicknameElements[0].parentNode.removeChild(nicknameElements[0]);
- }
-}
-
-/* APPLICATION SPECIFIC METHODS */
\ No newline at end of file
+};
\ No newline at end of file
diff --git a/openvidu-hello-world/web/index.html b/openvidu-hello-world/web/index.html
index d0b372ce..1b14d12d 100644
--- a/openvidu-hello-world/web/index.html
+++ b/openvidu-hello-world/web/index.html
@@ -2,33 +2,28 @@
openvidu-hello-world
-
-
+
Join a video session
-
-
+
YOU
OTHERS
diff --git a/openvidu-hello-world/web/openvidu-browser-1.0.4-beta.3.js b/openvidu-hello-world/web/openvidu-browser-1.0.4-beta.3.js
deleted file mode 100644
index 98e5df65..00000000
--- a/openvidu-hello-world/web/openvidu-browser-1.0.4-beta.3.js
+++ /dev/null
@@ -1,17486 +0,0 @@
-(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= v31,
- * and the Firebug extension (any Firefox version) are known
- * to support "%c" CSS customizations.
- *
- * TODO: add a `localStorage` variable to explicitly enable/disable colors
- */
-
-function useColors() {
- // NB: In an Electron preload script, document will be defined but not fully
- // initialized. Since we know we're in Chrome, we'll just detect this case
- // explicitly
- if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') {
- return true;
- }
-
- // is webkit? http://stackoverflow.com/a/16459606/376773
- // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
- return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) ||
- // is firebug? http://stackoverflow.com/a/398120/376773
- (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) ||
- // is firefox >= v31?
- // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
- (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) ||
- // double check webkit in userAgent just in case we are in a worker
- (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/));
-}
-
-/**
- * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
- */
-
-exports.formatters.j = function(v) {
- try {
- return JSON.stringify(v);
- } catch (err) {
- return '[UnexpectedJSONParseError]: ' + err.message;
- }
-};
-
-
-/**
- * Colorize log arguments if enabled.
- *
- * @api public
- */
-
-function formatArgs(args) {
- var useColors = this.useColors;
-
- args[0] = (useColors ? '%c' : '')
- + this.namespace
- + (useColors ? ' %c' : ' ')
- + args[0]
- + (useColors ? '%c ' : ' ')
- + '+' + exports.humanize(this.diff);
-
- if (!useColors) return;
-
- var c = 'color: ' + this.color;
- args.splice(1, 0, c, 'color: inherit')
-
- // the final "%c" is somewhat tricky, because there could be other
- // arguments passed either before or after the %c, so we need to
- // figure out the correct index to insert the CSS into
- var index = 0;
- var lastC = 0;
- args[0].replace(/%[a-zA-Z%]/g, function(match) {
- if ('%%' === match) return;
- index++;
- if ('%c' === match) {
- // we only are interested in the *last* %c
- // (the user may have provided their own)
- lastC = index;
- }
- });
-
- args.splice(lastC, 0, c);
-}
-
-/**
- * Invokes `console.log()` when available.
- * No-op when `console.log` is not a "function".
- *
- * @api public
- */
-
-function log() {
- // this hackery is required for IE8/9, where
- // the `console.log` function doesn't have 'apply'
- return 'object' === typeof console
- && console.log
- && Function.prototype.apply.call(console.log, console, arguments);
-}
-
-/**
- * Save `namespaces`.
- *
- * @param {String} namespaces
- * @api private
- */
-
-function save(namespaces) {
- try {
- if (null == namespaces) {
- exports.storage.removeItem('debug');
- } else {
- exports.storage.debug = namespaces;
- }
- } catch(e) {}
-}
-
-/**
- * Load `namespaces`.
- *
- * @return {String} returns the previously persisted debug modes
- * @api private
- */
-
-function load() {
- var r;
- try {
- r = exports.storage.debug;
- } catch(e) {}
-
- // If debug isn't set in LS, and we're in Electron, try to load $DEBUG
- if (!r && typeof process !== 'undefined' && 'env' in process) {
- r = process.env.DEBUG;
- }
-
- return r;
-}
-
-/**
- * Enable namespaces listed in `localStorage.debug` initially.
- */
-
-exports.enable(load());
-
-/**
- * Localstorage attempts to return the localstorage.
- *
- * This is necessary because safari throws
- * when a user disables cookies/localstorage
- * and you attempt to access it.
- *
- * @return {LocalStorage}
- * @api private
- */
-
-function localstorage() {
- try {
- return window.localStorage;
- } catch (e) {}
-}
-
-}).call(this,require('_process'))
-
-},{"./debug":2,"_process":114}],2:[function(require,module,exports){
-
-/**
- * This is the common logic for both the Node.js and web browser
- * implementations of `debug()`.
- *
- * Expose `debug()` as the module.
- */
-
-exports = module.exports = createDebug.debug = createDebug['default'] = createDebug;
-exports.coerce = coerce;
-exports.disable = disable;
-exports.enable = enable;
-exports.enabled = enabled;
-exports.humanize = require('ms');
-
-/**
- * The currently active debug mode names, and names to skip.
- */
-
-exports.names = [];
-exports.skips = [];
-
-/**
- * Map of special "%n" handling functions, for the debug "format" argument.
- *
- * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
- */
-
-exports.formatters = {};
-
-/**
- * Previous log timestamp.
- */
-
-var prevTime;
-
-/**
- * Select a color.
- * @param {String} namespace
- * @return {Number}
- * @api private
- */
-
-function selectColor(namespace) {
- var hash = 0, i;
-
- for (i in namespace) {
- hash = ((hash << 5) - hash) + namespace.charCodeAt(i);
- hash |= 0; // Convert to 32bit integer
- }
-
- return exports.colors[Math.abs(hash) % exports.colors.length];
-}
-
-/**
- * Create a debugger with the given `namespace`.
- *
- * @param {String} namespace
- * @return {Function}
- * @api public
- */
-
-function createDebug(namespace) {
-
- function debug() {
- // disabled?
- if (!debug.enabled) return;
-
- var self = debug;
-
- // set `diff` timestamp
- var curr = +new Date();
- var ms = curr - (prevTime || curr);
- self.diff = ms;
- self.prev = prevTime;
- self.curr = curr;
- prevTime = curr;
-
- // turn the `arguments` into a proper Array
- var args = new Array(arguments.length);
- for (var i = 0; i < args.length; i++) {
- args[i] = arguments[i];
- }
-
- args[0] = exports.coerce(args[0]);
-
- if ('string' !== typeof args[0]) {
- // anything else let's inspect with %O
- args.unshift('%O');
- }
-
- // apply any `formatters` transformations
- var index = 0;
- args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) {
- // if we encounter an escaped % then don't increase the array index
- if (match === '%%') return match;
- index++;
- var formatter = exports.formatters[format];
- if ('function' === typeof formatter) {
- var val = args[index];
- match = formatter.call(self, val);
-
- // now we need to remove `args[index]` since it's inlined in the `format`
- args.splice(index, 1);
- index--;
- }
- return match;
- });
-
- // apply env-specific formatting (colors, etc.)
- exports.formatArgs.call(self, args);
-
- var logFn = debug.log || exports.log || console.log.bind(console);
- logFn.apply(self, args);
- }
-
- debug.namespace = namespace;
- debug.enabled = exports.enabled(namespace);
- debug.useColors = exports.useColors();
- debug.color = selectColor(namespace);
-
- // env-specific initialization logic for debug instances
- if ('function' === typeof exports.init) {
- exports.init(debug);
- }
-
- return debug;
-}
-
-/**
- * Enables a debug mode by namespaces. This can include modes
- * separated by a colon and wildcards.
- *
- * @param {String} namespaces
- * @api public
- */
-
-function enable(namespaces) {
- exports.save(namespaces);
-
- exports.names = [];
- exports.skips = [];
-
- var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/);
- var len = split.length;
-
- for (var i = 0; i < len; i++) {
- if (!split[i]) continue; // ignore empty strings
- namespaces = split[i].replace(/\*/g, '.*?');
- if (namespaces[0] === '-') {
- exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$'));
- } else {
- exports.names.push(new RegExp('^' + namespaces + '$'));
- }
- }
-}
-
-/**
- * Disable debug output.
- *
- * @api public
- */
-
-function disable() {
- exports.enable('');
-}
-
-/**
- * Returns true if the given mode name is enabled, false otherwise.
- *
- * @param {String} name
- * @return {Boolean}
- * @api public
- */
-
-function enabled(name) {
- var i, len;
- for (i = 0, len = exports.skips.length; i < len; i++) {
- if (exports.skips[i].test(name)) {
- return false;
- }
- }
- for (i = 0, len = exports.names.length; i < len; i++) {
- if (exports.names[i].test(name)) {
- return true;
- }
- }
- return false;
-}
-
-/**
- * Coerce `val`.
- *
- * @param {Mixed} val
- * @return {Mixed}
- * @api private
- */
-
-function coerce(val) {
- if (val instanceof Error) return val.stack || val.message;
- return val;
-}
-
-},{"ms":21}],3:[function(require,module,exports){
-/* jshint node: true */
-'use strict';
-
-var normalice = require('normalice');
-
-/**
- # freeice
-
- The `freeice` module is a simple way of getting random STUN or TURN server
- for your WebRTC application. The list of servers (just STUN at this stage)
- were sourced from this [gist](https://gist.github.com/zziuni/3741933).
-
- ## Example Use
-
- The following demonstrates how you can use `freeice` with
- [rtc-quickconnect](https://github.com/rtc-io/rtc-quickconnect):
-
- <<< examples/quickconnect.js
-
- As the `freeice` module generates ice servers in a list compliant with the
- WebRTC spec you will be able to use it with raw `RTCPeerConnection`
- constructors and other WebRTC libraries.
-
- ## Hey, don't use my STUN/TURN server!
-
- If for some reason your free STUN or TURN server ends up in the
- list of servers ([stun](https://github.com/DamonOehlman/freeice/blob/master/stun.json) or
- [turn](https://github.com/DamonOehlman/freeice/blob/master/turn.json))
- that is used in this module, you can feel
- free to open an issue on this repository and those servers will be removed
- within 24 hours (or sooner). This is the quickest and probably the most
- polite way to have something removed (and provides us some visibility
- if someone opens a pull request requesting that a server is added).
-
- ## Please add my server!
-
- If you have a server that you wish to add to the list, that's awesome! I'm
- sure I speak on behalf of a whole pile of WebRTC developers who say thanks.
- To get it into the list, feel free to either open a pull request or if you
- find that process a bit daunting then just create an issue requesting
- the addition of the server (make sure you provide all the details, and if
- you have a Terms of Service then including that in the PR/issue would be
- awesome).
-
- ## I know of a free server, can I add it?
-
- Sure, if you do your homework and make sure it is ok to use (I'm currently
- in the process of reviewing the terms of those STUN servers included from
- the original list). If it's ok to go, then please see the previous entry
- for how to add it.
-
- ## Current List of Servers
-
- * current as at the time of last `README.md` file generation
-
- ### STUN
-
- <<< stun.json
-
- ### TURN
-
- <<< turn.json
-
-**/
-
-var freeice = module.exports = function(opts) {
- // if a list of servers has been provided, then use it instead of defaults
- var servers = {
- stun: (opts || {}).stun || require('./stun.json'),
- turn: (opts || {}).turn || require('./turn.json')
- };
-
- var stunCount = (opts || {}).stunCount || 2;
- var turnCount = (opts || {}).turnCount || 0;
- var selected;
-
- function getServers(type, count) {
- var out = [];
- var input = [].concat(servers[type]);
- var idx;
-
- while (input.length && out.length < count) {
- idx = (Math.random() * input.length) | 0;
- out = out.concat(input.splice(idx, 1));
- }
-
- return out.map(function(url) {
- //If it's a not a string, don't try to "normalice" it otherwise using type:url will screw it up
- if ((typeof url !== 'string') && (! (url instanceof String))) {
- return url;
- } else {
- return normalice(type + ':' + url);
- }
- });
- }
-
- // add stun servers
- selected = [].concat(getServers('stun', stunCount));
-
- if (turnCount) {
- selected = selected.concat(getServers('turn', turnCount));
- }
-
- return selected;
-};
-
-},{"./stun.json":4,"./turn.json":5,"normalice":22}],4:[function(require,module,exports){
-module.exports=[
- "stun.l.google.com:19302",
- "stun1.l.google.com:19302",
- "stun2.l.google.com:19302",
- "stun3.l.google.com:19302",
- "stun4.l.google.com:19302",
- "stun.ekiga.net",
- "stun.ideasip.com",
- "stun.schlund.de",
- "stun.stunprotocol.org:3478",
- "stun.voiparound.com",
- "stun.voipbuster.com",
- "stun.voipstunt.com",
- "stun.voxgratia.org",
- "stun.services.mozilla.com"
-]
-
-},{}],5:[function(require,module,exports){
-module.exports=[]
-
-},{}],6:[function(require,module,exports){
-var WildEmitter = require('wildemitter');
-
-function getMaxVolume (analyser, fftBins) {
- var maxVolume = -Infinity;
- analyser.getFloatFrequencyData(fftBins);
-
- for(var i=4, ii=fftBins.length; i < ii; i++) {
- if (fftBins[i] > maxVolume && fftBins[i] < 0) {
- maxVolume = fftBins[i];
- }
- };
-
- return maxVolume;
-}
-
-
-var audioContextType = window.AudioContext || window.webkitAudioContext;
-// use a single audio context due to hardware limits
-var audioContext = null;
-module.exports = function(stream, options) {
- var harker = new WildEmitter();
-
-
- // make it not break in non-supported browsers
- if (!audioContextType) return harker;
-
- //Config
- var options = options || {},
- smoothing = (options.smoothing || 0.1),
- interval = (options.interval || 50),
- threshold = options.threshold,
- play = options.play,
- history = options.history || 10,
- running = true;
-
- //Setup Audio Context
- if (!audioContext) {
- audioContext = new audioContextType();
- }
- var sourceNode, fftBins, analyser;
-
- analyser = audioContext.createAnalyser();
- analyser.fftSize = 512;
- analyser.smoothingTimeConstant = smoothing;
- fftBins = new Float32Array(analyser.fftSize);
-
- if (stream.jquery) stream = stream[0];
- if (stream instanceof HTMLAudioElement || stream instanceof HTMLVideoElement) {
- //Audio Tag
- sourceNode = audioContext.createMediaElementSource(stream);
- if (typeof play === 'undefined') play = true;
- threshold = threshold || -50;
- } else {
- //WebRTC Stream
- sourceNode = audioContext.createMediaStreamSource(stream);
- threshold = threshold || -50;
- }
-
- sourceNode.connect(analyser);
- if (play) analyser.connect(audioContext.destination);
-
- harker.speaking = false;
-
- harker.setThreshold = function(t) {
- threshold = t;
- };
-
- harker.setInterval = function(i) {
- interval = i;
- };
-
- harker.stop = function() {
- running = false;
- harker.emit('volume_change', -100, threshold);
- if (harker.speaking) {
- harker.speaking = false;
- harker.emit('stopped_speaking');
- }
- };
- harker.speakingHistory = [];
- for (var i = 0; i < history; i++) {
- harker.speakingHistory.push(0);
- }
-
- // Poll the analyser node to determine if speaking
- // and emit events if changed
- var looper = function() {
- setTimeout(function() {
-
- //check if stop has been called
- if(!running) {
- return;
- }
-
- var currentVolume = getMaxVolume(analyser, fftBins);
-
- harker.emit('volume_change', currentVolume, threshold);
-
- var history = 0;
- if (currentVolume > threshold && !harker.speaking) {
- // trigger quickly, short history
- for (var i = harker.speakingHistory.length - 3; i < harker.speakingHistory.length; i++) {
- history += harker.speakingHistory[i];
- }
- if (history >= 2) {
- harker.speaking = true;
- harker.emit('speaking');
- }
- } else if (currentVolume < threshold && harker.speaking) {
- for (var i = 0; i < harker.speakingHistory.length; i++) {
- history += harker.speakingHistory[i];
- }
- if (history == 0) {
- harker.speaking = false;
- harker.emit('stopped_speaking');
- }
- }
- harker.speakingHistory.shift();
- harker.speakingHistory.push(0 + (currentVolume > threshold));
-
- looper();
- }, interval);
- };
- looper();
-
-
- return harker;
-}
-
-},{"wildemitter":101}],7:[function(require,module,exports){
-if (typeof Object.create === 'function') {
- // implementation from standard node.js 'util' module
- module.exports = function inherits(ctor, superCtor) {
- ctor.super_ = superCtor
- ctor.prototype = Object.create(superCtor.prototype, {
- constructor: {
- value: ctor,
- enumerable: false,
- writable: true,
- configurable: true
- }
- });
- };
-} else {
- // old school shim for old browsers
- module.exports = function inherits(ctor, superCtor) {
- ctor.super_ = superCtor
- var TempCtor = function () {}
- TempCtor.prototype = superCtor.prototype
- ctor.prototype = new TempCtor()
- ctor.prototype.constructor = ctor
- }
-}
-
-},{}],8:[function(require,module,exports){
-(function (global){
-/*! JSON v3.3.2 | http://bestiejs.github.io/json3 | Copyright 2012-2014, Kit Cambridge | http://kit.mit-license.org */
-;(function () {
- // Detect the `define` function exposed by asynchronous module loaders. The
- // strict `define` check is necessary for compatibility with `r.js`.
- var isLoader = typeof define === "function" && define.amd;
-
- // A set of types used to distinguish objects from primitives.
- var objectTypes = {
- "function": true,
- "object": true
- };
-
- // Detect the `exports` object exposed by CommonJS implementations.
- var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
-
- // Use the `global` object exposed by Node (including Browserify via
- // `insert-module-globals`), Narwhal, and Ringo as the default context,
- // and the `window` object in browsers. Rhino exports a `global` function
- // instead.
- var root = objectTypes[typeof window] && window || this,
- freeGlobal = freeExports && objectTypes[typeof module] && module && !module.nodeType && typeof global == "object" && global;
-
- if (freeGlobal && (freeGlobal["global"] === freeGlobal || freeGlobal["window"] === freeGlobal || freeGlobal["self"] === freeGlobal)) {
- root = freeGlobal;
- }
-
- // Public: Initializes JSON 3 using the given `context` object, attaching the
- // `stringify` and `parse` functions to the specified `exports` object.
- function runInContext(context, exports) {
- context || (context = root["Object"]());
- exports || (exports = root["Object"]());
-
- // Native constructor aliases.
- var Number = context["Number"] || root["Number"],
- String = context["String"] || root["String"],
- Object = context["Object"] || root["Object"],
- Date = context["Date"] || root["Date"],
- SyntaxError = context["SyntaxError"] || root["SyntaxError"],
- TypeError = context["TypeError"] || root["TypeError"],
- Math = context["Math"] || root["Math"],
- nativeJSON = context["JSON"] || root["JSON"];
-
- // Delegate to the native `stringify` and `parse` implementations.
- if (typeof nativeJSON == "object" && nativeJSON) {
- exports.stringify = nativeJSON.stringify;
- exports.parse = nativeJSON.parse;
- }
-
- // Convenience aliases.
- var objectProto = Object.prototype,
- getClass = objectProto.toString,
- isProperty, forEach, undef;
-
- // Test the `Date#getUTC*` methods. Based on work by @Yaffle.
- var isExtended = new Date(-3509827334573292);
- try {
- // The `getUTCFullYear`, `Month`, and `Date` methods return nonsensical
- // results for certain dates in Opera >= 10.53.
- isExtended = isExtended.getUTCFullYear() == -109252 && isExtended.getUTCMonth() === 0 && isExtended.getUTCDate() === 1 &&
- // Safari < 2.0.2 stores the internal millisecond time value correctly,
- // but clips the values returned by the date methods to the range of
- // signed 32-bit integers ([-2 ** 31, 2 ** 31 - 1]).
- isExtended.getUTCHours() == 10 && isExtended.getUTCMinutes() == 37 && isExtended.getUTCSeconds() == 6 && isExtended.getUTCMilliseconds() == 708;
- } catch (exception) {}
-
- // Internal: Determines whether the native `JSON.stringify` and `parse`
- // implementations are spec-compliant. Based on work by Ken Snyder.
- function has(name) {
- if (has[name] !== undef) {
- // Return cached feature test result.
- return has[name];
- }
- var isSupported;
- if (name == "bug-string-char-index") {
- // IE <= 7 doesn't support accessing string characters using square
- // bracket notation. IE 8 only supports this for primitives.
- isSupported = "a"[0] != "a";
- } else if (name == "json") {
- // Indicates whether both `JSON.stringify` and `JSON.parse` are
- // supported.
- isSupported = has("json-stringify") && has("json-parse");
- } else {
- var value, serialized = '{"a":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}';
- // Test `JSON.stringify`.
- if (name == "json-stringify") {
- var stringify = exports.stringify, stringifySupported = typeof stringify == "function" && isExtended;
- if (stringifySupported) {
- // A test function object with a custom `toJSON` method.
- (value = function () {
- return 1;
- }).toJSON = value;
- try {
- stringifySupported =
- // Firefox 3.1b1 and b2 serialize string, number, and boolean
- // primitives as object literals.
- stringify(0) === "0" &&
- // FF 3.1b1, b2, and JSON 2 serialize wrapped primitives as object
- // literals.
- stringify(new Number()) === "0" &&
- stringify(new String()) == '""' &&
- // FF 3.1b1, 2 throw an error if the value is `null`, `undefined`, or
- // does not define a canonical JSON representation (this applies to
- // objects with `toJSON` properties as well, *unless* they are nested
- // within an object or array).
- stringify(getClass) === undef &&
- // IE 8 serializes `undefined` as `"undefined"`. Safari <= 5.1.7 and
- // FF 3.1b3 pass this test.
- stringify(undef) === undef &&
- // Safari <= 5.1.7 and FF 3.1b3 throw `Error`s and `TypeError`s,
- // respectively, if the value is omitted entirely.
- stringify() === undef &&
- // FF 3.1b1, 2 throw an error if the given value is not a number,
- // string, array, object, Boolean, or `null` literal. This applies to
- // objects with custom `toJSON` methods as well, unless they are nested
- // inside object or array literals. YUI 3.0.0b1 ignores custom `toJSON`
- // methods entirely.
- stringify(value) === "1" &&
- stringify([value]) == "[1]" &&
- // Prototype <= 1.6.1 serializes `[undefined]` as `"[]"` instead of
- // `"[null]"`.
- stringify([undef]) == "[null]" &&
- // YUI 3.0.0b1 fails to serialize `null` literals.
- stringify(null) == "null" &&
- // FF 3.1b1, 2 halts serialization if an array contains a function:
- // `[1, true, getClass, 1]` serializes as "[1,true,],". FF 3.1b3
- // elides non-JSON values from objects and arrays, unless they
- // define custom `toJSON` methods.
- stringify([undef, getClass, null]) == "[null,null,null]" &&
- // Simple serialization test. FF 3.1b1 uses Unicode escape sequences
- // where character escape codes are expected (e.g., `\b` => `\u0008`).
- stringify({ "a": [value, true, false, null, "\x00\b\n\f\r\t"] }) == serialized &&
- // FF 3.1b1 and b2 ignore the `filter` and `width` arguments.
- stringify(null, value) === "1" &&
- stringify([1, 2], null, 1) == "[\n 1,\n 2\n]" &&
- // JSON 2, Prototype <= 1.7, and older WebKit builds incorrectly
- // serialize extended years.
- stringify(new Date(-8.64e15)) == '"-271821-04-20T00:00:00.000Z"' &&
- // The milliseconds are optional in ES 5, but required in 5.1.
- stringify(new Date(8.64e15)) == '"+275760-09-13T00:00:00.000Z"' &&
- // Firefox <= 11.0 incorrectly serializes years prior to 0 as negative
- // four-digit years instead of six-digit years. Credits: @Yaffle.
- stringify(new Date(-621987552e5)) == '"-000001-01-01T00:00:00.000Z"' &&
- // Safari <= 5.1.5 and Opera >= 10.53 incorrectly serialize millisecond
- // values less than 1000. Credits: @Yaffle.
- stringify(new Date(-1)) == '"1969-12-31T23:59:59.999Z"';
- } catch (exception) {
- stringifySupported = false;
- }
- }
- isSupported = stringifySupported;
- }
- // Test `JSON.parse`.
- if (name == "json-parse") {
- var parse = exports.parse;
- if (typeof parse == "function") {
- try {
- // FF 3.1b1, b2 will throw an exception if a bare literal is provided.
- // Conforming implementations should also coerce the initial argument to
- // a string prior to parsing.
- if (parse("0") === 0 && !parse(false)) {
- // Simple parsing test.
- value = parse(serialized);
- var parseSupported = value["a"].length == 5 && value["a"][0] === 1;
- if (parseSupported) {
- try {
- // Safari <= 5.1.2 and FF 3.1b1 allow unescaped tabs in strings.
- parseSupported = !parse('"\t"');
- } catch (exception) {}
- if (parseSupported) {
- try {
- // FF 4.0 and 4.0.1 allow leading `+` signs and leading
- // decimal points. FF 4.0, 4.0.1, and IE 9-10 also allow
- // certain octal literals.
- parseSupported = parse("01") !== 1;
- } catch (exception) {}
- }
- if (parseSupported) {
- try {
- // FF 4.0, 4.0.1, and Rhino 1.7R3-R4 allow trailing decimal
- // points. These environments, along with FF 3.1b1 and 2,
- // also allow trailing commas in JSON objects and arrays.
- parseSupported = parse("1.") !== 1;
- } catch (exception) {}
- }
- }
- }
- } catch (exception) {
- parseSupported = false;
- }
- }
- isSupported = parseSupported;
- }
- }
- return has[name] = !!isSupported;
- }
-
- if (!has("json")) {
- // Common `[[Class]]` name aliases.
- var functionClass = "[object Function]",
- dateClass = "[object Date]",
- numberClass = "[object Number]",
- stringClass = "[object String]",
- arrayClass = "[object Array]",
- booleanClass = "[object Boolean]";
-
- // Detect incomplete support for accessing string characters by index.
- var charIndexBuggy = has("bug-string-char-index");
-
- // Define additional utility methods if the `Date` methods are buggy.
- if (!isExtended) {
- var floor = Math.floor;
- // A mapping between the months of the year and the number of days between
- // January 1st and the first of the respective month.
- var Months = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
- // Internal: Calculates the number of days between the Unix epoch and the
- // first day of the given month.
- var getDay = function (year, month) {
- return Months[month] + 365 * (year - 1970) + floor((year - 1969 + (month = +(month > 1))) / 4) - floor((year - 1901 + month) / 100) + floor((year - 1601 + month) / 400);
- };
- }
-
- // Internal: Determines if a property is a direct property of the given
- // object. Delegates to the native `Object#hasOwnProperty` method.
- if (!(isProperty = objectProto.hasOwnProperty)) {
- isProperty = function (property) {
- var members = {}, constructor;
- if ((members.__proto__ = null, members.__proto__ = {
- // The *proto* property cannot be set multiple times in recent
- // versions of Firefox and SeaMonkey.
- "toString": 1
- }, members).toString != getClass) {
- // Safari <= 2.0.3 doesn't implement `Object#hasOwnProperty`, but
- // supports the mutable *proto* property.
- isProperty = function (property) {
- // Capture and break the object's prototype chain (see section 8.6.2
- // of the ES 5.1 spec). The parenthesized expression prevents an
- // unsafe transformation by the Closure Compiler.
- var original = this.__proto__, result = property in (this.__proto__ = null, this);
- // Restore the original prototype chain.
- this.__proto__ = original;
- return result;
- };
- } else {
- // Capture a reference to the top-level `Object` constructor.
- constructor = members.constructor;
- // Use the `constructor` property to simulate `Object#hasOwnProperty` in
- // other environments.
- isProperty = function (property) {
- var parent = (this.constructor || constructor).prototype;
- return property in this && !(property in parent && this[property] === parent[property]);
- };
- }
- members = null;
- return isProperty.call(this, property);
- };
- }
-
- // Internal: Normalizes the `for...in` iteration algorithm across
- // environments. Each enumerated key is yielded to a `callback` function.
- forEach = function (object, callback) {
- var size = 0, Properties, members, property;
-
- // Tests for bugs in the current environment's `for...in` algorithm. The
- // `valueOf` property inherits the non-enumerable flag from
- // `Object.prototype` in older versions of IE, Netscape, and Mozilla.
- (Properties = function () {
- this.valueOf = 0;
- }).prototype.valueOf = 0;
-
- // Iterate over a new instance of the `Properties` class.
- members = new Properties();
- for (property in members) {
- // Ignore all properties inherited from `Object.prototype`.
- if (isProperty.call(members, property)) {
- size++;
- }
- }
- Properties = members = null;
-
- // Normalize the iteration algorithm.
- if (!size) {
- // A list of non-enumerable properties inherited from `Object.prototype`.
- members = ["valueOf", "toString", "toLocaleString", "propertyIsEnumerable", "isPrototypeOf", "hasOwnProperty", "constructor"];
- // IE <= 8, Mozilla 1.0, and Netscape 6.2 ignore shadowed non-enumerable
- // properties.
- forEach = function (object, callback) {
- var isFunction = getClass.call(object) == functionClass, property, length;
- var hasProperty = !isFunction && typeof object.constructor != "function" && objectTypes[typeof object.hasOwnProperty] && object.hasOwnProperty || isProperty;
- for (property in object) {
- // Gecko <= 1.0 enumerates the `prototype` property of functions under
- // certain conditions; IE does not.
- if (!(isFunction && property == "prototype") && hasProperty.call(object, property)) {
- callback(property);
- }
- }
- // Manually invoke the callback for each non-enumerable property.
- for (length = members.length; property = members[--length]; hasProperty.call(object, property) && callback(property));
- };
- } else if (size == 2) {
- // Safari <= 2.0.4 enumerates shadowed properties twice.
- forEach = function (object, callback) {
- // Create a set of iterated properties.
- var members = {}, isFunction = getClass.call(object) == functionClass, property;
- for (property in object) {
- // Store each property name to prevent double enumeration. The
- // `prototype` property of functions is not enumerated due to cross-
- // environment inconsistencies.
- if (!(isFunction && property == "prototype") && !isProperty.call(members, property) && (members[property] = 1) && isProperty.call(object, property)) {
- callback(property);
- }
- }
- };
- } else {
- // No bugs detected; use the standard `for...in` algorithm.
- forEach = function (object, callback) {
- var isFunction = getClass.call(object) == functionClass, property, isConstructor;
- for (property in object) {
- if (!(isFunction && property == "prototype") && isProperty.call(object, property) && !(isConstructor = property === "constructor")) {
- callback(property);
- }
- }
- // Manually invoke the callback for the `constructor` property due to
- // cross-environment inconsistencies.
- if (isConstructor || isProperty.call(object, (property = "constructor"))) {
- callback(property);
- }
- };
- }
- return forEach(object, callback);
- };
-
- // Public: Serializes a JavaScript `value` as a JSON string. The optional
- // `filter` argument may specify either a function that alters how object and
- // array members are serialized, or an array of strings and numbers that
- // indicates which properties should be serialized. The optional `width`
- // argument may be either a string or number that specifies the indentation
- // level of the output.
- if (!has("json-stringify")) {
- // Internal: A map of control characters and their escaped equivalents.
- var Escapes = {
- 92: "\\\\",
- 34: '\\"',
- 8: "\\b",
- 12: "\\f",
- 10: "\\n",
- 13: "\\r",
- 9: "\\t"
- };
-
- // Internal: Converts `value` into a zero-padded string such that its
- // length is at least equal to `width`. The `width` must be <= 6.
- var leadingZeroes = "000000";
- var toPaddedString = function (width, value) {
- // The `|| 0` expression is necessary to work around a bug in
- // Opera <= 7.54u2 where `0 == -0`, but `String(-0) !== "0"`.
- return (leadingZeroes + (value || 0)).slice(-width);
- };
-
- // Internal: Double-quotes a string `value`, replacing all ASCII control
- // characters (characters with code unit values between 0 and 31) with
- // their escaped equivalents. This is an implementation of the
- // `Quote(value)` operation defined in ES 5.1 section 15.12.3.
- var unicodePrefix = "\\u00";
- var quote = function (value) {
- var result = '"', index = 0, length = value.length, useCharIndex = !charIndexBuggy || length > 10;
- var symbols = useCharIndex && (charIndexBuggy ? value.split("") : value);
- for (; index < length; index++) {
- var charCode = value.charCodeAt(index);
- // If the character is a control character, append its Unicode or
- // shorthand escape sequence; otherwise, append the character as-is.
- switch (charCode) {
- case 8: case 9: case 10: case 12: case 13: case 34: case 92:
- result += Escapes[charCode];
- break;
- default:
- if (charCode < 32) {
- result += unicodePrefix + toPaddedString(2, charCode.toString(16));
- break;
- }
- result += useCharIndex ? symbols[index] : value.charAt(index);
- }
- }
- return result + '"';
- };
-
- // Internal: Recursively serializes an object. Implements the
- // `Str(key, holder)`, `JO(value)`, and `JA(value)` operations.
- var serialize = function (property, object, callback, properties, whitespace, indentation, stack) {
- var value, className, year, month, date, time, hours, minutes, seconds, milliseconds, results, element, index, length, prefix, result;
- try {
- // Necessary for host object support.
- value = object[property];
- } catch (exception) {}
- if (typeof value == "object" && value) {
- className = getClass.call(value);
- if (className == dateClass && !isProperty.call(value, "toJSON")) {
- if (value > -1 / 0 && value < 1 / 0) {
- // Dates are serialized according to the `Date#toJSON` method
- // specified in ES 5.1 section 15.9.5.44. See section 15.9.1.15
- // for the ISO 8601 date time string format.
- if (getDay) {
- // Manually compute the year, month, date, hours, minutes,
- // seconds, and milliseconds if the `getUTC*` methods are
- // buggy. Adapted from @Yaffle's `date-shim` project.
- date = floor(value / 864e5);
- for (year = floor(date / 365.2425) + 1970 - 1; getDay(year + 1, 0) <= date; year++);
- for (month = floor((date - getDay(year, 0)) / 30.42); getDay(year, month + 1) <= date; month++);
- date = 1 + date - getDay(year, month);
- // The `time` value specifies the time within the day (see ES
- // 5.1 section 15.9.1.2). The formula `(A % B + B) % B` is used
- // to compute `A modulo B`, as the `%` operator does not
- // correspond to the `modulo` operation for negative numbers.
- time = (value % 864e5 + 864e5) % 864e5;
- // The hours, minutes, seconds, and milliseconds are obtained by
- // decomposing the time within the day. See section 15.9.1.10.
- hours = floor(time / 36e5) % 24;
- minutes = floor(time / 6e4) % 60;
- seconds = floor(time / 1e3) % 60;
- milliseconds = time % 1e3;
- } else {
- year = value.getUTCFullYear();
- month = value.getUTCMonth();
- date = value.getUTCDate();
- hours = value.getUTCHours();
- minutes = value.getUTCMinutes();
- seconds = value.getUTCSeconds();
- milliseconds = value.getUTCMilliseconds();
- }
- // Serialize extended years correctly.
- value = (year <= 0 || year >= 1e4 ? (year < 0 ? "-" : "+") + toPaddedString(6, year < 0 ? -year : year) : toPaddedString(4, year)) +
- "-" + toPaddedString(2, month + 1) + "-" + toPaddedString(2, date) +
- // Months, dates, hours, minutes, and seconds should have two
- // digits; milliseconds should have three.
- "T" + toPaddedString(2, hours) + ":" + toPaddedString(2, minutes) + ":" + toPaddedString(2, seconds) +
- // Milliseconds are optional in ES 5.0, but required in 5.1.
- "." + toPaddedString(3, milliseconds) + "Z";
- } else {
- value = null;
- }
- } else if (typeof value.toJSON == "function" && ((className != numberClass && className != stringClass && className != arrayClass) || isProperty.call(value, "toJSON"))) {
- // Prototype <= 1.6.1 adds non-standard `toJSON` methods to the
- // `Number`, `String`, `Date`, and `Array` prototypes. JSON 3
- // ignores all `toJSON` methods on these objects unless they are
- // defined directly on an instance.
- value = value.toJSON(property);
- }
- }
- if (callback) {
- // If a replacement function was provided, call it to obtain the value
- // for serialization.
- value = callback.call(object, property, value);
- }
- if (value === null) {
- return "null";
- }
- className = getClass.call(value);
- if (className == booleanClass) {
- // Booleans are represented literally.
- return "" + value;
- } else if (className == numberClass) {
- // JSON numbers must be finite. `Infinity` and `NaN` are serialized as
- // `"null"`.
- return value > -1 / 0 && value < 1 / 0 ? "" + value : "null";
- } else if (className == stringClass) {
- // Strings are double-quoted and escaped.
- return quote("" + value);
- }
- // Recursively serialize objects and arrays.
- if (typeof value == "object") {
- // Check for cyclic structures. This is a linear search; performance
- // is inversely proportional to the number of unique nested objects.
- for (length = stack.length; length--;) {
- if (stack[length] === value) {
- // Cyclic structures cannot be serialized by `JSON.stringify`.
- throw TypeError();
- }
- }
- // Add the object to the stack of traversed objects.
- stack.push(value);
- results = [];
- // Save the current indentation level and indent one additional level.
- prefix = indentation;
- indentation += whitespace;
- if (className == arrayClass) {
- // Recursively serialize array elements.
- for (index = 0, length = value.length; index < length; index++) {
- element = serialize(index, value, callback, properties, whitespace, indentation, stack);
- results.push(element === undef ? "null" : element);
- }
- result = results.length ? (whitespace ? "[\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "]" : ("[" + results.join(",") + "]")) : "[]";
- } else {
- // Recursively serialize object members. Members are selected from
- // either a user-specified list of property names, or the object
- // itself.
- forEach(properties || value, function (property) {
- var element = serialize(property, value, callback, properties, whitespace, indentation, stack);
- if (element !== undef) {
- // According to ES 5.1 section 15.12.3: "If `gap` {whitespace}
- // is not the empty string, let `member` {quote(property) + ":"}
- // be the concatenation of `member` and the `space` character."
- // The "`space` character" refers to the literal space
- // character, not the `space` {width} argument provided to
- // `JSON.stringify`.
- results.push(quote(property) + ":" + (whitespace ? " " : "") + element);
- }
- });
- result = results.length ? (whitespace ? "{\n" + indentation + results.join(",\n" + indentation) + "\n" + prefix + "}" : ("{" + results.join(",") + "}")) : "{}";
- }
- // Remove the object from the traversed object stack.
- stack.pop();
- return result;
- }
- };
-
- // Public: `JSON.stringify`. See ES 5.1 section 15.12.3.
- exports.stringify = function (source, filter, width) {
- var whitespace, callback, properties, className;
- if (objectTypes[typeof filter] && filter) {
- if ((className = getClass.call(filter)) == functionClass) {
- callback = filter;
- } else if (className == arrayClass) {
- // Convert the property names array into a makeshift set.
- properties = {};
- for (var index = 0, length = filter.length, value; index < length; value = filter[index++], ((className = getClass.call(value)), className == stringClass || className == numberClass) && (properties[value] = 1));
- }
- }
- if (width) {
- if ((className = getClass.call(width)) == numberClass) {
- // Convert the `width` to an integer and create a string containing
- // `width` number of space characters.
- if ((width -= width % 1) > 0) {
- for (whitespace = "", width > 10 && (width = 10); whitespace.length < width; whitespace += " ");
- }
- } else if (className == stringClass) {
- whitespace = width.length <= 10 ? width : width.slice(0, 10);
- }
- }
- // Opera <= 7.54u2 discards the values associated with empty string keys
- // (`""`) only if they are used directly within an object member list
- // (e.g., `!("" in { "": 1})`).
- return serialize("", (value = {}, value[""] = source, value), callback, properties, whitespace, "", []);
- };
- }
-
- // Public: Parses a JSON source string.
- if (!has("json-parse")) {
- var fromCharCode = String.fromCharCode;
-
- // Internal: A map of escaped control characters and their unescaped
- // equivalents.
- var Unescapes = {
- 92: "\\",
- 34: '"',
- 47: "/",
- 98: "\b",
- 116: "\t",
- 110: "\n",
- 102: "\f",
- 114: "\r"
- };
-
- // Internal: Stores the parser state.
- var Index, Source;
-
- // Internal: Resets the parser state and throws a `SyntaxError`.
- var abort = function () {
- Index = Source = null;
- throw SyntaxError();
- };
-
- // Internal: Returns the next token, or `"$"` if the parser has reached
- // the end of the source string. A token may be a string, number, `null`
- // literal, or Boolean literal.
- var lex = function () {
- var source = Source, length = source.length, value, begin, position, isSigned, charCode;
- while (Index < length) {
- charCode = source.charCodeAt(Index);
- switch (charCode) {
- case 9: case 10: case 13: case 32:
- // Skip whitespace tokens, including tabs, carriage returns, line
- // feeds, and space characters.
- Index++;
- break;
- case 123: case 125: case 91: case 93: case 58: case 44:
- // Parse a punctuator token (`{`, `}`, `[`, `]`, `:`, or `,`) at
- // the current position.
- value = charIndexBuggy ? source.charAt(Index) : source[Index];
- Index++;
- return value;
- case 34:
- // `"` delimits a JSON string; advance to the next character and
- // begin parsing the string. String tokens are prefixed with the
- // sentinel `@` character to distinguish them from punctuators and
- // end-of-string tokens.
- for (value = "@", Index++; Index < length;) {
- charCode = source.charCodeAt(Index);
- if (charCode < 32) {
- // Unescaped ASCII control characters (those with a code unit
- // less than the space character) are not permitted.
- abort();
- } else if (charCode == 92) {
- // A reverse solidus (`\`) marks the beginning of an escaped
- // control character (including `"`, `\`, and `/`) or Unicode
- // escape sequence.
- charCode = source.charCodeAt(++Index);
- switch (charCode) {
- case 92: case 34: case 47: case 98: case 116: case 110: case 102: case 114:
- // Revive escaped control characters.
- value += Unescapes[charCode];
- Index++;
- break;
- case 117:
- // `\u` marks the beginning of a Unicode escape sequence.
- // Advance to the first character and validate the
- // four-digit code point.
- begin = ++Index;
- for (position = Index + 4; Index < position; Index++) {
- charCode = source.charCodeAt(Index);
- // A valid sequence comprises four hexdigits (case-
- // insensitive) that form a single hexadecimal value.
- if (!(charCode >= 48 && charCode <= 57 || charCode >= 97 && charCode <= 102 || charCode >= 65 && charCode <= 70)) {
- // Invalid Unicode escape sequence.
- abort();
- }
- }
- // Revive the escaped character.
- value += fromCharCode("0x" + source.slice(begin, Index));
- break;
- default:
- // Invalid escape sequence.
- abort();
- }
- } else {
- if (charCode == 34) {
- // An unescaped double-quote character marks the end of the
- // string.
- break;
- }
- charCode = source.charCodeAt(Index);
- begin = Index;
- // Optimize for the common case where a string is valid.
- while (charCode >= 32 && charCode != 92 && charCode != 34) {
- charCode = source.charCodeAt(++Index);
- }
- // Append the string as-is.
- value += source.slice(begin, Index);
- }
- }
- if (source.charCodeAt(Index) == 34) {
- // Advance to the next character and return the revived string.
- Index++;
- return value;
- }
- // Unterminated string.
- abort();
- default:
- // Parse numbers and literals.
- begin = Index;
- // Advance past the negative sign, if one is specified.
- if (charCode == 45) {
- isSigned = true;
- charCode = source.charCodeAt(++Index);
- }
- // Parse an integer or floating-point value.
- if (charCode >= 48 && charCode <= 57) {
- // Leading zeroes are interpreted as octal literals.
- if (charCode == 48 && ((charCode = source.charCodeAt(Index + 1)), charCode >= 48 && charCode <= 57)) {
- // Illegal octal literal.
- abort();
- }
- isSigned = false;
- // Parse the integer component.
- for (; Index < length && ((charCode = source.charCodeAt(Index)), charCode >= 48 && charCode <= 57); Index++);
- // Floats cannot contain a leading decimal point; however, this
- // case is already accounted for by the parser.
- if (source.charCodeAt(Index) == 46) {
- position = ++Index;
- // Parse the decimal component.
- for (; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++);
- if (position == Index) {
- // Illegal trailing decimal.
- abort();
- }
- Index = position;
- }
- // Parse exponents. The `e` denoting the exponent is
- // case-insensitive.
- charCode = source.charCodeAt(Index);
- if (charCode == 101 || charCode == 69) {
- charCode = source.charCodeAt(++Index);
- // Skip past the sign following the exponent, if one is
- // specified.
- if (charCode == 43 || charCode == 45) {
- Index++;
- }
- // Parse the exponential component.
- for (position = Index; position < length && ((charCode = source.charCodeAt(position)), charCode >= 48 && charCode <= 57); position++);
- if (position == Index) {
- // Illegal empty exponent.
- abort();
- }
- Index = position;
- }
- // Coerce the parsed value to a JavaScript number.
- return +source.slice(begin, Index);
- }
- // A negative sign may only precede numbers.
- if (isSigned) {
- abort();
- }
- // `true`, `false`, and `null` literals.
- if (source.slice(Index, Index + 4) == "true") {
- Index += 4;
- return true;
- } else if (source.slice(Index, Index + 5) == "false") {
- Index += 5;
- return false;
- } else if (source.slice(Index, Index + 4) == "null") {
- Index += 4;
- return null;
- }
- // Unrecognized token.
- abort();
- }
- }
- // Return the sentinel `$` character if the parser has reached the end
- // of the source string.
- return "$";
- };
-
- // Internal: Parses a JSON `value` token.
- var get = function (value) {
- var results, hasMembers;
- if (value == "$") {
- // Unexpected end of input.
- abort();
- }
- if (typeof value == "string") {
- if ((charIndexBuggy ? value.charAt(0) : value[0]) == "@") {
- // Remove the sentinel `@` character.
- return value.slice(1);
- }
- // Parse object and array literals.
- if (value == "[") {
- // Parses a JSON array, returning a new JavaScript array.
- results = [];
- for (;; hasMembers || (hasMembers = true)) {
- value = lex();
- // A closing square bracket marks the end of the array literal.
- if (value == "]") {
- break;
- }
- // If the array literal contains elements, the current token
- // should be a comma separating the previous element from the
- // next.
- if (hasMembers) {
- if (value == ",") {
- value = lex();
- if (value == "]") {
- // Unexpected trailing `,` in array literal.
- abort();
- }
- } else {
- // A `,` must separate each array element.
- abort();
- }
- }
- // Elisions and leading commas are not permitted.
- if (value == ",") {
- abort();
- }
- results.push(get(value));
- }
- return results;
- } else if (value == "{") {
- // Parses a JSON object, returning a new JavaScript object.
- results = {};
- for (;; hasMembers || (hasMembers = true)) {
- value = lex();
- // A closing curly brace marks the end of the object literal.
- if (value == "}") {
- break;
- }
- // If the object literal contains members, the current token
- // should be a comma separator.
- if (hasMembers) {
- if (value == ",") {
- value = lex();
- if (value == "}") {
- // Unexpected trailing `,` in object literal.
- abort();
- }
- } else {
- // A `,` must separate each object member.
- abort();
- }
- }
- // Leading commas are not permitted, object property names must be
- // double-quoted strings, and a `:` must separate each property
- // name and value.
- if (value == "," || typeof value != "string" || (charIndexBuggy ? value.charAt(0) : value[0]) != "@" || lex() != ":") {
- abort();
- }
- results[value.slice(1)] = get(lex());
- }
- return results;
- }
- // Unexpected token encountered.
- abort();
- }
- return value;
- };
-
- // Internal: Updates a traversed object member.
- var update = function (source, property, callback) {
- var element = walk(source, property, callback);
- if (element === undef) {
- delete source[property];
- } else {
- source[property] = element;
- }
- };
-
- // Internal: Recursively traverses a parsed JSON object, invoking the
- // `callback` function for each value. This is an implementation of the
- // `Walk(holder, name)` operation defined in ES 5.1 section 15.12.2.
- var walk = function (source, property, callback) {
- var value = source[property], length;
- if (typeof value == "object" && value) {
- // `forEach` can't be used to traverse an array in Opera <= 8.54
- // because its `Object#hasOwnProperty` implementation returns `false`
- // for array indices (e.g., `![1, 2, 3].hasOwnProperty("0")`).
- if (getClass.call(value) == arrayClass) {
- for (length = value.length; length--;) {
- update(value, length, callback);
- }
- } else {
- forEach(value, function (property) {
- update(value, property, callback);
- });
- }
- }
- return callback.call(source, property, value);
- };
-
- // Public: `JSON.parse`. See ES 5.1 section 15.12.2.
- exports.parse = function (source, callback) {
- var result, value;
- Index = 0;
- Source = "" + source;
- result = get(lex());
- // If a JSON string contains multiple tokens, it is invalid.
- if (lex() != "$") {
- abort();
- }
- // Reset the parser state.
- Index = Source = null;
- return callback && getClass.call(callback) == functionClass ? walk((value = {}, value[""] = result, value), "", callback) : result;
- };
- }
- }
-
- exports["runInContext"] = runInContext;
- return exports;
- }
-
- if (freeExports && !isLoader) {
- // Export for CommonJS environments.
- runInContext(root, freeExports);
- } else {
- // Export for web browsers and JavaScript engines.
- var nativeJSON = root.JSON,
- previousJSON = root["JSON3"],
- isRestored = false;
-
- var JSON3 = runInContext(root, (root["JSON3"] = {
- // Public: Restores the original value of the global `JSON` object and
- // returns a reference to the `JSON3` object.
- "noConflict": function () {
- if (!isRestored) {
- isRestored = true;
- root.JSON = nativeJSON;
- root["JSON3"] = previousJSON;
- nativeJSON = previousJSON = null;
- }
- return JSON3;
- }
- }));
-
- root.JSON = {
- "parse": JSON3.parse,
- "stringify": JSON3.stringify
- };
- }
-
- // Export for asynchronous module loaders.
- if (isLoader) {
- define(function () {
- return JSON3;
- });
- }
-}).call(this);
-
-}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-
-},{}],9:[function(require,module,exports){
-function Mapper()
-{
- var sources = {};
-
-
- this.forEach = function(callback)
- {
- for(var key in sources)
- {
- var source = sources[key];
-
- for(var key2 in source)
- callback(source[key2]);
- };
- };
-
- this.get = function(id, source)
- {
- var ids = sources[source];
- if(ids == undefined)
- return undefined;
-
- return ids[id];
- };
-
- this.remove = function(id, source)
- {
- var ids = sources[source];
- if(ids == undefined)
- return;
-
- delete ids[id];
-
- // Check it's empty
- for(var i in ids){return false}
-
- delete sources[source];
- };
-
- this.set = function(value, id, source)
- {
- if(value == undefined)
- return this.remove(id, source);
-
- var ids = sources[source];
- if(ids == undefined)
- sources[source] = ids = {};
-
- ids[id] = value;
- };
-};
-
-
-Mapper.prototype.pop = function(id, source)
-{
- var value = this.get(id, source);
- if(value == undefined)
- return undefined;
-
- this.remove(id, source);
-
- return value;
-};
-
-
-module.exports = Mapper;
-
-},{}],10:[function(require,module,exports){
-/*
- * (C) Copyright 2014 Kurento (http://kurento.org/)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-var JsonRpcClient = require('./jsonrpcclient');
-
-
-exports.JsonRpcClient = JsonRpcClient;
-},{"./jsonrpcclient":11}],11:[function(require,module,exports){
-/*
- * (C) Copyright 2014 Kurento (http://kurento.org/)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-var RpcBuilder = require('../..');
-var WebSocketWithReconnection = require('./transports/webSocketWithReconnection');
-
-Date.now = Date.now || function() {
- return +new Date;
-};
-
-var PING_INTERVAL = 5000;
-
-var RECONNECTING = 'RECONNECTING';
-var CONNECTED = 'CONNECTED';
-var DISCONNECTED = 'DISCONNECTED';
-
-var RECONNECTING = "RECONNECTING";
-var CONNECTED = "CONNECTED";
-var DISCONNECTED = "DISCONNECTED";
-
-
-/**
- *
- * heartbeat: interval in ms for each heartbeat message,
- * sendCloseMessage : true / false, before closing the connection, it sends a closeSession message
- *
- * ws : {
- * uri : URI to conntect to,
- * useSockJS : true (use SockJS) / false (use WebSocket) by default,
- * onconnected : callback method to invoke when connection is successful,
- * ondisconnect : callback method to invoke when the connection is lost,
- * onreconnecting : callback method to invoke when the client is reconnecting,
- * onreconnected : callback method to invoke when the client succesfully reconnects,
- * },
- * rpc : {
- * requestTimeout : timeout for a request,
- * sessionStatusChanged: callback method for changes in session status,
- * mediaRenegotiation: mediaRenegotiation
- * }
- *
- */
-function JsonRpcClient(configuration) {
-
- var self = this;
-
- var wsConfig = configuration.ws;
-
- var notReconnectIfNumLessThan = -1;
-
- var pingNextNum = 0;
- var enabledPings = true;
- var pingPongStarted = false;
- var pingInterval;
-
- var status = DISCONNECTED;
-
- var onreconnecting = wsConfig.onreconnecting;
- var onreconnected = wsConfig.onreconnected;
- var onconnected = wsConfig.onconnected;
-
- configuration.rpc.pull = function(params, request) {
- request.reply(null, "push");
- }
-
- wsConfig.onreconnecting = function() {
- console.log("--------- ONRECONNECTING -----------");
- if (status === RECONNECTING) {
- console.error("Websocket already in RECONNECTING state when receiving a new ONRECONNECTING message. Ignoring it");
- return;
- }
-
- status = RECONNECTING;
- if (onreconnecting) {
- onreconnecting();
- }
- }
-
- wsConfig.onreconnected = function() {
- console.log("--------- ONRECONNECTED -----------");
- if (status === CONNECTED) {
- console.error("Websocket already in CONNECTED state when receiving a new ONRECONNECTED message. Ignoring it");
- return;
- }
- status = CONNECTED;
-
- enabledPings = true;
- updateNotReconnectIfLessThan();
- usePing();
-
- if (onreconnected) {
- onreconnected();
- }
- }
-
- wsConfig.onconnected = function() {
- console.log("--------- ONCONNECTED -----------");
- if (status === CONNECTED) {
- console.error("Websocket already in CONNECTED state when receiving a new ONCONNECTED message. Ignoring it");
- return;
- }
- status = CONNECTED;
-
- enabledPings = true;
- usePing();
-
- if (onconnected) {
- onconnected();
- }
- }
-
- var ws = new WebSocketWithReconnection(wsConfig);
-
- console.log('Connecting websocket to URI: ' + wsConfig.uri);
-
- var rpcBuilderOptions = {
- request_timeout: configuration.rpc.requestTimeout
- };
-
- var rpc = new RpcBuilder(RpcBuilder.packers.JsonRPC, rpcBuilderOptions, ws,
- function(request) {
-
- console.log('Received request: ' + JSON.stringify(request));
-
- try {
- var func = configuration.rpc[request.method];
-
- if (func === undefined) {
- console.error("Method " + request.method + " not registered in client");
- } else {
- func(request.params, request);
- }
- } catch (err) {
- console.error('Exception processing request: ' + JSON.stringify(request));
- console.error(err);
- }
- });
-
- this.send = function(method, params, callback) {
- if (method !== 'ping') {
- console.log('Request: method:' + method + " params:" + JSON.stringify(params));
- }
-
- var requestTime = Date.now();
-
- rpc.encode(method, params, function(error, result) {
- if (error) {
- try {
- console.error("ERROR:" + error.message + " in Request: method:" + method + " params:" + JSON.stringify(params));
- if (error.data) {
- console.error("ERROR DATA:" + JSON.stringify(error.data));
- }
- } catch (e) {}
- error.requestTime = requestTime;
- }
- if (callback) {
- if (result != undefined && result.value !== 'pong') {
- console.log('Response: ' + JSON.stringify(result));
- }
- callback(error, result);
- }
- });
- }
-
- function updateNotReconnectIfLessThan() {
- notReconnectIfNumLessThan = pingNextNum;
- console.log("notReconnectIfNumLessThan = " + notReconnectIfNumLessThan);
- }
-
- function sendPing() {
- if (enabledPings) {
- var params = null;
-
- if (pingNextNum == 0 || pingNextNum == notReconnectIfNumLessThan) {
- params = {
- interval: PING_INTERVAL
- };
- }
-
- pingNextNum++;
-
- self.send('ping', params, (function(pingNum) {
- return function(error, result) {
- if (error) {
- if (pingNum > notReconnectIfNumLessThan) {
- enabledPings = false;
- updateNotReconnectIfLessThan();
- console.log("DSS did not respond to ping message " + pingNum + ". Reconnecting... ");
- ws.reconnectWs();
- }
- }
- }
- })(pingNextNum));
- } else {
- console.log("Trying to send ping, but ping is not enabled");
- }
- }
-
- /*
- * If configuration.hearbeat has any value, the ping-pong will work with the interval
- * of configuration.hearbeat
- */
- function usePing() {
- if (!pingPongStarted) {
- console.log("Starting ping (if configured)")
- pingPongStarted = true;
-
- if (configuration.heartbeat != undefined) {
- pingInterval = setInterval(sendPing, configuration.heartbeat);
- sendPing();
- }
- }
- }
-
- this.close = function() {
- console.log("Closing jsonRpcClient explicitely by client");
-
- if (pingInterval != undefined) {
- clearInterval(pingInterval);
- }
- pingPongStarted = false;
- enabledPings = false;
-
- if (configuration.sendCloseMessage) {
- this.send('closeSession', null, function(error, result) {
- if (error) {
- console.error("Error sending close message: " + JSON.stringify(error));
- }
-
- ws.close();
- });
- } else {
- ws.close();
- }
- }
-
- // This method is only for testing
- this.forceClose = function(millis) {
- ws.forceClose(millis);
- }
-
- this.reconnect = function() {
- ws.reconnectWs();
- }
-}
-
-
-module.exports = JsonRpcClient;
-
-},{"../..":14,"./transports/webSocketWithReconnection":13}],12:[function(require,module,exports){
-/*
- * (C) Copyright 2014 Kurento (http://kurento.org/)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-var WebSocketWithReconnection = require('./webSocketWithReconnection');
-
-
-exports.WebSocketWithReconnection = WebSocketWithReconnection;
-},{"./webSocketWithReconnection":13}],13:[function(require,module,exports){
-/*
- * (C) Copyright 2013-2015 Kurento (http://kurento.org/)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-"use strict";
-
-var WebSocket = require('ws');
-var SockJS = require('sockjs-client');
-
-var MAX_RETRIES = 2000; // Forever...
-var RETRY_TIME_MS = 3000; // FIXME: Implement exponential wait times...
-var PING_INTERVAL = 5000;
-var PING_MSG = JSON.stringify({
- 'method': 'ping'
-});
-
-var CONNECTING = 0;
-var OPEN = 1;
-var CLOSING = 2;
-var CLOSED = 3;
-
-/*
-config = {
- uri : wsUri,
- useSockJS : true (use SockJS) / false (use WebSocket) by default,
- onconnected : callback method to invoke when connection is successful,
- ondisconnect : callback method to invoke when the connection is lost,
- onreconnecting : callback method to invoke when the client is reconnecting,
- onreconnected : callback method to invoke when the client succesfully reconnects,
- };
-*/
-function WebSocketWithReconnection(config) {
-
- var closing = false;
- var registerMessageHandler;
- var wsUri = config.uri;
- var useSockJS = config.useSockJS;
- var reconnecting = false;
-
- var forcingDisconnection = false;
-
- var ws;
-
- if (useSockJS) {
- ws = new SockJS(wsUri);
- } else {
- ws = new WebSocket(wsUri);
- }
-
- ws.onopen = function() {
- logConnected(ws, wsUri);
- config.onconnected();
- };
-
- ws.onerror = function(evt) {
- config.onconnected(evt.data);
- };
-
- function logConnected(ws, wsUri) {
- try {
- console.log("WebSocket connected to " + wsUri);
- } catch (e) {
- console.error(e);
- }
- }
-
- var reconnectionOnClose = function() {
- if (ws.readyState === CLOSED) {
- if (closing) {
- console.log("Connection Closed by user");
- } else {
- console.log("Connection closed unexpectecly. Reconnecting...");
- reconnectInNewUri(MAX_RETRIES, 1);
- }
- } else {
- console.log("Close callback from previous websocket. Ignoring it");
- }
- };
-
- ws.onclose = reconnectionOnClose;
-
- function reconnectInNewUri(maxRetries, numRetries) {
- console.log("reconnectInNewUri");
-
- if (numRetries === 1) {
- if (reconnecting) {
- console
- .warn("Trying to reconnect when reconnecting... Ignoring this reconnection.")
- return;
- } else {
- reconnecting = true;
- }
-
- if (config.onreconnecting) {
- config.onreconnecting();
- }
- }
-
- if (forcingDisconnection) {
- reconnect(maxRetries, numRetries, wsUri);
-
- } else {
- if (config.newWsUriOnReconnection) {
- config.newWsUriOnReconnection(function(error, newWsUri) {
-
- if (error) {
- console.log(error);
- setTimeout(function() {
- reconnectInNewUri(maxRetries, numRetries + 1);
- }, RETRY_TIME_MS);
- } else {
- reconnect(maxRetries, numRetries, newWsUri);
- }
- })
- } else {
- reconnect(maxRetries, numRetries, wsUri);
- }
- }
- }
-
- // TODO Test retries. How to force not connection?
- function reconnect(maxRetries, numRetries, reconnectWsUri) {
-
- console.log("Trying to reconnect " + numRetries + " times");
-
- var newWs;
- if (useSockJS) {
- newWs = new SockJS(wsUri);
- } else {
- newWs = new WebSocket(wsUri);
- }
-
- newWs.onopen = function() {
- console.log("Reconnected in " + numRetries + " retries...");
- logConnected(newWs, reconnectWsUri);
- reconnecting = false;
- registerMessageHandler();
- if (config.onreconnected()) {
- config.onreconnected();
- }
-
- newWs.onclose = reconnectionOnClose;
- };
-
- var onErrorOrClose = function(error) {
- console.log("Reconnection error: ", error);
-
- if (numRetries === maxRetries) {
- if (config.ondisconnect) {
- config.ondisconnect();
- }
- } else {
- setTimeout(function() {
- reconnectInNewUri(maxRetries, numRetries + 1);
- }, RETRY_TIME_MS);
- }
- };
-
- newWs.onerror = onErrorOrClose;
-
- ws = newWs;
- }
-
- this.close = function() {
- closing = true;
- ws.close();
- };
-
-
- // This method is only for testing
- this.forceClose = function(millis) {
- console.log("Testing: Force WebSocket close");
-
- if (millis) {
- console.log("Testing: Change wsUri for " + millis + " millis to simulate net failure");
- var goodWsUri = wsUri;
- wsUri = "wss://21.234.12.34.4:443/";
-
- forcingDisconnection = true;
-
- setTimeout(function() {
- console.log("Testing: Recover good wsUri " + goodWsUri);
- wsUri = goodWsUri;
-
- forcingDisconnection = false;
-
- }, millis);
- }
-
- ws.close();
- };
-
- this.reconnectWs = function() {
- console.log("reconnectWs");
- reconnectInNewUri(MAX_RETRIES, 1, wsUri);
- };
-
- this.send = function(message) {
- ws.send(message);
- };
-
- this.addEventListener = function(type, callback) {
- registerMessageHandler = function() {
- ws.addEventListener(type, callback);
- };
-
- registerMessageHandler();
- };
-}
-
-module.exports = WebSocketWithReconnection;
-},{"sockjs-client":33,"ws":103}],14:[function(require,module,exports){
-/*
- * (C) Copyright 2014 Kurento (http://kurento.org/)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-
-var defineProperty_IE8 = false
-if(Object.defineProperty)
-{
- try
- {
- Object.defineProperty({}, "x", {});
- }
- catch(e)
- {
- defineProperty_IE8 = true
- }
-}
-
-// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
-if (!Function.prototype.bind) {
- Function.prototype.bind = function(oThis) {
- if (typeof this !== 'function') {
- // closest thing possible to the ECMAScript 5
- // internal IsCallable function
- throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
- }
-
- var aArgs = Array.prototype.slice.call(arguments, 1),
- fToBind = this,
- fNOP = function() {},
- fBound = function() {
- return fToBind.apply(this instanceof fNOP && oThis
- ? this
- : oThis,
- aArgs.concat(Array.prototype.slice.call(arguments)));
- };
-
- fNOP.prototype = this.prototype;
- fBound.prototype = new fNOP();
-
- return fBound;
- };
-}
-
-
-var EventEmitter = require('events').EventEmitter;
-
-var inherits = require('inherits');
-
-var packers = require('./packers');
-var Mapper = require('./Mapper');
-
-
-var BASE_TIMEOUT = 5000;
-
-
-function unifyResponseMethods(responseMethods)
-{
- if(!responseMethods) return {};
-
- for(var key in responseMethods)
- {
- var value = responseMethods[key];
-
- if(typeof value == 'string')
- responseMethods[key] =
- {
- response: value
- }
- };
-
- return responseMethods;
-};
-
-function unifyTransport(transport)
-{
- if(!transport) return;
-
- // Transport as a function
- if(transport instanceof Function)
- return {send: transport};
-
- // WebSocket & DataChannel
- if(transport.send instanceof Function)
- return transport;
-
- // Message API (Inter-window & WebWorker)
- if(transport.postMessage instanceof Function)
- {
- transport.send = transport.postMessage;
- return transport;
- }
-
- // Stream API
- if(transport.write instanceof Function)
- {
- transport.send = transport.write;
- return transport;
- }
-
- // Transports that only can receive messages, but not send
- if(transport.onmessage !== undefined) return;
- if(transport.pause instanceof Function) return;
-
- throw new SyntaxError("Transport is not a function nor a valid object");
-};
-
-
-/**
- * Representation of a RPC notification
- *
- * @class
- *
- * @constructor
- *
- * @param {String} method -method of the notification
- * @param params - parameters of the notification
- */
-function RpcNotification(method, params)
-{
- if(defineProperty_IE8)
- {
- this.method = method
- this.params = params
- }
- else
- {
- Object.defineProperty(this, 'method', {value: method, enumerable: true});
- Object.defineProperty(this, 'params', {value: params, enumerable: true});
- }
-};
-
-
-/**
- * @class
- *
- * @constructor
- *
- * @param {object} packer
- *
- * @param {object} [options]
- *
- * @param {object} [transport]
- *
- * @param {Function} [onRequest]
- */
-function RpcBuilder(packer, options, transport, onRequest)
-{
- var self = this;
-
- if(!packer)
- throw new SyntaxError('Packer is not defined');
-
- if(!packer.pack || !packer.unpack)
- throw new SyntaxError('Packer is invalid');
-
- var responseMethods = unifyResponseMethods(packer.responseMethods);
-
-
- if(options instanceof Function)
- {
- if(transport != undefined)
- throw new SyntaxError("There can't be parameters after onRequest");
-
- onRequest = options;
- transport = undefined;
- options = undefined;
- };
-
- if(options && options.send instanceof Function)
- {
- if(transport && !(transport instanceof Function))
- throw new SyntaxError("Only a function can be after transport");
-
- onRequest = transport;
- transport = options;
- options = undefined;
- };
-
- if(transport instanceof Function)
- {
- if(onRequest != undefined)
- throw new SyntaxError("There can't be parameters after onRequest");
-
- onRequest = transport;
- transport = undefined;
- };
-
- if(transport && transport.send instanceof Function)
- if(onRequest && !(onRequest instanceof Function))
- throw new SyntaxError("Only a function can be after transport");
-
- options = options || {};
-
-
- EventEmitter.call(this);
-
- if(onRequest)
- this.on('request', onRequest);
-
-
- if(defineProperty_IE8)
- this.peerID = options.peerID
- else
- Object.defineProperty(this, 'peerID', {value: options.peerID});
-
- var max_retries = options.max_retries || 0;
-
-
- function transportMessage(event)
- {
- self.decode(event.data || event);
- };
-
- this.getTransport = function()
- {
- return transport;
- }
- this.setTransport = function(value)
- {
- // Remove listener from old transport
- if(transport)
- {
- // W3C transports
- if(transport.removeEventListener)
- transport.removeEventListener('message', transportMessage);
-
- // Node.js Streams API
- else if(transport.removeListener)
- transport.removeListener('data', transportMessage);
- };
-
- // Set listener on new transport
- if(value)
- {
- // W3C transports
- if(value.addEventListener)
- value.addEventListener('message', transportMessage);
-
- // Node.js Streams API
- else if(value.addListener)
- value.addListener('data', transportMessage);
- };
-
- transport = unifyTransport(value);
- }
-
- if(!defineProperty_IE8)
- Object.defineProperty(this, 'transport',
- {
- get: this.getTransport.bind(this),
- set: this.setTransport.bind(this)
- })
-
- this.setTransport(transport);
-
-
- var request_timeout = options.request_timeout || BASE_TIMEOUT;
- var response_timeout = options.response_timeout || BASE_TIMEOUT;
- var duplicates_timeout = options.duplicates_timeout || BASE_TIMEOUT;
-
-
- var requestID = 0;
-
- var requests = new Mapper();
- var responses = new Mapper();
- var processedResponses = new Mapper();
-
- var message2Key = {};
-
-
- /**
- * Store the response to prevent to process duplicate request later
- */
- function storeResponse(message, id, dest)
- {
- var response =
- {
- message: message,
- /** Timeout to auto-clean old responses */
- timeout: setTimeout(function()
- {
- responses.remove(id, dest);
- },
- response_timeout)
- };
-
- responses.set(response, id, dest);
- };
-
- /**
- * Store the response to ignore duplicated messages later
- */
- function storeProcessedResponse(ack, from)
- {
- var timeout = setTimeout(function()
- {
- processedResponses.remove(ack, from);
- },
- duplicates_timeout);
-
- processedResponses.set(timeout, ack, from);
- };
-
-
- /**
- * Representation of a RPC request
- *
- * @class
- * @extends RpcNotification
- *
- * @constructor
- *
- * @param {String} method -method of the notification
- * @param params - parameters of the notification
- * @param {Integer} id - identifier of the request
- * @param [from] - source of the notification
- */
- function RpcRequest(method, params, id, from, transport)
- {
- RpcNotification.call(this, method, params);
-
- this.getTransport = function()
- {
- return transport;
- }
- this.setTransport = function(value)
- {
- transport = unifyTransport(value);
- }
-
- if(!defineProperty_IE8)
- Object.defineProperty(this, 'transport',
- {
- get: this.getTransport.bind(this),
- set: this.setTransport.bind(this)
- })
-
- var response = responses.get(id, from);
-
- /**
- * @constant {Boolean} duplicated
- */
- if(!(transport || self.getTransport()))
- {
- if(defineProperty_IE8)
- this.duplicated = Boolean(response)
- else
- Object.defineProperty(this, 'duplicated',
- {
- value: Boolean(response)
- });
- }
-
- var responseMethod = responseMethods[method];
-
- this.pack = packer.pack.bind(packer, this, id)
-
- /**
- * Generate a response to this request
- *
- * @param {Error} [error]
- * @param {*} [result]
- *
- * @returns {string}
- */
- this.reply = function(error, result, transport)
- {
- // Fix optional parameters
- if(error instanceof Function || error && error.send instanceof Function)
- {
- if(result != undefined)
- throw new SyntaxError("There can't be parameters after callback");
-
- transport = error;
- result = null;
- error = undefined;
- }
-
- else if(result instanceof Function
- || result && result.send instanceof Function)
- {
- if(transport != undefined)
- throw new SyntaxError("There can't be parameters after callback");
-
- transport = result;
- result = null;
- };
-
- transport = unifyTransport(transport);
-
- // Duplicated request, remove old response timeout
- if(response)
- clearTimeout(response.timeout);
-
- if(from != undefined)
- {
- if(error)
- error.dest = from;
-
- if(result)
- result.dest = from;
- };
-
- var message;
-
- // New request or overriden one, create new response with provided data
- if(error || result != undefined)
- {
- if(self.peerID != undefined)
- {
- if(error)
- error.from = self.peerID;
- else
- result.from = self.peerID;
- }
-
- // Protocol indicates that responses has own request methods
- if(responseMethod)
- {
- if(responseMethod.error == undefined && error)
- message =
- {
- error: error
- };
-
- else
- {
- var method = error
- ? responseMethod.error
- : responseMethod.response;
-
- message =
- {
- method: method,
- params: error || result
- };
- }
- }
- else
- message =
- {
- error: error,
- result: result
- };
-
- message = packer.pack(message, id);
- }
-
- // Duplicate & not-overriden request, re-send old response
- else if(response)
- message = response.message;
-
- // New empty reply, response null value
- else
- message = packer.pack({result: null}, id);
-
- // Store the response to prevent to process a duplicated request later
- storeResponse(message, id, from);
-
- // Return the stored response so it can be directly send back
- transport = transport || this.getTransport() || self.getTransport();
-
- if(transport)
- return transport.send(message);
-
- return message;
- }
- };
- inherits(RpcRequest, RpcNotification);
-
-
- function cancel(message)
- {
- var key = message2Key[message];
- if(!key) return;
-
- delete message2Key[message];
-
- var request = requests.pop(key.id, key.dest);
- if(!request) return;
-
- clearTimeout(request.timeout);
-
- // Start duplicated responses timeout
- storeProcessedResponse(key.id, key.dest);
- };
-
- /**
- * Allow to cancel a request and don't wait for a response
- *
- * If `message` is not given, cancel all the request
- */
- this.cancel = function(message)
- {
- if(message) return cancel(message);
-
- for(var message in message2Key)
- cancel(message);
- };
-
-
- this.close = function()
- {
- // Prevent to receive new messages
- var transport = this.getTransport();
- if(transport && transport.close)
- transport.close();
-
- // Request & processed responses
- this.cancel();
-
- processedResponses.forEach(clearTimeout);
-
- // Responses
- responses.forEach(function(response)
- {
- clearTimeout(response.timeout);
- });
- };
-
-
- /**
- * Generates and encode a JsonRPC 2.0 message
- *
- * @param {String} method -method of the notification
- * @param params - parameters of the notification
- * @param [dest] - destination of the notification
- * @param {object} [transport] - transport where to send the message
- * @param [callback] - function called when a response to this request is
- * received. If not defined, a notification will be send instead
- *
- * @returns {string} A raw JsonRPC 2.0 request or notification string
- */
- this.encode = function(method, params, dest, transport, callback)
- {
- // Fix optional parameters
- if(params instanceof Function)
- {
- if(dest != undefined)
- throw new SyntaxError("There can't be parameters after callback");
-
- callback = params;
- transport = undefined;
- dest = undefined;
- params = undefined;
- }
-
- else if(dest instanceof Function)
- {
- if(transport != undefined)
- throw new SyntaxError("There can't be parameters after callback");
-
- callback = dest;
- transport = undefined;
- dest = undefined;
- }
-
- else if(transport instanceof Function)
- {
- if(callback != undefined)
- throw new SyntaxError("There can't be parameters after callback");
-
- callback = transport;
- transport = undefined;
- };
-
- if(self.peerID != undefined)
- {
- params = params || {};
-
- params.from = self.peerID;
- };
-
- if(dest != undefined)
- {
- params = params || {};
-
- params.dest = dest;
- };
-
- // Encode message
- var message =
- {
- method: method,
- params: params
- };
-
- if(callback)
- {
- var id = requestID++;
- var retried = 0;
-
- message = packer.pack(message, id);
-
- function dispatchCallback(error, result)
- {
- self.cancel(message);
-
- callback(error, result);
- };
-
- var request =
- {
- message: message,
- callback: dispatchCallback,
- responseMethods: responseMethods[method] || {}
- };
-
- var encode_transport = unifyTransport(transport);
-
- function sendRequest(transport)
- {
- request.timeout = setTimeout(timeout,
- request_timeout*Math.pow(2, retried++));
- message2Key[message] = {id: id, dest: dest};
- requests.set(request, id, dest);
-
- transport = transport || encode_transport || self.getTransport();
- if(transport)
- return transport.send(message);
-
- return message;
- };
-
- function retry(transport)
- {
- transport = unifyTransport(transport);
-
- console.warn(retried+' retry for request message:',message);
-
- var timeout = processedResponses.pop(id, dest);
- clearTimeout(timeout);
-
- return sendRequest(transport);
- };
-
- function timeout()
- {
- if(retried < max_retries)
- return retry(transport);
-
- var error = new Error('Request has timed out');
- error.request = message;
-
- error.retry = retry;
-
- dispatchCallback(error)
- };
-
- return sendRequest(transport);
- };
-
- // Return the packed message
- message = packer.pack(message);
-
- transport = transport || this.getTransport();
- if(transport)
- return transport.send(message);
-
- return message;
- };
-
- /**
- * Decode and process a JsonRPC 2.0 message
- *
- * @param {string} message - string with the content of the message
- *
- * @returns {RpcNotification|RpcRequest|undefined} - the representation of the
- * notification or the request. If a response was processed, it will return
- * `undefined` to notify that it was processed
- *
- * @throws {TypeError} - Message is not defined
- */
- this.decode = function(message, transport)
- {
- if(!message)
- throw new TypeError("Message is not defined");
-
- try
- {
- message = packer.unpack(message);
- }
- catch(e)
- {
- // Ignore invalid messages
- return console.log(e, message);
- };
-
- var id = message.id;
- var ack = message.ack;
- var method = message.method;
- var params = message.params || {};
-
- var from = params.from;
- var dest = params.dest;
-
- // Ignore messages send by us
- if(self.peerID != undefined && from == self.peerID) return;
-
- // Notification
- if(id == undefined && ack == undefined)
- {
- var notification = new RpcNotification(method, params);
-
- if(self.emit('request', notification)) return;
- return notification;
- };
-
-
- function processRequest()
- {
- // If we have a transport and it's a duplicated request, reply inmediatly
- transport = unifyTransport(transport) || self.getTransport();
- if(transport)
- {
- var response = responses.get(id, from);
- if(response)
- return transport.send(response.message);
- };
-
- var idAck = (id != undefined) ? id : ack;
- var request = new RpcRequest(method, params, idAck, from, transport);
-
- if(self.emit('request', request)) return;
- return request;
- };
-
- function processResponse(request, error, result)
- {
- request.callback(error, result);
- };
-
- function duplicatedResponse(timeout)
- {
- console.warn("Response already processed", message);
-
- // Update duplicated responses timeout
- clearTimeout(timeout);
- storeProcessedResponse(ack, from);
- };
-
-
- // Request, or response with own method
- if(method)
- {
- // Check if it's a response with own method
- if(dest == undefined || dest == self.peerID)
- {
- var request = requests.get(ack, from);
- if(request)
- {
- var responseMethods = request.responseMethods;
-
- if(method == responseMethods.error)
- return processResponse(request, params);
-
- if(method == responseMethods.response)
- return processResponse(request, null, params);
-
- return processRequest();
- }
-
- var processed = processedResponses.get(ack, from);
- if(processed)
- return duplicatedResponse(processed);
- }
-
- // Request
- return processRequest();
- };
-
- var error = message.error;
- var result = message.result;
-
- // Ignore responses not send to us
- if(error && error.dest && error.dest != self.peerID) return;
- if(result && result.dest && result.dest != self.peerID) return;
-
- // Response
- var request = requests.get(ack, from);
- if(!request)
- {
- var processed = processedResponses.get(ack, from);
- if(processed)
- return duplicatedResponse(processed);
-
- return console.warn("No callback was defined for this message", message);
- };
-
- // Process response
- processResponse(request, error, result);
- };
-};
-inherits(RpcBuilder, EventEmitter);
-
-
-RpcBuilder.RpcNotification = RpcNotification;
-
-
-module.exports = RpcBuilder;
-
-var clients = require('./clients');
-var transports = require('./clients/transports');
-
-RpcBuilder.clients = clients;
-RpcBuilder.clients.transports = transports;
-RpcBuilder.packers = packers;
-
-},{"./Mapper":9,"./clients":10,"./clients/transports":12,"./packers":17,"events":113,"inherits":7}],15:[function(require,module,exports){
-/**
- * JsonRPC 2.0 packer
- */
-
-/**
- * Pack a JsonRPC 2.0 message
- *
- * @param {Object} message - object to be packaged. It requires to have all the
- * fields needed by the JsonRPC 2.0 message that it's going to be generated
- *
- * @return {String} - the stringified JsonRPC 2.0 message
- */
-function pack(message, id)
-{
- var result =
- {
- jsonrpc: "2.0"
- };
-
- // Request
- if(message.method)
- {
- result.method = message.method;
-
- if(message.params)
- result.params = message.params;
-
- // Request is a notification
- if(id != undefined)
- result.id = id;
- }
-
- // Response
- else if(id != undefined)
- {
- if(message.error)
- {
- if(message.result !== undefined)
- throw new TypeError("Both result and error are defined");
-
- result.error = message.error;
- }
- else if(message.result !== undefined)
- result.result = message.result;
- else
- throw new TypeError("No result or error is defined");
-
- result.id = id;
- };
-
- return JSON.stringify(result);
-};
-
-/**
- * Unpack a JsonRPC 2.0 message
- *
- * @param {String} message - string with the content of the JsonRPC 2.0 message
- *
- * @throws {TypeError} - Invalid JsonRPC version
- *
- * @return {Object} - object filled with the JsonRPC 2.0 message content
- */
-function unpack(message)
-{
- var result = message;
-
- if(typeof message === 'string' || message instanceof String)
- result = JSON.parse(message);
-
- // Check if it's a valid message
-
- var version = result.jsonrpc;
- if(version !== '2.0')
- throw new TypeError("Invalid JsonRPC version '" + version + "': " + message);
-
- // Response
- if(result.method == undefined)
- {
- if(result.id == undefined)
- throw new TypeError("Invalid message: "+message);
-
- var result_defined = result.result !== undefined;
- var error_defined = result.error !== undefined;
-
- // Check only result or error is defined, not both or none
- if(result_defined && error_defined)
- throw new TypeError("Both result and error are defined: "+message);
-
- if(!result_defined && !error_defined)
- throw new TypeError("No result or error is defined: "+message);
-
- result.ack = result.id;
- delete result.id;
- }
-
- // Return unpacked message
- return result;
-};
-
-
-exports.pack = pack;
-exports.unpack = unpack;
-
-},{}],16:[function(require,module,exports){
-function pack(message)
-{
- throw new TypeError("Not yet implemented");
-};
-
-function unpack(message)
-{
- throw new TypeError("Not yet implemented");
-};
-
-
-exports.pack = pack;
-exports.unpack = unpack;
-
-},{}],17:[function(require,module,exports){
-var JsonRPC = require('./JsonRPC');
-var XmlRPC = require('./XmlRPC');
-
-
-exports.JsonRPC = JsonRPC;
-exports.XmlRPC = XmlRPC;
-
-},{"./JsonRPC":15,"./XmlRPC":16}],18:[function(require,module,exports){
-/*
- * (C) Copyright 2014-2015 Kurento (http://kurento.org/)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var freeice = require('freeice')
-var inherits = require('inherits')
-var UAParser = require('ua-parser-js')
-var uuid = require('uuid')
-var hark = require('hark')
-
-var EventEmitter = require('events').EventEmitter
-var recursive = require('merge').recursive.bind(undefined, true)
-var sdpTranslator = require('sdp-translator')
-var logger = window.Logger || console
-
-// var gUM = navigator.mediaDevices.getUserMedia || function (constraints) {
-// return new Promise(navigator.getUserMedia(constraints, function (stream) {
-// videoStream = stream
-// start()
-// }).eror(callback));
-// }
-
-try {
- require('kurento-browser-extensions')
-} catch (error) {
- if (typeof getScreenConstraints === 'undefined') {
- logger.warn('screen sharing is not available')
-
- getScreenConstraints = function getScreenConstraints(sendSource, callback) {
- callback(new Error("This library is not enabled for screen sharing"))
- }
- }
-}
-
-var MEDIA_CONSTRAINTS = {
- audio: true,
- video: {
- width: 640,
- framerate: 15
- }
-}
-
-// Somehow, the UAParser constructor gets an empty window object.
-// We need to pass the user agent string in order to get information
-var ua = (window && window.navigator) ? window.navigator.userAgent : ''
-var parser = new UAParser(ua)
-var browser = parser.getBrowser()
-
-var usePlanB = false
-if (browser.name === 'Chrome' || browser.name === 'Chromium') {
- logger.info(browser.name + ": using SDP PlanB")
- usePlanB = true
-}
-
-function noop(error) {
- if (error) logger.error(error)
-}
-
-function trackStop(track) {
- track.stop && track.stop()
-}
-
-function streamStop(stream) {
- stream.getTracks().forEach(trackStop)
-}
-
-/**
- * Returns a string representation of a SessionDescription object.
- */
-var dumpSDP = function (description) {
- if (typeof description === 'undefined' || description === null) {
- return ''
- }
-
- return 'type: ' + description.type + '\r\n' + description.sdp
-}
-
-function bufferizeCandidates(pc, onerror) {
- var candidatesQueue = []
-
- pc.addEventListener('signalingstatechange', function () {
- if (this.signalingState === 'stable') {
- while (candidatesQueue.length) {
- var entry = candidatesQueue.shift()
-
- this.addIceCandidate(entry.candidate, entry.callback, entry.callback)
- }
- }
- })
-
- return function (candidate, callback) {
- callback = callback || onerror
-
- switch (pc.signalingState) {
- case 'closed':
- callback(new Error('PeerConnection object is closed'))
- break
- case 'stable':
- if (pc.remoteDescription) {
- pc.addIceCandidate(candidate, callback, callback)
- break
- }
- default:
- candidatesQueue.push({
- candidate: candidate,
- callback: callback
- })
- }
- }
-}
-
-/* Simulcast utilities */
-
-function removeFIDFromOffer(sdp) {
- var n = sdp.indexOf("a=ssrc-group:FID");
-
- if (n > 0) {
- return sdp.slice(0, n);
- } else {
- return sdp;
- }
-}
-
-function getSimulcastInfo(videoStream) {
- var videoTracks = videoStream.getVideoTracks();
- if (!videoTracks.length) {
- logger.warn('No video tracks available in the video stream')
- return ''
- }
- var lines = [
- 'a=x-google-flag:conference',
- 'a=ssrc-group:SIM 1 2 3',
- 'a=ssrc:1 cname:localVideo',
- 'a=ssrc:1 msid:' + videoStream.id + ' ' + videoTracks[0].id,
- 'a=ssrc:1 mslabel:' + videoStream.id,
- 'a=ssrc:1 label:' + videoTracks[0].id,
- 'a=ssrc:2 cname:localVideo',
- 'a=ssrc:2 msid:' + videoStream.id + ' ' + videoTracks[0].id,
- 'a=ssrc:2 mslabel:' + videoStream.id,
- 'a=ssrc:2 label:' + videoTracks[0].id,
- 'a=ssrc:3 cname:localVideo',
- 'a=ssrc:3 msid:' + videoStream.id + ' ' + videoTracks[0].id,
- 'a=ssrc:3 mslabel:' + videoStream.id,
- 'a=ssrc:3 label:' + videoTracks[0].id
- ];
-
- lines.push('');
-
- return lines.join('\n');
-}
-
-/**
- * Wrapper object of an RTCPeerConnection. This object is aimed to simplify the
- * development of WebRTC-based applications.
- *
- * @constructor module:kurentoUtils.WebRtcPeer
- *
- * @param {String} mode Mode in which the PeerConnection will be configured.
- * Valid values are: 'recv', 'send', and 'sendRecv'
- * @param localVideo Video tag for the local stream
- * @param remoteVideo Video tag for the remote stream
- * @param {MediaStream} videoStream Stream to be used as primary source
- * (typically video and audio, or only video if combined with audioStream) for
- * localVideo and to be added as stream to the RTCPeerConnection
- * @param {MediaStream} audioStream Stream to be used as second source
- * (typically for audio) for localVideo and to be added as stream to the
- * RTCPeerConnection
- */
-function WebRtcPeer(mode, options, callback) {
- if (!(this instanceof WebRtcPeer)) {
- return new WebRtcPeer(mode, options, callback)
- }
-
- WebRtcPeer.super_.call(this)
-
- if (options instanceof Function) {
- callback = options
- options = undefined
- }
-
- options = options || {}
- callback = (callback || noop).bind(this)
-
- var self = this
- var localVideo = options.localVideo
- var remoteVideo = options.remoteVideo
- var videoStream = options.videoStream
- var audioStream = options.audioStream
- var mediaConstraints = options.mediaConstraints
-
- var connectionConstraints = options.connectionConstraints
- var pc = options.peerConnection
- var sendSource = options.sendSource || 'webcam'
-
- var dataChannelConfig = options.dataChannelConfig
- var useDataChannels = options.dataChannels || false
- var dataChannel
-
- var guid = uuid.v4()
- var configuration = recursive({
- iceServers: freeice()
- },
- options.configuration)
-
- var onicecandidate = options.onicecandidate
- if (onicecandidate) this.on('icecandidate', onicecandidate)
-
- var oncandidategatheringdone = options.oncandidategatheringdone
- if (oncandidategatheringdone) {
- this.on('candidategatheringdone', oncandidategatheringdone)
- }
-
- var simulcast = options.simulcast
- var multistream = options.multistream
- var interop = new sdpTranslator.Interop()
- var candidatesQueueOut = []
- var candidategatheringdone = false
-
- Object.defineProperties(this, {
- 'peerConnection': {
- get: function () {
- return pc
- }
- },
-
- 'id': {
- value: options.id || guid,
- writable: false
- },
-
- 'remoteVideo': {
- get: function () {
- return remoteVideo
- }
- },
-
- 'localVideo': {
- get: function () {
- return localVideo
- }
- },
-
- 'dataChannel': {
- get: function () {
- return dataChannel
- }
- },
-
- /**
- * @member {(external:ImageData|undefined)} currentFrame
- */
- 'currentFrame': {
- get: function () {
- // [ToDo] Find solution when we have a remote stream but we didn't set
- // a remoteVideo tag
- if (!remoteVideo) return;
-
- if (remoteVideo.readyState < remoteVideo.HAVE_CURRENT_DATA)
- throw new Error('No video stream data available')
-
- var canvas = document.createElement('canvas')
- canvas.width = remoteVideo.videoWidth
- canvas.height = remoteVideo.videoHeight
-
- canvas.getContext('2d').drawImage(remoteVideo, 0, 0)
-
- return canvas
- }
- }
- })
-
- // Init PeerConnection
- if (!pc) {
- pc = new RTCPeerConnection(configuration);
- if (useDataChannels && !dataChannel) {
- var dcId = 'WebRtcPeer-' + self.id
- var dcOptions = undefined
- if (dataChannelConfig) {
- dcId = dataChannelConfig.id || dcId
- dcOptions = dataChannelConfig.options
- }
- dataChannel = pc.createDataChannel(dcId, dcOptions);
- if (dataChannelConfig) {
- dataChannel.onopen = dataChannelConfig.onopen;
- dataChannel.onclose = dataChannelConfig.onclose;
- dataChannel.onmessage = dataChannelConfig.onmessage;
- dataChannel.onbufferedamountlow = dataChannelConfig.onbufferedamountlow;
- dataChannel.onerror = dataChannelConfig.onerror || noop;
- }
- }
- }
-
- pc.addEventListener('icecandidate', function (event) {
- var candidate = event.candidate
-
- if (EventEmitter.listenerCount(self, 'icecandidate') ||
- EventEmitter.listenerCount(
- self, 'candidategatheringdone')) {
- if (candidate) {
- var cand
-
- if (multistream && usePlanB) {
- cand = interop.candidateToUnifiedPlan(candidate)
- } else {
- cand = candidate
- }
-
- self.emit('icecandidate', cand)
- candidategatheringdone = false
- } else if (!candidategatheringdone) {
- self.emit('candidategatheringdone')
- candidategatheringdone = true
- }
- } else if (!candidategatheringdone) {
- // Not listening to 'icecandidate' or 'candidategatheringdone' events, queue
- // the candidate until one of them is listened
- candidatesQueueOut.push(candidate)
-
- if (!candidate) candidategatheringdone = true
- }
- })
-
- pc.ontrack = options.onaddstream
- pc.onnegotiationneeded = options.onnegotiationneeded
- this.on('newListener', function (event, listener) {
- if (event === 'icecandidate' || event === 'candidategatheringdone') {
- while (candidatesQueueOut.length) {
- var candidate = candidatesQueueOut.shift()
-
- if (!candidate === (event === 'candidategatheringdone')) {
- listener(candidate)
- }
- }
- }
- })
-
- var addIceCandidate = bufferizeCandidates(pc)
-
- /**
- * Callback function invoked when an ICE candidate is received. Developers are
- * expected to invoke this function in order to complete the SDP negotiation.
- *
- * @function module:kurentoUtils.WebRtcPeer.prototype.addIceCandidate
- *
- * @param iceCandidate - Literal object with the ICE candidate description
- * @param callback - Called when the ICE candidate has been added.
- */
- this.addIceCandidate = function (iceCandidate, callback) {
- var candidate
-
- if (multistream && usePlanB) {
- candidate = interop.candidateToPlanB(iceCandidate)
- } else {
- candidate = new RTCIceCandidate(iceCandidate)
- }
-
- logger.debug('Remote ICE candidate received', iceCandidate)
- callback = (callback || noop).bind(this)
- addIceCandidate(candidate, callback)
- }
-
- this.generateOffer = function (callback) {
- callback = callback.bind(this)
-
- var offerAudio = true
- var offerVideo = true
- // Constraints must have both blocks
- if (mediaConstraints) {
- offerAudio = (typeof mediaConstraints.audio === 'boolean') ?
- mediaConstraints.audio : true
- offerVideo = (typeof mediaConstraints.video === 'boolean') ?
- mediaConstraints.video : true
- }
-
- var browserDependantConstraints = {
- offerToReceiveAudio: (mode !== 'sendonly' && offerAudio),
- offerToReceiveVideo: (mode !== 'sendonly' && offerVideo)
- }
-
- //FIXME: clarify possible constraints passed to createOffer()
- /*var constraints = recursive(browserDependantConstraints,
- connectionConstraints)*/
-
- var constraints = browserDependantConstraints;
-
- logger.info('constraints: ' + JSON.stringify(constraints))
-
- pc.createOffer(constraints).then(function (offer) {
- logger.info('Created SDP offer')
- offer = mangleSdpToAddSimulcast(offer)
- return pc.setLocalDescription(offer)
- }).then(function () {
- var localDescription = pc.localDescription
- logger.info('Local description set', localDescription.sdp)
- if (multistream && usePlanB) {
- localDescription = interop.toUnifiedPlan(localDescription)
- logger.info('offer::origPlanB->UnifiedPlan', dumpSDP(
- localDescription))
- }
- callback(null, localDescription.sdp, self.processAnswer.bind(
- self))
- }).catch(callback)
- }
-
- this.getLocalSessionDescriptor = function () {
- return pc.localDescription
- }
-
- this.getRemoteSessionDescriptor = function () {
- return pc.remoteDescription
- }
-
- function setRemoteVideo() {
- if (remoteVideo) {
- var stream = pc.getRemoteStreams()[0]
- var url = stream ? URL.createObjectURL(stream) : ''
-
- remoteVideo.pause()
- remoteVideo.src = url
- remoteVideo.load()
-
- logger.info('Remote URL:', url)
- }
- }
-
- this.showLocalVideo = function () {
- localVideo.src = URL.createObjectURL(videoStream)
- localVideo.muted = true
- }
-
- this.send = function (data) {
- if (dataChannel && dataChannel.readyState === 'open') {
- dataChannel.send(data)
- } else {
- logger.warn(
- 'Trying to send data over a non-existing or closed data channel')
- }
- }
-
- /**
- * Callback function invoked when a SDP answer is received. Developers are
- * expected to invoke this function in order to complete the SDP negotiation.
- *
- * @function module:kurentoUtils.WebRtcPeer.prototype.processAnswer
- *
- * @param sdpAnswer - Description of sdpAnswer
- * @param callback -
- * Invoked after the SDP answer is processed, or there is an error.
- */
- this.processAnswer = function (sdpAnswer, callback) {
- callback = (callback || noop).bind(this)
-
- var answer = new RTCSessionDescription({
- type: 'answer',
- sdp: sdpAnswer
- })
-
- if (multistream && usePlanB) {
- var planBAnswer = interop.toPlanB(answer)
- logger.info('asnwer::planB', dumpSDP(planBAnswer))
- answer = planBAnswer
- }
-
- logger.info('SDP answer received, setting remote description')
-
- if (pc.signalingState === 'closed') {
- return callback('PeerConnection is closed')
- }
-
- pc.setRemoteDescription(answer, function () {
- setRemoteVideo()
-
- callback()
- },
- callback)
- }
-
- /**
- * Callback function invoked when a SDP offer is received. Developers are
- * expected to invoke this function in order to complete the SDP negotiation.
- *
- * @function module:kurentoUtils.WebRtcPeer.prototype.processOffer
- *
- * @param sdpOffer - Description of sdpOffer
- * @param callback - Called when the remote description has been set
- * successfully.
- */
- this.processOffer = function (sdpOffer, callback) {
- callback = callback.bind(this)
-
- var offer = new RTCSessionDescription({
- type: 'offer',
- sdp: sdpOffer
- })
-
- if (multistream && usePlanB) {
- var planBOffer = interop.toPlanB(offer)
- logger.info('offer::planB', dumpSDP(planBOffer))
- offer = planBOffer
- }
-
- logger.info('SDP offer received, setting remote description')
-
- if (pc.signalingState === 'closed') {
- return callback('PeerConnection is closed')
- }
-
- pc.setRemoteDescription(offer).then(function () {
- return setRemoteVideo()
- }).then(function () {
- return pc.createAnswer()
- }).then(function (answer) {
- answer = mangleSdpToAddSimulcast(answer)
- logger.info('Created SDP answer')
- return pc.setLocalDescription(answer)
- }).then(function () {
- var localDescription = pc.localDescription
- if (multistream && usePlanB) {
- localDescription = interop.toUnifiedPlan(localDescription)
- logger.info('answer::origPlanB->UnifiedPlan', dumpSDP(
- localDescription))
- }
- logger.info('Local description set', localDescription.sdp)
- callback(null, localDescription.sdp)
- }).catch(callback)
- }
-
- function mangleSdpToAddSimulcast(answer) {
- if (simulcast) {
- if (browser.name === 'Chrome' || browser.name === 'Chromium') {
- logger.info('Adding multicast info')
- answer = new RTCSessionDescription({
- 'type': answer.type,
- 'sdp': removeFIDFromOffer(answer.sdp) + getSimulcastInfo(
- videoStream)
- })
- } else {
- logger.warn('Simulcast is only available in Chrome browser.')
- }
- }
-
- return answer
- }
-
- /**
- * This function creates the RTCPeerConnection object taking into account the
- * properties received in the constructor. It starts the SDP negotiation
- * process: generates the SDP offer and invokes the onsdpoffer callback. This
- * callback is expected to send the SDP offer, in order to obtain an SDP
- * answer from another peer.
- */
- function start() {
- if (pc.signalingState === 'closed') {
- callback(
- 'The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue'
- )
- }
-
- if (videoStream && localVideo) {
- self.showLocalVideo()
- }
-
- if (videoStream) {
- pc.addStream(videoStream)
- }
-
- if (audioStream) {
- pc.addStream(audioStream)
- }
-
- // [Hack] https://code.google.com/p/chromium/issues/detail?id=443558
- var browser = parser.getBrowser()
- if (mode === 'sendonly' &&
- (browser.name === 'Chrome' || browser.name === 'Chromium') &&
- browser.major === 39) {
- mode = 'sendrecv'
- }
-
- callback()
- }
-
- if (mode !== 'recvonly' && !videoStream && !audioStream) {
- function getMedia(constraints) {
- if (constraints === undefined) {
- constraints = MEDIA_CONSTRAINTS
- }
-
- navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
- videoStream = stream
- start()
- }).catch(callback);
- }
- if (sendSource === 'webcam') {
- getMedia(mediaConstraints)
- } else {
- getScreenConstraints(sendSource, function (error, constraints_) {
- if (error)
- return callback(error)
-
- constraints = [mediaConstraints]
- constraints.unshift(constraints_)
- getMedia(recursive.apply(undefined, constraints))
- }, guid)
- }
- } else {
- setTimeout(start, 0)
- }
-
- this.on('_dispose', function () {
- if (localVideo) {
- localVideo.pause()
- localVideo.src = ''
- localVideo.load()
- //Unmute local video in case the video tag is later used for remote video
- localVideo.muted = false
- }
- if (remoteVideo) {
- remoteVideo.pause()
- remoteVideo.src = ''
- remoteVideo.load()
- }
- self.removeAllListeners()
-
- if (window.cancelChooseDesktopMedia !== undefined) {
- window.cancelChooseDesktopMedia(guid)
- }
- })
-}
-inherits(WebRtcPeer, EventEmitter)
-
-function createEnableDescriptor(type) {
- var method = 'get' + type + 'Tracks'
-
- return {
- enumerable: true,
- get: function () {
- // [ToDo] Should return undefined if not all tracks have the same value?
-
- if (!this.peerConnection) return
-
- var streams = this.peerConnection.getLocalStreams()
- if (!streams.length) return
-
- for (var i = 0, stream; stream = streams[i]; i++) {
- var tracks = stream[method]()
- for (var j = 0, track; track = tracks[j]; j++)
- if (!track.enabled) return false
- }
-
- return true
- },
- set: function (value) {
- function trackSetEnable(track) {
- track.enabled = value
- }
-
- this.peerConnection.getLocalStreams().forEach(function (stream) {
- stream[method]().forEach(trackSetEnable)
- })
- }
- }
-}
-
-Object.defineProperties(WebRtcPeer.prototype, {
- 'enabled': {
- enumerable: true,
- get: function () {
- return this.audioEnabled && this.videoEnabled
- },
- set: function (value) {
- this.audioEnabled = this.videoEnabled = value
- }
- },
- 'audioEnabled': createEnableDescriptor('Audio'),
- 'videoEnabled': createEnableDescriptor('Video')
-})
-
-WebRtcPeer.prototype.getLocalStream = function (index) {
- if (this.peerConnection) {
- return this.peerConnection.getLocalStreams()[index || 0]
- }
-}
-
-WebRtcPeer.prototype.getRemoteStream = function (index) {
- if (this.peerConnection) {
- return this.peerConnection.getRemoteStreams()[index || 0]
- }
-}
-
-/**
- * @description This method frees the resources used by WebRtcPeer.
- *
- * @function module:kurentoUtils.WebRtcPeer.prototype.dispose
- */
-WebRtcPeer.prototype.dispose = function () {
- logger.info('Disposing WebRtcPeer')
-
- var pc = this.peerConnection
- var dc = this.dataChannel
- try {
- if (dc) {
- if (dc.signalingState === 'closed') return
-
- dc.close()
- }
-
- if (pc) {
- if (pc.signalingState === 'closed') return
-
- pc.getLocalStreams().forEach(streamStop)
-
- // FIXME This is not yet implemented in firefox
- // if(videoStream) pc.removeStream(videoStream);
- // if(audioStream) pc.removeStream(audioStream);
-
- pc.close()
- }
- } catch (err) {
- logger.warn('Exception disposing webrtc peer ' + err)
- }
-
- this.emit('_dispose')
-}
-
-//
-// Specialized child classes
-//
-
-function WebRtcPeerRecvonly(options, callback) {
- if (!(this instanceof WebRtcPeerRecvonly)) {
- return new WebRtcPeerRecvonly(options, callback)
- }
-
- WebRtcPeerRecvonly.super_.call(this, 'recvonly', options, callback)
-}
-inherits(WebRtcPeerRecvonly, WebRtcPeer)
-
-function WebRtcPeerSendonly(options, callback) {
- if (!(this instanceof WebRtcPeerSendonly)) {
- return new WebRtcPeerSendonly(options, callback)
- }
-
- WebRtcPeerSendonly.super_.call(this, 'sendonly', options, callback)
-}
-inherits(WebRtcPeerSendonly, WebRtcPeer)
-
-function WebRtcPeerSendrecv(options, callback) {
- if (!(this instanceof WebRtcPeerSendrecv)) {
- return new WebRtcPeerSendrecv(options, callback)
- }
-
- WebRtcPeerSendrecv.super_.call(this, 'sendrecv', options, callback)
-}
-inherits(WebRtcPeerSendrecv, WebRtcPeer)
-
-function harkUtils(stream, options) {
- return hark(stream, options);
-}
-
-exports.bufferizeCandidates = bufferizeCandidates
-
-exports.WebRtcPeerRecvonly = WebRtcPeerRecvonly
-exports.WebRtcPeerSendonly = WebRtcPeerSendonly
-exports.WebRtcPeerSendrecv = WebRtcPeerSendrecv
-exports.hark = harkUtils
-
-},{"events":113,"freeice":3,"hark":6,"inherits":7,"kurento-browser-extensions":undefined,"merge":20,"sdp-translator":29,"ua-parser-js":86,"uuid":90}],19:[function(require,module,exports){
-/*
- * (C) Copyright 2014 Kurento (http://kurento.org/)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-/**
- * This module contains a set of reusable components that have been found useful
- * during the development of the WebRTC applications with Kurento.
- *
- * @module kurentoUtils
- *
- * @copyright 2014 Kurento (http://kurento.org/)
- * @license ALv2
- */
-
-var WebRtcPeer = require('./WebRtcPeer');
-
-exports.WebRtcPeer = WebRtcPeer;
-
-},{"./WebRtcPeer":18}],20:[function(require,module,exports){
-/*!
- * @name JavaScript/NodeJS Merge v1.2.0
- * @author yeikos
- * @repository https://github.com/yeikos/js.merge
-
- * Copyright 2014 yeikos - MIT license
- * https://raw.github.com/yeikos/js.merge/master/LICENSE
- */
-
-;(function(isNode) {
-
- /**
- * Merge one or more objects
- * @param bool? clone
- * @param mixed,... arguments
- * @return object
- */
-
- var Public = function(clone) {
-
- return merge(clone === true, false, arguments);
-
- }, publicName = 'merge';
-
- /**
- * Merge two or more objects recursively
- * @param bool? clone
- * @param mixed,... arguments
- * @return object
- */
-
- Public.recursive = function(clone) {
-
- return merge(clone === true, true, arguments);
-
- };
-
- /**
- * Clone the input removing any reference
- * @param mixed input
- * @return mixed
- */
-
- Public.clone = function(input) {
-
- var output = input,
- type = typeOf(input),
- index, size;
-
- if (type === 'array') {
-
- output = [];
- size = input.length;
-
- for (index=0;index 0) {
- return parse(val);
- } else if (type === 'number' && isNaN(val) === false) {
- return options.long ? fmtLong(val) : fmtShort(val);
- }
- throw new Error(
- 'val is not a non-empty string or a valid number. val=' +
- JSON.stringify(val)
- );
-};
-
-/**
- * Parse the given `str` and return milliseconds.
- *
- * @param {String} str
- * @return {Number}
- * @api private
- */
-
-function parse(str) {
- str = String(str);
- if (str.length > 100) {
- return;
- }
- var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(
- str
- );
- if (!match) {
- return;
- }
- var n = parseFloat(match[1]);
- var type = (match[2] || 'ms').toLowerCase();
- switch (type) {
- case 'years':
- case 'year':
- case 'yrs':
- case 'yr':
- case 'y':
- return n * y;
- case 'days':
- case 'day':
- case 'd':
- return n * d;
- case 'hours':
- case 'hour':
- case 'hrs':
- case 'hr':
- case 'h':
- return n * h;
- case 'minutes':
- case 'minute':
- case 'mins':
- case 'min':
- case 'm':
- return n * m;
- case 'seconds':
- case 'second':
- case 'secs':
- case 'sec':
- case 's':
- return n * s;
- case 'milliseconds':
- case 'millisecond':
- case 'msecs':
- case 'msec':
- case 'ms':
- return n;
- default:
- return undefined;
- }
-}
-
-/**
- * Short format for `ms`.
- *
- * @param {Number} ms
- * @return {String}
- * @api private
- */
-
-function fmtShort(ms) {
- if (ms >= d) {
- return Math.round(ms / d) + 'd';
- }
- if (ms >= h) {
- return Math.round(ms / h) + 'h';
- }
- if (ms >= m) {
- return Math.round(ms / m) + 'm';
- }
- if (ms >= s) {
- return Math.round(ms / s) + 's';
- }
- return ms + 'ms';
-}
-
-/**
- * Long format for `ms`.
- *
- * @param {Number} ms
- * @return {String}
- * @api private
- */
-
-function fmtLong(ms) {
- return plural(ms, d, 'day') ||
- plural(ms, h, 'hour') ||
- plural(ms, m, 'minute') ||
- plural(ms, s, 'second') ||
- ms + ' ms';
-}
-
-/**
- * Pluralization helper.
- */
-
-function plural(ms, n, name) {
- if (ms < n) {
- return;
- }
- if (ms < n * 1.5) {
- return Math.floor(ms / n) + ' ' + name;
- }
- return Math.ceil(ms / n) + ' ' + name + 's';
-}
-
-},{}],22:[function(require,module,exports){
-/**
- # normalice
-
- Normalize an ice server configuration object (or plain old string) into a format
- that is usable in all browsers supporting WebRTC. Primarily this module is designed
- to help with the transition of the `url` attribute of the configuration object to
- the `urls` attribute.
-
- ## Example Usage
-
- <<< examples/simple.js
-
-**/
-
-var protocols = [
- 'stun:',
- 'turn:'
-];
-
-module.exports = function(input) {
- var url = (input || {}).url || input;
- var protocol;
- var parts;
- var output = {};
-
- // if we don't have a string url, then allow the input to passthrough
- if (typeof url != 'string' && (! (url instanceof String))) {
- return input;
- }
-
- // trim the url string, and convert to an array
- url = url.trim();
-
- // if the protocol is not known, then passthrough
- protocol = protocols[protocols.indexOf(url.slice(0, 5))];
- if (! protocol) {
- return input;
- }
-
- // now let's attack the remaining url parts
- url = url.slice(5);
- parts = url.split('@');
-
- output.username = input.username;
- output.credential = input.credential;
- // if we have an authentication part, then set the credentials
- if (parts.length > 1) {
- url = parts[1];
- parts = parts[0].split(':');
-
- // add the output credential and username
- output.username = parts[0];
- output.credential = (input || {}).credential || parts[1] || '';
- }
-
- output.url = protocol + url;
- output.urls = [ output.url ];
-
- return output;
-};
-
-},{}],23:[function(require,module,exports){
-'use strict';
-
-/**
- * Check if we're required to add a port number.
- *
- * @see https://url.spec.whatwg.org/#default-port
- * @param {Number|String} port Port number we need to check
- * @param {String} protocol Protocol we need to check against.
- * @returns {Boolean} Is it a default port for the given protocol
- * @api private
- */
-module.exports = function required(port, protocol) {
- protocol = protocol.split(':')[0];
- port = +port;
-
- if (!port) return false;
-
- switch (protocol) {
- case 'http':
- case 'ws':
- return port !== 80;
-
- case 'https':
- case 'wss':
- return port !== 443;
-
- case 'ftp':
- return port !== 21;
-
- case 'gopher':
- return port !== 70;
-
- case 'file':
- return false;
- }
-
- return port !== 0;
-};
-
-},{}],24:[function(require,module,exports){
-var grammar = module.exports = {
- v: [{
- name: 'version',
- reg: /^(\d*)$/
- }],
- o: [{ //o=- 20518 0 IN IP4 203.0.113.1
- // NB: sessionId will be a String in most cases because it is huge
- name: 'origin',
- reg: /^(\S*) (\d*) (\d*) (\S*) IP(\d) (\S*)/,
- names: ['username', 'sessionId', 'sessionVersion', 'netType', 'ipVer', 'address'],
- format: "%s %s %d %s IP%d %s"
- }],
- // default parsing of these only (though some of these feel outdated)
- s: [{ name: 'name' }],
- i: [{ name: 'description' }],
- u: [{ name: 'uri' }],
- e: [{ name: 'email' }],
- p: [{ name: 'phone' }],
- z: [{ name: 'timezones' }], // TODO: this one can actually be parsed properly..
- r: [{ name: 'repeats' }], // TODO: this one can also be parsed properly
- //k: [{}], // outdated thing ignored
- t: [{ //t=0 0
- name: 'timing',
- reg: /^(\d*) (\d*)/,
- names: ['start', 'stop'],
- format: "%d %d"
- }],
- c: [{ //c=IN IP4 10.47.197.26
- name: 'connection',
- reg: /^IN IP(\d) (\S*)/,
- names: ['version', 'ip'],
- format: "IN IP%d %s"
- }],
- b: [{ //b=AS:4000
- push: 'bandwidth',
- reg: /^(TIAS|AS|CT|RR|RS):(\d*)/,
- names: ['type', 'limit'],
- format: "%s:%s"
- }],
- m: [{ //m=video 51744 RTP/AVP 126 97 98 34 31
- // NB: special - pushes to session
- // TODO: rtp/fmtp should be filtered by the payloads found here?
- reg: /^(\w*) (\d*) ([\w\/]*)(?: (.*))?/,
- names: ['type', 'port', 'protocol', 'payloads'],
- format: "%s %d %s %s"
- }],
- a: [
- { //a=rtpmap:110 opus/48000/2
- push: 'rtp',
- reg: /^rtpmap:(\d*) ([\w\-]*)(?:\s*\/(\d*)(?:\s*\/(\S*))?)?/,
- names: ['payload', 'codec', 'rate', 'encoding'],
- format: function (o) {
- return (o.encoding) ?
- "rtpmap:%d %s/%s/%s":
- o.rate ?
- "rtpmap:%d %s/%s":
- "rtpmap:%d %s";
- }
- },
- {
- //a=fmtp:108 profile-level-id=24;object=23;bitrate=64000
- //a=fmtp:111 minptime=10; useinbandfec=1
- push: 'fmtp',
- reg: /^fmtp:(\d*) ([\S| ]*)/,
- names: ['payload', 'config'],
- format: "fmtp:%d %s"
- },
- { //a=control:streamid=0
- name: 'control',
- reg: /^control:(.*)/,
- format: "control:%s"
- },
- { //a=rtcp:65179 IN IP4 193.84.77.194
- name: 'rtcp',
- reg: /^rtcp:(\d*)(?: (\S*) IP(\d) (\S*))?/,
- names: ['port', 'netType', 'ipVer', 'address'],
- format: function (o) {
- return (o.address != null) ?
- "rtcp:%d %s IP%d %s":
- "rtcp:%d";
- }
- },
- { //a=rtcp-fb:98 trr-int 100
- push: 'rtcpFbTrrInt',
- reg: /^rtcp-fb:(\*|\d*) trr-int (\d*)/,
- names: ['payload', 'value'],
- format: "rtcp-fb:%d trr-int %d"
- },
- { //a=rtcp-fb:98 nack rpsi
- push: 'rtcpFb',
- reg: /^rtcp-fb:(\*|\d*) ([\w-_]*)(?: ([\w-_]*))?/,
- names: ['payload', 'type', 'subtype'],
- format: function (o) {
- return (o.subtype != null) ?
- "rtcp-fb:%s %s %s":
- "rtcp-fb:%s %s";
- }
- },
- { //a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
- //a=extmap:1/recvonly URI-gps-string
- push: 'ext',
- reg: /^extmap:([\w_\/]*) (\S*)(?: (\S*))?/,
- names: ['value', 'uri', 'config'], // value may include "/direction" suffix
- format: function (o) {
- return (o.config != null) ?
- "extmap:%s %s %s":
- "extmap:%s %s";
- }
- },
- {
- //a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32
- push: 'crypto',
- reg: /^crypto:(\d*) ([\w_]*) (\S*)(?: (\S*))?/,
- names: ['id', 'suite', 'config', 'sessionConfig'],
- format: function (o) {
- return (o.sessionConfig != null) ?
- "crypto:%d %s %s %s":
- "crypto:%d %s %s";
- }
- },
- { //a=setup:actpass
- name: 'setup',
- reg: /^setup:(\w*)/,
- format: "setup:%s"
- },
- { //a=mid:1
- name: 'mid',
- reg: /^mid:([^\s]*)/,
- format: "mid:%s"
- },
- { //a=msid:0c8b064d-d807-43b4-b434-f92a889d8587 98178685-d409-46e0-8e16-7ef0db0db64a
- name: 'msid',
- reg: /^msid:(.*)/,
- format: "msid:%s"
- },
- { //a=ptime:20
- name: 'ptime',
- reg: /^ptime:(\d*)/,
- format: "ptime:%d"
- },
- { //a=maxptime:60
- name: 'maxptime',
- reg: /^maxptime:(\d*)/,
- format: "maxptime:%d"
- },
- { //a=sendrecv
- name: 'direction',
- reg: /^(sendrecv|recvonly|sendonly|inactive)/
- },
- { //a=ice-lite
- name: 'icelite',
- reg: /^(ice-lite)/
- },
- { //a=ice-ufrag:F7gI
- name: 'iceUfrag',
- reg: /^ice-ufrag:(\S*)/,
- format: "ice-ufrag:%s"
- },
- { //a=ice-pwd:x9cml/YzichV2+XlhiMu8g
- name: 'icePwd',
- reg: /^ice-pwd:(\S*)/,
- format: "ice-pwd:%s"
- },
- { //a=fingerprint:SHA-1 00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33
- name: 'fingerprint',
- reg: /^fingerprint:(\S*) (\S*)/,
- names: ['type', 'hash'],
- format: "fingerprint:%s %s"
- },
- {
- //a=candidate:0 1 UDP 2113667327 203.0.113.1 54400 typ host
- //a=candidate:1162875081 1 udp 2113937151 192.168.34.75 60017 typ host generation 0
- //a=candidate:3289912957 2 udp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 generation 0
- //a=candidate:229815620 1 tcp 1518280447 192.168.150.19 60017 typ host tcptype active generation 0
- //a=candidate:3289912957 2 tcp 1845501695 193.84.77.194 60017 typ srflx raddr 192.168.34.75 rport 60017 tcptype passive generation 0
- push:'candidates',
- reg: /^candidate:(\S*) (\d*) (\S*) (\d*) (\S*) (\d*) typ (\S*)(?: raddr (\S*) rport (\d*))?(?: tcptype (\S*))?(?: generation (\d*))?/,
- names: ['foundation', 'component', 'transport', 'priority', 'ip', 'port', 'type', 'raddr', 'rport', 'tcptype', 'generation'],
- format: function (o) {
- var str = "candidate:%s %d %s %d %s %d typ %s";
-
- str += (o.raddr != null) ? " raddr %s rport %d" : "%v%v";
-
- // NB: candidate has three optional chunks, so %void middles one if it's missing
- str += (o.tcptype != null) ? " tcptype %s" : "%v";
-
- if (o.generation != null) {
- str += " generation %d";
- }
- return str;
- }
- },
- { //a=end-of-candidates (keep after the candidates line for readability)
- name: 'endOfCandidates',
- reg: /^(end-of-candidates)/
- },
- { //a=remote-candidates:1 203.0.113.1 54400 2 203.0.113.1 54401 ...
- name: 'remoteCandidates',
- reg: /^remote-candidates:(.*)/,
- format: "remote-candidates:%s"
- },
- { //a=ice-options:google-ice
- name: 'iceOptions',
- reg: /^ice-options:(\S*)/,
- format: "ice-options:%s"
- },
- { //a=ssrc:2566107569 cname:t9YU8M1UxTF8Y1A1
- push: "ssrcs",
- reg: /^ssrc:(\d*) ([\w_]*):(.*)/,
- names: ['id', 'attribute', 'value'],
- format: "ssrc:%d %s:%s"
- },
- { //a=ssrc-group:FEC 1 2
- push: "ssrcGroups",
- reg: /^ssrc-group:(\w*) (.*)/,
- names: ['semantics', 'ssrcs'],
- format: "ssrc-group:%s %s"
- },
- { //a=msid-semantic: WMS Jvlam5X3SX1OP6pn20zWogvaKJz5Hjf9OnlV
- name: "msidSemantic",
- reg: /^msid-semantic:\s?(\w*) (\S*)/,
- names: ['semantic', 'token'],
- format: "msid-semantic: %s %s" // space after ":" is not accidental
- },
- { //a=group:BUNDLE audio video
- push: 'groups',
- reg: /^group:(\w*) (.*)/,
- names: ['type', 'mids'],
- format: "group:%s %s"
- },
- { //a=rtcp-mux
- name: 'rtcpMux',
- reg: /^(rtcp-mux)/
- },
- { //a=rtcp-rsize
- name: 'rtcpRsize',
- reg: /^(rtcp-rsize)/
- },
- { // any a= that we don't understand is kepts verbatim on media.invalid
- push: 'invalid',
- names: ["value"]
- }
- ]
-};
-
-// set sensible defaults to avoid polluting the grammar with boring details
-Object.keys(grammar).forEach(function (key) {
- var objs = grammar[key];
- objs.forEach(function (obj) {
- if (!obj.reg) {
- obj.reg = /(.*)/;
- }
- if (!obj.format) {
- obj.format = "%s";
- }
- });
-});
-
-},{}],25:[function(require,module,exports){
-var parser = require('./parser');
-var writer = require('./writer');
-
-exports.write = writer;
-exports.parse = parser.parse;
-exports.parseFmtpConfig = parser.parseFmtpConfig;
-exports.parsePayloads = parser.parsePayloads;
-exports.parseRemoteCandidates = parser.parseRemoteCandidates;
-
-},{"./parser":26,"./writer":27}],26:[function(require,module,exports){
-var toIntIfInt = function (v) {
- return String(Number(v)) === v ? Number(v) : v;
-};
-
-var attachProperties = function (match, location, names, rawName) {
- if (rawName && !names) {
- location[rawName] = toIntIfInt(match[1]);
- }
- else {
- for (var i = 0; i < names.length; i += 1) {
- if (match[i+1] != null) {
- location[names[i]] = toIntIfInt(match[i+1]);
- }
- }
- }
-};
-
-var parseReg = function (obj, location, content) {
- var needsBlank = obj.name && obj.names;
- if (obj.push && !location[obj.push]) {
- location[obj.push] = [];
- }
- else if (needsBlank && !location[obj.name]) {
- location[obj.name] = {};
- }
- var keyLocation = obj.push ?
- {} : // blank object that will be pushed
- needsBlank ? location[obj.name] : location; // otherwise, named location or root
-
- attachProperties(content.match(obj.reg), keyLocation, obj.names, obj.name);
-
- if (obj.push) {
- location[obj.push].push(keyLocation);
- }
-};
-
-var grammar = require('./grammar');
-var validLine = RegExp.prototype.test.bind(/^([a-z])=(.*)/);
-
-exports.parse = function (sdp) {
- var session = {}
- , media = []
- , location = session; // points at where properties go under (one of the above)
-
- // parse lines we understand
- sdp.split(/(\r\n|\r|\n)/).filter(validLine).forEach(function (l) {
- var type = l[0];
- var content = l.slice(2);
- if (type === 'm') {
- media.push({rtp: [], fmtp: []});
- location = media[media.length-1]; // point at latest media line
- }
-
- for (var j = 0; j < (grammar[type] || []).length; j += 1) {
- var obj = grammar[type][j];
- if (obj.reg.test(content)) {
- return parseReg(obj, location, content);
- }
- }
- });
-
- session.media = media; // link it up
- return session;
-};
-
-var fmtpReducer = function (acc, expr) {
- var s = expr.split('=');
- if (s.length === 2) {
- acc[s[0]] = toIntIfInt(s[1]);
- }
- return acc;
-};
-
-exports.parseFmtpConfig = function (str) {
- return str.split(/\;\s?/).reduce(fmtpReducer, {});
-};
-
-exports.parsePayloads = function (str) {
- return str.split(' ').map(Number);
-};
-
-exports.parseRemoteCandidates = function (str) {
- var candidates = [];
- var parts = str.split(' ').map(toIntIfInt);
- for (var i = 0; i < parts.length; i += 3) {
- candidates.push({
- component: parts[i],
- ip: parts[i + 1],
- port: parts[i + 2]
- });
- }
- return candidates;
-};
-
-},{"./grammar":24}],27:[function(require,module,exports){
-var grammar = require('./grammar');
-
-// customized util.format - discards excess arguments and can void middle ones
-var formatRegExp = /%[sdv%]/g;
-var format = function (formatStr) {
- var i = 1;
- var args = arguments;
- var len = args.length;
- return formatStr.replace(formatRegExp, function (x) {
- if (i >= len) {
- return x; // missing argument
- }
- var arg = args[i];
- i += 1;
- switch (x) {
- case '%%':
- return '%';
- case '%s':
- return String(arg);
- case '%d':
- return Number(arg);
- case '%v':
- return '';
- }
- });
- // NB: we discard excess arguments - they are typically undefined from makeLine
-};
-
-var makeLine = function (type, obj, location) {
- var str = obj.format instanceof Function ?
- (obj.format(obj.push ? location : location[obj.name])) :
- obj.format;
-
- var args = [type + '=' + str];
- if (obj.names) {
- for (var i = 0; i < obj.names.length; i += 1) {
- var n = obj.names[i];
- if (obj.name) {
- args.push(location[obj.name][n]);
- }
- else { // for mLine and push attributes
- args.push(location[obj.names[i]]);
- }
- }
- }
- else {
- args.push(location[obj.name]);
- }
- return format.apply(null, args);
-};
-
-// RFC specified order
-// TODO: extend this with all the rest
-var defaultOuterOrder = [
- 'v', 'o', 's', 'i',
- 'u', 'e', 'p', 'c',
- 'b', 't', 'r', 'z', 'a'
-];
-var defaultInnerOrder = ['i', 'c', 'b', 'a'];
-
-
-module.exports = function (session, opts) {
- opts = opts || {};
- // ensure certain properties exist
- if (session.version == null) {
- session.version = 0; // "v=0" must be there (only defined version atm)
- }
- if (session.name == null) {
- session.name = " "; // "s= " must be there if no meaningful name set
- }
- session.media.forEach(function (mLine) {
- if (mLine.payloads == null) {
- mLine.payloads = "";
- }
- });
-
- var outerOrder = opts.outerOrder || defaultOuterOrder;
- var innerOrder = opts.innerOrder || defaultInnerOrder;
- var sdp = [];
-
- // loop through outerOrder for matching properties on session
- outerOrder.forEach(function (type) {
- grammar[type].forEach(function (obj) {
- if (obj.name in session && session[obj.name] != null) {
- sdp.push(makeLine(type, obj, session));
- }
- else if (obj.push in session && session[obj.push] != null) {
- session[obj.push].forEach(function (el) {
- sdp.push(makeLine(type, obj, el));
- });
- }
- });
- });
-
- // then for each media line, follow the innerOrder
- session.media.forEach(function (mLine) {
- sdp.push(makeLine('m', grammar.m[0], mLine));
-
- innerOrder.forEach(function (type) {
- grammar[type].forEach(function (obj) {
- if (obj.name in mLine && mLine[obj.name] != null) {
- sdp.push(makeLine(type, obj, mLine));
- }
- else if (obj.push in mLine && mLine[obj.push] != null) {
- mLine[obj.push].forEach(function (el) {
- sdp.push(makeLine(type, obj, el));
- });
- }
- });
- });
- });
-
- return sdp.join('\r\n') + '\r\n';
-};
-
-},{"./grammar":24}],28:[function(require,module,exports){
-/* Copyright @ 2015 Atlassian Pty Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-module.exports = function arrayEquals(array) {
- // if the other array is a falsy value, return
- if (!array)
- return false;
-
- // compare lengths - can save a lot of time
- if (this.length != array.length)
- return false;
-
- for (var i = 0, l = this.length; i < l; i++) {
- // Check if we have nested arrays
- if (this[i] instanceof Array && array[i] instanceof Array) {
- // recurse into the nested arrays
- if (!arrayEquals.apply(this[i], [array[i]]))
- return false;
- } else if (this[i] != array[i]) {
- // Warning - two different object instances will never be equal:
- // {x:20} != {x:20}
- return false;
- }
- }
- return true;
-};
-
-
-},{}],29:[function(require,module,exports){
-/* Copyright @ 2015 Atlassian Pty Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-exports.Interop = require('./interop');
-
-},{"./interop":30}],30:[function(require,module,exports){
-/* Copyright @ 2015 Atlassian Pty Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/* global RTCSessionDescription */
-/* global RTCIceCandidate */
-/* jshint -W097 */
-"use strict";
-
-var transform = require('./transform');
-var arrayEquals = require('./array-equals');
-
-function Interop() {
-
- /**
- * This map holds the most recent Unified Plan offer/answer SDP that was
- * converted to Plan B, with the SDP type ('offer' or 'answer') as keys and
- * the SDP string as values.
- *
- * @type {{}}
- */
- this.cache = {
- mlB2UMap : {},
- mlU2BMap : {}
- };
-}
-
-module.exports = Interop;
-
-/**
- * Changes the candidate args to match with the related Unified Plan
- */
-Interop.prototype.candidateToUnifiedPlan = function(candidate) {
- var cand = new RTCIceCandidate(candidate);
-
- cand.sdpMLineIndex = this.cache.mlB2UMap[cand.sdpMLineIndex];
- /* TODO: change sdpMid to (audio|video)-SSRC */
-
- return cand;
-};
-
-/**
- * Changes the candidate args to match with the related Plan B
- */
-Interop.prototype.candidateToPlanB = function(candidate) {
- var cand = new RTCIceCandidate(candidate);
-
- if (cand.sdpMid.indexOf('audio') === 0) {
- cand.sdpMid = 'audio';
- } else if (cand.sdpMid.indexOf('video') === 0) {
- cand.sdpMid = 'video';
- } else {
- throw new Error('candidate with ' + cand.sdpMid + ' not allowed');
- }
-
- cand.sdpMLineIndex = this.cache.mlU2BMap[cand.sdpMLineIndex];
-
- return cand;
-};
-
-/**
- * Returns the index of the first m-line with the given media type and with a
- * direction which allows sending, in the last Unified Plan description with
- * type "answer" converted to Plan B. Returns {null} if there is no saved
- * answer, or if none of its m-lines with the given type allow sending.
- * @param type the media type ("audio" or "video").
- * @returns {*}
- */
-Interop.prototype.getFirstSendingIndexFromAnswer = function(type) {
- if (!this.cache.answer) {
- return null;
- }
-
- var session = transform.parse(this.cache.answer);
- if (session && session.media && Array.isArray(session.media)){
- for (var i = 0; i < session.media.length; i++) {
- if (session.media[i].type == type &&
- (!session.media[i].direction /* default to sendrecv */ ||
- session.media[i].direction === 'sendrecv' ||
- session.media[i].direction === 'sendonly')){
- return i;
- }
- }
- }
-
- return null;
-};
-
-/**
- * This method transforms a Unified Plan SDP to an equivalent Plan B SDP. A
- * PeerConnection wrapper transforms the SDP to Plan B before passing it to the
- * application.
- *
- * @param desc
- * @returns {*}
- */
-Interop.prototype.toPlanB = function(desc) {
- var self = this;
- //#region Preliminary input validation.
-
- if (typeof desc !== 'object' || desc === null ||
- typeof desc.sdp !== 'string') {
- console.warn('An empty description was passed as an argument.');
- return desc;
- }
-
- // Objectify the SDP for easier manipulation.
- var session = transform.parse(desc.sdp);
-
- // If the SDP contains no media, there's nothing to transform.
- if (typeof session.media === 'undefined' ||
- !Array.isArray(session.media) || session.media.length === 0) {
- console.warn('The description has no media.');
- return desc;
- }
-
- // Try some heuristics to "make sure" this is a Unified Plan SDP. Plan B
- // SDP has a video, an audio and a data "channel" at most.
- if (session.media.length <= 3 && session.media.every(function(m) {
- return ['video', 'audio', 'data'].indexOf(m.mid) !== -1;
- })) {
- console.warn('This description does not look like Unified Plan.');
- return desc;
- }
-
- //#endregion
-
- // HACK https://bugzilla.mozilla.org/show_bug.cgi?id=1113443
- var sdp = desc.sdp;
- var rewrite = false;
- for (var i = 0; i < session.media.length; i++) {
- var uLine = session.media[i];
- uLine.rtp.forEach(function(rtp) {
- if (rtp.codec === 'NULL')
- {
- rewrite = true;
- var offer = transform.parse(self.cache.offer);
- rtp.codec = offer.media[i].rtp[0].codec;
- }
- });
- }
- if (rewrite) {
- sdp = transform.write(session);
- }
-
- // Unified Plan SDP is our "precious". Cache it for later use in the Plan B
- // -> Unified Plan transformation.
- this.cache[desc.type] = sdp;
-
- //#region Convert from Unified Plan to Plan B.
-
- // We rebuild the session.media array.
- var media = session.media;
- session.media = [];
-
- // Associative array that maps channel types to channel objects for fast
- // access to channel objects by their type, e.g. type2bl['audio']->channel
- // obj.
- var type2bl = {};
-
- // Used to build the group:BUNDLE value after the channels construction
- // loop.
- var types = [];
-
- media.forEach(function(uLine) {
- // rtcp-mux is required in the Plan B SDP.
- if ((typeof uLine.rtcpMux !== 'string' ||
- uLine.rtcpMux !== 'rtcp-mux') &&
- uLine.direction !== 'inactive') {
- throw new Error('Cannot convert to Plan B because m-lines ' +
- 'without the rtcp-mux attribute were found.');
- }
-
- // If we don't have a channel for this uLine.type OR the selected is
- // inactive, then select this uLine as the channel basis.
- if (typeof type2bl[uLine.type] === 'undefined' ||
- type2bl[uLine.type].direction === 'inactive') {
- type2bl[uLine.type] = uLine;
- }
-
- if (uLine.protocol != type2bl[uLine.type].protocol) {
- throw new Error('Cannot convert to Plan B because m-lines ' +
- 'have different protocols and this library does not have ' +
- 'support for that');
- }
-
- if (uLine.payloads != type2bl[uLine.type].payloads) {
- throw new Error('Cannot convert to Plan B because m-lines ' +
- 'have different payloads and this library does not have ' +
- 'support for that');
- }
-
- });
-
- // Implode the Unified Plan m-lines/tracks into Plan B channels.
- media.forEach(function(uLine) {
- if (uLine.type === 'application') {
- session.media.push(uLine);
- types.push(uLine.mid);
- return;
- }
-
- // Add sources to the channel and handle a=msid.
- if (typeof uLine.sources === 'object') {
- Object.keys(uLine.sources).forEach(function(ssrc) {
- if (typeof type2bl[uLine.type].sources !== 'object')
- type2bl[uLine.type].sources = {};
-
- // Assign the sources to the channel.
- type2bl[uLine.type].sources[ssrc] =
- uLine.sources[ssrc];
-
- if (typeof uLine.msid !== 'undefined') {
- // In Plan B the msid is an SSRC attribute. Also, we don't
- // care about the obsolete label and mslabel attributes.
- //
- // Note that it is not guaranteed that the uLine will
- // have an msid. recvonly channels in particular don't have
- // one.
- type2bl[uLine.type].sources[ssrc].msid =
- uLine.msid;
- }
- // NOTE ssrcs in ssrc groups will share msids, as
- // draft-uberti-rtcweb-plan-00 mandates.
- });
- }
-
- // Add ssrc groups to the channel.
- if (typeof uLine.ssrcGroups !== 'undefined' &&
- Array.isArray(uLine.ssrcGroups)) {
-
- // Create the ssrcGroups array, if it's not defined.
- if (typeof type2bl[uLine.type].ssrcGroups === 'undefined' ||
- !Array.isArray(type2bl[uLine.type].ssrcGroups)) {
- type2bl[uLine.type].ssrcGroups = [];
- }
-
- type2bl[uLine.type].ssrcGroups =
- type2bl[uLine.type].ssrcGroups.concat(
- uLine.ssrcGroups);
- }
-
- if (type2bl[uLine.type] === uLine) {
- // Plan B mids are in ['audio', 'video', 'data']
- uLine.mid = uLine.type;
-
- // Plan B doesn't support/need the bundle-only attribute.
- delete uLine.bundleOnly;
-
- // In Plan B the msid is an SSRC attribute.
- delete uLine.msid;
-
- if (uLine.type == media[0].type) {
- types.unshift(uLine.type);
- // Add the channel to the new media array.
- session.media.unshift(uLine);
- } else {
- types.push(uLine.type);
- // Add the channel to the new media array.
- session.media.push(uLine);
- }
- }
- });
-
- if (typeof session.groups !== 'undefined') {
- // We regenerate the BUNDLE group with the new mids.
- session.groups.some(function(group) {
- if (group.type === 'BUNDLE') {
- group.mids = types.join(' ');
- return true;
- }
- });
- }
-
- // msid semantic
- session.msidSemantic = {
- semantic: 'WMS',
- token: '*'
- };
-
- var resStr = transform.write(session);
-
- return new RTCSessionDescription({
- type: desc.type,
- sdp: resStr
- });
-
- //#endregion
-};
-
-/* follow rules defined in RFC4145 */
-function addSetupAttr(uLine) {
- if (typeof uLine.setup === 'undefined') {
- return;
- }
-
- if (uLine.setup === "active") {
- uLine.setup = "passive";
- } else if (uLine.setup === "passive") {
- uLine.setup = "active";
- }
-}
-
-/**
- * This method transforms a Plan B SDP to an equivalent Unified Plan SDP. A
- * PeerConnection wrapper transforms the SDP to Unified Plan before passing it
- * to FF.
- *
- * @param desc
- * @returns {*}
- */
-Interop.prototype.toUnifiedPlan = function(desc) {
- var self = this;
- //#region Preliminary input validation.
-
- if (typeof desc !== 'object' || desc === null ||
- typeof desc.sdp !== 'string') {
- console.warn('An empty description was passed as an argument.');
- return desc;
- }
-
- var session = transform.parse(desc.sdp);
-
- // If the SDP contains no media, there's nothing to transform.
- if (typeof session.media === 'undefined' ||
- !Array.isArray(session.media) || session.media.length === 0) {
- console.warn('The description has no media.');
- return desc;
- }
-
- // Try some heuristics to "make sure" this is a Plan B SDP. Plan B SDP has
- // a video, an audio and a data "channel" at most.
- if (session.media.length > 3 || !session.media.every(function(m) {
- return ['video', 'audio', 'data'].indexOf(m.mid) !== -1;
- })) {
- console.warn('This description does not look like Plan B.');
- return desc;
- }
-
- // Make sure this Plan B SDP can be converted to a Unified Plan SDP.
- var mids = [];
- session.media.forEach(function(m) {
- mids.push(m.mid);
- });
-
- var hasBundle = false;
- if (typeof session.groups !== 'undefined' &&
- Array.isArray(session.groups)) {
- hasBundle = session.groups.every(function(g) {
- return g.type !== 'BUNDLE' ||
- arrayEquals.apply(g.mids.sort(), [mids.sort()]);
- });
- }
-
- if (!hasBundle) {
- var mustBeBundle = false;
-
- session.media.forEach(function(m) {
- if (m.direction !== 'inactive') {
- mustBeBundle = true;
- }
- });
-
- if (mustBeBundle) {
- throw new Error("Cannot convert to Unified Plan because m-lines that" +
- " are not bundled were found.");
- }
- }
-
- //#endregion
-
-
- //#region Convert from Plan B to Unified Plan.
-
- // Unfortunately, a Plan B offer/answer doesn't have enough information to
- // rebuild an equivalent Unified Plan offer/answer.
- //
- // For example, if this is a local answer (in Unified Plan style) that we
- // convert to Plan B prior to handing it over to the application (the
- // PeerConnection wrapper called us, for instance, after a successful
- // createAnswer), we want to remember the m-line at which we've seen the
- // (local) SSRC. That's because when the application wants to do call the
- // SLD method, forcing us to do the inverse transformation (from Plan B to
- // Unified Plan), we need to know to which m-line to assign the (local)
- // SSRC. We also need to know all the other m-lines that the original
- // answer had and include them in the transformed answer as well.
- //
- // Another example is if this is a remote offer that we convert to Plan B
- // prior to giving it to the application, we want to remember the mid at
- // which we've seen the (remote) SSRC.
- //
- // In the iteration that follows, we use the cached Unified Plan (if it
- // exists) to assign mids to ssrcs.
-
- var type;
- if (desc.type === 'answer') {
- type = 'offer';
- } else if (desc.type === 'offer') {
- type = 'answer';
- } else {
- throw new Error("Type '" + desc.type + "' not supported.");
- }
-
- var cached;
- if (typeof this.cache[type] !== 'undefined') {
- cached = transform.parse(this.cache[type]);
- }
-
- var recvonlySsrcs = {
- audio: {},
- video: {}
- };
-
- // A helper map that sends mids to m-line objects. We use it later to
- // rebuild the Unified Plan style session.media array.
- var mid2ul = {};
- var bIdx = 0;
- var uIdx = 0;
-
- var sources2ul = {};
-
- var candidates;
- var iceUfrag;
- var icePwd;
- var fingerprint;
- var payloads = {};
- var rtcpFb = {};
- var rtp = {};
-
- session.media.forEach(function(bLine) {
- if ((typeof bLine.rtcpMux !== 'string' ||
- bLine.rtcpMux !== 'rtcp-mux') &&
- bLine.direction !== 'inactive') {
- throw new Error("Cannot convert to Unified Plan because m-lines " +
- "without the rtcp-mux attribute were found.");
- }
-
- if (bLine.type === 'application') {
- mid2ul[bLine.mid] = bLine;
- return;
- }
-
- // With rtcp-mux and bundle all the channels should have the same ICE
- // stuff.
- var sources = bLine.sources;
- var ssrcGroups = bLine.ssrcGroups;
- var port = bLine.port;
-
- /* Chrome adds different candidates even using bundle, so we concat the candidates list */
- if (typeof bLine.candidates != 'undefined') {
- if (typeof candidates != 'undefined') {
- candidates = candidates.concat(bLine.candidates);
- } else {
- candidates = bLine.candidates;
- }
- }
-
- if ((typeof iceUfrag != 'undefined') && (typeof bLine.iceUfrag != 'undefined') && (iceUfrag != bLine.iceUfrag)) {
- throw new Error("Only BUNDLE supported, iceUfrag must be the same for all m-lines.\n" +
- "\tLast iceUfrag: " + iceUfrag + "\n" +
- "\tNew iceUfrag: " + bLine.iceUfrag
- );
- }
-
- if (typeof bLine.iceUfrag != 'undefined') {
- iceUfrag = bLine.iceUfrag;
- }
-
- if ((typeof icePwd != 'undefined') && (typeof bLine.icePwd != 'undefined') && (icePwd != bLine.icePwd)) {
- throw new Error("Only BUNDLE supported, icePwd must be the same for all m-lines.\n" +
- "\tLast icePwd: " + icePwd + "\n" +
- "\tNew icePwd: " + bLine.icePwd
- );
- }
-
- if (typeof bLine.icePwd != 'undefined') {
- icePwd = bLine.icePwd;
- }
-
- if ((typeof fingerprint != 'undefined') && (typeof bLine.fingerprint != 'undefined') &&
- (fingerprint.type != bLine.fingerprint.type || fingerprint.hash != bLine.fingerprint.hash)) {
- throw new Error("Only BUNDLE supported, fingerprint must be the same for all m-lines.\n" +
- "\tLast fingerprint: " + JSON.stringify(fingerprint) + "\n" +
- "\tNew fingerprint: " + JSON.stringify(bLine.fingerprint)
- );
- }
-
- if (typeof bLine.fingerprint != 'undefined') {
- fingerprint = bLine.fingerprint;
- }
-
- payloads[bLine.type] = bLine.payloads;
- rtcpFb[bLine.type] = bLine.rtcpFb;
- rtp[bLine.type] = bLine.rtp;
-
- // inverted ssrc group map
- var ssrc2group = {};
- if (typeof ssrcGroups !== 'undefined' && Array.isArray(ssrcGroups)) {
- ssrcGroups.forEach(function (ssrcGroup) {
- // XXX This might brake if an SSRC is in more than one group
- // for some reason.
- if (typeof ssrcGroup.ssrcs !== 'undefined' &&
- Array.isArray(ssrcGroup.ssrcs)) {
- ssrcGroup.ssrcs.forEach(function (ssrc) {
- if (typeof ssrc2group[ssrc] === 'undefined') {
- ssrc2group[ssrc] = [];
- }
-
- ssrc2group[ssrc].push(ssrcGroup);
- });
- }
- });
- }
-
- // ssrc to m-line index.
- var ssrc2ml = {};
-
- if (typeof sources === 'object') {
-
- // We'll use the "bLine" object as a prototype for each new "mLine"
- // that we create, but first we need to clean it up a bit.
- delete bLine.sources;
- delete bLine.ssrcGroups;
- delete bLine.candidates;
- delete bLine.iceUfrag;
- delete bLine.icePwd;
- delete bLine.fingerprint;
- delete bLine.port;
- delete bLine.mid;
-
- // Explode the Plan B channel sources with one m-line per source.
- Object.keys(sources).forEach(function(ssrc) {
-
- // The (unified) m-line for this SSRC. We either create it from
- // scratch or, if it's a grouped SSRC, we re-use a related
- // mline. In other words, if the source is grouped with another
- // source, put the two together in the same m-line.
- var uLine;
-
- // We assume here that we are the answerer in the O/A, so any
- // offers which we translate come from the remote side, while
- // answers are local. So the check below is to make that we
- // handle receive-only SSRCs in a special way only if they come
- // from the remote side.
- if (desc.type==='offer') {
- // We want to detect SSRCs which are used by a remote peer
- // in an m-line with direction=recvonly (i.e. they are
- // being used for RTCP only).
- // This information would have gotten lost if the remote
- // peer used Unified Plan and their local description was
- // translated to Plan B. So we use the lack of an MSID
- // attribute to deduce a "receive only" SSRC.
- if (!sources[ssrc].msid) {
- recvonlySsrcs[bLine.type][ssrc] = sources[ssrc];
- // Receive-only SSRCs must not create new m-lines. We
- // will assign them to an existing m-line later.
- return;
- }
- }
-
- if (typeof ssrc2group[ssrc] !== 'undefined' &&
- Array.isArray(ssrc2group[ssrc])) {
- ssrc2group[ssrc].some(function (ssrcGroup) {
- // ssrcGroup.ssrcs *is* an Array, no need to check
- // again here.
- return ssrcGroup.ssrcs.some(function (related) {
- if (typeof ssrc2ml[related] === 'object') {
- uLine = ssrc2ml[related];
- return true;
- }
- });
- });
- }
-
- if (typeof uLine === 'object') {
- // the m-line already exists. Just add the source.
- uLine.sources[ssrc] = sources[ssrc];
- delete sources[ssrc].msid;
- } else {
- // Use the "bLine" as a prototype for the "uLine".
- uLine = Object.create(bLine);
- ssrc2ml[ssrc] = uLine;
-
- if (typeof sources[ssrc].msid !== 'undefined') {
- // Assign the msid of the source to the m-line. Note
- // that it is not guaranteed that the source will have
- // msid. In particular "recvonly" sources don't have an
- // msid. Note that "recvonly" is a term only defined
- // for m-lines.
- uLine.msid = sources[ssrc].msid;
- delete sources[ssrc].msid;
- }
-
- // We assign one SSRC per media line.
- uLine.sources = {};
- uLine.sources[ssrc] = sources[ssrc];
- uLine.ssrcGroups = ssrc2group[ssrc];
-
- // Use the cached Unified Plan SDP (if it exists) to assign
- // SSRCs to mids.
- if (typeof cached !== 'undefined' &&
- typeof cached.media !== 'undefined' &&
- Array.isArray(cached.media)) {
-
- cached.media.forEach(function (m) {
- if (typeof m.sources === 'object') {
- Object.keys(m.sources).forEach(function (s) {
- if (s === ssrc) {
- uLine.mid = m.mid;
- }
- });
- }
- });
- }
-
- if (typeof uLine.mid === 'undefined') {
-
- // If this is an SSRC that we see for the first time
- // assign it a new mid. This is typically the case when
- // this method is called to transform a remote
- // description for the first time or when there is a
- // new SSRC in the remote description because a new
- // peer has joined the conference. Local SSRCs should
- // have already been added to the map in the toPlanB
- // method.
- //
- // Because FF generates answers in Unified Plan style,
- // we MUST already have a cached answer with all the
- // local SSRCs mapped to some m-line/mid.
-
- uLine.mid = [bLine.type, '-', ssrc].join('');
- }
-
- // Include the candidates in the 1st media line.
- uLine.candidates = candidates;
- uLine.iceUfrag = iceUfrag;
- uLine.icePwd = icePwd;
- uLine.fingerprint = fingerprint;
- uLine.port = port;
-
- mid2ul[uLine.mid] = uLine;
- sources2ul[uIdx] = uLine.sources;
-
- self.cache.mlU2BMap[uIdx] = bIdx;
- if (typeof self.cache.mlB2UMap[bIdx] === 'undefined') {
- self.cache.mlB2UMap[bIdx] = uIdx;
- }
- uIdx++;
- }
- });
- } else {
- var uLine = bLine;
-
- uLine.candidates = candidates;
- uLine.iceUfrag = iceUfrag;
- uLine.icePwd = icePwd;
- uLine.fingerprint = fingerprint;
- uLine.port = port;
-
- mid2ul[uLine.mid] = uLine;
-
- self.cache.mlU2BMap[uIdx] = bIdx;
- if (typeof self.cache.mlB2UMap[bIdx] === 'undefined') {
- self.cache.mlB2UMap[bIdx] = uIdx;
- }
- }
-
- bIdx++;
- });
-
- // Rebuild the media array in the right order and add the missing mLines
- // (missing from the Plan B SDP).
- session.media = [];
- mids = []; // reuse
-
- if (desc.type === 'answer') {
-
- // The media lines in the answer must match the media lines in the
- // offer. The order is important too. Here we assume that Firefox is
- // the answerer, so we merely have to use the reconstructed (unified)
- // answer to update the cached (unified) answer accordingly.
- //
- // In the general case, one would have to use the cached (unified)
- // offer to find the m-lines that are missing from the reconstructed
- // answer, potentially grabbing them from the cached (unified) answer.
- // One has to be careful with this approach because inactive m-lines do
- // not always have an mid, making it tricky (impossible?) to find where
- // exactly and which m-lines are missing from the reconstructed answer.
-
- for (var i = 0; i < cached.media.length; i++) {
- var uLine = cached.media[i];
-
- delete uLine.msid;
- delete uLine.sources;
- delete uLine.ssrcGroups;
-
- if (typeof sources2ul[i] === 'undefined') {
- if (!uLine.direction
- || uLine.direction === 'sendrecv')
- uLine.direction = 'recvonly';
- else if (uLine.direction === 'sendonly')
- uLine.direction = 'inactive';
- } else {
- if (!uLine.direction
- || uLine.direction === 'sendrecv')
- uLine.direction = 'sendrecv';
- else if (uLine.direction === 'recvonly')
- uLine.direction = 'sendonly';
- }
-
- uLine.sources = sources2ul[i];
- uLine.candidates = candidates;
- uLine.iceUfrag = iceUfrag;
- uLine.icePwd = icePwd;
- uLine.fingerprint = fingerprint;
-
- uLine.rtp = rtp[uLine.type];
- uLine.payloads = payloads[uLine.type];
- uLine.rtcpFb = rtcpFb[uLine.type];
-
- session.media.push(uLine);
-
- if (typeof uLine.mid === 'string') {
- // inactive lines don't/may not have an mid.
- mids.push(uLine.mid);
- }
- }
- } else {
-
- // SDP offer/answer (and the JSEP spec) forbids removing an m-section
- // under any circumstances. If we are no longer interested in sending a
- // track, we just remove the msid and ssrc attributes and set it to
- // either a=recvonly (as the reofferer, we must use recvonly if the
- // other side was previously sending on the m-section, but we can also
- // leave the possibility open if it wasn't previously in use), or
- // a=inactive.
-
- if (typeof cached !== 'undefined' &&
- typeof cached.media !== 'undefined' &&
- Array.isArray(cached.media)) {
- cached.media.forEach(function(uLine) {
- mids.push(uLine.mid);
- if (typeof mid2ul[uLine.mid] !== 'undefined') {
- session.media.push(mid2ul[uLine.mid]);
- } else {
- delete uLine.msid;
- delete uLine.sources;
- delete uLine.ssrcGroups;
-
- if (!uLine.direction
- || uLine.direction === 'sendrecv') {
- uLine.direction = 'sendonly';
- }
- if (!uLine.direction
- || uLine.direction === 'recvonly') {
- uLine.direction = 'inactive';
- }
-
- addSetupAttr (uLine);
- session.media.push(uLine);
- }
- });
- }
-
- // Add all the remaining (new) m-lines of the transformed SDP.
- Object.keys(mid2ul).forEach(function(mid) {
- if (mids.indexOf(mid) === -1) {
- mids.push(mid);
- if (mid2ul[mid].direction === 'recvonly') {
- // This is a remote recvonly channel. Add its SSRC to the
- // appropriate sendrecv or sendonly channel.
- // TODO(gp) what if we don't have sendrecv/sendonly
- // channel?
-
- var done = false;
-
- session.media.some(function (uLine) {
- if ((uLine.direction === 'sendrecv' ||
- uLine.direction === 'sendonly') &&
- uLine.type === mid2ul[mid].type) {
- // mid2ul[mid] shouldn't have any ssrc-groups
- Object.keys(mid2ul[mid].sources).forEach(
- function (ssrc) {
- uLine.sources[ssrc] =
- mid2ul[mid].sources[ssrc];
- });
-
- done = true;
- return true;
- }
- });
-
- if (!done) {
- session.media.push(mid2ul[mid]);
- }
- } else {
- session.media.push(mid2ul[mid]);
- }
- }
- });
- }
-
- // After we have constructed the Plan Unified m-lines we can figure out
- // where (in which m-line) to place the 'recvonly SSRCs'.
- // Note: we assume here that we are the answerer in the O/A, so any offers
- // which we translate come from the remote side, while answers are local
- // (and so our last local description is cached as an 'answer').
- ["audio", "video"].forEach(function (type) {
- if (!session || !session.media || !Array.isArray(session.media))
- return;
-
- var idx = null;
- if (Object.keys(recvonlySsrcs[type]).length > 0) {
- idx = self.getFirstSendingIndexFromAnswer(type);
- if (idx === null){
- // If this is the first offer we receive, we don't have a
- // cached answer. Assume that we will be sending media using
- // the first m-line for each media type.
-
- for (var i = 0; i < session.media.length; i++) {
- if (session.media[i].type === type) {
- idx = i;
- break;
- }
- }
- }
- }
-
- if (idx && session.media.length > idx) {
- var mLine = session.media[idx];
- Object.keys(recvonlySsrcs[type]).forEach(function(ssrc) {
- if (mLine.sources && mLine.sources[ssrc]) {
- console.warn("Replacing an existing SSRC.");
- }
- if (!mLine.sources) {
- mLine.sources = {};
- }
-
- mLine.sources[ssrc] = recvonlySsrcs[type][ssrc];
- });
- }
- });
-
- if (typeof session.groups !== 'undefined') {
- // We regenerate the BUNDLE group (since we regenerated the mids)
- session.groups.some(function(group) {
- if (group.type === 'BUNDLE') {
- group.mids = mids.join(' ');
- return true;
- }
- });
- }
-
- // msid semantic
- session.msidSemantic = {
- semantic: 'WMS',
- token: '*'
- };
-
- var resStr = transform.write(session);
-
- // Cache the transformed SDP (Unified Plan) for later re-use in this
- // function.
- this.cache[desc.type] = resStr;
-
- return new RTCSessionDescription({
- type: desc.type,
- sdp: resStr
- });
-
- //#endregion
-};
-
-},{"./array-equals":28,"./transform":31}],31:[function(require,module,exports){
-/* Copyright @ 2015 Atlassian Pty Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var transform = require('sdp-transform');
-
-exports.write = function(session, opts) {
-
- if (typeof session !== 'undefined' &&
- typeof session.media !== 'undefined' &&
- Array.isArray(session.media)) {
-
- session.media.forEach(function (mLine) {
- // expand sources to ssrcs
- if (typeof mLine.sources !== 'undefined' &&
- Object.keys(mLine.sources).length !== 0) {
- mLine.ssrcs = [];
- Object.keys(mLine.sources).forEach(function (ssrc) {
- var source = mLine.sources[ssrc];
- Object.keys(source).forEach(function (attribute) {
- mLine.ssrcs.push({
- id: ssrc,
- attribute: attribute,
- value: source[attribute]
- });
- });
- });
- delete mLine.sources;
- }
-
- // join ssrcs in ssrc groups
- if (typeof mLine.ssrcGroups !== 'undefined' &&
- Array.isArray(mLine.ssrcGroups)) {
- mLine.ssrcGroups.forEach(function (ssrcGroup) {
- if (typeof ssrcGroup.ssrcs !== 'undefined' &&
- Array.isArray(ssrcGroup.ssrcs)) {
- ssrcGroup.ssrcs = ssrcGroup.ssrcs.join(' ');
- }
- });
- }
- });
- }
-
- // join group mids
- if (typeof session !== 'undefined' &&
- typeof session.groups !== 'undefined' && Array.isArray(session.groups)) {
-
- session.groups.forEach(function (g) {
- if (typeof g.mids !== 'undefined' && Array.isArray(g.mids)) {
- g.mids = g.mids.join(' ');
- }
- });
- }
-
- return transform.write(session, opts);
-};
-
-exports.parse = function(sdp) {
- var session = transform.parse(sdp);
-
- if (typeof session !== 'undefined' && typeof session.media !== 'undefined' &&
- Array.isArray(session.media)) {
-
- session.media.forEach(function (mLine) {
- // group sources attributes by ssrc
- if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) {
- mLine.sources = {};
- mLine.ssrcs.forEach(function (ssrc) {
- if (!mLine.sources[ssrc.id])
- mLine.sources[ssrc.id] = {};
- mLine.sources[ssrc.id][ssrc.attribute] = ssrc.value;
- });
-
- delete mLine.ssrcs;
- }
-
- // split ssrcs in ssrc groups
- if (typeof mLine.ssrcGroups !== 'undefined' &&
- Array.isArray(mLine.ssrcGroups)) {
- mLine.ssrcGroups.forEach(function (ssrcGroup) {
- if (typeof ssrcGroup.ssrcs === 'string') {
- ssrcGroup.ssrcs = ssrcGroup.ssrcs.split(' ');
- }
- });
- }
- });
- }
- // split group mids
- if (typeof session !== 'undefined' &&
- typeof session.groups !== 'undefined' && Array.isArray(session.groups)) {
-
- session.groups.forEach(function (g) {
- if (typeof g.mids === 'string') {
- g.mids = g.mids.split(' ');
- }
- });
- }
-
- return session;
-};
-
-
-},{"sdp-transform":25}],32:[function(require,module,exports){
- /* eslint-env node */
-'use strict';
-
-// SDP helpers.
-var SDPUtils = {};
-
-// Generate an alphanumeric identifier for cname or mids.
-// TODO: use UUIDs instead? https://gist.github.com/jed/982883
-SDPUtils.generateIdentifier = function() {
- return Math.random().toString(36).substr(2, 10);
-};
-
-// The RTCP CNAME used by all peerconnections from the same JS.
-SDPUtils.localCName = SDPUtils.generateIdentifier();
-
-// Splits SDP into lines, dealing with both CRLF and LF.
-SDPUtils.splitLines = function(blob) {
- return blob.trim().split('\n').map(function(line) {
- return line.trim();
- });
-};
-// Splits SDP into sessionpart and mediasections. Ensures CRLF.
-SDPUtils.splitSections = function(blob) {
- var parts = blob.split('\nm=');
- return parts.map(function(part, index) {
- return (index > 0 ? 'm=' + part : part).trim() + '\r\n';
- });
-};
-
-// Returns lines that start with a certain prefix.
-SDPUtils.matchPrefix = function(blob, prefix) {
- return SDPUtils.splitLines(blob).filter(function(line) {
- return line.indexOf(prefix) === 0;
- });
-};
-
-// Parses an ICE candidate line. Sample input:
-// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8
-// rport 55996"
-SDPUtils.parseCandidate = function(line) {
- var parts;
- // Parse both variants.
- if (line.indexOf('a=candidate:') === 0) {
- parts = line.substring(12).split(' ');
- } else {
- parts = line.substring(10).split(' ');
- }
-
- var candidate = {
- foundation: parts[0],
- component: parts[1],
- protocol: parts[2].toLowerCase(),
- priority: parseInt(parts[3], 10),
- ip: parts[4],
- port: parseInt(parts[5], 10),
- // skip parts[6] == 'typ'
- type: parts[7]
- };
-
- for (var i = 8; i < parts.length; i += 2) {
- switch (parts[i]) {
- case 'raddr':
- candidate.relatedAddress = parts[i + 1];
- break;
- case 'rport':
- candidate.relatedPort = parseInt(parts[i + 1], 10);
- break;
- case 'tcptype':
- candidate.tcpType = parts[i + 1];
- break;
- default: // extension handling, in particular ufrag
- candidate[parts[i]] = parts[i + 1];
- break;
- }
- }
- return candidate;
-};
-
-// Translates a candidate object into SDP candidate attribute.
-SDPUtils.writeCandidate = function(candidate) {
- var sdp = [];
- sdp.push(candidate.foundation);
- sdp.push(candidate.component);
- sdp.push(candidate.protocol.toUpperCase());
- sdp.push(candidate.priority);
- sdp.push(candidate.ip);
- sdp.push(candidate.port);
-
- var type = candidate.type;
- sdp.push('typ');
- sdp.push(type);
- if (type !== 'host' && candidate.relatedAddress &&
- candidate.relatedPort) {
- sdp.push('raddr');
- sdp.push(candidate.relatedAddress); // was: relAddr
- sdp.push('rport');
- sdp.push(candidate.relatedPort); // was: relPort
- }
- if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') {
- sdp.push('tcptype');
- sdp.push(candidate.tcpType);
- }
- return 'candidate:' + sdp.join(' ');
-};
-
-// Parses an ice-options line, returns an array of option tags.
-// a=ice-options:foo bar
-SDPUtils.parseIceOptions = function(line) {
- return line.substr(14).split(' ');
-}
-
-// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:
-// a=rtpmap:111 opus/48000/2
-SDPUtils.parseRtpMap = function(line) {
- var parts = line.substr(9).split(' ');
- var parsed = {
- payloadType: parseInt(parts.shift(), 10) // was: id
- };
-
- parts = parts[0].split('/');
-
- parsed.name = parts[0];
- parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
- // was: channels
- parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1;
- return parsed;
-};
-
-// Generate an a=rtpmap line from RTCRtpCodecCapability or
-// RTCRtpCodecParameters.
-SDPUtils.writeRtpMap = function(codec) {
- var pt = codec.payloadType;
- if (codec.preferredPayloadType !== undefined) {
- pt = codec.preferredPayloadType;
- }
- return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate +
- (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n';
-};
-
-// Parses an a=extmap line (headerextension from RFC 5285). Sample input:
-// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
-// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset
-SDPUtils.parseExtmap = function(line) {
- var parts = line.substr(9).split(' ');
- return {
- id: parseInt(parts[0], 10),
- direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv',
- uri: parts[1]
- };
-};
-
-// Generates a=extmap line from RTCRtpHeaderExtensionParameters or
-// RTCRtpHeaderExtension.
-SDPUtils.writeExtmap = function(headerExtension) {
- return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) +
- (headerExtension.direction && headerExtension.direction !== 'sendrecv'
- ? '/' + headerExtension.direction
- : '') +
- ' ' + headerExtension.uri + '\r\n';
-};
-
-// Parses an ftmp line, returns dictionary. Sample input:
-// a=fmtp:96 vbr=on;cng=on
-// Also deals with vbr=on; cng=on
-SDPUtils.parseFmtp = function(line) {
- var parsed = {};
- var kv;
- var parts = line.substr(line.indexOf(' ') + 1).split(';');
- for (var j = 0; j < parts.length; j++) {
- kv = parts[j].trim().split('=');
- parsed[kv[0].trim()] = kv[1];
- }
- return parsed;
-};
-
-// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.
-SDPUtils.writeFmtp = function(codec) {
- var line = '';
- var pt = codec.payloadType;
- if (codec.preferredPayloadType !== undefined) {
- pt = codec.preferredPayloadType;
- }
- if (codec.parameters && Object.keys(codec.parameters).length) {
- var params = [];
- Object.keys(codec.parameters).forEach(function(param) {
- params.push(param + '=' + codec.parameters[param]);
- });
- line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n';
- }
- return line;
-};
-
-// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:
-// a=rtcp-fb:98 nack rpsi
-SDPUtils.parseRtcpFb = function(line) {
- var parts = line.substr(line.indexOf(' ') + 1).split(' ');
- return {
- type: parts.shift(),
- parameter: parts.join(' ')
- };
-};
-// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.
-SDPUtils.writeRtcpFb = function(codec) {
- var lines = '';
- var pt = codec.payloadType;
- if (codec.preferredPayloadType !== undefined) {
- pt = codec.preferredPayloadType;
- }
- if (codec.rtcpFeedback && codec.rtcpFeedback.length) {
- // FIXME: special handling for trr-int?
- codec.rtcpFeedback.forEach(function(fb) {
- lines += 'a=rtcp-fb:' + pt + ' ' + fb.type +
- (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') +
- '\r\n';
- });
- }
- return lines;
-};
-
-// Parses an RFC 5576 ssrc media attribute. Sample input:
-// a=ssrc:3735928559 cname:something
-SDPUtils.parseSsrcMedia = function(line) {
- var sp = line.indexOf(' ');
- var parts = {
- ssrc: parseInt(line.substr(7, sp - 7), 10)
- };
- var colon = line.indexOf(':', sp);
- if (colon > -1) {
- parts.attribute = line.substr(sp + 1, colon - sp - 1);
- parts.value = line.substr(colon + 1);
- } else {
- parts.attribute = line.substr(sp + 1);
- }
- return parts;
-};
-
-// Extracts the MID (RFC 5888) from a media section.
-// returns the MID or undefined if no mid line was found.
-SDPUtils.getMid = function(mediaSection) {
- var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0];
- if (mid) {
- return mid.substr(6);
- }
-}
-
-SDPUtils.parseFingerprint = function(line) {
- var parts = line.substr(14).split(' ');
- return {
- algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge.
- value: parts[1]
- };
-};
-
-// Extracts DTLS parameters from SDP media section or sessionpart.
-// FIXME: for consistency with other functions this should only
-// get the fingerprint line as input. See also getIceParameters.
-SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {
- var lines = SDPUtils.matchPrefix(mediaSection + sessionpart,
- 'a=fingerprint:');
- // Note: a=setup line is ignored since we use the 'auto' role.
- // Note2: 'algorithm' is not case sensitive except in Edge.
- return {
- role: 'auto',
- fingerprints: lines.map(SDPUtils.parseFingerprint)
- };
-};
-
-// Serializes DTLS parameters to SDP.
-SDPUtils.writeDtlsParameters = function(params, setupType) {
- var sdp = 'a=setup:' + setupType + '\r\n';
- params.fingerprints.forEach(function(fp) {
- sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n';
- });
- return sdp;
-};
-// Parses ICE information from SDP media section or sessionpart.
-// FIXME: for consistency with other functions this should only
-// get the ice-ufrag and ice-pwd lines as input.
-SDPUtils.getIceParameters = function(mediaSection, sessionpart) {
- var lines = SDPUtils.splitLines(mediaSection);
- // Search in session part, too.
- lines = lines.concat(SDPUtils.splitLines(sessionpart));
- var iceParameters = {
- usernameFragment: lines.filter(function(line) {
- return line.indexOf('a=ice-ufrag:') === 0;
- })[0].substr(12),
- password: lines.filter(function(line) {
- return line.indexOf('a=ice-pwd:') === 0;
- })[0].substr(10)
- };
- return iceParameters;
-};
-
-// Serializes ICE parameters to SDP.
-SDPUtils.writeIceParameters = function(params) {
- return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' +
- 'a=ice-pwd:' + params.password + '\r\n';
-};
-
-// Parses the SDP media section and returns RTCRtpParameters.
-SDPUtils.parseRtpParameters = function(mediaSection) {
- var description = {
- codecs: [],
- headerExtensions: [],
- fecMechanisms: [],
- rtcp: []
- };
- var lines = SDPUtils.splitLines(mediaSection);
- var mline = lines[0].split(' ');
- for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..]
- var pt = mline[i];
- var rtpmapline = SDPUtils.matchPrefix(
- mediaSection, 'a=rtpmap:' + pt + ' ')[0];
- if (rtpmapline) {
- var codec = SDPUtils.parseRtpMap(rtpmapline);
- var fmtps = SDPUtils.matchPrefix(
- mediaSection, 'a=fmtp:' + pt + ' ');
- // Only the first a=fmtp: is considered.
- codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {};
- codec.rtcpFeedback = SDPUtils.matchPrefix(
- mediaSection, 'a=rtcp-fb:' + pt + ' ')
- .map(SDPUtils.parseRtcpFb);
- description.codecs.push(codec);
- // parse FEC mechanisms from rtpmap lines.
- switch (codec.name.toUpperCase()) {
- case 'RED':
- case 'ULPFEC':
- description.fecMechanisms.push(codec.name.toUpperCase());
- break;
- default: // only RED and ULPFEC are recognized as FEC mechanisms.
- break;
- }
- }
- }
- SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) {
- description.headerExtensions.push(SDPUtils.parseExtmap(line));
- });
- // FIXME: parse rtcp.
- return description;
-};
-
-// Generates parts of the SDP media section describing the capabilities /
-// parameters.
-SDPUtils.writeRtpDescription = function(kind, caps) {
- var sdp = '';
-
- // Build the mline.
- sdp += 'm=' + kind + ' ';
- sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs.
- sdp += ' UDP/TLS/RTP/SAVPF ';
- sdp += caps.codecs.map(function(codec) {
- if (codec.preferredPayloadType !== undefined) {
- return codec.preferredPayloadType;
- }
- return codec.payloadType;
- }).join(' ') + '\r\n';
-
- sdp += 'c=IN IP4 0.0.0.0\r\n';
- sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n';
-
- // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
- caps.codecs.forEach(function(codec) {
- sdp += SDPUtils.writeRtpMap(codec);
- sdp += SDPUtils.writeFmtp(codec);
- sdp += SDPUtils.writeRtcpFb(codec);
- });
- var maxptime = 0;
- caps.codecs.forEach(function(codec) {
- if (codec.maxptime > maxptime) {
- maxptime = codec.maxptime;
- }
- });
- if (maxptime > 0) {
- sdp += 'a=maxptime:' + maxptime + '\r\n';
- }
- sdp += 'a=rtcp-mux\r\n';
-
- caps.headerExtensions.forEach(function(extension) {
- sdp += SDPUtils.writeExtmap(extension);
- });
- // FIXME: write fecMechanisms.
- return sdp;
-};
-
-// Parses the SDP media section and returns an array of
-// RTCRtpEncodingParameters.
-SDPUtils.parseRtpEncodingParameters = function(mediaSection) {
- var encodingParameters = [];
- var description = SDPUtils.parseRtpParameters(mediaSection);
- var hasRed = description.fecMechanisms.indexOf('RED') !== -1;
- var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1;
-
- // filter a=ssrc:... cname:, ignore PlanB-msid
- var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
- .map(function(line) {
- return SDPUtils.parseSsrcMedia(line);
- })
- .filter(function(parts) {
- return parts.attribute === 'cname';
- });
- var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc;
- var secondarySsrc;
-
- var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID')
- .map(function(line) {
- var parts = line.split(' ');
- parts.shift();
- return parts.map(function(part) {
- return parseInt(part, 10);
- });
- });
- if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) {
- secondarySsrc = flows[0][1];
- }
-
- description.codecs.forEach(function(codec) {
- if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) {
- var encParam = {
- ssrc: primarySsrc,
- codecPayloadType: parseInt(codec.parameters.apt, 10),
- rtx: {
- ssrc: secondarySsrc
- }
- };
- encodingParameters.push(encParam);
- if (hasRed) {
- encParam = JSON.parse(JSON.stringify(encParam));
- encParam.fec = {
- ssrc: secondarySsrc,
- mechanism: hasUlpfec ? 'red+ulpfec' : 'red'
- };
- encodingParameters.push(encParam);
- }
- }
- });
- if (encodingParameters.length === 0 && primarySsrc) {
- encodingParameters.push({
- ssrc: primarySsrc
- });
- }
-
- // we support both b=AS and b=TIAS but interpret AS as TIAS.
- var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b=');
- if (bandwidth.length) {
- if (bandwidth[0].indexOf('b=TIAS:') === 0) {
- bandwidth = parseInt(bandwidth[0].substr(7), 10);
- } else if (bandwidth[0].indexOf('b=AS:') === 0) {
- bandwidth = parseInt(bandwidth[0].substr(5), 10);
- }
- encodingParameters.forEach(function(params) {
- params.maxBitrate = bandwidth;
- });
- }
- return encodingParameters;
-};
-
-// parses http://draft.ortc.org/#rtcrtcpparameters*
-SDPUtils.parseRtcpParameters = function(mediaSection) {
- var rtcpParameters = {};
-
- var cname;
- // Gets the first SSRC. Note that with RTX there might be multiple
- // SSRCs.
- var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
- .map(function(line) {
- return SDPUtils.parseSsrcMedia(line);
- })
- .filter(function(obj) {
- return obj.attribute === 'cname';
- })[0];
- if (remoteSsrc) {
- rtcpParameters.cname = remoteSsrc.value;
- rtcpParameters.ssrc = remoteSsrc.ssrc;
- }
-
- // Edge uses the compound attribute instead of reducedSize
- // compound is !reducedSize
- var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize');
- rtcpParameters.reducedSize = rsize.length > 0;
- rtcpParameters.compound = rsize.length === 0;
-
- // parses the rtcp-mux attrіbute.
- // Note that Edge does not support unmuxed RTCP.
- var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux');
- rtcpParameters.mux = mux.length > 0;
-
- return rtcpParameters;
-};
-
-// parses either a=msid: or a=ssrc:... msid lines and returns
-// the id of the MediaStream and MediaStreamTrack.
-SDPUtils.parseMsid = function(mediaSection) {
- var parts;
- var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:');
- if (spec.length === 1) {
- parts = spec[0].substr(7).split(' ');
- return {stream: parts[0], track: parts[1]};
- }
- var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
- .map(function(line) {
- return SDPUtils.parseSsrcMedia(line);
- })
- .filter(function(parts) {
- return parts.attribute === 'msid';
- });
- if (planB.length > 0) {
- parts = planB[0].value.split(' ');
- return {stream: parts[0], track: parts[1]};
- }
-};
-
-SDPUtils.writeSessionBoilerplate = function() {
- // FIXME: sess-id should be an NTP timestamp.
- return 'v=0\r\n' +
- 'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' +
- 's=-\r\n' +
- 't=0 0\r\n';
-};
-
-SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) {
- var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);
-
- // Map ICE parameters (ufrag, pwd) to SDP.
- sdp += SDPUtils.writeIceParameters(
- transceiver.iceGatherer.getLocalParameters());
-
- // Map DTLS parameters to SDP.
- sdp += SDPUtils.writeDtlsParameters(
- transceiver.dtlsTransport.getLocalParameters(),
- type === 'offer' ? 'actpass' : 'active');
-
- sdp += 'a=mid:' + transceiver.mid + '\r\n';
-
- if (transceiver.direction) {
- sdp += 'a=' + transceiver.direction + '\r\n';
- } else if (transceiver.rtpSender && transceiver.rtpReceiver) {
- sdp += 'a=sendrecv\r\n';
- } else if (transceiver.rtpSender) {
- sdp += 'a=sendonly\r\n';
- } else if (transceiver.rtpReceiver) {
- sdp += 'a=recvonly\r\n';
- } else {
- sdp += 'a=inactive\r\n';
- }
-
- if (transceiver.rtpSender) {
- // spec.
- var msid = 'msid:' + stream.id + ' ' +
- transceiver.rtpSender.track.id + '\r\n';
- sdp += 'a=' + msid;
-
- // for Chrome.
- sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
- ' ' + msid;
- if (transceiver.sendEncodingParameters[0].rtx) {
- sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
- ' ' + msid;
- sdp += 'a=ssrc-group:FID ' +
- transceiver.sendEncodingParameters[0].ssrc + ' ' +
- transceiver.sendEncodingParameters[0].rtx.ssrc +
- '\r\n';
- }
- }
- // FIXME: this should be written by writeRtpDescription.
- sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
- ' cname:' + SDPUtils.localCName + '\r\n';
- if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) {
- sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc +
- ' cname:' + SDPUtils.localCName + '\r\n';
- }
- return sdp;
-};
-
-// Gets the direction from the mediaSection or the sessionpart.
-SDPUtils.getDirection = function(mediaSection, sessionpart) {
- // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.
- var lines = SDPUtils.splitLines(mediaSection);
- for (var i = 0; i < lines.length; i++) {
- switch (lines[i]) {
- case 'a=sendrecv':
- case 'a=sendonly':
- case 'a=recvonly':
- case 'a=inactive':
- return lines[i].substr(2);
- default:
- // FIXME: What should happen here?
- }
- }
- if (sessionpart) {
- return SDPUtils.getDirection(sessionpart);
- }
- return 'sendrecv';
-};
-
-SDPUtils.getKind = function(mediaSection) {
- var lines = SDPUtils.splitLines(mediaSection);
- var mline = lines[0].split(' ');
- return mline[0].substr(2);
-};
-
-SDPUtils.isRejected = function(mediaSection) {
- return mediaSection.split(' ', 2)[1] === '0';
-};
-
-// Expose public methods.
-module.exports = SDPUtils;
-
-},{}],33:[function(require,module,exports){
-(function (global){
-'use strict';
-
-var transportList = require('./transport-list');
-
-module.exports = require('./main')(transportList);
-
-// TODO can't get rid of this until all servers do
-if ('_sockjs_onload' in global) {
- setTimeout(global._sockjs_onload, 1);
-}
-
-}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-
-},{"./main":46,"./transport-list":48}],34:[function(require,module,exports){
-'use strict';
-
-var inherits = require('inherits')
- , Event = require('./event')
- ;
-
-function CloseEvent() {
- Event.call(this);
- this.initEvent('close', false, false);
- this.wasClean = false;
- this.code = 0;
- this.reason = '';
-}
-
-inherits(CloseEvent, Event);
-
-module.exports = CloseEvent;
-
-},{"./event":36,"inherits":7}],35:[function(require,module,exports){
-'use strict';
-
-var inherits = require('inherits')
- , EventTarget = require('./eventtarget')
- ;
-
-function EventEmitter() {
- EventTarget.call(this);
-}
-
-inherits(EventEmitter, EventTarget);
-
-EventEmitter.prototype.removeAllListeners = function(type) {
- if (type) {
- delete this._listeners[type];
- } else {
- this._listeners = {};
- }
-};
-
-EventEmitter.prototype.once = function(type, listener) {
- var self = this
- , fired = false;
-
- function g() {
- self.removeListener(type, g);
-
- if (!fired) {
- fired = true;
- listener.apply(this, arguments);
- }
- }
-
- this.on(type, g);
-};
-
-EventEmitter.prototype.emit = function() {
- var type = arguments[0];
- var listeners = this._listeners[type];
- if (!listeners) {
- return;
- }
- // equivalent of Array.prototype.slice.call(arguments, 1);
- var l = arguments.length;
- var args = new Array(l - 1);
- for (var ai = 1; ai < l; ai++) {
- args[ai - 1] = arguments[ai];
- }
- for (var i = 0; i < listeners.length; i++) {
- listeners[i].apply(this, args);
- }
-};
-
-EventEmitter.prototype.on = EventEmitter.prototype.addListener = EventTarget.prototype.addEventListener;
-EventEmitter.prototype.removeListener = EventTarget.prototype.removeEventListener;
-
-module.exports.EventEmitter = EventEmitter;
-
-},{"./eventtarget":37,"inherits":7}],36:[function(require,module,exports){
-'use strict';
-
-function Event(eventType) {
- this.type = eventType;
-}
-
-Event.prototype.initEvent = function(eventType, canBubble, cancelable) {
- this.type = eventType;
- this.bubbles = canBubble;
- this.cancelable = cancelable;
- this.timeStamp = +new Date();
- return this;
-};
-
-Event.prototype.stopPropagation = function() {};
-Event.prototype.preventDefault = function() {};
-
-Event.CAPTURING_PHASE = 1;
-Event.AT_TARGET = 2;
-Event.BUBBLING_PHASE = 3;
-
-module.exports = Event;
-
-},{}],37:[function(require,module,exports){
-'use strict';
-
-/* Simplified implementation of DOM2 EventTarget.
- * http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget
- */
-
-function EventTarget() {
- this._listeners = {};
-}
-
-EventTarget.prototype.addEventListener = function(eventType, listener) {
- if (!(eventType in this._listeners)) {
- this._listeners[eventType] = [];
- }
- var arr = this._listeners[eventType];
- // #4
- if (arr.indexOf(listener) === -1) {
- // Make a copy so as not to interfere with a current dispatchEvent.
- arr = arr.concat([listener]);
- }
- this._listeners[eventType] = arr;
-};
-
-EventTarget.prototype.removeEventListener = function(eventType, listener) {
- var arr = this._listeners[eventType];
- if (!arr) {
- return;
- }
- var idx = arr.indexOf(listener);
- if (idx !== -1) {
- if (arr.length > 1) {
- // Make a copy so as not to interfere with a current dispatchEvent.
- this._listeners[eventType] = arr.slice(0, idx).concat(arr.slice(idx + 1));
- } else {
- delete this._listeners[eventType];
- }
- return;
- }
-};
-
-EventTarget.prototype.dispatchEvent = function() {
- var event = arguments[0];
- var t = event.type;
- // equivalent of Array.prototype.slice.call(arguments, 0);
- var args = arguments.length === 1 ? [event] : Array.apply(null, arguments);
- // TODO: This doesn't match the real behavior; per spec, onfoo get
- // their place in line from the /first/ time they're set from
- // non-null. Although WebKit bumps it to the end every time it's
- // set.
- if (this['on' + t]) {
- this['on' + t].apply(this, args);
- }
- if (t in this._listeners) {
- // Grab a reference to the listeners list. removeEventListener may alter the list.
- var listeners = this._listeners[t];
- for (var i = 0; i < listeners.length; i++) {
- listeners[i].apply(this, args);
- }
- }
-};
-
-module.exports = EventTarget;
-
-},{}],38:[function(require,module,exports){
-'use strict';
-
-var inherits = require('inherits')
- , Event = require('./event')
- ;
-
-function TransportMessageEvent(data) {
- Event.call(this);
- this.initEvent('message', false, false);
- this.data = data;
-}
-
-inherits(TransportMessageEvent, Event);
-
-module.exports = TransportMessageEvent;
-
-},{"./event":36,"inherits":7}],39:[function(require,module,exports){
-'use strict';
-
-var JSON3 = require('json3')
- , iframeUtils = require('./utils/iframe')
- ;
-
-function FacadeJS(transport) {
- this._transport = transport;
- transport.on('message', this._transportMessage.bind(this));
- transport.on('close', this._transportClose.bind(this));
-}
-
-FacadeJS.prototype._transportClose = function(code, reason) {
- iframeUtils.postMessage('c', JSON3.stringify([code, reason]));
-};
-FacadeJS.prototype._transportMessage = function(frame) {
- iframeUtils.postMessage('t', frame);
-};
-FacadeJS.prototype._send = function(data) {
- this._transport.send(data);
-};
-FacadeJS.prototype._close = function() {
- this._transport.close();
- this._transport.removeAllListeners();
-};
-
-module.exports = FacadeJS;
-
-},{"./utils/iframe":79,"json3":8}],40:[function(require,module,exports){
-(function (process){
-'use strict';
-
-var urlUtils = require('./utils/url')
- , eventUtils = require('./utils/event')
- , JSON3 = require('json3')
- , FacadeJS = require('./facade')
- , InfoIframeReceiver = require('./info-iframe-receiver')
- , iframeUtils = require('./utils/iframe')
- , loc = require('./location')
- ;
-
-var debug = function() {};
-if (process.env.NODE_ENV !== 'production') {
- debug = require('debug')('sockjs-client:iframe-bootstrap');
-}
-
-module.exports = function(SockJS, availableTransports) {
- var transportMap = {};
- availableTransports.forEach(function(at) {
- if (at.facadeTransport) {
- transportMap[at.facadeTransport.transportName] = at.facadeTransport;
- }
- });
-
- // hard-coded for the info iframe
- // TODO see if we can make this more dynamic
- transportMap[InfoIframeReceiver.transportName] = InfoIframeReceiver;
- var parentOrigin;
-
- /* eslint-disable camelcase */
- SockJS.bootstrap_iframe = function() {
- /* eslint-enable camelcase */
- var facade;
- iframeUtils.currentWindowId = loc.hash.slice(1);
- var onMessage = function(e) {
- if (e.source !== parent) {
- return;
- }
- if (typeof parentOrigin === 'undefined') {
- parentOrigin = e.origin;
- }
- if (e.origin !== parentOrigin) {
- return;
- }
-
- var iframeMessage;
- try {
- iframeMessage = JSON3.parse(e.data);
- } catch (ignored) {
- debug('bad json', e.data);
- return;
- }
-
- if (iframeMessage.windowId !== iframeUtils.currentWindowId) {
- return;
- }
- switch (iframeMessage.type) {
- case 's':
- var p;
- try {
- p = JSON3.parse(iframeMessage.data);
- } catch (ignored) {
- debug('bad json', iframeMessage.data);
- break;
- }
- var version = p[0];
- var transport = p[1];
- var transUrl = p[2];
- var baseUrl = p[3];
- debug(version, transport, transUrl, baseUrl);
- // change this to semver logic
- if (version !== SockJS.version) {
- throw new Error('Incompatible SockJS! Main site uses:' +
- ' "' + version + '", the iframe:' +
- ' "' + SockJS.version + '".');
- }
-
- if (!urlUtils.isOriginEqual(transUrl, loc.href) ||
- !urlUtils.isOriginEqual(baseUrl, loc.href)) {
- throw new Error('Can\'t connect to different domain from within an ' +
- 'iframe. (' + loc.href + ', ' + transUrl + ', ' + baseUrl + ')');
- }
- facade = new FacadeJS(new transportMap[transport](transUrl, baseUrl));
- break;
- case 'm':
- facade._send(iframeMessage.data);
- break;
- case 'c':
- if (facade) {
- facade._close();
- }
- facade = null;
- break;
- }
- };
-
- eventUtils.attachEvent('message', onMessage);
-
- // Start
- iframeUtils.postMessage('s');
- };
-};
-
-}).call(this,require('_process'))
-
-},{"./facade":39,"./info-iframe-receiver":42,"./location":45,"./utils/event":78,"./utils/iframe":79,"./utils/url":84,"_process":114,"debug":1,"json3":8}],41:[function(require,module,exports){
-(function (process){
-'use strict';
-
-var EventEmitter = require('events').EventEmitter
- , inherits = require('inherits')
- , JSON3 = require('json3')
- , objectUtils = require('./utils/object')
- ;
-
-var debug = function() {};
-if (process.env.NODE_ENV !== 'production') {
- debug = require('debug')('sockjs-client:info-ajax');
-}
-
-function InfoAjax(url, AjaxObject) {
- EventEmitter.call(this);
-
- var self = this;
- var t0 = +new Date();
- this.xo = new AjaxObject('GET', url);
-
- this.xo.once('finish', function(status, text) {
- var info, rtt;
- if (status === 200) {
- rtt = (+new Date()) - t0;
- if (text) {
- try {
- info = JSON3.parse(text);
- } catch (e) {
- debug('bad json', text);
- }
- }
-
- if (!objectUtils.isObject(info)) {
- info = {};
- }
- }
- self.emit('finish', info, rtt);
- self.removeAllListeners();
- });
-}
-
-inherits(InfoAjax, EventEmitter);
-
-InfoAjax.prototype.close = function() {
- this.removeAllListeners();
- this.xo.close();
-};
-
-module.exports = InfoAjax;
-
-}).call(this,require('_process'))
-
-},{"./utils/object":81,"_process":114,"debug":1,"events":35,"inherits":7,"json3":8}],42:[function(require,module,exports){
-'use strict';
-
-var inherits = require('inherits')
- , EventEmitter = require('events').EventEmitter
- , JSON3 = require('json3')
- , XHRLocalObject = require('./transport/sender/xhr-local')
- , InfoAjax = require('./info-ajax')
- ;
-
-function InfoReceiverIframe(transUrl) {
- var self = this;
- EventEmitter.call(this);
-
- this.ir = new InfoAjax(transUrl, XHRLocalObject);
- this.ir.once('finish', function(info, rtt) {
- self.ir = null;
- self.emit('message', JSON3.stringify([info, rtt]));
- });
-}
-
-inherits(InfoReceiverIframe, EventEmitter);
-
-InfoReceiverIframe.transportName = 'iframe-info-receiver';
-
-InfoReceiverIframe.prototype.close = function() {
- if (this.ir) {
- this.ir.close();
- this.ir = null;
- }
- this.removeAllListeners();
-};
-
-module.exports = InfoReceiverIframe;
-
-},{"./info-ajax":41,"./transport/sender/xhr-local":69,"events":35,"inherits":7,"json3":8}],43:[function(require,module,exports){
-(function (process,global){
-'use strict';
-
-var EventEmitter = require('events').EventEmitter
- , inherits = require('inherits')
- , JSON3 = require('json3')
- , utils = require('./utils/event')
- , IframeTransport = require('./transport/iframe')
- , InfoReceiverIframe = require('./info-iframe-receiver')
- ;
-
-var debug = function() {};
-if (process.env.NODE_ENV !== 'production') {
- debug = require('debug')('sockjs-client:info-iframe');
-}
-
-function InfoIframe(baseUrl, url) {
- var self = this;
- EventEmitter.call(this);
-
- var go = function() {
- var ifr = self.ifr = new IframeTransport(InfoReceiverIframe.transportName, url, baseUrl);
-
- ifr.once('message', function(msg) {
- if (msg) {
- var d;
- try {
- d = JSON3.parse(msg);
- } catch (e) {
- debug('bad json', msg);
- self.emit('finish');
- self.close();
- return;
- }
-
- var info = d[0], rtt = d[1];
- self.emit('finish', info, rtt);
- }
- self.close();
- });
-
- ifr.once('close', function() {
- self.emit('finish');
- self.close();
- });
- };
-
- // TODO this seems the same as the 'needBody' from transports
- if (!global.document.body) {
- utils.attachEvent('load', go);
- } else {
- go();
- }
-}
-
-inherits(InfoIframe, EventEmitter);
-
-InfoIframe.enabled = function() {
- return IframeTransport.enabled();
-};
-
-InfoIframe.prototype.close = function() {
- if (this.ifr) {
- this.ifr.close();
- }
- this.removeAllListeners();
- this.ifr = null;
-};
-
-module.exports = InfoIframe;
-
-}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-
-},{"./info-iframe-receiver":42,"./transport/iframe":54,"./utils/event":78,"_process":114,"debug":1,"events":35,"inherits":7,"json3":8}],44:[function(require,module,exports){
-(function (process){
-'use strict';
-
-var EventEmitter = require('events').EventEmitter
- , inherits = require('inherits')
- , urlUtils = require('./utils/url')
- , XDR = require('./transport/sender/xdr')
- , XHRCors = require('./transport/sender/xhr-cors')
- , XHRLocal = require('./transport/sender/xhr-local')
- , XHRFake = require('./transport/sender/xhr-fake')
- , InfoIframe = require('./info-iframe')
- , InfoAjax = require('./info-ajax')
- ;
-
-var debug = function() {};
-if (process.env.NODE_ENV !== 'production') {
- debug = require('debug')('sockjs-client:info-receiver');
-}
-
-function InfoReceiver(baseUrl, urlInfo) {
- debug(baseUrl);
- var self = this;
- EventEmitter.call(this);
-
- setTimeout(function() {
- self.doXhr(baseUrl, urlInfo);
- }, 0);
-}
-
-inherits(InfoReceiver, EventEmitter);
-
-// TODO this is currently ignoring the list of available transports and the whitelist
-
-InfoReceiver._getReceiver = function(baseUrl, url, urlInfo) {
- // determine method of CORS support (if needed)
- if (urlInfo.sameOrigin) {
- return new InfoAjax(url, XHRLocal);
- }
- if (XHRCors.enabled) {
- return new InfoAjax(url, XHRCors);
- }
- if (XDR.enabled && urlInfo.sameScheme) {
- return new InfoAjax(url, XDR);
- }
- if (InfoIframe.enabled()) {
- return new InfoIframe(baseUrl, url);
- }
- return new InfoAjax(url, XHRFake);
-};
-
-InfoReceiver.prototype.doXhr = function(baseUrl, urlInfo) {
- var self = this
- , url = urlUtils.addPath(baseUrl, '/info')
- ;
- debug('doXhr', url);
-
- this.xo = InfoReceiver._getReceiver(baseUrl, url, urlInfo);
-
- this.timeoutRef = setTimeout(function() {
- debug('timeout');
- self._cleanup(false);
- self.emit('finish');
- }, InfoReceiver.timeout);
-
- this.xo.once('finish', function(info, rtt) {
- debug('finish', info, rtt);
- self._cleanup(true);
- self.emit('finish', info, rtt);
- });
-};
-
-InfoReceiver.prototype._cleanup = function(wasClean) {
- debug('_cleanup');
- clearTimeout(this.timeoutRef);
- this.timeoutRef = null;
- if (!wasClean && this.xo) {
- this.xo.close();
- }
- this.xo = null;
-};
-
-InfoReceiver.prototype.close = function() {
- debug('close');
- this.removeAllListeners();
- this._cleanup(false);
-};
-
-InfoReceiver.timeout = 8000;
-
-module.exports = InfoReceiver;
-
-}).call(this,require('_process'))
-
-},{"./info-ajax":41,"./info-iframe":43,"./transport/sender/xdr":66,"./transport/sender/xhr-cors":67,"./transport/sender/xhr-fake":68,"./transport/sender/xhr-local":69,"./utils/url":84,"_process":114,"debug":1,"events":35,"inherits":7}],45:[function(require,module,exports){
-(function (global){
-'use strict';
-
-module.exports = global.location || {
- origin: 'http://localhost:80'
-, protocol: 'http'
-, host: 'localhost'
-, port: 80
-, href: 'http://localhost/'
-, hash: ''
-};
-
-}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-
-},{}],46:[function(require,module,exports){
-(function (process,global){
-'use strict';
-
-require('./shims');
-
-var URL = require('url-parse')
- , inherits = require('inherits')
- , JSON3 = require('json3')
- , random = require('./utils/random')
- , escape = require('./utils/escape')
- , urlUtils = require('./utils/url')
- , eventUtils = require('./utils/event')
- , transport = require('./utils/transport')
- , objectUtils = require('./utils/object')
- , browser = require('./utils/browser')
- , log = require('./utils/log')
- , Event = require('./event/event')
- , EventTarget = require('./event/eventtarget')
- , loc = require('./location')
- , CloseEvent = require('./event/close')
- , TransportMessageEvent = require('./event/trans-message')
- , InfoReceiver = require('./info-receiver')
- ;
-
-var debug = function() {};
-if (process.env.NODE_ENV !== 'production') {
- debug = require('debug')('sockjs-client:main');
-}
-
-var transports;
-
-// follow constructor steps defined at http://dev.w3.org/html5/websockets/#the-websocket-interface
-function SockJS(url, protocols, options) {
- if (!(this instanceof SockJS)) {
- return new SockJS(url, protocols, options);
- }
- if (arguments.length < 1) {
- throw new TypeError("Failed to construct 'SockJS: 1 argument required, but only 0 present");
- }
- EventTarget.call(this);
-
- this.readyState = SockJS.CONNECTING;
- this.extensions = '';
- this.protocol = '';
-
- // non-standard extension
- options = options || {};
- if (options.protocols_whitelist) {
- log.warn("'protocols_whitelist' is DEPRECATED. Use 'transports' instead.");
- }
- this._transportsWhitelist = options.transports;
- this._transportOptions = options.transportOptions || {};
-
- var sessionId = options.sessionId || 8;
- if (typeof sessionId === 'function') {
- this._generateSessionId = sessionId;
- } else if (typeof sessionId === 'number') {
- this._generateSessionId = function() {
- return random.string(sessionId);
- };
- } else {
- throw new TypeError('If sessionId is used in the options, it needs to be a number or a function.');
- }
-
- this._server = options.server || random.numberString(1000);
-
- // Step 1 of WS spec - parse and validate the url. Issue #8
- var parsedUrl = new URL(url);
- if (!parsedUrl.host || !parsedUrl.protocol) {
- throw new SyntaxError("The URL '" + url + "' is invalid");
- } else if (parsedUrl.hash) {
- throw new SyntaxError('The URL must not contain a fragment');
- } else if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
- throw new SyntaxError("The URL's scheme must be either 'http:' or 'https:'. '" + parsedUrl.protocol + "' is not allowed.");
- }
-
- var secure = parsedUrl.protocol === 'https:';
- // Step 2 - don't allow secure origin with an insecure protocol
- if (loc.protocol === 'https' && !secure) {
- throw new Error('SecurityError: An insecure SockJS connection may not be initiated from a page loaded over HTTPS');
- }
-
- // Step 3 - check port access - no need here
- // Step 4 - parse protocols argument
- if (!protocols) {
- protocols = [];
- } else if (!Array.isArray(protocols)) {
- protocols = [protocols];
- }
-
- // Step 5 - check protocols argument
- var sortedProtocols = protocols.sort();
- sortedProtocols.forEach(function(proto, i) {
- if (!proto) {
- throw new SyntaxError("The protocols entry '" + proto + "' is invalid.");
- }
- if (i < (sortedProtocols.length - 1) && proto === sortedProtocols[i + 1]) {
- throw new SyntaxError("The protocols entry '" + proto + "' is duplicated.");
- }
- });
-
- // Step 6 - convert origin
- var o = urlUtils.getOrigin(loc.href);
- this._origin = o ? o.toLowerCase() : null;
-
- // remove the trailing slash
- parsedUrl.set('pathname', parsedUrl.pathname.replace(/\/+$/, ''));
-
- // store the sanitized url
- this.url = parsedUrl.href;
- debug('using url', this.url);
-
- // Step 7 - start connection in background
- // obtain server info
- // http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html#section-26
- this._urlInfo = {
- nullOrigin: !browser.hasDomain()
- , sameOrigin: urlUtils.isOriginEqual(this.url, loc.href)
- , sameScheme: urlUtils.isSchemeEqual(this.url, loc.href)
- };
-
- this._ir = new InfoReceiver(this.url, this._urlInfo);
- this._ir.once('finish', this._receiveInfo.bind(this));
-}
-
-inherits(SockJS, EventTarget);
-
-function userSetCode(code) {
- return code === 1000 || (code >= 3000 && code <= 4999);
-}
-
-SockJS.prototype.close = function(code, reason) {
- // Step 1
- if (code && !userSetCode(code)) {
- throw new Error('InvalidAccessError: Invalid code');
- }
- // Step 2.4 states the max is 123 bytes, but we are just checking length
- if (reason && reason.length > 123) {
- throw new SyntaxError('reason argument has an invalid length');
- }
-
- // Step 3.1
- if (this.readyState === SockJS.CLOSING || this.readyState === SockJS.CLOSED) {
- return;
- }
-
- // TODO look at docs to determine how to set this
- var wasClean = true;
- this._close(code || 1000, reason || 'Normal closure', wasClean);
-};
-
-SockJS.prototype.send = function(data) {
- // #13 - convert anything non-string to string
- // TODO this currently turns objects into [object Object]
- if (typeof data !== 'string') {
- data = '' + data;
- }
- if (this.readyState === SockJS.CONNECTING) {
- throw new Error('InvalidStateError: The connection has not been established yet');
- }
- if (this.readyState !== SockJS.OPEN) {
- return;
- }
- this._transport.send(escape.quote(data));
-};
-
-SockJS.version = require('./version');
-
-SockJS.CONNECTING = 0;
-SockJS.OPEN = 1;
-SockJS.CLOSING = 2;
-SockJS.CLOSED = 3;
-
-SockJS.prototype._receiveInfo = function(info, rtt) {
- debug('_receiveInfo', rtt);
- this._ir = null;
- if (!info) {
- this._close(1002, 'Cannot connect to server');
- return;
- }
-
- // establish a round-trip timeout (RTO) based on the
- // round-trip time (RTT)
- this._rto = this.countRTO(rtt);
- // allow server to override url used for the actual transport
- this._transUrl = info.base_url ? info.base_url : this.url;
- info = objectUtils.extend(info, this._urlInfo);
- debug('info', info);
- // determine list of desired and supported transports
- var enabledTransports = transports.filterToEnabled(this._transportsWhitelist, info);
- this._transports = enabledTransports.main;
- debug(this._transports.length + ' enabled transports');
-
- this._connect();
-};
-
-SockJS.prototype._connect = function() {
- for (var Transport = this._transports.shift(); Transport; Transport = this._transports.shift()) {
- debug('attempt', Transport.transportName);
- if (Transport.needBody) {
- if (!global.document.body ||
- (typeof global.document.readyState !== 'undefined' &&
- global.document.readyState !== 'complete' &&
- global.document.readyState !== 'interactive')) {
- debug('waiting for body');
- this._transports.unshift(Transport);
- eventUtils.attachEvent('load', this._connect.bind(this));
- return;
- }
- }
-
- // calculate timeout based on RTO and round trips. Default to 5s
- var timeoutMs = (this._rto * Transport.roundTrips) || 5000;
- this._transportTimeoutId = setTimeout(this._transportTimeout.bind(this), timeoutMs);
- debug('using timeout', timeoutMs);
-
- var transportUrl = urlUtils.addPath(this._transUrl, '/' + this._server + '/' + this._generateSessionId());
- var options = this._transportOptions[Transport.transportName];
- debug('transport url', transportUrl);
- var transportObj = new Transport(transportUrl, this._transUrl, options);
- transportObj.on('message', this._transportMessage.bind(this));
- transportObj.once('close', this._transportClose.bind(this));
- transportObj.transportName = Transport.transportName;
- this._transport = transportObj;
-
- return;
- }
- this._close(2000, 'All transports failed', false);
-};
-
-SockJS.prototype._transportTimeout = function() {
- debug('_transportTimeout');
- if (this.readyState === SockJS.CONNECTING) {
- this._transportClose(2007, 'Transport timed out');
- }
-};
-
-SockJS.prototype._transportMessage = function(msg) {
- debug('_transportMessage', msg);
- var self = this
- , type = msg.slice(0, 1)
- , content = msg.slice(1)
- , payload
- ;
-
- // first check for messages that don't need a payload
- switch (type) {
- case 'o':
- this._open();
- return;
- case 'h':
- this.dispatchEvent(new Event('heartbeat'));
- debug('heartbeat', this.transport);
- return;
- }
-
- if (content) {
- try {
- payload = JSON3.parse(content);
- } catch (e) {
- debug('bad json', content);
- }
- }
-
- if (typeof payload === 'undefined') {
- debug('empty payload', content);
- return;
- }
-
- switch (type) {
- case 'a':
- if (Array.isArray(payload)) {
- payload.forEach(function(p) {
- debug('message', self.transport, p);
- self.dispatchEvent(new TransportMessageEvent(p));
- });
- }
- break;
- case 'm':
- debug('message', this.transport, payload);
- this.dispatchEvent(new TransportMessageEvent(payload));
- break;
- case 'c':
- if (Array.isArray(payload) && payload.length === 2) {
- this._close(payload[0], payload[1], true);
- }
- break;
- }
-};
-
-SockJS.prototype._transportClose = function(code, reason) {
- debug('_transportClose', this.transport, code, reason);
- if (this._transport) {
- this._transport.removeAllListeners();
- this._transport = null;
- this.transport = null;
- }
-
- if (!userSetCode(code) && code !== 2000 && this.readyState === SockJS.CONNECTING) {
- this._connect();
- return;
- }
-
- this._close(code, reason);
-};
-
-SockJS.prototype._open = function() {
- debug('_open', this._transport.transportName, this.readyState);
- if (this.readyState === SockJS.CONNECTING) {
- if (this._transportTimeoutId) {
- clearTimeout(this._transportTimeoutId);
- this._transportTimeoutId = null;
- }
- this.readyState = SockJS.OPEN;
- this.transport = this._transport.transportName;
- this.dispatchEvent(new Event('open'));
- debug('connected', this.transport);
- } else {
- // The server might have been restarted, and lost track of our
- // connection.
- this._close(1006, 'Server lost session');
- }
-};
-
-SockJS.prototype._close = function(code, reason, wasClean) {
- debug('_close', this.transport, code, reason, wasClean, this.readyState);
- var forceFail = false;
-
- if (this._ir) {
- forceFail = true;
- this._ir.close();
- this._ir = null;
- }
- if (this._transport) {
- this._transport.close();
- this._transport = null;
- this.transport = null;
- }
-
- if (this.readyState === SockJS.CLOSED) {
- throw new Error('InvalidStateError: SockJS has already been closed');
- }
-
- this.readyState = SockJS.CLOSING;
- setTimeout(function() {
- this.readyState = SockJS.CLOSED;
-
- if (forceFail) {
- this.dispatchEvent(new Event('error'));
- }
-
- var e = new CloseEvent('close');
- e.wasClean = wasClean || false;
- e.code = code || 1000;
- e.reason = reason;
-
- this.dispatchEvent(e);
- this.onmessage = this.onclose = this.onerror = null;
- debug('disconnected');
- }.bind(this), 0);
-};
-
-// See: http://www.erg.abdn.ac.uk/~gerrit/dccp/notes/ccid2/rto_estimator/
-// and RFC 2988.
-SockJS.prototype.countRTO = function(rtt) {
- // In a local environment, when using IE8/9 and the `jsonp-polling`
- // transport the time needed to establish a connection (the time that pass
- // from the opening of the transport to the call of `_dispatchOpen`) is
- // around 200msec (the lower bound used in the article above) and this
- // causes spurious timeouts. For this reason we calculate a value slightly
- // larger than that used in the article.
- if (rtt > 100) {
- return 4 * rtt; // rto > 400msec
- }
- return 300 + rtt; // 300msec < rto <= 400msec
-};
-
-module.exports = function(availableTransports) {
- transports = transport(availableTransports);
- require('./iframe-bootstrap')(SockJS, availableTransports);
- return SockJS;
-};
-
-}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-
-},{"./event/close":34,"./event/event":36,"./event/eventtarget":37,"./event/trans-message":38,"./iframe-bootstrap":40,"./info-receiver":44,"./location":45,"./shims":47,"./utils/browser":76,"./utils/escape":77,"./utils/event":78,"./utils/log":80,"./utils/object":81,"./utils/random":82,"./utils/transport":83,"./utils/url":84,"./version":85,"_process":114,"debug":1,"inherits":7,"json3":8,"url-parse":87}],47:[function(require,module,exports){
-/* eslint-disable */
-/* jscs: disable */
-'use strict';
-
-// pulled specific shims from https://github.com/es-shims/es5-shim
-
-var ArrayPrototype = Array.prototype;
-var ObjectPrototype = Object.prototype;
-var FunctionPrototype = Function.prototype;
-var StringPrototype = String.prototype;
-var array_slice = ArrayPrototype.slice;
-
-var _toString = ObjectPrototype.toString;
-var isFunction = function (val) {
- return ObjectPrototype.toString.call(val) === '[object Function]';
-};
-var isArray = function isArray(obj) {
- return _toString.call(obj) === '[object Array]';
-};
-var isString = function isString(obj) {
- return _toString.call(obj) === '[object String]';
-};
-
-var supportsDescriptors = Object.defineProperty && (function () {
- try {
- Object.defineProperty({}, 'x', {});
- return true;
- } catch (e) { /* this is ES3 */
- return false;
- }
-}());
-
-// Define configurable, writable and non-enumerable props
-// if they don't exist.
-var defineProperty;
-if (supportsDescriptors) {
- defineProperty = function (object, name, method, forceAssign) {
- if (!forceAssign && (name in object)) { return; }
- Object.defineProperty(object, name, {
- configurable: true,
- enumerable: false,
- writable: true,
- value: method
- });
- };
-} else {
- defineProperty = function (object, name, method, forceAssign) {
- if (!forceAssign && (name in object)) { return; }
- object[name] = method;
- };
-}
-var defineProperties = function (object, map, forceAssign) {
- for (var name in map) {
- if (ObjectPrototype.hasOwnProperty.call(map, name)) {
- defineProperty(object, name, map[name], forceAssign);
- }
- }
-};
-
-var toObject = function (o) {
- if (o == null) { // this matches both null and undefined
- throw new TypeError("can't convert " + o + ' to object');
- }
- return Object(o);
-};
-
-//
-// Util
-// ======
-//
-
-// ES5 9.4
-// http://es5.github.com/#x9.4
-// http://jsperf.com/to-integer
-
-function toInteger(num) {
- var n = +num;
- if (n !== n) { // isNaN
- n = 0;
- } else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) {
- n = (n > 0 || -1) * Math.floor(Math.abs(n));
- }
- return n;
-}
-
-function ToUint32(x) {
- return x >>> 0;
-}
-
-//
-// Function
-// ========
-//
-
-// ES-5 15.3.4.5
-// http://es5.github.com/#x15.3.4.5
-
-function Empty() {}
-
-defineProperties(FunctionPrototype, {
- bind: function bind(that) { // .length is 1
- // 1. Let Target be the this value.
- var target = this;
- // 2. If IsCallable(Target) is false, throw a TypeError exception.
- if (!isFunction(target)) {
- throw new TypeError('Function.prototype.bind called on incompatible ' + target);
- }
- // 3. Let A be a new (possibly empty) internal list of all of the
- // argument values provided after thisArg (arg1, arg2 etc), in order.
- // XXX slicedArgs will stand in for "A" if used
- var args = array_slice.call(arguments, 1); // for normal call
- // 4. Let F be a new native ECMAScript object.
- // 11. Set the [[Prototype]] internal property of F to the standard
- // built-in Function prototype object as specified in 15.3.3.1.
- // 12. Set the [[Call]] internal property of F as described in
- // 15.3.4.5.1.
- // 13. Set the [[Construct]] internal property of F as described in
- // 15.3.4.5.2.
- // 14. Set the [[HasInstance]] internal property of F as described in
- // 15.3.4.5.3.
- var binder = function () {
-
- if (this instanceof bound) {
- // 15.3.4.5.2 [[Construct]]
- // When the [[Construct]] internal method of a function object,
- // F that was created using the bind function is called with a
- // list of arguments ExtraArgs, the following steps are taken:
- // 1. Let target be the value of F's [[TargetFunction]]
- // internal property.
- // 2. If target has no [[Construct]] internal method, a
- // TypeError exception is thrown.
- // 3. Let boundArgs be the value of F's [[BoundArgs]] internal
- // property.
- // 4. Let args be a new list containing the same values as the
- // list boundArgs in the same order followed by the same
- // values as the list ExtraArgs in the same order.
- // 5. Return the result of calling the [[Construct]] internal
- // method of target providing args as the arguments.
-
- var result = target.apply(
- this,
- args.concat(array_slice.call(arguments))
- );
- if (Object(result) === result) {
- return result;
- }
- return this;
-
- } else {
- // 15.3.4.5.1 [[Call]]
- // When the [[Call]] internal method of a function object, F,
- // which was created using the bind function is called with a
- // this value and a list of arguments ExtraArgs, the following
- // steps are taken:
- // 1. Let boundArgs be the value of F's [[BoundArgs]] internal
- // property.
- // 2. Let boundThis be the value of F's [[BoundThis]] internal
- // property.
- // 3. Let target be the value of F's [[TargetFunction]] internal
- // property.
- // 4. Let args be a new list containing the same values as the
- // list boundArgs in the same order followed by the same
- // values as the list ExtraArgs in the same order.
- // 5. Return the result of calling the [[Call]] internal method
- // of target providing boundThis as the this value and
- // providing args as the arguments.
-
- // equiv: target.call(this, ...boundArgs, ...args)
- return target.apply(
- that,
- args.concat(array_slice.call(arguments))
- );
-
- }
-
- };
-
- // 15. If the [[Class]] internal property of Target is "Function", then
- // a. Let L be the length property of Target minus the length of A.
- // b. Set the length own property of F to either 0 or L, whichever is
- // larger.
- // 16. Else set the length own property of F to 0.
-
- var boundLength = Math.max(0, target.length - args.length);
-
- // 17. Set the attributes of the length own property of F to the values
- // specified in 15.3.5.1.
- var boundArgs = [];
- for (var i = 0; i < boundLength; i++) {
- boundArgs.push('$' + i);
- }
-
- // XXX Build a dynamic function with desired amount of arguments is the only
- // way to set the length property of a function.
- // In environments where Content Security Policies enabled (Chrome extensions,
- // for ex.) all use of eval or Function costructor throws an exception.
- // However in all of these environments Function.prototype.bind exists
- // and so this code will never be executed.
- var bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder);
-
- if (target.prototype) {
- Empty.prototype = target.prototype;
- bound.prototype = new Empty();
- // Clean up dangling references.
- Empty.prototype = null;
- }
-
- // TODO
- // 18. Set the [[Extensible]] internal property of F to true.
-
- // TODO
- // 19. Let thrower be the [[ThrowTypeError]] function Object (13.2.3).
- // 20. Call the [[DefineOwnProperty]] internal method of F with
- // arguments "caller", PropertyDescriptor {[[Get]]: thrower, [[Set]]:
- // thrower, [[Enumerable]]: false, [[Configurable]]: false}, and
- // false.
- // 21. Call the [[DefineOwnProperty]] internal method of F with
- // arguments "arguments", PropertyDescriptor {[[Get]]: thrower,
- // [[Set]]: thrower, [[Enumerable]]: false, [[Configurable]]: false},
- // and false.
-
- // TODO
- // NOTE Function objects created using Function.prototype.bind do not
- // have a prototype property or the [[Code]], [[FormalParameters]], and
- // [[Scope]] internal properties.
- // XXX can't delete prototype in pure-js.
-
- // 22. Return F.
- return bound;
- }
-});
-
-//
-// Array
-// =====
-//
-
-// ES5 15.4.3.2
-// http://es5.github.com/#x15.4.3.2
-// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray
-defineProperties(Array, { isArray: isArray });
-
-
-var boxedString = Object('a');
-var splitString = boxedString[0] !== 'a' || !(0 in boxedString);
-
-var properlyBoxesContext = function properlyBoxed(method) {
- // Check node 0.6.21 bug where third parameter is not boxed
- var properlyBoxesNonStrict = true;
- var properlyBoxesStrict = true;
- if (method) {
- method.call('foo', function (_, __, context) {
- if (typeof context !== 'object') { properlyBoxesNonStrict = false; }
- });
-
- method.call([1], function () {
- 'use strict';
- properlyBoxesStrict = typeof this === 'string';
- }, 'x');
- }
- return !!method && properlyBoxesNonStrict && properlyBoxesStrict;
-};
-
-defineProperties(ArrayPrototype, {
- forEach: function forEach(fun /*, thisp*/) {
- var object = toObject(this),
- self = splitString && isString(this) ? this.split('') : object,
- thisp = arguments[1],
- i = -1,
- length = self.length >>> 0;
-
- // If no callback function or if callback is not a callable function
- if (!isFunction(fun)) {
- throw new TypeError(); // TODO message
- }
-
- while (++i < length) {
- if (i in self) {
- // Invoke the callback function with call, passing arguments:
- // context, property value, property key, thisArg object
- // context
- fun.call(thisp, self[i], i, object);
- }
- }
- }
-}, !properlyBoxesContext(ArrayPrototype.forEach));
-
-// ES5 15.4.4.14
-// http://es5.github.com/#x15.4.4.14
-// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
-var hasFirefox2IndexOfBug = Array.prototype.indexOf && [0, 1].indexOf(1, 2) !== -1;
-defineProperties(ArrayPrototype, {
- indexOf: function indexOf(sought /*, fromIndex */ ) {
- var self = splitString && isString(this) ? this.split('') : toObject(this),
- length = self.length >>> 0;
-
- if (!length) {
- return -1;
- }
-
- var i = 0;
- if (arguments.length > 1) {
- i = toInteger(arguments[1]);
- }
-
- // handle negative indices
- i = i >= 0 ? i : Math.max(0, length + i);
- for (; i < length; i++) {
- if (i in self && self[i] === sought) {
- return i;
- }
- }
- return -1;
- }
-}, hasFirefox2IndexOfBug);
-
-//
-// String
-// ======
-//
-
-// ES5 15.5.4.14
-// http://es5.github.com/#x15.5.4.14
-
-// [bugfix, IE lt 9, firefox 4, Konqueror, Opera, obscure browsers]
-// Many browsers do not split properly with regular expressions or they
-// do not perform the split correctly under obscure conditions.
-// See http://blog.stevenlevithan.com/archives/cross-browser-split
-// I've tested in many browsers and this seems to cover the deviant ones:
-// 'ab'.split(/(?:ab)*/) should be ["", ""], not [""]
-// '.'.split(/(.?)(.?)/) should be ["", ".", "", ""], not ["", ""]
-// 'tesst'.split(/(s)*/) should be ["t", undefined, "e", "s", "t"], not
-// [undefined, "t", undefined, "e", ...]
-// ''.split(/.?/) should be [], not [""]
-// '.'.split(/()()/) should be ["."], not ["", "", "."]
-
-var string_split = StringPrototype.split;
-if (
- 'ab'.split(/(?:ab)*/).length !== 2 ||
- '.'.split(/(.?)(.?)/).length !== 4 ||
- 'tesst'.split(/(s)*/)[1] === 't' ||
- 'test'.split(/(?:)/, -1).length !== 4 ||
- ''.split(/.?/).length ||
- '.'.split(/()()/).length > 1
-) {
- (function () {
- var compliantExecNpcg = /()??/.exec('')[1] === void 0; // NPCG: nonparticipating capturing group
-
- StringPrototype.split = function (separator, limit) {
- var string = this;
- if (separator === void 0 && limit === 0) {
- return [];
- }
-
- // If `separator` is not a regex, use native split
- if (_toString.call(separator) !== '[object RegExp]') {
- return string_split.call(this, separator, limit);
- }
-
- var output = [],
- flags = (separator.ignoreCase ? 'i' : '') +
- (separator.multiline ? 'm' : '') +
- (separator.extended ? 'x' : '') + // Proposed for ES6
- (separator.sticky ? 'y' : ''), // Firefox 3+
- lastLastIndex = 0,
- // Make `global` and avoid `lastIndex` issues by working with a copy
- separator2, match, lastIndex, lastLength;
- separator = new RegExp(separator.source, flags + 'g');
- string += ''; // Type-convert
- if (!compliantExecNpcg) {
- // Doesn't need flags gy, but they don't hurt
- separator2 = new RegExp('^' + separator.source + '$(?!\\s)', flags);
- }
- /* Values for `limit`, per the spec:
- * If undefined: 4294967295 // Math.pow(2, 32) - 1
- * If 0, Infinity, or NaN: 0
- * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
- * If negative number: 4294967296 - Math.floor(Math.abs(limit))
- * If other: Type-convert, then use the above rules
- */
- limit = limit === void 0 ?
- -1 >>> 0 : // Math.pow(2, 32) - 1
- ToUint32(limit);
- while (match = separator.exec(string)) {
- // `separator.lastIndex` is not reliable cross-browser
- lastIndex = match.index + match[0].length;
- if (lastIndex > lastLastIndex) {
- output.push(string.slice(lastLastIndex, match.index));
- // Fix browsers whose `exec` methods don't consistently return `undefined` for
- // nonparticipating capturing groups
- if (!compliantExecNpcg && match.length > 1) {
- match[0].replace(separator2, function () {
- for (var i = 1; i < arguments.length - 2; i++) {
- if (arguments[i] === void 0) {
- match[i] = void 0;
- }
- }
- });
- }
- if (match.length > 1 && match.index < string.length) {
- ArrayPrototype.push.apply(output, match.slice(1));
- }
- lastLength = match[0].length;
- lastLastIndex = lastIndex;
- if (output.length >= limit) {
- break;
- }
- }
- if (separator.lastIndex === match.index) {
- separator.lastIndex++; // Avoid an infinite loop
- }
- }
- if (lastLastIndex === string.length) {
- if (lastLength || !separator.test('')) {
- output.push('');
- }
- } else {
- output.push(string.slice(lastLastIndex));
- }
- return output.length > limit ? output.slice(0, limit) : output;
- };
- }());
-
-// [bugfix, chrome]
-// If separator is undefined, then the result array contains just one String,
-// which is the this value (converted to a String). If limit is not undefined,
-// then the output array is truncated so that it contains no more than limit
-// elements.
-// "0".split(undefined, 0) -> []
-} else if ('0'.split(void 0, 0).length) {
- StringPrototype.split = function split(separator, limit) {
- if (separator === void 0 && limit === 0) { return []; }
- return string_split.call(this, separator, limit);
- };
-}
-
-// ES5 15.5.4.20
-// whitespace from: http://es5.github.io/#x15.5.4.20
-var ws = '\x09\x0A\x0B\x0C\x0D\x20\xA0\u1680\u180E\u2000\u2001\u2002\u2003' +
- '\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\u2028' +
- '\u2029\uFEFF';
-var zeroWidth = '\u200b';
-var wsRegexChars = '[' + ws + ']';
-var trimBeginRegexp = new RegExp('^' + wsRegexChars + wsRegexChars + '*');
-var trimEndRegexp = new RegExp(wsRegexChars + wsRegexChars + '*$');
-var hasTrimWhitespaceBug = StringPrototype.trim && (ws.trim() || !zeroWidth.trim());
-defineProperties(StringPrototype, {
- // http://blog.stevenlevithan.com/archives/faster-trim-javascript
- // http://perfectionkills.com/whitespace-deviations/
- trim: function trim() {
- if (this === void 0 || this === null) {
- throw new TypeError("can't convert " + this + ' to object');
- }
- return String(this).replace(trimBeginRegexp, '').replace(trimEndRegexp, '');
- }
-}, hasTrimWhitespaceBug);
-
-// ECMA-262, 3rd B.2.3
-// Not an ECMAScript standard, although ECMAScript 3rd Edition has a
-// non-normative section suggesting uniform semantics and it should be
-// normalized across all browsers
-// [bugfix, IE lt 9] IE < 9 substr() with negative value not working in IE
-var string_substr = StringPrototype.substr;
-var hasNegativeSubstrBug = ''.substr && '0b'.substr(-1) !== 'b';
-defineProperties(StringPrototype, {
- substr: function substr(start, length) {
- return string_substr.call(
- this,
- start < 0 ? ((start = this.length + start) < 0 ? 0 : start) : start,
- length
- );
- }
-}, hasNegativeSubstrBug);
-
-},{}],48:[function(require,module,exports){
-'use strict';
-
-module.exports = [
- // streaming transports
- require('./transport/websocket')
-, require('./transport/xhr-streaming')
-, require('./transport/xdr-streaming')
-, require('./transport/eventsource')
-, require('./transport/lib/iframe-wrap')(require('./transport/eventsource'))
-
- // polling transports
-, require('./transport/htmlfile')
-, require('./transport/lib/iframe-wrap')(require('./transport/htmlfile'))
-, require('./transport/xhr-polling')
-, require('./transport/xdr-polling')
-, require('./transport/lib/iframe-wrap')(require('./transport/xhr-polling'))
-, require('./transport/jsonp-polling')
-];
-
-},{"./transport/eventsource":52,"./transport/htmlfile":53,"./transport/jsonp-polling":55,"./transport/lib/iframe-wrap":58,"./transport/websocket":70,"./transport/xdr-polling":71,"./transport/xdr-streaming":72,"./transport/xhr-polling":73,"./transport/xhr-streaming":74}],49:[function(require,module,exports){
-(function (process,global){
-'use strict';
-
-var EventEmitter = require('events').EventEmitter
- , inherits = require('inherits')
- , utils = require('../../utils/event')
- , urlUtils = require('../../utils/url')
- , XHR = global.XMLHttpRequest
- ;
-
-var debug = function() {};
-if (process.env.NODE_ENV !== 'production') {
- debug = require('debug')('sockjs-client:browser:xhr');
-}
-
-function AbstractXHRObject(method, url, payload, opts) {
- debug(method, url);
- var self = this;
- EventEmitter.call(this);
-
- setTimeout(function () {
- self._start(method, url, payload, opts);
- }, 0);
-}
-
-inherits(AbstractXHRObject, EventEmitter);
-
-AbstractXHRObject.prototype._start = function(method, url, payload, opts) {
- var self = this;
-
- try {
- this.xhr = new XHR();
- } catch (x) {
- // intentionally empty
- }
-
- if (!this.xhr) {
- debug('no xhr');
- this.emit('finish', 0, 'no xhr support');
- this._cleanup();
- return;
- }
-
- // several browsers cache POSTs
- url = urlUtils.addQuery(url, 't=' + (+new Date()));
-
- // Explorer tends to keep connection open, even after the
- // tab gets closed: http://bugs.jquery.com/ticket/5280
- this.unloadRef = utils.unloadAdd(function() {
- debug('unload cleanup');
- self._cleanup(true);
- });
- try {
- this.xhr.open(method, url, true);
- if (this.timeout && 'timeout' in this.xhr) {
- this.xhr.timeout = this.timeout;
- this.xhr.ontimeout = function() {
- debug('xhr timeout');
- self.emit('finish', 0, '');
- self._cleanup(false);
- };
- }
- } catch (e) {
- debug('exception', e);
- // IE raises an exception on wrong port.
- this.emit('finish', 0, '');
- this._cleanup(false);
- return;
- }
-
- if ((!opts || !opts.noCredentials) && AbstractXHRObject.supportsCORS) {
- debug('withCredentials');
- // Mozilla docs says https://developer.mozilla.org/en/XMLHttpRequest :
- // "This never affects same-site requests."
-
- this.xhr.withCredentials = 'true';
- }
- if (opts && opts.headers) {
- for (var key in opts.headers) {
- this.xhr.setRequestHeader(key, opts.headers[key]);
- }
- }
-
- this.xhr.onreadystatechange = function() {
- if (self.xhr) {
- var x = self.xhr;
- var text, status;
- debug('readyState', x.readyState);
- switch (x.readyState) {
- case 3:
- // IE doesn't like peeking into responseText or status
- // on Microsoft.XMLHTTP and readystate=3
- try {
- status = x.status;
- text = x.responseText;
- } catch (e) {
- // intentionally empty
- }
- debug('status', status);
- // IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450
- if (status === 1223) {
- status = 204;
- }
-
- // IE does return readystate == 3 for 404 answers.
- if (status === 200 && text && text.length > 0) {
- debug('chunk');
- self.emit('chunk', status, text);
- }
- break;
- case 4:
- status = x.status;
- debug('status', status);
- // IE returns 1223 for 204: http://bugs.jquery.com/ticket/1450
- if (status === 1223) {
- status = 204;
- }
- // IE returns this for a bad port
- // http://msdn.microsoft.com/en-us/library/windows/desktop/aa383770(v=vs.85).aspx
- if (status === 12005 || status === 12029) {
- status = 0;
- }
-
- debug('finish', status, x.responseText);
- self.emit('finish', status, x.responseText);
- self._cleanup(false);
- break;
- }
- }
- };
-
- try {
- self.xhr.send(payload);
- } catch (e) {
- self.emit('finish', 0, '');
- self._cleanup(false);
- }
-};
-
-AbstractXHRObject.prototype._cleanup = function(abort) {
- debug('cleanup');
- if (!this.xhr) {
- return;
- }
- this.removeAllListeners();
- utils.unloadDel(this.unloadRef);
-
- // IE needs this field to be a function
- this.xhr.onreadystatechange = function() {};
- if (this.xhr.ontimeout) {
- this.xhr.ontimeout = null;
- }
-
- if (abort) {
- try {
- this.xhr.abort();
- } catch (x) {
- // intentionally empty
- }
- }
- this.unloadRef = this.xhr = null;
-};
-
-AbstractXHRObject.prototype.close = function() {
- debug('close');
- this._cleanup(true);
-};
-
-AbstractXHRObject.enabled = !!XHR;
-// override XMLHttpRequest for IE6/7
-// obfuscate to avoid firewalls
-var axo = ['Active'].concat('Object').join('X');
-if (!AbstractXHRObject.enabled && (axo in global)) {
- debug('overriding xmlhttprequest');
- XHR = function() {
- try {
- return new global[axo]('Microsoft.XMLHTTP');
- } catch (e) {
- return null;
- }
- };
- AbstractXHRObject.enabled = !!new XHR();
-}
-
-var cors = false;
-try {
- cors = 'withCredentials' in new XHR();
-} catch (ignored) {
- // intentionally empty
-}
-
-AbstractXHRObject.supportsCORS = cors;
-
-module.exports = AbstractXHRObject;
-
-}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-
-},{"../../utils/event":78,"../../utils/url":84,"_process":114,"debug":1,"events":35,"inherits":7}],50:[function(require,module,exports){
-(function (global){
-module.exports = global.EventSource;
-
-}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-
-},{}],51:[function(require,module,exports){
-(function (global){
-'use strict';
-
-var Driver = global.WebSocket || global.MozWebSocket;
-if (Driver) {
- module.exports = function WebSocketBrowserDriver(url) {
- return new Driver(url);
- };
-}
-
-}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-
-},{}],52:[function(require,module,exports){
-'use strict';
-
-var inherits = require('inherits')
- , AjaxBasedTransport = require('./lib/ajax-based')
- , EventSourceReceiver = require('./receiver/eventsource')
- , XHRCorsObject = require('./sender/xhr-cors')
- , EventSourceDriver = require('eventsource')
- ;
-
-function EventSourceTransport(transUrl) {
- if (!EventSourceTransport.enabled()) {
- throw new Error('Transport created when disabled');
- }
-
- AjaxBasedTransport.call(this, transUrl, '/eventsource', EventSourceReceiver, XHRCorsObject);
-}
-
-inherits(EventSourceTransport, AjaxBasedTransport);
-
-EventSourceTransport.enabled = function() {
- return !!EventSourceDriver;
-};
-
-EventSourceTransport.transportName = 'eventsource';
-EventSourceTransport.roundTrips = 2;
-
-module.exports = EventSourceTransport;
-
-},{"./lib/ajax-based":56,"./receiver/eventsource":61,"./sender/xhr-cors":67,"eventsource":50,"inherits":7}],53:[function(require,module,exports){
-'use strict';
-
-var inherits = require('inherits')
- , HtmlfileReceiver = require('./receiver/htmlfile')
- , XHRLocalObject = require('./sender/xhr-local')
- , AjaxBasedTransport = require('./lib/ajax-based')
- ;
-
-function HtmlFileTransport(transUrl) {
- if (!HtmlfileReceiver.enabled) {
- throw new Error('Transport created when disabled');
- }
- AjaxBasedTransport.call(this, transUrl, '/htmlfile', HtmlfileReceiver, XHRLocalObject);
-}
-
-inherits(HtmlFileTransport, AjaxBasedTransport);
-
-HtmlFileTransport.enabled = function(info) {
- return HtmlfileReceiver.enabled && info.sameOrigin;
-};
-
-HtmlFileTransport.transportName = 'htmlfile';
-HtmlFileTransport.roundTrips = 2;
-
-module.exports = HtmlFileTransport;
-
-},{"./lib/ajax-based":56,"./receiver/htmlfile":62,"./sender/xhr-local":69,"inherits":7}],54:[function(require,module,exports){
-(function (process){
-'use strict';
-
-// Few cool transports do work only for same-origin. In order to make
-// them work cross-domain we shall use iframe, served from the
-// remote domain. New browsers have capabilities to communicate with
-// cross domain iframe using postMessage(). In IE it was implemented
-// from IE 8+, but of course, IE got some details wrong:
-// http://msdn.microsoft.com/en-us/library/cc197015(v=VS.85).aspx
-// http://stevesouders.com/misc/test-postmessage.php
-
-var inherits = require('inherits')
- , JSON3 = require('json3')
- , EventEmitter = require('events').EventEmitter
- , version = require('../version')
- , urlUtils = require('../utils/url')
- , iframeUtils = require('../utils/iframe')
- , eventUtils = require('../utils/event')
- , random = require('../utils/random')
- ;
-
-var debug = function() {};
-if (process.env.NODE_ENV !== 'production') {
- debug = require('debug')('sockjs-client:transport:iframe');
-}
-
-function IframeTransport(transport, transUrl, baseUrl) {
- if (!IframeTransport.enabled()) {
- throw new Error('Transport created when disabled');
- }
- EventEmitter.call(this);
-
- var self = this;
- this.origin = urlUtils.getOrigin(baseUrl);
- this.baseUrl = baseUrl;
- this.transUrl = transUrl;
- this.transport = transport;
- this.windowId = random.string(8);
-
- var iframeUrl = urlUtils.addPath(baseUrl, '/iframe.html') + '#' + this.windowId;
- debug(transport, transUrl, iframeUrl);
-
- this.iframeObj = iframeUtils.createIframe(iframeUrl, function(r) {
- debug('err callback');
- self.emit('close', 1006, 'Unable to load an iframe (' + r + ')');
- self.close();
- });
-
- this.onmessageCallback = this._message.bind(this);
- eventUtils.attachEvent('message', this.onmessageCallback);
-}
-
-inherits(IframeTransport, EventEmitter);
-
-IframeTransport.prototype.close = function() {
- debug('close');
- this.removeAllListeners();
- if (this.iframeObj) {
- eventUtils.detachEvent('message', this.onmessageCallback);
- try {
- // When the iframe is not loaded, IE raises an exception
- // on 'contentWindow'.
- this.postMessage('c');
- } catch (x) {
- // intentionally empty
- }
- this.iframeObj.cleanup();
- this.iframeObj = null;
- this.onmessageCallback = this.iframeObj = null;
- }
-};
-
-IframeTransport.prototype._message = function(e) {
- debug('message', e.data);
- if (!urlUtils.isOriginEqual(e.origin, this.origin)) {
- debug('not same origin', e.origin, this.origin);
- return;
- }
-
- var iframeMessage;
- try {
- iframeMessage = JSON3.parse(e.data);
- } catch (ignored) {
- debug('bad json', e.data);
- return;
- }
-
- if (iframeMessage.windowId !== this.windowId) {
- debug('mismatched window id', iframeMessage.windowId, this.windowId);
- return;
- }
-
- switch (iframeMessage.type) {
- case 's':
- this.iframeObj.loaded();
- // window global dependency
- this.postMessage('s', JSON3.stringify([
- version
- , this.transport
- , this.transUrl
- , this.baseUrl
- ]));
- break;
- case 't':
- this.emit('message', iframeMessage.data);
- break;
- case 'c':
- var cdata;
- try {
- cdata = JSON3.parse(iframeMessage.data);
- } catch (ignored) {
- debug('bad json', iframeMessage.data);
- return;
- }
- this.emit('close', cdata[0], cdata[1]);
- this.close();
- break;
- }
-};
-
-IframeTransport.prototype.postMessage = function(type, data) {
- debug('postMessage', type, data);
- this.iframeObj.post(JSON3.stringify({
- windowId: this.windowId
- , type: type
- , data: data || ''
- }), this.origin);
-};
-
-IframeTransport.prototype.send = function(message) {
- debug('send', message);
- this.postMessage('m', message);
-};
-
-IframeTransport.enabled = function() {
- return iframeUtils.iframeEnabled;
-};
-
-IframeTransport.transportName = 'iframe';
-IframeTransport.roundTrips = 2;
-
-module.exports = IframeTransport;
-
-}).call(this,require('_process'))
-
-},{"../utils/event":78,"../utils/iframe":79,"../utils/random":82,"../utils/url":84,"../version":85,"_process":114,"debug":1,"events":35,"inherits":7,"json3":8}],55:[function(require,module,exports){
-(function (global){
-'use strict';
-
-// The simplest and most robust transport, using the well-know cross
-// domain hack - JSONP. This transport is quite inefficient - one
-// message could use up to one http request. But at least it works almost
-// everywhere.
-// Known limitations:
-// o you will get a spinning cursor
-// o for Konqueror a dumb timer is needed to detect errors
-
-var inherits = require('inherits')
- , SenderReceiver = require('./lib/sender-receiver')
- , JsonpReceiver = require('./receiver/jsonp')
- , jsonpSender = require('./sender/jsonp')
- ;
-
-function JsonPTransport(transUrl) {
- if (!JsonPTransport.enabled()) {
- throw new Error('Transport created when disabled');
- }
- SenderReceiver.call(this, transUrl, '/jsonp', jsonpSender, JsonpReceiver);
-}
-
-inherits(JsonPTransport, SenderReceiver);
-
-JsonPTransport.enabled = function() {
- return !!global.document;
-};
-
-JsonPTransport.transportName = 'jsonp-polling';
-JsonPTransport.roundTrips = 1;
-JsonPTransport.needBody = true;
-
-module.exports = JsonPTransport;
-
-}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-
-},{"./lib/sender-receiver":60,"./receiver/jsonp":63,"./sender/jsonp":65,"inherits":7}],56:[function(require,module,exports){
-(function (process){
-'use strict';
-
-var inherits = require('inherits')
- , urlUtils = require('../../utils/url')
- , SenderReceiver = require('./sender-receiver')
- ;
-
-var debug = function() {};
-if (process.env.NODE_ENV !== 'production') {
- debug = require('debug')('sockjs-client:ajax-based');
-}
-
-function createAjaxSender(AjaxObject) {
- return function(url, payload, callback) {
- debug('create ajax sender', url, payload);
- var opt = {};
- if (typeof payload === 'string') {
- opt.headers = {'Content-type': 'text/plain'};
- }
- var ajaxUrl = urlUtils.addPath(url, '/xhr_send');
- var xo = new AjaxObject('POST', ajaxUrl, payload, opt);
- xo.once('finish', function(status) {
- debug('finish', status);
- xo = null;
-
- if (status !== 200 && status !== 204) {
- return callback(new Error('http status ' + status));
- }
- callback();
- });
- return function() {
- debug('abort');
- xo.close();
- xo = null;
-
- var err = new Error('Aborted');
- err.code = 1000;
- callback(err);
- };
- };
-}
-
-function AjaxBasedTransport(transUrl, urlSuffix, Receiver, AjaxObject) {
- SenderReceiver.call(this, transUrl, urlSuffix, createAjaxSender(AjaxObject), Receiver, AjaxObject);
-}
-
-inherits(AjaxBasedTransport, SenderReceiver);
-
-module.exports = AjaxBasedTransport;
-
-}).call(this,require('_process'))
-
-},{"../../utils/url":84,"./sender-receiver":60,"_process":114,"debug":1,"inherits":7}],57:[function(require,module,exports){
-(function (process){
-'use strict';
-
-var inherits = require('inherits')
- , EventEmitter = require('events').EventEmitter
- ;
-
-var debug = function() {};
-if (process.env.NODE_ENV !== 'production') {
- debug = require('debug')('sockjs-client:buffered-sender');
-}
-
-function BufferedSender(url, sender) {
- debug(url);
- EventEmitter.call(this);
- this.sendBuffer = [];
- this.sender = sender;
- this.url = url;
-}
-
-inherits(BufferedSender, EventEmitter);
-
-BufferedSender.prototype.send = function(message) {
- debug('send', message);
- this.sendBuffer.push(message);
- if (!this.sendStop) {
- this.sendSchedule();
- }
-};
-
-// For polling transports in a situation when in the message callback,
-// new message is being send. If the sending connection was started
-// before receiving one, it is possible to saturate the network and
-// timeout due to the lack of receiving socket. To avoid that we delay
-// sending messages by some small time, in order to let receiving
-// connection be started beforehand. This is only a halfmeasure and
-// does not fix the big problem, but it does make the tests go more
-// stable on slow networks.
-BufferedSender.prototype.sendScheduleWait = function() {
- debug('sendScheduleWait');
- var self = this;
- var tref;
- this.sendStop = function() {
- debug('sendStop');
- self.sendStop = null;
- clearTimeout(tref);
- };
- tref = setTimeout(function() {
- debug('timeout');
- self.sendStop = null;
- self.sendSchedule();
- }, 25);
-};
-
-BufferedSender.prototype.sendSchedule = function() {
- debug('sendSchedule', this.sendBuffer.length);
- var self = this;
- if (this.sendBuffer.length > 0) {
- var payload = '[' + this.sendBuffer.join(',') + ']';
- this.sendStop = this.sender(this.url, payload, function(err) {
- self.sendStop = null;
- if (err) {
- debug('error', err);
- self.emit('close', err.code || 1006, 'Sending error: ' + err);
- self._cleanup();
- } else {
- self.sendScheduleWait();
- }
- });
- this.sendBuffer = [];
- }
-};
-
-BufferedSender.prototype._cleanup = function() {
- debug('_cleanup');
- this.removeAllListeners();
-};
-
-BufferedSender.prototype.stop = function() {
- debug('stop');
- this._cleanup();
- if (this.sendStop) {
- this.sendStop();
- this.sendStop = null;
- }
-};
-
-module.exports = BufferedSender;
-
-}).call(this,require('_process'))
-
-},{"_process":114,"debug":1,"events":35,"inherits":7}],58:[function(require,module,exports){
-(function (global){
-'use strict';
-
-var inherits = require('inherits')
- , IframeTransport = require('../iframe')
- , objectUtils = require('../../utils/object')
- ;
-
-module.exports = function(transport) {
-
- function IframeWrapTransport(transUrl, baseUrl) {
- IframeTransport.call(this, transport.transportName, transUrl, baseUrl);
- }
-
- inherits(IframeWrapTransport, IframeTransport);
-
- IframeWrapTransport.enabled = function(url, info) {
- if (!global.document) {
- return false;
- }
-
- var iframeInfo = objectUtils.extend({}, info);
- iframeInfo.sameOrigin = true;
- return transport.enabled(iframeInfo) && IframeTransport.enabled();
- };
-
- IframeWrapTransport.transportName = 'iframe-' + transport.transportName;
- IframeWrapTransport.needBody = true;
- IframeWrapTransport.roundTrips = IframeTransport.roundTrips + transport.roundTrips - 1; // html, javascript (2) + transport - no CORS (1)
-
- IframeWrapTransport.facadeTransport = transport;
-
- return IframeWrapTransport;
-};
-
-}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-
-},{"../../utils/object":81,"../iframe":54,"inherits":7}],59:[function(require,module,exports){
-(function (process){
-'use strict';
-
-var inherits = require('inherits')
- , EventEmitter = require('events').EventEmitter
- ;
-
-var debug = function() {};
-if (process.env.NODE_ENV !== 'production') {
- debug = require('debug')('sockjs-client:polling');
-}
-
-function Polling(Receiver, receiveUrl, AjaxObject) {
- debug(receiveUrl);
- EventEmitter.call(this);
- this.Receiver = Receiver;
- this.receiveUrl = receiveUrl;
- this.AjaxObject = AjaxObject;
- this._scheduleReceiver();
-}
-
-inherits(Polling, EventEmitter);
-
-Polling.prototype._scheduleReceiver = function() {
- debug('_scheduleReceiver');
- var self = this;
- var poll = this.poll = new this.Receiver(this.receiveUrl, this.AjaxObject);
-
- poll.on('message', function(msg) {
- debug('message', msg);
- self.emit('message', msg);
- });
-
- poll.once('close', function(code, reason) {
- debug('close', code, reason, self.pollIsClosing);
- self.poll = poll = null;
-
- if (!self.pollIsClosing) {
- if (reason === 'network') {
- self._scheduleReceiver();
- } else {
- self.emit('close', code || 1006, reason);
- self.removeAllListeners();
- }
- }
- });
-};
-
-Polling.prototype.abort = function() {
- debug('abort');
- this.removeAllListeners();
- this.pollIsClosing = true;
- if (this.poll) {
- this.poll.abort();
- }
-};
-
-module.exports = Polling;
-
-}).call(this,require('_process'))
-
-},{"_process":114,"debug":1,"events":35,"inherits":7}],60:[function(require,module,exports){
-(function (process){
-'use strict';
-
-var inherits = require('inherits')
- , urlUtils = require('../../utils/url')
- , BufferedSender = require('./buffered-sender')
- , Polling = require('./polling')
- ;
-
-var debug = function() {};
-if (process.env.NODE_ENV !== 'production') {
- debug = require('debug')('sockjs-client:sender-receiver');
-}
-
-function SenderReceiver(transUrl, urlSuffix, senderFunc, Receiver, AjaxObject) {
- var pollUrl = urlUtils.addPath(transUrl, urlSuffix);
- debug(pollUrl);
- var self = this;
- BufferedSender.call(this, transUrl, senderFunc);
-
- this.poll = new Polling(Receiver, pollUrl, AjaxObject);
- this.poll.on('message', function(msg) {
- debug('poll message', msg);
- self.emit('message', msg);
- });
- this.poll.once('close', function(code, reason) {
- debug('poll close', code, reason);
- self.poll = null;
- self.emit('close', code, reason);
- self.close();
- });
-}
-
-inherits(SenderReceiver, BufferedSender);
-
-SenderReceiver.prototype.close = function() {
- debug('close');
- this.removeAllListeners();
- if (this.poll) {
- this.poll.abort();
- this.poll = null;
- }
- this.stop();
-};
-
-module.exports = SenderReceiver;
-
-}).call(this,require('_process'))
-
-},{"../../utils/url":84,"./buffered-sender":57,"./polling":59,"_process":114,"debug":1,"inherits":7}],61:[function(require,module,exports){
-(function (process){
-'use strict';
-
-var inherits = require('inherits')
- , EventEmitter = require('events').EventEmitter
- , EventSourceDriver = require('eventsource')
- ;
-
-var debug = function() {};
-if (process.env.NODE_ENV !== 'production') {
- debug = require('debug')('sockjs-client:receiver:eventsource');
-}
-
-function EventSourceReceiver(url) {
- debug(url);
- EventEmitter.call(this);
-
- var self = this;
- var es = this.es = new EventSourceDriver(url);
- es.onmessage = function(e) {
- debug('message', e.data);
- self.emit('message', decodeURI(e.data));
- };
- es.onerror = function(e) {
- debug('error', es.readyState, e);
- // ES on reconnection has readyState = 0 or 1.
- // on network error it's CLOSED = 2
- var reason = (es.readyState !== 2 ? 'network' : 'permanent');
- self._cleanup();
- self._close(reason);
- };
-}
-
-inherits(EventSourceReceiver, EventEmitter);
-
-EventSourceReceiver.prototype.abort = function() {
- debug('abort');
- this._cleanup();
- this._close('user');
-};
-
-EventSourceReceiver.prototype._cleanup = function() {
- debug('cleanup');
- var es = this.es;
- if (es) {
- es.onmessage = es.onerror = null;
- es.close();
- this.es = null;
- }
-};
-
-EventSourceReceiver.prototype._close = function(reason) {
- debug('close', reason);
- var self = this;
- // Safari and chrome < 15 crash if we close window before
- // waiting for ES cleanup. See:
- // https://code.google.com/p/chromium/issues/detail?id=89155
- setTimeout(function() {
- self.emit('close', null, reason);
- self.removeAllListeners();
- }, 200);
-};
-
-module.exports = EventSourceReceiver;
-
-}).call(this,require('_process'))
-
-},{"_process":114,"debug":1,"events":35,"eventsource":50,"inherits":7}],62:[function(require,module,exports){
-(function (process,global){
-'use strict';
-
-var inherits = require('inherits')
- , iframeUtils = require('../../utils/iframe')
- , urlUtils = require('../../utils/url')
- , EventEmitter = require('events').EventEmitter
- , random = require('../../utils/random')
- ;
-
-var debug = function() {};
-if (process.env.NODE_ENV !== 'production') {
- debug = require('debug')('sockjs-client:receiver:htmlfile');
-}
-
-function HtmlfileReceiver(url) {
- debug(url);
- EventEmitter.call(this);
- var self = this;
- iframeUtils.polluteGlobalNamespace();
-
- this.id = 'a' + random.string(6);
- url = urlUtils.addQuery(url, 'c=' + decodeURIComponent(iframeUtils.WPrefix + '.' + this.id));
-
- debug('using htmlfile', HtmlfileReceiver.htmlfileEnabled);
- var constructFunc = HtmlfileReceiver.htmlfileEnabled ?
- iframeUtils.createHtmlfile : iframeUtils.createIframe;
-
- global[iframeUtils.WPrefix][this.id] = {
- start: function() {
- debug('start');
- self.iframeObj.loaded();
- }
- , message: function(data) {
- debug('message', data);
- self.emit('message', data);
- }
- , stop: function() {
- debug('stop');
- self._cleanup();
- self._close('network');
- }
- };
- this.iframeObj = constructFunc(url, function() {
- debug('callback');
- self._cleanup();
- self._close('permanent');
- });
-}
-
-inherits(HtmlfileReceiver, EventEmitter);
-
-HtmlfileReceiver.prototype.abort = function() {
- debug('abort');
- this._cleanup();
- this._close('user');
-};
-
-HtmlfileReceiver.prototype._cleanup = function() {
- debug('_cleanup');
- if (this.iframeObj) {
- this.iframeObj.cleanup();
- this.iframeObj = null;
- }
- delete global[iframeUtils.WPrefix][this.id];
-};
-
-HtmlfileReceiver.prototype._close = function(reason) {
- debug('_close', reason);
- this.emit('close', null, reason);
- this.removeAllListeners();
-};
-
-HtmlfileReceiver.htmlfileEnabled = false;
-
-// obfuscate to avoid firewalls
-var axo = ['Active'].concat('Object').join('X');
-if (axo in global) {
- try {
- HtmlfileReceiver.htmlfileEnabled = !!new global[axo]('htmlfile');
- } catch (x) {
- // intentionally empty
- }
-}
-
-HtmlfileReceiver.enabled = HtmlfileReceiver.htmlfileEnabled || iframeUtils.iframeEnabled;
-
-module.exports = HtmlfileReceiver;
-
-}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-
-},{"../../utils/iframe":79,"../../utils/random":82,"../../utils/url":84,"_process":114,"debug":1,"events":35,"inherits":7}],63:[function(require,module,exports){
-(function (process,global){
-'use strict';
-
-var utils = require('../../utils/iframe')
- , random = require('../../utils/random')
- , browser = require('../../utils/browser')
- , urlUtils = require('../../utils/url')
- , inherits = require('inherits')
- , EventEmitter = require('events').EventEmitter
- ;
-
-var debug = function() {};
-if (process.env.NODE_ENV !== 'production') {
- debug = require('debug')('sockjs-client:receiver:jsonp');
-}
-
-function JsonpReceiver(url) {
- debug(url);
- var self = this;
- EventEmitter.call(this);
-
- utils.polluteGlobalNamespace();
-
- this.id = 'a' + random.string(6);
- var urlWithId = urlUtils.addQuery(url, 'c=' + encodeURIComponent(utils.WPrefix + '.' + this.id));
-
- global[utils.WPrefix][this.id] = this._callback.bind(this);
- this._createScript(urlWithId);
-
- // Fallback mostly for Konqueror - stupid timer, 35 seconds shall be plenty.
- this.timeoutId = setTimeout(function() {
- debug('timeout');
- self._abort(new Error('JSONP script loaded abnormally (timeout)'));
- }, JsonpReceiver.timeout);
-}
-
-inherits(JsonpReceiver, EventEmitter);
-
-JsonpReceiver.prototype.abort = function() {
- debug('abort');
- if (global[utils.WPrefix][this.id]) {
- var err = new Error('JSONP user aborted read');
- err.code = 1000;
- this._abort(err);
- }
-};
-
-JsonpReceiver.timeout = 35000;
-JsonpReceiver.scriptErrorTimeout = 1000;
-
-JsonpReceiver.prototype._callback = function(data) {
- debug('_callback', data);
- this._cleanup();
-
- if (this.aborting) {
- return;
- }
-
- if (data) {
- debug('message', data);
- this.emit('message', data);
- }
- this.emit('close', null, 'network');
- this.removeAllListeners();
-};
-
-JsonpReceiver.prototype._abort = function(err) {
- debug('_abort', err);
- this._cleanup();
- this.aborting = true;
- this.emit('close', err.code, err.message);
- this.removeAllListeners();
-};
-
-JsonpReceiver.prototype._cleanup = function() {
- debug('_cleanup');
- clearTimeout(this.timeoutId);
- if (this.script2) {
- this.script2.parentNode.removeChild(this.script2);
- this.script2 = null;
- }
- if (this.script) {
- var script = this.script;
- // Unfortunately, you can't really abort script loading of
- // the script.
- script.parentNode.removeChild(script);
- script.onreadystatechange = script.onerror =
- script.onload = script.onclick = null;
- this.script = null;
- }
- delete global[utils.WPrefix][this.id];
-};
-
-JsonpReceiver.prototype._scriptError = function() {
- debug('_scriptError');
- var self = this;
- if (this.errorTimer) {
- return;
- }
-
- this.errorTimer = setTimeout(function() {
- if (!self.loadedOkay) {
- self._abort(new Error('JSONP script loaded abnormally (onerror)'));
- }
- }, JsonpReceiver.scriptErrorTimeout);
-};
-
-JsonpReceiver.prototype._createScript = function(url) {
- debug('_createScript', url);
- var self = this;
- var script = this.script = global.document.createElement('script');
- var script2; // Opera synchronous load trick.
-
- script.id = 'a' + random.string(8);
- script.src = url;
- script.type = 'text/javascript';
- script.charset = 'UTF-8';
- script.onerror = this._scriptError.bind(this);
- script.onload = function() {
- debug('onload');
- self._abort(new Error('JSONP script loaded abnormally (onload)'));
- };
-
- // IE9 fires 'error' event after onreadystatechange or before, in random order.
- // Use loadedOkay to determine if actually errored
- script.onreadystatechange = function() {
- debug('onreadystatechange', script.readyState);
- if (/loaded|closed/.test(script.readyState)) {
- if (script && script.htmlFor && script.onclick) {
- self.loadedOkay = true;
- try {
- // In IE, actually execute the script.
- script.onclick();
- } catch (x) {
- // intentionally empty
- }
- }
- if (script) {
- self._abort(new Error('JSONP script loaded abnormally (onreadystatechange)'));
- }
- }
- };
- // IE: event/htmlFor/onclick trick.
- // One can't rely on proper order for onreadystatechange. In order to
- // make sure, set a 'htmlFor' and 'event' properties, so that
- // script code will be installed as 'onclick' handler for the
- // script object. Later, onreadystatechange, manually execute this
- // code. FF and Chrome doesn't work with 'event' and 'htmlFor'
- // set. For reference see:
- // http://jaubourg.net/2010/07/loading-script-as-onclick-handler-of.html
- // Also, read on that about script ordering:
- // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order
- if (typeof script.async === 'undefined' && global.document.attachEvent) {
- // According to mozilla docs, in recent browsers script.async defaults
- // to 'true', so we may use it to detect a good browser:
- // https://developer.mozilla.org/en/HTML/Element/script
- if (!browser.isOpera()) {
- // Naively assume we're in IE
- try {
- script.htmlFor = script.id;
- script.event = 'onclick';
- } catch (x) {
- // intentionally empty
- }
- script.async = true;
- } else {
- // Opera, second sync script hack
- script2 = this.script2 = global.document.createElement('script');
- script2.text = "try{var a = document.getElementById('" + script.id + "'); if(a)a.onerror();}catch(x){};";
- script.async = script2.async = false;
- }
- }
- if (typeof script.async !== 'undefined') {
- script.async = true;
- }
-
- var head = global.document.getElementsByTagName('head')[0];
- head.insertBefore(script, head.firstChild);
- if (script2) {
- head.insertBefore(script2, head.firstChild);
- }
-};
-
-module.exports = JsonpReceiver;
-
-}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
-
-},{"../../utils/browser":76,"../../utils/iframe":79,"../../utils/random":82,"../../utils/url":84,"_process":114,"debug":1,"events":35,"inherits":7}],64:[function(require,module,exports){
-(function (process){
-'use strict';
-
-var inherits = require('inherits')
- , EventEmitter = require('events').EventEmitter
- ;
-
-var debug = function() {};
-if (process.env.NODE_ENV !== 'production') {
- debug = require('debug')('sockjs-client:receiver:xhr');
-}
-
-function XhrReceiver(url, AjaxObject) {
- debug(url);
- EventEmitter.call(this);
- var self = this;
-
- this.bufferPosition = 0;
-
- this.xo = new AjaxObject('POST', url, null);
- this.xo.on('chunk', this._chunkHandler.bind(this));
- this.xo.once('finish', function(status, text) {
- debug('finish', status, text);
- self._chunkHandler(status, text);
- self.xo = null;
- var reason = status === 200 ? 'network' : 'permanent';
- debug('close', reason);
- self.emit('close', null, reason);
- self._cleanup();
- });
-}
-
-inherits(XhrReceiver, EventEmitter);
-
-XhrReceiver.prototype._chunkHandler = function(status, text) {
- debug('_chunkHandler', status);
- if (status !== 200 || !text) {
- return;
- }
-
- for (var idx = -1; ; this.bufferPosition += idx + 1) {
- var buf = text.slice(this.bufferPosition);
- idx = buf.indexOf('\n');
- if (idx === -1) {
- break;
- }
- var msg = buf.slice(0, idx);
- if (msg) {
- debug('message', msg);
- this.emit('message', msg);
- }
- }
-};
-
-XhrReceiver.prototype._cleanup = function() {
- debug('_cleanup');
- this.removeAllListeners();
-};
-
-XhrReceiver.prototype.abort = function() {
- debug('abort');
- if (this.xo) {
- this.xo.close();
- debug('close');
- this.emit('close', null, 'user');
- this.xo = null;
- }
- this._cleanup();
-};
-
-module.exports = XhrReceiver;
-
-}).call(this,require('_process'))
-
-},{"_process":114,"debug":1,"events":35,"inherits":7}],65:[function(require,module,exports){
-(function (process,global){
-'use strict';
-
-var random = require('../../utils/random')
- , urlUtils = require('../../utils/url')
- ;
-
-var debug = function() {};
-if (process.env.NODE_ENV !== 'production') {
- debug = require('debug')('sockjs-client:sender:jsonp');
-}
-
-var form, area;
-
-function createIframe(id) {
- debug('createIframe', id);
- try {
- // ie6 dynamic iframes with target="" support (thanks Chris Lambacher)
- return global.document.createElement('