-
Notifications
You must be signed in to change notification settings - Fork 3
/
BrowserIframeStorage.ts
108 lines (95 loc) · 3.16 KB
/
BrowserIframeStorage.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import { AbstractStorage } from './AbstractStorage';
/**
* @category Storage
* @description Class representing browser iframe storage
*/
export class BrowserIframeStorage extends AbstractStorage {
private iframe: HTMLIFrameElement | null = null;
private readonly iframeOrigin: string;
private iframeReadyPromiseResolve: null | ((result: boolean) => void) = null;
private readonly reqs: Record<string, (result?: any) => void> = {};
constructor(private readonly iframeUrl: string = 'https://ks.ylide.io/', private readonly timeout: number = 5000) {
super();
this.iframeOrigin = new URL(this.iframeUrl).origin;
}
private op<T = any>(type: string, data: any, objectToTransfer?: Transferable | null) {
return new Promise<T>(resolve => {
const reqId = String(Math.random() * 100000000) + '.' + String(Math.random() * 100000000);
this.reqs[reqId] = resolve;
this.sendMessage(type, { reqId, ...data }, objectToTransfer);
});
}
private sendMessage(type: string, data?: any, objectToTransfer?: Transferable | null) {
if (!this.iframe || !this.iframe.contentWindow) {
return;
}
this.iframe.contentWindow.postMessage(
{
type,
data,
},
this.iframeOrigin,
objectToTransfer ? [objectToTransfer] : [],
);
}
private handleMessage(msg: { type: string; data: any }) {
if (msg.type === 'handshake-start') {
this.sendMessage('handshake-bond');
} else if (msg.type === 'handshake-success') {
if (this.iframeReadyPromiseResolve) {
this.iframeReadyPromiseResolve(true);
}
} else if (msg.type === 'handshake-failed') {
if (this.iframeReadyPromiseResolve) {
this.iframeReadyPromiseResolve(false);
}
} else if (msg.type === 'op-done') {
const { reqId, result } = msg.data;
if (this.reqs[reqId]) {
this.reqs[reqId](result);
}
}
}
async init() {
return new Promise<boolean>(resolve => {
const timeoutTimer = setTimeout(() => {
return resolve(false);
}, this.timeout);
window.addEventListener('message', ev => {
if (ev.origin === this.iframeOrigin && ev.source === this.iframe?.contentWindow) {
this.handleMessage(ev.data);
}
});
this.iframe = document.createElement('iframe');
this.iframe.src = this.iframeUrl;
this.iframe.style.width = '0px';
this.iframe.style.height = '0px';
this.iframe.style.display = 'none';
this.iframe.style.pointerEvents = 'none';
this.iframe.style.position = 'absolute';
this.iframe.style.left = '0px';
this.iframe.style.top = '0px';
this.iframe.style.opacity = '0';
document.body.appendChild(this.iframe);
this.iframeReadyPromiseResolve = result => {
clearTimeout(timeoutTimer);
resolve(result);
};
});
}
async storeBytes(key: string, bytes: Uint8Array): Promise<boolean> {
return this.op<boolean>('storeBytes', { key, bytes }, bytes.buffer);
}
async readBytes(key: string): Promise<Uint8Array | null> {
return this.op<Uint8Array | null>('readBytes', { key });
}
async clear(): Promise<boolean> {
return this.op<boolean>('clear', {});
}
async getKeys(): Promise<string[]> {
return this.op<string[]>('getKeys', {});
}
async delete(key: string): Promise<boolean> {
return this.op<boolean>('delete', { key });
}
}