@pezkuwi/extension-base 0.62.14 → 0.62.18
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/package.json +16 -470
- package/src/background/RequestBytesSign.ts +28 -0
- package/src/background/RequestExtrinsicSign.ts +22 -0
- package/src/background/handlers/Extension.spec.ts +478 -0
- package/src/background/handlers/Extension.ts +690 -0
- package/src/background/handlers/State.ts +664 -0
- package/src/background/handlers/Tabs.ts +289 -0
- package/src/background/handlers/helpers.ts +14 -0
- package/src/background/handlers/index.ts +60 -0
- package/src/background/handlers/subscriptions.ts +32 -0
- package/src/background/index.ts +4 -0
- package/src/background/types.ts +432 -0
- package/src/bundle.ts +4 -0
- package/{defaults.js → src/defaults.ts} +15 -2
- package/src/index.ts +7 -0
- package/{packageDetect.js → src/packageDetect.ts} +8 -0
- package/src/packageInfo.ts +6 -0
- package/src/page/Accounts.ts +33 -0
- package/src/page/Injected.ts +33 -0
- package/src/page/Metadata.ts +22 -0
- package/src/page/PostMessageProvider.ts +182 -0
- package/src/page/Signer.ts +45 -0
- package/src/page/index.ts +89 -0
- package/src/page/types.ts +10 -0
- package/src/stores/Accounts.ts +28 -0
- package/src/stores/Base.ts +93 -0
- package/src/stores/Metadata.ts +17 -0
- package/{stores/index.js → src/stores/index.ts} +3 -0
- package/src/types.ts +12 -0
- package/src/utils/canDerive.ts +8 -0
- package/src/utils/getId.ts +10 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/portUtils.ts +65 -0
- package/tsconfig.build.json +16 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.spec.json +18 -0
- package/tsconfig.spec.tsbuildinfo +1 -0
- package/LICENSE +0 -201
- package/background/RequestBytesSign.d.ts +0 -12
- package/background/RequestBytesSign.js +0 -12
- package/background/RequestExtrinsicSign.d.ts +0 -12
- package/background/RequestExtrinsicSign.js +0 -11
- package/background/handlers/Extension.d.ts +0 -49
- package/background/handlers/Extension.js +0 -489
- package/background/handlers/State.js +0 -478
- package/background/handlers/Tabs.d.ts +0 -25
- package/background/handlers/Tabs.js +0 -195
- package/background/handlers/helpers.js +0 -11
- package/background/handlers/index.d.ts +0 -3
- package/background/handlers/index.js +0 -40
- package/background/handlers/subscriptions.d.ts +0 -3
- package/background/handlers/subscriptions.js +0 -18
- package/background/index.d.ts +0 -1
- package/background/index.js +0 -1
- package/background/types.js +0 -1
- package/bundle.d.ts +0 -1
- package/bundle.js +0 -1
- package/cjs/background/RequestBytesSign.d.ts +0 -12
- package/cjs/background/RequestBytesSign.js +0 -15
- package/cjs/background/RequestExtrinsicSign.d.ts +0 -12
- package/cjs/background/RequestExtrinsicSign.js +0 -14
- package/cjs/background/handlers/Extension.d.ts +0 -49
- package/cjs/background/handlers/Extension.js +0 -492
- package/cjs/background/handlers/State.d.ts +0 -96
- package/cjs/background/handlers/State.js +0 -482
- package/cjs/background/handlers/Tabs.d.ts +0 -25
- package/cjs/background/handlers/Tabs.js +0 -199
- package/cjs/background/handlers/helpers.d.ts +0 -1
- package/cjs/background/handlers/helpers.js +0 -14
- package/cjs/background/handlers/index.d.ts +0 -3
- package/cjs/background/handlers/index.js +0 -46
- package/cjs/background/handlers/subscriptions.d.ts +0 -3
- package/cjs/background/handlers/subscriptions.js +0 -22
- package/cjs/background/index.d.ts +0 -1
- package/cjs/background/index.js +0 -7
- package/cjs/background/types.d.ts +0 -343
- package/cjs/background/types.js +0 -2
- package/cjs/bundle.d.ts +0 -1
- package/cjs/bundle.js +0 -5
- package/cjs/defaults.js +0 -16
- package/cjs/index.d.ts +0 -1
- package/cjs/index.js +0 -4
- package/cjs/package.json +0 -3
- package/cjs/packageDetect.d.ts +0 -1
- package/cjs/packageDetect.js +0 -8
- package/cjs/packageInfo.d.ts +0 -6
- package/cjs/packageInfo.js +0 -4
- package/cjs/page/Accounts.d.ts +0 -7
- package/cjs/page/Accounts.js +0 -24
- package/cjs/page/Injected.d.ts +0 -13
- package/cjs/page/Injected.js +0 -25
- package/cjs/page/Metadata.d.ts +0 -7
- package/cjs/page/Metadata.js +0 -15
- package/cjs/page/PostMessageProvider.d.ts +0 -63
- package/cjs/page/PostMessageProvider.js +0 -135
- package/cjs/page/Signer.d.ts +0 -8
- package/cjs/page/Signer.js +0 -29
- package/cjs/page/index.d.ts +0 -16
- package/cjs/page/index.js +0 -52
- package/cjs/page/types.d.ts +0 -6
- package/cjs/page/types.js +0 -2
- package/cjs/stores/Accounts.js +0 -21
- package/cjs/stores/Base.js +0 -70
- package/cjs/stores/Metadata.js +0 -13
- package/cjs/stores/index.js +0 -8
- package/cjs/types.js +0 -2
- package/cjs/utils/canDerive.d.ts +0 -2
- package/cjs/utils/canDerive.js +0 -6
- package/cjs/utils/getId.js +0 -8
- package/cjs/utils/index.d.ts +0 -1
- package/cjs/utils/index.js +0 -5
- package/cjs/utils/portUtils.d.ts +0 -13
- package/cjs/utils/portUtils.js +0 -49
- package/defaults.d.ts +0 -9
- package/index.d.ts +0 -1
- package/index.js +0 -1
- package/packageDetect.d.ts +0 -1
- package/packageInfo.d.ts +0 -6
- package/packageInfo.js +0 -1
- package/page/Accounts.d.ts +0 -7
- package/page/Accounts.js +0 -21
- package/page/Injected.d.ts +0 -13
- package/page/Injected.js +0 -21
- package/page/Metadata.d.ts +0 -7
- package/page/Metadata.js +0 -12
- package/page/PostMessageProvider.d.ts +0 -63
- package/page/PostMessageProvider.js +0 -132
- package/page/Signer.d.ts +0 -8
- package/page/Signer.js +0 -26
- package/page/index.d.ts +0 -16
- package/page/index.js +0 -45
- package/page/types.d.ts +0 -6
- package/page/types.js +0 -1
- package/stores/Accounts.d.ts +0 -6
- package/stores/Accounts.js +0 -17
- package/stores/Base.d.ts +0 -9
- package/stores/Base.js +0 -67
- package/stores/Metadata.d.ts +0 -5
- package/stores/Metadata.js +0 -9
- package/stores/index.d.ts +0 -2
- package/types.d.ts +0 -9
- package/types.js +0 -1
- package/utils/canDerive.d.ts +0 -2
- package/utils/canDerive.js +0 -3
- package/utils/getId.d.ts +0 -1
- package/utils/getId.js +0 -5
- package/utils/index.d.ts +0 -1
- package/utils/index.js +0 -1
- package/utils/portUtils.d.ts +0 -13
- package/utils/portUtils.js +0 -43
- /package/{background → src/background}/handlers/State.d.ts +0 -0
- /package/{background → src/background}/handlers/helpers.d.ts +0 -0
- /package/{background → src/background}/types.d.ts +0 -0
- /package/{cjs → src}/defaults.d.ts +0 -0
- /package/{cjs → src}/stores/Accounts.d.ts +0 -0
- /package/{cjs → src}/stores/Base.d.ts +0 -0
- /package/{cjs → src}/stores/Metadata.d.ts +0 -0
- /package/{cjs → src}/stores/index.d.ts +0 -0
- /package/{cjs → src}/types.d.ts +0 -0
- /package/{cjs → src}/utils/getId.d.ts +0 -0
|
@@ -0,0 +1,664 @@
|
|
|
1
|
+
// Copyright 2019-2026 @pezkuwi/extension-bg authors & contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
/* global chrome */
|
|
5
|
+
|
|
6
|
+
import type { MetadataDef, ProviderMeta } from '@pezkuwi/extension-inject/types';
|
|
7
|
+
import type { JsonRpcResponse, ProviderInterface, ProviderInterfaceCallback } from '@pezkuwi/rpc-provider/types';
|
|
8
|
+
import type { AccountJson, AuthorizeRequest, AuthUrlInfo, AuthUrls, MetadataRequest, RequestAuthorizeTab, RequestRpcSend, RequestRpcSubscribe, RequestRpcUnsubscribe, RequestSign, ResponseRpcListProviders, ResponseSigning, SigningRequest } from '../types.js';
|
|
9
|
+
|
|
10
|
+
import { BehaviorSubject } from 'rxjs';
|
|
11
|
+
|
|
12
|
+
import { addMetadata, knownMetadata } from '@pezkuwi/extension-chains';
|
|
13
|
+
import { knownGenesis } from '@pezkuwi/networks/defaults';
|
|
14
|
+
import { settings } from '@pezkuwi/ui-settings';
|
|
15
|
+
import { assert } from '@pezkuwi/util';
|
|
16
|
+
|
|
17
|
+
import { MetadataStore } from '../../stores/index.js';
|
|
18
|
+
import { getId } from '../../utils/getId.js';
|
|
19
|
+
import { withErrorLog } from './helpers.js';
|
|
20
|
+
|
|
21
|
+
interface Resolver<T> {
|
|
22
|
+
reject: (error: Error) => void;
|
|
23
|
+
resolve: (result: T) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface AuthRequest extends Resolver<AuthResponse> {
|
|
27
|
+
id: string;
|
|
28
|
+
idStr: string;
|
|
29
|
+
request: RequestAuthorizeTab;
|
|
30
|
+
url: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type AuthorizedAccountsDiff = [url: string, authorizedAccounts: AuthUrlInfo['authorizedAccounts']][]
|
|
34
|
+
|
|
35
|
+
interface MetaRequest extends Resolver<boolean> {
|
|
36
|
+
id: string;
|
|
37
|
+
request: MetadataDef;
|
|
38
|
+
url: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface AuthResponse {
|
|
42
|
+
result: boolean;
|
|
43
|
+
authorizedAccounts: string[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// List of providers passed into constructor. This is the list of providers
|
|
47
|
+
// exposed by the extension.
|
|
48
|
+
type Providers = Record<string, {
|
|
49
|
+
meta: ProviderMeta;
|
|
50
|
+
// The provider is not running at init, calling this will instantiate the
|
|
51
|
+
// provider.
|
|
52
|
+
start: () => ProviderInterface;
|
|
53
|
+
}>
|
|
54
|
+
|
|
55
|
+
interface SignRequest extends Resolver<ResponseSigning> {
|
|
56
|
+
account: AccountJson;
|
|
57
|
+
id: string;
|
|
58
|
+
request: RequestSign;
|
|
59
|
+
url: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const NOTIFICATION_URL = chrome.runtime.getURL('notification.html');
|
|
63
|
+
|
|
64
|
+
const POPUP_WINDOW_OPTS: chrome.windows.CreateData = {
|
|
65
|
+
focused: true,
|
|
66
|
+
height: 621,
|
|
67
|
+
left: 150,
|
|
68
|
+
top: 150,
|
|
69
|
+
type: 'popup',
|
|
70
|
+
url: NOTIFICATION_URL,
|
|
71
|
+
width: 560
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const NORMAL_WINDOW_OPTS: chrome.windows.CreateData = {
|
|
75
|
+
focused: true,
|
|
76
|
+
type: 'normal',
|
|
77
|
+
url: NOTIFICATION_URL
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export enum NotificationOptions {
|
|
81
|
+
None,
|
|
82
|
+
Normal,
|
|
83
|
+
PopUp,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const AUTH_URLS_KEY = 'authUrls';
|
|
87
|
+
const DEFAULT_AUTH_ACCOUNTS = 'defaultAuthAccounts';
|
|
88
|
+
|
|
89
|
+
async function extractMetadata (store: MetadataStore): Promise<void> {
|
|
90
|
+
await store.allMap(async (map): Promise<void> => {
|
|
91
|
+
const knownEntries = Object.entries(knownGenesis);
|
|
92
|
+
const defs: Record<string, { def: MetadataDef, index: number, key: string }> = {};
|
|
93
|
+
const removals: string[] = [];
|
|
94
|
+
|
|
95
|
+
Object
|
|
96
|
+
.entries(map)
|
|
97
|
+
.forEach(([key, def]): void => {
|
|
98
|
+
const entry = knownEntries.find(([, hashes]) => hashes.includes(def.genesisHash));
|
|
99
|
+
|
|
100
|
+
if (entry) {
|
|
101
|
+
const [name, hashes] = entry;
|
|
102
|
+
const index = hashes.indexOf(def.genesisHash);
|
|
103
|
+
|
|
104
|
+
// flatten the known metadata based on the genesis index
|
|
105
|
+
// (lower is better/newer)
|
|
106
|
+
if (!defs[name] || (defs[name].index > index)) {
|
|
107
|
+
if (defs[name]) {
|
|
108
|
+
// remove the old version of the metadata
|
|
109
|
+
removals.push(defs[name].key);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
defs[name] = { def, index, key };
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
// this is not a known entry, so we will just apply it
|
|
116
|
+
defs[key] = { def, index: 0, key };
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
for (const key of removals) {
|
|
121
|
+
await store.remove(key);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
Object.values(defs).forEach(({ def }) => addMetadata(def));
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export default class State {
|
|
129
|
+
#authUrls = new Map<string, AuthUrlInfo>();
|
|
130
|
+
|
|
131
|
+
#lastRequestTimestamps = new Map<string, number>();
|
|
132
|
+
#maxEntries = 10;
|
|
133
|
+
#rateLimitInterval = 3000; // 3 seconds
|
|
134
|
+
|
|
135
|
+
readonly #authRequests: Record<string, AuthRequest> = {};
|
|
136
|
+
|
|
137
|
+
readonly #metaStore = new MetadataStore();
|
|
138
|
+
|
|
139
|
+
// Map of providers currently injected in tabs
|
|
140
|
+
readonly #injectedProviders = new Map<chrome.runtime.Port, ProviderInterface>();
|
|
141
|
+
|
|
142
|
+
readonly #metaRequests: Record<string, MetaRequest> = {};
|
|
143
|
+
|
|
144
|
+
#notification = settings.notification;
|
|
145
|
+
|
|
146
|
+
// Map of all providers exposed by the extension, they are retrievable by key
|
|
147
|
+
readonly #providers: Providers;
|
|
148
|
+
|
|
149
|
+
readonly #signRequests: Record<string, SignRequest> = {};
|
|
150
|
+
|
|
151
|
+
#windows: number[] = [];
|
|
152
|
+
|
|
153
|
+
#connectedTabsUrl: string[] = [];
|
|
154
|
+
|
|
155
|
+
public readonly authSubject: BehaviorSubject<AuthorizeRequest[]> = new BehaviorSubject<AuthorizeRequest[]>([]);
|
|
156
|
+
|
|
157
|
+
public readonly metaSubject: BehaviorSubject<MetadataRequest[]> = new BehaviorSubject<MetadataRequest[]>([]);
|
|
158
|
+
|
|
159
|
+
public readonly signSubject: BehaviorSubject<SigningRequest[]> = new BehaviorSubject<SigningRequest[]>([]);
|
|
160
|
+
|
|
161
|
+
public readonly authUrlSubjects: Record<string, BehaviorSubject<AuthUrlInfo>> = {};
|
|
162
|
+
|
|
163
|
+
public defaultAuthAccountSelection: string[] = [];
|
|
164
|
+
|
|
165
|
+
constructor (providers: Providers = {}, rateLimitInterval = 3000) {
|
|
166
|
+
assert(rateLimitInterval >= 0, 'Expects non-negative number for rateLimitInterval');
|
|
167
|
+
this.#providers = providers;
|
|
168
|
+
this.#rateLimitInterval = rateLimitInterval;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
public async init () {
|
|
172
|
+
await extractMetadata(this.#metaStore);
|
|
173
|
+
// retrieve previously set authorizations
|
|
174
|
+
const storageAuthUrls: Record<string, string> = await chrome.storage.local.get(AUTH_URLS_KEY);
|
|
175
|
+
const authString = storageAuthUrls?.[AUTH_URLS_KEY] || '{}';
|
|
176
|
+
const previousAuth = JSON.parse(authString) as AuthUrls;
|
|
177
|
+
|
|
178
|
+
this.#authUrls = new Map(Object.entries(previousAuth));
|
|
179
|
+
|
|
180
|
+
// Initialize authUrlSubjects for each URL
|
|
181
|
+
this.#authUrls.forEach((authInfo, url) => {
|
|
182
|
+
this.authUrlSubjects[url] = new BehaviorSubject<AuthUrlInfo>(authInfo);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// retrieve previously set default auth accounts
|
|
186
|
+
const storageDefaultAuthAccounts: Record<string, string> = await chrome.storage.local.get(DEFAULT_AUTH_ACCOUNTS);
|
|
187
|
+
const defaultAuthString: string = storageDefaultAuthAccounts?.[DEFAULT_AUTH_ACCOUNTS] || '[]';
|
|
188
|
+
const previousDefaultAuth = JSON.parse(defaultAuthString) as string[];
|
|
189
|
+
|
|
190
|
+
this.defaultAuthAccountSelection = previousDefaultAuth;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
public get knownMetadata (): MetadataDef[] {
|
|
194
|
+
return knownMetadata();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
public get numAuthRequests (): number {
|
|
198
|
+
return Object.keys(this.#authRequests).length;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
public get numMetaRequests (): number {
|
|
202
|
+
return Object.keys(this.#metaRequests).length;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public get numSignRequests (): number {
|
|
206
|
+
return Object.keys(this.#signRequests).length;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
public get allAuthRequests (): AuthorizeRequest[] {
|
|
210
|
+
return Object
|
|
211
|
+
.values(this.#authRequests)
|
|
212
|
+
.map(({ id, request, url }): AuthorizeRequest => ({ id, request, url }));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
public get allMetaRequests (): MetadataRequest[] {
|
|
216
|
+
return Object
|
|
217
|
+
.values(this.#metaRequests)
|
|
218
|
+
.map(({ id, request, url }): MetadataRequest => ({ id, request, url }));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
public get allSignRequests (): SigningRequest[] {
|
|
222
|
+
return Object
|
|
223
|
+
.values(this.#signRequests)
|
|
224
|
+
.map(({ account, id, request, url }): SigningRequest => ({ account, id, request, url }));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
public get authUrls (): AuthUrls {
|
|
228
|
+
return Object.fromEntries(this.#authUrls);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private popupClose (): void {
|
|
232
|
+
this.#windows.forEach((id: number) =>
|
|
233
|
+
withErrorLog(() => chrome.windows.remove(id))
|
|
234
|
+
);
|
|
235
|
+
this.#windows = [];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private popupOpen (): void {
|
|
239
|
+
this.#notification !== 'extension' &&
|
|
240
|
+
chrome.windows.create(
|
|
241
|
+
this.#notification === 'window'
|
|
242
|
+
? NORMAL_WINDOW_OPTS
|
|
243
|
+
: POPUP_WINDOW_OPTS,
|
|
244
|
+
(window): void => {
|
|
245
|
+
if (window) {
|
|
246
|
+
this.#windows.push(window.id || 0);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
private authComplete = (id: string, resolve: (resValue: AuthResponse) => void, reject: (error: Error) => void): Resolver<AuthResponse> => {
|
|
252
|
+
const complete = async (authorizedAccounts: string[] = []) => {
|
|
253
|
+
const { idStr, request: { origin }, url } = this.#authRequests[id];
|
|
254
|
+
|
|
255
|
+
const strippedUrl = this.stripUrl(url);
|
|
256
|
+
|
|
257
|
+
const authInfo: AuthUrlInfo = {
|
|
258
|
+
authorizedAccounts,
|
|
259
|
+
count: 0,
|
|
260
|
+
id: idStr,
|
|
261
|
+
origin,
|
|
262
|
+
url
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
this.#authUrls.set(strippedUrl, authInfo);
|
|
266
|
+
|
|
267
|
+
if (!this.authUrlSubjects[strippedUrl]) {
|
|
268
|
+
this.authUrlSubjects[strippedUrl] = new BehaviorSubject<AuthUrlInfo>(authInfo);
|
|
269
|
+
} else {
|
|
270
|
+
this.authUrlSubjects[strippedUrl].next(authInfo);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
await this.saveCurrentAuthList();
|
|
274
|
+
await this.updateDefaultAuthAccounts(authorizedAccounts);
|
|
275
|
+
delete this.#authRequests[id];
|
|
276
|
+
this.updateIconAuth(true);
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
281
|
+
reject: async (error: Error): Promise<void> => {
|
|
282
|
+
if (error.message === 'Cancelled') {
|
|
283
|
+
delete this.#authRequests[id];
|
|
284
|
+
this.updateIconAuth(true);
|
|
285
|
+
reject(new Error('Connection request was cancelled by the user.'));
|
|
286
|
+
} else {
|
|
287
|
+
await complete();
|
|
288
|
+
reject(new Error('Connection request was rejected by the user.'));
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
292
|
+
resolve: async ({ authorizedAccounts, result }: AuthResponse): Promise<void> => {
|
|
293
|
+
await complete(authorizedAccounts);
|
|
294
|
+
resolve({ authorizedAccounts, result });
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* @deprecated This method is deprecated in favor of {@link updateCurrentTabs} and will be removed in a future release.
|
|
301
|
+
*/
|
|
302
|
+
public udateCurrentTabsUrl (urls: string[]) {
|
|
303
|
+
this.updateCurrentTabsUrl(urls);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
public updateCurrentTabsUrl (urls: string[]) {
|
|
307
|
+
const connectedTabs = urls.map((url) => {
|
|
308
|
+
let strippedUrl = '';
|
|
309
|
+
|
|
310
|
+
// the assert in stripUrl may throw for new tabs with "chrome://newtab/"
|
|
311
|
+
try {
|
|
312
|
+
strippedUrl = this.stripUrl(url);
|
|
313
|
+
} catch (e) {
|
|
314
|
+
console.error(e);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// return the stripped url only if this website is known
|
|
318
|
+
return !!strippedUrl && this.authUrls[strippedUrl]
|
|
319
|
+
? strippedUrl
|
|
320
|
+
: undefined;
|
|
321
|
+
})
|
|
322
|
+
.filter((value) => !!value) as string[];
|
|
323
|
+
|
|
324
|
+
this.#connectedTabsUrl = connectedTabs;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
public getConnectedTabsUrl () {
|
|
328
|
+
return this.#connectedTabsUrl;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
public deleteAuthRequest (requestId: string) {
|
|
332
|
+
delete this.#authRequests[requestId];
|
|
333
|
+
this.updateIconAuth(true);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private async saveCurrentAuthList () {
|
|
337
|
+
await chrome.storage.local.set({ [AUTH_URLS_KEY]: JSON.stringify(Object.fromEntries(this.#authUrls)) });
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private async saveDefaultAuthAccounts () {
|
|
341
|
+
await chrome.storage.local.set({ [DEFAULT_AUTH_ACCOUNTS]: JSON.stringify(this.defaultAuthAccountSelection) });
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
public async updateDefaultAuthAccounts (newList: string[]) {
|
|
345
|
+
this.defaultAuthAccountSelection = newList;
|
|
346
|
+
await this.saveDefaultAuthAccounts();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
private metaComplete = (id: string, resolve: (result: boolean) => void, reject: (error: Error) => void): Resolver<boolean> => {
|
|
350
|
+
const complete = (): void => {
|
|
351
|
+
delete this.#metaRequests[id];
|
|
352
|
+
this.updateIconMeta(true);
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
reject: (error: Error): void => {
|
|
357
|
+
complete();
|
|
358
|
+
reject(error);
|
|
359
|
+
},
|
|
360
|
+
resolve: (result: boolean): void => {
|
|
361
|
+
complete();
|
|
362
|
+
resolve(result);
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
private signComplete = (id: string, resolve: (result: ResponseSigning) => void, reject: (error: Error) => void): Resolver<ResponseSigning> => {
|
|
368
|
+
const complete = (): void => {
|
|
369
|
+
delete this.#signRequests[id];
|
|
370
|
+
this.updateIconSign(true);
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
reject: (error: Error): void => {
|
|
375
|
+
complete();
|
|
376
|
+
reject(error);
|
|
377
|
+
},
|
|
378
|
+
resolve: (result: ResponseSigning): void => {
|
|
379
|
+
complete();
|
|
380
|
+
resolve(result);
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
public stripUrl (url: string): string {
|
|
386
|
+
try {
|
|
387
|
+
const parsedUrl = new URL(url);
|
|
388
|
+
|
|
389
|
+
if (!['http:', 'https:', 'ipfs:', 'ipns:'].includes(parsedUrl.protocol)) {
|
|
390
|
+
throw new Error(`Invalid protocol ${parsedUrl.protocol}`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// For ipfs/ipns which don't have a standard origin, we handle it differently.
|
|
394
|
+
if (parsedUrl.protocol === 'ipfs:' || parsedUrl.protocol === 'ipns:') {
|
|
395
|
+
// ipfs://<hash> | ipns://<hash>
|
|
396
|
+
return `${parsedUrl.protocol}//${parsedUrl.hostname}`;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return parsedUrl.origin;
|
|
400
|
+
} catch (e) {
|
|
401
|
+
console.error(e);
|
|
402
|
+
throw new Error('Invalid URL');
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private updateIcon (shouldClose?: boolean): void {
|
|
407
|
+
const authCount = this.numAuthRequests;
|
|
408
|
+
const metaCount = this.numMetaRequests;
|
|
409
|
+
const signCount = this.numSignRequests;
|
|
410
|
+
const text = (
|
|
411
|
+
authCount
|
|
412
|
+
? 'Auth'
|
|
413
|
+
: metaCount
|
|
414
|
+
? 'Meta'
|
|
415
|
+
: (signCount ? `${signCount}` : '')
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
withErrorLog(() => chrome.action.setBadgeText({ text }));
|
|
419
|
+
|
|
420
|
+
if (shouldClose && text === '') {
|
|
421
|
+
this.popupClose();
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
public async removeAuthorization (url: string): Promise<AuthUrls> {
|
|
426
|
+
const entry = this.#authUrls.get(url);
|
|
427
|
+
|
|
428
|
+
assert(entry, `The source ${url} is not known`);
|
|
429
|
+
|
|
430
|
+
this.#authUrls.delete(url);
|
|
431
|
+
await this.saveCurrentAuthList();
|
|
432
|
+
|
|
433
|
+
if (this.authUrlSubjects[url]) {
|
|
434
|
+
entry.authorizedAccounts = [];
|
|
435
|
+
this.authUrlSubjects[url].next(entry);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return this.authUrls;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private updateIconAuth (shouldClose?: boolean): void {
|
|
442
|
+
this.authSubject.next(this.allAuthRequests);
|
|
443
|
+
this.updateIcon(shouldClose);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
private updateIconMeta (shouldClose?: boolean): void {
|
|
447
|
+
this.metaSubject.next(this.allMetaRequests);
|
|
448
|
+
this.updateIcon(shouldClose);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
private updateIconSign (shouldClose?: boolean): void {
|
|
452
|
+
this.signSubject.next(this.allSignRequests);
|
|
453
|
+
this.updateIcon(shouldClose);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
public async updateAuthorizedAccounts (authorizedAccountsDiff: AuthorizedAccountsDiff): Promise<void> {
|
|
457
|
+
authorizedAccountsDiff.forEach(([url, authorizedAccountDiff]) => {
|
|
458
|
+
const authInfo = this.#authUrls.get(url);
|
|
459
|
+
|
|
460
|
+
if (authInfo) {
|
|
461
|
+
authInfo.authorizedAccounts = authorizedAccountDiff;
|
|
462
|
+
this.#authUrls.set(url, authInfo);
|
|
463
|
+
this.authUrlSubjects[url].next(authInfo);
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
await this.saveCurrentAuthList();
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
public async authorizeUrl (url: string, request: RequestAuthorizeTab): Promise<AuthResponse> {
|
|
471
|
+
const idStr = this.stripUrl(url);
|
|
472
|
+
|
|
473
|
+
// Do not enqueue duplicate authorization requests.
|
|
474
|
+
const isDuplicate = Object
|
|
475
|
+
.values(this.#authRequests)
|
|
476
|
+
.some((request) => request.idStr === idStr);
|
|
477
|
+
|
|
478
|
+
assert(!isDuplicate, `The source ${url} has a pending authorization request`);
|
|
479
|
+
|
|
480
|
+
if (this.#authUrls.has(idStr)) {
|
|
481
|
+
// this url was seen in the past
|
|
482
|
+
const authInfo = this.#authUrls.get(idStr);
|
|
483
|
+
|
|
484
|
+
assert(authInfo?.authorizedAccounts || authInfo?.isAllowed, `The source ${url} is not allowed to interact with this extension`);
|
|
485
|
+
|
|
486
|
+
return {
|
|
487
|
+
authorizedAccounts: [],
|
|
488
|
+
result: false
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return new Promise((resolve, reject): void => {
|
|
493
|
+
const id = getId();
|
|
494
|
+
|
|
495
|
+
this.#authRequests[id] = {
|
|
496
|
+
...this.authComplete(id, resolve, reject),
|
|
497
|
+
id,
|
|
498
|
+
idStr,
|
|
499
|
+
request,
|
|
500
|
+
url
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
this.updateIconAuth();
|
|
504
|
+
this.popupOpen();
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
public ensureUrlAuthorized (url: string): boolean {
|
|
509
|
+
const entry = this.#authUrls.get(this.stripUrl(url));
|
|
510
|
+
|
|
511
|
+
assert(entry, `The source ${url} has not been enabled yet`);
|
|
512
|
+
|
|
513
|
+
return true;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
public injectMetadata (url: string, request: MetadataDef): Promise<boolean> {
|
|
517
|
+
return new Promise((resolve, reject): void => {
|
|
518
|
+
const id = getId();
|
|
519
|
+
|
|
520
|
+
this.#metaRequests[id] = {
|
|
521
|
+
...this.metaComplete(id, resolve, reject),
|
|
522
|
+
id,
|
|
523
|
+
request,
|
|
524
|
+
url
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
this.updateIconMeta();
|
|
528
|
+
this.popupOpen();
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
public getAuthRequest (id: string): AuthRequest {
|
|
533
|
+
return this.#authRequests[id];
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
public getMetaRequest (id: string): MetaRequest {
|
|
537
|
+
return this.#metaRequests[id];
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
public getSignRequest (id: string): SignRequest {
|
|
541
|
+
return this.#signRequests[id];
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// List all providers the extension is exposing
|
|
545
|
+
public rpcListProviders (): Promise<ResponseRpcListProviders> {
|
|
546
|
+
return Promise.resolve(Object.keys(this.#providers).reduce((acc, key) => {
|
|
547
|
+
acc[key] = this.#providers[key].meta;
|
|
548
|
+
|
|
549
|
+
return acc;
|
|
550
|
+
}, {} as ResponseRpcListProviders));
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
public rpcSend (request: RequestRpcSend, port: chrome.runtime.Port): Promise<JsonRpcResponse<unknown>> {
|
|
554
|
+
const provider = this.#injectedProviders.get(port);
|
|
555
|
+
|
|
556
|
+
assert(provider, 'Cannot call pub(rpc.subscribe) before provider is set');
|
|
557
|
+
|
|
558
|
+
return provider.send(request.method, request.params);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Start a provider, return its meta
|
|
562
|
+
public rpcStartProvider (key: string, port: chrome.runtime.Port): Promise<ProviderMeta> {
|
|
563
|
+
assert(Object.keys(this.#providers).includes(key), `Provider ${key} is not exposed by extension`);
|
|
564
|
+
|
|
565
|
+
if (this.#injectedProviders.get(port)) {
|
|
566
|
+
return Promise.resolve(this.#providers[key].meta);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Instantiate the provider
|
|
570
|
+
this.#injectedProviders.set(port, this.#providers[key].start());
|
|
571
|
+
|
|
572
|
+
// Close provider connection when page is closed
|
|
573
|
+
port.onDisconnect.addListener((): void => {
|
|
574
|
+
const provider = this.#injectedProviders.get(port);
|
|
575
|
+
|
|
576
|
+
if (provider) {
|
|
577
|
+
withErrorLog(() => provider.disconnect());
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
this.#injectedProviders.delete(port);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
return Promise.resolve(this.#providers[key].meta);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
public rpcSubscribe ({ method, params, type }: RequestRpcSubscribe, cb: ProviderInterfaceCallback, port: chrome.runtime.Port): Promise<number | string> {
|
|
587
|
+
const provider = this.#injectedProviders.get(port);
|
|
588
|
+
|
|
589
|
+
assert(provider, 'Cannot call pub(rpc.subscribe) before provider is set');
|
|
590
|
+
|
|
591
|
+
return provider.subscribe(type, method, params, cb);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
public rpcSubscribeConnected (_request: null, cb: ProviderInterfaceCallback, port: chrome.runtime.Port): void {
|
|
595
|
+
const provider = this.#injectedProviders.get(port);
|
|
596
|
+
|
|
597
|
+
assert(provider, 'Cannot call pub(rpc.subscribeConnected) before provider is set');
|
|
598
|
+
|
|
599
|
+
cb(null, provider.isConnected); // Immediately send back current isConnected
|
|
600
|
+
provider.on('connected', () => cb(null, true));
|
|
601
|
+
provider.on('disconnected', () => cb(null, false));
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
public rpcUnsubscribe (request: RequestRpcUnsubscribe, port: chrome.runtime.Port): Promise<boolean> {
|
|
605
|
+
const provider = this.#injectedProviders.get(port);
|
|
606
|
+
|
|
607
|
+
assert(provider, 'Cannot call pub(rpc.unsubscribe) before provider is set');
|
|
608
|
+
|
|
609
|
+
return provider.unsubscribe(request.type, request.method, request.subscriptionId);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
public async saveMetadata (meta: MetadataDef): Promise<void> {
|
|
613
|
+
await this.#metaStore.set(meta.genesisHash, meta);
|
|
614
|
+
|
|
615
|
+
addMetadata(meta);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
public setNotification (notification: string): boolean {
|
|
619
|
+
this.#notification = notification;
|
|
620
|
+
|
|
621
|
+
return true;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
private handleSignRequest (origin: string) {
|
|
625
|
+
const now = Date.now();
|
|
626
|
+
const lastTime = this.#lastRequestTimestamps.get(origin) || 0;
|
|
627
|
+
|
|
628
|
+
if (now - lastTime < this.#rateLimitInterval) {
|
|
629
|
+
throw new Error('Rate limit exceeded. Try again later.');
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// If we're about to exceed max entries, evict the oldest
|
|
633
|
+
if (!this.#lastRequestTimestamps.has(origin) && this.#lastRequestTimestamps.size >= this.#maxEntries) {
|
|
634
|
+
const oldestKey = this.#lastRequestTimestamps.keys().next().value;
|
|
635
|
+
|
|
636
|
+
oldestKey && this.#lastRequestTimestamps.delete(oldestKey);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
this.#lastRequestTimestamps.set(origin, now);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
public sign (url: string, request: RequestSign, account: AccountJson): Promise<ResponseSigning> {
|
|
643
|
+
const id = getId();
|
|
644
|
+
|
|
645
|
+
try {
|
|
646
|
+
this.handleSignRequest(url);
|
|
647
|
+
} catch (error) {
|
|
648
|
+
return Promise.reject(error);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return new Promise((resolve, reject): void => {
|
|
652
|
+
this.#signRequests[id] = {
|
|
653
|
+
...this.signComplete(id, resolve, reject),
|
|
654
|
+
account,
|
|
655
|
+
id,
|
|
656
|
+
request,
|
|
657
|
+
url
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
this.updateIconSign();
|
|
661
|
+
this.popupOpen();
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
}
|