protoo-client
Advanced tools
Comparing version
@@ -70,3 +70,3 @@ module.exports = | ||
tabWidth: 2, | ||
comments: 110, | ||
comments: 100, | ||
ignoreUrls: true, | ||
@@ -73,0 +73,0 @@ ignoreStrings: true, |
@@ -0,15 +1,24 @@ | ||
const { version } = require('../package.json'); | ||
const Peer = require('./Peer'); | ||
const transports = require('./transports'); | ||
const WebSocketTransport = require('./transports/WebSocketTransport'); | ||
module.exports = | ||
{ | ||
/** | ||
* Expose Peer. | ||
*/ | ||
Peer : Peer, | ||
/** | ||
* Expose mediasoup-client version. | ||
* | ||
* @type {String} | ||
*/ | ||
exports.version = version; | ||
/** | ||
* Expose the built-in WebSocketTransport. | ||
*/ | ||
WebSocketTransport : transports.WebSocketTransport | ||
}; | ||
/** | ||
* Expose Peer class. | ||
* | ||
* @type {Class} | ||
*/ | ||
exports.Peer = Peer; | ||
/** | ||
* Expose WebSocketTransport class. | ||
* | ||
* @type {Class} | ||
*/ | ||
exports.WebSocketTransport = WebSocketTransport; |
@@ -1,4 +0,6 @@ | ||
const logger = require('./logger')('Message'); | ||
const utils = require('./utils'); | ||
const Logger = require('./Logger'); | ||
const { generateRandomNumber } = require('./utils'); | ||
const logger = new Logger('Message'); | ||
class Message | ||
@@ -49,3 +51,2 @@ { | ||
message.id = object.id; | ||
message.method = object.method; | ||
@@ -77,2 +78,3 @@ message.data = object.data || {}; | ||
{ | ||
message.ok = false; | ||
message.errorCode = object.errorCode; | ||
@@ -108,3 +110,3 @@ message.errorReason = object.errorReason; | ||
static requestFactory(method, data) | ||
static createRequest(method, data) | ||
{ | ||
@@ -114,3 +116,3 @@ const request = | ||
request : true, | ||
id : utils.randomNumber(), | ||
id : generateRandomNumber(), | ||
method : method, | ||
@@ -123,3 +125,3 @@ data : data || {} | ||
static successResponseFactory(request, data) | ||
static createSuccessResponse(request, data) | ||
{ | ||
@@ -137,3 +139,3 @@ const response = | ||
static errorResponseFactory(request, errorCode, errorReason) | ||
static createErrorResponse(request, errorCode, errorReason) | ||
{ | ||
@@ -144,2 +146,3 @@ const response = | ||
id : request.id, | ||
ok : false, | ||
errorCode : errorCode, | ||
@@ -152,3 +155,3 @@ errorReason : errorReason | ||
static notificationFactory(method, data) | ||
static createNotification(method, data) | ||
{ | ||
@@ -155,0 +158,0 @@ const notification = |
346
lib/Peer.js
@@ -1,28 +0,44 @@ | ||
const EventEmitter = require('events').EventEmitter; | ||
const logger = require('./logger')('Peer'); | ||
const Logger = require('./Logger'); | ||
const EnhancedEventEmitter = require('./EnhancedEventEmitter'); | ||
const Message = require('./Message'); | ||
// Max time waiting for a response. | ||
const REQUEST_TIMEOUT = 20000; | ||
const logger = new Logger('Peer'); | ||
class Peer extends EventEmitter | ||
class Peer extends EnhancedEventEmitter | ||
{ | ||
/** | ||
* @param {protoo.Transport} transport | ||
* | ||
* @emits open | ||
* @emits {currentAttempt: Number} failed | ||
* @emits disconnected | ||
* @emits close | ||
* @emits {request: protoo.Request, accept: Function, reject: Function} request | ||
* @emits {notification: protoo.Notification} notification | ||
*/ | ||
constructor(transport) | ||
{ | ||
super(logger); | ||
logger.debug('constructor()'); | ||
super(); | ||
this.setMaxListeners(Infinity); | ||
// Closed flag. | ||
// @type {Boolean} | ||
this._closed = false; | ||
// Transport. | ||
// @type {protoo.Transport} | ||
this._transport = transport; | ||
// Closed flag. | ||
this._closed = false; | ||
// Connected flag. | ||
// @type {Boolean} | ||
this._connected = false; | ||
// Custom data object. | ||
// @type {Object} | ||
this._data = {}; | ||
// Map of sent requests' handlers indexed by request.id. | ||
this._requestHandlers = new Map(); | ||
// Map of pending sent request objects indexed by request id. | ||
// @type {Map<Number, Object>} | ||
this._sents = new Map(); | ||
@@ -33,93 +49,144 @@ // Handle transport. | ||
get data() | ||
/** | ||
* Whether the Peer is closed. | ||
* | ||
* @returns {Boolean} | ||
*/ | ||
get closed() | ||
{ | ||
return this._data; | ||
return this._closed; | ||
} | ||
set data(obj) | ||
/** | ||
* Whether the Peer is connected. | ||
* | ||
* @returns {Boolean} | ||
*/ | ||
get connected() | ||
{ | ||
this._data = obj || {}; | ||
return this._connected; | ||
} | ||
get closed() | ||
/** | ||
* App custom data. | ||
* | ||
* @returns {Object} | ||
*/ | ||
get data() | ||
{ | ||
return this._closed; | ||
return this._data; | ||
} | ||
send(method, data) | ||
/** | ||
* Invalid setter. | ||
*/ | ||
set data(data) // eslint-disable-line no-unused-vars | ||
{ | ||
const request = Message.requestFactory(method, data); | ||
throw new Error('cannot override data object'); | ||
} | ||
return this._transport.send(request) | ||
.then(() => | ||
{ | ||
return new Promise((pResolve, pReject) => | ||
{ | ||
const handler = | ||
{ | ||
resolve : (data2) => | ||
{ | ||
if (!this._requestHandlers.delete(request.id)) | ||
return; | ||
/** | ||
* Close this Peer and its Transport. | ||
*/ | ||
close() | ||
{ | ||
if (this._closed) | ||
return; | ||
clearTimeout(handler.timer); | ||
pResolve(data2); | ||
}, | ||
logger.debug('close()'); | ||
reject : (error) => | ||
{ | ||
if (!this._requestHandlers.delete(request.id)) | ||
return; | ||
this._closed = true; | ||
this._connected = false; | ||
clearTimeout(handler.timer); | ||
pReject(error); | ||
}, | ||
// Close Transport. | ||
this._transport.close(); | ||
timer : setTimeout(() => | ||
{ | ||
if (!this._requestHandlers.delete(request.id)) | ||
return; | ||
// Close every pending sent. | ||
for (const sent of this._sents.values()) | ||
{ | ||
sent.close(); | ||
} | ||
pReject(new Error('request timeout')); | ||
}, REQUEST_TIMEOUT), | ||
close : () => | ||
{ | ||
clearTimeout(handler.timer); | ||
pReject(new Error('peer closed')); | ||
} | ||
}; | ||
// Add handler stuff to the Map. | ||
this._requestHandlers.set(request.id, handler); | ||
}); | ||
}); | ||
// Emit 'close' event. | ||
this.safeEmit('close'); | ||
} | ||
notify(method, data) | ||
/** | ||
* Send a protoo request to the server-side Room. | ||
* | ||
* @param {String} method | ||
* @param {Object} [data] | ||
* | ||
* @async | ||
* @returns {Object} The response data Object if a success response is received. | ||
*/ | ||
async request(method, data = undefined) | ||
{ | ||
const notification = Message.notificationFactory(method, data); | ||
const request = Message.createRequest(method, data); | ||
return this._transport.send(notification); | ||
} | ||
this._logger.debug('request() [method:%s, id:%s]', method, request.id); | ||
close() | ||
{ | ||
logger.debug('close()'); | ||
// This may throw. | ||
this._transport.send(request); | ||
if (this._closed) | ||
return; | ||
return new Promise((pResolve, pReject) => | ||
{ | ||
const timeout = 1500 * (15 + (0.1 * this._sents.size)); | ||
const sent = | ||
{ | ||
id : request.id, | ||
method : request.method, | ||
resolve : (data2) => | ||
{ | ||
if (!this._sents.delete(request.id)) | ||
return; | ||
this._closed = true; | ||
clearTimeout(sent.timer); | ||
pResolve(data2); | ||
}, | ||
reject : (error) => | ||
{ | ||
if (!this._sents.delete(request.id)) | ||
return; | ||
// Close transport. | ||
this._transport.close(); | ||
clearTimeout(sent.timer); | ||
pReject(error); | ||
}, | ||
timer : setTimeout(() => | ||
{ | ||
if (!this._sents.delete(request.id)) | ||
return; | ||
// Close every pending request handler. | ||
this._requestHandlers.forEach((handler) => handler.close()); | ||
pReject(new Error('request timeout')); | ||
}, timeout), | ||
close : () => | ||
{ | ||
clearTimeout(sent.timer); | ||
pReject(new Error('peer closed')); | ||
} | ||
}; | ||
// Emit 'close' event. | ||
this.emit('close'); | ||
// Add sent stuff to the map. | ||
this._sents.set(request.id, sent); | ||
}); | ||
} | ||
/** | ||
* Send a protoo notification to the server-side Room. | ||
* | ||
* @param {String} method | ||
* @param {Object} [data] | ||
* | ||
* @async | ||
*/ | ||
async notify(method, data = undefined) | ||
{ | ||
const notification = Message.createNotification(method, data); | ||
this._logger.debug('notify() [method:%s]', method); | ||
// This may throw. | ||
this._transport.send(notification); | ||
} | ||
_handleTransport() | ||
@@ -130,14 +197,16 @@ { | ||
this._closed = true; | ||
setTimeout(() => this.emit('close')); | ||
setTimeout(() => | ||
{ | ||
if (this._closed) | ||
return; | ||
this._connected = false; | ||
this.safeEmit('close'); | ||
}); | ||
return; | ||
} | ||
this._transport.on('connecting', (currentAttempt) => | ||
{ | ||
logger.debug('emit "connecting" [currentAttempt:%s]', currentAttempt); | ||
this.emit('connecting', currentAttempt); | ||
}); | ||
this._transport.on('open', () => | ||
@@ -150,4 +219,5 @@ { | ||
// Emit 'open' event. | ||
this.emit('open'); | ||
this._connected = true; | ||
this.safeEmit('open'); | ||
}); | ||
@@ -157,5 +227,10 @@ | ||
{ | ||
if (this._closed) | ||
return; | ||
logger.debug('emit "disconnected"'); | ||
this.emit('disconnected'); | ||
this._connected = false; | ||
this.safeEmit('disconnected'); | ||
}); | ||
@@ -165,5 +240,10 @@ | ||
{ | ||
if (this._closed) | ||
return; | ||
logger.debug('emit "failed" [currentAttempt:%s]', currentAttempt); | ||
this.emit('failed', currentAttempt); | ||
this._connected = false; | ||
this.safeEmit('failed', currentAttempt); | ||
}); | ||
@@ -180,4 +260,5 @@ | ||
// Emit 'close' event. | ||
this.emit('close'); | ||
this._connected = false; | ||
this.safeEmit('close'); | ||
}); | ||
@@ -188,13 +269,7 @@ | ||
if (message.request) | ||
{ | ||
this._handleRequest(message); | ||
} | ||
else if (message.response) | ||
{ | ||
this._handleResponse(message); | ||
} | ||
else if (message.notification) | ||
{ | ||
this._handleNotification(message); | ||
} | ||
}); | ||
@@ -205,40 +280,42 @@ } | ||
{ | ||
this.emit('request', | ||
// Request. | ||
request, | ||
// accept() function. | ||
(data) => | ||
{ | ||
const response = Message.successResponseFactory(request, data); | ||
try | ||
{ | ||
this.emit('request', | ||
// Request. | ||
request, | ||
// accept() function. | ||
(data) => | ||
{ | ||
const response = Message.createSuccessResponse(request, data); | ||
this._transport.send(response) | ||
.catch((error) => | ||
this._transport.send(response) | ||
.catch(() => {}); | ||
}, | ||
// reject() function. | ||
(errorCode, errorReason) => | ||
{ | ||
if (errorCode instanceof Error) | ||
{ | ||
logger.warn( | ||
'accept() failed, response could not be sent: %o', error); | ||
}); | ||
}, | ||
// reject() function. | ||
(errorCode, errorReason) => | ||
{ | ||
if (errorCode instanceof Error) | ||
{ | ||
errorReason = errorCode.toString(); | ||
errorCode = 500; | ||
} | ||
else if (typeof errorCode === 'number' && errorReason instanceof Error) | ||
{ | ||
errorReason = errorReason.toString(); | ||
} | ||
errorCode = 500; | ||
errorReason = String(errorCode); | ||
} | ||
else if (typeof errorCode === 'number' && errorReason instanceof Error) | ||
{ | ||
errorReason = String(errorReason); | ||
} | ||
const response = | ||
Message.errorResponseFactory(request, errorCode, errorReason); | ||
const response = | ||
Message.createErrorResponse(request, errorCode, errorReason); | ||
this._transport.send(response) | ||
.catch((error) => | ||
{ | ||
logger.warn( | ||
'reject() failed, response could not be sent: %o', error); | ||
}); | ||
}); | ||
this._transport.send(response) | ||
.catch(() => {}); | ||
}); | ||
} | ||
catch (error) | ||
{ | ||
const response = Message.createErrorResponse(request, 500, String(error)); | ||
this._transport.send(response) | ||
.catch(() => {}); | ||
} | ||
} | ||
@@ -248,7 +325,8 @@ | ||
{ | ||
const handler = this._requestHandlers.get(response.id); | ||
const sent = this._sents.get(response.id); | ||
if (!handler) | ||
if (!sent) | ||
{ | ||
logger.error('received response does not match any sent request'); | ||
logger.error( | ||
'received response does not match any sent request [id:%s]', response.id); | ||
@@ -260,3 +338,3 @@ return; | ||
{ | ||
handler.resolve(response.data); | ||
sent.resolve(response.data); | ||
} | ||
@@ -268,3 +346,3 @@ else | ||
error.code = response.errorCode; | ||
handler.reject(error); | ||
sent.reject(error); | ||
} | ||
@@ -275,3 +353,3 @@ } | ||
{ | ||
this.emit('notification', notification); | ||
this.safeEmit('notification', notification); | ||
} | ||
@@ -278,0 +356,0 @@ } |
@@ -1,5 +0,5 @@ | ||
const EventEmitter = require('events').EventEmitter; | ||
const W3CWebSocket = require('websocket').w3cwebsocket; | ||
const retry = require('retry'); | ||
const logger = require('../logger')('WebSocketTransport'); | ||
const Logger = require('../Logger'); | ||
const EnhancedEventEmitter = require('../EnhancedEventEmitter'); | ||
const Message = require('../Message'); | ||
@@ -16,23 +16,34 @@ | ||
class WebSocketTransport extends EventEmitter | ||
const logger = new Logger('WebSocketTransport'); | ||
class WebSocketTransport extends EnhancedEventEmitter | ||
{ | ||
/** | ||
* @param {String} url - WebSocket URL. | ||
* @param {Object} [options] - Options for WebSocket-Node.W3CWebSocket and retry. | ||
*/ | ||
constructor(url, options) | ||
{ | ||
logger.debug('constructor() [url:"%s", options:%o]', url, options); | ||
super(logger); | ||
super(); | ||
this.setMaxListeners(Infinity); | ||
logger.debug('constructor() [url:%s, options:%o]', url, options); | ||
// Save URL and options. | ||
// Closed flag. | ||
// @type {Boolean} | ||
this._closed = false; | ||
// WebSocket URL. | ||
// @type {String} | ||
this._url = url; | ||
// Options. | ||
// @type {Object} | ||
this._options = options || {}; | ||
// WebSocket instance. | ||
// @type {WebSocket} | ||
this._ws = null; | ||
// Closed flag. | ||
this._closed = false; | ||
// Set WebSocket | ||
this._setWebSocket(); | ||
// Run the WebSocket. | ||
this._runWebSocket(); | ||
} | ||
@@ -45,31 +56,12 @@ | ||
send(message) | ||
close() | ||
{ | ||
if (this._closed) | ||
return Promise.reject(new Error('transport closed')); | ||
return; | ||
try | ||
{ | ||
this._ws.send(JSON.stringify(message)); | ||
return Promise.resolve(); | ||
} | ||
catch (error) | ||
{ | ||
logger.error('send() | error sending message: %o', error); | ||
return Promise.reject(error); | ||
} | ||
} | ||
close() | ||
{ | ||
logger.debug('close()'); | ||
if (this._closed) | ||
return; | ||
// Don't wait for the WebSocket 'close' event, do it now. | ||
this._closed = true; | ||
this.emit('close'); | ||
this.safeEmit('close'); | ||
@@ -90,6 +82,24 @@ try | ||
_setWebSocket() | ||
async send(message) | ||
{ | ||
const options = this._options; | ||
const operation = retry.operation(this._options.retry || DEFAULT_RETRY_OPTIONS); | ||
if (this._closed) | ||
throw new Error('transport closed'); | ||
try | ||
{ | ||
this._ws.send(JSON.stringify(message)); | ||
} | ||
catch (error) | ||
{ | ||
logger.warn('send() failed:%o', error); | ||
throw error; | ||
} | ||
} | ||
_runWebSocket() | ||
{ | ||
const operation = | ||
retry.operation(this._options.retry || DEFAULT_RETRY_OPTIONS); | ||
let wasConnected = false; | ||
@@ -106,3 +116,3 @@ | ||
logger.debug('_setWebSocket() [currentAttempt:%s]', currentAttempt); | ||
logger.debug('_runWebSocket() [currentAttempt:%s]', currentAttempt); | ||
@@ -112,10 +122,7 @@ this._ws = new W3CWebSocket( | ||
WS_SUBPROTOCOL, | ||
options.origin, | ||
options.headers, | ||
options.requestOptions, | ||
options.clientConfig | ||
); | ||
this._options.origin, | ||
this._options.headers, | ||
this._options.requestOptions, | ||
this._options.clientConfig); | ||
this.emit('connecting', currentAttempt); | ||
this._ws.onopen = () => | ||
@@ -129,3 +136,3 @@ { | ||
// Emit 'open' event. | ||
this.emit('open'); | ||
this.safeEmit('open'); | ||
}; | ||
@@ -138,3 +145,4 @@ | ||
logger.warn('WebSocket "close" event [wasClean:%s, code:%s, reason:"%s"]', | ||
logger.warn( | ||
'WebSocket "close" event [wasClean:%s, code:%s, reason:"%s"]', | ||
event.wasClean, event.code, event.reason); | ||
@@ -148,3 +156,3 @@ | ||
{ | ||
this.emit('failed', currentAttempt); | ||
this.safeEmit('failed', currentAttempt); | ||
@@ -162,3 +170,3 @@ if (this._closed) | ||
this.emit('disconnected'); | ||
this.safeEmit('disconnected'); | ||
@@ -168,3 +176,3 @@ if (this._closed) | ||
this._setWebSocket(); | ||
this._runWebSocket(); | ||
@@ -178,3 +186,3 @@ return; | ||
// Emit 'close' event. | ||
this.emit('close'); | ||
this.safeEmit('close'); | ||
}; | ||
@@ -202,3 +210,4 @@ | ||
{ | ||
logger.error('no listeners for WebSocket "message" event, ignoring received message'); | ||
logger.error( | ||
'no listeners for WebSocket "message" event, ignoring received message'); | ||
@@ -209,3 +218,3 @@ return; | ||
// Emit 'message' event. | ||
this.emit('message', message); | ||
this.safeEmit('message', message); | ||
}; | ||
@@ -212,0 +221,0 @@ }); |
@@ -1,13 +0,9 @@ | ||
const randomNumber = require('random-number'); | ||
const randomNumberGenerator = randomNumber.generator( | ||
{ | ||
min : 1000000, | ||
max : 9999999, | ||
integer : true | ||
}); | ||
module.exports = | ||
/** | ||
* Generates a random positive integer. | ||
* | ||
* @returns {Number} | ||
*/ | ||
exports.generateRandomNumber = function() | ||
{ | ||
randomNumber : randomNumberGenerator | ||
return Math.round(Math.random() * 10000000); | ||
}; |
{ | ||
"name": "protoo-client", | ||
"version": "3.0.3", | ||
"description": "protoo JavaScript client library", | ||
"version": "4.0.0", | ||
"description": "protoo JavaScript client module", | ||
"author": "Iñaki Baz Castillo <ibc@aliax.net>", | ||
@@ -10,7 +10,12 @@ "homepage": "https://protoojs.org", | ||
"type": "git", | ||
"url": "git+https://github.com/ibc/protoo.git" | ||
"url": "https://github.com/ibc/protoo.git" | ||
}, | ||
"main": "lib/index.js", | ||
"keywords": [ | ||
"nodejs", | ||
"browser", | ||
"websocket" | ||
], | ||
"engines": { | ||
"node": ">=6.4" | ||
"node": ">=8.0.0" | ||
}, | ||
@@ -21,9 +26,8 @@ "scripts": { | ||
"dependencies": { | ||
"debug": "^4.1.0", | ||
"debug": "^4.1.1", | ||
"events": "^3.0.0", | ||
"random-number": "0.0.9", | ||
"retry": "^0.12.0" | ||
}, | ||
"devDependencies": { | ||
"eslint": "^5.10.0" | ||
"eslint": "^5.13.0" | ||
}, | ||
@@ -30,0 +34,0 @@ "optionalDependencies": { |
19964
12.71%4
-20%851
14.69%- Removed
- Removed
Updated