@polkadot/extension-base 0.44.9 → 0.45.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.
Files changed (58) hide show
  1. package/background/RequestBytesSign.js +9 -11
  2. package/background/RequestExtrinsicSign.js +9 -11
  3. package/background/handlers/Extension.js +456 -611
  4. package/background/handlers/State.js +374 -421
  5. package/background/handlers/Tabs.js +174 -211
  6. package/background/handlers/helpers.js +8 -10
  7. package/background/handlers/index.js +29 -39
  8. package/background/handlers/subscriptions.js +13 -22
  9. package/bundle.js +1 -4
  10. package/cjs/background/RequestBytesSign.js +11 -18
  11. package/cjs/background/RequestExtrinsicSign.js +9 -16
  12. package/cjs/background/handlers/Extension.js +468 -658
  13. package/cjs/background/handlers/State.js +389 -460
  14. package/cjs/background/handlers/Tabs.js +185 -242
  15. package/cjs/background/handlers/helpers.js +12 -16
  16. package/cjs/background/handlers/index.js +37 -52
  17. package/cjs/background/handlers/subscriptions.js +17 -28
  18. package/cjs/background/types.js +2 -1
  19. package/cjs/bundle.js +4 -11
  20. package/cjs/defaults.js +3 -12
  21. package/cjs/detectOther.js +5 -12
  22. package/cjs/detectPackage.js +6 -11
  23. package/cjs/index.js +3 -15
  24. package/cjs/packageInfo.js +2 -16
  25. package/cjs/page/Accounts.js +19 -28
  26. package/cjs/page/Injected.js +19 -26
  27. package/cjs/page/Metadata.js +10 -18
  28. package/cjs/page/PostMessageProvider.js +127 -160
  29. package/cjs/page/Signer.js +23 -38
  30. package/cjs/page/index.js +44 -61
  31. package/cjs/page/types.js +2 -1
  32. package/cjs/stores/Accounts.js +17 -22
  33. package/cjs/stores/Base.js +56 -63
  34. package/cjs/stores/Metadata.js +10 -15
  35. package/cjs/stores/index.js +9 -19
  36. package/cjs/types.js +2 -1
  37. package/cjs/utils/canDerive.js +5 -10
  38. package/cjs/utils/getId.js +6 -11
  39. package/cjs/utils/index.js +4 -11
  40. package/defaults.js +1 -8
  41. package/detectOther.js +0 -3
  42. package/detectPackage.js +2 -7
  43. package/index.js +1 -7
  44. package/package.json +20 -17
  45. package/packageInfo.js +1 -11
  46. package/page/Accounts.js +18 -23
  47. package/page/Injected.js +19 -18
  48. package/page/Metadata.js +9 -13
  49. package/page/PostMessageProvider.js +118 -149
  50. package/page/Signer.js +22 -33
  51. package/page/index.js +36 -59
  52. package/stores/Accounts.js +14 -15
  53. package/stores/Base.js +51 -56
  54. package/stores/Metadata.js +7 -8
  55. package/stores/index.js +2 -5
  56. package/utils/canDerive.js +1 -4
  57. package/utils/getId.js +2 -5
  58. package/utils/index.js +1 -4
@@ -1,6 +1,3 @@
1
- // Copyright 2019-2023 @polkadot/extension authors & contributors
2
- // SPDX-License-Identifier: Apache-2.0
3
-
4
1
  import { ALLOWED_PATH, PASSWORD_EXPIRY_MS } from '@polkadot/extension-base/defaults';
5
2
  import { metadataExpand } from '@polkadot/extension-chains';
6
3
  import { TypeRegistry } from '@polkadot/types';
@@ -8,620 +5,468 @@ import keyring from '@polkadot/ui-keyring';
8
5
  import { accounts as accountsObservable } from '@polkadot/ui-keyring/observable/accounts';
9
6
  import { assert, isHex } from '@polkadot/util';
10
7
  import { keyExtractSuri, mnemonicGenerate, mnemonicValidate } from '@polkadot/util-crypto';
11
- import { withErrorLog } from "./helpers.js";
12
- import { createSubscription, unsubscribe } from "./subscriptions.js";
8
+ import { withErrorLog } from './helpers.js';
9
+ import { createSubscription, unsubscribe } from './subscriptions.js';
13
10
  const SEED_DEFAULT_LENGTH = 12;
14
11
  const SEED_LENGTHS = [12, 15, 18, 21, 24];
15
12
  const ETH_DERIVE_DEFAULT = "/m/44'/60'/0'/0/0";
16
13
  function getSuri(seed, type) {
17
- return type === 'ethereum' ? `${seed}${ETH_DERIVE_DEFAULT}` : seed;
14
+ return type === 'ethereum'
15
+ ? `${seed}${ETH_DERIVE_DEFAULT}`
16
+ : seed;
18
17
  }
19
18
  function isJsonPayload(value) {
20
- return value.genesisHash !== undefined;
19
+ return value.genesisHash !== undefined;
21
20
  }
22
21
  export default class Extension {
23
- #cachedUnlocks;
24
- #state;
25
- constructor(state) {
26
- this.#cachedUnlocks = {};
27
- this.#state = state;
28
- }
29
- transformAccounts(accounts) {
30
- return Object.values(accounts).map(({
31
- json: {
32
- address,
33
- meta
34
- },
35
- type
36
- }) => ({
37
- address,
38
- isDefaultAuthSelected: this.#state.defaultAuthAccountSelection.includes(address),
39
- ...meta,
40
- type
41
- }));
42
- }
43
- accountsCreateExternal({
44
- address,
45
- genesisHash,
46
- name
47
- }) {
48
- keyring.addExternal(address, {
49
- genesisHash,
50
- name
51
- });
52
- return true;
53
- }
54
- accountsCreateHardware({
55
- accountIndex,
56
- address,
57
- addressOffset,
58
- genesisHash,
59
- hardwareType,
60
- name
61
- }) {
62
- keyring.addHardware(address, hardwareType, {
63
- accountIndex,
64
- addressOffset,
65
- genesisHash,
66
- name
67
- });
68
- return true;
69
- }
70
- accountsCreateSuri({
71
- genesisHash,
72
- name,
73
- password,
74
- suri,
75
- type
76
- }) {
77
- keyring.addUri(getSuri(suri, type), password, {
78
- genesisHash,
79
- name
80
- }, type);
81
- return true;
82
- }
83
- accountsChangePassword({
84
- address,
85
- newPass,
86
- oldPass
87
- }) {
88
- const pair = keyring.getPair(address);
89
- assert(pair, 'Unable to find pair');
90
- try {
91
- if (!pair.isLocked) {
92
- pair.lock();
93
- }
94
- pair.decodePkcs8(oldPass);
95
- } catch (error) {
96
- throw new Error('oldPass is invalid');
97
- }
98
- keyring.encryptAccount(pair, newPass);
99
- return true;
100
- }
101
- accountsEdit({
102
- address,
103
- name
104
- }) {
105
- const pair = keyring.getPair(address);
106
- assert(pair, 'Unable to find pair');
107
- keyring.saveAccountMeta(pair, {
108
- ...pair.meta,
109
- name
110
- });
111
- return true;
112
- }
113
- accountsExport({
114
- address,
115
- password
116
- }) {
117
- return {
118
- exportedJson: keyring.backupAccount(keyring.getPair(address), password)
119
- };
120
- }
121
- async accountsBatchExport({
122
- addresses,
123
- password
124
- }) {
125
- return {
126
- exportedJson: await keyring.backupAccounts(addresses, password)
127
- };
128
- }
129
- accountsForget({
130
- address
131
- }) {
132
- const authorizedAccountsDiff = [];
133
-
134
- // cycle through authUrls and prepare the array of diff
135
- Object.entries(this.#state.authUrls).forEach(([url, urlInfo]) => {
136
- if (!urlInfo.authorizedAccounts.includes(address)) {
137
- return;
138
- }
139
- authorizedAccountsDiff.push([url, urlInfo.authorizedAccounts.filter(previousAddress => previousAddress !== address)]);
140
- });
141
- this.#state.updateAuthorizedAccounts(authorizedAccountsDiff);
142
-
143
- // cycle through default account selection for auth and remove any occurence of the account
144
- const newDefaultAuthAccounts = this.#state.defaultAuthAccountSelection.filter(defaultSelectionAddress => defaultSelectionAddress !== address);
145
- this.#state.updateDefaultAuthAccounts(newDefaultAuthAccounts);
146
- keyring.forgetAccount(address);
147
- return true;
148
- }
149
- refreshAccountPasswordCache(pair) {
150
- const {
151
- address
152
- } = pair;
153
- const savedExpiry = this.#cachedUnlocks[address] || 0;
154
- const remainingTime = savedExpiry - Date.now();
155
- if (remainingTime < 0) {
156
- this.#cachedUnlocks[address] = 0;
157
- pair.lock();
158
- return 0;
159
- }
160
- return remainingTime;
161
- }
162
- accountsShow({
163
- address,
164
- isShowing
165
- }) {
166
- const pair = keyring.getPair(address);
167
- assert(pair, 'Unable to find pair');
168
- keyring.saveAccountMeta(pair, {
169
- ...pair.meta,
170
- isHidden: !isShowing
171
- });
172
- return true;
173
- }
174
- accountsTie({
175
- address,
176
- genesisHash
177
- }) {
178
- const pair = keyring.getPair(address);
179
- assert(pair, 'Unable to find pair');
180
- keyring.saveAccountMeta(pair, {
181
- ...pair.meta,
182
- genesisHash
183
- });
184
- return true;
185
- }
186
- accountsValidate({
187
- address,
188
- password
189
- }) {
190
- try {
191
- keyring.backupAccount(keyring.getPair(address), password);
192
- return true;
193
- } catch (e) {
194
- return false;
195
- }
196
- }
197
- accountsSubscribe(id, port) {
198
- const cb = createSubscription(id, port);
199
- const subscription = accountsObservable.subject.subscribe(accounts => cb(this.transformAccounts(accounts)));
200
- port.onDisconnect.addListener(() => {
201
- unsubscribe(id);
202
- subscription.unsubscribe();
203
- });
204
- return true;
205
- }
206
- authorizeApprove({
207
- authorizedAccounts,
208
- id
209
- }) {
210
- const queued = this.#state.getAuthRequest(id);
211
- assert(queued, 'Unable to find request');
212
- const {
213
- resolve
214
- } = queued;
215
- resolve({
216
- authorizedAccounts,
217
- result: true
218
- });
219
- return true;
220
- }
221
- authorizeUpdate({
222
- authorizedAccounts,
223
- url
224
- }) {
225
- return this.#state.updateAuthorizedAccounts([[url, authorizedAccounts]]);
226
- }
227
- getAuthList() {
228
- return {
229
- list: this.#state.authUrls
230
- };
231
- }
232
-
233
- // FIXME This looks very much like what we have in accounts
234
- authorizeSubscribe(id, port) {
235
- const cb = createSubscription(id, port);
236
- const subscription = this.#state.authSubject.subscribe(requests => cb(requests));
237
- port.onDisconnect.addListener(() => {
238
- unsubscribe(id);
239
- subscription.unsubscribe();
240
- });
241
- return true;
242
- }
243
- metadataApprove({
244
- id
245
- }) {
246
- const queued = this.#state.getMetaRequest(id);
247
- assert(queued, 'Unable to find request');
248
- const {
249
- request,
250
- resolve
251
- } = queued;
252
- this.#state.saveMetadata(request);
253
- resolve(true);
254
- return true;
255
- }
256
- metadataGet(genesisHash) {
257
- return this.#state.knownMetadata.find(result => result.genesisHash === genesisHash) || null;
258
- }
259
- metadataList() {
260
- return this.#state.knownMetadata;
261
- }
262
- metadataReject({
263
- id
264
- }) {
265
- const queued = this.#state.getMetaRequest(id);
266
- assert(queued, 'Unable to find request');
267
- const {
268
- reject
269
- } = queued;
270
- reject(new Error('Rejected'));
271
- return true;
272
- }
273
- metadataSubscribe(id, port) {
274
- const cb = createSubscription(id, port);
275
- const subscription = this.#state.metaSubject.subscribe(requests => cb(requests));
276
- port.onDisconnect.addListener(() => {
277
- unsubscribe(id);
278
- subscription.unsubscribe();
279
- });
280
- return true;
281
- }
282
- jsonRestore({
283
- file,
284
- password
285
- }) {
286
- try {
287
- keyring.restoreAccount(file, password);
288
- } catch (error) {
289
- throw new Error(error.message);
290
- }
291
- }
292
- batchRestore({
293
- file,
294
- password
295
- }) {
296
- try {
297
- keyring.restoreAccounts(file, password);
298
- } catch (error) {
299
- throw new Error(error.message);
300
- }
301
- }
302
- jsonGetAccountInfo(json) {
303
- try {
304
- const {
305
- address,
306
- meta: {
307
- genesisHash,
308
- name
309
- },
310
- type
311
- } = keyring.createFromJson(json);
312
- return {
313
- address,
314
- genesisHash,
315
- name,
316
- type
317
- };
318
- } catch (e) {
319
- console.error(e);
320
- throw new Error(e.message);
321
- }
322
- }
323
- seedCreate({
324
- length = SEED_DEFAULT_LENGTH,
325
- seed: _seed,
326
- type
327
- }) {
328
- const seed = _seed || mnemonicGenerate(length);
329
- return {
330
- address: keyring.createFromUri(getSuri(seed, type), {}, type).address,
331
- seed
332
- };
333
- }
334
- seedValidate({
335
- suri,
336
- type
337
- }) {
338
- const {
339
- phrase
340
- } = keyExtractSuri(suri);
341
- if (isHex(phrase)) {
342
- assert(isHex(phrase, 256), 'Hex seed needs to be 256-bits');
343
- } else {
344
- // sadly isHex detects as string, so we need a cast here
345
- assert(SEED_LENGTHS.includes(phrase.split(' ').length), `Mnemonic needs to contain ${SEED_LENGTHS.join(', ')} words`);
346
- assert(mnemonicValidate(phrase), 'Not a valid mnemonic seed');
347
- }
348
- return {
349
- address: keyring.createFromUri(getSuri(suri, type), {}, type).address,
350
- suri
351
- };
352
- }
353
- signingApprovePassword({
354
- id,
355
- password,
356
- savePass
357
- }) {
358
- const queued = this.#state.getSignRequest(id);
359
- assert(queued, 'Unable to find request');
360
- const {
361
- reject,
362
- request,
363
- resolve
364
- } = queued;
365
- const pair = keyring.getPair(queued.account.address);
366
- if (!pair) {
367
- reject(new Error('Unable to find pair'));
368
- return false;
369
- }
370
- this.refreshAccountPasswordCache(pair);
371
-
372
- // if the keyring pair is locked, the password is needed
373
- if (pair.isLocked && !password) {
374
- reject(new Error('Password needed to unlock the account'));
375
- }
376
- if (pair.isLocked) {
377
- pair.decodePkcs8(password);
378
- }
379
-
380
- // construct a new registry (avoiding pollution), between requests
381
- let registry;
382
- const {
383
- payload
384
- } = request;
385
- if (isJsonPayload(payload)) {
386
- // Get the metadata for the genesisHash
387
- const metadata = this.#state.knownMetadata.find(({
388
- genesisHash
389
- }) => genesisHash === payload.genesisHash);
390
- if (metadata) {
391
- // we have metadata, expand it and extract the info/registry
392
- const expanded = metadataExpand(metadata, false);
393
- registry = expanded.registry;
394
- registry.setSignedExtensions(payload.signedExtensions, expanded.definition.userExtensions);
395
- } else {
396
- // we have no metadata, create a new registry
397
- registry = new TypeRegistry();
398
- registry.setSignedExtensions(payload.signedExtensions);
399
- }
400
- } else {
401
- // for non-payload, just create a registry to use
402
- registry = new TypeRegistry();
403
- }
404
- const result = request.sign(registry, pair);
405
- if (savePass) {
406
- // unlike queued.account.address the following
407
- // address is encoded with the default prefix
408
- // which what is used for password caching mapping
409
- this.#cachedUnlocks[pair.address] = Date.now() + PASSWORD_EXPIRY_MS;
410
- } else {
411
- pair.lock();
412
- }
413
- resolve({
414
- id,
415
- ...result
416
- });
417
- return true;
418
- }
419
- signingApproveSignature({
420
- id,
421
- signature
422
- }) {
423
- const queued = this.#state.getSignRequest(id);
424
- assert(queued, 'Unable to find request');
425
- const {
426
- resolve
427
- } = queued;
428
- resolve({
429
- id,
430
- signature
431
- });
432
- return true;
433
- }
434
- signingCancel({
435
- id
436
- }) {
437
- const queued = this.#state.getSignRequest(id);
438
- assert(queued, 'Unable to find request');
439
- const {
440
- reject
441
- } = queued;
442
- reject(new Error('Cancelled'));
443
- return true;
444
- }
445
- signingIsLocked({
446
- id
447
- }) {
448
- const queued = this.#state.getSignRequest(id);
449
- assert(queued, 'Unable to find request');
450
- const address = queued.request.payload.address;
451
- const pair = keyring.getPair(address);
452
- assert(pair, 'Unable to find pair');
453
- const remainingTime = this.refreshAccountPasswordCache(pair);
454
- return {
455
- isLocked: pair.isLocked,
456
- remainingTime
457
- };
458
- }
459
-
460
- // FIXME This looks very much like what we have in authorization
461
- signingSubscribe(id, port) {
462
- const cb = createSubscription(id, port);
463
- const subscription = this.#state.signSubject.subscribe(requests => cb(requests));
464
- port.onDisconnect.addListener(() => {
465
- unsubscribe(id);
466
- subscription.unsubscribe();
467
- });
468
- return true;
469
- }
470
- windowOpen(path) {
471
- const url = `${chrome.extension.getURL('index.html')}#${path}`;
472
- if (!ALLOWED_PATH.includes(path)) {
473
- console.error('Not allowed to open the url:', url);
474
- return false;
475
- }
476
- withErrorLog(() => chrome.tabs.create({
477
- url
478
- }));
479
- return true;
480
- }
481
- derive(parentAddress, suri, password, metadata) {
482
- const parentPair = keyring.getPair(parentAddress);
483
- try {
484
- parentPair.decodePkcs8(password);
485
- } catch (e) {
486
- throw new Error('invalid password');
487
- }
488
- try {
489
- return parentPair.derive(suri, metadata);
490
- } catch (err) {
491
- throw new Error(`"${suri}" is not a valid derivation path`);
492
- }
493
- }
494
- derivationValidate({
495
- parentAddress,
496
- parentPassword,
497
- suri
498
- }) {
499
- const childPair = this.derive(parentAddress, suri, parentPassword, {});
500
- return {
501
- address: childPair.address,
502
- suri
503
- };
504
- }
505
- derivationCreate({
506
- genesisHash,
507
- name,
508
- parentAddress,
509
- parentPassword,
510
- password,
511
- suri
512
- }) {
513
- const childPair = this.derive(parentAddress, suri, parentPassword, {
514
- genesisHash,
515
- name,
516
- parentAddress,
517
- suri
518
- });
519
- keyring.addPair(childPair, password);
520
- return true;
521
- }
522
- removeAuthorization(url) {
523
- return {
524
- list: this.#state.removeAuthorization(url)
525
- };
526
- }
527
- deleteAuthRequest(requestId) {
528
- return this.#state.deleteAuthRequest(requestId);
529
- }
530
- updateCurrentTabs({
531
- urls
532
- }) {
533
- this.#state.udateCurrentTabsUrl(urls);
534
- }
535
- getConnectedTabsUrl() {
536
- return this.#state.getConnectedTabsUrl();
537
- }
538
-
539
- // Weird thought, the eslint override is not needed in Tabs
540
- // eslint-disable-next-line @typescript-eslint/require-await
541
- async handle(id, type, request, port) {
542
- switch (type) {
543
- case 'pri(authorize.approve)':
544
- return this.authorizeApprove(request);
545
- case 'pri(authorize.list)':
546
- return this.getAuthList();
547
- case 'pri(authorize.remove)':
548
- return this.removeAuthorization(request);
549
- case 'pri(authorize.delete.request)':
550
- return this.deleteAuthRequest(request);
551
- case 'pri(authorize.requests)':
552
- return port && this.authorizeSubscribe(id, port);
553
- case 'pri(authorize.update)':
554
- return this.authorizeUpdate(request);
555
- case 'pri(accounts.create.external)':
556
- return this.accountsCreateExternal(request);
557
- case 'pri(accounts.create.hardware)':
558
- return this.accountsCreateHardware(request);
559
- case 'pri(accounts.create.suri)':
560
- return this.accountsCreateSuri(request);
561
- case 'pri(accounts.changePassword)':
562
- return this.accountsChangePassword(request);
563
- case 'pri(accounts.edit)':
564
- return this.accountsEdit(request);
565
- case 'pri(accounts.export)':
566
- return this.accountsExport(request);
567
- case 'pri(accounts.batchExport)':
568
- return this.accountsBatchExport(request);
569
- case 'pri(accounts.forget)':
570
- return this.accountsForget(request);
571
- case 'pri(accounts.show)':
572
- return this.accountsShow(request);
573
- case 'pri(accounts.subscribe)':
574
- return port && this.accountsSubscribe(id, port);
575
- case 'pri(accounts.tie)':
576
- return this.accountsTie(request);
577
- case 'pri(accounts.validate)':
578
- return this.accountsValidate(request);
579
- case 'pri(metadata.approve)':
580
- return this.metadataApprove(request);
581
- case 'pri(metadata.get)':
582
- return this.metadataGet(request);
583
- case 'pri(metadata.list)':
584
- return this.metadataList();
585
- case 'pri(metadata.reject)':
586
- return this.metadataReject(request);
587
- case 'pri(metadata.requests)':
588
- return port && this.metadataSubscribe(id, port);
589
- case 'pri(activeTabsUrl.update)':
590
- return this.updateCurrentTabs(request);
591
- case 'pri(connectedTabsUrl.get)':
592
- return this.getConnectedTabsUrl();
593
- case 'pri(derivation.create)':
594
- return this.derivationCreate(request);
595
- case 'pri(derivation.validate)':
596
- return this.derivationValidate(request);
597
- case 'pri(json.restore)':
598
- return this.jsonRestore(request);
599
- case 'pri(json.batchRestore)':
600
- return this.batchRestore(request);
601
- case 'pri(json.account.info)':
602
- return this.jsonGetAccountInfo(request);
603
- case 'pri(ping)':
604
- return Promise.resolve(true);
605
- case 'pri(seed.create)':
606
- return this.seedCreate(request);
607
- case 'pri(seed.validate)':
608
- return this.seedValidate(request);
609
- case 'pri(settings.notification)':
610
- return this.#state.setNotification(request);
611
- case 'pri(signing.approve.password)':
612
- return this.signingApprovePassword(request);
613
- case 'pri(signing.approve.signature)':
614
- return this.signingApproveSignature(request);
615
- case 'pri(signing.cancel)':
616
- return this.signingCancel(request);
617
- case 'pri(signing.isLocked)':
618
- return this.signingIsLocked(request);
619
- case 'pri(signing.requests)':
620
- return port && this.signingSubscribe(id, port);
621
- case 'pri(window.open)':
622
- return this.windowOpen(request);
623
- default:
624
- throw new Error(`Unable to handle message of type ${type}`);
625
- }
626
- }
22
+ #cachedUnlocks;
23
+ #state;
24
+ constructor(state) {
25
+ this.#cachedUnlocks = {};
26
+ this.#state = state;
27
+ }
28
+ transformAccounts(accounts) {
29
+ return Object.values(accounts).map(({ json: { address, meta }, type }) => ({
30
+ address,
31
+ isDefaultAuthSelected: this.#state.defaultAuthAccountSelection.includes(address),
32
+ ...meta,
33
+ type
34
+ }));
35
+ }
36
+ accountsCreateExternal({ address, genesisHash, name }) {
37
+ keyring.addExternal(address, { genesisHash, name });
38
+ return true;
39
+ }
40
+ accountsCreateHardware({ accountIndex, address, addressOffset, genesisHash, hardwareType, name }) {
41
+ keyring.addHardware(address, hardwareType, { accountIndex, addressOffset, genesisHash, name });
42
+ return true;
43
+ }
44
+ accountsCreateSuri({ genesisHash, name, password, suri, type }) {
45
+ keyring.addUri(getSuri(suri, type), password, { genesisHash, name }, type);
46
+ return true;
47
+ }
48
+ accountsChangePassword({ address, newPass, oldPass }) {
49
+ const pair = keyring.getPair(address);
50
+ assert(pair, 'Unable to find pair');
51
+ try {
52
+ if (!pair.isLocked) {
53
+ pair.lock();
54
+ }
55
+ pair.decodePkcs8(oldPass);
56
+ }
57
+ catch (error) {
58
+ throw new Error('oldPass is invalid');
59
+ }
60
+ keyring.encryptAccount(pair, newPass);
61
+ return true;
62
+ }
63
+ accountsEdit({ address, name }) {
64
+ const pair = keyring.getPair(address);
65
+ assert(pair, 'Unable to find pair');
66
+ keyring.saveAccountMeta(pair, { ...pair.meta, name });
67
+ return true;
68
+ }
69
+ accountsExport({ address, password }) {
70
+ return { exportedJson: keyring.backupAccount(keyring.getPair(address), password) };
71
+ }
72
+ async accountsBatchExport({ addresses, password }) {
73
+ return {
74
+ exportedJson: await keyring.backupAccounts(addresses, password)
75
+ };
76
+ }
77
+ accountsForget({ address }) {
78
+ const authorizedAccountsDiff = [];
79
+ // cycle through authUrls and prepare the array of diff
80
+ Object.entries(this.#state.authUrls).forEach(([url, urlInfo]) => {
81
+ if (!urlInfo.authorizedAccounts.includes(address)) {
82
+ return;
83
+ }
84
+ authorizedAccountsDiff.push([url, urlInfo.authorizedAccounts.filter((previousAddress) => previousAddress !== address)]);
85
+ });
86
+ this.#state.updateAuthorizedAccounts(authorizedAccountsDiff);
87
+ // cycle through default account selection for auth and remove any occurence of the account
88
+ const newDefaultAuthAccounts = this.#state.defaultAuthAccountSelection.filter((defaultSelectionAddress) => defaultSelectionAddress !== address);
89
+ this.#state.updateDefaultAuthAccounts(newDefaultAuthAccounts);
90
+ keyring.forgetAccount(address);
91
+ return true;
92
+ }
93
+ refreshAccountPasswordCache(pair) {
94
+ const { address } = pair;
95
+ const savedExpiry = this.#cachedUnlocks[address] || 0;
96
+ const remainingTime = savedExpiry - Date.now();
97
+ if (remainingTime < 0) {
98
+ this.#cachedUnlocks[address] = 0;
99
+ pair.lock();
100
+ return 0;
101
+ }
102
+ return remainingTime;
103
+ }
104
+ accountsShow({ address, isShowing }) {
105
+ const pair = keyring.getPair(address);
106
+ assert(pair, 'Unable to find pair');
107
+ keyring.saveAccountMeta(pair, { ...pair.meta, isHidden: !isShowing });
108
+ return true;
109
+ }
110
+ accountsTie({ address, genesisHash }) {
111
+ const pair = keyring.getPair(address);
112
+ assert(pair, 'Unable to find pair');
113
+ keyring.saveAccountMeta(pair, { ...pair.meta, genesisHash });
114
+ return true;
115
+ }
116
+ accountsValidate({ address, password }) {
117
+ try {
118
+ keyring.backupAccount(keyring.getPair(address), password);
119
+ return true;
120
+ }
121
+ catch (e) {
122
+ return false;
123
+ }
124
+ }
125
+ accountsSubscribe(id, port) {
126
+ const cb = createSubscription(id, port);
127
+ const subscription = accountsObservable.subject.subscribe((accounts) => cb(this.transformAccounts(accounts)));
128
+ port.onDisconnect.addListener(() => {
129
+ unsubscribe(id);
130
+ subscription.unsubscribe();
131
+ });
132
+ return true;
133
+ }
134
+ authorizeApprove({ authorizedAccounts, id }) {
135
+ const queued = this.#state.getAuthRequest(id);
136
+ assert(queued, 'Unable to find request');
137
+ const { resolve } = queued;
138
+ resolve({ authorizedAccounts, result: true });
139
+ return true;
140
+ }
141
+ authorizeUpdate({ authorizedAccounts, url }) {
142
+ return this.#state.updateAuthorizedAccounts([[url, authorizedAccounts]]);
143
+ }
144
+ getAuthList() {
145
+ return { list: this.#state.authUrls };
146
+ }
147
+ // FIXME This looks very much like what we have in accounts
148
+ authorizeSubscribe(id, port) {
149
+ const cb = createSubscription(id, port);
150
+ const subscription = this.#state.authSubject.subscribe((requests) => cb(requests));
151
+ port.onDisconnect.addListener(() => {
152
+ unsubscribe(id);
153
+ subscription.unsubscribe();
154
+ });
155
+ return true;
156
+ }
157
+ metadataApprove({ id }) {
158
+ const queued = this.#state.getMetaRequest(id);
159
+ assert(queued, 'Unable to find request');
160
+ const { request, resolve } = queued;
161
+ this.#state.saveMetadata(request);
162
+ resolve(true);
163
+ return true;
164
+ }
165
+ metadataGet(genesisHash) {
166
+ return this.#state.knownMetadata.find((result) => result.genesisHash === genesisHash) || null;
167
+ }
168
+ metadataList() {
169
+ return this.#state.knownMetadata;
170
+ }
171
+ metadataReject({ id }) {
172
+ const queued = this.#state.getMetaRequest(id);
173
+ assert(queued, 'Unable to find request');
174
+ const { reject } = queued;
175
+ reject(new Error('Rejected'));
176
+ return true;
177
+ }
178
+ metadataSubscribe(id, port) {
179
+ const cb = createSubscription(id, port);
180
+ const subscription = this.#state.metaSubject.subscribe((requests) => cb(requests));
181
+ port.onDisconnect.addListener(() => {
182
+ unsubscribe(id);
183
+ subscription.unsubscribe();
184
+ });
185
+ return true;
186
+ }
187
+ jsonRestore({ file, password }) {
188
+ try {
189
+ keyring.restoreAccount(file, password);
190
+ }
191
+ catch (error) {
192
+ throw new Error(error.message);
193
+ }
194
+ }
195
+ batchRestore({ file, password }) {
196
+ try {
197
+ keyring.restoreAccounts(file, password);
198
+ }
199
+ catch (error) {
200
+ throw new Error(error.message);
201
+ }
202
+ }
203
+ jsonGetAccountInfo(json) {
204
+ try {
205
+ const { address, meta: { genesisHash, name }, type } = keyring.createFromJson(json);
206
+ return {
207
+ address,
208
+ genesisHash,
209
+ name,
210
+ type
211
+ };
212
+ }
213
+ catch (e) {
214
+ console.error(e);
215
+ throw new Error(e.message);
216
+ }
217
+ }
218
+ seedCreate({ length = SEED_DEFAULT_LENGTH, seed: _seed, type }) {
219
+ const seed = _seed || mnemonicGenerate(length);
220
+ return {
221
+ address: keyring.createFromUri(getSuri(seed, type), {}, type).address,
222
+ seed
223
+ };
224
+ }
225
+ seedValidate({ suri, type }) {
226
+ const { phrase } = keyExtractSuri(suri);
227
+ if (isHex(phrase)) {
228
+ assert(isHex(phrase, 256), 'Hex seed needs to be 256-bits');
229
+ }
230
+ else {
231
+ // sadly isHex detects as string, so we need a cast here
232
+ assert(SEED_LENGTHS.includes((phrase).split(' ').length), `Mnemonic needs to contain ${SEED_LENGTHS.join(', ')} words`);
233
+ assert(mnemonicValidate(phrase), 'Not a valid mnemonic seed');
234
+ }
235
+ return {
236
+ address: keyring.createFromUri(getSuri(suri, type), {}, type).address,
237
+ suri
238
+ };
239
+ }
240
+ signingApprovePassword({ id, password, savePass }) {
241
+ const queued = this.#state.getSignRequest(id);
242
+ assert(queued, 'Unable to find request');
243
+ const { reject, request, resolve } = queued;
244
+ const pair = keyring.getPair(queued.account.address);
245
+ if (!pair) {
246
+ reject(new Error('Unable to find pair'));
247
+ return false;
248
+ }
249
+ this.refreshAccountPasswordCache(pair);
250
+ // if the keyring pair is locked, the password is needed
251
+ if (pair.isLocked && !password) {
252
+ reject(new Error('Password needed to unlock the account'));
253
+ }
254
+ if (pair.isLocked) {
255
+ pair.decodePkcs8(password);
256
+ }
257
+ // construct a new registry (avoiding pollution), between requests
258
+ let registry;
259
+ const { payload } = request;
260
+ if (isJsonPayload(payload)) {
261
+ // Get the metadata for the genesisHash
262
+ const metadata = this.#state.knownMetadata.find(({ genesisHash }) => genesisHash === payload.genesisHash);
263
+ if (metadata) {
264
+ // we have metadata, expand it and extract the info/registry
265
+ const expanded = metadataExpand(metadata, false);
266
+ registry = expanded.registry;
267
+ registry.setSignedExtensions(payload.signedExtensions, expanded.definition.userExtensions);
268
+ }
269
+ else {
270
+ // we have no metadata, create a new registry
271
+ registry = new TypeRegistry();
272
+ registry.setSignedExtensions(payload.signedExtensions);
273
+ }
274
+ }
275
+ else {
276
+ // for non-payload, just create a registry to use
277
+ registry = new TypeRegistry();
278
+ }
279
+ const result = request.sign(registry, pair);
280
+ if (savePass) {
281
+ // unlike queued.account.address the following
282
+ // address is encoded with the default prefix
283
+ // which what is used for password caching mapping
284
+ this.#cachedUnlocks[pair.address] = Date.now() + PASSWORD_EXPIRY_MS;
285
+ }
286
+ else {
287
+ pair.lock();
288
+ }
289
+ resolve({
290
+ id,
291
+ ...result
292
+ });
293
+ return true;
294
+ }
295
+ signingApproveSignature({ id, signature }) {
296
+ const queued = this.#state.getSignRequest(id);
297
+ assert(queued, 'Unable to find request');
298
+ const { resolve } = queued;
299
+ resolve({ id, signature });
300
+ return true;
301
+ }
302
+ signingCancel({ id }) {
303
+ const queued = this.#state.getSignRequest(id);
304
+ assert(queued, 'Unable to find request');
305
+ const { reject } = queued;
306
+ reject(new Error('Cancelled'));
307
+ return true;
308
+ }
309
+ signingIsLocked({ id }) {
310
+ const queued = this.#state.getSignRequest(id);
311
+ assert(queued, 'Unable to find request');
312
+ const address = queued.request.payload.address;
313
+ const pair = keyring.getPair(address);
314
+ assert(pair, 'Unable to find pair');
315
+ const remainingTime = this.refreshAccountPasswordCache(pair);
316
+ return {
317
+ isLocked: pair.isLocked,
318
+ remainingTime
319
+ };
320
+ }
321
+ // FIXME This looks very much like what we have in authorization
322
+ signingSubscribe(id, port) {
323
+ const cb = createSubscription(id, port);
324
+ const subscription = this.#state.signSubject.subscribe((requests) => cb(requests));
325
+ port.onDisconnect.addListener(() => {
326
+ unsubscribe(id);
327
+ subscription.unsubscribe();
328
+ });
329
+ return true;
330
+ }
331
+ windowOpen(path) {
332
+ const url = `${chrome.extension.getURL('index.html')}#${path}`;
333
+ if (!ALLOWED_PATH.includes(path)) {
334
+ console.error('Not allowed to open the url:', url);
335
+ return false;
336
+ }
337
+ withErrorLog(() => chrome.tabs.create({ url }));
338
+ return true;
339
+ }
340
+ derive(parentAddress, suri, password, metadata) {
341
+ const parentPair = keyring.getPair(parentAddress);
342
+ try {
343
+ parentPair.decodePkcs8(password);
344
+ }
345
+ catch (e) {
346
+ throw new Error('invalid password');
347
+ }
348
+ try {
349
+ return parentPair.derive(suri, metadata);
350
+ }
351
+ catch (err) {
352
+ throw new Error(`"${suri}" is not a valid derivation path`);
353
+ }
354
+ }
355
+ derivationValidate({ parentAddress, parentPassword, suri }) {
356
+ const childPair = this.derive(parentAddress, suri, parentPassword, {});
357
+ return {
358
+ address: childPair.address,
359
+ suri
360
+ };
361
+ }
362
+ derivationCreate({ genesisHash, name, parentAddress, parentPassword, password, suri }) {
363
+ const childPair = this.derive(parentAddress, suri, parentPassword, {
364
+ genesisHash,
365
+ name,
366
+ parentAddress,
367
+ suri
368
+ });
369
+ keyring.addPair(childPair, password);
370
+ return true;
371
+ }
372
+ removeAuthorization(url) {
373
+ return { list: this.#state.removeAuthorization(url) };
374
+ }
375
+ deleteAuthRequest(requestId) {
376
+ return this.#state.deleteAuthRequest(requestId);
377
+ }
378
+ updateCurrentTabs({ urls }) {
379
+ this.#state.udateCurrentTabsUrl(urls);
380
+ }
381
+ getConnectedTabsUrl() {
382
+ return this.#state.getConnectedTabsUrl();
383
+ }
384
+ // Weird thought, the eslint override is not needed in Tabs
385
+ // eslint-disable-next-line @typescript-eslint/require-await
386
+ async handle(id, type, request, port) {
387
+ switch (type) {
388
+ case 'pri(authorize.approve)':
389
+ return this.authorizeApprove(request);
390
+ case 'pri(authorize.list)':
391
+ return this.getAuthList();
392
+ case 'pri(authorize.remove)':
393
+ return this.removeAuthorization(request);
394
+ case 'pri(authorize.delete.request)':
395
+ return this.deleteAuthRequest(request);
396
+ case 'pri(authorize.requests)':
397
+ return port && this.authorizeSubscribe(id, port);
398
+ case 'pri(authorize.update)':
399
+ return this.authorizeUpdate(request);
400
+ case 'pri(accounts.create.external)':
401
+ return this.accountsCreateExternal(request);
402
+ case 'pri(accounts.create.hardware)':
403
+ return this.accountsCreateHardware(request);
404
+ case 'pri(accounts.create.suri)':
405
+ return this.accountsCreateSuri(request);
406
+ case 'pri(accounts.changePassword)':
407
+ return this.accountsChangePassword(request);
408
+ case 'pri(accounts.edit)':
409
+ return this.accountsEdit(request);
410
+ case 'pri(accounts.export)':
411
+ return this.accountsExport(request);
412
+ case 'pri(accounts.batchExport)':
413
+ return this.accountsBatchExport(request);
414
+ case 'pri(accounts.forget)':
415
+ return this.accountsForget(request);
416
+ case 'pri(accounts.show)':
417
+ return this.accountsShow(request);
418
+ case 'pri(accounts.subscribe)':
419
+ return port && this.accountsSubscribe(id, port);
420
+ case 'pri(accounts.tie)':
421
+ return this.accountsTie(request);
422
+ case 'pri(accounts.validate)':
423
+ return this.accountsValidate(request);
424
+ case 'pri(metadata.approve)':
425
+ return this.metadataApprove(request);
426
+ case 'pri(metadata.get)':
427
+ return this.metadataGet(request);
428
+ case 'pri(metadata.list)':
429
+ return this.metadataList();
430
+ case 'pri(metadata.reject)':
431
+ return this.metadataReject(request);
432
+ case 'pri(metadata.requests)':
433
+ return port && this.metadataSubscribe(id, port);
434
+ case 'pri(activeTabsUrl.update)':
435
+ return this.updateCurrentTabs(request);
436
+ case 'pri(connectedTabsUrl.get)':
437
+ return this.getConnectedTabsUrl();
438
+ case 'pri(derivation.create)':
439
+ return this.derivationCreate(request);
440
+ case 'pri(derivation.validate)':
441
+ return this.derivationValidate(request);
442
+ case 'pri(json.restore)':
443
+ return this.jsonRestore(request);
444
+ case 'pri(json.batchRestore)':
445
+ return this.batchRestore(request);
446
+ case 'pri(json.account.info)':
447
+ return this.jsonGetAccountInfo(request);
448
+ case 'pri(ping)':
449
+ return Promise.resolve(true);
450
+ case 'pri(seed.create)':
451
+ return this.seedCreate(request);
452
+ case 'pri(seed.validate)':
453
+ return this.seedValidate(request);
454
+ case 'pri(settings.notification)':
455
+ return this.#state.setNotification(request);
456
+ case 'pri(signing.approve.password)':
457
+ return this.signingApprovePassword(request);
458
+ case 'pri(signing.approve.signature)':
459
+ return this.signingApproveSignature(request);
460
+ case 'pri(signing.cancel)':
461
+ return this.signingCancel(request);
462
+ case 'pri(signing.isLocked)':
463
+ return this.signingIsLocked(request);
464
+ case 'pri(signing.requests)':
465
+ return port && this.signingSubscribe(id, port);
466
+ case 'pri(window.open)':
467
+ return this.windowOpen(request);
468
+ default:
469
+ throw new Error(`Unable to handle message of type ${type}`);
470
+ }
471
+ }
627
472
  }