Skip to content
Permalink
Browse files

Rework terminology and types within worker

Avoid overuse of generic ideas like events and messages.
  • Loading branch information...
lovett committed Mar 22, 2019
1 parent 551c3b0 commit f2189f47a545b9e0e43b926fcc69e0c9c9376c26
Showing with 169 additions and 142 deletions.
  1. +28 −11 app/services.ts
  2. +10 −0 package-lock.json
  3. +1 −0 package.json
  4. +1 −1 tsconfig.json
  5. +5 −0 worker/command.ts
  6. +1 −6 worker/events.ts
  7. +109 −0 worker/listener.ts
  8. +0 −93 worker/receiver.ts
  9. +3 −3 worker/{workermessage.ts → reply.ts}
  10. +7 −10 worker/types/worker.d.ts
  11. +4 −18 worker/worker.ts
@@ -1,6 +1,7 @@
import Message from './message';
import Store from './store';
import {WorkerCommand, WorkerEvent} from '../worker/events';
import {Command} from '../worker/command';
import {ReplyEvent} from '../worker/events';

const appServices = angular.module('appServices', []);

@@ -89,39 +90,55 @@ appServices.factory('PushClient', ['$rootScope', '$log', '$filter', 'MessageList
const now = $filter('date')(new Date(), 'mediumTime');
const reply = e.data;

if (reply.event === WorkerEvent.disconnected) {
if (reply.event === ReplyEvent.disconnected) {
$log.warn('Disconnected as of ' + now);
$rootScope.$broadcast('connection:change', 'disconnected');
}

if (reply.event === WorkerEvent.connected) {
if (reply.event === ReplyEvent.connected) {
$log.info('Connected as of ' + now);
$rootScope.$broadcast('connection:change', 'connected');
}

if (reply.event === WorkerEvent.dropped) {
if (reply.event === ReplyEvent.dropped) {
MessageList.drop(reply.retractions);
}

if (reply.event === WorkerEvent.add) {
if (reply.event === ReplyEvent.add) {
MessageList.add(reply.message);
}

if (reply.event === WorkerEvent.parsefail) {
if (reply.event === ReplyEvent.parsefail) {
$log.error('Failed to parse message');
}
});

return {
connect() {
pushWorker.postMessage({
action: WorkerCommand.connect,
agent: window.navigator.userAgent
});
// There is a problem with EventSource on Firefox for Android.
// The browser will crash if the page is reloaded or unloaded
// after the EventSource connection has been made. The exact
// nature of the problem is unknown; this is a
// workaround. Pretending the connection was successful allows
// the initial message fetch to continue working. Real-time
// updates are lost, but on a mobile screen this is not
// entirely terrible because usage is probably short-lived
// anyway. A long-running connection isn't happening on mobile
// the way it is on desktop.
const agent = window.navigator.userAgent;
const isAndroid = agent.indexOf('Android') > -1;
const isFirefox = agent.indexOf('Firefox') > -1;

let action = Command.connect;
if (isAndroid && isFirefox) {
action = Command.fauxconnect;
}

pushWorker.postMessage(action);
},

disconnect() {
pushWorker.postMessage({action: WorkerCommand.disconnect});
pushWorker.postMessage(Command.disconnect);
},
};
}]);

Some generated files are not rendered by default. Learn more.

@@ -56,6 +56,7 @@
"@types/sanitize-html": "1.18.2",
"@types/sequelize": "4.27.44",
"@types/serve-favicon": "2.2.30",
"@types/terser-webpack-plugin": "^1.2.1",
"@types/useragent": "2.1.1",
"@types/uuid": "3.4.4",
"@types/webpack": "4.4.26",
@@ -10,6 +10,6 @@
"sourceMap": true,
"target": "ES5",
"strict": true,
"noImplicitAny": false,
"noImplicitAny": false
}
}
@@ -0,0 +1,5 @@
export enum Command {
connect = 'CONNECT',
fauxconnect = 'FAUXCONNECT',
disconnect = 'DISCONNECT',
}
@@ -1,9 +1,4 @@
export enum WorkerCommand {
connect = 'CONNECT',
disconnect = 'DISCONNECT',
}

export enum WorkerEvent {
export enum ReplyEvent {
add = 'ADD',
connected = 'CONNECTED',
disconnected = 'DISCONNECTED',
@@ -0,0 +1,109 @@
import {Reply} from './reply';
import {Command} from './command';
import {ReplyEvent} from './events';

export class Listener {
private eventSource?: EventSource;

private reconnectTimer = 0;

public do(command: worker.Command) {
if (command === Command.connect) {
this.connect();
}

if (command === Command.fauxconnect) {
this.fauxconnect();
}

if (command === Command.disconnect) {
this.disconnect();
}
}

/**
* Simulate a successful connection
*
* This bypasses EventSource and is a workaround for Firefox for
* Android, as described in app/services.ts.
*/
private fauxconnect() {
const reply = new Reply(ReplyEvent.connected);
return reply.send();
}

/**
* Open a persistent connection to the server
*/
private connect() {
this.eventSource = new EventSource('push');

this.eventSource.addEventListener('connection', () => {
const reply = new Reply(ReplyEvent.connected);
reply.send();
return;
});

this.eventSource.onmessage = (e: MessageEvent) => {
const reply = this.buildReply(e.data);
reply.send();
return;
};

this.eventSource.onerror = (() => {
this.setReconnectTimer();
const reply = new Reply(ReplyEvent.disconnected);
reply.send();
return;
});

this.clearReconnectTimer();
}

/**
* Close a previously-opened EventSource connection
*/
private disconnect() {
if (this.eventSource === undefined) {
return;
}

this.eventSource.close();
}

private clearReconnectTimer() {
clearTimeout(this.reconnectTimer);
}

private setReconnectTimer() {
const self = this;
this.clearReconnectTimer();

this.reconnectTimer = setTimeout(() => {
self.reconnect();
}, 2000);
}

private reconnect() {
this.disconnect();
this.connect();
}

private buildReply(data: string): Reply {
let message: worker.Message;

try {
message = JSON.parse(data);
} catch (ex) {
return new Reply(ReplyEvent.parsefail);
}

if (message.retracted) {
const reply = new Reply(ReplyEvent.dropped);
reply.setRetractions(message.retracted);
return reply;
}

return new Reply(ReplyEvent.add, message);
}
}

This file was deleted.

@@ -1,9 +1,9 @@
import {WorkerEvent} from '../worker/events';
import {ReplyEvent} from './events';

export class WorkerMessage {
export class Reply {
private payload: worker.Reply;

constructor(event: WorkerEvent, message?: worker.Message) {
constructor(event: ReplyEvent, message?: worker.Message) {
this.payload = {event, message};
}

@@ -1,8 +1,11 @@
declare namespace worker {
interface Command {
action: 'CONNECT' | 'DISCONNECT';
agent?: string;
token?: string;
type Command = 'CONNECT' | 'FAUXCONNECT' | 'DISCONNECT';
type ReplyEvent = 'ADD' | 'CONNECTED' | 'DISCONNECTED' | 'DROPPED' | 'PARSEFAIL';

interface Reply {
event: ReplyEvent;
message?: Message;
retractions?: string[];
}

interface Message {
@@ -15,10 +18,4 @@ declare namespace worker {
received?: string;
retracted?: string[];
}

interface Reply {
event: 'ADD' | 'CONNECTED' | 'DISCONNECTED' | 'DROPPED' | 'PARSEFAIL';
message?: Message;
retractions?: string[];
}
}
@@ -1,21 +1,7 @@
import {Receiver} from './receiver';
import {WorkerCommand} from './events';
import {Listener} from './listener';

const listener = new Listener();

let receiver: Receiver;

self.addEventListener('message', (e: Event) => {
const command: worker.Command = (e as MessageEvent).data;

if (!receiver) {
receiver = new Receiver();
}

if (command.action === WorkerCommand.connect) {
receiver.connect(command.agent);
}

if (command.action === WorkerCommand.disconnect) {
receiver.disconnect();
}
self.addEventListener('message', (e: MessageEvent) => {
listener.do(e.data);
});

0 comments on commit f2189f4

Please sign in to comment.
You can’t perform that action at this time.