@polkadot/extension-base 0.59.2 → 0.61.1
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/background/handlers/Extension.js +34 -34
- package/background/handlers/State.d.ts +1 -0
- package/background/handlers/State.js +109 -69
- package/background/handlers/Tabs.d.ts +1 -0
- package/background/handlers/Tabs.js +37 -22
- package/cjs/background/handlers/Extension.js +34 -34
- package/cjs/background/handlers/State.d.ts +1 -0
- package/cjs/background/handlers/State.js +109 -69
- package/cjs/background/handlers/Tabs.d.ts +1 -0
- package/cjs/background/handlers/Tabs.js +37 -22
- package/cjs/packageInfo.js +1 -1
- package/cjs/page/PostMessageProvider.js +15 -15
- package/cjs/stores/Base.js +7 -7
- package/package.json +15 -14
- package/packageInfo.js +1 -1
- package/page/PostMessageProvider.js +15 -15
- package/stores/Base.js +7 -7
|
@@ -66,35 +66,38 @@ async function extractMetadata(store) {
|
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
class State {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
#authUrls = new Map();
|
|
70
|
+
#lastRequestTimestamps = new Map();
|
|
71
|
+
#maxEntries = 10;
|
|
72
|
+
#rateLimitInterval = 3000; // 3 seconds
|
|
73
|
+
#authRequests = {};
|
|
74
|
+
#metaStore = new index_js_1.MetadataStore();
|
|
72
75
|
// Map of providers currently injected in tabs
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
#injectedProviders = new Map();
|
|
77
|
+
#metaRequests = {};
|
|
78
|
+
#notification = ui_settings_1.settings.notification;
|
|
76
79
|
// Map of all providers exposed by the extension, they are retrievable by key
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
#providers;
|
|
81
|
+
#signRequests = {};
|
|
82
|
+
#windows = [];
|
|
83
|
+
#connectedTabsUrl = [];
|
|
81
84
|
authSubject = new rxjs_1.BehaviorSubject([]);
|
|
82
85
|
metaSubject = new rxjs_1.BehaviorSubject([]);
|
|
83
86
|
signSubject = new rxjs_1.BehaviorSubject([]);
|
|
84
87
|
authUrlSubjects = {};
|
|
85
88
|
defaultAuthAccountSelection = [];
|
|
86
89
|
constructor(providers = {}) {
|
|
87
|
-
this
|
|
90
|
+
this.#providers = providers;
|
|
88
91
|
}
|
|
89
92
|
async init() {
|
|
90
|
-
await extractMetadata(this
|
|
93
|
+
await extractMetadata(this.#metaStore);
|
|
91
94
|
// retrieve previously set authorizations
|
|
92
95
|
const storageAuthUrls = await chrome.storage.local.get(AUTH_URLS_KEY);
|
|
93
96
|
const authString = storageAuthUrls?.[AUTH_URLS_KEY] || '{}';
|
|
94
97
|
const previousAuth = JSON.parse(authString);
|
|
95
|
-
this
|
|
98
|
+
this.#authUrls = new Map(Object.entries(previousAuth));
|
|
96
99
|
// Initialize authUrlSubjects for each URL
|
|
97
|
-
|
|
100
|
+
this.#authUrls.forEach((authInfo, url) => {
|
|
98
101
|
this.authUrlSubjects[url] = new rxjs_1.BehaviorSubject(authInfo);
|
|
99
102
|
});
|
|
100
103
|
// retrieve previously set default auth accounts
|
|
@@ -107,49 +110,49 @@ class State {
|
|
|
107
110
|
return (0, extension_chains_1.knownMetadata)();
|
|
108
111
|
}
|
|
109
112
|
get numAuthRequests() {
|
|
110
|
-
return Object.keys(this
|
|
113
|
+
return Object.keys(this.#authRequests).length;
|
|
111
114
|
}
|
|
112
115
|
get numMetaRequests() {
|
|
113
|
-
return Object.keys(this
|
|
116
|
+
return Object.keys(this.#metaRequests).length;
|
|
114
117
|
}
|
|
115
118
|
get numSignRequests() {
|
|
116
|
-
return Object.keys(this
|
|
119
|
+
return Object.keys(this.#signRequests).length;
|
|
117
120
|
}
|
|
118
121
|
get allAuthRequests() {
|
|
119
122
|
return Object
|
|
120
|
-
.values(this
|
|
123
|
+
.values(this.#authRequests)
|
|
121
124
|
.map(({ id, request, url }) => ({ id, request, url }));
|
|
122
125
|
}
|
|
123
126
|
get allMetaRequests() {
|
|
124
127
|
return Object
|
|
125
|
-
.values(this
|
|
128
|
+
.values(this.#metaRequests)
|
|
126
129
|
.map(({ id, request, url }) => ({ id, request, url }));
|
|
127
130
|
}
|
|
128
131
|
get allSignRequests() {
|
|
129
132
|
return Object
|
|
130
|
-
.values(this
|
|
133
|
+
.values(this.#signRequests)
|
|
131
134
|
.map(({ account, id, request, url }) => ({ account, id, request, url }));
|
|
132
135
|
}
|
|
133
136
|
get authUrls() {
|
|
134
|
-
return this
|
|
137
|
+
return Object.fromEntries(this.#authUrls);
|
|
135
138
|
}
|
|
136
139
|
popupClose() {
|
|
137
|
-
this.
|
|
138
|
-
this
|
|
140
|
+
this.#windows.forEach((id) => (0, helpers_js_1.withErrorLog)(() => chrome.windows.remove(id)));
|
|
141
|
+
this.#windows = [];
|
|
139
142
|
}
|
|
140
143
|
popupOpen() {
|
|
141
|
-
this
|
|
142
|
-
chrome.windows.create(this
|
|
144
|
+
this.#notification !== 'extension' &&
|
|
145
|
+
chrome.windows.create(this.#notification === 'window'
|
|
143
146
|
? NORMAL_WINDOW_OPTS
|
|
144
147
|
: POPUP_WINDOW_OPTS, (window) => {
|
|
145
148
|
if (window) {
|
|
146
|
-
this.
|
|
149
|
+
this.#windows.push(window.id || 0);
|
|
147
150
|
}
|
|
148
151
|
});
|
|
149
152
|
}
|
|
150
153
|
authComplete = (id, resolve, reject) => {
|
|
151
154
|
const complete = async (authorizedAccounts = []) => {
|
|
152
|
-
const { idStr, request: { origin }, url } = this
|
|
155
|
+
const { idStr, request: { origin }, url } = this.#authRequests[id];
|
|
153
156
|
const strippedUrl = this.stripUrl(url);
|
|
154
157
|
const authInfo = {
|
|
155
158
|
authorizedAccounts,
|
|
@@ -158,7 +161,7 @@ class State {
|
|
|
158
161
|
origin,
|
|
159
162
|
url
|
|
160
163
|
};
|
|
161
|
-
this.
|
|
164
|
+
this.#authUrls.set(strippedUrl, authInfo);
|
|
162
165
|
if (!this.authUrlSubjects[strippedUrl]) {
|
|
163
166
|
this.authUrlSubjects[strippedUrl] = new rxjs_1.BehaviorSubject(authInfo);
|
|
164
167
|
}
|
|
@@ -167,14 +170,14 @@ class State {
|
|
|
167
170
|
}
|
|
168
171
|
await this.saveCurrentAuthList();
|
|
169
172
|
await this.updateDefaultAuthAccounts(authorizedAccounts);
|
|
170
|
-
delete this
|
|
173
|
+
delete this.#authRequests[id];
|
|
171
174
|
this.updateIconAuth(true);
|
|
172
175
|
};
|
|
173
176
|
return {
|
|
174
177
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
175
178
|
reject: async (error) => {
|
|
176
179
|
if (error.message === 'Cancelled') {
|
|
177
|
-
delete this
|
|
180
|
+
delete this.#authRequests[id];
|
|
178
181
|
this.updateIconAuth(true);
|
|
179
182
|
reject(new Error('Connection request was cancelled by the user.'));
|
|
180
183
|
}
|
|
@@ -212,17 +215,17 @@ class State {
|
|
|
212
215
|
: undefined;
|
|
213
216
|
})
|
|
214
217
|
.filter((value) => !!value);
|
|
215
|
-
this
|
|
218
|
+
this.#connectedTabsUrl = connectedTabs;
|
|
216
219
|
}
|
|
217
220
|
getConnectedTabsUrl() {
|
|
218
|
-
return this
|
|
221
|
+
return this.#connectedTabsUrl;
|
|
219
222
|
}
|
|
220
223
|
deleteAuthRequest(requestId) {
|
|
221
|
-
delete this
|
|
224
|
+
delete this.#authRequests[requestId];
|
|
222
225
|
this.updateIconAuth(true);
|
|
223
226
|
}
|
|
224
227
|
async saveCurrentAuthList() {
|
|
225
|
-
await chrome.storage.local.set({ [AUTH_URLS_KEY]: JSON.stringify(this
|
|
228
|
+
await chrome.storage.local.set({ [AUTH_URLS_KEY]: JSON.stringify(Object.fromEntries(this.#authUrls)) });
|
|
226
229
|
}
|
|
227
230
|
async saveDefaultAuthAccounts() {
|
|
228
231
|
await chrome.storage.local.set({ [DEFAULT_AUTH_ACCOUNTS]: JSON.stringify(this.defaultAuthAccountSelection) });
|
|
@@ -233,7 +236,7 @@ class State {
|
|
|
233
236
|
}
|
|
234
237
|
metaComplete = (id, resolve, reject) => {
|
|
235
238
|
const complete = () => {
|
|
236
|
-
delete this
|
|
239
|
+
delete this.#metaRequests[id];
|
|
237
240
|
this.updateIconMeta(true);
|
|
238
241
|
};
|
|
239
242
|
return {
|
|
@@ -249,7 +252,7 @@ class State {
|
|
|
249
252
|
};
|
|
250
253
|
signComplete = (id, resolve, reject) => {
|
|
251
254
|
const complete = () => {
|
|
252
|
-
delete this
|
|
255
|
+
delete this.#signRequests[id];
|
|
253
256
|
this.updateIconSign(true);
|
|
254
257
|
};
|
|
255
258
|
return {
|
|
@@ -264,9 +267,22 @@ class State {
|
|
|
264
267
|
};
|
|
265
268
|
};
|
|
266
269
|
stripUrl(url) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
+
try {
|
|
271
|
+
const parsedUrl = new URL(url);
|
|
272
|
+
if (!['http:', 'https:', 'ipfs:', 'ipns:'].includes(parsedUrl.protocol)) {
|
|
273
|
+
throw new Error(`Invalid protocol ${parsedUrl.protocol}`);
|
|
274
|
+
}
|
|
275
|
+
// For ipfs/ipns which don't have a standard origin, we handle it differently.
|
|
276
|
+
if (parsedUrl.protocol === 'ipfs:' || parsedUrl.protocol === 'ipns:') {
|
|
277
|
+
// ipfs://<hash> | ipns://<hash>
|
|
278
|
+
return `${parsedUrl.protocol}//${parsedUrl.hostname}`;
|
|
279
|
+
}
|
|
280
|
+
return parsedUrl.origin;
|
|
281
|
+
}
|
|
282
|
+
catch (e) {
|
|
283
|
+
console.error(e);
|
|
284
|
+
throw new Error('Invalid URL');
|
|
285
|
+
}
|
|
270
286
|
}
|
|
271
287
|
updateIcon(shouldClose) {
|
|
272
288
|
const authCount = this.numAuthRequests;
|
|
@@ -283,15 +299,15 @@ class State {
|
|
|
283
299
|
}
|
|
284
300
|
}
|
|
285
301
|
async removeAuthorization(url) {
|
|
286
|
-
const entry = this.
|
|
302
|
+
const entry = this.#authUrls.get(url);
|
|
287
303
|
(0, util_1.assert)(entry, `The source ${url} is not known`);
|
|
288
|
-
|
|
304
|
+
this.#authUrls.delete(url);
|
|
289
305
|
await this.saveCurrentAuthList();
|
|
290
306
|
if (this.authUrlSubjects[url]) {
|
|
291
307
|
entry.authorizedAccounts = [];
|
|
292
308
|
this.authUrlSubjects[url].next(entry);
|
|
293
309
|
}
|
|
294
|
-
return this.
|
|
310
|
+
return this.authUrls;
|
|
295
311
|
}
|
|
296
312
|
updateIconAuth(shouldClose) {
|
|
297
313
|
this.authSubject.next(this.allAuthRequests);
|
|
@@ -307,8 +323,12 @@ class State {
|
|
|
307
323
|
}
|
|
308
324
|
async updateAuthorizedAccounts(authorizedAccountsDiff) {
|
|
309
325
|
authorizedAccountsDiff.forEach(([url, authorizedAccountDiff]) => {
|
|
310
|
-
this.
|
|
311
|
-
|
|
326
|
+
const authInfo = this.#authUrls.get(url);
|
|
327
|
+
if (authInfo) {
|
|
328
|
+
authInfo.authorizedAccounts = authorizedAccountDiff;
|
|
329
|
+
this.#authUrls.set(url, authInfo);
|
|
330
|
+
this.authUrlSubjects[url].next(authInfo);
|
|
331
|
+
}
|
|
312
332
|
});
|
|
313
333
|
await this.saveCurrentAuthList();
|
|
314
334
|
}
|
|
@@ -316,12 +336,13 @@ class State {
|
|
|
316
336
|
const idStr = this.stripUrl(url);
|
|
317
337
|
// Do not enqueue duplicate authorization requests.
|
|
318
338
|
const isDuplicate = Object
|
|
319
|
-
.values(this
|
|
339
|
+
.values(this.#authRequests)
|
|
320
340
|
.some((request) => request.idStr === idStr);
|
|
321
341
|
(0, util_1.assert)(!isDuplicate, `The source ${url} has a pending authorization request`);
|
|
322
|
-
if (this.
|
|
342
|
+
if (this.#authUrls.has(idStr)) {
|
|
323
343
|
// this url was seen in the past
|
|
324
|
-
|
|
344
|
+
const authInfo = this.#authUrls.get(idStr);
|
|
345
|
+
(0, util_1.assert)(authInfo?.authorizedAccounts || authInfo?.isAllowed, `The source ${url} is not allowed to interact with this extension`);
|
|
325
346
|
return {
|
|
326
347
|
authorizedAccounts: [],
|
|
327
348
|
result: false
|
|
@@ -329,7 +350,7 @@ class State {
|
|
|
329
350
|
}
|
|
330
351
|
return new Promise((resolve, reject) => {
|
|
331
352
|
const id = (0, getId_js_1.getId)();
|
|
332
|
-
this
|
|
353
|
+
this.#authRequests[id] = {
|
|
333
354
|
...this.authComplete(id, resolve, reject),
|
|
334
355
|
id,
|
|
335
356
|
idStr,
|
|
@@ -341,14 +362,14 @@ class State {
|
|
|
341
362
|
});
|
|
342
363
|
}
|
|
343
364
|
ensureUrlAuthorized(url) {
|
|
344
|
-
const entry = this.
|
|
365
|
+
const entry = this.#authUrls.get(this.stripUrl(url));
|
|
345
366
|
(0, util_1.assert)(entry, `The source ${url} has not been enabled yet`);
|
|
346
367
|
return true;
|
|
347
368
|
}
|
|
348
369
|
injectMetadata(url, request) {
|
|
349
370
|
return new Promise((resolve, reject) => {
|
|
350
371
|
const id = (0, getId_js_1.getId)();
|
|
351
|
-
this
|
|
372
|
+
this.#metaRequests[id] = {
|
|
352
373
|
...this.metaComplete(id, resolve, reject),
|
|
353
374
|
id,
|
|
354
375
|
request,
|
|
@@ -359,73 +380,92 @@ class State {
|
|
|
359
380
|
});
|
|
360
381
|
}
|
|
361
382
|
getAuthRequest(id) {
|
|
362
|
-
return this
|
|
383
|
+
return this.#authRequests[id];
|
|
363
384
|
}
|
|
364
385
|
getMetaRequest(id) {
|
|
365
|
-
return this
|
|
386
|
+
return this.#metaRequests[id];
|
|
366
387
|
}
|
|
367
388
|
getSignRequest(id) {
|
|
368
|
-
return this
|
|
389
|
+
return this.#signRequests[id];
|
|
369
390
|
}
|
|
370
391
|
// List all providers the extension is exposing
|
|
371
392
|
rpcListProviders() {
|
|
372
|
-
return Promise.resolve(Object.keys(this
|
|
373
|
-
acc[key] = this
|
|
393
|
+
return Promise.resolve(Object.keys(this.#providers).reduce((acc, key) => {
|
|
394
|
+
acc[key] = this.#providers[key].meta;
|
|
374
395
|
return acc;
|
|
375
396
|
}, {}));
|
|
376
397
|
}
|
|
377
398
|
rpcSend(request, port) {
|
|
378
|
-
const provider = this.
|
|
399
|
+
const provider = this.#injectedProviders.get(port);
|
|
379
400
|
(0, util_1.assert)(provider, 'Cannot call pub(rpc.subscribe) before provider is set');
|
|
380
401
|
return provider.send(request.method, request.params);
|
|
381
402
|
}
|
|
382
403
|
// Start a provider, return its meta
|
|
383
404
|
rpcStartProvider(key, port) {
|
|
384
|
-
(0, util_1.assert)(Object.keys(this
|
|
385
|
-
if (this.
|
|
386
|
-
return Promise.resolve(this
|
|
405
|
+
(0, util_1.assert)(Object.keys(this.#providers).includes(key), `Provider ${key} is not exposed by extension`);
|
|
406
|
+
if (this.#injectedProviders.get(port)) {
|
|
407
|
+
return Promise.resolve(this.#providers[key].meta);
|
|
387
408
|
}
|
|
388
409
|
// Instantiate the provider
|
|
389
|
-
this.
|
|
410
|
+
this.#injectedProviders.set(port, this.#providers[key].start());
|
|
390
411
|
// Close provider connection when page is closed
|
|
391
412
|
port.onDisconnect.addListener(() => {
|
|
392
|
-
const provider = this.
|
|
413
|
+
const provider = this.#injectedProviders.get(port);
|
|
393
414
|
if (provider) {
|
|
394
415
|
(0, helpers_js_1.withErrorLog)(() => provider.disconnect());
|
|
395
416
|
}
|
|
396
|
-
this.
|
|
417
|
+
this.#injectedProviders.delete(port);
|
|
397
418
|
});
|
|
398
|
-
return Promise.resolve(this
|
|
419
|
+
return Promise.resolve(this.#providers[key].meta);
|
|
399
420
|
}
|
|
400
421
|
rpcSubscribe({ method, params, type }, cb, port) {
|
|
401
|
-
const provider = this.
|
|
422
|
+
const provider = this.#injectedProviders.get(port);
|
|
402
423
|
(0, util_1.assert)(provider, 'Cannot call pub(rpc.subscribe) before provider is set');
|
|
403
424
|
return provider.subscribe(type, method, params, cb);
|
|
404
425
|
}
|
|
405
426
|
rpcSubscribeConnected(_request, cb, port) {
|
|
406
|
-
const provider = this.
|
|
427
|
+
const provider = this.#injectedProviders.get(port);
|
|
407
428
|
(0, util_1.assert)(provider, 'Cannot call pub(rpc.subscribeConnected) before provider is set');
|
|
408
429
|
cb(null, provider.isConnected); // Immediately send back current isConnected
|
|
409
430
|
provider.on('connected', () => cb(null, true));
|
|
410
431
|
provider.on('disconnected', () => cb(null, false));
|
|
411
432
|
}
|
|
412
433
|
rpcUnsubscribe(request, port) {
|
|
413
|
-
const provider = this.
|
|
434
|
+
const provider = this.#injectedProviders.get(port);
|
|
414
435
|
(0, util_1.assert)(provider, 'Cannot call pub(rpc.unsubscribe) before provider is set');
|
|
415
436
|
return provider.unsubscribe(request.type, request.method, request.subscriptionId);
|
|
416
437
|
}
|
|
417
438
|
async saveMetadata(meta) {
|
|
418
|
-
await this.
|
|
439
|
+
await this.#metaStore.set(meta.genesisHash, meta);
|
|
419
440
|
(0, extension_chains_1.addMetadata)(meta);
|
|
420
441
|
}
|
|
421
442
|
setNotification(notification) {
|
|
422
|
-
this
|
|
443
|
+
this.#notification = notification;
|
|
423
444
|
return true;
|
|
424
445
|
}
|
|
446
|
+
handleSignRequest(origin) {
|
|
447
|
+
const now = Date.now();
|
|
448
|
+
const lastTime = this.#lastRequestTimestamps.get(origin) || 0;
|
|
449
|
+
if (now - lastTime < this.#rateLimitInterval) {
|
|
450
|
+
throw new Error('Rate limit exceeded. Try again later.');
|
|
451
|
+
}
|
|
452
|
+
// If we're about to exceed max entries, evict the oldest
|
|
453
|
+
if (!this.#lastRequestTimestamps.has(origin) && this.#lastRequestTimestamps.size >= this.#maxEntries) {
|
|
454
|
+
const oldestKey = this.#lastRequestTimestamps.keys().next().value;
|
|
455
|
+
oldestKey && this.#lastRequestTimestamps.delete(oldestKey);
|
|
456
|
+
}
|
|
457
|
+
this.#lastRequestTimestamps.set(origin, now);
|
|
458
|
+
}
|
|
425
459
|
sign(url, request, account) {
|
|
426
460
|
const id = (0, getId_js_1.getId)();
|
|
461
|
+
try {
|
|
462
|
+
this.handleSignRequest(url);
|
|
463
|
+
}
|
|
464
|
+
catch (error) {
|
|
465
|
+
return Promise.reject(error);
|
|
466
|
+
}
|
|
427
467
|
return new Promise((resolve, reject) => {
|
|
428
|
-
this
|
|
468
|
+
this.#signRequests[id] = {
|
|
429
469
|
...this.signComplete(id, resolve, reject),
|
|
430
470
|
account,
|
|
431
471
|
id,
|
|
@@ -20,6 +20,7 @@ export default class Tabs {
|
|
|
20
20
|
private rpcSubscribeConnected;
|
|
21
21
|
private rpcUnsubscribe;
|
|
22
22
|
private redirectPhishingLanding;
|
|
23
|
+
private parseUrl;
|
|
23
24
|
private redirectIfPhishing;
|
|
24
25
|
handle<TMessageType extends MessageTypes>(id: string, type: TMessageType, request: RequestTypes[TMessageType], url: string, port?: chrome.runtime.Port): Promise<ResponseTypes[keyof ResponseTypes]>;
|
|
25
26
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
const rxjs_1 = require("rxjs");
|
|
5
|
+
const tldts_1 = require("tldts");
|
|
5
6
|
const phishing_1 = require("@polkadot/phishing");
|
|
6
7
|
const ui_keyring_1 = require("@polkadot/ui-keyring");
|
|
7
8
|
const accounts_1 = require("@polkadot/ui-keyring/observable/accounts");
|
|
@@ -26,13 +27,13 @@ function transformAccounts(accounts, anyType = false) {
|
|
|
26
27
|
}));
|
|
27
28
|
}
|
|
28
29
|
class Tabs {
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
#accountSubs = {};
|
|
31
|
+
#state;
|
|
31
32
|
constructor(state) {
|
|
32
|
-
this
|
|
33
|
+
this.#state = state;
|
|
33
34
|
}
|
|
34
35
|
filterForAuthorizedAccounts(accounts, url) {
|
|
35
|
-
const auth = this.
|
|
36
|
+
const auth = this.#state.authUrls[this.#state.stripUrl(url)];
|
|
36
37
|
if (!auth) {
|
|
37
38
|
return [];
|
|
38
39
|
}
|
|
@@ -43,7 +44,7 @@ class Tabs {
|
|
|
43
44
|
: auth.isAllowed);
|
|
44
45
|
}
|
|
45
46
|
authorize(url, request) {
|
|
46
|
-
return this.
|
|
47
|
+
return this.#state.authorizeUrl(url, request);
|
|
47
48
|
}
|
|
48
49
|
accountsListAuthorized(url, { anyType }) {
|
|
49
50
|
const transformedAccounts = transformAccounts(accounts_1.accounts.subject.getValue(), anyType);
|
|
@@ -51,13 +52,13 @@ class Tabs {
|
|
|
51
52
|
}
|
|
52
53
|
accountsSubscribeAuthorized(url, id, port) {
|
|
53
54
|
const cb = (0, subscriptions_js_1.createSubscription)(id, port);
|
|
54
|
-
const strippedUrl = this.
|
|
55
|
-
const authUrlObservable = this.
|
|
55
|
+
const strippedUrl = this.#state.stripUrl(url);
|
|
56
|
+
const authUrlObservable = this.#state.authUrlSubjects[strippedUrl]?.asObservable();
|
|
56
57
|
if (!authUrlObservable) {
|
|
57
58
|
console.error(`No authUrlSubject found for ${strippedUrl}`);
|
|
58
59
|
return id;
|
|
59
60
|
}
|
|
60
|
-
this
|
|
61
|
+
this.#accountSubs[id] = {
|
|
61
62
|
subscription: (0, rxjs_1.combineLatest)([accounts_1.accounts.subject, authUrlObservable]).subscribe(([accounts, _authUrlInfo]) => {
|
|
62
63
|
const transformedAccounts = transformAccounts(accounts);
|
|
63
64
|
cb(this.filterForAuthorizedAccounts(transformedAccounts, url));
|
|
@@ -70,11 +71,11 @@ class Tabs {
|
|
|
70
71
|
return id;
|
|
71
72
|
}
|
|
72
73
|
accountsUnsubscribe(url, { id }) {
|
|
73
|
-
const sub = this
|
|
74
|
+
const sub = this.#accountSubs[id];
|
|
74
75
|
if (!sub || sub.url !== url) {
|
|
75
76
|
return false;
|
|
76
77
|
}
|
|
77
|
-
delete this
|
|
78
|
+
delete this.#accountSubs[id];
|
|
78
79
|
(0, subscriptions_js_1.unsubscribe)(id);
|
|
79
80
|
sub.subscription.unsubscribe();
|
|
80
81
|
return true;
|
|
@@ -87,35 +88,35 @@ class Tabs {
|
|
|
87
88
|
bytesSign(url, request) {
|
|
88
89
|
const address = request.address;
|
|
89
90
|
const pair = this.getSigningPair(address);
|
|
90
|
-
return this.
|
|
91
|
+
return this.#state.sign(url, new RequestBytesSign_js_1.default(request), { address, ...pair.meta });
|
|
91
92
|
}
|
|
92
93
|
extrinsicSign(url, request) {
|
|
93
94
|
const address = request.address;
|
|
94
95
|
const pair = this.getSigningPair(address);
|
|
95
|
-
return this.
|
|
96
|
+
return this.#state.sign(url, new RequestExtrinsicSign_js_1.default(request), { address, ...pair.meta });
|
|
96
97
|
}
|
|
97
98
|
metadataProvide(url, request) {
|
|
98
|
-
return this.
|
|
99
|
+
return this.#state.injectMetadata(url, request);
|
|
99
100
|
}
|
|
100
101
|
metadataList(_url) {
|
|
101
|
-
return this.
|
|
102
|
+
return this.#state.knownMetadata.map(({ genesisHash, specVersion }) => ({
|
|
102
103
|
genesisHash,
|
|
103
104
|
specVersion
|
|
104
105
|
}));
|
|
105
106
|
}
|
|
106
107
|
rpcListProviders() {
|
|
107
|
-
return this.
|
|
108
|
+
return this.#state.rpcListProviders();
|
|
108
109
|
}
|
|
109
110
|
rpcSend(request, port) {
|
|
110
|
-
return this.
|
|
111
|
+
return this.#state.rpcSend(request, port);
|
|
111
112
|
}
|
|
112
113
|
rpcStartProvider(key, port) {
|
|
113
|
-
return this.
|
|
114
|
+
return this.#state.rpcStartProvider(key, port);
|
|
114
115
|
}
|
|
115
116
|
async rpcSubscribe(request, id, port) {
|
|
116
117
|
const innerCb = (0, subscriptions_js_1.createSubscription)(id, port);
|
|
117
118
|
const cb = (_error, data) => innerCb(data);
|
|
118
|
-
const subscriptionId = await this.
|
|
119
|
+
const subscriptionId = await this.#state.rpcSubscribe(request, cb, port);
|
|
119
120
|
port.onDisconnect.addListener(() => {
|
|
120
121
|
(0, subscriptions_js_1.unsubscribe)(id);
|
|
121
122
|
(0, helpers_js_1.withErrorLog)(() => this.rpcUnsubscribe({ ...request, subscriptionId }, port));
|
|
@@ -125,14 +126,14 @@ class Tabs {
|
|
|
125
126
|
rpcSubscribeConnected(request, id, port) {
|
|
126
127
|
const innerCb = (0, subscriptions_js_1.createSubscription)(id, port);
|
|
127
128
|
const cb = (_error, data) => innerCb(data);
|
|
128
|
-
this.
|
|
129
|
+
this.#state.rpcSubscribeConnected(request, cb, port);
|
|
129
130
|
port.onDisconnect.addListener(() => {
|
|
130
131
|
(0, subscriptions_js_1.unsubscribe)(id);
|
|
131
132
|
});
|
|
132
133
|
return Promise.resolve(true);
|
|
133
134
|
}
|
|
134
135
|
async rpcUnsubscribe(request, port) {
|
|
135
|
-
return this.
|
|
136
|
+
return this.#state.rpcUnsubscribe(request, port);
|
|
136
137
|
}
|
|
137
138
|
redirectPhishingLanding(phishingWebsite) {
|
|
138
139
|
const nonFragment = phishingWebsite.split('#')[0];
|
|
@@ -145,6 +146,19 @@ class Tabs {
|
|
|
145
146
|
.forEach((id) => (0, helpers_js_1.withErrorLog)(() => chrome.tabs.update(id, { url })));
|
|
146
147
|
});
|
|
147
148
|
}
|
|
149
|
+
parseUrl(rawUrl) {
|
|
150
|
+
let from = 'extension';
|
|
151
|
+
if (rawUrl) {
|
|
152
|
+
try {
|
|
153
|
+
const { hostname } = (0, tldts_1.parse)(rawUrl);
|
|
154
|
+
from = hostname || '<unknown>'; // Only use the hostname
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
from = '<unknown>';
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return from;
|
|
161
|
+
}
|
|
148
162
|
async redirectIfPhishing(url) {
|
|
149
163
|
const isInDenyList = await (0, phishing_1.checkIfDenied)(url);
|
|
150
164
|
if (isInDenyList) {
|
|
@@ -155,10 +169,11 @@ class Tabs {
|
|
|
155
169
|
}
|
|
156
170
|
async handle(id, type, request, url, port) {
|
|
157
171
|
if (type === 'pub(phishing.redirectIfDenied)') {
|
|
158
|
-
|
|
172
|
+
const parsedUrl = this.parseUrl(url);
|
|
173
|
+
return this.redirectIfPhishing(parsedUrl);
|
|
159
174
|
}
|
|
160
175
|
if (type !== 'pub(authorize.tab)') {
|
|
161
|
-
this.
|
|
176
|
+
this.#state.ensureUrlAuthorized(url);
|
|
162
177
|
}
|
|
163
178
|
switch (type) {
|
|
164
179
|
case 'pub(authorize.tab)':
|
package/cjs/packageInfo.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.packageInfo = void 0;
|
|
4
|
-
exports.packageInfo = { name: '@polkadot/extension-base', path: typeof __dirname === 'string' ? __dirname : 'auto', type: 'cjs', version: '0.
|
|
4
|
+
exports.packageInfo = { name: '@polkadot/extension-base', path: typeof __dirname === 'string' ? __dirname : 'auto', type: 'cjs', version: '0.61.1' };
|
|
@@ -10,19 +10,19 @@ let sendRequest;
|
|
|
10
10
|
* @description Extension provider to be used by dapps
|
|
11
11
|
*/
|
|
12
12
|
class PostMessageProvider {
|
|
13
|
-
|
|
13
|
+
#eventemitter;
|
|
14
14
|
// Whether or not the actual extension background provider is connected
|
|
15
|
-
|
|
15
|
+
#isConnected = false;
|
|
16
16
|
// Subscription IDs are (historically) not guaranteed to be globally unique;
|
|
17
17
|
// only unique for a given subscription method; which is why we identify
|
|
18
18
|
// the subscriptions based on subscription id + type
|
|
19
|
-
|
|
19
|
+
#subscriptions = {}; // {[(type,subscriptionId)]: callback}
|
|
20
20
|
/**
|
|
21
21
|
* @param {function} sendRequest The function to be called to send requests to the node
|
|
22
22
|
* @param {function} subscriptionNotificationHandler Channel for receiving subscription messages
|
|
23
23
|
*/
|
|
24
24
|
constructor(_sendRequest) {
|
|
25
|
-
this
|
|
25
|
+
this.#eventemitter = new eventemitter3_1.EventEmitter();
|
|
26
26
|
sendRequest = _sendRequest;
|
|
27
27
|
}
|
|
28
28
|
get isClonable() {
|
|
@@ -62,7 +62,7 @@ class PostMessageProvider {
|
|
|
62
62
|
* @return {boolean} true if connected
|
|
63
63
|
*/
|
|
64
64
|
get isConnected() {
|
|
65
|
-
return this
|
|
65
|
+
return this.#isConnected;
|
|
66
66
|
}
|
|
67
67
|
listProviders() {
|
|
68
68
|
return sendRequest('pub(rpc.listProviders)', undefined);
|
|
@@ -74,9 +74,9 @@ class PostMessageProvider {
|
|
|
74
74
|
* @return unsubscribe function
|
|
75
75
|
*/
|
|
76
76
|
on(type, sub) {
|
|
77
|
-
this.
|
|
77
|
+
this.#eventemitter.on(type, sub);
|
|
78
78
|
return () => {
|
|
79
|
-
this.
|
|
79
|
+
this.#eventemitter.removeListener(type, sub);
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
82
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -86,7 +86,7 @@ class PostMessageProvider {
|
|
|
86
86
|
const id = await sendRequest('pub(rpc.subscribe)', { method, params, type }, (res) => {
|
|
87
87
|
subscription.callback(null, res);
|
|
88
88
|
});
|
|
89
|
-
this
|
|
89
|
+
this.#subscriptions[`${type}::${id}`] = callback;
|
|
90
90
|
return id;
|
|
91
91
|
}
|
|
92
92
|
return sendRequest('pub(rpc.send)', { method, params });
|
|
@@ -96,17 +96,17 @@ class PostMessageProvider {
|
|
|
96
96
|
*/
|
|
97
97
|
async startProvider(key) {
|
|
98
98
|
// Disconnect from the previous provider
|
|
99
|
-
this
|
|
100
|
-
this.
|
|
99
|
+
this.#isConnected = false;
|
|
100
|
+
this.#eventemitter.emit('disconnected');
|
|
101
101
|
const meta = await sendRequest('pub(rpc.startProvider)', key);
|
|
102
102
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
103
103
|
sendRequest('pub(rpc.subscribeConnected)', null, (connected) => {
|
|
104
|
-
this
|
|
104
|
+
this.#isConnected = connected;
|
|
105
105
|
if (connected) {
|
|
106
|
-
this.
|
|
106
|
+
this.#eventemitter.emit('connected');
|
|
107
107
|
}
|
|
108
108
|
else {
|
|
109
|
-
this.
|
|
109
|
+
this.#eventemitter.emit('disconnected');
|
|
110
110
|
}
|
|
111
111
|
return true;
|
|
112
112
|
});
|
|
@@ -124,11 +124,11 @@ class PostMessageProvider {
|
|
|
124
124
|
// the assigned id now does not match what the API user originally received. It has
|
|
125
125
|
// a slight complication in solving - since we cannot rely on the send id, but rather
|
|
126
126
|
// need to find the actual subscription id to map it
|
|
127
|
-
if ((0, util_1.isUndefined)(this
|
|
127
|
+
if ((0, util_1.isUndefined)(this.#subscriptions[subscription])) {
|
|
128
128
|
l.debug(() => `Unable to find active subscription=${subscription}`);
|
|
129
129
|
return false;
|
|
130
130
|
}
|
|
131
|
-
delete this
|
|
131
|
+
delete this.#subscriptions[subscription];
|
|
132
132
|
return this.send(method, [id]);
|
|
133
133
|
}
|
|
134
134
|
}
|