vigor-bridge 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +82 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +571 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +567 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +21 -0
- package/rollup.config.mjs +25 -0
- package/src/index.ts +672 -0
- package/tsconfig.json +25 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
type BridgeUtilCryptoResult = {
|
|
2
|
+
iv: Array<number>;
|
|
3
|
+
encrypted: Uint8Array<ArrayBuffer>;
|
|
4
|
+
};
|
|
5
|
+
type BridgeTokensType = {
|
|
6
|
+
validation: {
|
|
7
|
+
request: string;
|
|
8
|
+
receive: string;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
type BridgeTokens = {
|
|
12
|
+
validation: string;
|
|
13
|
+
bridge: string;
|
|
14
|
+
testFn: string;
|
|
15
|
+
testResult: string;
|
|
16
|
+
portValidation: {
|
|
17
|
+
host: string;
|
|
18
|
+
remote: string;
|
|
19
|
+
};
|
|
20
|
+
directValidation: string;
|
|
21
|
+
};
|
|
22
|
+
type BridgeChannelListenerId = symbol & {
|
|
23
|
+
__brand__: 'Bridge Listener Id';
|
|
24
|
+
};
|
|
25
|
+
type BridgeChannelContent = any;
|
|
26
|
+
type BridgeValidationWrapper = (getEncrypted: (random: string) => BridgeUtilCryptoResult | Promise<BridgeUtilCryptoResult>) => Promise<(<T>(content: BridgeChannelContent) => any)>;
|
|
27
|
+
declare class BridgeDirect {
|
|
28
|
+
private listeners;
|
|
29
|
+
private host;
|
|
30
|
+
private readonly validation;
|
|
31
|
+
private caches;
|
|
32
|
+
constructor(listeners: Map<BridgeChannelListenerId, {
|
|
33
|
+
action: string;
|
|
34
|
+
callback: BridgeValidationWrapper;
|
|
35
|
+
type: string;
|
|
36
|
+
host: boolean;
|
|
37
|
+
}>, host: boolean, validation: string);
|
|
38
|
+
addEventListener(action: string, callback: (content: BridgeChannelContent) => void): {
|
|
39
|
+
id: BridgeChannelListenerId;
|
|
40
|
+
removeEventListener: () => boolean;
|
|
41
|
+
};
|
|
42
|
+
removeEventListener(listenerId: BridgeChannelListenerId): boolean;
|
|
43
|
+
postMessage(action: string, content: BridgeChannelContent): void;
|
|
44
|
+
handle(action: string, callback: <T>(content: BridgeChannelContent) => T | Promise<T>): void;
|
|
45
|
+
request(action: string, content: BridgeChannelContent): Promise<unknown>;
|
|
46
|
+
}
|
|
47
|
+
declare class BridgeChannel {
|
|
48
|
+
private readonly port;
|
|
49
|
+
private listeners;
|
|
50
|
+
private caches;
|
|
51
|
+
constructor(port: MessagePort);
|
|
52
|
+
addEventListener(action: string, callback: (content: BridgeChannelContent) => void): {
|
|
53
|
+
id: BridgeChannelListenerId;
|
|
54
|
+
removeEventListener: () => boolean;
|
|
55
|
+
};
|
|
56
|
+
removeEventListener(listenerId: BridgeChannelListenerId): boolean;
|
|
57
|
+
postMessage(action: string, content: BridgeChannelContent): void;
|
|
58
|
+
handle(action: string, callback: <T>(content: BridgeChannelContent) => T | Promise<T>): {
|
|
59
|
+
id: BridgeChannelListenerId;
|
|
60
|
+
removeEventListener: () => boolean;
|
|
61
|
+
};
|
|
62
|
+
request(action: string, content: BridgeChannelContent): Promise<unknown>;
|
|
63
|
+
static create(port: MessagePort, host: boolean, validation: {
|
|
64
|
+
host: string;
|
|
65
|
+
remote: string;
|
|
66
|
+
}): Promise<BridgeChannel>;
|
|
67
|
+
}
|
|
68
|
+
declare const BridgeEntry: {
|
|
69
|
+
create: () => {
|
|
70
|
+
certification: {
|
|
71
|
+
tokens: BridgeTokens;
|
|
72
|
+
type: BridgeTokensType;
|
|
73
|
+
};
|
|
74
|
+
bridge: Promise<BridgeDirect | BridgeChannel>;
|
|
75
|
+
};
|
|
76
|
+
connect: ({ tokens, type }: {
|
|
77
|
+
tokens: BridgeTokens;
|
|
78
|
+
type: BridgeTokensType;
|
|
79
|
+
}) => Promise<BridgeDirect | BridgeChannel>;
|
|
80
|
+
};
|
|
81
|
+
export default BridgeEntry;
|
|
82
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
const Bridge_Error_Messages = {
|
|
6
|
+
INVALID_TYPE: ({ expected, received }) => `Invalid Type: ${typeof received} (expected: ${expected.map(e => JSON.stringify(e)).join(', ')})`,
|
|
7
|
+
INVALID_VALIDATION: ({ expected, received }) => `Invalid Validation: ${typeof received} (expected: ${expected.join(', ')})`,
|
|
8
|
+
INVALID_PORT: ({ expected, received }) => `Invalid Type: ${typeof received} (expected: ${expected.map(e => JSON.stringify(e)).join(', ')})`,
|
|
9
|
+
MALICIOUS_DATA: ({ position, step }) => `Malicious Data Detected: ${position}`
|
|
10
|
+
};
|
|
11
|
+
class BridgeError extends Error {
|
|
12
|
+
code;
|
|
13
|
+
timestamp = new Date();
|
|
14
|
+
constructor(code, options) {
|
|
15
|
+
const messageFn = Bridge_Error_Messages[code];
|
|
16
|
+
const message = `[${code}] ${messageFn(options.data)}`;
|
|
17
|
+
super(message);
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.name = new.target.name;
|
|
20
|
+
Object.assign(this, options);
|
|
21
|
+
Object.setPrototypeOf(this, new.target.prototype)(Error).captureStackTrace?.(this, new.target);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
class BridgeConnectError extends BridgeError {
|
|
25
|
+
constructor(code, options) {
|
|
26
|
+
super(code, options);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
class BridgeDirectError extends BridgeError {
|
|
30
|
+
constructor(code, options) {
|
|
31
|
+
super(code, options);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
class BridgeChannelError extends BridgeError {
|
|
35
|
+
constructor(code, options) {
|
|
36
|
+
super(code, options);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
class BridgeUtilCrypto {
|
|
40
|
+
static generateRandomHex(length = 100) {
|
|
41
|
+
const byteLength = Math.ceil(length / 2);
|
|
42
|
+
const array = new Uint8Array(byteLength);
|
|
43
|
+
self.crypto.getRandomValues(array);
|
|
44
|
+
const hex = Array.from(array)
|
|
45
|
+
.map(byte => byte.toString(16).padStart(2, '0'))
|
|
46
|
+
.join('');
|
|
47
|
+
return hex.substring(0, length);
|
|
48
|
+
}
|
|
49
|
+
static aes = {
|
|
50
|
+
convertPassword: async (passwordRaw) => {
|
|
51
|
+
const encoder = new TextEncoder();
|
|
52
|
+
const password = encoder.encode(passwordRaw.padEnd(32, '0').substring(0, 32));
|
|
53
|
+
return await window.crypto.subtle.importKey("raw", password, { name: "AES-GCM" }, false, ["encrypt", "decrypt"]);
|
|
54
|
+
},
|
|
55
|
+
encrypt: async (text, passwordRaw) => {
|
|
56
|
+
const encoder = new TextEncoder();
|
|
57
|
+
const password = await BridgeUtilCrypto.aes.convertPassword(passwordRaw);
|
|
58
|
+
const data = encoder.encode(text);
|
|
59
|
+
const iv = window.crypto.getRandomValues(new Uint8Array(12));
|
|
60
|
+
const encryptedBuffer = await window.crypto.subtle.encrypt({ name: "AES-GCM", iv: iv }, password, data);
|
|
61
|
+
return {
|
|
62
|
+
iv: Array.from(iv),
|
|
63
|
+
encrypted: new Uint8Array(encryptedBuffer)
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
decrypt: async ({ iv: ivArray, encrypted }, passwordRaw) => {
|
|
67
|
+
const decoder = new TextDecoder();
|
|
68
|
+
const password = await BridgeUtilCrypto.aes.convertPassword(passwordRaw);
|
|
69
|
+
const iv = new Uint8Array(ivArray);
|
|
70
|
+
const decryptedBuffer = await window.crypto.subtle.decrypt({
|
|
71
|
+
name: "AES-GCM",
|
|
72
|
+
iv
|
|
73
|
+
}, password, encrypted);
|
|
74
|
+
return decoder.decode(decryptedBuffer);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
static hmac = {
|
|
78
|
+
convertPassword: async (passwordRaw) => {
|
|
79
|
+
const encoder = new TextEncoder();
|
|
80
|
+
const passwordBytes = encoder.encode(passwordRaw.padEnd(32, '0').substring(0, 32));
|
|
81
|
+
return await window.crypto.subtle.importKey("raw", passwordBytes, {
|
|
82
|
+
name: "HMAC",
|
|
83
|
+
hash: { name: "SHA-256" }
|
|
84
|
+
}, false, ["sign", "verify"]);
|
|
85
|
+
},
|
|
86
|
+
sign: async (text, passwordRaw) => {
|
|
87
|
+
const encoder = new TextEncoder();
|
|
88
|
+
const password = await BridgeUtilCrypto.hmac.convertPassword(passwordRaw);
|
|
89
|
+
const data = encoder.encode(text);
|
|
90
|
+
const encryptedBuffer = await window.crypto.subtle.sign("HMAC", password, data);
|
|
91
|
+
return new Uint8Array(encryptedBuffer);
|
|
92
|
+
},
|
|
93
|
+
verify: async (text, passwordRaw, signature) => {
|
|
94
|
+
const encoder = new TextEncoder();
|
|
95
|
+
const password = await BridgeUtilCrypto.hmac.convertPassword(passwordRaw);
|
|
96
|
+
const data = encoder.encode(text);
|
|
97
|
+
return await window.crypto.subtle.verify("HMAC", password, signature, data);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
class BridgeUtilListener {
|
|
102
|
+
static listenerMap = new Map();
|
|
103
|
+
static addEventListener(callback) {
|
|
104
|
+
const listenerId = Symbol("Listener");
|
|
105
|
+
window.addEventListener("message", callback);
|
|
106
|
+
this.listenerMap.set(listenerId, callback);
|
|
107
|
+
return {
|
|
108
|
+
id: listenerId,
|
|
109
|
+
removeEventListener: () => this.removeEventListener(listenerId)
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
static removeEventListener(listenerId) {
|
|
113
|
+
const callback = this.listenerMap.get(listenerId);
|
|
114
|
+
if (!callback)
|
|
115
|
+
return false;
|
|
116
|
+
window.removeEventListener("message", callback);
|
|
117
|
+
this.listenerMap.delete(listenerId);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
class BridgeDirect {
|
|
122
|
+
listeners;
|
|
123
|
+
host;
|
|
124
|
+
validation;
|
|
125
|
+
caches = [];
|
|
126
|
+
constructor(listeners, host, validation) {
|
|
127
|
+
this.listeners = listeners;
|
|
128
|
+
this.host = host;
|
|
129
|
+
this.validation = validation;
|
|
130
|
+
}
|
|
131
|
+
addEventListener(action, callback) {
|
|
132
|
+
const type = "normal";
|
|
133
|
+
for (const cache of this.caches) {
|
|
134
|
+
if (cache.action !== action || cache.type !== type)
|
|
135
|
+
continue;
|
|
136
|
+
callback(cache.content);
|
|
137
|
+
}
|
|
138
|
+
const listenerId = Symbol("LISTENER_NORMAL");
|
|
139
|
+
this.listeners.set(listenerId, {
|
|
140
|
+
action,
|
|
141
|
+
callback: async (getEncrypted) => {
|
|
142
|
+
const random = BridgeUtilCrypto.generateRandomHex();
|
|
143
|
+
const encryptedRandom = await getEncrypted(random);
|
|
144
|
+
const decryptedRandom = await BridgeUtilCrypto.aes.decrypt(encryptedRandom, this.validation);
|
|
145
|
+
if (decryptedRandom !== random)
|
|
146
|
+
throw new BridgeDirectError("MALICIOUS_DATA", {
|
|
147
|
+
data: { position: this.host ? "host" : "remote", step: 2 }
|
|
148
|
+
});
|
|
149
|
+
return callback;
|
|
150
|
+
},
|
|
151
|
+
type,
|
|
152
|
+
host: this.host
|
|
153
|
+
});
|
|
154
|
+
return {
|
|
155
|
+
id: listenerId,
|
|
156
|
+
removeEventListener: () => this.removeEventListener(listenerId)
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
removeEventListener(listenerId) {
|
|
160
|
+
return this.listeners.delete(listenerId);
|
|
161
|
+
}
|
|
162
|
+
postMessage(action, content) {
|
|
163
|
+
const type = "normal";
|
|
164
|
+
const targets = Array.from(this.listeners.values()).filter(listener => listener.action === action && listener.type === type && listener.host !== this.host);
|
|
165
|
+
const promises = targets.map(async (listener) => {
|
|
166
|
+
const callback = await listener.callback(async (random) => {
|
|
167
|
+
return await BridgeUtilCrypto.aes.encrypt(random, this.validation);
|
|
168
|
+
});
|
|
169
|
+
if (callback)
|
|
170
|
+
callback(content);
|
|
171
|
+
});
|
|
172
|
+
Promise.all(promises);
|
|
173
|
+
}
|
|
174
|
+
handle(action, callback) {
|
|
175
|
+
const type = "request";
|
|
176
|
+
for (const [listenerId, listener] of this.listeners) {
|
|
177
|
+
if (listener.action !== action || listener.type !== type)
|
|
178
|
+
continue;
|
|
179
|
+
this.listeners.delete(listenerId);
|
|
180
|
+
}
|
|
181
|
+
for (const cache of this.caches) {
|
|
182
|
+
if (cache.action !== action || cache.type !== type)
|
|
183
|
+
continue;
|
|
184
|
+
callback(cache.content);
|
|
185
|
+
}
|
|
186
|
+
const listenerId = Symbol("LISTENER_HANDLE");
|
|
187
|
+
this.listeners.set(listenerId, {
|
|
188
|
+
action,
|
|
189
|
+
callback: async (getEncrypted) => {
|
|
190
|
+
const random = BridgeUtilCrypto.generateRandomHex();
|
|
191
|
+
const encryptedRandom = await getEncrypted(random);
|
|
192
|
+
const decryptedRandom = await BridgeUtilCrypto.aes.decrypt(encryptedRandom, this.validation);
|
|
193
|
+
if (decryptedRandom !== random)
|
|
194
|
+
throw new BridgeDirectError("MALICIOUS_DATA", {
|
|
195
|
+
data: { position: this.host ? "host" : "remote", step: 2 }
|
|
196
|
+
});
|
|
197
|
+
return async (content) => {
|
|
198
|
+
const { requestId, data, host } = content;
|
|
199
|
+
try {
|
|
200
|
+
const result = await callback(data);
|
|
201
|
+
return {
|
|
202
|
+
action,
|
|
203
|
+
content: {
|
|
204
|
+
success: true,
|
|
205
|
+
data: result,
|
|
206
|
+
error: null,
|
|
207
|
+
responseId: requestId
|
|
208
|
+
},
|
|
209
|
+
type,
|
|
210
|
+
host
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
return {
|
|
215
|
+
action,
|
|
216
|
+
content: {
|
|
217
|
+
success: false,
|
|
218
|
+
data: null,
|
|
219
|
+
error: (error instanceof Error) ? error.message : error,
|
|
220
|
+
responseId: requestId
|
|
221
|
+
},
|
|
222
|
+
type,
|
|
223
|
+
host
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
},
|
|
228
|
+
type,
|
|
229
|
+
host: this.host
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
request(action, content) {
|
|
233
|
+
return new Promise((resolve, reject) => {
|
|
234
|
+
const requestId = BridgeUtilCrypto.generateRandomHex();
|
|
235
|
+
const type = "request";
|
|
236
|
+
const listener = Array.from(this.listeners.values()).find(listener => listener.action === action && listener.type === type && listener.host !== this.host);
|
|
237
|
+
listener?.callback(async (random) => {
|
|
238
|
+
const encryptedRandom = await BridgeUtilCrypto.aes.encrypt(random, this.validation);
|
|
239
|
+
return encryptedRandom;
|
|
240
|
+
}).then(callback => callback({
|
|
241
|
+
action,
|
|
242
|
+
content: {
|
|
243
|
+
requestId,
|
|
244
|
+
data: content
|
|
245
|
+
},
|
|
246
|
+
type
|
|
247
|
+
}).then(({ content }) => {
|
|
248
|
+
const { success, data, error, responseId } = content;
|
|
249
|
+
if (responseId !== requestId)
|
|
250
|
+
return;
|
|
251
|
+
if (success)
|
|
252
|
+
resolve(data);
|
|
253
|
+
else
|
|
254
|
+
reject(error);
|
|
255
|
+
}));
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
class BridgeChannel {
|
|
260
|
+
port;
|
|
261
|
+
listeners = new Map();
|
|
262
|
+
caches = [];
|
|
263
|
+
constructor(port) {
|
|
264
|
+
this.port = port;
|
|
265
|
+
port.onmessage = ({ data }) => {
|
|
266
|
+
this.caches.push({ action: data.action, content: data.content, type: data.type });
|
|
267
|
+
for (const listener of this.listeners.values()) {
|
|
268
|
+
if (listener.action !== data.action || listener.type !== data.type)
|
|
269
|
+
continue;
|
|
270
|
+
listener.callback(data.content);
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
addEventListener(action, callback) {
|
|
275
|
+
const type = "normal";
|
|
276
|
+
for (const cache of this.caches) {
|
|
277
|
+
if (cache.action !== action || cache.type !== type)
|
|
278
|
+
continue;
|
|
279
|
+
callback(cache.content);
|
|
280
|
+
}
|
|
281
|
+
const listenerId = Symbol("LISTENER_NORMAL");
|
|
282
|
+
this.listeners.set(listenerId, { action, callback, type });
|
|
283
|
+
return {
|
|
284
|
+
id: listenerId,
|
|
285
|
+
removeEventListener: () => this.removeEventListener(listenerId)
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
removeEventListener(listenerId) {
|
|
289
|
+
return this.listeners.delete(listenerId);
|
|
290
|
+
}
|
|
291
|
+
postMessage(action, content) {
|
|
292
|
+
const type = "normal";
|
|
293
|
+
this.port.postMessage({
|
|
294
|
+
action,
|
|
295
|
+
content,
|
|
296
|
+
type
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
handle(action, callback) {
|
|
300
|
+
const type = "request";
|
|
301
|
+
for (const [listenerId, listener] of this.listeners) {
|
|
302
|
+
if (listener.action !== action || listener.type !== type)
|
|
303
|
+
continue;
|
|
304
|
+
this.listeners.delete(listenerId);
|
|
305
|
+
}
|
|
306
|
+
for (const cache of this.caches) {
|
|
307
|
+
if (cache.action !== action || cache.type !== type)
|
|
308
|
+
continue;
|
|
309
|
+
callback(cache.content);
|
|
310
|
+
}
|
|
311
|
+
const listenerId = Symbol("LISTENER_HANDLE");
|
|
312
|
+
this.listeners.set(listenerId, { action, callback: async (content) => {
|
|
313
|
+
const type = "response";
|
|
314
|
+
const { requestId, data } = content;
|
|
315
|
+
try {
|
|
316
|
+
const result = await callback(data);
|
|
317
|
+
this.port.postMessage({
|
|
318
|
+
action,
|
|
319
|
+
content: {
|
|
320
|
+
success: true,
|
|
321
|
+
data: result,
|
|
322
|
+
error: null,
|
|
323
|
+
responseId: requestId
|
|
324
|
+
},
|
|
325
|
+
type
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
this.port.postMessage({
|
|
330
|
+
action,
|
|
331
|
+
content: {
|
|
332
|
+
success: false,
|
|
333
|
+
data: null,
|
|
334
|
+
error: (error instanceof Error) ? error.message : error,
|
|
335
|
+
responseId: requestId
|
|
336
|
+
},
|
|
337
|
+
type
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}, type });
|
|
341
|
+
return {
|
|
342
|
+
id: listenerId,
|
|
343
|
+
removeEventListener: () => this.removeEventListener(listenerId)
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
request(action, content) {
|
|
347
|
+
return new Promise((resolve, reject) => {
|
|
348
|
+
const listenerId = Symbol("LISTENER_RESPONSE");
|
|
349
|
+
const requestId = BridgeUtilCrypto.generateRandomHex();
|
|
350
|
+
this.listeners.set(listenerId, { action, callback: (content) => {
|
|
351
|
+
const { success, data, error, responseId } = content;
|
|
352
|
+
if (responseId !== requestId)
|
|
353
|
+
return;
|
|
354
|
+
this.listeners.delete(listenerId);
|
|
355
|
+
if (success)
|
|
356
|
+
resolve(data);
|
|
357
|
+
else
|
|
358
|
+
reject(error);
|
|
359
|
+
}, type: "response" });
|
|
360
|
+
const type = "request";
|
|
361
|
+
this.port.postMessage({
|
|
362
|
+
action,
|
|
363
|
+
content: {
|
|
364
|
+
requestId,
|
|
365
|
+
data: content
|
|
366
|
+
},
|
|
367
|
+
type
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
static async create(port, host, validation) {
|
|
372
|
+
await new Promise((resolve, reject) => {
|
|
373
|
+
const random = BridgeUtilCrypto.generateRandomHex();
|
|
374
|
+
if (host) {
|
|
375
|
+
port.onmessage = async ({ data }) => {
|
|
376
|
+
if (data.type === 'port-validation-1') {
|
|
377
|
+
const encryptedRandom = await BridgeUtilCrypto.aes.encrypt(data.content.random, validation.remote);
|
|
378
|
+
port.postMessage({
|
|
379
|
+
type: 'port-validation-2',
|
|
380
|
+
content: {
|
|
381
|
+
random,
|
|
382
|
+
encrypted: encryptedRandom
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
else if (data.type === 'port-validation-3') {
|
|
387
|
+
const decryptedRandom = await BridgeUtilCrypto.aes.decrypt(data.content.encrypted, validation.host);
|
|
388
|
+
if (decryptedRandom !== random) {
|
|
389
|
+
port.close();
|
|
390
|
+
return reject(new BridgeChannelError("MALICIOUS_DATA", {
|
|
391
|
+
data: {
|
|
392
|
+
position: "host",
|
|
393
|
+
step: 3
|
|
394
|
+
}
|
|
395
|
+
}));
|
|
396
|
+
}
|
|
397
|
+
port.postMessage({
|
|
398
|
+
type: 'port-validation-4',
|
|
399
|
+
content: {}
|
|
400
|
+
});
|
|
401
|
+
resolve({});
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
port.close();
|
|
405
|
+
return reject(new BridgeChannelError("MALICIOUS_DATA", {
|
|
406
|
+
data: {
|
|
407
|
+
position: "host",
|
|
408
|
+
step: 0
|
|
409
|
+
}
|
|
410
|
+
}));
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
const portValidationRequest = setInterval(() => {
|
|
416
|
+
port.postMessage({
|
|
417
|
+
type: 'port-validation-1',
|
|
418
|
+
content: {
|
|
419
|
+
random
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
}, 100);
|
|
423
|
+
port.onmessage = async ({ data }) => {
|
|
424
|
+
clearInterval(portValidationRequest);
|
|
425
|
+
if (data.type === 'port-validation-2') {
|
|
426
|
+
const decryptedRandom = await BridgeUtilCrypto.aes.decrypt(data.content.encrypted, validation.remote);
|
|
427
|
+
if (decryptedRandom !== random) {
|
|
428
|
+
port.close();
|
|
429
|
+
return reject(new BridgeChannelError("MALICIOUS_DATA", {
|
|
430
|
+
data: {
|
|
431
|
+
position: "remote",
|
|
432
|
+
step: 2
|
|
433
|
+
}
|
|
434
|
+
}));
|
|
435
|
+
}
|
|
436
|
+
const encryptedRandom = await BridgeUtilCrypto.aes.encrypt(data.content.random, validation.host);
|
|
437
|
+
port.postMessage({
|
|
438
|
+
type: 'port-validation-3',
|
|
439
|
+
content: {
|
|
440
|
+
encrypted: encryptedRandom
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
else if (data.type === 'port-validation-4') {
|
|
445
|
+
resolve({});
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
port.close();
|
|
449
|
+
return reject(new BridgeChannelError("MALICIOUS_DATA", {
|
|
450
|
+
data: {
|
|
451
|
+
position: "host",
|
|
452
|
+
step: 0
|
|
453
|
+
}
|
|
454
|
+
}));
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
return new BridgeChannel(port);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
const BridgeEntry = {
|
|
463
|
+
create: () => {
|
|
464
|
+
const type = {
|
|
465
|
+
validation: {
|
|
466
|
+
request: BridgeUtilCrypto.generateRandomHex(),
|
|
467
|
+
receive: BridgeUtilCrypto.generateRandomHex()
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
const tokens = {
|
|
471
|
+
validation: BridgeUtilCrypto.generateRandomHex(),
|
|
472
|
+
bridge: BridgeUtilCrypto.generateRandomHex(),
|
|
473
|
+
testFn: BridgeUtilCrypto.generateRandomHex(),
|
|
474
|
+
testResult: BridgeUtilCrypto.generateRandomHex(),
|
|
475
|
+
portValidation: {
|
|
476
|
+
host: BridgeUtilCrypto.generateRandomHex(),
|
|
477
|
+
remote: BridgeUtilCrypto.generateRandomHex()
|
|
478
|
+
},
|
|
479
|
+
directValidation: BridgeUtilCrypto.generateRandomHex()
|
|
480
|
+
};
|
|
481
|
+
const bridge = new Promise(async (resolve, reject) => {
|
|
482
|
+
const connectReceive = BridgeUtilListener.addEventListener(async (event) => {
|
|
483
|
+
const { data } = event;
|
|
484
|
+
if (data.type !== type.validation.request)
|
|
485
|
+
return;
|
|
486
|
+
connectReceive.removeEventListener();
|
|
487
|
+
const main = window[tokens.bridge];
|
|
488
|
+
delete window[tokens.bridge];
|
|
489
|
+
const shareContext = ((main) => {
|
|
490
|
+
if (main instanceof Map) {
|
|
491
|
+
const testFn = main.get(tokens.testFn);
|
|
492
|
+
if (typeof testFn === 'function' && testFn() === tokens.testResult)
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
return false;
|
|
496
|
+
})(main);
|
|
497
|
+
const encryptedRandom = await BridgeUtilCrypto.aes.encrypt(data.content.random, tokens.validation);
|
|
498
|
+
if (shareContext) {
|
|
499
|
+
window.postMessage({
|
|
500
|
+
type: type.validation.receive,
|
|
501
|
+
content: {
|
|
502
|
+
encrypted: encryptedRandom,
|
|
503
|
+
shareContext
|
|
504
|
+
}
|
|
505
|
+
}, event.origin);
|
|
506
|
+
resolve(new BridgeDirect(main, true, tokens.directValidation));
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
const channel = new MessageChannel();
|
|
510
|
+
const port1 = channel.port1;
|
|
511
|
+
window.postMessage({
|
|
512
|
+
type: type.validation.receive,
|
|
513
|
+
content: {
|
|
514
|
+
encrypted: encryptedRandom,
|
|
515
|
+
shareContext
|
|
516
|
+
}
|
|
517
|
+
}, event.origin, [channel.port2]);
|
|
518
|
+
resolve(await BridgeChannel.create(port1, true, tokens.portValidation));
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
return { certification: { tokens, type }, bridge };
|
|
523
|
+
},
|
|
524
|
+
connect: ({ tokens, type }) => {
|
|
525
|
+
return new Promise(async (resolve, reject) => {
|
|
526
|
+
const random = BridgeUtilCrypto.generateRandomHex();
|
|
527
|
+
const main = window[tokens.bridge] = new Map([[tokens.testFn, () => tokens.testResult]]);
|
|
528
|
+
const connectValidationRequest = setInterval(() => {
|
|
529
|
+
window.postMessage({
|
|
530
|
+
type: type.validation.request,
|
|
531
|
+
content: {
|
|
532
|
+
random
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
}, 100);
|
|
536
|
+
const connectValidationReceive = BridgeUtilListener.addEventListener(async (event) => {
|
|
537
|
+
const { data } = event;
|
|
538
|
+
if (data.type !== type.validation.receive)
|
|
539
|
+
return;
|
|
540
|
+
connectValidationReceive.removeEventListener();
|
|
541
|
+
clearInterval(connectValidationRequest);
|
|
542
|
+
const decryptedRandom = await BridgeUtilCrypto.aes.decrypt(data.content.encrypted, tokens.validation);
|
|
543
|
+
if (random !== decryptedRandom)
|
|
544
|
+
return reject(new BridgeConnectError("INVALID_VALIDATION", {
|
|
545
|
+
data: {
|
|
546
|
+
expected: [random],
|
|
547
|
+
received: decryptedRandom
|
|
548
|
+
}
|
|
549
|
+
}));
|
|
550
|
+
const shareContext = data.content.shareContext;
|
|
551
|
+
if (shareContext) {
|
|
552
|
+
resolve(new BridgeDirect(main, false, tokens.directValidation));
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
const [port2] = event.ports;
|
|
556
|
+
if (!(port2 instanceof MessagePort))
|
|
557
|
+
return reject(new BridgeConnectError("INVALID_PORT", {
|
|
558
|
+
data: {
|
|
559
|
+
expected: ["MessagePort"],
|
|
560
|
+
received: port2
|
|
561
|
+
}
|
|
562
|
+
}));
|
|
563
|
+
resolve(await BridgeChannel.create(port2, false, tokens.portValidation));
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
|
|
570
|
+
exports.default = BridgeEntry;
|
|
571
|
+
//# sourceMappingURL=index.js.map
|