Skip to content

Commit 3f70617

Browse files
authored
Merge pull request #18 from hamed-musallam/main
implement window observable and custom event
2 parents 40a5ede + 9848d46 commit 3f70617

8 files changed

Lines changed: 340 additions & 220 deletions

File tree

package-lock.json

Lines changed: 205 additions & 195 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"license": "MIT",
66
"dependencies": {
77
"@emotion/react": "^11.8.2",
8-
"nmrium": "^0.25.0"
8+
"nmrium": "^0.26.0"
99
},
1010
"main": "lib-cjs/NMRiumWrapper.js",
1111
"module": "lib/NMRiumWrapper.js",

src/NMRiumWrapper.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import NMRium, { NMRiumData } from 'nmrium';
44
import Button from 'nmrium/lib/component/elements/Button';
55
import { useEffect, useState } from 'react';
66
import events from './events';
7+
import observableEvents from './observables';
78
import useActions from './hooks/useActions';
89

910
const styles = {
@@ -16,6 +17,7 @@ const styles = {
1617
height: 40px;
1718
width: 100%;
1819
padding: 5px;
20+
display: flex;
1921
`,
2022
wrapper: css`
2123
flex: 1;
@@ -40,20 +42,41 @@ export default function NMRiumWrapper() {
4042
const actionHandler = useActions();
4143

4244
useEffect(() => {
43-
events.on('load', (_data) => {
45+
const unsubscribe = observableEvents.subscribe('load', (_data) => {
46+
// eslint-disable-next-line no-console
47+
console.log('test load data with subscribe');
4448
setDate(_data);
4549
});
50+
51+
const clearListener = events.on('load', (_data) => {
52+
// eslint-disable-next-line no-console
53+
console.log('test load data with custom event');
54+
setDate(_data);
55+
});
56+
57+
return () => {
58+
clearListener();
59+
unsubscribe();
60+
};
4661
});
4762

4863
return (
4964
<div css={styles.container}>
5065
<div css={styles.header}>
5166
<Button.Done
67+
style={{ margin: '0 10px' }}
5268
onClick={() => {
5369
events.trigger('load', testData);
5470
}}
5571
>
56-
Test Load from external URL
72+
Trigger load custom event
73+
</Button.Done>
74+
<Button.Done
75+
onClick={() => {
76+
observableEvents.trigger('load', testData);
77+
}}
78+
>
79+
Test Load observable
5780
</Button.Done>
5881
</div>
5982

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { NMRiumData } from 'nmrium';
22

3-
type Actions = 'load' | 'test';
3+
type EventType = 'load' | 'test';
44

55
interface TestData {
66
testData: string;
77
}
88

9-
type EventData<T extends Actions> = T extends 'load'
9+
type EventData<T extends EventType> = T extends 'load'
1010
? NMRiumData
1111
: T extends 'test'
1212
? TestData
1313
: never;
14-
export type { Actions, EventData };
14+
export type { EventType, EventData };

src/events/event.ts

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,49 @@
1-
import { Actions, EventData } from './types';
1+
import { EventType, EventData } from '../actions/types';
2+
3+
const ALLOWED_ORIGINS: string[] =
4+
process.env.NODE_ENV === 'development'
5+
? ['http://localhost:3000']
6+
: ['https://nmrxiv.org'];
27

38
const namespace = 'nmr-wrapper';
49

5-
function trigger<T extends Actions>(action: T, data: EventData<T>) {
6-
const event = new CustomEvent(`${namespace}:${action}`, {
7-
detail: data,
8-
});
9-
window.dispatchEvent(event);
10+
function parseOrigin(origin: string) {
11+
const urlSegments = origin.split('://');
12+
const hostSegments = urlSegments[1].split('.');
13+
14+
const url = `${urlSegments[0]}://${hostSegments
15+
.slice(hostSegments.length > 1 ? 1 : 0)
16+
.join('.')}`;
17+
18+
return url;
19+
}
20+
21+
function trigger<T extends EventType>(type: T, data: EventData<T>) {
22+
window.postMessage({ type: `${namespace}:${type}`, data });
1023
}
1124

12-
function on<T extends Actions>(
13-
action: T,
14-
dataListener: (data: EventData<T>, event?: Event) => void,
25+
function on<T extends EventType>(
26+
type: T,
27+
dataListener: (data: EventData<T>) => void,
1528
options?: boolean | AddEventListenerOptions,
1629
) {
17-
function listener(object: Event) {
18-
const { detail, ...event } = object as CustomEvent;
19-
dataListener?.(detail, event);
30+
function listener(event: MessageEvent) {
31+
const {
32+
origin,
33+
data: { type: targetType, data },
34+
} = event;
35+
36+
if (!ALLOWED_ORIGINS.includes(parseOrigin(origin))) {
37+
throw new Error(`Invalid Origin ${origin}`);
38+
}
39+
40+
if (`${namespace}:${type}` === targetType) {
41+
dataListener?.(data);
42+
}
2043
}
21-
window.addEventListener(`${namespace}:${action}`, listener, options);
22-
}
44+
window.addEventListener(`message`, listener, options);
2345

24-
function clean(action: Actions, listener: (object: Event) => void) {
25-
window.removeEventListener(`${namespace}:${action}`, listener);
46+
return () => window.removeEventListener(`message`, listener);
2647
}
2748

28-
export default { trigger, on, clean };
49+
export default { trigger, on };

src/events/index.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { Actions } from './types';
21
import event from './event';
32

43
export default event;
5-
export type { Actions };

src/observables/index.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { Subject, Observer } from './subject';
2+
import { EventData, EventType } from '../actions/types';
3+
4+
const NAMESPACE = '__nmrium_events__';
5+
6+
function initSubject(event: EventType) {
7+
if (!window?.[NAMESPACE]?.[event]) {
8+
window[NAMESPACE] = {
9+
...window[NAMESPACE],
10+
[event]: new Subject(),
11+
};
12+
}
13+
}
14+
15+
function trigger<T extends EventType>(event: T, data: EventData<T>) {
16+
initSubject(event);
17+
window[NAMESPACE][event].notify(data);
18+
}
19+
20+
function unsubscribe(event: EventType, observerID: string) {
21+
return () => {
22+
if (window?.[NAMESPACE]?.[event]) {
23+
window[NAMESPACE][event].unsubscribe(observerID);
24+
} else {
25+
throw new Error('No event Registered');
26+
}
27+
};
28+
}
29+
30+
function subscribe<T extends EventType>(
31+
event: T,
32+
onData: (data: EventData<T>) => void,
33+
) {
34+
initSubject(event);
35+
const id = Math.random().toString(36).slice(2, 9);
36+
37+
const observer: Observer = { id, update: onData } as Observer;
38+
39+
(window[NAMESPACE][event] as Subject).subscribe(observer);
40+
41+
return unsubscribe(event, id);
42+
}
43+
44+
export default { trigger, subscribe };

src/observables/subject.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
export interface Observer {
2+
readonly id: string;
3+
update: (data: unknown) => void;
4+
}
5+
6+
export class Subject {
7+
private observers: Observer[] = [];
8+
9+
subscribe(observer: Observer) {
10+
this.observers.push(observer);
11+
}
12+
13+
unsubscribe(observerID: string) {
14+
this.observers = this.observers.filter((_observer) => {
15+
return _observer.id !== observerID;
16+
});
17+
}
18+
19+
notify(data: unknown) {
20+
this.observers.forEach((observer) => {
21+
observer.update(data);
22+
});
23+
}
24+
}

0 commit comments

Comments
 (0)