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;
......
This diff is collapsed.
......@@ -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