@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.
@@ -19,16 +19,16 @@ function isJsonPayload(value) {
19
19
  return value.genesisHash !== undefined;
20
20
  }
21
21
  export default class Extension {
22
- __internal__cachedUnlocks;
23
- __internal__state;
22
+ #cachedUnlocks;
23
+ #state;
24
24
  constructor(state) {
25
- this.__internal__cachedUnlocks = {};
26
- this.__internal__state = state;
25
+ this.#cachedUnlocks = {};
26
+ this.#state = state;
27
27
  }
28
28
  transformAccounts(accounts) {
29
29
  return Object.values(accounts).map(({ json: { address, meta }, type }) => ({
30
30
  address,
31
- isDefaultAuthSelected: this.__internal__state.defaultAuthAccountSelection.includes(address),
31
+ isDefaultAuthSelected: this.#state.defaultAuthAccountSelection.includes(address),
32
32
  ...meta,
33
33
  type
34
34
  }));
@@ -77,26 +77,26 @@ export default class Extension {
77
77
  async accountsForget({ address }) {
78
78
  const authorizedAccountsDiff = [];
79
79
  // cycle through authUrls and prepare the array of diff
80
- Object.entries(this.__internal__state.authUrls).forEach(([url, urlInfo]) => {
80
+ Object.entries(this.#state.authUrls).forEach(([url, urlInfo]) => {
81
81
  // Note that urlInfo.authorizedAccounts may be undefined if this website entry
82
82
  // was created before the "account authorization per website" functionality was introduced
83
83
  if (urlInfo.authorizedAccounts?.includes(address)) {
84
84
  authorizedAccountsDiff.push([url, urlInfo.authorizedAccounts.filter((previousAddress) => previousAddress !== address)]);
85
85
  }
86
86
  });
87
- await this.__internal__state.updateAuthorizedAccounts(authorizedAccountsDiff);
87
+ await this.#state.updateAuthorizedAccounts(authorizedAccountsDiff);
88
88
  // cycle through default account selection for auth and remove any occurrence of the account
89
- const newDefaultAuthAccounts = this.__internal__state.defaultAuthAccountSelection.filter((defaultSelectionAddress) => defaultSelectionAddress !== address);
90
- await this.__internal__state.updateDefaultAuthAccounts(newDefaultAuthAccounts);
89
+ const newDefaultAuthAccounts = this.#state.defaultAuthAccountSelection.filter((defaultSelectionAddress) => defaultSelectionAddress !== address);
90
+ await this.#state.updateDefaultAuthAccounts(newDefaultAuthAccounts);
91
91
  keyring.forgetAccount(address);
92
92
  return true;
93
93
  }
94
94
  refreshAccountPasswordCache(pair) {
95
95
  const { address } = pair;
96
- const savedExpiry = this.__internal__cachedUnlocks[address] || 0;
96
+ const savedExpiry = this.#cachedUnlocks[address] || 0;
97
97
  const remainingTime = savedExpiry - Date.now();
98
98
  if (remainingTime < 0) {
99
- this.__internal__cachedUnlocks[address] = 0;
99
+ this.#cachedUnlocks[address] = 0;
100
100
  pair.lock();
101
101
  return 0;
102
102
  }
@@ -133,22 +133,22 @@ export default class Extension {
133
133
  return true;
134
134
  }
135
135
  authorizeApprove({ authorizedAccounts, id }) {
136
- const queued = this.__internal__state.getAuthRequest(id);
136
+ const queued = this.#state.getAuthRequest(id);
137
137
  assert(queued, 'Unable to find request');
138
138
  const { resolve } = queued;
139
139
  resolve({ authorizedAccounts, result: true });
140
140
  return true;
141
141
  }
142
142
  async authorizeUpdate({ authorizedAccounts, url }) {
143
- return await this.__internal__state.updateAuthorizedAccounts([[url, authorizedAccounts]]);
143
+ return await this.#state.updateAuthorizedAccounts([[url, authorizedAccounts]]);
144
144
  }
145
145
  getAuthList() {
146
- return { list: this.__internal__state.authUrls };
146
+ return { list: this.#state.authUrls };
147
147
  }
148
148
  // FIXME This looks very much like what we have in accounts
149
149
  authorizeSubscribe(id, port) {
150
150
  const cb = createSubscription(id, port);
151
- const subscription = this.__internal__state.authSubject.subscribe((requests) => cb(requests));
151
+ const subscription = this.#state.authSubject.subscribe((requests) => cb(requests));
152
152
  port.onDisconnect.addListener(() => {
153
153
  unsubscribe(id);
154
154
  subscription.unsubscribe();
@@ -156,21 +156,21 @@ export default class Extension {
156
156
  return true;
157
157
  }
158
158
  async metadataApprove({ id }) {
159
- const queued = this.__internal__state.getMetaRequest(id);
159
+ const queued = this.#state.getMetaRequest(id);
160
160
  assert(queued, 'Unable to find request');
161
161
  const { request, resolve } = queued;
162
- await this.__internal__state.saveMetadata(request);
162
+ await this.#state.saveMetadata(request);
163
163
  resolve(true);
164
164
  return true;
165
165
  }
166
166
  metadataGet(genesisHash) {
167
- return this.__internal__state.knownMetadata.find((result) => result.genesisHash === genesisHash) || null;
167
+ return this.#state.knownMetadata.find((result) => result.genesisHash === genesisHash) || null;
168
168
  }
169
169
  metadataList() {
170
- return this.__internal__state.knownMetadata;
170
+ return this.#state.knownMetadata;
171
171
  }
172
172
  metadataReject({ id }) {
173
- const queued = this.__internal__state.getMetaRequest(id);
173
+ const queued = this.#state.getMetaRequest(id);
174
174
  assert(queued, 'Unable to find request');
175
175
  const { reject } = queued;
176
176
  reject(new Error('Rejected'));
@@ -178,7 +178,7 @@ export default class Extension {
178
178
  }
179
179
  metadataSubscribe(id, port) {
180
180
  const cb = createSubscription(id, port);
181
- const subscription = this.__internal__state.metaSubject.subscribe((requests) => cb(requests));
181
+ const subscription = this.#state.metaSubject.subscribe((requests) => cb(requests));
182
182
  port.onDisconnect.addListener(() => {
183
183
  unsubscribe(id);
184
184
  subscription.unsubscribe();
@@ -239,7 +239,7 @@ export default class Extension {
239
239
  };
240
240
  }
241
241
  signingApprovePassword({ id, password, savePass }) {
242
- const queued = this.__internal__state.getSignRequest(id);
242
+ const queued = this.#state.getSignRequest(id);
243
243
  assert(queued, 'Unable to find request');
244
244
  const { reject, request, resolve } = queued;
245
245
  const pair = keyring.getPair(queued.account.address);
@@ -260,7 +260,7 @@ export default class Extension {
260
260
  const { payload } = request;
261
261
  if (isJsonPayload(payload)) {
262
262
  // Get the metadata for the genesisHash
263
- const metadata = this.__internal__state.knownMetadata.find(({ genesisHash }) => genesisHash === payload.genesisHash);
263
+ const metadata = this.#state.knownMetadata.find(({ genesisHash }) => genesisHash === payload.genesisHash);
264
264
  if (metadata) {
265
265
  // we have metadata, expand it and extract the info/registry
266
266
  const expanded = metadataExpand(metadata, false);
@@ -282,7 +282,7 @@ export default class Extension {
282
282
  // unlike queued.account.address the following
283
283
  // address is encoded with the default prefix
284
284
  // which what is used for password caching mapping
285
- this.__internal__cachedUnlocks[pair.address] = Date.now() + PASSWORD_EXPIRY_MS;
285
+ this.#cachedUnlocks[pair.address] = Date.now() + PASSWORD_EXPIRY_MS;
286
286
  }
287
287
  else {
288
288
  pair.lock();
@@ -294,21 +294,21 @@ export default class Extension {
294
294
  return true;
295
295
  }
296
296
  signingApproveSignature({ id, signature, signedTransaction }) {
297
- const queued = this.__internal__state.getSignRequest(id);
297
+ const queued = this.#state.getSignRequest(id);
298
298
  assert(queued, 'Unable to find request');
299
299
  const { resolve } = queued;
300
300
  resolve({ id, signature, signedTransaction });
301
301
  return true;
302
302
  }
303
303
  signingCancel({ id }) {
304
- const queued = this.__internal__state.getSignRequest(id);
304
+ const queued = this.#state.getSignRequest(id);
305
305
  assert(queued, 'Unable to find request');
306
306
  const { reject } = queued;
307
307
  reject(new Error('Cancelled'));
308
308
  return true;
309
309
  }
310
310
  signingIsLocked({ id }) {
311
- const queued = this.__internal__state.getSignRequest(id);
311
+ const queued = this.#state.getSignRequest(id);
312
312
  assert(queued, 'Unable to find request');
313
313
  const address = queued.request.payload.address;
314
314
  const pair = keyring.getPair(address);
@@ -322,7 +322,7 @@ export default class Extension {
322
322
  // FIXME This looks very much like what we have in authorization
323
323
  signingSubscribe(id, port) {
324
324
  const cb = createSubscription(id, port);
325
- const subscription = this.__internal__state.signSubject.subscribe((requests) => cb(requests));
325
+ const subscription = this.#state.signSubject.subscribe((requests) => cb(requests));
326
326
  port.onDisconnect.addListener(() => {
327
327
  unsubscribe(id);
328
328
  subscription.unsubscribe();
@@ -371,13 +371,13 @@ export default class Extension {
371
371
  return true;
372
372
  }
373
373
  async removeAuthorization(url) {
374
- const remAuth = await this.__internal__state.removeAuthorization(url);
374
+ const remAuth = await this.#state.removeAuthorization(url);
375
375
  return { list: remAuth };
376
376
  }
377
377
  // Reject the authorization request and add the URL to the authorized list with no keys.
378
378
  // The site will not prompt for re-authorization on future visits.
379
379
  rejectAuthRequest(id) {
380
- const queued = this.__internal__state.getAuthRequest(id);
380
+ const queued = this.#state.getAuthRequest(id);
381
381
  assert(queued, 'Unable to find request');
382
382
  const { reject } = queued;
383
383
  reject(new Error('Rejected'));
@@ -385,16 +385,16 @@ export default class Extension {
385
385
  // Cancel the authorization request and do not add the URL to the authorized list.
386
386
  // The site will prompt for authorization on future visits.
387
387
  cancelAuthRequest(id) {
388
- const queued = this.__internal__state.getAuthRequest(id);
388
+ const queued = this.#state.getAuthRequest(id);
389
389
  assert(queued, 'Unable to find request');
390
390
  const { reject } = queued;
391
391
  reject(new Error('Cancelled'));
392
392
  }
393
393
  updateCurrentTabs({ urls }) {
394
- this.__internal__state.updateCurrentTabsUrl(urls);
394
+ this.#state.updateCurrentTabsUrl(urls);
395
395
  }
396
396
  getConnectedTabsUrl() {
397
- return this.__internal__state.getConnectedTabsUrl();
397
+ return this.#state.getConnectedTabsUrl();
398
398
  }
399
399
  // Weird thought, the eslint override is not needed in Tabs
400
400
  // eslint-disable-next-line @typescript-eslint/require-await
@@ -469,7 +469,7 @@ export default class Extension {
469
469
  case 'pri(seed.validate)':
470
470
  return this.seedValidate(request);
471
471
  case 'pri(settings.notification)':
472
- return this.__internal__state.setNotification(request);
472
+ return this.#state.setNotification(request);
473
473
  case 'pri(signing.approve.password)':
474
474
  return this.signingApprovePassword(request);
475
475
  case 'pri(signing.approve.signature)':
@@ -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 {};
@@ -63,35 +63,38 @@ async function extractMetadata(store) {
63
63
  });
64
64
  }
65
65
  export default class State {
66
- __internal__authUrls = {};
67
- __internal__authRequests = {};
68
- __internal__metaStore = new MetadataStore();
66
+ #authUrls = new Map();
67
+ #lastRequestTimestamps = new Map();
68
+ #maxEntries = 10;
69
+ #rateLimitInterval = 3000; // 3 seconds
70
+ #authRequests = {};
71
+ #metaStore = new MetadataStore();
69
72
  // Map of providers currently injected in tabs
70
- __internal__injectedProviders = new Map();
71
- __internal__metaRequests = {};
72
- __internal__notification = settings.notification;
73
+ #injectedProviders = new Map();
74
+ #metaRequests = {};
75
+ #notification = settings.notification;
73
76
  // Map of all providers exposed by the extension, they are retrievable by key
74
- __internal__providers;
75
- __internal__signRequests = {};
76
- __internal__windows = [];
77
- __internal__connectedTabsUrl = [];
77
+ #providers;
78
+ #signRequests = {};
79
+ #windows = [];
80
+ #connectedTabsUrl = [];
78
81
  authSubject = new BehaviorSubject([]);
79
82
  metaSubject = new BehaviorSubject([]);
80
83
  signSubject = new BehaviorSubject([]);
81
84
  authUrlSubjects = {};
82
85
  defaultAuthAccountSelection = [];
83
86
  constructor(providers = {}) {
84
- this.__internal__providers = providers;
87
+ this.#providers = providers;
85
88
  }
86
89
  async init() {
87
- await extractMetadata(this.__internal__metaStore);
90
+ await extractMetadata(this.#metaStore);
88
91
  // retrieve previously set authorizations
89
92
  const storageAuthUrls = await chrome.storage.local.get(AUTH_URLS_KEY);
90
93
  const authString = storageAuthUrls?.[AUTH_URLS_KEY] || '{}';
91
94
  const previousAuth = JSON.parse(authString);
92
- this.__internal__authUrls = previousAuth;
95
+ this.#authUrls = new Map(Object.entries(previousAuth));
93
96
  // Initialize authUrlSubjects for each URL
94
- Object.entries(previousAuth).forEach(([url, authInfo]) => {
97
+ this.#authUrls.forEach((authInfo, url) => {
95
98
  this.authUrlSubjects[url] = new BehaviorSubject(authInfo);
96
99
  });
97
100
  // retrieve previously set default auth accounts
@@ -104,49 +107,49 @@ export default class State {
104
107
  return knownMetadata();
105
108
  }
106
109
  get numAuthRequests() {
107
- return Object.keys(this.__internal__authRequests).length;
110
+ return Object.keys(this.#authRequests).length;
108
111
  }
109
112
  get numMetaRequests() {
110
- return Object.keys(this.__internal__metaRequests).length;
113
+ return Object.keys(this.#metaRequests).length;
111
114
  }
112
115
  get numSignRequests() {
113
- return Object.keys(this.__internal__signRequests).length;
116
+ return Object.keys(this.#signRequests).length;
114
117
  }
115
118
  get allAuthRequests() {
116
119
  return Object
117
- .values(this.__internal__authRequests)
120
+ .values(this.#authRequests)
118
121
  .map(({ id, request, url }) => ({ id, request, url }));
119
122
  }
120
123
  get allMetaRequests() {
121
124
  return Object
122
- .values(this.__internal__metaRequests)
125
+ .values(this.#metaRequests)
123
126
  .map(({ id, request, url }) => ({ id, request, url }));
124
127
  }
125
128
  get allSignRequests() {
126
129
  return Object
127
- .values(this.__internal__signRequests)
130
+ .values(this.#signRequests)
128
131
  .map(({ account, id, request, url }) => ({ account, id, request, url }));
129
132
  }
130
133
  get authUrls() {
131
- return this.__internal__authUrls;
134
+ return Object.fromEntries(this.#authUrls);
132
135
  }
133
136
  popupClose() {
134
- this.__internal__windows.forEach((id) => withErrorLog(() => chrome.windows.remove(id)));
135
- this.__internal__windows = [];
137
+ this.#windows.forEach((id) => withErrorLog(() => chrome.windows.remove(id)));
138
+ this.#windows = [];
136
139
  }
137
140
  popupOpen() {
138
- this.__internal__notification !== 'extension' &&
139
- chrome.windows.create(this.__internal__notification === 'window'
141
+ this.#notification !== 'extension' &&
142
+ chrome.windows.create(this.#notification === 'window'
140
143
  ? NORMAL_WINDOW_OPTS
141
144
  : POPUP_WINDOW_OPTS, (window) => {
142
145
  if (window) {
143
- this.__internal__windows.push(window.id || 0);
146
+ this.#windows.push(window.id || 0);
144
147
  }
145
148
  });
146
149
  }
147
150
  authComplete = (id, resolve, reject) => {
148
151
  const complete = async (authorizedAccounts = []) => {
149
- const { idStr, request: { origin }, url } = this.__internal__authRequests[id];
152
+ const { idStr, request: { origin }, url } = this.#authRequests[id];
150
153
  const strippedUrl = this.stripUrl(url);
151
154
  const authInfo = {
152
155
  authorizedAccounts,
@@ -155,7 +158,7 @@ export default class State {
155
158
  origin,
156
159
  url
157
160
  };
158
- this.__internal__authUrls[strippedUrl] = authInfo;
161
+ this.#authUrls.set(strippedUrl, authInfo);
159
162
  if (!this.authUrlSubjects[strippedUrl]) {
160
163
  this.authUrlSubjects[strippedUrl] = new BehaviorSubject(authInfo);
161
164
  }
@@ -164,14 +167,14 @@ export default class State {
164
167
  }
165
168
  await this.saveCurrentAuthList();
166
169
  await this.updateDefaultAuthAccounts(authorizedAccounts);
167
- delete this.__internal__authRequests[id];
170
+ delete this.#authRequests[id];
168
171
  this.updateIconAuth(true);
169
172
  };
170
173
  return {
171
174
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
172
175
  reject: async (error) => {
173
176
  if (error.message === 'Cancelled') {
174
- delete this.__internal__authRequests[id];
177
+ delete this.#authRequests[id];
175
178
  this.updateIconAuth(true);
176
179
  reject(new Error('Connection request was cancelled by the user.'));
177
180
  }
@@ -209,17 +212,17 @@ export default class State {
209
212
  : undefined;
210
213
  })
211
214
  .filter((value) => !!value);
212
- this.__internal__connectedTabsUrl = connectedTabs;
215
+ this.#connectedTabsUrl = connectedTabs;
213
216
  }
214
217
  getConnectedTabsUrl() {
215
- return this.__internal__connectedTabsUrl;
218
+ return this.#connectedTabsUrl;
216
219
  }
217
220
  deleteAuthRequest(requestId) {
218
- delete this.__internal__authRequests[requestId];
221
+ delete this.#authRequests[requestId];
219
222
  this.updateIconAuth(true);
220
223
  }
221
224
  async saveCurrentAuthList() {
222
- await chrome.storage.local.set({ [AUTH_URLS_KEY]: JSON.stringify(this.__internal__authUrls) });
225
+ await chrome.storage.local.set({ [AUTH_URLS_KEY]: JSON.stringify(Object.fromEntries(this.#authUrls)) });
223
226
  }
224
227
  async saveDefaultAuthAccounts() {
225
228
  await chrome.storage.local.set({ [DEFAULT_AUTH_ACCOUNTS]: JSON.stringify(this.defaultAuthAccountSelection) });
@@ -230,7 +233,7 @@ export default class State {
230
233
  }
231
234
  metaComplete = (id, resolve, reject) => {
232
235
  const complete = () => {
233
- delete this.__internal__metaRequests[id];
236
+ delete this.#metaRequests[id];
234
237
  this.updateIconMeta(true);
235
238
  };
236
239
  return {
@@ -246,7 +249,7 @@ export default class State {
246
249
  };
247
250
  signComplete = (id, resolve, reject) => {
248
251
  const complete = () => {
249
- delete this.__internal__signRequests[id];
252
+ delete this.#signRequests[id];
250
253
  this.updateIconSign(true);
251
254
  };
252
255
  return {
@@ -261,9 +264,22 @@ export default class State {
261
264
  };
262
265
  };
263
266
  stripUrl(url) {
264
- assert(url && (url.startsWith('http:') || url.startsWith('https:') || url.startsWith('ipfs:') || url.startsWith('ipns:')), `Invalid url ${url}, expected to start with http: or https: or ipfs: or ipns:`);
265
- const parts = url.split('/');
266
- return parts[2];
267
+ try {
268
+ const parsedUrl = new URL(url);
269
+ if (!['http:', 'https:', 'ipfs:', 'ipns:'].includes(parsedUrl.protocol)) {
270
+ throw new Error(`Invalid protocol ${parsedUrl.protocol}`);
271
+ }
272
+ // For ipfs/ipns which don't have a standard origin, we handle it differently.
273
+ if (parsedUrl.protocol === 'ipfs:' || parsedUrl.protocol === 'ipns:') {
274
+ // ipfs://<hash> | ipns://<hash>
275
+ return `${parsedUrl.protocol}//${parsedUrl.hostname}`;
276
+ }
277
+ return parsedUrl.origin;
278
+ }
279
+ catch (e) {
280
+ console.error(e);
281
+ throw new Error('Invalid URL');
282
+ }
267
283
  }
268
284
  updateIcon(shouldClose) {
269
285
  const authCount = this.numAuthRequests;
@@ -280,15 +296,15 @@ export default class State {
280
296
  }
281
297
  }
282
298
  async removeAuthorization(url) {
283
- const entry = this.__internal__authUrls[url];
299
+ const entry = this.#authUrls.get(url);
284
300
  assert(entry, `The source ${url} is not known`);
285
- delete this.__internal__authUrls[url];
301
+ this.#authUrls.delete(url);
286
302
  await this.saveCurrentAuthList();
287
303
  if (this.authUrlSubjects[url]) {
288
304
  entry.authorizedAccounts = [];
289
305
  this.authUrlSubjects[url].next(entry);
290
306
  }
291
- return this.__internal__authUrls;
307
+ return this.authUrls;
292
308
  }
293
309
  updateIconAuth(shouldClose) {
294
310
  this.authSubject.next(this.allAuthRequests);
@@ -304,8 +320,12 @@ export default class State {
304
320
  }
305
321
  async updateAuthorizedAccounts(authorizedAccountsDiff) {
306
322
  authorizedAccountsDiff.forEach(([url, authorizedAccountDiff]) => {
307
- this.__internal__authUrls[url].authorizedAccounts = authorizedAccountDiff;
308
- this.authUrlSubjects[url].next(this.__internal__authUrls[url]);
323
+ const authInfo = this.#authUrls.get(url);
324
+ if (authInfo) {
325
+ authInfo.authorizedAccounts = authorizedAccountDiff;
326
+ this.#authUrls.set(url, authInfo);
327
+ this.authUrlSubjects[url].next(authInfo);
328
+ }
309
329
  });
310
330
  await this.saveCurrentAuthList();
311
331
  }
@@ -313,12 +333,13 @@ export default class State {
313
333
  const idStr = this.stripUrl(url);
314
334
  // Do not enqueue duplicate authorization requests.
315
335
  const isDuplicate = Object
316
- .values(this.__internal__authRequests)
336
+ .values(this.#authRequests)
317
337
  .some((request) => request.idStr === idStr);
318
338
  assert(!isDuplicate, `The source ${url} has a pending authorization request`);
319
- if (this.__internal__authUrls[idStr]) {
339
+ if (this.#authUrls.has(idStr)) {
320
340
  // this url was seen in the past
321
- assert(this.__internal__authUrls[idStr].authorizedAccounts || this.__internal__authUrls[idStr].isAllowed, `The source ${url} is not allowed to interact with this extension`);
341
+ const authInfo = this.#authUrls.get(idStr);
342
+ assert(authInfo?.authorizedAccounts || authInfo?.isAllowed, `The source ${url} is not allowed to interact with this extension`);
322
343
  return {
323
344
  authorizedAccounts: [],
324
345
  result: false
@@ -326,7 +347,7 @@ export default class State {
326
347
  }
327
348
  return new Promise((resolve, reject) => {
328
349
  const id = getId();
329
- this.__internal__authRequests[id] = {
350
+ this.#authRequests[id] = {
330
351
  ...this.authComplete(id, resolve, reject),
331
352
  id,
332
353
  idStr,
@@ -338,14 +359,14 @@ export default class State {
338
359
  });
339
360
  }
340
361
  ensureUrlAuthorized(url) {
341
- const entry = this.__internal__authUrls[this.stripUrl(url)];
362
+ const entry = this.#authUrls.get(this.stripUrl(url));
342
363
  assert(entry, `The source ${url} has not been enabled yet`);
343
364
  return true;
344
365
  }
345
366
  injectMetadata(url, request) {
346
367
  return new Promise((resolve, reject) => {
347
368
  const id = getId();
348
- this.__internal__metaRequests[id] = {
369
+ this.#metaRequests[id] = {
349
370
  ...this.metaComplete(id, resolve, reject),
350
371
  id,
351
372
  request,
@@ -356,73 +377,92 @@ export default class State {
356
377
  });
357
378
  }
358
379
  getAuthRequest(id) {
359
- return this.__internal__authRequests[id];
380
+ return this.#authRequests[id];
360
381
  }
361
382
  getMetaRequest(id) {
362
- return this.__internal__metaRequests[id];
383
+ return this.#metaRequests[id];
363
384
  }
364
385
  getSignRequest(id) {
365
- return this.__internal__signRequests[id];
386
+ return this.#signRequests[id];
366
387
  }
367
388
  // List all providers the extension is exposing
368
389
  rpcListProviders() {
369
- return Promise.resolve(Object.keys(this.__internal__providers).reduce((acc, key) => {
370
- acc[key] = this.__internal__providers[key].meta;
390
+ return Promise.resolve(Object.keys(this.#providers).reduce((acc, key) => {
391
+ acc[key] = this.#providers[key].meta;
371
392
  return acc;
372
393
  }, {}));
373
394
  }
374
395
  rpcSend(request, port) {
375
- const provider = this.__internal__injectedProviders.get(port);
396
+ const provider = this.#injectedProviders.get(port);
376
397
  assert(provider, 'Cannot call pub(rpc.subscribe) before provider is set');
377
398
  return provider.send(request.method, request.params);
378
399
  }
379
400
  // Start a provider, return its meta
380
401
  rpcStartProvider(key, port) {
381
- assert(Object.keys(this.__internal__providers).includes(key), `Provider ${key} is not exposed by extension`);
382
- if (this.__internal__injectedProviders.get(port)) {
383
- return Promise.resolve(this.__internal__providers[key].meta);
402
+ assert(Object.keys(this.#providers).includes(key), `Provider ${key} is not exposed by extension`);
403
+ if (this.#injectedProviders.get(port)) {
404
+ return Promise.resolve(this.#providers[key].meta);
384
405
  }
385
406
  // Instantiate the provider
386
- this.__internal__injectedProviders.set(port, this.__internal__providers[key].start());
407
+ this.#injectedProviders.set(port, this.#providers[key].start());
387
408
  // Close provider connection when page is closed
388
409
  port.onDisconnect.addListener(() => {
389
- const provider = this.__internal__injectedProviders.get(port);
410
+ const provider = this.#injectedProviders.get(port);
390
411
  if (provider) {
391
412
  withErrorLog(() => provider.disconnect());
392
413
  }
393
- this.__internal__injectedProviders.delete(port);
414
+ this.#injectedProviders.delete(port);
394
415
  });
395
- return Promise.resolve(this.__internal__providers[key].meta);
416
+ return Promise.resolve(this.#providers[key].meta);
396
417
  }
397
418
  rpcSubscribe({ method, params, type }, cb, port) {
398
- const provider = this.__internal__injectedProviders.get(port);
419
+ const provider = this.#injectedProviders.get(port);
399
420
  assert(provider, 'Cannot call pub(rpc.subscribe) before provider is set');
400
421
  return provider.subscribe(type, method, params, cb);
401
422
  }
402
423
  rpcSubscribeConnected(_request, cb, port) {
403
- const provider = this.__internal__injectedProviders.get(port);
424
+ const provider = this.#injectedProviders.get(port);
404
425
  assert(provider, 'Cannot call pub(rpc.subscribeConnected) before provider is set');
405
426
  cb(null, provider.isConnected); // Immediately send back current isConnected
406
427
  provider.on('connected', () => cb(null, true));
407
428
  provider.on('disconnected', () => cb(null, false));
408
429
  }
409
430
  rpcUnsubscribe(request, port) {
410
- const provider = this.__internal__injectedProviders.get(port);
431
+ const provider = this.#injectedProviders.get(port);
411
432
  assert(provider, 'Cannot call pub(rpc.unsubscribe) before provider is set');
412
433
  return provider.unsubscribe(request.type, request.method, request.subscriptionId);
413
434
  }
414
435
  async saveMetadata(meta) {
415
- await this.__internal__metaStore.set(meta.genesisHash, meta);
436
+ await this.#metaStore.set(meta.genesisHash, meta);
416
437
  addMetadata(meta);
417
438
  }
418
439
  setNotification(notification) {
419
- this.__internal__notification = notification;
440
+ this.#notification = notification;
420
441
  return true;
421
442
  }
443
+ handleSignRequest(origin) {
444
+ const now = Date.now();
445
+ const lastTime = this.#lastRequestTimestamps.get(origin) || 0;
446
+ if (now - lastTime < this.#rateLimitInterval) {
447
+ throw new Error('Rate limit exceeded. Try again later.');
448
+ }
449
+ // If we're about to exceed max entries, evict the oldest
450
+ if (!this.#lastRequestTimestamps.has(origin) && this.#lastRequestTimestamps.size >= this.#maxEntries) {
451
+ const oldestKey = this.#lastRequestTimestamps.keys().next().value;
452
+ oldestKey && this.#lastRequestTimestamps.delete(oldestKey);
453
+ }
454
+ this.#lastRequestTimestamps.set(origin, now);
455
+ }
422
456
  sign(url, request, account) {
423
457
  const id = getId();
458
+ try {
459
+ this.handleSignRequest(url);
460
+ }
461
+ catch (error) {
462
+ return Promise.reject(error);
463
+ }
424
464
  return new Promise((resolve, reject) => {
425
- this.__internal__signRequests[id] = {
465
+ this.#signRequests[id] = {
426
466
  ...this.signComplete(id, resolve, reject),
427
467
  account,
428
468
  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
  }