From 62ba579d4a4af37c55076afbf844ede6629c77a6 Mon Sep 17 00:00:00 2001 From: Yann Flory Date: Mon, 7 Mar 2016 11:06:06 +0100 Subject: [PATCH] Add Netflux API with WebSocket service V1 --- www/common/netflux.js | 1300 ++++++++++++++++++++++++++++++++++ www/common/realtime-input.js | 303 +++----- 2 files changed, 1405 insertions(+), 198 deletions(-) create mode 100644 www/common/netflux.js diff --git a/www/common/netflux.js b/www/common/netflux.js new file mode 100644 index 000000000..1d0e9c809 --- /dev/null +++ b/www/common/netflux.js @@ -0,0 +1,1300 @@ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["nf"] = factory(); + else + root["nf"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + var _Facade = __webpack_require__(1); + + var _Facade2 = _interopRequireDefault(_Facade); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + module.exports = new _Facade2.default(); + +/***/ }, +/* 1 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _WebChannel = __webpack_require__(2); + + var _WebChannel2 = _interopRequireDefault(_WebChannel); + + var _ServiceProvider = __webpack_require__(4); + + var _ServiceProvider2 = _interopRequireDefault(_ServiceProvider); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var Facade = function () { + function Facade() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, Facade); + + this.defaults = { + webrtc: {} + }; + this.settings = Object.assign({}, this.defaults, options); + } + + _createClass(Facade, [{ + key: 'create', + value: function create() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + return new _WebChannel2.default(); + } + }, { + key: 'join', + value: function join(key) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var defaults = { + connector: 'WebRTCService', + protocol: 'ExchangeProtocolService' + }; + var settings = Object.assign({}, defaults, options); + var connector = _ServiceProvider2.default.get(settings.connector); + var protocol = _ServiceProvider2.default.get(settings.protocol); + var connectorOptions = { signaling: settings.signaling, facade: this }; + return new Promise(function (resolve, reject) { + connector.join(key, connectorOptions).then(function (channel) { + var webChannel = new _WebChannel2.default(options); + channel.webChannel = webChannel; + channel.onmessage = protocol.onmessage; + webChannel.channels.add(channel); + webChannel.onopen = function () { + resolve(webChannel); + }; + if (settings.openWebChannel && settings.openWebChannel === true) { + webChannel.onopen(); + } + }); + }); + } + }, { + key: 'invite', + value: function invite() { + // TODO + } + }, { + key: '_onJoining', + value: function _onJoining() { + // TODO + } + }, { + key: '_onLeaving', + value: function _onLeaving() { + // TODO + } + }, { + key: '_onMessage', + value: function _onMessage() { + // TODO + } + }, { + key: '_onPeerMessage', + value: function _onPeerMessage() { + // TODO + } + }, { + key: '_onInvite', + value: function _onInvite() { + // TODO + } + }]); + + return Facade; + }(); + + exports.default = Facade; + +/***/ }, +/* 2 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _constants = __webpack_require__(3); + + var cs = _interopRequireWildcard(_constants); + + var _ServiceProvider = __webpack_require__(4); + + var _ServiceProvider2 = _interopRequireDefault(_ServiceProvider); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var WebChannel = function () { + function WebChannel() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, WebChannel); + + this.defaults = { + connector: cs.WEBRTC_SERVICE, + topology: cs.FULLYCONNECTED_SERVICE, + protocol: cs.EXCHANGEPROTOCOL_SERVICE + }; + this.settings = Object.assign({}, this.defaults, options); + + // Private attributes + this.protocol = cs.EXCHANGEPROTOCOL_SERVICE; + + // Public attributes + this.topology = this.settings.topology; + this.topologyService = _ServiceProvider2.default.get(this.topology); + this.id; + this.myID = this._generateID(); + this.channels = new Set(); + this.onjoining; + this.onleaving; + this.onmessage; + } + + _createClass(WebChannel, [{ + key: 'leave', + value: function leave() {} + }, { + key: 'send', + value: function send(data) { + var protocol = _ServiceProvider2.default.get(this.settings.protocol); + this.topologyService.broadcast(this, protocol.message(cs.USER_DATA, { id: this.myID, data: data })); + } + }, { + key: 'openForJoining', + value: function openForJoining() { + var _this = this; + + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + var settings = Object.assign({}, this.settings, options); + var connector = _ServiceProvider2.default.get(settings.connector); + return connector.open(function (channel) { + // 1) New dataChannel connection established. + // NEXT: add it to the network + var protocol = _ServiceProvider2.default.get(_this.protocol); + _this.topologyService = _ServiceProvider2.default.get(_this.settings.topology); + channel.webChannel = _this; + channel.onmessage = protocol.onmessage; + + // 2.1) Send to the new client the webChannel topology name + channel.send(protocol.message(cs.JOIN_START, _this.settings.topology)); + + // 2.2) Ask to topology to add the new client to this webChannel + _this.topologyService.addStart(channel, _this).then(function (id) { + _this.topologyService.broadcast(_this, protocol.message(cs.JOIN_FINISH, id)); + _this.onJoining(id); + }); + }).then(function (data) { + return data; + }); + } + }, { + key: 'closeForJoining', + value: function closeForJoining() {} + }, { + key: 'isInviting', + value: function isInviting() {} + }, { + key: '_generateID', + value: function _generateID() { + var MIN_LENGTH = 10; + var DELTA_LENGTH = 10; + var MASK = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + var result = ''; + var length = MIN_LENGTH + Math.round(Math.random() * DELTA_LENGTH); + + for (var i = 0; i < length; i++) { + result += MASK[Math.round(Math.random() * (MASK.length - 1))]; + } + return result; + } + }, { + key: 'topology', + set: function set(topologyServiceName) { + this.settings.topology = topologyServiceName; + this.topologyService = _ServiceProvider2.default.get(topologyServiceName); + }, + get: function get() { + return this.settings.topology; + } + }]); + + return WebChannel; + }(); + + exports.default = WebChannel; + +/***/ }, +/* 3 */ +/***/ function(module, exports) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + // API user's message + var USER_DATA = exports.USER_DATA = 0; + + // Internal messages + var JOIN_START = exports.JOIN_START = 2; + var JOIN_FINISH = exports.JOIN_FINISH = 4; + var YOUR_NEW_ID = exports.YOUR_NEW_ID = 5; + + // Internal message to a specific Service + var SERVICE_DATA = exports.SERVICE_DATA = 3; + + var WEBRTC_SERVICE = exports.WEBRTC_SERVICE = 'WebRTCService'; + var WEBSOCKET_SERVICE = exports.WEBSOCKET_SERVICE = 'WebSocketService'; + var FULLYCONNECTED_SERVICE = exports.FULLYCONNECTED_SERVICE = 'FullyConnectedService'; + var STAR_SERVICE = exports.STAR_SERVICE = 'StarTopologyService'; + var EXCHANGEPROTOCOL_SERVICE = exports.EXCHANGEPROTOCOL_SERVICE = 'ExchangeProtocolService'; + var WSPROTOCOL_SERVICE = exports.WSPROTOCOL_SERVICE = 'WebSocketProtocolService'; + +/***/ }, +/* 4 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _constants = __webpack_require__(3); + + var cs = _interopRequireWildcard(_constants); + + var _FullyConnectedService = __webpack_require__(5); + + var _FullyConnectedService2 = _interopRequireDefault(_FullyConnectedService); + + var _StarTopologyService = __webpack_require__(6); + + var _StarTopologyService2 = _interopRequireDefault(_StarTopologyService); + + var _WebRTCService = __webpack_require__(7); + + var _WebRTCService2 = _interopRequireDefault(_WebRTCService); + + var _WebSocketService = __webpack_require__(8); + + var _WebSocketService2 = _interopRequireDefault(_WebSocketService); + + var _ExchangeProtocolService = __webpack_require__(9); + + var _ExchangeProtocolService2 = _interopRequireDefault(_ExchangeProtocolService); + + var _WebSocketProtocolService = __webpack_require__(10); + + var _WebSocketProtocolService2 = _interopRequireDefault(_WebSocketProtocolService); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var services = new Map(); + + var ServiceProvider = function () { + function ServiceProvider() { + _classCallCheck(this, ServiceProvider); + } + + _createClass(ServiceProvider, null, [{ + key: 'get', + value: function get(code) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var service = undefined; + switch (code) { + case cs.WEBRTC_SERVICE: + service = new _WebRTCService2.default(options); + break; + case cs.WEBSOCKET_SERVICE: + service = new _WebSocketService2.default(options); + break; + case cs.FULLYCONNECTED_SERVICE: + service = new _FullyConnectedService2.default(options); + break; + case cs.STAR_SERVICE: + service = new _StarTopologyService2.default(options); + break; + case cs.EXCHANGEPROTOCOL_SERVICE: + service = new _ExchangeProtocolService2.default(options); + break; + case cs.WSPROTOCOL_SERVICE: + service = new _WebSocketProtocolService2.default(options); + break; + } + services.set(code, service); + return service; + } + }]); + + return ServiceProvider; + }(); + + exports.default = ServiceProvider; + +/***/ }, +/* 5 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _constants = __webpack_require__(3); + + var cs = _interopRequireWildcard(_constants); + + var _ServiceProvider = __webpack_require__(4); + + var _ServiceProvider2 = _interopRequireDefault(_ServiceProvider); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var FullyConnectedService = function () { + function FullyConnectedService() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, FullyConnectedService); + } + + _createClass(FullyConnectedService, [{ + key: 'addStart', + value: function addStart(channel, webChannel) { + var _this = this; + + var protocol = _ServiceProvider2.default.get(cs.EXCHANGEPROTOCOL_SERVICE); + return new Promise(function (resolve, reject) { + channel.peerID = _this._generateID(); + channel.send(protocol.message(cs.YOUR_NEW_ID, { + newID: channel.peerID, + myID: webChannel.myID + })); + if (Reflect.has(webChannel, 'aboutToJoin') && webChannel.aboutToJoin instanceof Map) { + webChannel.aboutToJoin.set(channel.peerID, channel); + } else { + webChannel.aboutToJoin = new Map(); + } + + if (webChannel.channels.size === 0) { + webChannel.channels.add(channel); + resolve(channel.peerID); + } else { + (function () { + webChannel.successfullyConnected = new Map(); + webChannel.successfullyConnected.set(channel.peerID, 0); + webChannel.connectionSucceed = function (id, withId) { + var counter = webChannel.successfullyConnected.get(withId); + webChannel.successfullyConnected.set(withId, ++counter); + if (webChannel.successfullyConnected.get(withId) === webChannel.channels.size) { + _this.addFinish(webChannel, withId); + resolve(withId); + } + }; + var connector = _ServiceProvider2.default.get(cs.WEBRTC_SERVICE); + webChannel.channels.forEach(function (c) { + connector.connect(channel.peerID, webChannel, c.peerID); + }); + })(); + } + }); + } + }, { + key: 'addFinish', + value: function addFinish(webChannel, id) { + if (id != webChannel.myID) { + webChannel.channels.add(webChannel.aboutToJoin.get(id)); + //webChannel.aboutToJoin.delete(id) + if (Reflect.has(webChannel, 'successfullyConnected')) { + webChannel.successfullyConnected.delete(id); + } + } else { + webChannel.onopen(); + } + } + }, { + key: 'broadcast', + value: function broadcast(webChannel, data) { + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = webChannel.channels[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var c = _step.value; + + c.send(data); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + } + }, { + key: 'sendTo', + value: function sendTo(id, webChannel, data) { + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = webChannel.channels[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var c = _step2.value; + + if (c.peerID == id) { + c.send(data); + } + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + } + }, { + key: 'leave', + value: function leave(webChannel) { + this.broadcast(webChannel); + } + }, { + key: '_generateID', + value: function _generateID() { + var MIN_LENGTH = 10; + var DELTA_LENGTH = 10; + var MASK = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + var length = MIN_LENGTH + Math.round(Math.random() * DELTA_LENGTH); + var maskLastIndex = MASK.length - 1; + var result = ''; + + for (var i = 0; i < length; i++) { + result += MASK[Math.round(Math.random() * maskLastIndex)]; + } + return result; + } + }]); + + return FullyConnectedService; + }(); + + exports.default = FullyConnectedService; + +/***/ }, +/* 6 */ +/***/ function(module, exports) { + + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var StarTopologyService = function () { + function StarTopologyService() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, StarTopologyService); + } + + _createClass(StarTopologyService, [{ + key: "broadcast", + value: function broadcast(webChannel, data) { + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = webChannel.channels[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var c = _step.value; + + c.send(data); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + } + }, { + key: "sendTo", + value: function sendTo(id, webChannel, data) { + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = webChannel.channels[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var c = _step2.value; + + c.send(data); + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + } + }]); + + return StarTopologyService; + }(); + + exports.default = StarTopologyService; + +/***/ }, +/* 7 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _constants = __webpack_require__(3); + + var cs = _interopRequireWildcard(_constants); + + var _ServiceProvider = __webpack_require__(4); + + var _ServiceProvider2 = _interopRequireDefault(_ServiceProvider); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var WEBRTC_DATA = 0; + var CONNECT_WITH = 1; + var CONNECT_WITH_SUCCEED = 2; + + var WebRTCService = function () { + function WebRTCService() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, WebRTCService); + + this.NAME = this.constructor.name; + this.protocol = _ServiceProvider2.default.get(cs.EXCHANGEPROTOCOL_SERVICE); + this.defaults = { + signaling: 'ws://localhost:8000', + webRTCOptions: { + iceServers: [{ + urls: ['stun:23.21.150.121', 'stun:stun.l.google.com:19302'] + }, { + urls: 'turn:numb.viagenie.ca', + credential: 'webrtcdemo', + username: 'louis%40mozilla.com' + }] + } + }; + this.settings = Object.assign({}, this.defaults, options); + + // Declare WebRTC related global(window) constructors + this.RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection || window.msRTCPeerConnection; + + this.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate || window.RTCIceCandidate || window.msRTCIceCandidate; + + this.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription || window.webkitRTCSessionDescription || window.msRTCSessionDescription; + } + + _createClass(WebRTCService, [{ + key: 'connect', + value: function connect(newPeerID, webChannel, peerID) { + webChannel.topologyService.sendTo(peerID, webChannel, this._msg(CONNECT_WITH, { key: newPeerID, intermediaryID: webChannel.myID })); + } + }, { + key: 'open', + value: function open(onchannel) { + var _this = this; + + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var defaults = { + key: this._randomKey() + }; + var settings = Object.assign({}, this.settings, defaults, options); + + return new Promise(function (resolve, reject) { + var connections = []; + var socket = new window.WebSocket(settings.signaling); + socket.onopen = function () { + socket.send(JSON.stringify({ key: settings.key })); + resolve({ url: _this.settings.signaling, key: settings.key }); + }; + socket.onmessage = function (e) { + var msg = JSON.parse(e.data); + if (Reflect.has(msg, 'id') && Reflect.has(msg, 'data')) { + if (Reflect.has(msg.data, 'offer')) { + (function () { + var connection = new _this.RTCPeerConnection(settings.webRTCOptions); + connections.push(connection); + connection.ondatachannel = function (e) { + e.channel.onopen = function () { + onchannel(e.channel); + }; + }; + connection.onicecandidate = function (e) { + if (e.candidate !== null) { + var candidate = { + candidate: e.candidate.candidate, + sdpMLineIndex: e.candidate.sdpMLineIndex + }; + socket.send(JSON.stringify({ id: msg.id, data: { candidate: candidate } })); + } + }; + var sd = Object.assign(new _this.RTCSessionDescription(), msg.data.offer); + connection.setRemoteDescription(sd, function () { + connection.createAnswer(function (answer) { + connection.setLocalDescription(answer, function () { + socket.send(JSON.stringify({ + id: msg.id, + data: { answer: connection.localDescription.toJSON() } })); + }, function () {}); + }, function () {}); + }, function () {}); + })(); + } else if (Reflect.has(msg.data, 'candidate')) { + var candidate = new _this.RTCIceCandidate(msg.data.candidate); + connections[msg.id].addIceCandidate(candidate); + } + } + }; + socket.onerror = reject; + }); + } + }, { + key: 'join', + value: function join(key) { + var _this2 = this; + + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var settings = Object.assign({}, this.settings, options); + return new Promise(function (resolve, reject) { + var connection = undefined; + var socket = new window.WebSocket(settings.signaling); + socket.onopen = function () { + connection = new _this2.RTCPeerConnection(settings.webRTCOptions); + connection.onicecandidate = function (e) { + if (e.candidate !== null) { + var candidate = { + candidate: e.candidate.candidate, + sdpMLineIndex: e.candidate.sdpMLineIndex + }; + socket.send(JSON.stringify({ data: { candidate: candidate } })); + } + }; + var dc = connection.createDataChannel(key); + dc.onopen = function () { + resolve(dc); + }; + connection.createOffer(function (offer) { + connection.setLocalDescription(offer, function () { + socket.send(JSON.stringify({ join: key, data: { offer: connection.localDescription.toJSON() } })); + }, reject); + }, reject); + }; + socket.onmessage = function (e) { + var msg = JSON.parse(e.data); + if (Reflect.has(msg, 'data')) { + if (Reflect.has(msg.data, 'answer')) { + var sd = Object.assign(new _this2.RTCSessionDescription(), msg.data.answer); + connection.setRemoteDescription(sd, function () {}, reject); + } else if (Reflect.has(msg.data, 'candidate')) { + var candidate = new _this2.RTCIceCandidate(msg.data.candidate); + connection.addIceCandidate(candidate); + } else { + reject(); + } + } else { + reject(); + } + }; + socket.onerror = reject; + }); + } + }, { + key: '_randomKey', + value: function _randomKey() { + var MIN_LENGTH = 10; + var DELTA_LENGTH = 10; + var MASK = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + var result = ''; + var length = MIN_LENGTH + Math.round(Math.random() * DELTA_LENGTH); + + for (var i = 0; i < length; i++) { + result += MASK[Math.round(Math.random() * (MASK.length - 1))]; + } + return result; + } + }, { + key: '_msg', + value: function _msg(code, data) { + var msg = { service: this.constructor.name }; + msg.data = {}; + msg.data.code = code; + Object.assign(msg.data, data); + return this.protocol.message(cs.SERVICE_DATA, msg); + } + }, { + key: 'onmessage', + value: function onmessage(channel, msg) { + var _this3 = this; + + var webChannel = channel.webChannel; + if (!Reflect.has(webChannel, 'connections')) { + webChannel.connections = new Map(); + } + switch (msg.code) { + case WEBRTC_DATA: + if (webChannel.myID === msg.recipientPeerID) { + if (Reflect.has(msg, 'sdp')) { + if (msg.sdp.type === 'offer') { + (function () { + var connection = new _this3.RTCPeerConnection(_this3.settings.webRTCOptions); + webChannel.connections.set(msg.senderPeerID, connection); + connection.ondatachannel = function (e) { + e.channel.onopen = function () { + e.channel.peerID = msg.senderPeerID; + e.channel.webChannel = webChannel; + e.channel.onmessage = _this3.protocol.onmessage; + webChannel.channels.add(e.channel); + }; + }; + connection.onicecandidate = function (e) { + if (e.candidate !== null) { + var candidate = { + candidate: e.candidate.candidate, + sdpMLineIndex: e.candidate.sdpMLineIndex + }; + channel.send(_this3._msg(WEBRTC_DATA, { + senderPeerID: webChannel.myID, + recipientPeerID: msg.senderPeerID, + candidate: candidate + })); + } + }; + var sd = Object.assign(new _this3.RTCSessionDescription(), msg.sdp); + connection.setRemoteDescription(sd, function () { + connection.createAnswer(function (answer) { + connection.setLocalDescription(answer, function () { + channel.send(_this3._msg(WEBRTC_DATA, { + senderPeerID: webChannel.myID, + recipientPeerID: msg.senderPeerID, + sdp: connection.localDescription.toJSON() + })); + }, function () {}); + }, function () {}); + }, function () {}); + })(); + } else if (msg.sdp.type === 'answer') { + var sd = Object.assign(new this.RTCSessionDescription(), msg.sdp); + webChannel.connections.get(msg.senderPeerID).setRemoteDescription(sd, function () {}, function () {}); + } + } else if (Reflect.has(msg, 'candidate')) { + webChannel.connections.get(msg.senderPeerID).addIceCandidate(new this.RTCIceCandidate(msg.candidate)); + } + } else { + var data = this._msg(WEBRTC_DATA, msg); + if (webChannel.aboutToJoin.has(msg.recipientPeerID)) { + webChannel.aboutToJoin.get(msg.recipientPeerID).send(data); + } else { + webChannel.topologyService.sendTo(msg.recipientPeerID, webChannel, data); + } + } + break; + case CONNECT_WITH: + var connection = new this.RTCPeerConnection(this.settings.webRTCOptions); + connection.onicecandidate = function (e) { + if (e.candidate !== null) { + var candidate = { + candidate: e.candidate.candidate, + sdpMLineIndex: e.candidate.sdpMLineIndex + }; + webChannel.topologyService.sendTo(msg.intermediaryID, webChannel, _this3._msg(WEBRTC_DATA, { + senderPeerID: webChannel.myID, + recipientPeerID: msg.key, + candidate: candidate + })); + } + }; + var dc = connection.createDataChannel(msg.key); + dc.onopen = function () { + if (!Reflect.has(webChannel, 'aboutToJoin')) { + webChannel.aboutToJoin = new Map(); + } + webChannel.aboutToJoin.set(dc.label, dc); + dc.onmessage = _this3.protocol.onmessage; + dc.peerID = dc.label; + dc.webChannel = webChannel; + webChannel.topologyService.sendTo(msg.intermediaryID, webChannel, _this3._msg(CONNECT_WITH_SUCCEED, { + senderPeerID: webChannel.myID, + recipientPeerID: dc.label + })); + }; + connection.createOffer(function (offer) { + connection.setLocalDescription(offer, function () { + webChannel.topologyService.sendTo(msg.intermediaryID, webChannel, _this3._msg(WEBRTC_DATA, { + senderPeerID: webChannel.myID, + recipientPeerID: msg.key, + sdp: connection.localDescription.toJSON() + })); + webChannel.connections.set(msg.key, connection); + }, function () {}); + }, function () {}); + break; + case CONNECT_WITH_SUCCEED: + webChannel.connectionSucceed(msg.senderPeerID, msg.recipientPeerID); + break; + } + } + }]); + + return WebRTCService; + }(); + + exports.default = WebRTCService; + +/***/ }, +/* 8 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _constants = __webpack_require__(3); + + var cs = _interopRequireWildcard(_constants); + + var _ServiceProvider = __webpack_require__(4); + + var _ServiceProvider2 = _interopRequireDefault(_ServiceProvider); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var WebSocketService = function () { + function WebSocketService() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, WebSocketService); + + this.NAME = this.constructor.name; + this.protocol = _ServiceProvider2.default.get(cs.EXCHANGEPROTOCOL_SERVICE); + this.defaults = { + signaling: 'ws://localhost:9000', + /** + * If an error is encountered but it is recoverable, do not immediately fail + * but if it keeps firing errors over and over, do fail. + */ + recoverableErrorCount: 0, + MAX_RECOVERABLE_ERRORS: 15, + // Maximum number of milliseconds of lag before we fail the connection. + MAX_LAG_BEFORE_DISCONNECT: 20000 + }; + this.settings = Object.assign({}, this.defaults, options); + } + + _createClass(WebSocketService, [{ + key: 'join', + value: function join(key) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var settings = Object.assign({}, this.settings, options); + return new Promise(function (resolve, reject) { + var connection = undefined; + var socket = new window.WebSocket(settings.signaling); + socket.facade = options.facade || null; + socket.onopen = function () { + resolve(socket); + }; + socket.onerror = reject; + }); + } + + // Check the status of the socket connection + /*var isSocketDisconnected = function (realtime) { + let sock = ws._socket; + return sock.readyState === sock.CLOSING + || sock.readyState === sock.CLOSED + || (realtime.getLag().waiting && realtime.getLag().lag > MAX_LAG_BEFORE_DISCONNECT); + } + var checkSocket = module.exports.checkSocket = function (realtime) { + if (isSocketDisconnected(realtime) && !socket.intentionallyClosing) { + return true; + } else { + return false; + } + };*/ + + }]); + + return WebSocketService; + }(); + + exports.default = WebSocketService; + +/***/ }, +/* 9 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _constants = __webpack_require__(3); + + var cs = _interopRequireWildcard(_constants); + + var _ServiceProvider = __webpack_require__(4); + + var _ServiceProvider2 = _interopRequireDefault(_ServiceProvider); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var ExchangeProtocolService = function () { + function ExchangeProtocolService() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, ExchangeProtocolService); + } + + _createClass(ExchangeProtocolService, [{ + key: 'onmessage', + value: function onmessage(e) { + var msg = JSON.parse(e.data); + var channel = e.currentTarget; + var webChannel = channel.webChannel; + + switch (msg.code) { + case cs.USER_DATA: + webChannel.onmessage(msg.id, msg.data); + break; + case cs.SERVICE_DATA: + var service = _ServiceProvider2.default.get(msg.service); + service.onmessage(channel, msg.data); + break; + case cs.YOUR_NEW_ID: + // TODO: change names + webChannel.myID = msg.newID; + channel.peerID = msg.myID; + break; + case cs.JOIN_START: + // 2.1) Send to the new client the webChannel topology + webChannel.topology = msg.topology; + webChannel.topologyService = _ServiceProvider2.default.get(msg.topology); + break; + case cs.JOIN_FINISH: + webChannel.topologyService.addFinish(webChannel, msg.id); + if (msg.id != webChannel.myID) { + webChannel.onJoining(msg.id); + } + break; + } + } + }, { + key: 'message', + value: function message(code, data) { + var msg = { code: code }; + switch (code) { + case cs.USER_DATA: + msg.id = data.id; + msg.data = data.data; + break; + case cs.SERVICE_DATA: + msg.service = data.service; + msg.data = Object.assign({}, data.data); + break; + case cs.YOUR_NEW_ID: + msg.newID = data.newID; + msg.myID = data.myID; + break; + case cs.JOIN_START: + msg.topology = data; + break; + case cs.JOIN_FINISH: + msg.id = data; + break; + } + return JSON.stringify(msg); + } + }]); + + return ExchangeProtocolService; + }(); + + exports.default = ExchangeProtocolService; + +/***/ }, +/* 10 */ +/***/ function(module, exports, __webpack_require__) { + + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _constants = __webpack_require__(3); + + var cs = _interopRequireWildcard(_constants); + + var _ServiceProvider = __webpack_require__(4); + + var _ServiceProvider2 = _interopRequireDefault(_ServiceProvider); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var WebSocketProtocolService = function () { + function WebSocketProtocolService() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + _classCallCheck(this, WebSocketProtocolService); + } + + _createClass(WebSocketProtocolService, [{ + key: 'onmessage', + value: function onmessage(e) { + var msg = e.data; + var socket = e.currentTarget; + var webChannel = socket.webChannel; + var topology = cs.STAR_SERVICE; + webChannel.topology = topology; + webChannel.topologyService = _ServiceProvider2.default.get(topology); + webChannel.onMessage('', msg); + + /* + let topology = cs.STAR_SERVICE + let topologyService = ServiceProvider.get(topology) + if (msg[0] !== 0) { + return; + } + if (msg[1] === 'IDENT') { + socket.uid = msg[2]; + webChannel.peers = []; + webChannel.topology = topology; + return; + } + if (msg[1] === 'PING') { + msg[1] = 'PONG'; + socket.send(JSON.stringify(msg)); + return; + } + if (msg[2] === 'MSG') { + } + // We have received a new direct message from another user + if (msg[2] === 'MSG' && msg[3] === socket.uid) { + // Find the peer exists in one of our channels or create a new one + if(typeof socket.facade._onPeerMessage === "function") + socket.facade._onPeerMessage(msg[1], msg); + } + if (msg[2] === 'JOIN' && (webChannel.id == null || webChannel.id === msg[3])) { + if(!webChannel.id) { // New unnamed channel : get its name from the first "JOIN" message + var chanName = window.location.hash = msg[3]; + webChannel.id = chanName; + } + if (msg[1] === socket.uid) { // If the user catches himself registering, he is synchronized with the server + webChannel.onopen(); + } + else { // Trigger onJoining() when another user is joining the channel + // Register the user in the list of peers in the channel + var linkQuality = (msg[1] === '_HISTORY_KEEPER_') ? 1000 : 0; + var sendToPeer = function(data) { + topologyService.sendTo(msg[1], webChannel, {type : 'MSG', msg: data}); + } + var peer = {id: msg[1], connector: socket, linkQuality: linkQuality, send: sendToPeer}; + if(webChannel.peers.indexOf(peer) === -1) { + webChannel.peers.push(peer); + } + if(typeof webChannel.onJoining === "function") + webChannel.onJoining(msg[1]); + } + } + // We have received a new message in that channel + if (msg[2] === 'MSG' && msg[3] === webChannel.id) { + // Find the peer who sent the message and display it + //TODO Use Peer instead of peer.id (msg[1]) : + if(typeof webChannel.onMessage === "function") + + } + // Someone else has left the channel, remove him from the list of peers + if (msg[2] === 'LEAVE' && msg[3] === webChannel.id) { + //TODO Use Peer instead of peer.id (msg[1]) : + if(typeof webChannel.onLeaving === "function") + webChannel.onLeaving(msg[1], webChannel); + } + */ + } + }, { + key: 'message', + value: function message(code, data) { + // The message is already prepared and encrypted + return data.data; + } + }]); + + return WebSocketProtocolService; + }(); + + exports.default = WebSocketProtocolService; + +/***/ } +/******/ ]) +}); +; \ No newline at end of file diff --git a/www/common/realtime-input.js b/www/common/realtime-input.js index 0fa9afb1d..579450c18 100644 --- a/www/common/realtime-input.js +++ b/www/common/realtime-input.js @@ -16,31 +16,22 @@ */ define([ '/common/messages.js', - // TODO remove in favour of netflux - '/bower_components/reconnectingWebsocket/reconnecting-websocket.js', + '/common/Netflux.js', '/common/crypto.js', '/common/toolbar.js', '/common/sharejs_textarea.js', '/common/chainpad.js', '/bower_components/jquery/dist/jquery.min.js', -], function (Messages,/*FIXME*/ ReconnectingWebSocket, Crypto, Toolbar, sharejs) { +], function (Messages, Netflux, Crypto, Toolbar, sharejs) { var $ = window.jQuery; var ChainPad = window.ChainPad; var PARANOIA = true; var module = { exports: {} }; - /** - * If an error is encountered but it is recoverable, do not immediately fail - * but if it keeps firing errors over and over, do fail. - */ - var MAX_RECOVERABLE_ERRORS = 15; - - /** Maximum number of milliseconds of lag before we fail the connection. */ - var MAX_LAG_BEFORE_DISCONNECT = 20000; - var debug = function (x) { console.log(x); }, warn = function (x) { console.error(x); }, - verbose = function (x) { /*console.log(x);*/ }; + verbose = function (x) { console.log(x); }; + // verbose = function () {}; // comment out to enable verbose logging // ------------------ Trapping Keyboard Events ---------------------- // @@ -82,67 +73,6 @@ define([ unbind); }; - /* websocket stuff */ - var isSocketDisconnected = function (socket, realtime) { - var sock = socket._socket; - return sock.readyState === sock.CLOSING - || sock.readyState === sock.CLOSED - || (realtime.getLag().waiting && realtime.getLag().lag > MAX_LAG_BEFORE_DISCONNECT); - }; - - // this differs from other functions with similar names in that - // you are expected to pass a socket into it. - var checkSocket = function (socket) { - if (isSocketDisconnected(socket, socket.realtime) && - !socket.intentionallyClosing) { - return true; - } else { - return false; - } - }; - - // TODO before removing websocket implementation - // bind abort to onLeaving - var abort = function (socket, realtime) { - realtime.abort(); - realtime.toolbar.failed(); - try { socket._socket.close(); } catch (e) { warn(e); } - }; - - var handleError = function (socket, realtime, err, docHTML, allMessages) { - // var internalError = createDebugInfo(err, realtime, docHTML, allMessages); - abort(socket, realtime); - }; - - var makeWebsocket = function (url) { - var socket = new ReconnectingWebSocket(url); - var out = { - onOpen: [], - onClose: [], - onError: [], - onMessage: [], - send: function (msg) { socket.send(msg); }, - close: function () { socket.close(); }, - _socket: socket - }; - var mkHandler = function (name) { - return function (evt) { - for (var i = 0; i < out[name].length; i++) { - if (out[name][i](evt) === false) { - console.log(name +"Handler"); - return; - } - } - }; - }; - socket.onopen = mkHandler('onOpen'); - socket.onclose = mkHandler('onClose'); - socket.onerror = mkHandler('onError'); - socket.onmessage = mkHandler('onMessage'); - return out; - }; - /* end websocket stuff */ - var start = module.exports.start = function (textarea, websocketUrl, userName, channel, cryptKey, config) { @@ -153,147 +83,134 @@ define([ config = config || {}; var doc = config.doc || null; - + // trying to deprecate onRemote, prefer loading it via the conf - var onRemote = config.onRemote || null; - - var transformFunction = config.transformFunction || null; + onRemote = config.onRemote || null; - var socket; + transformFunction = config.transformFunction || null; - if (config.socketAdaptor) { - // do netflux stuff - } else { - socket = makeWebsocket(websocketUrl); - } // define this in case it gets called before the rest of our stuff is ready. var onEvent = function () { }; var allMessages = []; - var isErrorState = false; var initializing = true; - var recoverableErrorCount = 0; - - var $textarea = $(textarea); var bump = function () {}; - socket.onOpen.push(function (evt) { - if (!initializing) { - console.log("Starting"); - // realtime is passed around as an attribute of the socket - // FIXME?? - socket.realtime.start(); - return; - } - - var realtime = socket.realtime = ChainPad.create(userName, - passwd, - channel, - $(textarea).val(), - { - transformFunction: config.transformFunction - }); + var options = { + signaling: websocketUrl, + topology: 'StarTopologyService', + protocol: 'WebSocketProtocolService', + connector: 'WebSocketService', + openWebChannel: true + }; + var realtime; + + // Connect to the WebSocket server + Netflux.join(channel, options).then(function(wc) { + wc.onMessage = onMessage; // On receiving message + wc.onJoining = onJoining; // On user joining the session + + // Open a Chainpad session + realtime = createRealtime(); + realtime.onUserListChange(function (userList) { + var opt = {userList : userList}; + // TODO : onJoining should only a a "newPeer" parameter + wc.onJoining(opt); + }); + // On sending message + realtime.onMessage(function(message) { + message = Crypto.encrypt(message, cryptKey); + wc.send(message); + }); - if (config.onInit) { - // extend as you wish - config.onInit({ - realtime: realtime - }); - } + // Check the connection to the channel + //checkConnection(wc); - onEvent = function () { - // This looks broken - if (isErrorState || initializing) { return; } - }; + bindAllEvents(textarea, doc, onEvent, false); - realtime.onUserListChange(function (userList) { - if (!initializing || userList.indexOf(userName) === -1) { - return; - } - // if we spot ourselves being added to the document, we'll switch - // 'initializing' off because it means we're fully synced. - initializing = false; + sharejs.attach(textarea, realtime); + bump = realtime.bumpSharejs; - // execute an onReady callback if one was supplied - // pass an object so we can extend this later - if (config.onReady) { - // extend as you wish - config.onReady({ - userList: userList - }); - } - }); + realtime.start(); + }, function(error) { + warn(error); + }); - var whoami = new RegExp(userName.replace(/[\/\+]/g, function (c) { - return '\\' +c; - })); + var createRealtime = function() { + return ChainPad.create(userName, + passwd, + channel, + $(textarea).val(), + { + transformFunction: config.transformFunction + }); + } + + var whoami = new RegExp(userName.replace(/[\/\+]/g, function (c) { + return '\\' +c; + })); - // when you receive a message... - socket.onMessage.push(function (evt) { - verbose(evt.data); - if (isErrorState) { return; } + var onMessage = function(user, message) { - var message = Crypto.decrypt(evt.data, cryptKey); - verbose(message); - allMessages.push(message); - if (!initializing) { - if (PARANOIA) { - onEvent(); - } + message = Crypto.decrypt(message, cryptKey); + + verbose(message); + allMessages.push(message); + if (!initializing) { + if (PARANOIA) { + onEvent(); } - realtime.message(message); - if (/\[5,/.test(message)) { verbose("pong"); } + } + realtime.message(message); + if (/\[5,/.test(message)) { verbose("pong"); } - if (!initializing) { - if (/\[2,/.test(message)) { - //verbose("Got a patch"); - if (whoami.test(message)) { - //verbose("Received own message"); - } else { - //verbose("Received remote message"); - // obviously this is only going to get called if - if (onRemote) { onRemote(realtime.getUserDoc()); } - } + if (!initializing) { + if (/\[2,/.test(message)) { + //verbose("Got a patch"); + if (whoami.test(message)) { + //verbose("Received own message"); + } else { + //verbose("Received remote message"); + // obviously this is only going to get called if + if (onRemote) { onRemote(realtime.getUserDoc()); } } } - }); - - // when a message is ready to send - realtime.onMessage(function (message) { - if (isErrorState) { return; } - message = Crypto.encrypt(message, cryptKey); - try { - socket.send(message); - } catch (e) { - warn(e); - } - }); - - // actual socket bindings - socket.onmessage = function (evt) { - for (var i = 0; i < socket.onMessage.length; i++) { - if (socket.onMessage[i](evt) === false) { return; } - } - }; - socket.onclose = function (evt) { - for (var i = 0; i < socket.onMessage.length; i++) { - if (socket.onClose[i](evt) === false) { return; } - } - }; - - socket.onerror = warn; + } + } + var onJoining = function(optionnalData) { + var userList = optionnalData.userList || []; + if (!initializing || userList.indexOf(userName) === -1) { + return; + } + // if we spot ourselves being added to the document, we'll switch + // 'initializing' off because it means we're fully synced. + initializing = false; + + // execute an onReady callback if one was supplied + // pass an object so we can extend this later + if (config.onReady) { + config.onReady({ + userList: userList + }); + } + } - // TODO confirm that we can rely on netflux API - var socketChecker = setInterval(function () { - if (checkSocket(socket)) { + var checkConnection = function(wc) { + //TODO + /*var socketChecker = setInterval(function () { + if (netflux.checkSocket(realtime)) { warn("Socket disconnected!"); recoverableErrorCount += 1; if (recoverableErrorCount >= MAX_RECOVERABLE_ERRORS) { warn("Giving up!"); - abort(socket, realtime); + realtime.abort(); + wc.leave() + .then(null, function(err) { + warn(err); + }); if (config.onAbort) { config.onAbort({ socket: socket @@ -304,19 +221,9 @@ define([ } else { // it's working as expected, continue } - }, 200); - - bindAllEvents(textarea, doc, onEvent, false); - - // attach textarea - // NOTE: should be able to remove the websocket without damaging this - sharejs.attach(textarea, realtime); - - realtime.start(); - debug('started'); + }, 200);*/ + } - bump = realtime.bumpSharejs; - }); return { onEvent: function () { onEvent();