Skip to content

Commit 907b882

Browse files
committed
feat: use rxjs to send messages within coordinator host #ADEN-9976
feat: use connector instead of host chore: change interface fix: fix native connector chore: fix coordinator spec chore: fix channel spec chore: check connector in channel spec chore: fix receiver spec chore: add connector factory spec chore: add native connector spec chore: add post message connector spec chore: remove coverage check from host.ts chore: update dependencies chore: change operators to functions chore: change lite transmitter constructor chore: fix @types/node version chore: export lite transmitter chore: fix setup chore: fix channel chore: add methods for sending and receiving messages chore: use methods for sending and receiving messages chore: create sender and listener chore: add listener/sender memoization chore: fix channel spec chore: fix receiver spec chore: remove connector chore: add isRxMode spec chore: handle not serializable messages chore: update setup spec chore: update transmitter spec chore: remove lite transmitter chore: remove experimental decorators
1 parent e328199 commit 907b882

38 files changed

+4775
-4597
lines changed

.lintstagedrc

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
{
22
"*.ts": [
33
"prettier --write",
4-
"tslint --fix",
5-
"git add"
4+
"tslint --fix"
65
],
76
"*.{js,json,css,scss,html}": [
8-
"prettier --write",
9-
"git add"
7+
"prettier --write"
108
]
119
}

jest.config.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,9 @@ module.exports = {
33
transform: {
44
'^.+\\.tsx?$': 'ts-jest',
55
},
6-
collectCoverageFrom: ['src/**/*.ts', '!src/**/*.mock.ts'],
6+
collectCoverageFrom: [
7+
'src/**/*.ts',
8+
'!src/**/*.stub.ts',
9+
'!src/models/host.ts', // there is only interface there, jest is incorrectly interpreting it
10+
],
711
};

package-lock.json

+4,103-4,298
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+22-22
Original file line numberDiff line numberDiff line change
@@ -44,35 +44,35 @@
4444
"semantic-release": "semantic-release"
4545
},
4646
"dependencies": {
47-
"rxjs": "^6.5.3"
47+
"rxjs": "^6.5.4"
4848
},
4949
"devDependencies": {
50-
"@commitlint/cli": "8.2.0",
51-
"@commitlint/config-conventional": "8.2.0",
52-
"@types/jest": "24.0.19",
53-
"@types/node": "12.12.17",
50+
"@commitlint/cli": "^8.3.5",
51+
"@commitlint/config-conventional": "^8.3.4",
52+
"@types/jest": "^25.1.4",
53+
"@types/node": "^12.12.6",
5454
"commitizen": "4.0.3",
55-
"coveralls": "3.0.7",
56-
"cz-conventional-changelog": "3.0.2",
57-
"husky": "3.0.9",
58-
"jest": "24.9.0",
59-
"lint-staged": "9.4.2",
60-
"prettier": "1.18.2",
61-
"rimraf": "3.0.0",
62-
"rollup": "1.25.1",
55+
"coveralls": "^3.0.9",
56+
"cz-conventional-changelog": "^3.1.0",
57+
"husky": "^4.2.3",
58+
"jest": "^25.1.0",
59+
"lint-staged": "^10.0.8",
60+
"prettier": "^1.19.1",
61+
"rimraf": "^3.0.2",
62+
"rollup": "^2.0.5",
6363
"rollup-plugin-commonjs": "10.1.0",
6464
"rollup-plugin-json": "4.0.0",
6565
"rollup-plugin-node-resolve": "5.2.0",
66-
"rollup-plugin-sourcemaps": "0.4.2",
67-
"rollup-plugin-typescript2": "0.24.3",
68-
"rollup-plugin-uglify": "6.0.3",
69-
"semantic-release": "15.13.27",
70-
"source-map-support": "0.5.13",
71-
"ts-jest": "24.1.0",
72-
"ts-node": "8.4.1",
73-
"tslint": "5.20.0",
66+
"rollup-plugin-sourcemaps": "^0.5.0",
67+
"rollup-plugin-typescript2": "^0.26.0",
68+
"rollup-plugin-uglify": "^6.0.4",
69+
"semantic-release": "^17.0.4",
70+
"source-map-support": "^0.5.16",
71+
"ts-jest": "^25.2.1",
72+
"ts-node": "^8.6.2",
73+
"tslint": "^6.1.0",
7474
"tslint-config-prettier": "1.18.0",
75-
"typescript": "3.6.4"
75+
"typescript": "^3.8.3"
7676
},
7777
"config": {
7878
"commitizen": {

src/communicator.spec.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Communicator } from './communicator';
2-
import { createHostMock, HostMock } from './models/host.mock';
2+
import { createHostStub, HostStub } from './models/host.stub';
33
import { DEFAULT_OPTIONS } from './models/options';
44
import { Receiver } from './receiver';
55
import { Transmitter } from './transmitter';
@@ -28,10 +28,10 @@ describe('Communicator', () => {
2828

2929
describe('Implementation', () => {
3030
let communicator: Communicator;
31-
let host: HostMock;
31+
let host: HostStub;
3232

3333
beforeEach(() => {
34-
host = createHostMock();
34+
host = createHostStub();
3535
communicator = new Communicator({ host, coordinatorHost: host });
3636
});
3737

src/communicator.ts

+8-10
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,17 @@ import { Receiver } from './receiver';
55
import { Transmitter } from './transmitter';
66

77
export class Communicator {
8-
actions$: Observable<Action>;
9-
private readonly options: PostQuecastOptions;
10-
private transmitter: Transmitter;
11-
private receiver: Receiver;
8+
readonly actions$: Observable<Action>;
9+
private readonly transmitter: Transmitter;
1210

13-
constructor(options: Partial<PostQuecastOptions> = {}) {
14-
this.options = {
11+
constructor(_options: Partial<PostQuecastOptions> = {}) {
12+
const options: PostQuecastOptions = {
1513
...DEFAULT_OPTIONS,
16-
...options,
14+
..._options,
1715
};
18-
this.transmitter = new Transmitter(this.options);
19-
this.receiver = new Receiver(this.options);
20-
this.actions$ = this.receiver.actions$;
16+
17+
this.transmitter = new Transmitter(options);
18+
this.actions$ = new Receiver(options).actions$;
2119
}
2220

2321
dispatch<T>(action: Action<T>): void {

src/connectors/is-rx-mode.spec.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { createHostStub } from '../models/host.stub';
2+
import { isRxMode } from './is-rx-mode';
3+
4+
describe('isRxMode', () => {
5+
const host1 = createHostStub();
6+
const host2 = createHostStub();
7+
8+
it('should return true', () => {
9+
expect(isRxMode({ host: host1, coordinatorHost: host1 }));
10+
});
11+
12+
it('should return false', () => {
13+
expect(isRxMode({ host: host1, coordinatorHost: host2 }));
14+
});
15+
});

src/connectors/is-rx-mode.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { LIB_SUBJECT } from '../models/constants';
2+
import { PostQuecastOptions } from '../models/options';
3+
4+
export function isRxMode(options: Pick<PostQuecastOptions, 'host' | 'coordinatorHost'>): boolean {
5+
return options.host === options.coordinatorHost && !!options.coordinatorHost[LIB_SUBJECT];
6+
}

src/connectors/listener.spec.ts

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { LIB_ID, LIB_SUBJECT } from '../models/constants';
2+
import { createHostStub, HostStub } from '../models/host.stub';
3+
import { PostMessageData } from '../models/post-message-data';
4+
import { PostMessageEvent } from '../models/post-message-event';
5+
import { Listener, NativeListener, RxListener } from './listener';
6+
7+
describe('Listener', () => {
8+
let host1: HostStub;
9+
let host2: HostStub;
10+
11+
beforeEach(() => {
12+
host1 = createHostStub();
13+
host2 = createHostStub();
14+
});
15+
16+
it('should create rx listener if the same host', () => {
17+
const spy = jest.spyOn(RxListener, 'make');
18+
const instance = Listener.make({ coordinatorHost: host1, host: host1 });
19+
20+
expect(instance instanceof RxListener).toBe(true);
21+
expect(spy).toHaveBeenCalledTimes(1);
22+
expect(spy).toHaveBeenCalledWith(host1);
23+
});
24+
25+
it('should create native listener different hosts', () => {
26+
const spy = jest.spyOn(NativeListener, 'make');
27+
const instance = Listener.make({ coordinatorHost: host1, host: host2 });
28+
29+
expect(instance instanceof NativeListener).toBe(true);
30+
expect(spy).toHaveBeenCalledTimes(1);
31+
expect(spy).toHaveBeenCalledWith(host2);
32+
});
33+
34+
describe('RxListener', () => {
35+
it('should receive messages from host subject', () => {
36+
const rxListener = RxListener.make(host1);
37+
const results: PostMessageEvent[] = [];
38+
39+
rxListener.messages$.subscribe(value => results.push(value));
40+
host1[LIB_SUBJECT].next(makeMessage('test1'));
41+
host1[LIB_SUBJECT].next(makeMessage('test2'));
42+
host1[LIB_SUBJECT].next('invalid' as any);
43+
44+
expect(results.length).toBe(2);
45+
expect(results.map(result => result.data.action.type)).toEqual(['test1', 'test2']);
46+
});
47+
});
48+
49+
describe('NativeListener', () => {
50+
it('should receive messages from host subject', () => {
51+
const nativeListener = NativeListener.make(host1);
52+
const results: PostMessageEvent[] = [];
53+
54+
nativeListener.messages$.subscribe(value => results.push(value));
55+
host1.postMessage(makeMessageData('test1'), '*');
56+
host1.postMessage(makeMessageData('test2'), '*');
57+
host1.postMessage('invalid', '*');
58+
59+
expect(results.length).toBe(2);
60+
expect(results.map(result => result.data.action.type)).toEqual(['test1', 'test2']);
61+
});
62+
});
63+
64+
function makeMessage(type: string): PostMessageEvent {
65+
return {
66+
data: makeMessageData(type),
67+
source: host1,
68+
};
69+
}
70+
71+
function makeMessageData(type: string): PostMessageData {
72+
return {
73+
action: { type, timestamp: 10 },
74+
channelId: 'default',
75+
libId: LIB_ID,
76+
private: true,
77+
};
78+
}
79+
});

src/connectors/listener.stub.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Subject } from 'rxjs';
2+
import { PostMessageEvent } from '../models/post-message-event';
3+
4+
export type ListenerStub = {
5+
messages$: Subject<PostMessageEvent>;
6+
};
7+
8+
export function createListenerStub(): ListenerStub {
9+
return {
10+
messages$: new Subject(),
11+
};
12+
}

src/connectors/listener.ts

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* tslint:disable:max-classes-per-file */
2+
import { fromEvent, Observable } from 'rxjs';
3+
import { LIB_SUBJECT } from '../models/constants';
4+
import { Host } from '../models/host';
5+
import { PostQuecastOptions } from '../models/options';
6+
import { PostMessageEvent } from '../models/post-message-event';
7+
import { onlyValidMessages } from '../rxjs/only-valid-messages';
8+
import { isRxMode } from './is-rx-mode';
9+
10+
export abstract class Listener {
11+
static make(options: Pick<PostQuecastOptions, 'host' | 'coordinatorHost'>): Listener {
12+
if (isRxMode(options)) {
13+
return RxListener.make(options.coordinatorHost);
14+
}
15+
16+
return NativeListener.make(options.host);
17+
}
18+
19+
messages$: Observable<PostMessageEvent>;
20+
}
21+
22+
export class RxListener implements Listener {
23+
static make(host: Host): RxListener {
24+
return new RxListener(host);
25+
}
26+
27+
readonly messages$: Observable<PostMessageEvent>;
28+
29+
private constructor(host: Host) {
30+
this.messages$ = host[LIB_SUBJECT].asObservable().pipe(onlyValidMessages());
31+
}
32+
}
33+
34+
export class NativeListener implements Listener {
35+
static make(host: Host): NativeListener {
36+
return new NativeListener(host);
37+
}
38+
39+
readonly messages$: Observable<PostMessageEvent>;
40+
41+
private constructor(host: Host) {
42+
this.messages$ = fromEvent(host, 'message').pipe(onlyValidMessages());
43+
}
44+
}

src/connectors/sender.spec.ts

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { LIB_ID, LIB_SUBJECT } from '../models/constants';
2+
import { createHostStub, HostStub } from '../models/host.stub';
3+
import { PostMessageData } from '../models/post-message-data';
4+
import { PostMessageEvent } from '../models/post-message-event';
5+
import { NativeSender, RxSender, Sender } from './sender';
6+
7+
describe('Sender', () => {
8+
let host1: HostStub;
9+
let host2: HostStub;
10+
11+
beforeEach(() => {
12+
host1 = createHostStub();
13+
host2 = createHostStub();
14+
});
15+
16+
it('should create RxSender if the same host', () => {
17+
const spy = jest.spyOn(RxSender, 'make');
18+
const instance = Sender.make({ coordinatorHost: host1, host: host1 });
19+
20+
expect(instance instanceof RxSender).toBe(true);
21+
expect(spy).toHaveBeenCalledTimes(1);
22+
expect(spy).toHaveBeenCalledWith(host1);
23+
});
24+
25+
it('should create NativeSender different hosts', () => {
26+
const spy = jest.spyOn(NativeSender, 'make');
27+
const instance = Sender.make({ coordinatorHost: host1, host: host2 });
28+
29+
expect(instance instanceof NativeSender).toBe(true);
30+
expect(spy).toHaveBeenCalledTimes(1);
31+
expect(spy).toHaveBeenCalledWith(host1);
32+
});
33+
34+
describe('RxSender', () => {
35+
it('should send messages through host', () => {
36+
const message1 = makeMessageData('test1');
37+
const message2 = makeMessageData('test2');
38+
const rxSender = RxSender.make(host1);
39+
const results: PostMessageEvent[] = [];
40+
41+
host1[LIB_SUBJECT].subscribe(value => results.push(value));
42+
43+
rxSender.postMessage(message1);
44+
rxSender.postMessage(message2);
45+
46+
expect(results.length).toBe(2);
47+
expect(results.map(result => result.data.action.type)).toEqual(['test1', 'test2']);
48+
expect(results[0].source).toBe(results[1].source);
49+
expect(results[0].source).toBe(host1);
50+
});
51+
});
52+
53+
describe('NativeSender', () => {
54+
it('should send messages through host', () => {
55+
const message1 = makeMessageData('test1');
56+
const message2 = makeMessageData('test2');
57+
const nativeSender = NativeSender.make(host1);
58+
59+
nativeSender.postMessage(message1);
60+
expect(host1.postMessage).toHaveBeenCalledTimes(1);
61+
expect(host1.postMessage).toHaveBeenCalledWith(message1, '*');
62+
63+
host1.postMessage.mockClear();
64+
65+
nativeSender.postMessage(message2);
66+
expect(host1.postMessage).toHaveBeenCalledTimes(1);
67+
expect(host1.postMessage).toHaveBeenCalledWith(message2, '*');
68+
});
69+
70+
it('should fail silently so that not serializable messages pass', () => {
71+
const nativeSender = NativeSender.make(host1);
72+
73+
host1.postMessage.mockImplementation(() => {
74+
throw new Error();
75+
});
76+
nativeSender.postMessage(makeMessageData('test'));
77+
});
78+
});
79+
80+
function makeMessageData(type: string): PostMessageData {
81+
return {
82+
action: { type, timestamp: 10 },
83+
channelId: 'default',
84+
libId: LIB_ID,
85+
private: true,
86+
};
87+
}
88+
});

src/connectors/sender.stub.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Sender } from './sender';
2+
3+
export type SenderStub = {
4+
[key in keyof Sender]: jest.SpyInstance & Sender[key];
5+
};
6+
7+
export function createSenderStub(): SenderStub {
8+
return {
9+
postMessage: jest.fn(),
10+
};
11+
}

0 commit comments

Comments
 (0)