@tezos-x/octez.connect-dapp 4.8.3 → 4.8.5
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/README.md +55 -7
- package/dist/cjs/beacon-message-events.js.map +1 -1
- package/dist/cjs/dapp-client/DAppClient.d.ts +114 -6
- package/dist/cjs/dapp-client/DAppClient.js +986 -392
- package/dist/cjs/dapp-client/DAppClient.js.map +1 -1
- package/dist/cjs/dapp-client/DAppClientOptions.d.ts +17 -0
- package/dist/cjs/dapp-client/DAppClientOptions.js.map +1 -1
- package/dist/cjs/events.d.ts +29 -3
- package/dist/cjs/events.js +41 -37
- package/dist/cjs/events.js.map +1 -1
- package/dist/cjs/index.d.ts +4 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/transports/DappP2PTransport.d.ts +1 -1
- package/dist/cjs/transports/DappP2PTransport.js.map +1 -1
- package/dist/cjs/transports/DappPostMessageTransport.d.ts +1 -1
- package/dist/cjs/transports/DappPostMessageTransport.js.map +1 -1
- package/dist/cjs/transports/DappWalletConnectTransport.d.ts +1 -1
- package/dist/cjs/transports/DappWalletConnectTransport.js.map +1 -1
- package/dist/cjs/utils/available-transports.js.map +1 -1
- package/dist/cjs/utils/block-explorer.d.ts +2 -2
- package/dist/cjs/utils/block-explorer.js +5 -1
- package/dist/cjs/utils/block-explorer.js.map +1 -1
- package/dist/cjs/utils/get-instance.js +7 -1
- package/dist/cjs/utils/get-instance.js.map +1 -1
- package/dist/cjs/utils/shorten-string.js.map +1 -1
- package/dist/cjs/utils/tzkt-blockexplorer.d.ts +2 -2
- package/dist/cjs/utils/tzkt-blockexplorer.js +3 -17
- package/dist/cjs/utils/tzkt-blockexplorer.js.map +1 -1
- package/dist/esm/beacon-message-events.js.map +1 -1
- package/dist/esm/dapp-client/DAppClient.d.ts +114 -6
- package/dist/esm/dapp-client/DAppClient.js +991 -386
- package/dist/esm/dapp-client/DAppClient.js.map +1 -1
- package/dist/esm/dapp-client/DAppClientOptions.d.ts +17 -0
- package/dist/esm/dapp-client/DAppClientOptions.js.map +1 -1
- package/dist/esm/events.d.ts +29 -3
- package/dist/esm/events.js +6 -3
- package/dist/esm/events.js.map +1 -1
- package/dist/esm/index.d.ts +4 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/transports/DappP2PTransport.d.ts +1 -1
- package/dist/esm/transports/DappP2PTransport.js.map +1 -1
- package/dist/esm/transports/DappPostMessageTransport.d.ts +1 -1
- package/dist/esm/transports/DappPostMessageTransport.js.map +1 -1
- package/dist/esm/transports/DappWalletConnectTransport.d.ts +1 -1
- package/dist/esm/transports/DappWalletConnectTransport.js.map +1 -1
- package/dist/esm/utils/available-transports.js.map +1 -1
- package/dist/esm/utils/block-explorer.d.ts +2 -2
- package/dist/esm/utils/block-explorer.js +5 -1
- package/dist/esm/utils/block-explorer.js.map +1 -1
- package/dist/esm/utils/get-instance.js +7 -1
- package/dist/esm/utils/get-instance.js.map +1 -1
- package/dist/esm/utils/shorten-string.js.map +1 -1
- package/dist/esm/utils/tzkt-blockexplorer.d.ts +2 -2
- package/dist/esm/utils/tzkt-blockexplorer.js +3 -17
- package/dist/esm/utils/tzkt-blockexplorer.js.map +1 -1
- package/dist/octez.connect.dapp.min.js +1 -1066
- package/package.json +17 -13
- package/LICENCE +0 -19
- package/dist/walletbeacon.dapp.min.js +0 -1066
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
1
|
import bs58check from 'bs58check';
|
|
3
|
-
import { BeaconEvent } from '../events';
|
|
4
2
|
import { TransportType, StorageKey, BeaconMessageType, PermissionScope, NetworkType, Origin, BeaconErrorType, SigningType, ColorMode, TransportStatus
|
|
5
3
|
// PermissionRequestV3
|
|
6
4
|
// RequestEncryptPayloadInput,
|
|
@@ -8,11 +6,11 @@ import { TransportType, StorageKey, BeaconMessageType, PermissionScope, NetworkT
|
|
|
8
6
|
// EncryptPayloadResponse,
|
|
9
7
|
// EncryptPayloadRequest
|
|
10
8
|
} from '@tezos-x/octez.connect-types';
|
|
11
|
-
import { Client, AppMetadataManager, Serializer, LocalStorage, getAccountIdentifier, getSenderId, Logger, StorageValidator, SDK_VERSION, IndexedDBStorage, MultiTabChannel,
|
|
9
|
+
import { Client, AppMetadataManager, Serializer, LocalStorage, getAccountIdentifier, getSenderId, Logger, StorageValidator, SDK_VERSION, IndexedDBStorage, MultiTabChannel, getError } from '@tezos-x/octez.connect-core';
|
|
12
10
|
import { getAddressFromPublicKey, ExposedPromise, generateGUID, toHex, signMessage, CONTRACT_PREFIX, prefixPublicKey, isValidAddress, getKeypairFromSeed } from '@tezos-x/octez.connect-utils';
|
|
13
11
|
import { messageEvents } from '../beacon-message-events';
|
|
12
|
+
import { BeaconEvent, BeaconEventHandler } from '../events';
|
|
14
13
|
import { TzktBlockExplorer } from '../utils/tzkt-blockexplorer';
|
|
15
|
-
import { BeaconEventHandler } from '@tezos-x/octez.connect-dapp';
|
|
16
14
|
import { DappPostMessageTransport } from '../transports/DappPostMessageTransport';
|
|
17
15
|
import { DappP2PTransport } from '../transports/DappP2PTransport';
|
|
18
16
|
import { DappWalletConnectTransport } from '../transports/DappWalletConnectTransport';
|
|
@@ -20,6 +18,23 @@ import { PostMessageTransport } from '@tezos-x/octez.connect-transport-postmessa
|
|
|
20
18
|
import { closeToast, getColorMode, setColorMode, setDesktopList, setExtensionList, setWebList, setiOSList, getiOSList, getDesktopList, getExtensionList, getWebList, isBrowser, isDesktop, isMobileOS, isIOS, currentOS } from '@tezos-x/octez.connect-ui';
|
|
21
19
|
import { WalletConnectTransport } from '@tezos-x/octez.connect-transport-walletconnect';
|
|
22
20
|
const logger = new Logger('DAppClient');
|
|
21
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
|
|
22
|
+
const SESSION_UPDATE_REQUEST_ID = 'session_update';
|
|
23
|
+
const createLazyPromise = (factory) => {
|
|
24
|
+
let promise;
|
|
25
|
+
const getPromise = () => {
|
|
26
|
+
if (!promise) {
|
|
27
|
+
promise = factory();
|
|
28
|
+
}
|
|
29
|
+
return promise;
|
|
30
|
+
};
|
|
31
|
+
const lazyPromise = {
|
|
32
|
+
then: (onfulfilled, onrejected) => getPromise().then(onfulfilled, onrejected),
|
|
33
|
+
catch: (onrejected) => getPromise().catch(onrejected),
|
|
34
|
+
finally: (onfinally) => getPromise().finally(onfinally ?? undefined)
|
|
35
|
+
};
|
|
36
|
+
return lazyPromise;
|
|
37
|
+
};
|
|
23
38
|
/**
|
|
24
39
|
* @publicapi
|
|
25
40
|
*
|
|
@@ -53,12 +68,20 @@ export class DAppClient extends Client {
|
|
|
53
68
|
walletConnectTransport;
|
|
54
69
|
wcProjectId;
|
|
55
70
|
wcRelayUrl;
|
|
71
|
+
/**
|
|
72
|
+
* When true, the WalletConnect transport is not built or listened to. See
|
|
73
|
+
* DAppClientOptions.disableWalletConnect.
|
|
74
|
+
*/
|
|
75
|
+
disableWalletConnect;
|
|
56
76
|
isGetActiveAccountHandled = false;
|
|
57
77
|
openRequestsOtherTabs = new Set();
|
|
58
78
|
/**
|
|
59
79
|
* A map of requests that are currently "open", meaning we have sent them to a wallet and are still awaiting a response.
|
|
60
80
|
*/
|
|
61
81
|
openRequests = new Map();
|
|
82
|
+
acknowledgedRequests = new Set();
|
|
83
|
+
openRequestTimeouts = new Map();
|
|
84
|
+
requestTimeoutMs;
|
|
62
85
|
/**
|
|
63
86
|
* The currently active account. For all requests that are associated to a specific request (operation request, signing request),
|
|
64
87
|
* the active account is used to determine the network and destination wallet
|
|
@@ -69,8 +92,22 @@ export class DAppClient extends Client {
|
|
|
69
92
|
*/
|
|
70
93
|
_activePeer = new ExposedPromise();
|
|
71
94
|
_initPromise;
|
|
72
|
-
|
|
95
|
+
/**
|
|
96
|
+
* Rejector for the in-flight {@link _initPromise}. Populated while init is
|
|
97
|
+
* awaiting a peer pairing; cleared automatically when {@link _initPromise}
|
|
98
|
+
* settles. Abort paths (modal close, WC session-proposal rejection,
|
|
99
|
+
* transport-level connection failure) call this to unwedge any
|
|
100
|
+
* `await this.init()` caller, surfacing the error to handleRequestError
|
|
101
|
+
* instead of hanging.
|
|
102
|
+
*/
|
|
103
|
+
_initReject;
|
|
104
|
+
_initSubstratePairing;
|
|
105
|
+
_requestPermissionsPromise;
|
|
106
|
+
_requestPermissionsKey;
|
|
107
|
+
_sdkSecretSeedPromise;
|
|
108
|
+
destroyed = false;
|
|
73
109
|
activeAccountLoaded;
|
|
110
|
+
storageValidated;
|
|
74
111
|
appMetadataManager;
|
|
75
112
|
disclaimerText;
|
|
76
113
|
errorMessages;
|
|
@@ -78,6 +115,11 @@ export class DAppClient extends Client {
|
|
|
78
115
|
storageValidator;
|
|
79
116
|
beaconIDB = new IndexedDBStorage('beacon', ['bug_report', 'metrics']);
|
|
80
117
|
debounceSetActiveAccount = false;
|
|
118
|
+
// Constructor-time invalid storage recovery can race through multiple async paths.
|
|
119
|
+
// Keep this guard scoped to those startup paths.
|
|
120
|
+
hasEmittedInvalidAccountDeactivated = false;
|
|
121
|
+
_disconnectPromise;
|
|
122
|
+
_disconnectNotifyPeers = false;
|
|
81
123
|
multiTabChannel = new MultiTabChannel('octez.connect-sdk-channel', this.onBCMessageHandler.bind(this), this.onElectedLeaderhandler.bind(this));
|
|
82
124
|
constructor(config) {
|
|
83
125
|
super({
|
|
@@ -85,8 +127,11 @@ export class DAppClient extends Client {
|
|
|
85
127
|
...config
|
|
86
128
|
});
|
|
87
129
|
this.description = config.description;
|
|
88
|
-
this.
|
|
89
|
-
this.
|
|
130
|
+
this.disableWalletConnect = config.disableWalletConnect ?? false;
|
|
131
|
+
if (!this.disableWalletConnect) {
|
|
132
|
+
this.wcProjectId = config.walletConnectOptions?.projectId || '24469fd0a06df227b6e5f7dc7de0ff4f';
|
|
133
|
+
this.wcRelayUrl = config.walletConnectOptions?.relayUrl;
|
|
134
|
+
}
|
|
90
135
|
this.featuredWallets = config.featuredWallets;
|
|
91
136
|
this.events = new BeaconEventHandler(config.eventHandlers, config.disableDefaultEvents ?? false);
|
|
92
137
|
this.blockExplorer = config.blockExplorer ?? new TzktBlockExplorer();
|
|
@@ -99,24 +144,32 @@ export class DAppClient extends Client {
|
|
|
99
144
|
this.enableAppSwitching =
|
|
100
145
|
config.enableAppSwitching === undefined ? true : !!config.enableAppSwitching;
|
|
101
146
|
this.enableMetrics = config.enableMetrics ? true : false;
|
|
147
|
+
this.requestTimeoutMs = config.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
102
148
|
// Subscribe to storage changes and update the active account if it changes on other tabs
|
|
103
149
|
this.storage.subscribeToStorageChanged(async (event) => {
|
|
150
|
+
if (event.oldValue === event.newValue) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
104
153
|
if (event.eventType === 'storageCleared') {
|
|
105
|
-
this.setActiveAccount(undefined);
|
|
154
|
+
await this.setActiveAccount(undefined);
|
|
106
155
|
return;
|
|
107
156
|
}
|
|
108
157
|
if (event.eventType === 'entryModified') {
|
|
109
158
|
if (event.key === this.storage.getPrefixedKey(StorageKey.ACTIVE_ACCOUNT)) {
|
|
110
159
|
const accountIdentifier = event.newValue;
|
|
111
160
|
if (!accountIdentifier || accountIdentifier === 'undefined') {
|
|
112
|
-
this.setActiveAccount(undefined);
|
|
161
|
+
await this.setActiveAccount(undefined);
|
|
113
162
|
}
|
|
114
163
|
else {
|
|
115
164
|
const account = await this.getAccount(accountIdentifier);
|
|
116
|
-
this.setActiveAccount(account);
|
|
165
|
+
await this.setActiveAccount(account);
|
|
117
166
|
}
|
|
118
167
|
return;
|
|
119
168
|
}
|
|
169
|
+
if (event.key === this.storage.getPrefixedKey(StorageKey.ACCOUNTS)) {
|
|
170
|
+
await this.recoverActiveAccountFromAccountsChange();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
120
173
|
if (event.key === this.storage.getPrefixedKey(StorageKey.ENABLE_METRICS)) {
|
|
121
174
|
this.enableMetrics = !!(await this.storage.get(StorageKey.ENABLE_METRICS));
|
|
122
175
|
return;
|
|
@@ -132,30 +185,42 @@ export class DAppClient extends Client {
|
|
|
132
185
|
this.activeAccountLoaded = this.storage
|
|
133
186
|
.get(StorageKey.ACTIVE_ACCOUNT)
|
|
134
187
|
.then(async (activeAccountIdentifier) => {
|
|
188
|
+
const accounts = await this.accountManager.getAccounts();
|
|
189
|
+
if (activeAccountIdentifier === 'undefined') {
|
|
190
|
+
// Non-normalizing storage backends can still surface legacy sentinel strings directly.
|
|
191
|
+
await this.deactivateInvalidAccountState('missing_active_account');
|
|
192
|
+
return undefined;
|
|
193
|
+
}
|
|
135
194
|
if (activeAccountIdentifier) {
|
|
136
|
-
const account =
|
|
195
|
+
const account = accounts.find((storedAccount) => storedAccount.accountIdentifier === activeAccountIdentifier);
|
|
196
|
+
if (!account) {
|
|
197
|
+
await this.deactivateInvalidAccountState('missing_active_account');
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
137
200
|
await this.setActiveAccount(account);
|
|
138
201
|
return account;
|
|
139
202
|
}
|
|
140
|
-
|
|
141
|
-
await this.
|
|
203
|
+
if (accounts.length > 0 && this.hasStoredActiveAccountPointer()) {
|
|
204
|
+
await this.deactivateInvalidAccountState('missing_active_account');
|
|
142
205
|
return undefined;
|
|
143
206
|
}
|
|
207
|
+
await this.setActiveAccount(undefined);
|
|
208
|
+
return undefined;
|
|
144
209
|
})
|
|
145
210
|
.catch(async (storageError) => {
|
|
146
211
|
logger.error(storageError);
|
|
147
|
-
await this.
|
|
148
|
-
this.events.emit(BeaconEvent.INVALID_ACCOUNT_DEACTIVATED);
|
|
212
|
+
await this.deactivateInvalidAccountState('invalid_active_account_storage');
|
|
149
213
|
return undefined;
|
|
150
214
|
});
|
|
151
215
|
this.handleResponse = async (message, connectionInfo) => {
|
|
152
|
-
const
|
|
216
|
+
const isV3WrappedMessage = message.version === '3' && 'message' in message;
|
|
217
|
+
const typedMessage = isV3WrappedMessage
|
|
153
218
|
? message.message
|
|
154
219
|
: message;
|
|
155
|
-
let appMetadata =
|
|
220
|
+
let appMetadata = isV3WrappedMessage
|
|
156
221
|
? typedMessage.blockchainData?.appMetadata
|
|
157
222
|
: typedMessage.appMetadata;
|
|
158
|
-
if (!appMetadata &&
|
|
223
|
+
if (!appMetadata && isV3WrappedMessage) {
|
|
159
224
|
const storedMetadata = await Promise.all([
|
|
160
225
|
this.storage.get(StorageKey.TRANSPORT_P2P_PEERS_DAPP),
|
|
161
226
|
this.storage.get(StorageKey.TRANSPORT_WALLETCONNECT_PEERS_DAPP),
|
|
@@ -184,8 +249,11 @@ export class DAppClient extends Client {
|
|
|
184
249
|
},
|
|
185
250
|
id: message.id
|
|
186
251
|
});
|
|
187
|
-
if (typedMessage.type
|
|
188
|
-
this.
|
|
252
|
+
if (typedMessage.type === BeaconMessageType.Acknowledge) {
|
|
253
|
+
this.clearOpenRequestTimeout(message.id);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
this.clearOpenRequest(message.id);
|
|
189
257
|
}
|
|
190
258
|
return;
|
|
191
259
|
}
|
|
@@ -212,15 +280,19 @@ export class DAppClient extends Client {
|
|
|
212
280
|
await this.events.emit(BeaconEvent.CHANNEL_CLOSED);
|
|
213
281
|
};
|
|
214
282
|
if (openRequest && typedMessage.type === BeaconMessageType.Acknowledge) {
|
|
215
|
-
this.
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
.
|
|
219
|
-
message
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
283
|
+
if (!this.acknowledgedRequests.has(message.id)) {
|
|
284
|
+
this.acknowledgedRequests.add(message.id);
|
|
285
|
+
this.clearOpenRequestTimeout(message.id);
|
|
286
|
+
this.analytics.track('event', 'DAppClient', 'Acknowledge received from Wallet');
|
|
287
|
+
logger.log('handleResponse', `acknowledge message received for ${message.id}`);
|
|
288
|
+
this.events
|
|
289
|
+
.emit(BeaconEvent.ACKNOWLEDGE_RECEIVED, {
|
|
290
|
+
message: typedMessage,
|
|
291
|
+
extraInfo: {},
|
|
292
|
+
walletInfo: await this.getWalletInfo()
|
|
293
|
+
})
|
|
294
|
+
.catch((emitError) => logger.error('handleResponse', emitError));
|
|
295
|
+
}
|
|
224
296
|
}
|
|
225
297
|
else if (openRequest) {
|
|
226
298
|
if (typedMessage.type === BeaconMessageType.PermissionResponse && appMetadata) {
|
|
@@ -232,7 +304,8 @@ export class DAppClient extends Client {
|
|
|
232
304
|
else {
|
|
233
305
|
openRequest.resolve({ message, connectionInfo });
|
|
234
306
|
}
|
|
235
|
-
this.
|
|
307
|
+
this.clearOpenRequest(message.id);
|
|
308
|
+
this.acknowledgedRequests.delete(message.id);
|
|
236
309
|
}
|
|
237
310
|
else {
|
|
238
311
|
if (typedMessage.type === BeaconMessageType.Disconnect) {
|
|
@@ -241,16 +314,23 @@ export class DAppClient extends Client {
|
|
|
241
314
|
else if (typedMessage.type === BeaconMessageType.ChangeAccountRequest) {
|
|
242
315
|
await this.onNewAccount(typedMessage, connectionInfo);
|
|
243
316
|
}
|
|
317
|
+
else {
|
|
318
|
+
logger.warn('handleResponse', 'received response for unknown or closed request', {
|
|
319
|
+
id: message.id,
|
|
320
|
+
type: typedMessage.type,
|
|
321
|
+
connectionInfo
|
|
322
|
+
});
|
|
323
|
+
}
|
|
244
324
|
}
|
|
245
325
|
if (this._transport.isResolved()) {
|
|
246
326
|
const transport = await this.transport;
|
|
247
327
|
if (transport instanceof WalletConnectTransport &&
|
|
248
|
-
!this.openRequests.has(
|
|
249
|
-
this.openRequests.set(
|
|
328
|
+
!this.openRequests.has(SESSION_UPDATE_REQUEST_ID)) {
|
|
329
|
+
this.openRequests.set(SESSION_UPDATE_REQUEST_ID, new ExposedPromise());
|
|
250
330
|
}
|
|
251
331
|
}
|
|
252
332
|
};
|
|
253
|
-
this.storageValidator
|
|
333
|
+
this.storageValidated = this.storageValidator
|
|
254
334
|
.validate()
|
|
255
335
|
.then(async (isValid) => {
|
|
256
336
|
const account = await this.activeAccountLoaded;
|
|
@@ -266,15 +346,22 @@ export class DAppClient extends Client {
|
|
|
266
346
|
url: info.deeplink
|
|
267
347
|
});
|
|
268
348
|
const nowValid = await this.storageValidator.validate();
|
|
269
|
-
if (!nowValid) {
|
|
270
|
-
this.
|
|
349
|
+
if (!nowValid && account) {
|
|
350
|
+
await this.deactivateInvalidAccountState('storage_validation_failed');
|
|
351
|
+
}
|
|
352
|
+
else if (!nowValid) {
|
|
353
|
+
await this.resetInvalidState(false);
|
|
271
354
|
}
|
|
272
355
|
}
|
|
273
356
|
if (account && account.origin.type !== 'p2p') {
|
|
274
|
-
|
|
357
|
+
// Fire-and-forget eager init for stored sessions. init() can now
|
|
358
|
+
// reject (e.g. when an abort path settles _initPromise), so swallow
|
|
359
|
+
// here; downstream callers of init() handle rejection on their own.
|
|
360
|
+
this.init().catch((initError) => logger.error('eager init failed', initError?.message ?? initError));
|
|
275
361
|
}
|
|
276
362
|
})
|
|
277
363
|
.catch((err) => logger.error(err.message));
|
|
364
|
+
this.retainStorageValidationPromise();
|
|
278
365
|
this.sendMetrics('enable-metrics?' + this.addQueryParam('version', SDK_VERSION), undefined, (res) => {
|
|
279
366
|
if (!res.ok) {
|
|
280
367
|
res.status === 426
|
|
@@ -345,9 +432,43 @@ export class DAppClient extends Client {
|
|
|
345
432
|
const transport = (await this._transport.promise);
|
|
346
433
|
await transport.waitForResolution();
|
|
347
434
|
this.openRequestsOtherTabs.add(message.id);
|
|
348
|
-
isV3
|
|
435
|
+
const requestPromise = isV3
|
|
349
436
|
? this.makeRequestV3(message.data, message.id)
|
|
350
437
|
: this.makeRequest(message.data, false, message.id);
|
|
438
|
+
requestPromise.catch((requestError) => {
|
|
439
|
+
const errorResponse = this.buildDelegatedRequestError(message.id, requestError);
|
|
440
|
+
this.multiTabChannel.postMessage({
|
|
441
|
+
type: 'RESPONSE',
|
|
442
|
+
data: {
|
|
443
|
+
message: errorResponse,
|
|
444
|
+
connectionInfo: {
|
|
445
|
+
origin: Origin.WALLETCONNECT,
|
|
446
|
+
id: ''
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
id: message.id
|
|
450
|
+
});
|
|
451
|
+
this.clearOpenRequest(message.id);
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
buildDelegatedRequestError(id, error) {
|
|
455
|
+
if (typeof error === 'object' &&
|
|
456
|
+
error !== null &&
|
|
457
|
+
'type' in error &&
|
|
458
|
+
'errorType' in error &&
|
|
459
|
+
error.type === BeaconMessageType.Error) {
|
|
460
|
+
return {
|
|
461
|
+
...error,
|
|
462
|
+
id
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
id,
|
|
467
|
+
type: BeaconMessageType.Error,
|
|
468
|
+
errorType: BeaconErrorType.UNKNOWN_ERROR,
|
|
469
|
+
senderId: '',
|
|
470
|
+
version: '2'
|
|
471
|
+
};
|
|
351
472
|
}
|
|
352
473
|
async createStateSnapshot() {
|
|
353
474
|
if (!localStorage || !this.enableMetrics) {
|
|
@@ -373,10 +494,7 @@ export class DAppClient extends Client {
|
|
|
373
494
|
this.storage.set(StorageKey.USER_ID, this.userId);
|
|
374
495
|
}
|
|
375
496
|
async initInternalTransports() {
|
|
376
|
-
const seed = await this.
|
|
377
|
-
if (!seed) {
|
|
378
|
-
throw new Error('Secret seed not found');
|
|
379
|
-
}
|
|
497
|
+
const seed = await this.getOrCreateSDKSecretSeed();
|
|
380
498
|
const keyPair = await getKeypairFromSeed(seed);
|
|
381
499
|
if (this.postMessageTransport || this.p2pTransport || this.walletConnectTransport) {
|
|
382
500
|
return;
|
|
@@ -385,6 +503,11 @@ export class DAppClient extends Client {
|
|
|
385
503
|
await this.addListener(this.postMessageTransport);
|
|
386
504
|
this.p2pTransport = new DappP2PTransport(this.name, keyPair, this.storage, this.matrixNodes, this.iconUrl, this.appUrl);
|
|
387
505
|
await this.addListener(this.p2pTransport);
|
|
506
|
+
// Skip WalletConnect entirely when disabled (e.g. inside a Firefox MV3 content-script
|
|
507
|
+
// compartment where the WC provider cannot run). postMessage + P2P are unaffected.
|
|
508
|
+
if (this.disableWalletConnect) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
388
511
|
const wcOptions = {
|
|
389
512
|
projectId: this.wcProjectId,
|
|
390
513
|
relayUrl: this.wcRelayUrl,
|
|
@@ -402,6 +525,37 @@ export class DAppClient extends Client {
|
|
|
402
525
|
this.initEvents();
|
|
403
526
|
await this.addListener(this.walletConnectTransport);
|
|
404
527
|
}
|
|
528
|
+
async getOrCreateSDKSecretSeed() {
|
|
529
|
+
if (this._sdkSecretSeedPromise) {
|
|
530
|
+
return this._sdkSecretSeedPromise;
|
|
531
|
+
}
|
|
532
|
+
this._sdkSecretSeedPromise = this.loadOrCreateSDKSecretSeed()
|
|
533
|
+
.finally(() => {
|
|
534
|
+
this._sdkSecretSeedPromise = undefined;
|
|
535
|
+
})
|
|
536
|
+
.catch((error) => {
|
|
537
|
+
throw error;
|
|
538
|
+
});
|
|
539
|
+
return this._sdkSecretSeedPromise;
|
|
540
|
+
}
|
|
541
|
+
async loadOrCreateSDKSecretSeed() {
|
|
542
|
+
const existingSeed = await this.storage.get(StorageKey.BEACON_SDK_SECRET_SEED);
|
|
543
|
+
if (this.isValidSDKSecretSeed(existingSeed)) {
|
|
544
|
+
return existingSeed;
|
|
545
|
+
}
|
|
546
|
+
await this.storage.delete(StorageKey.BEACON_SDK_SECRET_SEED);
|
|
547
|
+
this._keyPair = new ExposedPromise();
|
|
548
|
+
this._beaconId = new ExposedPromise();
|
|
549
|
+
await this.initSDK();
|
|
550
|
+
const restoredSeed = await this.storage.get(StorageKey.BEACON_SDK_SECRET_SEED);
|
|
551
|
+
if (this.isValidSDKSecretSeed(restoredSeed)) {
|
|
552
|
+
return restoredSeed;
|
|
553
|
+
}
|
|
554
|
+
throw new Error('Secret seed not found');
|
|
555
|
+
}
|
|
556
|
+
isValidSDKSecretSeed(seed) {
|
|
557
|
+
return typeof seed === 'string' && seed !== '' && seed !== 'undefined' && seed !== 'null';
|
|
558
|
+
}
|
|
405
559
|
initEvents() {
|
|
406
560
|
if (!this.walletConnectTransport) {
|
|
407
561
|
return;
|
|
@@ -433,20 +587,26 @@ export class DAppClient extends Client {
|
|
|
433
587
|
});
|
|
434
588
|
}
|
|
435
589
|
else {
|
|
436
|
-
this
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
590
|
+
// The WC transport reports session-proposal rejection through this event
|
|
591
|
+
// when there are no active listeners yet (no peer paired). Reject the
|
|
592
|
+
// in-flight init promise so `await dapp.requestPermissions()` unwinds
|
|
593
|
+
// through requestPermissions' catch -> handleRequestError, which both
|
|
594
|
+
// emits PERMISSION_REQUEST_ERROR and resets transport state. Without
|
|
595
|
+
// this, the dapp wedges with a pending `_initPromise`.
|
|
596
|
+
this._initReject?.(this.createAbortedError());
|
|
440
597
|
}
|
|
441
598
|
}
|
|
442
599
|
async channelClosedHandler(type) {
|
|
600
|
+
if (!this._transport.isResolved()) {
|
|
601
|
+
await this.events.emit(BeaconEvent.CHANNEL_CLOSED);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
443
604
|
const transport = await this.transport;
|
|
444
605
|
if (transport.type !== type) {
|
|
445
606
|
return;
|
|
446
607
|
}
|
|
447
608
|
await this.events.emit(BeaconEvent.CHANNEL_CLOSED);
|
|
448
|
-
this.
|
|
449
|
-
await this.disconnect();
|
|
609
|
+
await this.disconnect({ notifyPeers: false });
|
|
450
610
|
}
|
|
451
611
|
/**
|
|
452
612
|
* Destroy the instance.
|
|
@@ -456,11 +616,73 @@ export class DAppClient extends Client {
|
|
|
456
616
|
* If you wish to disconnect your dApp, use `disconnect` instead.
|
|
457
617
|
*/
|
|
458
618
|
async destroy() {
|
|
619
|
+
this.destroyed = true;
|
|
459
620
|
await this.createStateSnapshot();
|
|
621
|
+
await this.destroyInternalTransports();
|
|
460
622
|
await super.destroy();
|
|
461
623
|
}
|
|
624
|
+
async destroyInternalTransports() {
|
|
625
|
+
this.abortPendingInit();
|
|
626
|
+
const transports = [];
|
|
627
|
+
if (this.postMessageTransport) {
|
|
628
|
+
transports.push(this.postMessageTransport);
|
|
629
|
+
}
|
|
630
|
+
if (this.p2pTransport) {
|
|
631
|
+
transports.push(this.p2pTransport);
|
|
632
|
+
}
|
|
633
|
+
if (this.walletConnectTransport) {
|
|
634
|
+
transports.push(this.walletConnectTransport);
|
|
635
|
+
}
|
|
636
|
+
if (transports.length > 0) {
|
|
637
|
+
await this.cleanup(transports);
|
|
638
|
+
await Promise.all(transports.map(async (transport) => {
|
|
639
|
+
await transport.disconnect();
|
|
640
|
+
if ('doClientCleanup' in transport) {
|
|
641
|
+
await transport.doClientCleanup();
|
|
642
|
+
}
|
|
643
|
+
}));
|
|
644
|
+
this.postMessageTransport = this.p2pTransport = this.walletConnectTransport = undefined;
|
|
645
|
+
await this.setTransport(undefined);
|
|
646
|
+
}
|
|
647
|
+
await this.multiTabChannel.close();
|
|
648
|
+
}
|
|
649
|
+
abortPendingInit() {
|
|
650
|
+
this._initReject?.(this.createAbortedError());
|
|
651
|
+
this._initPromise = undefined;
|
|
652
|
+
this._initReject = undefined;
|
|
653
|
+
this._initSubstratePairing = undefined;
|
|
654
|
+
this._requestPermissionsPromise = undefined;
|
|
655
|
+
this._requestPermissionsKey = undefined;
|
|
656
|
+
}
|
|
657
|
+
createAbortedError(id = '') {
|
|
658
|
+
return {
|
|
659
|
+
type: BeaconMessageType.Error,
|
|
660
|
+
id,
|
|
661
|
+
senderId: '',
|
|
662
|
+
version: '2',
|
|
663
|
+
errorType: BeaconErrorType.ABORTED_ERROR
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* @internal
|
|
668
|
+
*/
|
|
669
|
+
isDestroyed() {
|
|
670
|
+
return this.destroyed;
|
|
671
|
+
}
|
|
672
|
+
assertNotDestroyed() {
|
|
673
|
+
if (this.destroyed) {
|
|
674
|
+
throw new Error('DAppClient has been destroyed and cannot be used again.');
|
|
675
|
+
}
|
|
676
|
+
}
|
|
462
677
|
async init(transport, substratePairing) {
|
|
678
|
+
this.assertNotDestroyed();
|
|
679
|
+
const requestedSubstratePairing = substratePairing === true;
|
|
463
680
|
if (this._initPromise) {
|
|
681
|
+
if (!transport &&
|
|
682
|
+
this._initReject &&
|
|
683
|
+
this._initSubstratePairing !== requestedSubstratePairing) {
|
|
684
|
+
throw new Error('Cannot start a permission request with different pairing options while another pairing is pending');
|
|
685
|
+
}
|
|
464
686
|
return this._initPromise;
|
|
465
687
|
}
|
|
466
688
|
try {
|
|
@@ -469,160 +691,265 @@ export class DAppClient extends Client {
|
|
|
469
691
|
catch {
|
|
470
692
|
//
|
|
471
693
|
}
|
|
472
|
-
this._initPromise = new Promise(async (resolve) => {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
}
|
|
493
|
-
};
|
|
494
|
-
await this.initInternalTransports();
|
|
495
|
-
if (!this.postMessageTransport || !this.p2pTransport || !this.walletConnectTransport) {
|
|
496
|
-
return;
|
|
694
|
+
this._initPromise = new Promise(async (resolve, reject) => {
|
|
695
|
+
this._initSubstratePairing = requestedSubstratePairing;
|
|
696
|
+
// Capture reject so abort paths (modal close, transport-level rejection)
|
|
697
|
+
// can unwedge a pending `await this.init()`. _initReject is cleared by
|
|
698
|
+
// the .finally below whether the promise resolves or rejects, so the
|
|
699
|
+
// field accurately means "currently rejectable" rather than "most recent
|
|
700
|
+
// rejector".
|
|
701
|
+
this._initReject = (reason) => {
|
|
702
|
+
this._initPromise = undefined;
|
|
703
|
+
reject(reason);
|
|
704
|
+
};
|
|
705
|
+
// The body uses `new Promise(async ...)` (an anti-pattern: unhandled
|
|
706
|
+
// throws are swallowed). Wrap it so any unexpected throw rejects the
|
|
707
|
+
// promise instead of stranding `await this.init()` callers forever.
|
|
708
|
+
// A proper structural fix would replace the async-executor entirely
|
|
709
|
+
// with chained promises; left as follow-up to keep this PR focused.
|
|
710
|
+
try {
|
|
711
|
+
if (transport) {
|
|
712
|
+
await this.addListener(transport);
|
|
713
|
+
resolve(await super.init(transport));
|
|
497
714
|
}
|
|
498
|
-
this.
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
// Select the transport that matches the active account
|
|
502
|
-
if (origin === Origin.EXTENSION) {
|
|
503
|
-
resolve(await super.init(this.postMessageTransport));
|
|
504
|
-
}
|
|
505
|
-
else if (origin === Origin.P2P) {
|
|
506
|
-
resolve(await super.init(this.p2pTransport));
|
|
507
|
-
}
|
|
508
|
-
else if (origin === Origin.WALLETCONNECT) {
|
|
509
|
-
resolve(await super.init(this.walletConnectTransport));
|
|
510
|
-
}
|
|
715
|
+
else if (this._transport.isSettled()) {
|
|
716
|
+
await (await this.transport).connect();
|
|
717
|
+
resolve(await super.init(await this.transport));
|
|
511
718
|
}
|
|
512
719
|
else {
|
|
513
|
-
const
|
|
514
|
-
const
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
this.
|
|
520
|
-
|
|
720
|
+
const activeAccount = await this.getActiveAccount();
|
|
721
|
+
const stopListening = () => {
|
|
722
|
+
const onError = (err) => logger.error('stopListeningForNewPeers', err);
|
|
723
|
+
if (this.postMessageTransport) {
|
|
724
|
+
this.postMessageTransport.stopListeningForNewPeers().catch(onError);
|
|
725
|
+
}
|
|
726
|
+
if (this.p2pTransport) {
|
|
727
|
+
this.p2pTransport.stopListeningForNewPeers().catch(onError);
|
|
728
|
+
}
|
|
729
|
+
if (this.walletConnectTransport) {
|
|
730
|
+
this.walletConnectTransport.stopListeningForNewPeers().catch(onError);
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
await this.initInternalTransports();
|
|
734
|
+
if (!this.postMessageTransport ||
|
|
735
|
+
!this.p2pTransport ||
|
|
736
|
+
(!this.disableWalletConnect && !this.walletConnectTransport)) {
|
|
737
|
+
// Bare return here used to strand the promise forever -- no UI is
|
|
738
|
+
// emitted, no peer ever pairs, no abort path fires. Reject explicitly
|
|
739
|
+
// so callers see a deterministic error. (WalletConnect is exempt when
|
|
740
|
+
// disableWalletConnect is set -- its transport is intentionally absent.)
|
|
741
|
+
const noTransportError = {
|
|
742
|
+
type: BeaconMessageType.Error,
|
|
743
|
+
id: '',
|
|
744
|
+
senderId: '',
|
|
745
|
+
version: '2',
|
|
746
|
+
errorType: BeaconErrorType.UNKNOWN_ERROR
|
|
747
|
+
};
|
|
748
|
+
reject(noTransportError);
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
this.postMessageTransport.connect().then().catch(console.error);
|
|
752
|
+
if (activeAccount && activeAccount.origin) {
|
|
753
|
+
const origin = activeAccount.origin.type;
|
|
754
|
+
// Select the transport that matches the active account
|
|
755
|
+
if (origin === Origin.EXTENSION) {
|
|
756
|
+
resolve(await super.init(this.postMessageTransport));
|
|
757
|
+
}
|
|
758
|
+
else if (origin === Origin.P2P) {
|
|
759
|
+
resolve(await super.init(this.p2pTransport));
|
|
760
|
+
}
|
|
761
|
+
else if (origin === Origin.WALLETCONNECT && this.walletConnectTransport) {
|
|
762
|
+
resolve(await super.init(this.walletConnectTransport));
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
const p2pTransport = this.p2pTransport;
|
|
767
|
+
const postMessageTransport = this.postMessageTransport;
|
|
768
|
+
const walletConnectTransport = this.walletConnectTransport;
|
|
769
|
+
postMessageTransport
|
|
770
|
+
.listenForNewPeer((peer) => {
|
|
771
|
+
logger.log('init', 'postmessage transport peer connected', peer);
|
|
772
|
+
this.analytics.track('event', 'DAppClient', 'Extension connected', {
|
|
773
|
+
peerName: peer.name
|
|
774
|
+
});
|
|
775
|
+
this.events
|
|
776
|
+
.emit(BeaconEvent.PAIR_SUCCESS, peer)
|
|
777
|
+
.catch((emitError) => console.warn(emitError));
|
|
778
|
+
this.setActivePeer(peer).catch(console.error);
|
|
779
|
+
this.setTransport(this.postMessageTransport).catch(console.error);
|
|
780
|
+
stopListening();
|
|
781
|
+
resolve(TransportType.POST_MESSAGE);
|
|
782
|
+
})
|
|
783
|
+
.catch(console.error);
|
|
784
|
+
p2pTransport
|
|
785
|
+
.listenForNewPeer((peer) => {
|
|
786
|
+
logger.log('init', 'p2p transport peer connected', peer);
|
|
787
|
+
this.analytics.track('event', 'DAppClient', 'octez.connect Wallet connected', {
|
|
788
|
+
peerName: peer.name
|
|
789
|
+
});
|
|
790
|
+
this.events
|
|
791
|
+
.emit(BeaconEvent.PAIR_SUCCESS, peer)
|
|
792
|
+
.catch((emitError) => console.warn(emitError));
|
|
793
|
+
this.setActivePeer(peer).catch(console.error);
|
|
794
|
+
this.setTransport(this.p2pTransport).catch(console.error);
|
|
795
|
+
stopListening();
|
|
796
|
+
resolve(TransportType.P2P);
|
|
797
|
+
})
|
|
798
|
+
.catch(console.error);
|
|
799
|
+
walletConnectTransport
|
|
800
|
+
?.listenForNewPeer((peer) => {
|
|
801
|
+
logger.log('init', 'walletconnect transport peer connected', peer);
|
|
802
|
+
this.analytics.track('event', 'DAppClient', 'WalletConnect Wallet connected', {
|
|
803
|
+
peerName: peer.name
|
|
804
|
+
});
|
|
805
|
+
this.events
|
|
806
|
+
.emit(BeaconEvent.PAIR_SUCCESS, peer)
|
|
807
|
+
.catch((emitError) => console.warn(emitError));
|
|
808
|
+
this.setActivePeer(peer).catch(console.error);
|
|
809
|
+
this.setTransport(this.walletConnectTransport).catch(console.error);
|
|
810
|
+
stopListening();
|
|
811
|
+
resolve(TransportType.WALLETCONNECT);
|
|
812
|
+
})
|
|
813
|
+
.catch(console.error);
|
|
814
|
+
PostMessageTransport.getAvailableExtensions()
|
|
815
|
+
.then(async (extensions) => {
|
|
816
|
+
this.analytics.track('event', 'DAppClient', 'Extensions detected', { extensions });
|
|
817
|
+
})
|
|
818
|
+
.catch((error) => {
|
|
819
|
+
this._initPromise = undefined;
|
|
820
|
+
console.error(error);
|
|
521
821
|
});
|
|
522
|
-
|
|
523
|
-
.
|
|
524
|
-
.
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
822
|
+
const abortHandler = async () => {
|
|
823
|
+
logger.log('init', 'ABORTED');
|
|
824
|
+
this.sendMetrics('performance-metrics/save', await this.buildPayload('connect', 'abort'));
|
|
825
|
+
await Promise.all([
|
|
826
|
+
postMessageTransport.disconnect(),
|
|
827
|
+
// p2pTransport.disconnect(), do not abort connection manually
|
|
828
|
+
walletConnectTransport?.disconnect()
|
|
829
|
+
]);
|
|
830
|
+
this.postMessageTransport = this.walletConnectTransport = this.p2pTransport = undefined;
|
|
831
|
+
this._activeAccount.isResolved() && this.clearActiveAccount();
|
|
832
|
+
// Reject _initPromise so any awaiter (makeRequest -> requestPermissions)
|
|
833
|
+
// unwinds via handleRequestError instead of hanging. _initReject
|
|
834
|
+
// also clears _initPromise as a side effect.
|
|
835
|
+
this._initReject?.(this.createAbortedError());
|
|
836
|
+
};
|
|
837
|
+
const serializer = new Serializer();
|
|
838
|
+
const p2pPeerInfo = new Promise(async (resolve) => {
|
|
839
|
+
try {
|
|
840
|
+
await p2pTransport.connect();
|
|
841
|
+
}
|
|
842
|
+
catch (err) {
|
|
843
|
+
logger.error(err);
|
|
844
|
+
await this.hideUI(['alert']); // hide pairing alert
|
|
845
|
+
setTimeout(() => this.events.emit(BeaconEvent.GENERIC_ERROR, err.message), 1000);
|
|
846
|
+
abortHandler().catch((abortErr) => logger.warn('init', 'abortHandler', abortErr));
|
|
847
|
+
resolve('');
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
resolve(await serializer.serialize(await p2pTransport.getPairingRequestInfo()));
|
|
536
851
|
});
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
logger.log('init', 'walletconnect transport peer connected', peer);
|
|
549
|
-
this.analytics.track('event', 'DAppClient', 'WalletConnect Wallet connected', {
|
|
550
|
-
peerName: peer.name
|
|
852
|
+
const walletConnectPeerInfo = walletConnectTransport
|
|
853
|
+
? createLazyPromise(() => walletConnectTransport
|
|
854
|
+
.getPairingRequestInfo()
|
|
855
|
+
.then((pairingRequestInfo) => pairingRequestInfo.uri)
|
|
856
|
+
.catch((error) => {
|
|
857
|
+
logger.warn('init', 'walletconnect pairing request failed', error);
|
|
858
|
+
return '';
|
|
859
|
+
}))
|
|
860
|
+
: undefined;
|
|
861
|
+
const postmessagePeerInfo = new Promise(async (resolve) => {
|
|
862
|
+
resolve(await serializer.serialize(await postMessageTransport.getPairingRequestInfo()));
|
|
551
863
|
});
|
|
552
864
|
this.events
|
|
553
|
-
.emit(BeaconEvent.
|
|
865
|
+
.emit(BeaconEvent.PAIR_INIT, {
|
|
866
|
+
p2pPeerInfo,
|
|
867
|
+
postmessagePeerInfo,
|
|
868
|
+
// Omitted entirely when WalletConnect is disabled, so the pairing UI
|
|
869
|
+
// never tries to read a peer-info promise for a transport that isn't there.
|
|
870
|
+
...(walletConnectPeerInfo ? { walletConnectPeerInfo } : {}),
|
|
871
|
+
networkType: this.network.type,
|
|
872
|
+
abortedHandler: abortHandler.bind(this),
|
|
873
|
+
disclaimerText: this.disclaimerText,
|
|
874
|
+
analytics: this.analytics,
|
|
875
|
+
featuredWallets: this.featuredWallets,
|
|
876
|
+
substratePairing
|
|
877
|
+
})
|
|
554
878
|
.catch((emitError) => console.warn(emitError));
|
|
555
|
-
|
|
556
|
-
this.setTransport(this.walletConnectTransport).catch(console.error);
|
|
557
|
-
stopListening();
|
|
558
|
-
resolve(TransportType.WALLETCONNECT);
|
|
559
|
-
})
|
|
560
|
-
.catch(console.error);
|
|
561
|
-
PostMessageTransport.getAvailableExtensions()
|
|
562
|
-
.then(async (extensions) => {
|
|
563
|
-
this.analytics.track('event', 'DAppClient', 'Extensions detected', { extensions });
|
|
564
|
-
})
|
|
565
|
-
.catch((error) => {
|
|
566
|
-
this._initPromise = undefined;
|
|
567
|
-
console.error(error);
|
|
568
|
-
});
|
|
569
|
-
const abortHandler = async () => {
|
|
570
|
-
logger.log('init', 'ABORTED');
|
|
571
|
-
this.sendMetrics('performance-metrics/save', await this.buildPayload('connect', 'abort'));
|
|
572
|
-
await Promise.all([
|
|
573
|
-
postMessageTransport.disconnect(),
|
|
574
|
-
// p2pTransport.disconnect(), do not abort connection manually
|
|
575
|
-
walletConnectTransport.disconnect()
|
|
576
|
-
]);
|
|
577
|
-
this.postMessageTransport = this.walletConnectTransport = this.p2pTransport = undefined;
|
|
578
|
-
this._activeAccount.isResolved() && this.clearActiveAccount();
|
|
579
|
-
this._initPromise = undefined;
|
|
580
|
-
};
|
|
581
|
-
const serializer = new Serializer();
|
|
582
|
-
const p2pPeerInfo = new Promise(async (resolve) => {
|
|
583
|
-
try {
|
|
584
|
-
await p2pTransport.connect();
|
|
585
|
-
}
|
|
586
|
-
catch (err) {
|
|
587
|
-
logger.error(err);
|
|
588
|
-
await this.hideUI(['alert']); // hide pairing alert
|
|
589
|
-
setTimeout(() => this.events.emit(BeaconEvent.GENERIC_ERROR, err.message), 1000);
|
|
590
|
-
abortHandler();
|
|
591
|
-
resolve('');
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
|
-
resolve(await serializer.serialize(await p2pTransport.getPairingRequestInfo()));
|
|
595
|
-
});
|
|
596
|
-
const walletConnectPeerInfo = new Promise(async (resolve) => {
|
|
597
|
-
resolve((await walletConnectTransport.getPairingRequestInfo()).uri);
|
|
598
|
-
});
|
|
599
|
-
const postmessagePeerInfo = new Promise(async (resolve) => {
|
|
600
|
-
resolve(await serializer.serialize(await postMessageTransport.getPairingRequestInfo()));
|
|
601
|
-
});
|
|
602
|
-
this.events
|
|
603
|
-
.emit(BeaconEvent.PAIR_INIT, {
|
|
604
|
-
p2pPeerInfo,
|
|
605
|
-
postmessagePeerInfo,
|
|
606
|
-
walletConnectPeerInfo,
|
|
607
|
-
networkType: this.network.type,
|
|
608
|
-
abortedHandler: abortHandler.bind(this),
|
|
609
|
-
disclaimerText: this.disclaimerText,
|
|
610
|
-
analytics: this.analytics,
|
|
611
|
-
featuredWallets: this.featuredWallets,
|
|
612
|
-
substratePairing
|
|
613
|
-
})
|
|
614
|
-
.catch((emitError) => console.warn(emitError));
|
|
879
|
+
}
|
|
615
880
|
}
|
|
616
881
|
}
|
|
882
|
+
catch (err) {
|
|
883
|
+
// An async-executor throw would otherwise be silently swallowed,
|
|
884
|
+
// leaving _initPromise pending and every awaiter stranded.
|
|
885
|
+
reject(err);
|
|
886
|
+
}
|
|
617
887
|
});
|
|
888
|
+
// Drop the _initReject reference whether init resolved or rejected, so the
|
|
889
|
+
// field is never stale. Using an explicit helper keeps the dangling
|
|
890
|
+
// observer chain out of statement position (which the lint rules forbid).
|
|
891
|
+
this.clearInitRejectOnSettle(this._initPromise);
|
|
618
892
|
return this._initPromise;
|
|
619
893
|
}
|
|
894
|
+
/**
|
|
895
|
+
* Attach a settle observer to {@link _initPromise} that drops
|
|
896
|
+
* {@link _initReject} once the promise settles (resolved or rejected).
|
|
897
|
+
* Errors on the observer chain are swallowed; only the original
|
|
898
|
+
* {@link _initPromise} surfaces them to its awaiters.
|
|
899
|
+
*/
|
|
900
|
+
clearInitRejectOnSettle(initPromise) {
|
|
901
|
+
initPromise
|
|
902
|
+
.finally(() => {
|
|
903
|
+
this._initReject = undefined;
|
|
904
|
+
this._initSubstratePairing = undefined;
|
|
905
|
+
})
|
|
906
|
+
.catch(() => {
|
|
907
|
+
// observer-only; original promise's rejection is delivered to awaiters
|
|
908
|
+
});
|
|
909
|
+
}
|
|
620
910
|
/**
|
|
621
911
|
* Returns the active account
|
|
622
912
|
*/
|
|
623
913
|
async getActiveAccount() {
|
|
914
|
+
this.assertNotDestroyed();
|
|
624
915
|
return this._activeAccount.promise;
|
|
625
916
|
}
|
|
917
|
+
retainStorageValidationPromise() {
|
|
918
|
+
// Constructor validation is intentionally fire-and-forget, but retaining
|
|
919
|
+
// the promise gives tests a deterministic hook without changing public API.
|
|
920
|
+
this.storageValidated.catch((err) => logger.error(err.message));
|
|
921
|
+
}
|
|
922
|
+
async deactivateInvalidAccountState(reason) {
|
|
923
|
+
if (this.hasEmittedInvalidAccountDeactivated) {
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
this.hasEmittedInvalidAccountDeactivated = true;
|
|
927
|
+
logger.log('deactivateInvalidAccountState', reason);
|
|
928
|
+
if (reason === 'missing_active_account') {
|
|
929
|
+
await this.repairMissingActiveAccount();
|
|
930
|
+
}
|
|
931
|
+
else {
|
|
932
|
+
await this.resetInvalidState(false);
|
|
933
|
+
}
|
|
934
|
+
await this.events.emit(BeaconEvent.INVALID_ACCOUNT_DEACTIVATED, { reason });
|
|
935
|
+
}
|
|
936
|
+
async repairMissingActiveAccount() {
|
|
937
|
+
this._activeAccount = ExposedPromise.resolve(undefined);
|
|
938
|
+
await this.storage.delete(StorageKey.ACTIVE_ACCOUNT);
|
|
939
|
+
await this.setActivePeer(undefined);
|
|
940
|
+
}
|
|
941
|
+
hasStoredActiveAccountPointer() {
|
|
942
|
+
// LocalStorage-specific repair: that backend is where literal sentinel strings were produced.
|
|
943
|
+
if (typeof localStorage === 'undefined') {
|
|
944
|
+
return false;
|
|
945
|
+
}
|
|
946
|
+
try {
|
|
947
|
+
return localStorage.getItem(this.storage.getPrefixedKey(StorageKey.ACTIVE_ACCOUNT)) !== null;
|
|
948
|
+
}
|
|
949
|
+
catch {
|
|
950
|
+
return false;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
626
953
|
async isInvalidState(account) {
|
|
627
954
|
const activeAccount = await this._activeAccount.promise;
|
|
628
955
|
return !activeAccount
|
|
@@ -630,11 +957,15 @@ export class DAppClient extends Client {
|
|
|
630
957
|
: activeAccount?.address !== account?.address && !this.isGetActiveAccountHandled;
|
|
631
958
|
}
|
|
632
959
|
async resetInvalidState(emit = true) {
|
|
633
|
-
this.accountManager.removeAllAccounts();
|
|
960
|
+
await this.accountManager.removeAllAccounts();
|
|
634
961
|
this._activeAccount = ExposedPromise.resolve(undefined);
|
|
635
|
-
this.storage.
|
|
636
|
-
|
|
637
|
-
|
|
962
|
+
await this.storage.delete(StorageKey.ACTIVE_ACCOUNT);
|
|
963
|
+
if (emit) {
|
|
964
|
+
await this.events.emit(BeaconEvent.INVALID_ACTIVE_ACCOUNT_STATE);
|
|
965
|
+
}
|
|
966
|
+
else {
|
|
967
|
+
await this.hideUI(['alert']);
|
|
968
|
+
}
|
|
638
969
|
await Promise.all([
|
|
639
970
|
this.postMessageTransport?.disconnect(),
|
|
640
971
|
this.walletConnectTransport?.disconnect()
|
|
@@ -650,7 +981,8 @@ export class DAppClient extends Client {
|
|
|
650
981
|
* @param account The account that will be set as the active account
|
|
651
982
|
*/
|
|
652
983
|
async setActiveAccount(account) {
|
|
653
|
-
|
|
984
|
+
const activeAccountAlreadyCleared = !account && (await this.isActiveAccountAlreadyCleared());
|
|
985
|
+
if (!activeAccountAlreadyCleared && !this.isGetActiveAccountHandled) {
|
|
654
986
|
console.warn(`An active account has been received, but no active subscription was found for BeaconEvent.ACTIVE_ACCOUNT_SET.`);
|
|
655
987
|
}
|
|
656
988
|
if (account && this._activeAccount.isSettled() && (await this.isInvalidState(account))) {
|
|
@@ -662,9 +994,9 @@ export class DAppClient extends Client {
|
|
|
662
994
|
}
|
|
663
995
|
// when I'm resetting the activeAccount
|
|
664
996
|
if (!account && this._activeAccount.isResolved() && (await this.getActiveAccount())) {
|
|
665
|
-
const transport = await this.transport;
|
|
997
|
+
const transport = this._transport.isResolved() ? await this.transport : undefined;
|
|
666
998
|
const activeAccount = await this.getActiveAccount();
|
|
667
|
-
if (!
|
|
999
|
+
if (!activeAccount) {
|
|
668
1000
|
return;
|
|
669
1001
|
}
|
|
670
1002
|
if (!this.debounceSetActiveAccount && transport instanceof WalletConnectTransport) {
|
|
@@ -680,22 +1012,15 @@ export class DAppClient extends Client {
|
|
|
680
1012
|
type: 'DISCONNECT'
|
|
681
1013
|
});
|
|
682
1014
|
}
|
|
683
|
-
|
|
684
|
-
.filter(([id, _promise]) => id !== 'session_update')
|
|
685
|
-
.forEach(([id, promise]) => {
|
|
686
|
-
promise.reject({
|
|
687
|
-
type: BeaconMessageType.Error,
|
|
688
|
-
errorType: BeaconErrorType.ABORTED_ERROR,
|
|
689
|
-
id,
|
|
690
|
-
senderId: '',
|
|
691
|
-
version: '2'
|
|
692
|
-
});
|
|
693
|
-
});
|
|
694
|
-
this.openRequests.clear();
|
|
1015
|
+
this.abortOpenRequests();
|
|
695
1016
|
this.debounceSetActiveAccount = false;
|
|
696
1017
|
}
|
|
697
1018
|
}
|
|
698
|
-
if (
|
|
1019
|
+
if (activeAccountAlreadyCleared) {
|
|
1020
|
+
// Keep peer and transport cleanup below idempotent, but do not write storage or emit
|
|
1021
|
+
// another ACTIVE_ACCOUNT_SET(undefined) for an already-empty account.
|
|
1022
|
+
}
|
|
1023
|
+
else if (this._activeAccount.isSettled()) {
|
|
699
1024
|
// If the promise has already been resolved we need to create a new one.
|
|
700
1025
|
this._activeAccount = ExposedPromise.resolve(account);
|
|
701
1026
|
}
|
|
@@ -736,10 +1061,37 @@ export class DAppClient extends Client {
|
|
|
736
1061
|
await this.setActivePeer(undefined);
|
|
737
1062
|
await this.setTransport(undefined);
|
|
738
1063
|
}
|
|
739
|
-
|
|
740
|
-
|
|
1064
|
+
if (account) {
|
|
1065
|
+
await this.storage.set(StorageKey.ACTIVE_ACCOUNT, account.accountIdentifier);
|
|
1066
|
+
}
|
|
1067
|
+
else if (!activeAccountAlreadyCleared) {
|
|
1068
|
+
await this.storage.delete(StorageKey.ACTIVE_ACCOUNT);
|
|
1069
|
+
}
|
|
1070
|
+
if (!activeAccountAlreadyCleared) {
|
|
1071
|
+
await this.events.emit(BeaconEvent.ACTIVE_ACCOUNT_SET, account);
|
|
1072
|
+
}
|
|
741
1073
|
return;
|
|
742
1074
|
}
|
|
1075
|
+
async isActiveAccountAlreadyCleared() {
|
|
1076
|
+
return this._activeAccount.isResolved() && !(await this.getActiveAccount());
|
|
1077
|
+
}
|
|
1078
|
+
async recoverActiveAccountFromAccountsChange() {
|
|
1079
|
+
const activeAccountIdentifier = await this.storage.get(StorageKey.ACTIVE_ACCOUNT);
|
|
1080
|
+
if (!activeAccountIdentifier || activeAccountIdentifier === 'undefined') {
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
if (this._activeAccount.isResolved()) {
|
|
1084
|
+
const activeAccount = await this.getActiveAccount();
|
|
1085
|
+
if (activeAccount?.accountIdentifier === activeAccountIdentifier) {
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
const account = await this.getAccount(activeAccountIdentifier);
|
|
1090
|
+
if (!account) {
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
await this.setActiveAccount(account);
|
|
1094
|
+
}
|
|
743
1095
|
/**
|
|
744
1096
|
* Clear the active account
|
|
745
1097
|
*/
|
|
@@ -814,30 +1166,16 @@ export class DAppClient extends Client {
|
|
|
814
1166
|
})
|
|
815
1167
|
};
|
|
816
1168
|
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
sendMetrics(uri, options, thenHandler, catchHandler) {
|
|
826
|
-
if (!this.enableMetrics && uri === 'performance-metrics/save') {
|
|
827
|
-
options && this.updateMetricsStorage(options.body);
|
|
828
|
-
}
|
|
829
|
-
if (!this.enableMetrics) {
|
|
830
|
-
return;
|
|
831
|
-
}
|
|
832
|
-
fetch(`${BACKEND_URL}/${uri}`, options)
|
|
833
|
-
.then((res) => thenHandler && thenHandler(res))
|
|
834
|
-
.catch((err) => {
|
|
835
|
-
console.warn('Network error encountered. Metrics sharing have been automatically disabled.');
|
|
836
|
-
logger.error(err.message);
|
|
837
|
-
this.enableMetrics = false; // in the event of a network error, stop sending metrics
|
|
838
|
-
catchHandler && catchHandler(err);
|
|
839
|
-
});
|
|
1169
|
+
// Metrics phone home to a Papers-controlled endpoint that the ECAD fork
|
|
1170
|
+
// does not control or ingest. The disabled path also queued payloads into
|
|
1171
|
+
// IndexedDB before the DB was ready, surfacing as unhandledRejection.
|
|
1172
|
+
// Hard-disabled here; full removal (option, storage key, IDB store,
|
|
1173
|
+
// BACKEND_URL constant, all call sites) tracked as a follow-up.
|
|
1174
|
+
/* eslint-disable @typescript-eslint/no-unused-vars -- hard-disabled stub keeps its signature for call-site type-safety; params are intentionally unused pending full removal. */
|
|
1175
|
+
sendMetrics(_uri, _options, _thenHandler, _catchHandler) {
|
|
1176
|
+
return;
|
|
840
1177
|
}
|
|
1178
|
+
/* eslint-enable @typescript-eslint/no-unused-vars */
|
|
841
1179
|
async checkMakeRequest() {
|
|
842
1180
|
const isResolved = this._transport.isResolved();
|
|
843
1181
|
const isWCInstance = isResolved && (await this.transport) instanceof WalletConnectTransport;
|
|
@@ -882,14 +1220,19 @@ export class DAppClient extends Client {
|
|
|
882
1220
|
/**
|
|
883
1221
|
* Remove all peers and all accounts that have been connected through those peers
|
|
884
1222
|
*/
|
|
885
|
-
async removeAllPeers(sendDisconnectToPeers = false) {
|
|
886
|
-
const transport = await this.transport;
|
|
1223
|
+
async removeAllPeers(sendDisconnectToPeers = false, options = {}) {
|
|
1224
|
+
const transport = (await this.transport);
|
|
887
1225
|
const peers = await transport.getPeers();
|
|
888
|
-
const removePeerResult = transport.removeAllPeers();
|
|
1226
|
+
const removePeerResult = await transport.removeAllPeers();
|
|
889
1227
|
await this.removeAccountsForPeers(peers);
|
|
890
1228
|
if (sendDisconnectToPeers) {
|
|
891
1229
|
const disconnectPromises = peers.map((peer) => this.sendDisconnectToPeer(peer, transport));
|
|
892
|
-
|
|
1230
|
+
if (options.ignoreDisconnectErrors) {
|
|
1231
|
+
await Promise.allSettled(disconnectPromises);
|
|
1232
|
+
}
|
|
1233
|
+
else {
|
|
1234
|
+
await Promise.all(disconnectPromises);
|
|
1235
|
+
}
|
|
893
1236
|
}
|
|
894
1237
|
return removePeerResult;
|
|
895
1238
|
}
|
|
@@ -994,13 +1337,15 @@ export class DAppClient extends Client {
|
|
|
994
1337
|
this.sendMetrics('performance-metrics/save', await this.buildPayload('connect', 'start'));
|
|
995
1338
|
const logId = `makeRequestV3 ${Date.now()}`;
|
|
996
1339
|
logger.time(true, logId);
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1340
|
+
let resolved;
|
|
1341
|
+
try {
|
|
1342
|
+
resolved = await this.makeRequestV3(request);
|
|
1343
|
+
}
|
|
1344
|
+
catch (requestError) {
|
|
1345
|
+
await this.runRequestErrorSideEffects(request, requestError, logId);
|
|
1346
|
+
throw requestError;
|
|
1347
|
+
}
|
|
1348
|
+
const { message: response, connectionInfo } = resolved;
|
|
1004
1349
|
logger.time(false, logId);
|
|
1005
1350
|
this.sendMetrics('performance-metrics/save', await this.buildPayload('connect', 'start'));
|
|
1006
1351
|
logger.log('RESPONSE V3', response, connectionInfo);
|
|
@@ -1064,14 +1409,18 @@ export class DAppClient extends Client {
|
|
|
1064
1409
|
const res = (await this.checkMakeRequest())
|
|
1065
1410
|
? this.makeRequestV3(request)
|
|
1066
1411
|
: this.makeRequestBC(request);
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1412
|
+
let resolved;
|
|
1413
|
+
try {
|
|
1414
|
+
resolved = await res;
|
|
1415
|
+
}
|
|
1416
|
+
catch (requestError) {
|
|
1417
|
+
await this.runRequestErrorSideEffects(request, requestError, logId);
|
|
1418
|
+
throw requestError;
|
|
1419
|
+
}
|
|
1420
|
+
if (!resolved) {
|
|
1421
|
+
throw new Error('Internal error: makeRequest returned no result');
|
|
1422
|
+
}
|
|
1423
|
+
const { message: response, connectionInfo } = resolved;
|
|
1075
1424
|
logger.time(false, logId);
|
|
1076
1425
|
this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'success'));
|
|
1077
1426
|
await blockchain.handleResponse({
|
|
@@ -1095,16 +1444,34 @@ export class DAppClient extends Client {
|
|
|
1095
1444
|
* @param input The message details we need to prepare the PermissionRequest message.
|
|
1096
1445
|
*/
|
|
1097
1446
|
async requestPermissions(input) {
|
|
1098
|
-
|
|
1099
|
-
|
|
1447
|
+
this.assertNotDestroyed();
|
|
1448
|
+
const scopes = this.getPermissionRequestScopes(input);
|
|
1449
|
+
const requestPermissionsKey = this.getPermissionRequestKey(scopes);
|
|
1450
|
+
if (this._requestPermissionsPromise) {
|
|
1451
|
+
if (this._requestPermissionsKey !== requestPermissionsKey) {
|
|
1452
|
+
throw new Error('Cannot start a permission request with different scopes while another permission request is pending');
|
|
1453
|
+
}
|
|
1454
|
+
return this._requestPermissionsPromise;
|
|
1100
1455
|
}
|
|
1456
|
+
const requestPermissionsPromise = this.requestPermissionsInternal(scopes);
|
|
1457
|
+
this._requestPermissionsPromise = requestPermissionsPromise;
|
|
1458
|
+
this._requestPermissionsKey = requestPermissionsKey;
|
|
1459
|
+
try {
|
|
1460
|
+
return await requestPermissionsPromise;
|
|
1461
|
+
}
|
|
1462
|
+
finally {
|
|
1463
|
+
if (this._requestPermissionsPromise === requestPermissionsPromise) {
|
|
1464
|
+
this._requestPermissionsPromise = undefined;
|
|
1465
|
+
this._requestPermissionsKey = undefined;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
async requestPermissionsInternal(scopes) {
|
|
1101
1470
|
const request = {
|
|
1102
1471
|
appMetadata: await this.getOwnAppMetadata(),
|
|
1103
1472
|
type: BeaconMessageType.PermissionRequest,
|
|
1104
1473
|
network: this.network,
|
|
1105
|
-
scopes
|
|
1106
|
-
? input.scopes
|
|
1107
|
-
: [PermissionScope.OPERATION_REQUEST, PermissionScope.SIGN]
|
|
1474
|
+
scopes
|
|
1108
1475
|
};
|
|
1109
1476
|
this.analytics.track('event', 'DAppClient', 'Permission requested');
|
|
1110
1477
|
this.sendMetrics('performance-metrics/save', await this.buildPayload('connect', 'start'));
|
|
@@ -1113,14 +1480,18 @@ export class DAppClient extends Client {
|
|
|
1113
1480
|
const res = (await this.checkMakeRequest()) || !(await this.getActiveAccount())
|
|
1114
1481
|
? this.makeRequest(request, undefined, undefined)
|
|
1115
1482
|
: this.makeRequestBC(request);
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1483
|
+
let resolved;
|
|
1484
|
+
try {
|
|
1485
|
+
resolved = await res;
|
|
1486
|
+
}
|
|
1487
|
+
catch (requestError) {
|
|
1488
|
+
await this.runRequestErrorSideEffects(request, requestError, logId);
|
|
1489
|
+
throw requestError;
|
|
1490
|
+
}
|
|
1491
|
+
if (!resolved) {
|
|
1492
|
+
throw new Error('Internal error: makeRequest returned no result');
|
|
1493
|
+
}
|
|
1494
|
+
const { message, connectionInfo } = resolved;
|
|
1124
1495
|
logger.time(false, logId);
|
|
1125
1496
|
this.sendMetrics('performance-metrics/save', await this.buildPayload('connect', 'success'));
|
|
1126
1497
|
logger.log('requestPermissions', '######## MESSAGE #######');
|
|
@@ -1147,6 +1518,17 @@ export class DAppClient extends Client {
|
|
|
1147
1518
|
});
|
|
1148
1519
|
return output;
|
|
1149
1520
|
}
|
|
1521
|
+
getPermissionRequestScopes(input) {
|
|
1522
|
+
if (input && 'network' in input) {
|
|
1523
|
+
throw new Error('[BEACON] the "network" property is no longer accepted in input. Please provide it when instantiating DAppClient.');
|
|
1524
|
+
}
|
|
1525
|
+
return input && input.scopes
|
|
1526
|
+
? input.scopes
|
|
1527
|
+
: [PermissionScope.OPERATION_REQUEST, PermissionScope.SIGN];
|
|
1528
|
+
}
|
|
1529
|
+
getPermissionRequestKey(scopes) {
|
|
1530
|
+
return [...scopes].sort().join('|');
|
|
1531
|
+
}
|
|
1150
1532
|
/**
|
|
1151
1533
|
* Send a proof of event request to the wallet. The wallet will either accept or decline the challenge.
|
|
1152
1534
|
* If it is accepted, the challenge will be stored, meaning that even if the user refresh the page, the DAppClient will keep checking if the challenge has been fulfilled.
|
|
@@ -1173,14 +1555,18 @@ export class DAppClient extends Client {
|
|
|
1173
1555
|
const res = (await this.checkMakeRequest())
|
|
1174
1556
|
? this.makeRequest(request)
|
|
1175
1557
|
: this.makeRequestBC(request);
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1558
|
+
let resolved;
|
|
1559
|
+
try {
|
|
1560
|
+
resolved = await res;
|
|
1561
|
+
}
|
|
1562
|
+
catch (requestError) {
|
|
1563
|
+
await this.runRequestErrorSideEffects(request, requestError, logId);
|
|
1564
|
+
throw requestError;
|
|
1565
|
+
}
|
|
1566
|
+
if (!resolved) {
|
|
1567
|
+
throw new Error('Internal error: makeRequest returned no result');
|
|
1568
|
+
}
|
|
1569
|
+
const { message, connectionInfo } = resolved;
|
|
1184
1570
|
logger.time(false, logId);
|
|
1185
1571
|
this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'success'));
|
|
1186
1572
|
this.analytics.track('event', 'DAppClient', `Proof of event challenge ${message.isAccepted ? 'accepted' : 'refused'}`, { address: activeAccount.address });
|
|
@@ -1219,14 +1605,18 @@ export class DAppClient extends Client {
|
|
|
1219
1605
|
const res = (await this.checkMakeRequest())
|
|
1220
1606
|
? this.makeRequest(request)
|
|
1221
1607
|
: this.makeRequestBC(request);
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1608
|
+
let resolved;
|
|
1609
|
+
try {
|
|
1610
|
+
resolved = await res;
|
|
1611
|
+
}
|
|
1612
|
+
catch (requestError) {
|
|
1613
|
+
await this.runRequestErrorSideEffects(request, requestError, logId);
|
|
1614
|
+
throw requestError;
|
|
1615
|
+
}
|
|
1616
|
+
if (!resolved) {
|
|
1617
|
+
throw new Error('Internal error: makeRequest returned no result');
|
|
1618
|
+
}
|
|
1619
|
+
const { message, connectionInfo } = resolved;
|
|
1230
1620
|
logger.time(false, logId);
|
|
1231
1621
|
this.analytics.track('event', 'DAppClient', `Simulated proof of event challenge ${!message.errorMessage ? 'accepted' : 'refused'}`, { address: activeAccount.address });
|
|
1232
1622
|
await this.notifySuccess(request, {
|
|
@@ -1286,14 +1676,18 @@ export class DAppClient extends Client {
|
|
|
1286
1676
|
const res = (await this.checkMakeRequest())
|
|
1287
1677
|
? this.makeRequest(request)
|
|
1288
1678
|
: this.makeRequestBC(request);
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1679
|
+
let resolved;
|
|
1680
|
+
try {
|
|
1681
|
+
resolved = await res;
|
|
1682
|
+
}
|
|
1683
|
+
catch (requestError) {
|
|
1684
|
+
await this.runRequestErrorSideEffects(request, requestError, logId);
|
|
1685
|
+
throw requestError;
|
|
1686
|
+
}
|
|
1687
|
+
if (!resolved) {
|
|
1688
|
+
throw new Error('Internal error: makeRequest returned no result');
|
|
1689
|
+
}
|
|
1690
|
+
const { message, connectionInfo } = resolved;
|
|
1297
1691
|
logger.time(false, logId);
|
|
1298
1692
|
this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'success'));
|
|
1299
1693
|
await this.notifySuccess(request, {
|
|
@@ -1381,14 +1775,18 @@ export class DAppClient extends Client {
|
|
|
1381
1775
|
const res = (await this.checkMakeRequest())
|
|
1382
1776
|
? this.makeRequest(request)
|
|
1383
1777
|
: this.makeRequestBC(request);
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1778
|
+
let resolved;
|
|
1779
|
+
try {
|
|
1780
|
+
resolved = await res;
|
|
1781
|
+
}
|
|
1782
|
+
catch (requestError) {
|
|
1783
|
+
await this.runRequestErrorSideEffects(request, requestError, logId);
|
|
1784
|
+
throw requestError;
|
|
1785
|
+
}
|
|
1786
|
+
if (!resolved) {
|
|
1787
|
+
throw new Error('Internal error: makeRequest returned no result');
|
|
1788
|
+
}
|
|
1789
|
+
const { message, connectionInfo } = resolved;
|
|
1392
1790
|
logger.time(false, logId);
|
|
1393
1791
|
this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'success'));
|
|
1394
1792
|
await this.notifySuccess(request, {
|
|
@@ -1428,14 +1826,18 @@ export class DAppClient extends Client {
|
|
|
1428
1826
|
const res = (await this.checkMakeRequest())
|
|
1429
1827
|
? this.makeRequest(request)
|
|
1430
1828
|
: this.makeRequestBC(request);
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1829
|
+
let resolved;
|
|
1830
|
+
try {
|
|
1831
|
+
resolved = await res;
|
|
1832
|
+
}
|
|
1833
|
+
catch (requestError) {
|
|
1834
|
+
await this.runRequestErrorSideEffects(request, requestError, logId);
|
|
1835
|
+
throw requestError;
|
|
1836
|
+
}
|
|
1837
|
+
if (!resolved) {
|
|
1838
|
+
throw new Error('Internal error: makeRequest returned no result');
|
|
1839
|
+
}
|
|
1840
|
+
const { message, connectionInfo } = resolved;
|
|
1439
1841
|
logger.time(false, logId);
|
|
1440
1842
|
this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'success'));
|
|
1441
1843
|
await this.notifySuccess(request, {
|
|
@@ -1501,11 +1903,14 @@ export class DAppClient extends Client {
|
|
|
1501
1903
|
* @param peersToRemove An array of peers for which accounts should be removed
|
|
1502
1904
|
*/
|
|
1503
1905
|
async removeAccountsForPeers(peersToRemove) {
|
|
1906
|
+
if (peersToRemove.length === 0) {
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1504
1909
|
const peerIdsToRemove = peersToRemove.map((peer) => peer.senderId);
|
|
1505
1910
|
return this.removeAccountsForPeerIds(peerIdsToRemove);
|
|
1506
1911
|
}
|
|
1507
1912
|
async removeAccountsForPeerIds(peerIds) {
|
|
1508
|
-
const accounts = await this.accountManager.getAccounts();
|
|
1913
|
+
const accounts = (await this.accountManager.getAccounts()) ?? [];
|
|
1509
1914
|
// Remove all accounts with origin of the specified peer
|
|
1510
1915
|
const accountsToRemove = accounts.filter((account) => peerIds.includes(account.senderId));
|
|
1511
1916
|
const accountIdentifiersToRemove = accountsToRemove.map((accountInfo) => accountInfo.accountIdentifier);
|
|
@@ -1518,6 +1923,44 @@ export class DAppClient extends Client {
|
|
|
1518
1923
|
}
|
|
1519
1924
|
}
|
|
1520
1925
|
}
|
|
1926
|
+
/**
|
|
1927
|
+
* Run the standard request-error side effects (abort/error metric,
|
|
1928
|
+
* timer stop, {@link handleRequestError}) on a single awaited control
|
|
1929
|
+
* flow so callers can re-throw the original {@link ErrorResponse} without
|
|
1930
|
+
* leaking an UnhandledPromiseRejection.
|
|
1931
|
+
*
|
|
1932
|
+
* Why this exists: every request method historically used a fire-and-
|
|
1933
|
+
* forget `res.catch(handler)` whose handler did `throw await
|
|
1934
|
+
* this.handleRequestError(...)`. The catch returned a detached promise
|
|
1935
|
+
* nobody awaited; `handleRequestError`'s thrown wrapped error became an
|
|
1936
|
+
* unhandled rejection. The bug rarely surfaced before the dapp-init
|
|
1937
|
+
* promise was made rejectable, because pre-pairing rejection paths used
|
|
1938
|
+
* to hang instead of routing through these handlers. Once init started
|
|
1939
|
+
* rejecting reliably, the unhandled rejection started firing on every
|
|
1940
|
+
* WC session-proposal rejection. We swallow the wrapped throw here so
|
|
1941
|
+
* callers can re-throw the original `ErrorResponse`, matching the
|
|
1942
|
+
* post-pairing rejection contract at the openRequest path.
|
|
1943
|
+
*
|
|
1944
|
+
* @param request The request we sent
|
|
1945
|
+
* @param requestError The error we received
|
|
1946
|
+
* @param logId The {@link logger.time} label opened for this request
|
|
1947
|
+
*/
|
|
1948
|
+
async runRequestErrorSideEffects(request, rawError, logId) {
|
|
1949
|
+
// V3 request shapes (PermissionRequestV3, BlockchainRequestV3) flow through
|
|
1950
|
+
// here too. They aren't part of BeaconRequestInputMessage, but
|
|
1951
|
+
// handleRequestError only inspects `.type` plus optional fields it already
|
|
1952
|
+
// guards on. Catch params are `unknown` under strict TS, same story.
|
|
1953
|
+
const typedRequest = request;
|
|
1954
|
+
const requestError = rawError;
|
|
1955
|
+
if (requestError.errorType === BeaconErrorType.ABORTED_ERROR) {
|
|
1956
|
+
this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'abort'));
|
|
1957
|
+
}
|
|
1958
|
+
else {
|
|
1959
|
+
this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'error'));
|
|
1960
|
+
}
|
|
1961
|
+
logger.time(false, logId);
|
|
1962
|
+
await this.handleRequestError(typedRequest, requestError).catch(() => undefined);
|
|
1963
|
+
}
|
|
1521
1964
|
/**
|
|
1522
1965
|
* This message handles errors that we receive from the wallet.
|
|
1523
1966
|
*
|
|
@@ -1685,18 +2128,8 @@ export class DAppClient extends Client {
|
|
|
1685
2128
|
}
|
|
1686
2129
|
async makeRequest(requestInput, skipResponse, otherTabMessageId) {
|
|
1687
2130
|
const messageId = otherTabMessageId ?? (await generateGUID());
|
|
1688
|
-
if (this._initPromise && this.isInitPending) {
|
|
1689
|
-
await Promise.all([
|
|
1690
|
-
this.postMessageTransport?.disconnect(),
|
|
1691
|
-
this.walletConnectTransport?.disconnect()
|
|
1692
|
-
]);
|
|
1693
|
-
this._initPromise = undefined;
|
|
1694
|
-
this.hideUI(['toast']);
|
|
1695
|
-
}
|
|
1696
2131
|
logger.log('makeRequest', 'starting');
|
|
1697
|
-
this.isInitPending = true;
|
|
1698
2132
|
await this.init();
|
|
1699
|
-
this.isInitPending = false;
|
|
1700
2133
|
logger.log('makeRequest', 'after init');
|
|
1701
2134
|
if (await this.addRequestAndCheckIfRateLimited()) {
|
|
1702
2135
|
this.events
|
|
@@ -1722,20 +2155,37 @@ export class DAppClient extends Client {
|
|
|
1722
2155
|
exposed = new ExposedPromise();
|
|
1723
2156
|
this.addOpenRequest(request.id, exposed);
|
|
1724
2157
|
}
|
|
1725
|
-
const payload = await new Serializer().serialize(request);
|
|
1726
|
-
const account = await this.getActiveAccount();
|
|
1727
|
-
const peer = await this.getPeer(account);
|
|
1728
|
-
const walletInfo = await this.getWalletInfo(peer, account);
|
|
1729
|
-
logger.log('makeRequest', 'sending message', request);
|
|
1730
2158
|
try {
|
|
1731
|
-
;
|
|
1732
|
-
|
|
2159
|
+
const payload = await new Serializer().serialize(request);
|
|
2160
|
+
const account = await this.getActiveAccount();
|
|
2161
|
+
const peer = await this.getPeer(account);
|
|
2162
|
+
const walletInfo = await this.getWalletInfo(peer, account);
|
|
2163
|
+
logger.log('makeRequest', 'sending message', request);
|
|
2164
|
+
await (await this.transport).send(payload, peer);
|
|
1733
2165
|
if (request.type !== BeaconMessageType.PermissionRequest ||
|
|
1734
2166
|
(this._activeAccount.isResolved() && (await this._activeAccount.promise))) {
|
|
1735
2167
|
this.tryToAppSwitch();
|
|
1736
2168
|
}
|
|
2169
|
+
if (!otherTabMessageId) {
|
|
2170
|
+
this.events
|
|
2171
|
+
.emit(messageEvents[requestInput.type].sent, {
|
|
2172
|
+
walletInfo: {
|
|
2173
|
+
...walletInfo,
|
|
2174
|
+
name: walletInfo.name ?? 'Wallet'
|
|
2175
|
+
},
|
|
2176
|
+
extraInfo: {
|
|
2177
|
+
resetCallback: async () => {
|
|
2178
|
+
await this.disconnect();
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
})
|
|
2182
|
+
.catch((emitError) => logger.warn('makeRequest', emitError));
|
|
2183
|
+
}
|
|
1737
2184
|
}
|
|
1738
2185
|
catch (sendError) {
|
|
2186
|
+
if (!skipResponse) {
|
|
2187
|
+
this.clearOpenRequest(request.id);
|
|
2188
|
+
}
|
|
1739
2189
|
this.events.emit(BeaconEvent.INTERNAL_ERROR, {
|
|
1740
2190
|
text: 'Unable to send message. If this problem persists, please reset the connection and pair your wallet again.',
|
|
1741
2191
|
buttons: [
|
|
@@ -1750,21 +2200,6 @@ export class DAppClient extends Client {
|
|
|
1750
2200
|
});
|
|
1751
2201
|
throw sendError;
|
|
1752
2202
|
}
|
|
1753
|
-
if (!otherTabMessageId) {
|
|
1754
|
-
this.events
|
|
1755
|
-
.emit(messageEvents[requestInput.type].sent, {
|
|
1756
|
-
walletInfo: {
|
|
1757
|
-
...walletInfo,
|
|
1758
|
-
name: walletInfo.name ?? 'Wallet'
|
|
1759
|
-
},
|
|
1760
|
-
extraInfo: {
|
|
1761
|
-
resetCallback: async () => {
|
|
1762
|
-
this.disconnect();
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
})
|
|
1766
|
-
.catch((emitError) => console.warn(emitError));
|
|
1767
|
-
}
|
|
1768
2203
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1769
2204
|
return exposed?.promise; // TODO: fix type
|
|
1770
2205
|
}
|
|
@@ -1777,19 +2212,9 @@ export class DAppClient extends Client {
|
|
|
1777
2212
|
* @param account The account that the message will be sent to
|
|
1778
2213
|
*/
|
|
1779
2214
|
async makeRequestV3(requestInput, otherTabMessageId) {
|
|
1780
|
-
if (this._initPromise && this.isInitPending) {
|
|
1781
|
-
await Promise.all([
|
|
1782
|
-
this.postMessageTransport?.disconnect(),
|
|
1783
|
-
this.walletConnectTransport?.disconnect()
|
|
1784
|
-
]);
|
|
1785
|
-
this._initPromise = undefined;
|
|
1786
|
-
this.hideUI(['toast']);
|
|
1787
|
-
}
|
|
1788
2215
|
const messageId = otherTabMessageId ?? (await generateGUID());
|
|
1789
2216
|
logger.log('makeRequest', 'starting');
|
|
1790
|
-
this.isInitPending = true;
|
|
1791
2217
|
await this.init(undefined, true);
|
|
1792
|
-
this.isInitPending = false;
|
|
1793
2218
|
logger.log('makeRequest', 'after init');
|
|
1794
2219
|
if (await this.addRequestAndCheckIfRateLimited()) {
|
|
1795
2220
|
this.events
|
|
@@ -1808,20 +2233,34 @@ export class DAppClient extends Client {
|
|
|
1808
2233
|
};
|
|
1809
2234
|
const exposed = new ExposedPromise();
|
|
1810
2235
|
this.addOpenRequest(request.id, exposed);
|
|
1811
|
-
const payload = await new Serializer().serialize(request);
|
|
1812
|
-
const account = await this.getActiveAccount();
|
|
1813
|
-
const peer = await this.getPeer(account);
|
|
1814
|
-
const walletInfo = await this.getWalletInfo(peer, account);
|
|
1815
|
-
logger.log('makeRequest', 'sending message', request);
|
|
1816
2236
|
try {
|
|
1817
|
-
;
|
|
1818
|
-
|
|
2237
|
+
const payload = await new Serializer().serialize(request);
|
|
2238
|
+
const account = await this.getActiveAccount();
|
|
2239
|
+
const peer = await this.getPeer(account);
|
|
2240
|
+
const walletInfo = await this.getWalletInfo(peer, account);
|
|
2241
|
+
logger.log('makeRequest', 'sending message', request);
|
|
2242
|
+
await (await this.transport).send(payload, peer);
|
|
1819
2243
|
if (request.message.type !== BeaconMessageType.PermissionRequest ||
|
|
1820
2244
|
(this._activeAccount.isResolved() && (await this._activeAccount.promise))) {
|
|
1821
2245
|
this.tryToAppSwitch();
|
|
1822
2246
|
}
|
|
2247
|
+
const index = requestInput.type;
|
|
2248
|
+
this.events
|
|
2249
|
+
.emit(messageEvents[index].sent, {
|
|
2250
|
+
walletInfo: {
|
|
2251
|
+
...walletInfo,
|
|
2252
|
+
name: walletInfo.name ?? 'Wallet'
|
|
2253
|
+
},
|
|
2254
|
+
extraInfo: {
|
|
2255
|
+
resetCallback: async () => {
|
|
2256
|
+
await this.disconnect();
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
})
|
|
2260
|
+
.catch((emitError) => logger.warn('makeRequestV3', emitError));
|
|
1823
2261
|
}
|
|
1824
2262
|
catch (sendError) {
|
|
2263
|
+
this.clearOpenRequest(request.id);
|
|
1825
2264
|
this.events.emit(BeaconEvent.INTERNAL_ERROR, {
|
|
1826
2265
|
text: 'Unable to send message. If this problem persists, please reset the connection and pair your wallet again.',
|
|
1827
2266
|
buttons: [
|
|
@@ -1836,20 +2275,6 @@ export class DAppClient extends Client {
|
|
|
1836
2275
|
});
|
|
1837
2276
|
throw sendError;
|
|
1838
2277
|
}
|
|
1839
|
-
const index = requestInput.type;
|
|
1840
|
-
this.events
|
|
1841
|
-
.emit(messageEvents[index].sent, {
|
|
1842
|
-
walletInfo: {
|
|
1843
|
-
...walletInfo,
|
|
1844
|
-
name: walletInfo.name ?? 'Wallet'
|
|
1845
|
-
},
|
|
1846
|
-
extraInfo: {
|
|
1847
|
-
resetCallback: async () => {
|
|
1848
|
-
this.disconnect();
|
|
1849
|
-
}
|
|
1850
|
-
}
|
|
1851
|
-
})
|
|
1852
|
-
.catch((emitError) => console.warn(emitError));
|
|
1853
2278
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1854
2279
|
return exposed.promise; // TODO: fix type
|
|
1855
2280
|
}
|
|
@@ -1889,24 +2314,140 @@ export class DAppClient extends Client {
|
|
|
1889
2314
|
this.addOpenRequest(id, exposed);
|
|
1890
2315
|
return exposed.promise;
|
|
1891
2316
|
}
|
|
1892
|
-
|
|
2317
|
+
/**
|
|
2318
|
+
* Disconnects all resolved transports (postMessage, P2P, WalletConnect), removes their peers,
|
|
2319
|
+
* and clears active account state. After calling, the DAppClient remains usable for a new
|
|
2320
|
+
* requestPermissions().
|
|
2321
|
+
*/
|
|
2322
|
+
async disconnect(options = {}) {
|
|
2323
|
+
const { notifyPeers = true } = options;
|
|
2324
|
+
if (!this._disconnectPromise) {
|
|
2325
|
+
this._disconnectNotifyPeers = false;
|
|
2326
|
+
}
|
|
2327
|
+
this._disconnectNotifyPeers = this._disconnectNotifyPeers || notifyPeers;
|
|
2328
|
+
if (this._disconnectPromise) {
|
|
2329
|
+
return this._disconnectPromise;
|
|
2330
|
+
}
|
|
2331
|
+
this._disconnectPromise = (async () => this.disconnectInternal())().finally(() => {
|
|
2332
|
+
this._disconnectPromise = undefined;
|
|
2333
|
+
this._disconnectNotifyPeers = false;
|
|
2334
|
+
});
|
|
2335
|
+
return this._disconnectPromise;
|
|
2336
|
+
}
|
|
2337
|
+
async disconnectInternal() {
|
|
2338
|
+
this.abortPendingInit();
|
|
1893
2339
|
if (!this._transport.isResolved()) {
|
|
1894
|
-
|
|
2340
|
+
const initializedTransports = this.getInitializedTransports();
|
|
2341
|
+
this.abortOpenRequests();
|
|
2342
|
+
await this.removeAllPeersFromTransports(initializedTransports);
|
|
2343
|
+
await this.clearActiveAccount();
|
|
2344
|
+
await this.disconnectResolvedTransports(initializedTransports);
|
|
2345
|
+
this.clearInternalTransportReferences();
|
|
2346
|
+
return;
|
|
1895
2347
|
}
|
|
1896
|
-
const transport = await this.transport;
|
|
2348
|
+
const transport = (await this.transport);
|
|
1897
2349
|
if (transport.connectionStatus === TransportStatus.NOT_CONNECTED) {
|
|
1898
|
-
|
|
2350
|
+
await this.clearActiveAccount();
|
|
2351
|
+
this.abortOpenRequests();
|
|
2352
|
+
return;
|
|
1899
2353
|
}
|
|
1900
2354
|
await this.createStateSnapshot();
|
|
1901
2355
|
this.sendMetrics('performance-metrics/save', await this.buildPayload('disconnect', 'start'));
|
|
2356
|
+
this.abortOpenRequests();
|
|
2357
|
+
const transports = this.getResolvedTransports(transport);
|
|
2358
|
+
await this.removeAllPeersFromTransports(transports);
|
|
1902
2359
|
await this.clearActiveAccount();
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
2360
|
+
await this.disconnectResolvedTransports(transports);
|
|
2361
|
+
this.clearInternalTransportReferences();
|
|
2362
|
+
this.sendMetrics('performance-metrics/save', await this.buildPayload('disconnect', 'success'));
|
|
2363
|
+
}
|
|
2364
|
+
getInitializedTransports() {
|
|
2365
|
+
const transports = [];
|
|
2366
|
+
const addTransport = (transport) => {
|
|
2367
|
+
if (transport && !transports.includes(transport)) {
|
|
2368
|
+
transports.push(transport);
|
|
2369
|
+
}
|
|
2370
|
+
};
|
|
2371
|
+
addTransport(this.postMessageTransport);
|
|
2372
|
+
addTransport(this.p2pTransport);
|
|
2373
|
+
addTransport(this.walletConnectTransport);
|
|
2374
|
+
return transports;
|
|
2375
|
+
}
|
|
2376
|
+
getResolvedTransports(selectedTransport) {
|
|
2377
|
+
const transports = this.getInitializedTransports();
|
|
2378
|
+
const addTransport = (transport) => {
|
|
2379
|
+
if (transport && !transports.includes(transport)) {
|
|
2380
|
+
transports.push(transport);
|
|
2381
|
+
}
|
|
2382
|
+
};
|
|
2383
|
+
addTransport(this.postMessageTransport);
|
|
2384
|
+
addTransport(this.p2pTransport);
|
|
2385
|
+
addTransport(this.walletConnectTransport);
|
|
2386
|
+
addTransport(selectedTransport);
|
|
2387
|
+
return transports;
|
|
2388
|
+
}
|
|
2389
|
+
clearInternalTransportReferences() {
|
|
1906
2390
|
this.postMessageTransport = undefined;
|
|
1907
2391
|
this.p2pTransport = undefined;
|
|
1908
2392
|
this.walletConnectTransport = undefined;
|
|
1909
|
-
|
|
2393
|
+
}
|
|
2394
|
+
async removeAllPeersFromTransports(transports) {
|
|
2395
|
+
const peersByTransport = await Promise.all(transports.map(async (transport) => {
|
|
2396
|
+
const transportPeers = await transport.getPeers();
|
|
2397
|
+
return {
|
|
2398
|
+
transport,
|
|
2399
|
+
peers: transportPeers
|
|
2400
|
+
};
|
|
2401
|
+
}));
|
|
2402
|
+
await Promise.all(peersByTransport.map(({ transport }) => transport.removeAllPeers()));
|
|
2403
|
+
const peersToRemove = peersByTransport.flatMap(({ peers: transportPeers }) => transportPeers);
|
|
2404
|
+
await this.removeAccountsForPeers(peersToRemove);
|
|
2405
|
+
// If cleanup already removed peers, a late notify upgrade has nothing left to notify.
|
|
2406
|
+
const sendDisconnectToPeers = this._disconnectNotifyPeers;
|
|
2407
|
+
if (!sendDisconnectToPeers) {
|
|
2408
|
+
return;
|
|
2409
|
+
}
|
|
2410
|
+
await Promise.allSettled(this.getUniquePeerNotifications(peersByTransport).map(({ transport, peer }) => this.sendDisconnectToPeer(peer, transport)));
|
|
2411
|
+
}
|
|
2412
|
+
getUniquePeerNotifications(peersByTransport) {
|
|
2413
|
+
const notifications = [];
|
|
2414
|
+
const seen = new Set();
|
|
2415
|
+
peersByTransport.forEach(({ transport, peers: transportPeers }) => {
|
|
2416
|
+
transportPeers.forEach((peer) => {
|
|
2417
|
+
const key = this.getPeerNotificationKey(transport, peer);
|
|
2418
|
+
if (seen.has(key)) {
|
|
2419
|
+
return;
|
|
2420
|
+
}
|
|
2421
|
+
seen.add(key);
|
|
2422
|
+
notifications.push({ transport, peer });
|
|
2423
|
+
});
|
|
2424
|
+
});
|
|
2425
|
+
return notifications;
|
|
2426
|
+
}
|
|
2427
|
+
getPeerNotificationKey(transport, peer) {
|
|
2428
|
+
return [transport.type, peer.publicKey, peer.senderId, peer.id].filter(Boolean).join(':');
|
|
2429
|
+
}
|
|
2430
|
+
async disconnectResolvedTransports(transports) {
|
|
2431
|
+
const results = await Promise.allSettled(transports.map(async (transport) => {
|
|
2432
|
+
if (this.isTransportConnected(transport)) {
|
|
2433
|
+
await transport.disconnect();
|
|
2434
|
+
}
|
|
2435
|
+
if (this.hasClientCleanup(transport)) {
|
|
2436
|
+
await transport.doClientCleanup();
|
|
2437
|
+
}
|
|
2438
|
+
}));
|
|
2439
|
+
results.forEach((result) => {
|
|
2440
|
+
if (result.status === 'rejected') {
|
|
2441
|
+
logger.warn('disconnectResolvedTransports', result.reason);
|
|
2442
|
+
}
|
|
2443
|
+
});
|
|
2444
|
+
}
|
|
2445
|
+
hasClientCleanup(transport) {
|
|
2446
|
+
const transportWithClientCleanup = transport;
|
|
2447
|
+
return typeof transportWithClientCleanup.doClientCleanup === 'function';
|
|
2448
|
+
}
|
|
2449
|
+
isTransportConnected(transport) {
|
|
2450
|
+
return transport.connectionStatus !== TransportStatus.NOT_CONNECTED;
|
|
1910
2451
|
}
|
|
1911
2452
|
/**
|
|
1912
2453
|
* Adds a requests to the "openRequests" set so we know what messages have already been answered/handled.
|
|
@@ -1916,7 +2457,64 @@ export class DAppClient extends Client {
|
|
|
1916
2457
|
*/
|
|
1917
2458
|
addOpenRequest(id, promise) {
|
|
1918
2459
|
logger.log('addOpenRequest', this.name, `adding request ${id} and waiting for answer`);
|
|
2460
|
+
this.clearOpenRequestTimeout(id);
|
|
2461
|
+
this.openRequests.delete(id);
|
|
1919
2462
|
this.openRequests.set(id, promise);
|
|
2463
|
+
this.addOpenRequestTimeout(id, promise);
|
|
2464
|
+
}
|
|
2465
|
+
addOpenRequestTimeout(id, promise) {
|
|
2466
|
+
if (id === SESSION_UPDATE_REQUEST_ID) {
|
|
2467
|
+
return;
|
|
2468
|
+
}
|
|
2469
|
+
if (!Number.isFinite(this.requestTimeoutMs) || this.requestTimeoutMs <= 0) {
|
|
2470
|
+
return;
|
|
2471
|
+
}
|
|
2472
|
+
const timeout = setTimeout(() => {
|
|
2473
|
+
if (this.openRequests.get(id) !== promise) {
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
const errorResponse = {
|
|
2477
|
+
id,
|
|
2478
|
+
type: BeaconMessageType.Error,
|
|
2479
|
+
errorType: BeaconErrorType.PEER_UNREACHABLE,
|
|
2480
|
+
senderId: '',
|
|
2481
|
+
version: '2'
|
|
2482
|
+
};
|
|
2483
|
+
promise.reject(errorResponse);
|
|
2484
|
+
this.clearOpenRequest(id);
|
|
2485
|
+
this.events.emit(BeaconEvent.CHANNEL_CLOSED).catch((emitError) => {
|
|
2486
|
+
logger.error('addOpenRequestTimeout', emitError);
|
|
2487
|
+
});
|
|
2488
|
+
}, this.requestTimeoutMs);
|
|
2489
|
+
this.openRequestTimeouts.set(id, timeout);
|
|
2490
|
+
}
|
|
2491
|
+
clearOpenRequest(id) {
|
|
2492
|
+
this.clearOpenRequestTimeout(id);
|
|
2493
|
+
this.openRequests.delete(id);
|
|
2494
|
+
this.openRequestsOtherTabs.delete(id);
|
|
2495
|
+
}
|
|
2496
|
+
clearOpenRequestTimeout(id) {
|
|
2497
|
+
const timeout = this.openRequestTimeouts.get(id);
|
|
2498
|
+
if (timeout) {
|
|
2499
|
+
clearTimeout(timeout);
|
|
2500
|
+
this.openRequestTimeouts.delete(id);
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
clearAllOpenRequestTimeouts() {
|
|
2504
|
+
this.openRequestTimeouts.forEach((timeout) => clearTimeout(timeout));
|
|
2505
|
+
this.openRequestTimeouts.clear();
|
|
2506
|
+
}
|
|
2507
|
+
abortOpenRequests() {
|
|
2508
|
+
Array.from(this.openRequests.entries())
|
|
2509
|
+
.filter(([id]) => id !== SESSION_UPDATE_REQUEST_ID)
|
|
2510
|
+
.forEach(([id, promise]) => {
|
|
2511
|
+
promise.reject(this.createAbortedError(id));
|
|
2512
|
+
this.clearOpenRequest(id);
|
|
2513
|
+
});
|
|
2514
|
+
this.openRequests.clear();
|
|
2515
|
+
this.openRequestsOtherTabs.clear();
|
|
2516
|
+
this.acknowledgedRequests.clear();
|
|
2517
|
+
this.clearAllOpenRequestTimeouts();
|
|
1920
2518
|
}
|
|
1921
2519
|
async sendNotificationWithAccessToken(notification) {
|
|
1922
2520
|
const { url, recipient, title, body, payload, protocolIdentifier, accessToken } = notification;
|
|
@@ -1938,26 +2536,33 @@ export class DAppClient extends Client {
|
|
|
1938
2536
|
const signature = await signMessage(payloadBytes, {
|
|
1939
2537
|
secretKey: Buffer.from(keypair.secretKey)
|
|
1940
2538
|
});
|
|
1941
|
-
const notificationResponse = await
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
body
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
2539
|
+
const notificationResponse = await fetch(`${url}/send`, {
|
|
2540
|
+
method: 'POST',
|
|
2541
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2542
|
+
body: JSON.stringify({
|
|
2543
|
+
recipient,
|
|
2544
|
+
title,
|
|
2545
|
+
body,
|
|
2546
|
+
timestamp,
|
|
2547
|
+
payload,
|
|
2548
|
+
accessToken,
|
|
2549
|
+
protocolIdentifier,
|
|
2550
|
+
sender: {
|
|
2551
|
+
name: this.name,
|
|
2552
|
+
publicKey,
|
|
2553
|
+
signature
|
|
2554
|
+
}
|
|
2555
|
+
})
|
|
1954
2556
|
});
|
|
1955
|
-
|
|
2557
|
+
if (!notificationResponse.ok) {
|
|
2558
|
+
throw new Error(`sendNotification failed: ${notificationResponse.status} ${notificationResponse.statusText}`);
|
|
2559
|
+
}
|
|
2560
|
+
return notificationResponse.json();
|
|
1956
2561
|
}
|
|
1957
2562
|
async onNewAccount(message, connectionInfo) {
|
|
1958
2563
|
// TODO: Migration code. Remove sometime after 1.0.0 release.
|
|
1959
2564
|
const tempPK = message.publicKey || message.pubkey || message.pubKey;
|
|
1960
|
-
const publicKey =
|
|
2565
|
+
const publicKey = tempPK ? prefixPublicKey(tempPK) : undefined;
|
|
1961
2566
|
if (!publicKey && !message.address) {
|
|
1962
2567
|
throw new Error('PublicKey or Address must be defined');
|
|
1963
2568
|
}
|