Unverified Commit fbfc20e0 authored by Sean Hammond's avatar Sean Hammond Committed by GitHub

Merge pull request #705 from hypothesis/replace-inherits-with-classes

Replace "inherits" package with ES 2015 class inheritance
parents a34342cc 30f9dff4
......@@ -57,7 +57,6 @@
"gulp-sourcemaps": "^1.6.0",
"gulp-util": "^3.0.7",
"hammerjs": "^2.0.4",
"inherits": "^2.0.1",
"isparta": "^4.0.0",
"istanbul": "^0.4.5",
"jquery": "^3.2.1",
......
'use strict';
var angular = require('angular');
var inherits = require('inherits');
var proxyquire = require('proxyquire');
var EventEmitter = require('tiny-emitter');
......@@ -9,32 +8,38 @@ var events = require('../../events');
var noCallThru = require('../../../shared/test/util').noCallThru;
var searchClients;
function FakeSearchClient(searchFn, opts) {
assert.ok(searchFn);
searchClients.push(this);
this.cancel = sinon.stub();
this.incremental = !!opts.incremental;
this.get = sinon.spy(function (query) {
assert.ok(query.uri);
for (var i = 0; i < query.uri.length; i++) {
var uri = query.uri[i];
this.emit('results', [{id: uri + '123', group: '__world__'}]);
this.emit('results', [{id: uri + '456', group: 'private-group'}]);
}
this.emit('end');
});
class FakeSearchClient extends EventEmitter {
constructor(searchFn, opts) {
super();
assert.ok(searchFn);
searchClients.push(this);
this.cancel = sinon.stub();
this.incremental = !!opts.incremental;
this.get = sinon.spy(function (query) {
assert.ok(query.uri);
for (var i = 0; i < query.uri.length; i++) {
var uri = query.uri[i];
this.emit('results', [{id: uri + '123', group: '__world__'}]);
this.emit('results', [{id: uri + '456', group: 'private-group'}]);
}
this.emit('end');
});
}
}
inherits(FakeSearchClient, EventEmitter);
function FakeRootThread() {
this.thread = sinon.stub().returns({
totalChildren: 0,
});
class FakeRootThread extends EventEmitter {
constructor() {
super();
this.thread = sinon.stub().returns({
totalChildren: 0,
});
}
}
inherits(FakeRootThread, EventEmitter);
describe('sidebar.components.sidebar-content', function () {
var $rootScope;
......
'use strict';
var angular = require('angular');
var inherits = require('inherits');
var EventEmitter = require('tiny-emitter');
function FakeRootThread() {
this.thread = sinon.stub();
class FakeRootThread extends EventEmitter {
constructor() {
super();
this.thread = sinon.stub();
}
}
inherits(FakeRootThread, EventEmitter);
describe('StreamContentController', function () {
var $componentController;
......
......@@ -3,7 +3,6 @@
var angular = require('angular');
var EventEmitter = require('tiny-emitter');
var inherits = require('inherits');
var immutable = require('seamless-immutable');
var events = require('../../events');
......@@ -43,29 +42,31 @@ var threadFixtures = immutable({
var fakeVirtualThread;
var fakeSettings = {};
function FakeVirtualThreadList($scope, $window, rootThread, options) {
class FakeVirtualThreadList extends EventEmitter {
constructor($scope, $window, rootThread, options) {
super();
fakeVirtualThread = this; // eslint-disable-line consistent-this
fakeVirtualThread = this; // eslint-disable-line consistent-this
var thread = rootThread;
var thread = rootThread;
this.options = options;
this.setRootThread = function (_thread) {
thread = _thread;
};
this.notify = function () {
this.emit('changed', {
offscreenLowerHeight: 10,
offscreenUpperHeight: 20,
visibleThreads: thread.children,
});
};
this.detach = sinon.stub();
this.yOffsetOf = function () {
return 42;
};
this.options = options;
this.setRootThread = function (_thread) {
thread = _thread;
};
this.notify = function () {
this.emit('changed', {
offscreenLowerHeight: 10,
offscreenUpperHeight: 20,
visibleThreads: thread.children,
});
};
this.detach = sinon.stub();
this.yOffsetOf = function () {
return 42;
};
}
}
inherits(FakeVirtualThreadList, EventEmitter);
describe('threadList', function () {
var threadListContainers;
......
'use strict';
var EventEmitter = require('tiny-emitter');
var inherits = require('inherits');
/**
* Client for the Hypothesis search API.
*
* SearchClient handles paging through results, canceling search etc.
*
* @param {Object} searchFn - Function for querying the search API
* @param {Object} opts - Search options
* @constructor
*/
function SearchClient(searchFn, opts) {
opts = opts || {};
class SearchClient extends EventEmitter {
/**
* @param {Object} searchFn - Function for querying the search API
* @param {Object} opts - Search options
*/
constructor(searchFn, opts) {
super();
opts = opts || {};
var DEFAULT_CHUNK_SIZE = 200;
this._searchFn = searchFn;
this._chunkSize = opts.chunkSize || DEFAULT_CHUNK_SIZE;
if (typeof opts.incremental !== 'undefined') {
this._incremental = opts.incremental;
} else {
this._incremental = true;
var DEFAULT_CHUNK_SIZE = 200;
this._searchFn = searchFn;
this._chunkSize = opts.chunkSize || DEFAULT_CHUNK_SIZE;
if (typeof opts.incremental !== 'undefined') {
this._incremental = opts.incremental;
} else {
this._incremental = true;
}
this._canceled = false;
}
this._canceled = false;
}
inherits(SearchClient, EventEmitter);
SearchClient.prototype._getBatch = function (query, offset) {
var searchQuery = Object.assign({
limit: this._chunkSize,
offset: offset,
sort: 'created',
order: 'asc',
_separate_replies: true,
}, query);
_getBatch(query, offset) {
var searchQuery = Object.assign({
limit: this._chunkSize,
offset: offset,
sort: 'created',
order: 'asc',
_separate_replies: true,
}, query);
var self = this;
this._searchFn(searchQuery).then(function (results) {
if (self._canceled) {
return;
}
var self = this;
this._searchFn(searchQuery).then(function (results) {
if (self._canceled) {
return;
}
var chunk = results.rows.concat(results.replies || []);
if (self._incremental) {
self.emit('results', chunk);
} else {
self._results = self._results.concat(chunk);
}
var chunk = results.rows.concat(results.replies || []);
if (self._incremental) {
self.emit('results', chunk);
} else {
self._results = self._results.concat(chunk);
}
// Check if there are additional pages of results to fetch. In addition to
// checking the `total` figure from the server, we also require that at
// least one result was returned in the current page, otherwise we would
// end up repeating the same query for the next page. If the server's
// `total` count is incorrect for any reason, that will lead to the client
// polling the server indefinitely.
var nextOffset = offset + results.rows.length;
if (results.total > nextOffset && chunk.length > 0) {
self._getBatch(query, nextOffset);
} else {
if (!self._incremental) {
self.emit('results', self._results);
// Check if there are additional pages of results to fetch. In addition to
// checking the `total` figure from the server, we also require that at
// least one result was returned in the current page, otherwise we would
// end up repeating the same query for the next page. If the server's
// `total` count is incorrect for any reason, that will lead to the client
// polling the server indefinitely.
var nextOffset = offset + results.rows.length;
if (results.total > nextOffset && chunk.length > 0) {
self._getBatch(query, nextOffset);
} else {
if (!self._incremental) {
self.emit('results', self._results);
}
self.emit('end');
}
}).catch(function (err) {
if (self._canceled) {
return;
}
self.emit('error', err);
}).then(function () {
if (self._canceled) {
return;
}
self.emit('end');
}
}).catch(function (err) {
if (self._canceled) {
return;
}
self.emit('error', err);
}).then(function () {
if (self._canceled) {
return;
}
self.emit('end');
});
};
});
}
/**
* Perform a search against the Hypothesis API.
*
* Emits a 'results' event with an array of annotations as they become
* available (in incremental mode) or when all annotations are available
* (in non-incremental mode).
*
* Emits an 'error' event if the search fails.
* Emits an 'end' event once the search completes.
*/
SearchClient.prototype.get = function (query) {
this._results = [];
this._getBatch(query, 0);
};
/**
* Perform a search against the Hypothesis API.
*
* Emits a 'results' event with an array of annotations as they become
* available (in incremental mode) or when all annotations are available
* (in non-incremental mode).
*
* Emits an 'error' event if the search fails.
* Emits an 'end' event once the search completes.
*/
get(query) {
this._results = [];
this._getBatch(query, 0);
}
/**
* Cancel the current search and emit the 'end' event.
* No further events will be emitted after this.
*/
SearchClient.prototype.cancel = function () {
this._canceled = true;
this.emit('end');
};
/**
* Cancel the current search and emit the 'end' event.
* No further events will be emitted after this.
*/
cancel() {
this._canceled = true;
this.emit('end');
}
}
module.exports = SearchClient;
'use strict';
var EventEmitter = require('tiny-emitter');
var inherits = require('inherits');
var proxyquire = require('proxyquire');
var events = require('../../events');
......@@ -42,28 +41,31 @@ var fixtures = {
// the most recently created FakeSocket instance
var fakeWebSocket = null;
function FakeSocket(url) {
fakeWebSocket = this; // eslint-disable-line consistent-this
class FakeSocket extends EventEmitter {
constructor(url) {
super();
this.url = url;
this.messages = [];
this.didClose = false;
fakeWebSocket = this; // eslint-disable-line consistent-this
this.isConnected = sinon.stub().returns(true);
this.url = url;
this.messages = [];
this.didClose = false;
this.send = function (message) {
this.messages.push(message);
};
this.isConnected = sinon.stub().returns(true);
this.notify = function (message) {
this.emit('message', {data: JSON.stringify(message)});
};
this.send = function (message) {
this.messages.push(message);
};
this.notify = function (message) {
this.emit('message', {data: JSON.stringify(message)});
};
this.close = function () {
this.didClose = true;
};
this.close = function () {
this.didClose = true;
};
}
}
inherits(FakeSocket, EventEmitter);
describe('Streamer', function () {
var fakeAnnotationMapper;
......
......@@ -2,7 +2,6 @@
var EventEmitter = require('tiny-emitter');
var debounce = require('lodash.debounce');
var inherits = require('inherits');
/**
* @typedef Options
......@@ -26,178 +25,182 @@ var inherits = require('inherits');
* applications this also helps significantly with UI responsiveness by limiting
* the number of watchers (functions created by template expressions or
* '$scope.$watch' calls) that have to be run on every '$scope.$digest()' cycle.
*
* @param {Window} container - The Window displaying the list of annotation threads.
* @param {Thread} rootThread - The initial Thread object for the top-level
* threads.
* @param {Options} options
*/
function VirtualThreadList($scope, window_, rootThread, options) {
var self = this;
this._rootThread = rootThread;
this._options = Object.assign({}, options);
// Cache of thread ID -> last-seen height
this._heights = {};
this.window = window_;
this.scrollRoot = options.scrollRoot || document.body;
var debouncedUpdate = debounce(function () {
self._updateVisibleThreads();
$scope.$digest();
}, 20);
this.scrollRoot.addEventListener('scroll', debouncedUpdate);
this.window.addEventListener('resize', debouncedUpdate);
this._detach = function () {
this.scrollRoot.removeEventListener('scroll', debouncedUpdate);
this.window.removeEventListener('resize', debouncedUpdate);
};
}
inherits(VirtualThreadList, EventEmitter);
class VirtualThreadList extends EventEmitter {
/*
* @param {Window} container - The Window displaying the list of annotation threads.
* @param {Thread} rootThread - The initial Thread object for the top-level
* threads.
* @param {Options} options
*/
constructor($scope, window_, rootThread, options) {
super();
var self = this;
this._rootThread = rootThread;
this._options = Object.assign({}, options);
// Cache of thread ID -> last-seen height
this._heights = {};
this.window = window_;
this.scrollRoot = options.scrollRoot || document.body;
var debouncedUpdate = debounce(function () {
self._updateVisibleThreads();
$scope.$digest();
}, 20);
this.scrollRoot.addEventListener('scroll', debouncedUpdate);
this.window.addEventListener('resize', debouncedUpdate);
this._detach = function () {
this.scrollRoot.removeEventListener('scroll', debouncedUpdate);
this.window.removeEventListener('resize', debouncedUpdate);
};
}
/**
* Detach event listeners and clear any pending timeouts.
*
* This should be invoked when the UI view presenting the virtual thread list
* is torn down.
*/
VirtualThreadList.prototype.detach = function () {
this._detach();
};
/**
* Detach event listeners and clear any pending timeouts.
*
* This should be invoked when the UI view presenting the virtual thread list
* is torn down.
*/
detach() {
this._detach();
}
/**
* Sets the root thread containing all conversations matching the current
* filters.
*
* This should be called with the current Thread object whenever the set of
* matching annotations changes.
*/
VirtualThreadList.prototype.setRootThread = function (thread) {
if (thread === this._rootThread) {
return;
/**
* Sets the root thread containing all conversations matching the current
* filters.
*
* This should be called with the current Thread object whenever the set of
* matching annotations changes.
*/
setRootThread(thread) {
if (thread === this._rootThread) {
return;
}
this._rootThread = thread;
this._updateVisibleThreads();
}
this._rootThread = thread;
this._updateVisibleThreads();
};
/**
* Sets the actual height for a thread.
*
* When calculating the amount of space required for offscreen threads,
* the actual or 'last-seen' height is used if known. Otherwise an estimate
* is used.
*
* @param {string} id - The annotation ID or $tag
* @param {number} height - The height of the annotation thread.
*/
VirtualThreadList.prototype.setThreadHeight = function (id, height) {
if (isNaN(height) || height <= 0) {
throw new Error('Invalid thread height %d', height);
/**
* Sets the actual height for a thread.
*
* When calculating the amount of space required for offscreen threads,
* the actual or 'last-seen' height is used if known. Otherwise an estimate
* is used.
*
* @param {string} id - The annotation ID or $tag
* @param {number} height - The height of the annotation thread.
*/
setThreadHeight(id, height) {
if (isNaN(height) || height <= 0) {
throw new Error('Invalid thread height %d', height);
}
this._heights[id] = height;
}
this._heights[id] = height;
};
VirtualThreadList.prototype._height = function (id) {
// Default guess of the height required for a threads that have not been
// measured
var DEFAULT_HEIGHT = 200;
return this._heights[id] || DEFAULT_HEIGHT;
};
/** Return the vertical offset of an annotation card from the top of the list. */
VirtualThreadList.prototype.yOffsetOf = function (id) {
var self = this;
var allThreads = this._rootThread.children;
var matchIndex = allThreads.findIndex(function (thread) {
return thread.id === id;
});
if (matchIndex === -1) {
return 0;
_height(id) {
// Default guess of the height required for a threads that have not been
// measured
var DEFAULT_HEIGHT = 200;
return this._heights[id] || DEFAULT_HEIGHT;
}
return allThreads.slice(0, matchIndex).reduce(function (offset, thread) {
return offset + self._height(thread.id);
}, 0);
};
/**
* Recalculates the set of visible threads and estimates of the amount of space
* required for offscreen threads above and below the viewport.
*
* Emits a `changed` event with the recalculated set of visible threads.
*/
VirtualThreadList.prototype._updateVisibleThreads = function () {
// Space above the viewport in pixels which should be considered 'on-screen'
// when calculating the set of visible threads
var MARGIN_ABOVE = 800;
// Same as MARGIN_ABOVE but for the space below the viewport
var MARGIN_BELOW = 800;
// Estimated height in pixels of annotation cards which are below the
// viewport and not actually created. This is used to create an empty spacer
// element below visible cards in order to give the list's scrollbar the
// correct dimensions.
var offscreenLowerHeight = 0;
// Same as offscreenLowerHeight but for cards above the viewport.
var offscreenUpperHeight = 0;
// List of annotations which are in or near the viewport and need to
// actually be created.
var visibleThreads = [];
// List of annotations which are required to be rendered but we do not
// want them visible. This is to ensure that we allow items to be rendered
// and initialized (for saving purposes) without having them be presented
// in out of context scenarios (i.e. in wrong order for sort)
var invisibleThreads = [];
var allThreads = this._rootThread.children;
var visibleHeight = this.window.innerHeight;
var usedHeight = 0;
var thread;
for (var i = 0; i < allThreads.length; i++) {
thread = allThreads[i];
var threadHeight = this._height(thread.id);
var added = false;
if (usedHeight + threadHeight < this.scrollRoot.scrollTop - MARGIN_ABOVE) {
// Thread is above viewport
offscreenUpperHeight += threadHeight;
} else if (usedHeight <
this.scrollRoot.scrollTop + visibleHeight + MARGIN_BELOW) {
// Thread is either in or close to the viewport
visibleThreads.push(thread);
added = true;
} else {
// Thread is below viewport
offscreenLowerHeight += threadHeight;
/** Return the vertical offset of an annotation card from the top of the list. */
yOffsetOf(id) {
var self = this;
var allThreads = this._rootThread.children;
var matchIndex = allThreads.findIndex(function (thread) {
return thread.id === id;
});
if (matchIndex === -1) {
return 0;
}
return allThreads.slice(0, matchIndex).reduce(function (offset, thread) {
return offset + self._height(thread.id);
}, 0);
}
// any thread that is not going to go through the render process
// because it is already outside of the viewport should be checked
// to see if it needs to be added as an invisible render. So it will
// be available to go through rendering but not visible to the user
if(!added &&
this._options.invisibleThreadFilter &&
this._options.invisibleThreadFilter(thread)){
invisibleThreads.push(thread);
/**
* Recalculates the set of visible threads and estimates of the amount of space
* required for offscreen threads above and below the viewport.
*
* Emits a `changed` event with the recalculated set of visible threads.
*/
_updateVisibleThreads() {
// Space above the viewport in pixels which should be considered 'on-screen'
// when calculating the set of visible threads
var MARGIN_ABOVE = 800;
// Same as MARGIN_ABOVE but for the space below the viewport
var MARGIN_BELOW = 800;
// Estimated height in pixels of annotation cards which are below the
// viewport and not actually created. This is used to create an empty spacer
// element below visible cards in order to give the list's scrollbar the
// correct dimensions.
var offscreenLowerHeight = 0;
// Same as offscreenLowerHeight but for cards above the viewport.
var offscreenUpperHeight = 0;
// List of annotations which are in or near the viewport and need to
// actually be created.
var visibleThreads = [];
// List of annotations which are required to be rendered but we do not
// want them visible. This is to ensure that we allow items to be rendered
// and initialized (for saving purposes) without having them be presented
// in out of context scenarios (i.e. in wrong order for sort)
var invisibleThreads = [];
var allThreads = this._rootThread.children;
var visibleHeight = this.window.innerHeight;
var usedHeight = 0;
var thread;
for (var i = 0; i < allThreads.length; i++) {
thread = allThreads[i];
var threadHeight = this._height(thread.id);
var added = false;
if (usedHeight + threadHeight < this.scrollRoot.scrollTop - MARGIN_ABOVE) {
// Thread is above viewport
offscreenUpperHeight += threadHeight;
} else if (usedHeight <
this.scrollRoot.scrollTop + visibleHeight + MARGIN_BELOW) {
// Thread is either in or close to the viewport
visibleThreads.push(thread);
added = true;
} else {
// Thread is below viewport
offscreenLowerHeight += threadHeight;
}
// any thread that is not going to go through the render process
// because it is already outside of the viewport should be checked
// to see if it needs to be added as an invisible render. So it will
// be available to go through rendering but not visible to the user
if(!added &&
this._options.invisibleThreadFilter &&
this._options.invisibleThreadFilter(thread)){
invisibleThreads.push(thread);
}
usedHeight += threadHeight;
}
usedHeight += threadHeight;
this.emit('changed', {
offscreenLowerHeight: offscreenLowerHeight,
offscreenUpperHeight: offscreenUpperHeight,
visibleThreads: visibleThreads,
invisibleThreads: invisibleThreads,
});
}
this.emit('changed', {
offscreenLowerHeight: offscreenLowerHeight,
offscreenUpperHeight: offscreenUpperHeight,
visibleThreads: visibleThreads,
invisibleThreads: invisibleThreads,
});
};
}
module.exports = VirtualThreadList;
......@@ -2,7 +2,6 @@
var retry = require('retry');
var EventEmitter = require('tiny-emitter');
var inherits = require('inherits');
// see https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
var CLOSE_NORMAL = 1000;
......@@ -19,114 +18,116 @@ var RECONNECT_MIN_DELAY = 1000;
* - Uses the standard EventEmitter API for reporting open, close, error
* and message events.
*/
function Socket(url) {
var self = this;
class Socket extends EventEmitter {
constructor(url) {
super();
// queue of JSON objects which have not yet been submitted
var messageQueue = [];
var self = this;
// the current WebSocket instance
var socket;
// queue of JSON objects which have not yet been submitted
var messageQueue = [];
// a pending operation to connect a WebSocket
var operation;
// the current WebSocket instance
var socket;
function sendMessages() {
while (messageQueue.length > 0) {
var messageString = JSON.stringify(messageQueue.shift());
socket.send(messageString);
}
}
// a pending operation to connect a WebSocket
var operation;
// Connect the websocket immediately. If a connection attempt is already in
// progress, do nothing.
function connect() {
if (operation) {
return;
function sendMessages() {
while (messageQueue.length > 0) {
var messageString = JSON.stringify(messageQueue.shift());
socket.send(messageString);
}
}
operation = retry.operation({
minTimeout: RECONNECT_MIN_DELAY * 2,
// Don't retry forever -- fail permanently after 10 retries
retries: 10,
// Randomize retry times to minimise the thundering herd effect
randomize: true,
});
operation.attempt(function () {
socket = new WebSocket(url);
socket.onopen = function (event) {
onOpen();
self.emit('open', event);
};
socket.onclose = function (event) {
if (event.code === CLOSE_NORMAL) {
self.emit('close', event);
return;
}
var err = new Error('WebSocket closed abnormally, code: ' + event.code);
console.warn(err);
onAbnormalClose(err);
};
socket.onerror = function (event) {
self.emit('error', event);
};
socket.onmessage = function (event) {
self.emit('message', event);
};
});
}
// onOpen is called when a websocket connection is successfully established.
function onOpen() {
operation = null;
sendMessages();
}
// onAbnormalClose is called when a websocket connection closes abnormally.
// This may be the result of a failure to connect, or an abnormal close after
// a previous successful connection.
function onAbnormalClose(error) {
// If we're already in a reconnection loop, trigger a retry...
if (operation) {
if (!operation.retry(error)) {
console.error('reached max retries attempting to reconnect websocket');
// Connect the websocket immediately. If a connection attempt is already in
// progress, do nothing.
function connect() {
if (operation) {
return;
}
return;
operation = retry.operation({
minTimeout: RECONNECT_MIN_DELAY * 2,
// Don't retry forever -- fail permanently after 10 retries
retries: 10,
// Randomize retry times to minimise the thundering herd effect
randomize: true,
});
operation.attempt(function () {
socket = new WebSocket(url);
socket.onopen = function (event) {
onOpen();
self.emit('open', event);
};
socket.onclose = function (event) {
if (event.code === CLOSE_NORMAL) {
self.emit('close', event);
return;
}
var err = new Error('WebSocket closed abnormally, code: ' + event.code);
console.warn(err);
onAbnormalClose(err);
};
socket.onerror = function (event) {
self.emit('error', event);
};
socket.onmessage = function (event) {
self.emit('message', event);
};
});
}
// ...otherwise reconnect the websocket after a short delay.
var delay = RECONNECT_MIN_DELAY;
delay += Math.floor(Math.random() * delay);
operation = setTimeout(function () {
operation = null;
connect();
}, delay);
}
/** Close the underlying WebSocket connection */
this.close = function () {
socket.close();
};
/**
* Send a JSON object via the WebSocket connection, or queue it
* for later delivery if not currently connected.
*/
this.send = function (message) {
messageQueue.push(message);
if (this.isConnected()) {
// onOpen is called when a websocket connection is successfully established.
function onOpen() {
operation = null;
sendMessages();
}
};
/** Returns true if the WebSocket is currently connected. */
this.isConnected = function () {
return socket.readyState === WebSocket.OPEN;
};
// onAbnormalClose is called when a websocket connection closes abnormally.
// This may be the result of a failure to connect, or an abnormal close after
// a previous successful connection.
function onAbnormalClose(error) {
// If we're already in a reconnection loop, trigger a retry...
if (operation) {
if (!operation.retry(error)) {
console.error('reached max retries attempting to reconnect websocket');
}
return;
}
// ...otherwise reconnect the websocket after a short delay.
var delay = RECONNECT_MIN_DELAY;
delay += Math.floor(Math.random() * delay);
operation = setTimeout(function () {
operation = null;
connect();
}, delay);
}
/** Close the underlying WebSocket connection */
this.close = function () {
socket.close();
};
/**
* Send a JSON object via the WebSocket connection, or queue it
* for later delivery if not currently connected.
*/
this.send = function (message) {
messageQueue.push(message);
if (this.isConnected()) {
sendMessages();
}
};
connect();
}
/** Returns true if the WebSocket is currently connected. */
this.isConnected = function () {
return socket.readyState === WebSocket.OPEN;
};
inherits(Socket, EventEmitter);
connect();
}
}
module.exports = Socket;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment