@polkadot/extension-base 0.60.1 → 0.61.2

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.
@@ -44,7 +44,7 @@ export default class State {
44
44
  readonly signSubject: BehaviorSubject<SigningRequest[]>;
45
45
  readonly authUrlSubjects: Record<string, BehaviorSubject<AuthUrlInfo>>;
46
46
  defaultAuthAccountSelection: string[];
47
- constructor(providers?: Providers);
47
+ constructor(providers?: Providers, rateLimitInterval?: number);
48
48
  init(): Promise<void>;
49
49
  get knownMetadata(): MetadataDef[];
50
50
  get numAuthRequests(): number;
@@ -90,6 +90,7 @@ export default class State {
90
90
  rpcUnsubscribe(request: RequestRpcUnsubscribe, port: chrome.runtime.Port): Promise<boolean>;
91
91
  saveMetadata(meta: MetadataDef): Promise<void>;
92
92
  setNotification(notification: string): boolean;
93
+ private handleSignRequest;
93
94
  sign(url: string, request: RequestSign, account: AccountJson): Promise<ResponseSigning>;
94
95
  }
95
96
  export {};
@@ -64,6 +64,9 @@ async function extractMetadata(store) {
64
64
  }
65
65
  export default class State {
66
66
  #authUrls = new Map();
67
+ #lastRequestTimestamps = new Map();
68
+ #maxEntries = 10;
69
+ #rateLimitInterval = 3000; // 3 seconds
67
70
  #authRequests = {};
68
71
  #metaStore = new MetadataStore();
69
72
  // Map of providers currently injected in tabs
@@ -80,8 +83,10 @@ export default class State {
80
83
  signSubject = new BehaviorSubject([]);
81
84
  authUrlSubjects = {};
82
85
  defaultAuthAccountSelection = [];
83
- constructor(providers = {}) {
86
+ constructor(providers = {}, rateLimitInterval = 3000) {
87
+ assert(rateLimitInterval >= 0, 'Expects non-negative number for rateLimitInterval');
84
88
  this.#providers = providers;
89
+ this.#rateLimitInterval = rateLimitInterval;
85
90
  }
86
91
  async init() {
87
92
  await extractMetadata(this.#metaStore);
@@ -437,8 +442,27 @@ export default class State {
437
442
  this.#notification = notification;
438
443
  return true;
439
444
  }
445
+ handleSignRequest(origin) {
446
+ const now = Date.now();
447
+ const lastTime = this.#lastRequestTimestamps.get(origin) || 0;
448
+ if (now - lastTime < this.#rateLimitInterval) {
449
+ throw new Error('Rate limit exceeded. Try again later.');
450
+ }
451
+ // If we're about to exceed max entries, evict the oldest
452
+ if (!this.#lastRequestTimestamps.has(origin) && this.#lastRequestTimestamps.size >= this.#maxEntries) {
453
+ const oldestKey = this.#lastRequestTimestamps.keys().next().value;
454
+ oldestKey && this.#lastRequestTimestamps.delete(oldestKey);
455
+ }
456
+ this.#lastRequestTimestamps.set(origin, now);
457
+ }
440
458
  sign(url, request, account) {
441
459
  const id = getId();
460
+ try {
461
+ this.handleSignRequest(url);
462
+ }
463
+ catch (error) {
464
+ return Promise.reject(error);
465
+ }
442
466
  return new Promise((resolve, reject) => {
443
467
  this.#signRequests[id] = {
444
468
  ...this.signComplete(id, resolve, reject),
@@ -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
  }
@@ -1,4 +1,5 @@
1
1
  import { combineLatest } from 'rxjs';
2
+ import { parse } from 'tldts';
2
3
  import { checkIfDenied } from '@polkadot/phishing';
3
4
  import { keyring } from '@polkadot/ui-keyring';
4
5
  import { accounts as accountsObservable } from '@polkadot/ui-keyring/observable/accounts';
@@ -142,6 +143,19 @@ export default class Tabs {
142
143
  .forEach((id) => withErrorLog(() => chrome.tabs.update(id, { url })));
143
144
  });
144
145
  }
146
+ parseUrl(rawUrl) {
147
+ let from = 'extension';
148
+ if (rawUrl) {
149
+ try {
150
+ const { hostname } = parse(rawUrl);
151
+ from = hostname || '<unknown>'; // Only use the hostname
152
+ }
153
+ catch {
154
+ from = '<unknown>';
155
+ }
156
+ }
157
+ return from;
158
+ }
145
159
  async redirectIfPhishing(url) {
146
160
  const isInDenyList = await checkIfDenied(url);
147
161
  if (isInDenyList) {
@@ -152,7 +166,8 @@ export default class Tabs {
152
166
  }
153
167
  async handle(id, type, request, url, port) {
154
168
  if (type === 'pub(phishing.redirectIfDenied)') {
155
- return this.redirectIfPhishing(url);
169
+ const parsedUrl = this.parseUrl(url);
170
+ return this.redirectIfPhishing(parsedUrl);
156
171
  }
157
172
  if (type !== 'pub(authorize.tab)') {
158
173
  this.#state.ensureUrlAuthorized(url);
@@ -44,7 +44,7 @@ export default class State {
44
44
  readonly signSubject: BehaviorSubject<SigningRequest[]>;
45
45
  readonly authUrlSubjects: Record<string, BehaviorSubject<AuthUrlInfo>>;
46
46
  defaultAuthAccountSelection: string[];
47
- constructor(providers?: Providers);
47
+ constructor(providers?: Providers, rateLimitInterval?: number);
48
48
  init(): Promise<void>;
49
49
  get knownMetadata(): MetadataDef[];
50
50
  get numAuthRequests(): number;
@@ -90,6 +90,7 @@ export default class State {
90
90
  rpcUnsubscribe(request: RequestRpcUnsubscribe, port: chrome.runtime.Port): Promise<boolean>;
91
91
  saveMetadata(meta: MetadataDef): Promise<void>;
92
92
  setNotification(notification: string): boolean;
93
+ private handleSignRequest;
93
94
  sign(url: string, request: RequestSign, account: AccountJson): Promise<ResponseSigning>;
94
95
  }
95
96
  export {};
@@ -67,6 +67,9 @@ async function extractMetadata(store) {
67
67
  }
68
68
  class State {
69
69
  #authUrls = new Map();
70
+ #lastRequestTimestamps = new Map();
71
+ #maxEntries = 10;
72
+ #rateLimitInterval = 3000; // 3 seconds
70
73
  #authRequests = {};
71
74
  #metaStore = new index_js_1.MetadataStore();
72
75
  // Map of providers currently injected in tabs
@@ -83,8 +86,10 @@ class State {
83
86
  signSubject = new rxjs_1.BehaviorSubject([]);
84
87
  authUrlSubjects = {};
85
88
  defaultAuthAccountSelection = [];
86
- constructor(providers = {}) {
89
+ constructor(providers = {}, rateLimitInterval = 3000) {
90
+ (0, util_1.assert)(rateLimitInterval >= 0, 'Expects non-negative number for rateLimitInterval');
87
91
  this.#providers = providers;
92
+ this.#rateLimitInterval = rateLimitInterval;
88
93
  }
89
94
  async init() {
90
95
  await extractMetadata(this.#metaStore);
@@ -440,8 +445,27 @@ class State {
440
445
  this.#notification = notification;
441
446
  return true;
442
447
  }
448
+ handleSignRequest(origin) {
449
+ const now = Date.now();
450
+ const lastTime = this.#lastRequestTimestamps.get(origin) || 0;
451
+ if (now - lastTime < this.#rateLimitInterval) {
452
+ throw new Error('Rate limit exceeded. Try again later.');
453
+ }
454
+ // If we're about to exceed max entries, evict the oldest
455
+ if (!this.#lastRequestTimestamps.has(origin) && this.#lastRequestTimestamps.size >= this.#maxEntries) {
456
+ const oldestKey = this.#lastRequestTimestamps.keys().next().value;
457
+ oldestKey && this.#lastRequestTimestamps.delete(oldestKey);
458
+ }
459
+ this.#lastRequestTimestamps.set(origin, now);
460
+ }
443
461
  sign(url, request, account) {
444
462
  const id = (0, getId_js_1.getId)();
463
+ try {
464
+ this.handleSignRequest(url);
465
+ }
466
+ catch (error) {
467
+ return Promise.reject(error);
468
+ }
445
469
  return new Promise((resolve, reject) => {
446
470
  this.#signRequests[id] = {
447
471
  ...this.signComplete(id, resolve, reject),
@@ -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");
@@ -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,7 +169,8 @@ class Tabs {
155
169
  }
156
170
  async handle(id, type, request, url, port) {
157
171
  if (type === 'pub(phishing.redirectIfDenied)') {
158
- return this.redirectIfPhishing(url);
172
+ const parsedUrl = this.parseUrl(url);
173
+ return this.redirectIfPhishing(parsedUrl);
159
174
  }
160
175
  if (type !== 'pub(authorize.tab)') {
161
176
  this.#state.ensureUrlAuthorized(url);
@@ -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.60.1' };
4
+ exports.packageInfo = { name: '@polkadot/extension-base', path: typeof __dirname === 'string' ? __dirname : 'auto', type: 'cjs', version: '0.61.2' };
package/package.json CHANGED
@@ -18,7 +18,7 @@
18
18
  "./cjs/packageDetect.js"
19
19
  ],
20
20
  "type": "module",
21
- "version": "0.60.1",
21
+ "version": "0.61.2",
22
22
  "main": "./cjs/index.js",
23
23
  "module": "./index.js",
24
24
  "types": "./index.d.ts",
@@ -479,21 +479,22 @@
479
479
  }
480
480
  },
481
481
  "dependencies": {
482
- "@polkadot/api": "^16.2.2",
483
- "@polkadot/extension-chains": "0.60.1",
484
- "@polkadot/extension-dapp": "0.60.1",
485
- "@polkadot/extension-inject": "0.60.1",
486
- "@polkadot/keyring": "^13.5.2",
487
- "@polkadot/networks": "^13.5.2",
488
- "@polkadot/phishing": "^0.25.13",
489
- "@polkadot/rpc-provider": "^16.2.2",
490
- "@polkadot/types": "^16.2.2",
491
- "@polkadot/ui-keyring": "^3.15.1",
492
- "@polkadot/ui-settings": "^3.15.1",
493
- "@polkadot/util": "^13.5.2",
494
- "@polkadot/util-crypto": "^13.5.2",
482
+ "@polkadot/api": "^16.3.1",
483
+ "@polkadot/extension-chains": "0.61.2",
484
+ "@polkadot/extension-dapp": "0.61.2",
485
+ "@polkadot/extension-inject": "0.61.2",
486
+ "@polkadot/keyring": "^13.5.3",
487
+ "@polkadot/networks": "^13.5.3",
488
+ "@polkadot/phishing": "^0.25.14",
489
+ "@polkadot/rpc-provider": "^16.3.1",
490
+ "@polkadot/types": "^16.3.1",
491
+ "@polkadot/ui-keyring": "^3.15.2",
492
+ "@polkadot/ui-settings": "^3.15.2",
493
+ "@polkadot/util": "^13.5.3",
494
+ "@polkadot/util-crypto": "^13.5.3",
495
495
  "eventemitter3": "^5.0.1",
496
496
  "rxjs": "^7.8.1",
497
+ "tldts": "^7.0.8",
497
498
  "tslib": "^2.8.1"
498
499
  }
499
500
  }
package/packageInfo.js CHANGED
@@ -1 +1 @@
1
- export const packageInfo = { name: '@polkadot/extension-base', path: (import.meta && import.meta.url) ? new URL(import.meta.url).pathname.substring(0, new URL(import.meta.url).pathname.lastIndexOf('/') + 1) : 'auto', type: 'esm', version: '0.60.1' };
1
+ export const packageInfo = { name: '@polkadot/extension-base', path: (import.meta && import.meta.url) ? new URL(import.meta.url).pathname.substring(0, new URL(import.meta.url).pathname.lastIndexOf('/') + 1) : 'auto', type: 'esm', version: '0.61.2' };