Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also .

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also .
Choose a Base Repository
hypothesis/h
robertknight/h
40a/h
AFDudley/h
BigBlueHat/h
BinaryStars/h
CCH543/h
Cinemacloud/h
Ericgood/h
FTG-003/h
Forethinker/h
GratefulTony/h
HGldJ1966/h
JJediny/h
John-Williams/h
Laurian/h
LittleFancy/h
MattyQ/h
Mishkin2015/h
RichardLitt/h
Staffan1/h
SteelWagstaff/h
TowerBR/h
VanyTang/h
abigailricarte/h
ackermann/h
alecchap/h
alesarrett/h
alexsegura/h
almereyda/h
alon/h
andzi/h
angelicxsoul/h
ansmoh/h
apurvajalit/h
arjunvasan/h
asdevor/h
bZichett/h
badgettrg/Webmarks
balmas/h
balupton/h
bbarker/h
bennlich/h
benthor/h
blakewest/h
bogste/h
bradparks/h
brittanystoroz/h
buiquangchien/h
cdchapman/h
charblanc/h
chowsamihq/h
chr7stos/Webmarks
chrber/h
chrismPssina/h
christinaphamAD/h
cmbirk/h
codeaudit/h
coolcool21/h
cove/h
csillag/h
danjimilk/h
dannyhope/h
daredream/h
davidmcclure/h
dennisplucinik/h
dezynetechnologies/h
diegodlh/h
djcun95/h
donsequitur/h
edsu/h
eiro10/h
emckean/h
ercchy/h
eshellman/h
fangang123/h
fchasen/h
fcrimins/h
fhirsch/h
ficolo/h
fragkopoulos/h
gauravkeerthi/h
geass/h
gergely-ujvari/h
gitter-badger/h
gnott/h
gobengo/h
gorinovic/h
gus3000/h
hashin/h
helemaalbigt/h
hmstepanek/h
hwasiti/h
hylhero/h
hyperstudio/h
iHDeveloper/h
imeysam/h
jackspaceBerkeley/h
jarey/h
jasdeep/h
jason790/h
jasonzou/j
jazahn/h
jccr/h
jean/h
jeka57/h
jeremydean/h
jermnelson/h
jibe-b/h
jnishiyama/h
jojksd/h
jpadilla/h
jtremback/h
judell/h
juli-so/h
kabacs/h
karissa/h
kaushikvijay/h
kaydoh/h
kill4uk/h
klopiinas/h
klrkdekira/h
koulihong311/h
krassif/h
krstnkngs/h
leoqmp/h
linhua55/h
lucadealfaro/h
lyspooner/h
lyzadanger/h
m1yag1/h
magee/h
mambocab/h
manunymous/h
maraino/h
mari-ja/h
markbarratt/h
martinq/h
mbbaig/h
mcarv63/h
meawoppl/h
meflyup/h
metasj/h
mgasner/h
mgax/h
mollycr/h
mrchrisadams/h
mrienstra/h
mshavlovsky/h
muddasani/h
nagyist/hyphothesis-h
nagyistoce/hypothesis-h
nanxio/h
neozhangthe1/h
ningyifan/h
nkingsley/h
nlholdem/h
nlisgo/h
noscripter/h
nshkuro/h
odnodn/h
oliversauter/h
openbizgit/h
opengovfoundation/h
openstax/hypothesis-server
ouroboros8/h
pablomarti/h
pamo/h
philipn/h
philschatz/h
pinballwonder/h
plainspace/h
raowl/h
rickyhan/h
rmoorman/h
rmtsukuru/h
rowhit/h
rsarxiv/h
saakaifoundry/h
samrose/h
scharf/h
shepazu/h
sherah/h
shofheinz/h
soapdog/h
ssin122/test-h
st-fresh/h
stuk88/h
sylvanmist/h
tetratorus/h
tilgovi/h
tomnar/h
trivenews/h
truthadjustr/h
utngz/h
voidfiles/h
wenchen/h
yargevad/h
yumatch/h
zshen777/h
Nothing to show
Choose a base branch
2263-tooltip-for-note-button
anchoring-rewrite
angular-1.3
angular-1.4_rob
app-cors
app_startup_metrics
assets-view-tests
atom-link-related
autoprefix_css
avoid-group-reload-on-change
better-dockerfile-caching
bridge_timeout_debugging
browserify_extension
build_deps_before_test
categorized_logging
change-username-form
check-asset-versions-v2
decaf
deduplicate-password-validation
deps-auto-update
ejp
embedding-docs-link
enable-group-creator-deletion-split-delete-method
enriched-stream
extension-badge-tint
extension_badge_refactor
extension_build_type_indicator
extension_embed_conflict
form-preact
frontend_build_refactor
frontend_tests_docs
gh2505-autolink
gh2553-fix_selection_filter
gh2561-highlight_text_revert
gh2563-test_timeout_debugging
gh2568-safari_missing_urls
gh2641-no_reload_on_groups_list_change
gh2642-reload_fouc
gh2646-bucket_bar_pos
gh2654-safari_search_expander
gh2663-do_not_switch_group_on_leave
groups_changed_exception
groups_list_selection_fix
groups
icon_font_fix
icon_font_fix_2
improve-form-field-list-design
index-and-search-elasticsearch6
index-authority
karma-watch-target
login-with-google
master
multitarget
nipsa
oauth-admin
oauth-login-prototype
p-frontend_build_refactor
p-fx-webextensions
p-new_highlighter
p-new_threading_impl
p-preact_annotation
p-preact_components
p-thread_model
parallel-reindex
publish-btn-cleanup
py3-docker
quote-anchor-integration-test
refactor-sidebar-injector-test
replace-angular_websocket
reply-count-badge
rob-setup_docs_corrections
search-bar-preact
search-bar-tidy-up
search-decaf
search-tests
server_sorted_groups
support-sqs
t87-editorconfig-sass
t87-group_scope_dropdown_ui
t88-remove_self_from_group
t89-combined_scope_save_btn
t89-group_list_css_refactor
t90-search_icon_click
t90-top_bar_new_design
t90-top_bar_refactor
t91-sort_dropdown_move_to_top_bar
t91-sort_dropdown_refactor
t93-clear_selection_btn_ux
t93-create_group_refresh
t105-group_push_notifications
t112-center_post_dropdown
t125-post_button_ui_fixes
t139-new_loading_indicator
t148-card_group_style_refactor
t152-move_unsaved_annot_to_current_group
t182-chrome_perms_refactor
t187-new_homepage_design
thread-collapsing
travis-flake8
v0.2.x
v0.3.x
visual-truncation
websocket-send-on-reconnect
z-login-with-google
z-py3-working-docker-img
z-py3
z-search-tests
Nothing to show
Choose a Head Repository
hypothesis/h
robertknight/h
40a/h
AFDudley/h
BigBlueHat/h
BinaryStars/h
CCH543/h
Cinemacloud/h
Ericgood/h
FTG-003/h
Forethinker/h
GratefulTony/h
HGldJ1966/h
JJediny/h
John-Williams/h
Laurian/h
LittleFancy/h
MattyQ/h
Mishkin2015/h
RichardLitt/h
Staffan1/h
SteelWagstaff/h
TowerBR/h
VanyTang/h
abigailricarte/h
ackermann/h
alecchap/h
alesarrett/h
alexsegura/h
almereyda/h
alon/h
andzi/h
angelicxsoul/h
ansmoh/h
apurvajalit/h
arjunvasan/h
asdevor/h
bZichett/h
badgettrg/Webmarks
balmas/h
balupton/h
bbarker/h
bennlich/h
benthor/h
blakewest/h
bogste/h
bradparks/h
brittanystoroz/h
buiquangchien/h
cdchapman/h
charblanc/h
chowsamihq/h
chr7stos/Webmarks
chrber/h
chrismPssina/h
christinaphamAD/h
cmbirk/h
codeaudit/h
coolcool21/h
cove/h
csillag/h
danjimilk/h
dannyhope/h
daredream/h
davidmcclure/h
dennisplucinik/h
dezynetechnologies/h
diegodlh/h
djcun95/h
donsequitur/h
edsu/h
eiro10/h
emckean/h
ercchy/h
eshellman/h
fangang123/h
fchasen/h
fcrimins/h
fhirsch/h
ficolo/h
fragkopoulos/h
gauravkeerthi/h
geass/h
gergely-ujvari/h
gitter-badger/h
gnott/h
gobengo/h
gorinovic/h
gus3000/h
hashin/h
helemaalbigt/h
hmstepanek/h
hwasiti/h
hylhero/h
hyperstudio/h
iHDeveloper/h
imeysam/h
jackspaceBerkeley/h
jarey/h
jasdeep/h
jason790/h
jasonzou/j
jazahn/h
jccr/h
jean/h
jeka57/h
jeremydean/h
jermnelson/h
jibe-b/h
jnishiyama/h
jojksd/h
jpadilla/h
jtremback/h
judell/h
juli-so/h
kabacs/h
karissa/h
kaushikvijay/h
kaydoh/h
kill4uk/h
klopiinas/h
klrkdekira/h
koulihong311/h
krassif/h
krstnkngs/h
leoqmp/h
linhua55/h
lucadealfaro/h
lyspooner/h
lyzadanger/h
m1yag1/h
magee/h
mambocab/h
manunymous/h
maraino/h
mari-ja/h
markbarratt/h
martinq/h
mbbaig/h
mcarv63/h
meawoppl/h
meflyup/h
metasj/h
mgasner/h
mgax/h
mollycr/h
mrchrisadams/h
mrienstra/h
mshavlovsky/h
muddasani/h
nagyist/hyphothesis-h
nagyistoce/hypothesis-h
nanxio/h
neozhangthe1/h
ningyifan/h
nkingsley/h
nlholdem/h
nlisgo/h
noscripter/h
nshkuro/h
odnodn/h
oliversauter/h
openbizgit/h
opengovfoundation/h
openstax/hypothesis-server
ouroboros8/h
pablomarti/h
pamo/h
philipn/h
philschatz/h
pinballwonder/h
plainspace/h
raowl/h
rickyhan/h
rmoorman/h
rmtsukuru/h
rowhit/h
rsarxiv/h
saakaifoundry/h
samrose/h
scharf/h
shepazu/h
sherah/h
shofheinz/h
soapdog/h
ssin122/test-h
st-fresh/h
stuk88/h
sylvanmist/h
tetratorus/h
tilgovi/h
tomnar/h
trivenews/h
truthadjustr/h
utngz/h
voidfiles/h
wenchen/h
yargevad/h
yumatch/h
zshen777/h
Nothing to show
Choose a head branch
2263-tooltip-for-note-button
anchoring-rewrite
angular-1.3
angular-1.4_rob
app-cors
app_startup_metrics
assets-view-tests
atom-link-related
autoprefix_css
avoid-group-reload-on-change
better-dockerfile-caching
bridge_timeout_debugging
browserify_extension
build_deps_before_test
categorized_logging
change-username-form
check-asset-versions-v2
decaf
deduplicate-password-validation
deps-auto-update
ejp
embedding-docs-link
enable-group-creator-deletion-split-delete-method
enriched-stream
extension-badge-tint
extension_badge_refactor
extension_build_type_indicator
extension_embed_conflict
form-preact
frontend_build_refactor
frontend_tests_docs
gh2505-autolink
gh2553-fix_selection_filter
gh2561-highlight_text_revert
gh2563-test_timeout_debugging
gh2568-safari_missing_urls
gh2641-no_reload_on_groups_list_change
gh2642-reload_fouc
gh2646-bucket_bar_pos
gh2654-safari_search_expander
gh2663-do_not_switch_group_on_leave
groups_changed_exception
groups_list_selection_fix
groups
icon_font_fix
icon_font_fix_2
improve-form-field-list-design
index-and-search-elasticsearch6
index-authority
karma-watch-target
login-with-google
master
multitarget
nipsa
oauth-admin
oauth-login-prototype
p-frontend_build_refactor
p-fx-webextensions
p-new_highlighter
p-new_threading_impl
p-preact_annotation
p-preact_components
p-thread_model
parallel-reindex
publish-btn-cleanup
py3-docker
quote-anchor-integration-test
refactor-sidebar-injector-test
replace-angular_websocket
reply-count-badge
rob-setup_docs_corrections
search-bar-preact
search-bar-tidy-up
search-decaf
search-tests
server_sorted_groups
support-sqs
t87-editorconfig-sass
t87-group_scope_dropdown_ui
t88-remove_self_from_group
t89-combined_scope_save_btn
t89-group_list_css_refactor
t90-search_icon_click
t90-top_bar_new_design
t90-top_bar_refactor
t91-sort_dropdown_move_to_top_bar
t91-sort_dropdown_refactor
t93-clear_selection_btn_ux
t93-create_group_refresh
t105-group_push_notifications
t112-center_post_dropdown
t125-post_button_ui_fixes
t139-new_loading_indicator
t148-card_group_style_refactor
t152-move_unsaved_annot_to_current_group
t182-chrome_perms_refactor
t187-new_homepage_design
thread-collapsing
travis-flake8
v0.2.x
v0.3.x
visual-truncation
websocket-send-on-reconnect
z-login-with-google
z-py3-working-docker-img
z-py3
z-search-tests
Nothing to show
  • 1 commit
  • 6 files changed
  • 0 commit comments
  • 1 contributor
Commits on Nov 06, 2015
Replace angular-websocket with a minimal WebSocket wrapper
We used to have our own WebSocket wrapper, which was replaced in
cf410d0 with angular-websocket,
since that provided the functionality that we needed on top
of WebSockets, namely:

 * Integration with Angular $scope by wrapping onmessage callbacks
   with $scope.$apply()

 * Automatic reconnection in the event of a disconnection

 * Queuing of messages sent via

Unfortunately, angular-websocket also brings in a somewhat unnecessary
dependency on the "ws" module. That got out of date and resulted in no longer
being able to build H with the current version of Node.
See gdi2290/angular-websocket#47

This gave us either the choice of fixing angular-websocket or
replacing the dependency.

This commit removes the angular-websocket dependency in favor of
our own WebSocket wrapper, but unlike before, we outsource the retry logic
to the "retry" package. A benefit of this is that we get better logging
of what is going on during automated reconnects.
@@ -1,7 +1,6 @@
require('autofill-event')
baseURI = require('document-base-uri')
angular = require('angular')
require('angular-websocket')
require('angular-jwt')
streamer = require('./streamer')
@@ -93,7 +92,6 @@ module.exports = angular.module('h', [
'ngRoute'
'ngSanitize'
'ngTagsInput'
'ngWebSocket'
'toastr'
'ui.bootstrap'
])
@@ -1,5 +1,7 @@
var uuid = require('node-uuid')
var Socket = require('./websocket');
// the randomly generated session UUID
var clientId = uuid.v4();
@@ -12,7 +14,7 @@ var socket;
* Only one websocket connection may exist at a time, any existing socket is
* closed.
*
* @param $websocket - angular-websocket constructor
* @param $rootScope
* @param annotationMapper - The local annotation store
* @param groups - The local groups store
* @param session - Provides access to read and update the session state
@@ -21,7 +23,7 @@ var socket;
* @return An angular-websocket wrapper around the socket.
*/
// @ngInject
function connect($websocket, annotationMapper, groups, session, settings) {
function connect($rootScope, annotationMapper, groups, session, settings) {
// Get the socket URL
var url = settings.websocketUrl;
@@ -31,9 +33,7 @@ function connect($websocket, annotationMapper, groups, session, settings) {
}
// Open the socket
socket = $websocket(url, [], {
reconnectIfNotNormalClose: true
});
socket = new Socket(url);
socket.send({
messageType: 'client_id',
value: clientId
@@ -70,20 +70,28 @@ function connect($websocket, annotationMapper, groups, session, settings) {
session.update(message.model);
}
// Listen for updates
socket.onMessage(function (event) {
message = JSON.parse(event.data);
if (!message) {
return;
}
socket.on('error', function (error) {
console.warn('Error connecting to H push notification service:', error);
});
if (message.type === 'annotation-notification') {
handleAnnotationNotification(message)
} else if (message.type === 'session-change') {
handleSessionChangeNotification(message)
} else {
console.warn('received unsupported notification', message.type)
}
socket.on('message', function (event) {
// wrap message dispatches in $rootScope.$apply() so that
// scope watches on app state affected by the received message
// are updated
$rootScope.$apply(function () {
message = JSON.parse(event.data);
if (!message) {
return;
}
if (message.type === 'annotation-notification') {
handleAnnotationNotification(message)
} else if (message.type === 'session-change') {
handleSessionChangeNotification(message)
} else {
console.warn('received unsupported notification', message.type)
}
});
});
return socket
@@ -1,43 +1,43 @@
'use strict';
var streamer = require('../streamer');
function fakeSocketConstructor(url) {
return {
messages: [],
onMessageCallbacks: [],
didClose: false,
send: function (message) {
this.messages.push(message);
},
onMessage: function (callback) {
this.onMessageCallbacks.push(callback);
},
notify: function (message) {
this.onMessageCallbacks.forEach(function (callback) {
callback({
data: JSON.stringify(message)
});
});
},
var EventEmitter = require('events');
var util = require('util');
var proxyquire = require('proxyquire');
function FakeSocket(url) {
this.messages = [];
this.didClose = false;
close: function () {
this.didClose = true
}
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
};
}
util.inherits(FakeSocket, EventEmitter);
describe('streamer', function () {
var fakeAnnotationMapper;
var fakeGroups;
var fakeSession;
var fakeSettings;
var socket;
var streamer;
beforeEach(function () {
fakeRootScope = {
$apply: function (callback) {
callback();
}
};
fakeAnnotationMapper = {
loadAnnotations: sinon.stub(),
unloadAnnotations: sinon.stub(),
@@ -57,8 +57,12 @@ describe('streamer', function () {
websocketUrl: 'ws://example.com/ws',
};
streamer = proxyquire('../streamer', {
'./websocket': FakeSocket,
});
socket = streamer.connect(
fakeSocketConstructor,
fakeRootScope,
fakeAnnotationMapper,
fakeGroups,
fakeSession,
@@ -74,7 +78,8 @@ describe('streamer', function () {
it('should close any existing socket', function () {
var oldSocket = socket;
var newSocket = streamer.connect(fakeSocketConstructor,
var newSocket = streamer.connect(
fakeRootScope,
fakeAnnotationMapper,
fakeGroups,
fakeSession,
@@ -0,0 +1,59 @@
var Socket = require('../websocket');
describe('websocket wrapper', function () {
var fakeSocket;
var clock;
function FakeWebSocket() {
this.close = sinon.stub();
this.send = sinon.stub();
fakeSocket = this;
};
FakeWebSocket.OPEN = 1;
var WebSocket = window.WebSocket;
beforeEach(function () {
global.WebSocket = FakeWebSocket;
clock = sinon.useFakeTimers();
});
afterEach(function () {
global.WebSocket = WebSocket;
clock.restore();
});
it('should reconnect after an abnormal disconnection', function () {
var socket = new Socket('ws://test:1234');
assert.ok(fakeSocket);
var initialSocket = fakeSocket;
fakeSocket.onclose({code: 1006});
clock.tick(1000);
assert.ok(fakeSocket);
assert.notEqual(fakeSocket, initialSocket);
});
it('should not reconnect after a normal disconnection', function () {
var socket = new Socket('ws://test:1234');
socket.close();
assert.called(fakeSocket.close);
var initialSocket = fakeSocket;
clock.tick(1000);
assert.equal(fakeSocket, initialSocket);
});
it('should queue messages sent prior to connection', function () {
var socket = new Socket('ws://test:1234');
socket.send({abc: 'foo'});
assert.notCalled(fakeSocket.send);
fakeSocket.onopen({});
assert.calledWith(fakeSocket.send, '{"abc":"foo"}');
});
it('should send messages immediately when connected', function () {
var socket = new Socket('ws://test:1234');
fakeSocket.readyState = FakeWebSocket.OPEN;
socket.send({abc: 'foo'});
assert.calledWith(fakeSocket.send, '{"abc":"foo"}');
});
});
@@ -0,0 +1,99 @@
'use strict';
var retry = require('retry');
var util = require('util');
var EventEmitter = require('events');
// see https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
var CLOSE_NORMAL = 1000;
/**
* Socket is a minimal wrapper around WebSocket which provides:
*
* - Automatic reconnection in the event of an abnormal close
* - Queuing of messages passed to send() whilst the socket is
* connecting
* - Uses the standard EventEmitter API for reporting open, close, error
* and message events.
*/
function Socket(url) {
var self = this;
// queue of JSON objects which have not yet been submitted
var messageQueue = [];
// the current WebSocket instance or null if disconnected
var socket;
function sendMessages() {
while (messageQueue.length > 0) {
var messageString = JSON.stringify(messageQueue.shift());
socket.send(messageString);
}
}
function reconnect() {
var didConnect = false;
var connectOperation = retry.operation();
connectOperation.attempt(function (currentAttempt) {
socket = new WebSocket(url);
socket.onopen = function (event) {
// signal successful connection
connectOperation.retry();
didConnect = true;
sendMessages();
self.emit('open', event);
};
socket.onclose = function (event) {
if (event.code !== CLOSE_NORMAL) {
if (didConnect) {
console.warn('The WebSocket connection closed abnormally ' +
'(code: %d, reason: %s). Reconnecting automatically.',
event.code, event.reason);
reconnect();
} else {
console.warn('Retrying connection (attempt %d)', currentAttempt);
connectOperation.retry(new Error(event.reason));
}
}
socket = null;
};
socket.onerror = function (event) {
self.emit('error', event);
};
socket.onmessage = function (event) {
self.emit('message', event);
};
});
};
this.close = function () {
if (!socket) {
console.error('Socket.close() called before socket was connected');
return;
}
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 (socket && socket.readyState === WebSocket.OPEN) {
sendMessages();
}
};
// establish the initial connection
reconnect();
}
util.inherits(Socket, EventEmitter);
module.exports = Socket;
View
@@ -12,7 +12,6 @@
"angular-route": "1.4.7",
"angular-sanitize": "1.4.7",
"angular-toastr": "^1.5.0",
"angular-websocket": "^1.0.13",
"angulartics": "0.17.2",
"autofill-event": "0.0.1",
"autoprefixer": "^6.0.3",
@@ -44,6 +43,7 @@
"node-uuid": "^1.4.3",
"postcss": "^5.0.6",
"raf": "^3.1.0",
"retry": "^0.8.0",
"scroll-into-view": "^1.3.1",
"showdown": "^1.2.1",
"uglify-js": "^2.4.14",

No commit comments for this range