@pezkuwi/extension-base 0.62.13 → 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.
Files changed (160) hide show
  1. package/package.json +21 -475
  2. package/src/background/RequestBytesSign.ts +28 -0
  3. package/src/background/RequestExtrinsicSign.ts +22 -0
  4. package/src/background/handlers/Extension.spec.ts +478 -0
  5. package/src/background/handlers/Extension.ts +690 -0
  6. package/src/background/handlers/State.ts +664 -0
  7. package/src/background/handlers/Tabs.ts +289 -0
  8. package/src/background/handlers/helpers.ts +14 -0
  9. package/src/background/handlers/index.ts +60 -0
  10. package/src/background/handlers/subscriptions.ts +32 -0
  11. package/src/background/index.ts +4 -0
  12. package/src/background/types.ts +432 -0
  13. package/src/bundle.ts +4 -0
  14. package/{defaults.js → src/defaults.ts} +15 -2
  15. package/src/index.ts +7 -0
  16. package/{packageDetect.js → src/packageDetect.ts} +8 -0
  17. package/src/packageInfo.ts +6 -0
  18. package/src/page/Accounts.ts +33 -0
  19. package/src/page/Injected.ts +33 -0
  20. package/src/page/Metadata.ts +22 -0
  21. package/src/page/PostMessageProvider.ts +182 -0
  22. package/src/page/Signer.ts +45 -0
  23. package/src/page/index.ts +89 -0
  24. package/src/page/types.ts +10 -0
  25. package/src/stores/Accounts.ts +28 -0
  26. package/src/stores/Base.ts +93 -0
  27. package/src/stores/Metadata.ts +17 -0
  28. package/{stores/index.js → src/stores/index.ts} +3 -0
  29. package/src/types.ts +12 -0
  30. package/src/utils/canDerive.ts +8 -0
  31. package/src/utils/getId.ts +10 -0
  32. package/src/utils/index.ts +4 -0
  33. package/src/utils/portUtils.ts +65 -0
  34. package/tsconfig.build.json +16 -0
  35. package/tsconfig.build.tsbuildinfo +1 -0
  36. package/tsconfig.spec.json +18 -0
  37. package/tsconfig.spec.tsbuildinfo +1 -0
  38. package/LICENSE +0 -201
  39. package/background/RequestBytesSign.d.ts +0 -12
  40. package/background/RequestBytesSign.js +0 -12
  41. package/background/RequestExtrinsicSign.d.ts +0 -12
  42. package/background/RequestExtrinsicSign.js +0 -11
  43. package/background/handlers/Extension.d.ts +0 -49
  44. package/background/handlers/Extension.js +0 -489
  45. package/background/handlers/State.js +0 -478
  46. package/background/handlers/Tabs.d.ts +0 -25
  47. package/background/handlers/Tabs.js +0 -195
  48. package/background/handlers/helpers.js +0 -11
  49. package/background/handlers/index.d.ts +0 -3
  50. package/background/handlers/index.js +0 -40
  51. package/background/handlers/subscriptions.d.ts +0 -3
  52. package/background/handlers/subscriptions.js +0 -18
  53. package/background/index.d.ts +0 -1
  54. package/background/index.js +0 -1
  55. package/background/types.js +0 -1
  56. package/bundle.d.ts +0 -1
  57. package/bundle.js +0 -1
  58. package/cjs/background/RequestBytesSign.d.ts +0 -12
  59. package/cjs/background/RequestBytesSign.js +0 -15
  60. package/cjs/background/RequestExtrinsicSign.d.ts +0 -12
  61. package/cjs/background/RequestExtrinsicSign.js +0 -14
  62. package/cjs/background/handlers/Extension.d.ts +0 -49
  63. package/cjs/background/handlers/Extension.js +0 -492
  64. package/cjs/background/handlers/State.d.ts +0 -96
  65. package/cjs/background/handlers/State.js +0 -482
  66. package/cjs/background/handlers/Tabs.d.ts +0 -25
  67. package/cjs/background/handlers/Tabs.js +0 -199
  68. package/cjs/background/handlers/helpers.d.ts +0 -1
  69. package/cjs/background/handlers/helpers.js +0 -14
  70. package/cjs/background/handlers/index.d.ts +0 -3
  71. package/cjs/background/handlers/index.js +0 -46
  72. package/cjs/background/handlers/subscriptions.d.ts +0 -3
  73. package/cjs/background/handlers/subscriptions.js +0 -22
  74. package/cjs/background/index.d.ts +0 -1
  75. package/cjs/background/index.js +0 -7
  76. package/cjs/background/types.d.ts +0 -343
  77. package/cjs/background/types.js +0 -2
  78. package/cjs/bundle.d.ts +0 -1
  79. package/cjs/bundle.js +0 -5
  80. package/cjs/defaults.js +0 -16
  81. package/cjs/index.d.ts +0 -1
  82. package/cjs/index.js +0 -4
  83. package/cjs/package.json +0 -3
  84. package/cjs/packageDetect.d.ts +0 -1
  85. package/cjs/packageDetect.js +0 -8
  86. package/cjs/packageInfo.d.ts +0 -6
  87. package/cjs/packageInfo.js +0 -4
  88. package/cjs/page/Accounts.d.ts +0 -7
  89. package/cjs/page/Accounts.js +0 -24
  90. package/cjs/page/Injected.d.ts +0 -13
  91. package/cjs/page/Injected.js +0 -25
  92. package/cjs/page/Metadata.d.ts +0 -7
  93. package/cjs/page/Metadata.js +0 -15
  94. package/cjs/page/PostMessageProvider.d.ts +0 -63
  95. package/cjs/page/PostMessageProvider.js +0 -135
  96. package/cjs/page/Signer.d.ts +0 -8
  97. package/cjs/page/Signer.js +0 -29
  98. package/cjs/page/index.d.ts +0 -16
  99. package/cjs/page/index.js +0 -52
  100. package/cjs/page/types.d.ts +0 -6
  101. package/cjs/page/types.js +0 -2
  102. package/cjs/stores/Accounts.js +0 -21
  103. package/cjs/stores/Base.js +0 -70
  104. package/cjs/stores/Metadata.js +0 -13
  105. package/cjs/stores/index.js +0 -8
  106. package/cjs/types.js +0 -2
  107. package/cjs/utils/canDerive.d.ts +0 -2
  108. package/cjs/utils/canDerive.js +0 -6
  109. package/cjs/utils/getId.js +0 -8
  110. package/cjs/utils/index.d.ts +0 -1
  111. package/cjs/utils/index.js +0 -5
  112. package/cjs/utils/portUtils.d.ts +0 -13
  113. package/cjs/utils/portUtils.js +0 -49
  114. package/defaults.d.ts +0 -9
  115. package/index.d.ts +0 -1
  116. package/index.js +0 -1
  117. package/packageDetect.d.ts +0 -1
  118. package/packageInfo.d.ts +0 -6
  119. package/packageInfo.js +0 -1
  120. package/page/Accounts.d.ts +0 -7
  121. package/page/Accounts.js +0 -21
  122. package/page/Injected.d.ts +0 -13
  123. package/page/Injected.js +0 -21
  124. package/page/Metadata.d.ts +0 -7
  125. package/page/Metadata.js +0 -12
  126. package/page/PostMessageProvider.d.ts +0 -63
  127. package/page/PostMessageProvider.js +0 -132
  128. package/page/Signer.d.ts +0 -8
  129. package/page/Signer.js +0 -26
  130. package/page/index.d.ts +0 -16
  131. package/page/index.js +0 -45
  132. package/page/types.d.ts +0 -6
  133. package/page/types.js +0 -1
  134. package/stores/Accounts.d.ts +0 -6
  135. package/stores/Accounts.js +0 -17
  136. package/stores/Base.d.ts +0 -9
  137. package/stores/Base.js +0 -67
  138. package/stores/Metadata.d.ts +0 -5
  139. package/stores/Metadata.js +0 -9
  140. package/stores/index.d.ts +0 -2
  141. package/types.d.ts +0 -9
  142. package/types.js +0 -1
  143. package/utils/canDerive.d.ts +0 -2
  144. package/utils/canDerive.js +0 -3
  145. package/utils/getId.d.ts +0 -1
  146. package/utils/getId.js +0 -5
  147. package/utils/index.d.ts +0 -1
  148. package/utils/index.js +0 -1
  149. package/utils/portUtils.d.ts +0 -13
  150. package/utils/portUtils.js +0 -43
  151. /package/{background → src/background}/handlers/State.d.ts +0 -0
  152. /package/{background → src/background}/handlers/helpers.d.ts +0 -0
  153. /package/{background → src/background}/types.d.ts +0 -0
  154. /package/{cjs → src}/defaults.d.ts +0 -0
  155. /package/{cjs → src}/stores/Accounts.d.ts +0 -0
  156. /package/{cjs → src}/stores/Base.d.ts +0 -0
  157. /package/{cjs → src}/stores/Metadata.d.ts +0 -0
  158. /package/{cjs → src}/stores/index.d.ts +0 -0
  159. /package/{cjs → src}/types.d.ts +0 -0
  160. /package/{cjs → src}/utils/getId.d.ts +0 -0
@@ -0,0 +1,289 @@
1
+ // Copyright 2019-2026 @pezkuwi/extension-base authors & contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ /* global chrome */
5
+
6
+ import type { InjectedAccount, InjectedMetadataKnown, MetadataDef, ProviderMeta } from '@pezkuwi/extension-inject/types';
7
+ import type { KeyringPair } from '@pezkuwi/keyring/types';
8
+ import type { JsonRpcResponse } from '@pezkuwi/rpc-provider/types';
9
+ import type { SignerPayloadJSON, SignerPayloadRaw } from '@pezkuwi/types/types';
10
+ import type { SubjectInfo } from '@pezkuwi/ui-keyring/observable/types';
11
+ import type { AuthUrlInfo, MessageTypes, RequestAccountList, RequestAccountUnsubscribe, RequestAuthorizeTab, RequestRpcSend, RequestRpcSubscribe, RequestRpcUnsubscribe, RequestTypes, ResponseRpcListProviders, ResponseSigning, ResponseTypes, SubscriptionMessageTypes } from '../types.js';
12
+ import type { AuthResponse } from './State.js';
13
+ import type State from './State.js';
14
+
15
+ import { combineLatest, type Subscription } from 'rxjs';
16
+
17
+ import { checkIfDenied } from '@pezkuwi/phishing';
18
+ import { keyring } from '@pezkuwi/ui-keyring';
19
+ import { accounts as accountsObservable } from '@pezkuwi/ui-keyring/observable/accounts';
20
+ import { assert, isNumber } from '@pezkuwi/util';
21
+
22
+ import { PHISHING_PAGE_REDIRECT } from '../../defaults.js';
23
+ import { canDerive } from '../../utils/index.js';
24
+ import RequestBytesSign from '../RequestBytesSign.js';
25
+ import RequestExtrinsicSign from '../RequestExtrinsicSign.js';
26
+ import { withErrorLog } from './helpers.js';
27
+ import { createSubscription, unsubscribe } from './subscriptions.js';
28
+
29
+ interface AccountSub {
30
+ subscription: Subscription;
31
+ url: string;
32
+ }
33
+
34
+ function transformAccounts (accounts: SubjectInfo, anyType = false): InjectedAccount[] {
35
+ return Object
36
+ .values(accounts)
37
+ .filter(({ json: { meta: { isHidden } } }) => !isHidden)
38
+ .filter(({ type }) => anyType ? true : canDerive(type))
39
+ .sort((a, b) => (a.json.meta.whenCreated || 0) - (b.json.meta.whenCreated || 0))
40
+ .map(({ json: { address, meta: { genesisHash, name } }, type }): InjectedAccount => ({
41
+ address,
42
+ genesisHash,
43
+ name,
44
+ type
45
+ }));
46
+ }
47
+
48
+ export default class Tabs {
49
+ readonly #accountSubs: Record<string, AccountSub> = {};
50
+
51
+ readonly #state: State;
52
+
53
+ constructor (state: State) {
54
+ this.#state = state;
55
+ }
56
+
57
+ private filterForAuthorizedAccounts (accounts: InjectedAccount[], url: string): InjectedAccount[] {
58
+ const auth = this.#state.authUrls[this.#state.stripUrl(url)];
59
+
60
+ if (!auth) {
61
+ return [];
62
+ }
63
+
64
+ return accounts.filter(
65
+ (allAcc) =>
66
+ auth.authorizedAccounts
67
+ // we have a list, use it
68
+ ? auth.authorizedAccounts.includes(allAcc.address)
69
+ // if no authorizedAccounts and isAllowed return all - these are old converted urls
70
+ : auth.isAllowed
71
+ );
72
+ }
73
+
74
+ private authorize (url: string, request: RequestAuthorizeTab): Promise<AuthResponse> {
75
+ return this.#state.authorizeUrl(url, request);
76
+ }
77
+
78
+ private accountsListAuthorized (url: string, { anyType }: RequestAccountList): InjectedAccount[] {
79
+ const transformedAccounts = transformAccounts(accountsObservable.subject.getValue(), anyType);
80
+
81
+ return this.filterForAuthorizedAccounts(transformedAccounts, url);
82
+ }
83
+
84
+ private accountsSubscribeAuthorized (url: string, id: string, port: chrome.runtime.Port): string {
85
+ const cb = createSubscription<'pub(accounts.subscribe)'>(id, port);
86
+
87
+ const strippedUrl = this.#state.stripUrl(url);
88
+
89
+ const authUrlObservable = this.#state.authUrlSubjects[strippedUrl]?.asObservable();
90
+
91
+ if (!authUrlObservable) {
92
+ console.error(`No authUrlSubject found for ${strippedUrl}`);
93
+
94
+ return id;
95
+ }
96
+
97
+ this.#accountSubs[id] = {
98
+ subscription: combineLatest([accountsObservable.subject, authUrlObservable]).subscribe(([accounts, _authUrlInfo]: [SubjectInfo, AuthUrlInfo]): void => {
99
+ const transformedAccounts = transformAccounts(accounts);
100
+
101
+ cb(this.filterForAuthorizedAccounts(transformedAccounts, url));
102
+ }),
103
+ url
104
+ };
105
+
106
+ port.onDisconnect.addListener((): void => {
107
+ this.accountsUnsubscribe(url, { id });
108
+ });
109
+
110
+ return id;
111
+ }
112
+
113
+ private accountsUnsubscribe (url: string, { id }: RequestAccountUnsubscribe): boolean {
114
+ const sub = this.#accountSubs[id];
115
+
116
+ if (!sub || sub.url !== url) {
117
+ return false;
118
+ }
119
+
120
+ delete this.#accountSubs[id];
121
+
122
+ unsubscribe(id);
123
+ sub.subscription.unsubscribe();
124
+
125
+ return true;
126
+ }
127
+
128
+ private getSigningPair (address: string): KeyringPair {
129
+ const pair = keyring.getPair(address);
130
+
131
+ assert(pair, 'Unable to find keypair');
132
+
133
+ return pair;
134
+ }
135
+
136
+ private bytesSign (url: string, request: SignerPayloadRaw): Promise<ResponseSigning> {
137
+ const address = request.address;
138
+ const pair = this.getSigningPair(address);
139
+
140
+ return this.#state.sign(url, new RequestBytesSign(request), { address, ...pair.meta });
141
+ }
142
+
143
+ private extrinsicSign (url: string, request: SignerPayloadJSON): Promise<ResponseSigning> {
144
+ const address = request.address;
145
+ const pair = this.getSigningPair(address);
146
+
147
+ return this.#state.sign(url, new RequestExtrinsicSign(request), { address, ...pair.meta });
148
+ }
149
+
150
+ private metadataProvide (url: string, request: MetadataDef): Promise<boolean> {
151
+ return this.#state.injectMetadata(url, request);
152
+ }
153
+
154
+ private metadataList (_url: string): InjectedMetadataKnown[] {
155
+ return this.#state.knownMetadata.map(({ genesisHash, specVersion }) => ({
156
+ genesisHash,
157
+ specVersion
158
+ }));
159
+ }
160
+
161
+ private rpcListProviders (): Promise<ResponseRpcListProviders> {
162
+ return this.#state.rpcListProviders();
163
+ }
164
+
165
+ private rpcSend (request: RequestRpcSend, port: chrome.runtime.Port): Promise<JsonRpcResponse<unknown>> {
166
+ return this.#state.rpcSend(request, port);
167
+ }
168
+
169
+ private rpcStartProvider (key: string, port: chrome.runtime.Port): Promise<ProviderMeta> {
170
+ return this.#state.rpcStartProvider(key, port);
171
+ }
172
+
173
+ private async rpcSubscribe (request: RequestRpcSubscribe, id: string, port: chrome.runtime.Port): Promise<boolean> {
174
+ const innerCb = createSubscription<'pub(rpc.subscribe)'>(id, port);
175
+ const cb = (_error: Error | null, data: SubscriptionMessageTypes['pub(rpc.subscribe)']): void => innerCb(data);
176
+ const subscriptionId = await this.#state.rpcSubscribe(request, cb, port);
177
+
178
+ port.onDisconnect.addListener((): void => {
179
+ unsubscribe(id);
180
+ withErrorLog(() => this.rpcUnsubscribe({ ...request, subscriptionId }, port));
181
+ });
182
+
183
+ return true;
184
+ }
185
+
186
+ private rpcSubscribeConnected (request: null, id: string, port: chrome.runtime.Port): Promise<boolean> {
187
+ const innerCb = createSubscription<'pub(rpc.subscribeConnected)'>(id, port);
188
+ const cb = (_error: Error | null, data: SubscriptionMessageTypes['pub(rpc.subscribeConnected)']): void => innerCb(data);
189
+
190
+ this.#state.rpcSubscribeConnected(request, cb, port);
191
+
192
+ port.onDisconnect.addListener((): void => {
193
+ unsubscribe(id);
194
+ });
195
+
196
+ return Promise.resolve(true);
197
+ }
198
+
199
+ private async rpcUnsubscribe (request: RequestRpcUnsubscribe, port: chrome.runtime.Port): Promise<boolean> {
200
+ return this.#state.rpcUnsubscribe(request, port);
201
+ }
202
+
203
+ private redirectPhishingLanding (phishingWebsite: string): void {
204
+ const nonFragment = phishingWebsite.split('#')[0];
205
+ const encodedWebsite = encodeURIComponent(nonFragment);
206
+ const url = `${chrome.runtime.getURL('index.html')}#${PHISHING_PAGE_REDIRECT}/${encodedWebsite}`;
207
+
208
+ chrome.tabs.query({ url: nonFragment }, (tabs) => {
209
+ tabs
210
+ .map(({ id }) => id)
211
+ .filter((id): id is number => isNumber(id))
212
+ .forEach((id) =>
213
+ withErrorLog(() => chrome.tabs.update(id, { url }))
214
+ );
215
+ });
216
+ }
217
+
218
+ private async redirectIfPhishing (url: string): Promise<boolean> {
219
+ const isInDenyList = await checkIfDenied(url);
220
+
221
+ if (isInDenyList) {
222
+ this.redirectPhishingLanding(url);
223
+
224
+ return true;
225
+ }
226
+
227
+ return false;
228
+ }
229
+
230
+ public async handle<TMessageType extends MessageTypes> (id: string, type: TMessageType, request: RequestTypes[TMessageType], url: string, port?: chrome.runtime.Port): Promise<ResponseTypes[keyof ResponseTypes]> {
231
+ if (type === 'pub(phishing.redirectIfDenied)') {
232
+ return this.redirectIfPhishing(url);
233
+ }
234
+
235
+ if (type !== 'pub(authorize.tab)') {
236
+ this.#state.ensureUrlAuthorized(url);
237
+ }
238
+
239
+ switch (type) {
240
+ case 'pub(authorize.tab)':
241
+ return this.authorize(url, request as RequestAuthorizeTab);
242
+
243
+ case 'pub(accounts.list)':
244
+ return this.accountsListAuthorized(url, request as RequestAccountList);
245
+
246
+ case 'pub(accounts.subscribe)':
247
+ return port && this.accountsSubscribeAuthorized(url, id, port);
248
+
249
+ case 'pub(accounts.unsubscribe)':
250
+ return this.accountsUnsubscribe(url, request as RequestAccountUnsubscribe);
251
+
252
+ case 'pub(bytes.sign)':
253
+ return this.bytesSign(url, request as SignerPayloadRaw);
254
+
255
+ case 'pub(extrinsic.sign)':
256
+ return this.extrinsicSign(url, request as SignerPayloadJSON);
257
+
258
+ case 'pub(metadata.list)':
259
+ return this.metadataList(url);
260
+
261
+ case 'pub(metadata.provide)':
262
+ return this.metadataProvide(url, request as MetadataDef);
263
+
264
+ case 'pub(ping)':
265
+ return Promise.resolve(true);
266
+
267
+ case 'pub(rpc.listProviders)':
268
+ return this.rpcListProviders();
269
+
270
+ case 'pub(rpc.send)':
271
+ return port && this.rpcSend(request as RequestRpcSend, port);
272
+
273
+ case 'pub(rpc.startProvider)':
274
+ return port && this.rpcStartProvider(request as string, port);
275
+
276
+ case 'pub(rpc.subscribe)':
277
+ return port && this.rpcSubscribe(request as RequestRpcSubscribe, id, port);
278
+
279
+ case 'pub(rpc.subscribeConnected)':
280
+ return port && this.rpcSubscribeConnected(request as null, id, port);
281
+
282
+ case 'pub(rpc.unsubscribe)':
283
+ return port && this.rpcUnsubscribe(request as RequestRpcUnsubscribe, port);
284
+
285
+ default:
286
+ throw new Error(`Unable to handle message of type ${type}`);
287
+ }
288
+ }
289
+ }
@@ -0,0 +1,14 @@
1
+ // Copyright 2019-2026 @pezkuwi/extension-base authors & contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ export function withErrorLog (fn: () => unknown): void {
5
+ try {
6
+ const p = fn();
7
+
8
+ if (p && typeof p === 'object' && typeof (p as Promise<unknown>).catch === 'function') {
9
+ (p as Promise<unknown>).catch(console.error);
10
+ }
11
+ } catch (e) {
12
+ console.error(e);
13
+ }
14
+ }
@@ -0,0 +1,60 @@
1
+ // Copyright 2019-2026 @pezkuwi/extension-base authors & contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ /* global chrome */
5
+
6
+ import type { MessageTypes, TransportRequestMessage } from '../types.js';
7
+
8
+ import { assert } from '@pezkuwi/util';
9
+
10
+ import { PORT_EXTENSION } from '../../defaults.js';
11
+ import Extension from './Extension.js';
12
+ import State from './State.js';
13
+ import Tabs from './Tabs.js';
14
+
15
+ export { withErrorLog } from './helpers.js';
16
+
17
+ const state = new State();
18
+
19
+ await state.init();
20
+ const extension = new Extension(state);
21
+ const tabs = new Tabs(state);
22
+
23
+ export default function handler<TMessageType extends MessageTypes> ({ id, message, request }: TransportRequestMessage<TMessageType>, port?: chrome.runtime.Port, extensionPortName = PORT_EXTENSION): void {
24
+ const isExtension = !port || port?.name === extensionPortName;
25
+ const sender = port?.sender;
26
+
27
+ if (!isExtension && !sender) {
28
+ throw new Error('Unable to extract message sender');
29
+ }
30
+
31
+ const from = isExtension
32
+ ? 'extension'
33
+ : sender?.url || sender?.tab?.url || '<unknown>';
34
+ const source = `${from}: ${id}: ${message}`;
35
+
36
+ console.log(` [in] ${source}`); // :: ${JSON.stringify(request)}`);
37
+
38
+ const promise = isExtension
39
+ ? extension.handle(id, message, request, port)
40
+ : tabs.handle(id, message, request, from, port);
41
+
42
+ promise
43
+ .then((response: unknown): void => {
44
+ console.log(`[out] ${source}`); // :: ${JSON.stringify(response)}`);
45
+
46
+ // between the start and the end of the promise, the user may have closed
47
+ // the tab, in which case port will be undefined
48
+ assert(port, 'Port has been disconnected');
49
+
50
+ port.postMessage({ id, response });
51
+ })
52
+ .catch((error: Error): void => {
53
+ console.log(`[err] ${source}:: ${error.message}`);
54
+
55
+ // only send message back to port if it's still connected
56
+ if (port) {
57
+ port.postMessage({ error: error.message, id });
58
+ }
59
+ });
60
+ }
@@ -0,0 +1,32 @@
1
+ // Copyright 2019-2026 @pezkuwi/extension-base authors & contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ /* global chrome */
5
+
6
+ import type { MessageTypesWithSubscriptions, SubscriptionMessageTypes } from '../types.js';
7
+
8
+ type Subscriptions = Record<string, chrome.runtime.Port>;
9
+
10
+ const subscriptions: Subscriptions = {};
11
+
12
+ // return a subscription callback, that will send the data to the caller via the port
13
+ export function createSubscription<TMessageType extends MessageTypesWithSubscriptions> (id: string, port: chrome.runtime.Port): (data: SubscriptionMessageTypes[TMessageType]) => void {
14
+ subscriptions[id] = port;
15
+
16
+ return (subscription: unknown): void => {
17
+ if (subscriptions[id]) {
18
+ port.postMessage({ id, subscription });
19
+ }
20
+ };
21
+ }
22
+
23
+ // clear a previous subscriber
24
+ export function unsubscribe (id: string): void {
25
+ if (subscriptions[id]) {
26
+ console.log(`Unsubscribing from ${id}`);
27
+
28
+ delete subscriptions[id];
29
+ } else {
30
+ console.error(`Unable to unsubscribe from ${id}`);
31
+ }
32
+ }
@@ -0,0 +1,4 @@
1
+ // Copyright 2019-2026 @pezkuwi/extension-base authors & contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ export { default as handlers, withErrorLog } from './handlers/index.js';