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 @@ ...@@ -57,7 +57,6 @@
"gulp-sourcemaps": "^1.6.0", "gulp-sourcemaps": "^1.6.0",
"gulp-util": "^3.0.7", "gulp-util": "^3.0.7",
"hammerjs": "^2.0.4", "hammerjs": "^2.0.4",
"inherits": "^2.0.1",
"isparta": "^4.0.0", "isparta": "^4.0.0",
"istanbul": "^0.4.5", "istanbul": "^0.4.5",
"jquery": "^3.2.1", "jquery": "^3.2.1",
......
'use strict'; 'use strict';
var angular = require('angular'); var angular = require('angular');
var inherits = require('inherits');
var proxyquire = require('proxyquire'); var proxyquire = require('proxyquire');
var EventEmitter = require('tiny-emitter'); var EventEmitter = require('tiny-emitter');
...@@ -9,32 +8,38 @@ var events = require('../../events'); ...@@ -9,32 +8,38 @@ var events = require('../../events');
var noCallThru = require('../../../shared/test/util').noCallThru; var noCallThru = require('../../../shared/test/util').noCallThru;
var searchClients; 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() { class FakeRootThread extends EventEmitter {
this.thread = sinon.stub().returns({ constructor() {
totalChildren: 0, super();
}); this.thread = sinon.stub().returns({
totalChildren: 0,
});
}
} }
inherits(FakeRootThread, EventEmitter);
describe('sidebar.components.sidebar-content', function () { describe('sidebar.components.sidebar-content', function () {
var $rootScope; var $rootScope;
......
'use strict'; 'use strict';
var angular = require('angular'); var angular = require('angular');
var inherits = require('inherits');
var EventEmitter = require('tiny-emitter'); var EventEmitter = require('tiny-emitter');
function FakeRootThread() { class FakeRootThread extends EventEmitter {
this.thread = sinon.stub(); constructor() {
super();
this.thread = sinon.stub();
}
} }
inherits(FakeRootThread, EventEmitter);
describe('StreamContentController', function () { describe('StreamContentController', function () {
var $componentController; var $componentController;
......
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
var angular = require('angular'); var angular = require('angular');
var EventEmitter = require('tiny-emitter'); var EventEmitter = require('tiny-emitter');
var inherits = require('inherits');
var immutable = require('seamless-immutable'); var immutable = require('seamless-immutable');
var events = require('../../events'); var events = require('../../events');
...@@ -43,29 +42,31 @@ var threadFixtures = immutable({ ...@@ -43,29 +42,31 @@ var threadFixtures = immutable({
var fakeVirtualThread; var fakeVirtualThread;
var fakeSettings = {}; 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.options = options;
this.setRootThread = function (_thread) { this.setRootThread = function (_thread) {
thread = _thread; thread = _thread;
}; };
this.notify = function () { this.notify = function () {
this.emit('changed', { this.emit('changed', {
offscreenLowerHeight: 10, offscreenLowerHeight: 10,
offscreenUpperHeight: 20, offscreenUpperHeight: 20,
visibleThreads: thread.children, visibleThreads: thread.children,
}); });
}; };
this.detach = sinon.stub(); this.detach = sinon.stub();
this.yOffsetOf = function () { this.yOffsetOf = function () {
return 42; return 42;
}; };
}
} }
inherits(FakeVirtualThreadList, EventEmitter);
describe('threadList', function () { describe('threadList', function () {
var threadListContainers; var threadListContainers;
......
'use strict'; 'use strict';
var EventEmitter = require('tiny-emitter'); var EventEmitter = require('tiny-emitter');
var inherits = require('inherits');
/** /**
* Client for the Hypothesis search API. * Client for the Hypothesis search API.
* *
* SearchClient handles paging through results, canceling search etc. * 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) { class SearchClient extends EventEmitter {
opts = opts || {}; /**
* @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; var DEFAULT_CHUNK_SIZE = 200;
this._searchFn = searchFn; this._searchFn = searchFn;
this._chunkSize = opts.chunkSize || DEFAULT_CHUNK_SIZE; this._chunkSize = opts.chunkSize || DEFAULT_CHUNK_SIZE;
if (typeof opts.incremental !== 'undefined') { if (typeof opts.incremental !== 'undefined') {
this._incremental = opts.incremental; this._incremental = opts.incremental;
} else { } else {
this._incremental = true; this._incremental = true;
}
this._canceled = false;
} }
this._canceled = false;
}
inherits(SearchClient, EventEmitter);
SearchClient.prototype._getBatch = function (query, offset) { _getBatch(query, offset) {
var searchQuery = Object.assign({ var searchQuery = Object.assign({
limit: this._chunkSize, limit: this._chunkSize,
offset: offset, offset: offset,
sort: 'created', sort: 'created',
order: 'asc', order: 'asc',
_separate_replies: true, _separate_replies: true,
}, query); }, query);
var self = this; var self = this;
this._searchFn(searchQuery).then(function (results) { this._searchFn(searchQuery).then(function (results) {
if (self._canceled) { if (self._canceled) {
return; return;
} }
var chunk = results.rows.concat(results.replies || []); var chunk = results.rows.concat(results.replies || []);
if (self._incremental) { if (self._incremental) {
self.emit('results', chunk); self.emit('results', chunk);
} else { } else {
self._results = self._results.concat(chunk); self._results = self._results.concat(chunk);
} }
// Check if there are additional pages of results to fetch. In addition to // 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 // checking the `total` figure from the server, we also require that at
// least one result was returned in the current page, otherwise we would // 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 // 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 // `total` count is incorrect for any reason, that will lead to the client
// polling the server indefinitely. // polling the server indefinitely.
var nextOffset = offset + results.rows.length; var nextOffset = offset + results.rows.length;
if (results.total > nextOffset && chunk.length > 0) { if (results.total > nextOffset && chunk.length > 0) {
self._getBatch(query, nextOffset); self._getBatch(query, nextOffset);
} else { } else {
if (!self._incremental) { if (!self._incremental) {
self.emit('results', self._results); 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'); 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. * Perform a search against the Hypothesis API.
* *
* Emits a 'results' event with an array of annotations as they become * Emits a 'results' event with an array of annotations as they become
* available (in incremental mode) or when all annotations are available * available (in incremental mode) or when all annotations are available
* (in non-incremental mode). * (in non-incremental mode).
* *
* Emits an 'error' event if the search fails. * Emits an 'error' event if the search fails.
* Emits an 'end' event once the search completes. * Emits an 'end' event once the search completes.
*/ */
SearchClient.prototype.get = function (query) { get(query) {
this._results = []; this._results = [];
this._getBatch(query, 0); this._getBatch(query, 0);
}; }
/** /**
* Cancel the current search and emit the 'end' event. * Cancel the current search and emit the 'end' event.
* No further events will be emitted after this. * No further events will be emitted after this.
*/ */
SearchClient.prototype.cancel = function () { cancel() {
this._canceled = true; this._canceled = true;
this.emit('end'); this.emit('end');
}; }
}
module.exports = SearchClient; module.exports = SearchClient;
'use strict'; 'use strict';
var EventEmitter = require('tiny-emitter'); var EventEmitter = require('tiny-emitter');
var inherits = require('inherits');
var proxyquire = require('proxyquire'); var proxyquire = require('proxyquire');
var events = require('../../events'); var events = require('../../events');
...@@ -42,28 +41,31 @@ var fixtures = { ...@@ -42,28 +41,31 @@ var fixtures = {
// the most recently created FakeSocket instance // the most recently created FakeSocket instance
var fakeWebSocket = null; var fakeWebSocket = null;
function FakeSocket(url) { class FakeSocket extends EventEmitter {
fakeWebSocket = this; // eslint-disable-line consistent-this constructor(url) {
super();
this.url = url; fakeWebSocket = this; // eslint-disable-line consistent-this
this.messages = [];
this.didClose = false;
this.isConnected = sinon.stub().returns(true); this.url = url;
this.messages = [];
this.didClose = false;
this.send = function (message) { this.isConnected = sinon.stub().returns(true);
this.messages.push(message);
};
this.notify = function (message) { this.send = function (message) {
this.emit('message', {data: JSON.stringify(message)}); this.messages.push(message);
}; };
this.notify = function (message) {
this.emit('message', {data: JSON.stringify(message)});
};
this.close = function () { this.close = function () {
this.didClose = true; this.didClose = true;
}; };
}
} }
inherits(FakeSocket, EventEmitter);
describe('Streamer', function () { describe('Streamer', function () {
var fakeAnnotationMapper; var fakeAnnotationMapper;
......
This diff is collapsed.
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
var retry = require('retry'); var retry = require('retry');
var EventEmitter = require('tiny-emitter'); var EventEmitter = require('tiny-emitter');
var inherits = require('inherits');
// see https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent // see https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
var CLOSE_NORMAL = 1000; var CLOSE_NORMAL = 1000;
...@@ -19,114 +18,116 @@ var RECONNECT_MIN_DELAY = 1000; ...@@ -19,114 +18,116 @@ var RECONNECT_MIN_DELAY = 1000;
* - Uses the standard EventEmitter API for reporting open, close, error * - Uses the standard EventEmitter API for reporting open, close, error
* and message events. * and message events.
*/ */
function Socket(url) { class Socket extends EventEmitter {
var self = this; constructor(url) {
super();
// queue of JSON objects which have not yet been submitted var self = this;
var messageQueue = [];
// the current WebSocket instance // queue of JSON objects which have not yet been submitted
var socket; var messageQueue = [];
// a pending operation to connect a WebSocket // the current WebSocket instance
var operation; var socket;
function sendMessages() { // a pending operation to connect a WebSocket
while (messageQueue.length > 0) { var operation;
var messageString = JSON.stringify(messageQueue.shift());
socket.send(messageString);
}
}
// Connect the websocket immediately. If a connection attempt is already in function sendMessages() {
// progress, do nothing. while (messageQueue.length > 0) {
function connect() { var messageString = JSON.stringify(messageQueue.shift());
if (operation) { socket.send(messageString);
return; }
} }
operation = retry.operation({ // Connect the websocket immediately. If a connection attempt is already in
minTimeout: RECONNECT_MIN_DELAY * 2, // progress, do nothing.
// Don't retry forever -- fail permanently after 10 retries function connect() {
retries: 10, if (operation) {
// Randomize retry times to minimise the thundering herd effect return;
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');
} }
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 */ // onOpen is called when a websocket connection is successfully established.
this.close = function () { function onOpen() {
socket.close(); operation = null;
};
/**
* 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(); sendMessages();
} }
};
/** Returns true if the WebSocket is currently connected. */ // onAbnormalClose is called when a websocket connection closes abnormally.
this.isConnected = function () { // This may be the result of a failure to connect, or an abnormal close after
return socket.readyState === WebSocket.OPEN; // 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; 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