Skip to content

Commit

Permalink
Merge pull request #1301 from RoundingWell/websocket
Browse files Browse the repository at this point in the history
Add Websocket service
  • Loading branch information
paulfalgout authored Aug 22, 2024
2 parents f066155 + 524e2e5 commit 65b9968
Show file tree
Hide file tree
Showing 10 changed files with 340 additions and 0 deletions.
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@
"globals": "^15.6.0",
"istanbul-lib-coverage": "^3.0.2",
"lodash.camelcase": "^4.3.0",
"mock-socket": "^9.3.1",
"nyc": "^17.0.0",
"postcss": "^8.3.6",
"rimraf": "^5.0.1",
Expand Down
2 changes: 2 additions & 0 deletions src/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import BootstrapService from 'js/services/bootstrap';
import HistoryService from 'js/services/history';
import LastestListService from 'js/services/latest-list';
import ModalService from 'js/services/modal';
import WSService from 'js/services/ws';

import ErrorApp from 'js/apps/globals/error_app';

Expand Down Expand Up @@ -72,6 +73,7 @@ const Application = App.extend({
},

startServices() {
new WSService({ url: appConfig.ws });
new AlertService({ region: this.getRegion('alert') });
new LastestListService();
new ModalService({
Expand Down
1 change: 1 addition & 0 deletions src/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ document.addEventListener('DOMContentLoaded', () => {
versions.frontend = 'cypress';
appConfig.name = 'Cypress Clinic';
appConfig.cypress = 'cypress';
appConfig.ws = 'ws://cypress-websocket/ws';

if (location.pathname === '/logout') return;

Expand Down
149 changes: 149 additions & 0 deletions src/js/services/ws.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import Radio from 'backbone.radio';

import WSService from './ws';

context('WS Service', function() {
let service;

before(function() {
Radio.reply('auth', 'getToken', () => 'token');
});

beforeEach(function() {
const url = 'ws://cypress-websocket/ws';
cy.mockWs(url);
service = new WSService({ url });
});

afterEach(function() {
service.destroy();
});

specify('ws url not configured', function() {
service.destroy();

service = new WSService({ url: null });
const channel = Radio.channel('ws');

channel.request('subscribe', { id: 'foo', type: 'bar' });

expect(service.isRunning()).to.be.false;
});

specify('ws error', function() {
cy.spy(console, 'error').as('consoleError');

// Start the service to initialize WebSocket and listeners
service.start();

cy.errorWs();

cy.get('@consoleError').should('have.been.calledOnce');
});

specify('Constructing the websocket', function() {
const channel = Radio.channel('ws');

cy.interceptWs('SendTest').as('SendTestWs');

channel.request('send', { name: 'SendTest', data: 'NOTCONNECTED' });

cy
.get('@SendTestWs')
.should('equal', 'NOTCONNECTED');

cy.interceptWs('SendTest').as('SendTestWs');

channel.request('send', { name: 'SendTest', data: 'CONNECTING' });

cy
.get('@SendTestWs')
.should('equal', 'CONNECTING');

expect(service.isRunning()).to.be.true;

cy.interceptWs('SendTest').as('SendTestWs');

service.ws.onopen = () => {
channel.request('send', { name: 'SendTest', data: 'OPEN' });
};

cy
.get('@SendTestWs')
.should('equal', 'OPEN');
});

specify('Subscribing', function() {
const notifications = [
{ id: 'foo', type: 'bar' },
{ id: 'foo2', type: 'bar2' },
{ id: 'foo3', type: 'bar3' },
{ id: 'foo4', type: 'bar4' },
];

const channel = Radio.channel('ws');

cy.interceptWs('Subscribe').as('SubscribeWs');

channel.request('subscribe', notifications[0]);

cy
.get('@SubscribeWs')
.should('deep.equal', { resources: [notifications[0]] });

cy.interceptWs('Subscribe').as('Subscribe2Ws');

channel.request('subscribe:persist', notifications[1]);

cy
.get('@Subscribe2Ws')
.should('deep.equal', { resources: [notifications[0], notifications[1]] });

cy.interceptWs('Subscribe').as('Subscribe3Ws');

channel.request('subscribe', [notifications[2]]);

cy
.get('@Subscribe3Ws')
.should('deep.equal', { resources: [notifications[2], notifications[1]] });

cy.interceptWs('Subscribe').as('Subscribe4Ws');

channel.request('subscribe:persist', [notifications[3]]);

cy
.get('@Subscribe4Ws')
.should('deep.equal', { resources: [notifications[2], notifications[1], notifications[3]] });

cy.interceptWs('Subscribe').as('UnsubscribeWs');

channel.request('unsubscribe', notifications[1]);

cy
.get('@UnsubscribeWs')
.should('deep.equal', { resources: [notifications[2], notifications[3]] });

cy.interceptWs('Subscribe').as('Unsubscribe2Ws');

channel.request('unsubscribe', [notifications[3]]);

cy
.get('@Unsubscribe2Ws')
.should('deep.equal', { resources: [notifications[2]] });
});

specify('Message handling', function() {
const channel = Radio.channel('ws');

const handler = cy.stub();

service.listenTo(channel, 'message', handler);

channel.request('subscribe', {});
cy.sendWs({ id: 'foo', category: 'Test' });

cy.then(() => {
expect(handler).to.be.calledWith({ id: 'foo', category: 'Test' });
});
});
});
94 changes: 94 additions & 0 deletions src/js/services/ws.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { each, values, isArray } from 'underscore';
import Backbone from 'backbone';
import Radio from 'backbone.radio';

import App from 'js/base/app';

export default App.extend({
channelName: 'ws',

radioRequests: {
'send': 'send',
'subscribe': 'subscribe',
'subscribe:persist': 'subscribePersist',
'unsubscribe': 'unsubscribe',
},

initialize({ url }) {
this.resources = new Backbone.Collection();
this.persistent = {};
this.ws = {};
this.url = url;
},

beforeStart() {
Radio.request('auth', 'getToken');
},

onStart({ data }, token) {
this.ws = new WebSocket(this.url, token);
this.ws.addEventListener('open', () => this.ws.send(data));
this.ws.addEventListener('message', this._onMessage.bind(this));
this.ws.addEventListener('error', this._onError.bind(this));
},

_subscribe() {
this.send({ name: 'Subscribe', data: { resources: this.resources.toJSON() } });
},

send(data) {
if (!this.url) return;

data = JSON.stringify(data);

if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(data);
return;
}

if (this.ws.readyState !== WebSocket.CONNECTING) {
this.start({ data });
return;
}

this.ws.addEventListener('open', () => this.ws.send(data));
},

// TODO: handle message
_onMessage(event) {
const channel = this.getChannel();

const data = JSON.parse(event.data);

channel.trigger('message', data);
},

_onError(event) {
// eslint-disable-next-line no-console
console.error(event);
},

subscribe(resources) {
this.resources.reset(resources);
this.resources.add(values(this.persistent));
this._subscribe();
},

subscribePersist(resources) {
resources = isArray(resources) ? resources : [resources];
each(resources, ({ id, type }) => {
this.persistent[id] = { id, type };
});
this.resources.add(resources);
this._subscribe();
},

unsubscribe(resources) {
resources = isArray(resources) ? resources : [resources];
each(resources, ({ id }) => {
delete this.persistent[id];
});
this.resources.remove(resources);
this._subscribe();
},
});
1 change: 1 addition & 0 deletions test/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ Cypress.Commands.overwrite('visit', (originalFn, url = '/', options = {}) => {

// pageLoadTimeout for visit is 60000ms
return cy
.mockWs('ws://cypress-websocket/ws')
.wrap(originalFn(url, options), { timeout: 60000 })
.wait(waits);
});
Expand Down
1 change: 1 addition & 0 deletions test/support/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
} from '@cypress/mount-utils';

import './coverage';
import './websockets';

import 'js/base/setup';
import $ from 'jquery';
Expand Down
1 change: 1 addition & 0 deletions test/support/e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import 'js/base/dayjs';
import './defaults';
import './commands';
import './coverage';
import './websockets';

import './api/actions';
import './api/clinicians';
Expand Down
Loading

0 comments on commit 65b9968

Please sign in to comment.