@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.
Files changed (59) hide show
  1. package/README.md +55 -7
  2. package/dist/cjs/beacon-message-events.js.map +1 -1
  3. package/dist/cjs/dapp-client/DAppClient.d.ts +114 -6
  4. package/dist/cjs/dapp-client/DAppClient.js +986 -392
  5. package/dist/cjs/dapp-client/DAppClient.js.map +1 -1
  6. package/dist/cjs/dapp-client/DAppClientOptions.d.ts +17 -0
  7. package/dist/cjs/dapp-client/DAppClientOptions.js.map +1 -1
  8. package/dist/cjs/events.d.ts +29 -3
  9. package/dist/cjs/events.js +41 -37
  10. package/dist/cjs/events.js.map +1 -1
  11. package/dist/cjs/index.d.ts +4 -2
  12. package/dist/cjs/index.js.map +1 -1
  13. package/dist/cjs/transports/DappP2PTransport.d.ts +1 -1
  14. package/dist/cjs/transports/DappP2PTransport.js.map +1 -1
  15. package/dist/cjs/transports/DappPostMessageTransport.d.ts +1 -1
  16. package/dist/cjs/transports/DappPostMessageTransport.js.map +1 -1
  17. package/dist/cjs/transports/DappWalletConnectTransport.d.ts +1 -1
  18. package/dist/cjs/transports/DappWalletConnectTransport.js.map +1 -1
  19. package/dist/cjs/utils/available-transports.js.map +1 -1
  20. package/dist/cjs/utils/block-explorer.d.ts +2 -2
  21. package/dist/cjs/utils/block-explorer.js +5 -1
  22. package/dist/cjs/utils/block-explorer.js.map +1 -1
  23. package/dist/cjs/utils/get-instance.js +7 -1
  24. package/dist/cjs/utils/get-instance.js.map +1 -1
  25. package/dist/cjs/utils/shorten-string.js.map +1 -1
  26. package/dist/cjs/utils/tzkt-blockexplorer.d.ts +2 -2
  27. package/dist/cjs/utils/tzkt-blockexplorer.js +3 -17
  28. package/dist/cjs/utils/tzkt-blockexplorer.js.map +1 -1
  29. package/dist/esm/beacon-message-events.js.map +1 -1
  30. package/dist/esm/dapp-client/DAppClient.d.ts +114 -6
  31. package/dist/esm/dapp-client/DAppClient.js +991 -386
  32. package/dist/esm/dapp-client/DAppClient.js.map +1 -1
  33. package/dist/esm/dapp-client/DAppClientOptions.d.ts +17 -0
  34. package/dist/esm/dapp-client/DAppClientOptions.js.map +1 -1
  35. package/dist/esm/events.d.ts +29 -3
  36. package/dist/esm/events.js +6 -3
  37. package/dist/esm/events.js.map +1 -1
  38. package/dist/esm/index.d.ts +4 -2
  39. package/dist/esm/index.js.map +1 -1
  40. package/dist/esm/transports/DappP2PTransport.d.ts +1 -1
  41. package/dist/esm/transports/DappP2PTransport.js.map +1 -1
  42. package/dist/esm/transports/DappPostMessageTransport.d.ts +1 -1
  43. package/dist/esm/transports/DappPostMessageTransport.js.map +1 -1
  44. package/dist/esm/transports/DappWalletConnectTransport.d.ts +1 -1
  45. package/dist/esm/transports/DappWalletConnectTransport.js.map +1 -1
  46. package/dist/esm/utils/available-transports.js.map +1 -1
  47. package/dist/esm/utils/block-explorer.d.ts +2 -2
  48. package/dist/esm/utils/block-explorer.js +5 -1
  49. package/dist/esm/utils/block-explorer.js.map +1 -1
  50. package/dist/esm/utils/get-instance.js +7 -1
  51. package/dist/esm/utils/get-instance.js.map +1 -1
  52. package/dist/esm/utils/shorten-string.js.map +1 -1
  53. package/dist/esm/utils/tzkt-blockexplorer.d.ts +2 -2
  54. package/dist/esm/utils/tzkt-blockexplorer.js +3 -17
  55. package/dist/esm/utils/tzkt-blockexplorer.js.map +1 -1
  56. package/dist/octez.connect.dapp.min.js +1 -1066
  57. package/package.json +17 -13
  58. package/LICENCE +0 -19
  59. package/dist/walletbeacon.dapp.min.js +0 -1066
@@ -10,15 +10,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.DAppClient = void 0;
13
- const axios_1 = require("axios");
14
13
  const bs58check_1 = require("bs58check");
15
- const events_1 = require("../events");
16
14
  const octez_connect_types_1 = require("@tezos-x/octez.connect-types");
17
15
  const octez_connect_core_1 = require("@tezos-x/octez.connect-core");
18
16
  const octez_connect_utils_1 = require("@tezos-x/octez.connect-utils");
19
17
  const beacon_message_events_1 = require("../beacon-message-events");
18
+ const events_1 = require("../events");
20
19
  const tzkt_blockexplorer_1 = require("../utils/tzkt-blockexplorer");
21
- const octez_connect_dapp_1 = require("@tezos-x/octez.connect-dapp");
22
20
  const DappPostMessageTransport_1 = require("../transports/DappPostMessageTransport");
23
21
  const DappP2PTransport_1 = require("../transports/DappP2PTransport");
24
22
  const DappWalletConnectTransport_1 = require("../transports/DappWalletConnectTransport");
@@ -26,6 +24,23 @@ const octez_connect_transport_postmessage_1 = require("@tezos-x/octez.connect-tr
26
24
  const octez_connect_ui_1 = require("@tezos-x/octez.connect-ui");
27
25
  const octez_connect_transport_walletconnect_1 = require("@tezos-x/octez.connect-transport-walletconnect");
28
26
  const logger = new octez_connect_core_1.Logger('DAppClient');
27
+ const DEFAULT_REQUEST_TIMEOUT_MS = 60000;
28
+ const SESSION_UPDATE_REQUEST_ID = 'session_update';
29
+ const createLazyPromise = (factory) => {
30
+ let promise;
31
+ const getPromise = () => {
32
+ if (!promise) {
33
+ promise = factory();
34
+ }
35
+ return promise;
36
+ };
37
+ const lazyPromise = {
38
+ then: (onfulfilled, onrejected) => getPromise().then(onfulfilled, onrejected),
39
+ catch: (onrejected) => getPromise().catch(onrejected),
40
+ finally: (onfinally) => getPromise().finally(onfinally !== null && onfinally !== void 0 ? onfinally : undefined)
41
+ };
42
+ return lazyPromise;
43
+ };
29
44
  /**
30
45
  * @publicapi
31
46
  *
@@ -36,15 +51,17 @@ const logger = new octez_connect_core_1.Logger('DAppClient');
36
51
  */
37
52
  class DAppClient extends octez_connect_core_1.Client {
38
53
  constructor(config) {
39
- var _a, _b, _c, _d, _e, _f, _g, _h;
54
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
40
55
  super(Object.assign({ storage: config && config.storage ? config.storage : new octez_connect_core_1.LocalStorage() }, config));
41
- this.events = new octez_connect_dapp_1.BeaconEventHandler();
56
+ this.events = new events_1.BeaconEventHandler();
42
57
  this.isGetActiveAccountHandled = false;
43
58
  this.openRequestsOtherTabs = new Set();
44
59
  /**
45
60
  * A map of requests that are currently "open", meaning we have sent them to a wallet and are still awaiting a response.
46
61
  */
47
62
  this.openRequests = new Map();
63
+ this.acknowledgedRequests = new Set();
64
+ this.openRequestTimeouts = new Map();
48
65
  /**
49
66
  * The currently active account. For all requests that are associated to a specific request (operation request, signing request),
50
67
  * the active account is used to determine the network and destination wallet
@@ -54,44 +71,59 @@ class DAppClient extends octez_connect_core_1.Client {
54
71
  * The currently active peer. This is used to address a peer in case the active account is not set. (Eg. for permission requests)
55
72
  */
56
73
  this._activePeer = new octez_connect_utils_1.ExposedPromise();
57
- this.isInitPending = false;
74
+ this.destroyed = false;
58
75
  this.beaconIDB = new octez_connect_core_1.IndexedDBStorage('beacon', ['bug_report', 'metrics']);
59
76
  this.debounceSetActiveAccount = false;
77
+ // Constructor-time invalid storage recovery can race through multiple async paths.
78
+ // Keep this guard scoped to those startup paths.
79
+ this.hasEmittedInvalidAccountDeactivated = false;
80
+ this._disconnectNotifyPeers = false;
60
81
  this.multiTabChannel = new octez_connect_core_1.MultiTabChannel('octez.connect-sdk-channel', this.onBCMessageHandler.bind(this), this.onElectedLeaderhandler.bind(this));
61
82
  this.blockchains = new Map();
62
83
  this.description = config.description;
63
- this.wcProjectId = ((_a = config.walletConnectOptions) === null || _a === void 0 ? void 0 : _a.projectId) || '24469fd0a06df227b6e5f7dc7de0ff4f';
64
- this.wcRelayUrl = (_b = config.walletConnectOptions) === null || _b === void 0 ? void 0 : _b.relayUrl;
84
+ this.disableWalletConnect = (_a = config.disableWalletConnect) !== null && _a !== void 0 ? _a : false;
85
+ if (!this.disableWalletConnect) {
86
+ this.wcProjectId = ((_b = config.walletConnectOptions) === null || _b === void 0 ? void 0 : _b.projectId) || '24469fd0a06df227b6e5f7dc7de0ff4f';
87
+ this.wcRelayUrl = (_c = config.walletConnectOptions) === null || _c === void 0 ? void 0 : _c.relayUrl;
88
+ }
65
89
  this.featuredWallets = config.featuredWallets;
66
- this.events = new octez_connect_dapp_1.BeaconEventHandler(config.eventHandlers, (_c = config.disableDefaultEvents) !== null && _c !== void 0 ? _c : false);
67
- this.blockExplorer = (_d = config.blockExplorer) !== null && _d !== void 0 ? _d : new tzkt_blockexplorer_1.TzktBlockExplorer();
68
- this.network = (_e = config.network) !== null && _e !== void 0 ? _e : { type: (_f = config.preferredNetwork) !== null && _f !== void 0 ? _f : octez_connect_types_1.NetworkType.MAINNET };
69
- (0, octez_connect_ui_1.setColorMode)((_g = config.colorMode) !== null && _g !== void 0 ? _g : octez_connect_types_1.ColorMode.LIGHT);
90
+ this.events = new events_1.BeaconEventHandler(config.eventHandlers, (_d = config.disableDefaultEvents) !== null && _d !== void 0 ? _d : false);
91
+ this.blockExplorer = (_e = config.blockExplorer) !== null && _e !== void 0 ? _e : new tzkt_blockexplorer_1.TzktBlockExplorer();
92
+ this.network = (_f = config.network) !== null && _f !== void 0 ? _f : { type: (_g = config.preferredNetwork) !== null && _g !== void 0 ? _g : octez_connect_types_1.NetworkType.MAINNET };
93
+ (0, octez_connect_ui_1.setColorMode)((_h = config.colorMode) !== null && _h !== void 0 ? _h : octez_connect_types_1.ColorMode.LIGHT);
70
94
  this.disclaimerText = config.disclaimerText;
71
- this.errorMessages = (_h = config.errorMessages) !== null && _h !== void 0 ? _h : {};
95
+ this.errorMessages = (_j = config.errorMessages) !== null && _j !== void 0 ? _j : {};
72
96
  this.appMetadataManager = new octez_connect_core_1.AppMetadataManager(this.storage);
73
97
  this.storageValidator = new octez_connect_core_1.StorageValidator(this.storage);
74
98
  this.enableAppSwitching =
75
99
  config.enableAppSwitching === undefined ? true : !!config.enableAppSwitching;
76
100
  this.enableMetrics = config.enableMetrics ? true : false;
101
+ this.requestTimeoutMs = (_k = config.requestTimeoutMs) !== null && _k !== void 0 ? _k : DEFAULT_REQUEST_TIMEOUT_MS;
77
102
  // Subscribe to storage changes and update the active account if it changes on other tabs
78
103
  this.storage.subscribeToStorageChanged((event) => __awaiter(this, void 0, void 0, function* () {
104
+ if (event.oldValue === event.newValue) {
105
+ return;
106
+ }
79
107
  if (event.eventType === 'storageCleared') {
80
- this.setActiveAccount(undefined);
108
+ yield this.setActiveAccount(undefined);
81
109
  return;
82
110
  }
83
111
  if (event.eventType === 'entryModified') {
84
112
  if (event.key === this.storage.getPrefixedKey(octez_connect_types_1.StorageKey.ACTIVE_ACCOUNT)) {
85
113
  const accountIdentifier = event.newValue;
86
114
  if (!accountIdentifier || accountIdentifier === 'undefined') {
87
- this.setActiveAccount(undefined);
115
+ yield this.setActiveAccount(undefined);
88
116
  }
89
117
  else {
90
118
  const account = yield this.getAccount(accountIdentifier);
91
- this.setActiveAccount(account);
119
+ yield this.setActiveAccount(account);
92
120
  }
93
121
  return;
94
122
  }
123
+ if (event.key === this.storage.getPrefixedKey(octez_connect_types_1.StorageKey.ACCOUNTS)) {
124
+ yield this.recoverActiveAccountFromAccountsChange();
125
+ return;
126
+ }
95
127
  if (event.key === this.storage.getPrefixedKey(octez_connect_types_1.StorageKey.ENABLE_METRICS)) {
96
128
  this.enableMetrics = !!(yield this.storage.get(octez_connect_types_1.StorageKey.ENABLE_METRICS));
97
129
  return;
@@ -107,31 +139,43 @@ class DAppClient extends octez_connect_core_1.Client {
107
139
  this.activeAccountLoaded = this.storage
108
140
  .get(octez_connect_types_1.StorageKey.ACTIVE_ACCOUNT)
109
141
  .then((activeAccountIdentifier) => __awaiter(this, void 0, void 0, function* () {
142
+ const accounts = yield this.accountManager.getAccounts();
143
+ if (activeAccountIdentifier === 'undefined') {
144
+ // Non-normalizing storage backends can still surface legacy sentinel strings directly.
145
+ yield this.deactivateInvalidAccountState('missing_active_account');
146
+ return undefined;
147
+ }
110
148
  if (activeAccountIdentifier) {
111
- const account = yield this.accountManager.getAccount(activeAccountIdentifier);
149
+ const account = accounts.find((storedAccount) => storedAccount.accountIdentifier === activeAccountIdentifier);
150
+ if (!account) {
151
+ yield this.deactivateInvalidAccountState('missing_active_account');
152
+ return undefined;
153
+ }
112
154
  yield this.setActiveAccount(account);
113
155
  return account;
114
156
  }
115
- else {
116
- yield this.setActiveAccount(undefined);
157
+ if (accounts.length > 0 && this.hasStoredActiveAccountPointer()) {
158
+ yield this.deactivateInvalidAccountState('missing_active_account');
117
159
  return undefined;
118
160
  }
161
+ yield this.setActiveAccount(undefined);
162
+ return undefined;
119
163
  }))
120
164
  .catch((storageError) => __awaiter(this, void 0, void 0, function* () {
121
165
  logger.error(storageError);
122
- yield this.resetInvalidState(false);
123
- this.events.emit(events_1.BeaconEvent.INVALID_ACCOUNT_DEACTIVATED);
166
+ yield this.deactivateInvalidAccountState('invalid_active_account_storage');
124
167
  return undefined;
125
168
  }));
126
169
  this.handleResponse = (message, connectionInfo) => __awaiter(this, void 0, void 0, function* () {
127
170
  var _a;
128
- const typedMessage = message.version === '3'
171
+ const isV3WrappedMessage = message.version === '3' && 'message' in message;
172
+ const typedMessage = isV3WrappedMessage
129
173
  ? message.message
130
174
  : message;
131
- let appMetadata = message.version === '3'
175
+ let appMetadata = isV3WrappedMessage
132
176
  ? (_a = typedMessage.blockchainData) === null || _a === void 0 ? void 0 : _a.appMetadata
133
177
  : typedMessage.appMetadata;
134
- if (!appMetadata && message.version === '3') {
178
+ if (!appMetadata && isV3WrappedMessage) {
135
179
  const storedMetadata = yield Promise.all([
136
180
  this.storage.get(octez_connect_types_1.StorageKey.TRANSPORT_P2P_PEERS_DAPP),
137
181
  this.storage.get(octez_connect_types_1.StorageKey.TRANSPORT_WALLETCONNECT_PEERS_DAPP),
@@ -160,8 +204,11 @@ class DAppClient extends octez_connect_core_1.Client {
160
204
  },
161
205
  id: message.id
162
206
  });
163
- if (typedMessage.type !== octez_connect_types_1.BeaconMessageType.Acknowledge) {
164
- this.openRequestsOtherTabs.delete(message.id);
207
+ if (typedMessage.type === octez_connect_types_1.BeaconMessageType.Acknowledge) {
208
+ this.clearOpenRequestTimeout(message.id);
209
+ }
210
+ else {
211
+ this.clearOpenRequest(message.id);
165
212
  }
166
213
  return;
167
214
  }
@@ -189,15 +236,19 @@ class DAppClient extends octez_connect_core_1.Client {
189
236
  yield this.events.emit(events_1.BeaconEvent.CHANNEL_CLOSED);
190
237
  });
191
238
  if (openRequest && typedMessage.type === octez_connect_types_1.BeaconMessageType.Acknowledge) {
192
- this.analytics.track('event', 'DAppClient', 'Acknowledge received from Wallet');
193
- logger.log('handleResponse', `acknowledge message received for ${message.id}`);
194
- this.events
195
- .emit(events_1.BeaconEvent.ACKNOWLEDGE_RECEIVED, {
196
- message: typedMessage,
197
- extraInfo: {},
198
- walletInfo: yield this.getWalletInfo()
199
- })
200
- .catch(console.error);
239
+ if (!this.acknowledgedRequests.has(message.id)) {
240
+ this.acknowledgedRequests.add(message.id);
241
+ this.clearOpenRequestTimeout(message.id);
242
+ this.analytics.track('event', 'DAppClient', 'Acknowledge received from Wallet');
243
+ logger.log('handleResponse', `acknowledge message received for ${message.id}`);
244
+ this.events
245
+ .emit(events_1.BeaconEvent.ACKNOWLEDGE_RECEIVED, {
246
+ message: typedMessage,
247
+ extraInfo: {},
248
+ walletInfo: yield this.getWalletInfo()
249
+ })
250
+ .catch((emitError) => logger.error('handleResponse', emitError));
251
+ }
201
252
  }
202
253
  else if (openRequest) {
203
254
  if (typedMessage.type === octez_connect_types_1.BeaconMessageType.PermissionResponse && appMetadata) {
@@ -209,7 +260,8 @@ class DAppClient extends octez_connect_core_1.Client {
209
260
  else {
210
261
  openRequest.resolve({ message, connectionInfo });
211
262
  }
212
- this.openRequests.delete(typedMessage.id);
263
+ this.clearOpenRequest(message.id);
264
+ this.acknowledgedRequests.delete(message.id);
213
265
  }
214
266
  else {
215
267
  if (typedMessage.type === octez_connect_types_1.BeaconMessageType.Disconnect) {
@@ -218,16 +270,23 @@ class DAppClient extends octez_connect_core_1.Client {
218
270
  else if (typedMessage.type === octez_connect_types_1.BeaconMessageType.ChangeAccountRequest) {
219
271
  yield this.onNewAccount(typedMessage, connectionInfo);
220
272
  }
273
+ else {
274
+ logger.warn('handleResponse', 'received response for unknown or closed request', {
275
+ id: message.id,
276
+ type: typedMessage.type,
277
+ connectionInfo
278
+ });
279
+ }
221
280
  }
222
281
  if (this._transport.isResolved()) {
223
282
  const transport = yield this.transport;
224
283
  if (transport instanceof octez_connect_transport_walletconnect_1.WalletConnectTransport &&
225
- !this.openRequests.has('session_update')) {
226
- this.openRequests.set('session_update', new octez_connect_utils_1.ExposedPromise());
284
+ !this.openRequests.has(SESSION_UPDATE_REQUEST_ID)) {
285
+ this.openRequests.set(SESSION_UPDATE_REQUEST_ID, new octez_connect_utils_1.ExposedPromise());
227
286
  }
228
287
  }
229
288
  });
230
- this.storageValidator
289
+ this.storageValidated = this.storageValidator
231
290
  .validate()
232
291
  .then((isValid) => __awaiter(this, void 0, void 0, function* () {
233
292
  var _a, _b;
@@ -244,15 +303,22 @@ class DAppClient extends octez_connect_core_1.Client {
244
303
  url: info.deeplink
245
304
  });
246
305
  const nowValid = yield this.storageValidator.validate();
247
- if (!nowValid) {
248
- this.resetInvalidState(false);
306
+ if (!nowValid && account) {
307
+ yield this.deactivateInvalidAccountState('storage_validation_failed');
308
+ }
309
+ else if (!nowValid) {
310
+ yield this.resetInvalidState(false);
249
311
  }
250
312
  }
251
313
  if (account && account.origin.type !== 'p2p') {
252
- this.init();
314
+ // Fire-and-forget eager init for stored sessions. init() can now
315
+ // reject (e.g. when an abort path settles _initPromise), so swallow
316
+ // here; downstream callers of init() handle rejection on their own.
317
+ this.init().catch((initError) => { var _a; return logger.error('eager init failed', (_a = initError === null || initError === void 0 ? void 0 : initError.message) !== null && _a !== void 0 ? _a : initError); });
253
318
  }
254
319
  }))
255
320
  .catch((err) => logger.error(err.message));
321
+ this.retainStorageValidationPromise();
256
322
  this.sendMetrics('enable-metrics?' + this.addQueryParam('version', octez_connect_core_1.SDK_VERSION), undefined, (res) => {
257
323
  if (!res.ok) {
258
324
  res.status === 426
@@ -330,11 +396,42 @@ class DAppClient extends octez_connect_core_1.Client {
330
396
  const transport = (yield this._transport.promise);
331
397
  yield transport.waitForResolution();
332
398
  this.openRequestsOtherTabs.add(message.id);
333
- isV3
399
+ const requestPromise = isV3
334
400
  ? this.makeRequestV3(message.data, message.id)
335
401
  : this.makeRequest(message.data, false, message.id);
402
+ requestPromise.catch((requestError) => {
403
+ const errorResponse = this.buildDelegatedRequestError(message.id, requestError);
404
+ this.multiTabChannel.postMessage({
405
+ type: 'RESPONSE',
406
+ data: {
407
+ message: errorResponse,
408
+ connectionInfo: {
409
+ origin: octez_connect_types_1.Origin.WALLETCONNECT,
410
+ id: ''
411
+ }
412
+ },
413
+ id: message.id
414
+ });
415
+ this.clearOpenRequest(message.id);
416
+ });
336
417
  });
337
418
  }
419
+ buildDelegatedRequestError(id, error) {
420
+ if (typeof error === 'object' &&
421
+ error !== null &&
422
+ 'type' in error &&
423
+ 'errorType' in error &&
424
+ error.type === octez_connect_types_1.BeaconMessageType.Error) {
425
+ return Object.assign(Object.assign({}, error), { id });
426
+ }
427
+ return {
428
+ id,
429
+ type: octez_connect_types_1.BeaconMessageType.Error,
430
+ errorType: octez_connect_types_1.BeaconErrorType.UNKNOWN_ERROR,
431
+ senderId: '',
432
+ version: '2'
433
+ };
434
+ }
338
435
  createStateSnapshot() {
339
436
  return __awaiter(this, void 0, void 0, function* () {
340
437
  if (!localStorage || !this.enableMetrics) {
@@ -365,10 +462,7 @@ class DAppClient extends octez_connect_core_1.Client {
365
462
  initInternalTransports() {
366
463
  return __awaiter(this, void 0, void 0, function* () {
367
464
  var _a, _b;
368
- const seed = yield this.storage.get(octez_connect_types_1.StorageKey.BEACON_SDK_SECRET_SEED);
369
- if (!seed) {
370
- throw new Error('Secret seed not found');
371
- }
465
+ const seed = yield this.getOrCreateSDKSecretSeed();
372
466
  const keyPair = yield (0, octez_connect_utils_1.getKeypairFromSeed)(seed);
373
467
  if (this.postMessageTransport || this.p2pTransport || this.walletConnectTransport) {
374
468
  return;
@@ -377,6 +471,11 @@ class DAppClient extends octez_connect_core_1.Client {
377
471
  yield this.addListener(this.postMessageTransport);
378
472
  this.p2pTransport = new DappP2PTransport_1.DappP2PTransport(this.name, keyPair, this.storage, this.matrixNodes, this.iconUrl, this.appUrl);
379
473
  yield this.addListener(this.p2pTransport);
474
+ // Skip WalletConnect entirely when disabled (e.g. inside a Firefox MV3 content-script
475
+ // compartment where the WC provider cannot run). postMessage + P2P are unaffected.
476
+ if (this.disableWalletConnect) {
477
+ return;
478
+ }
380
479
  const wcOptions = {
381
480
  projectId: this.wcProjectId,
382
481
  relayUrl: this.wcRelayUrl,
@@ -395,6 +494,41 @@ class DAppClient extends octez_connect_core_1.Client {
395
494
  yield this.addListener(this.walletConnectTransport);
396
495
  });
397
496
  }
497
+ getOrCreateSDKSecretSeed() {
498
+ return __awaiter(this, void 0, void 0, function* () {
499
+ if (this._sdkSecretSeedPromise) {
500
+ return this._sdkSecretSeedPromise;
501
+ }
502
+ this._sdkSecretSeedPromise = this.loadOrCreateSDKSecretSeed()
503
+ .finally(() => {
504
+ this._sdkSecretSeedPromise = undefined;
505
+ })
506
+ .catch((error) => {
507
+ throw error;
508
+ });
509
+ return this._sdkSecretSeedPromise;
510
+ });
511
+ }
512
+ loadOrCreateSDKSecretSeed() {
513
+ return __awaiter(this, void 0, void 0, function* () {
514
+ const existingSeed = yield this.storage.get(octez_connect_types_1.StorageKey.BEACON_SDK_SECRET_SEED);
515
+ if (this.isValidSDKSecretSeed(existingSeed)) {
516
+ return existingSeed;
517
+ }
518
+ yield this.storage.delete(octez_connect_types_1.StorageKey.BEACON_SDK_SECRET_SEED);
519
+ this._keyPair = new octez_connect_utils_1.ExposedPromise();
520
+ this._beaconId = new octez_connect_utils_1.ExposedPromise();
521
+ yield this.initSDK();
522
+ const restoredSeed = yield this.storage.get(octez_connect_types_1.StorageKey.BEACON_SDK_SECRET_SEED);
523
+ if (this.isValidSDKSecretSeed(restoredSeed)) {
524
+ return restoredSeed;
525
+ }
526
+ throw new Error('Secret seed not found');
527
+ });
528
+ }
529
+ isValidSDKSecretSeed(seed) {
530
+ return typeof seed === 'string' && seed !== '' && seed !== 'undefined' && seed !== 'null';
531
+ }
398
532
  initEvents() {
399
533
  if (!this.walletConnectTransport) {
400
534
  return;
@@ -412,6 +546,7 @@ class DAppClient extends octez_connect_core_1.Client {
412
546
  }
413
547
  wcToastHandler(status) {
414
548
  return __awaiter(this, void 0, void 0, function* () {
549
+ var _a;
415
550
  const walletInfo = yield (() => __awaiter(this, void 0, void 0, function* () {
416
551
  try {
417
552
  return yield this.getWalletInfo();
@@ -429,22 +564,28 @@ class DAppClient extends octez_connect_core_1.Client {
429
564
  });
430
565
  }
431
566
  else {
432
- this.events.emit(events_1.BeaconEvent.PERMISSION_REQUEST_ERROR, {
433
- errorResponse: { errorType: octez_connect_types_1.BeaconErrorType.ABORTED_ERROR },
434
- walletInfo
435
- });
567
+ // The WC transport reports session-proposal rejection through this event
568
+ // when there are no active listeners yet (no peer paired). Reject the
569
+ // in-flight init promise so `await dapp.requestPermissions()` unwinds
570
+ // through requestPermissions' catch -> handleRequestError, which both
571
+ // emits PERMISSION_REQUEST_ERROR and resets transport state. Without
572
+ // this, the dapp wedges with a pending `_initPromise`.
573
+ (_a = this._initReject) === null || _a === void 0 ? void 0 : _a.call(this, this.createAbortedError());
436
574
  }
437
575
  });
438
576
  }
439
577
  channelClosedHandler(type) {
440
578
  return __awaiter(this, void 0, void 0, function* () {
579
+ if (!this._transport.isResolved()) {
580
+ yield this.events.emit(events_1.BeaconEvent.CHANNEL_CLOSED);
581
+ return;
582
+ }
441
583
  const transport = yield this.transport;
442
584
  if (transport.type !== type) {
443
585
  return;
444
586
  }
445
587
  yield this.events.emit(events_1.BeaconEvent.CHANNEL_CLOSED);
446
- this.setActiveAccount(undefined);
447
- yield this.disconnect();
588
+ yield this.disconnect({ notifyPeers: false });
448
589
  });
449
590
  }
450
591
  /**
@@ -459,16 +600,81 @@ class DAppClient extends octez_connect_core_1.Client {
459
600
  destroy: { get: () => super.destroy }
460
601
  });
461
602
  return __awaiter(this, void 0, void 0, function* () {
603
+ this.destroyed = true;
462
604
  yield this.createStateSnapshot();
605
+ yield this.destroyInternalTransports();
463
606
  yield _super.destroy.call(this);
464
607
  });
465
608
  }
609
+ destroyInternalTransports() {
610
+ return __awaiter(this, void 0, void 0, function* () {
611
+ this.abortPendingInit();
612
+ const transports = [];
613
+ if (this.postMessageTransport) {
614
+ transports.push(this.postMessageTransport);
615
+ }
616
+ if (this.p2pTransport) {
617
+ transports.push(this.p2pTransport);
618
+ }
619
+ if (this.walletConnectTransport) {
620
+ transports.push(this.walletConnectTransport);
621
+ }
622
+ if (transports.length > 0) {
623
+ yield this.cleanup(transports);
624
+ yield Promise.all(transports.map((transport) => __awaiter(this, void 0, void 0, function* () {
625
+ yield transport.disconnect();
626
+ if ('doClientCleanup' in transport) {
627
+ yield transport.doClientCleanup();
628
+ }
629
+ })));
630
+ this.postMessageTransport = this.p2pTransport = this.walletConnectTransport = undefined;
631
+ yield this.setTransport(undefined);
632
+ }
633
+ yield this.multiTabChannel.close();
634
+ });
635
+ }
636
+ abortPendingInit() {
637
+ var _a;
638
+ (_a = this._initReject) === null || _a === void 0 ? void 0 : _a.call(this, this.createAbortedError());
639
+ this._initPromise = undefined;
640
+ this._initReject = undefined;
641
+ this._initSubstratePairing = undefined;
642
+ this._requestPermissionsPromise = undefined;
643
+ this._requestPermissionsKey = undefined;
644
+ }
645
+ createAbortedError(id = '') {
646
+ return {
647
+ type: octez_connect_types_1.BeaconMessageType.Error,
648
+ id,
649
+ senderId: '',
650
+ version: '2',
651
+ errorType: octez_connect_types_1.BeaconErrorType.ABORTED_ERROR
652
+ };
653
+ }
654
+ /**
655
+ * @internal
656
+ */
657
+ isDestroyed() {
658
+ return this.destroyed;
659
+ }
660
+ assertNotDestroyed() {
661
+ if (this.destroyed) {
662
+ throw new Error('DAppClient has been destroyed and cannot be used again.');
663
+ }
664
+ }
466
665
  init(transport, substratePairing) {
467
666
  const _super = Object.create(null, {
468
667
  init: { get: () => super.init }
469
668
  });
470
669
  return __awaiter(this, void 0, void 0, function* () {
670
+ this.assertNotDestroyed();
671
+ const requestedSubstratePairing = substratePairing === true;
471
672
  if (this._initPromise) {
673
+ if (!transport &&
674
+ this._initReject &&
675
+ this._initSubstratePairing !== requestedSubstratePairing) {
676
+ throw new Error('Cannot start a permission request with different pairing options while another pairing is pending');
677
+ }
472
678
  return this._initPromise;
473
679
  }
474
680
  try {
@@ -477,163 +683,260 @@ class DAppClient extends octez_connect_core_1.Client {
477
683
  catch (_a) {
478
684
  //
479
685
  }
480
- this._initPromise = new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
481
- if (transport) {
482
- yield this.addListener(transport);
483
- resolve(yield _super.init.call(this, transport));
484
- }
485
- else if (this._transport.isSettled()) {
486
- yield (yield this.transport).connect();
487
- resolve(yield _super.init.call(this, yield this.transport));
488
- }
489
- else {
490
- const activeAccount = yield this.getActiveAccount();
491
- const stopListening = () => {
492
- if (this.postMessageTransport) {
493
- this.postMessageTransport.stopListeningForNewPeers().catch(console.error);
494
- }
495
- if (this.p2pTransport) {
496
- this.p2pTransport.stopListeningForNewPeers().catch(console.error);
497
- }
498
- if (this.walletConnectTransport) {
499
- this.walletConnectTransport.stopListeningForNewPeers().catch(console.error);
500
- }
501
- };
502
- yield this.initInternalTransports();
503
- if (!this.postMessageTransport || !this.p2pTransport || !this.walletConnectTransport) {
504
- return;
686
+ this._initPromise = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
687
+ this._initSubstratePairing = requestedSubstratePairing;
688
+ // Capture reject so abort paths (modal close, transport-level rejection)
689
+ // can unwedge a pending `await this.init()`. _initReject is cleared by
690
+ // the .finally below whether the promise resolves or rejects, so the
691
+ // field accurately means "currently rejectable" rather than "most recent
692
+ // rejector".
693
+ this._initReject = (reason) => {
694
+ this._initPromise = undefined;
695
+ reject(reason);
696
+ };
697
+ // The body uses `new Promise(async ...)` (an anti-pattern: unhandled
698
+ // throws are swallowed). Wrap it so any unexpected throw rejects the
699
+ // promise instead of stranding `await this.init()` callers forever.
700
+ // A proper structural fix would replace the async-executor entirely
701
+ // with chained promises; left as follow-up to keep this PR focused.
702
+ try {
703
+ if (transport) {
704
+ yield this.addListener(transport);
705
+ resolve(yield _super.init.call(this, transport));
505
706
  }
506
- this.postMessageTransport.connect().then().catch(console.error);
507
- if (activeAccount && activeAccount.origin) {
508
- const origin = activeAccount.origin.type;
509
- // Select the transport that matches the active account
510
- if (origin === octez_connect_types_1.Origin.EXTENSION) {
511
- resolve(yield _super.init.call(this, this.postMessageTransport));
512
- }
513
- else if (origin === octez_connect_types_1.Origin.P2P) {
514
- resolve(yield _super.init.call(this, this.p2pTransport));
515
- }
516
- else if (origin === octez_connect_types_1.Origin.WALLETCONNECT) {
517
- resolve(yield _super.init.call(this, this.walletConnectTransport));
518
- }
707
+ else if (this._transport.isSettled()) {
708
+ yield (yield this.transport).connect();
709
+ resolve(yield _super.init.call(this, yield this.transport));
519
710
  }
520
711
  else {
521
- const p2pTransport = this.p2pTransport;
522
- const postMessageTransport = this.postMessageTransport;
523
- const walletConnectTransport = this.walletConnectTransport;
524
- postMessageTransport
525
- .listenForNewPeer((peer) => {
526
- logger.log('init', 'postmessage transport peer connected', peer);
527
- this.analytics.track('event', 'DAppClient', 'Extension connected', {
528
- peerName: peer.name
529
- });
530
- this.events
531
- .emit(events_1.BeaconEvent.PAIR_SUCCESS, peer)
532
- .catch((emitError) => console.warn(emitError));
533
- this.setActivePeer(peer).catch(console.error);
534
- this.setTransport(this.postMessageTransport).catch(console.error);
535
- stopListening();
536
- resolve(octez_connect_types_1.TransportType.POST_MESSAGE);
537
- })
538
- .catch(console.error);
539
- p2pTransport
540
- .listenForNewPeer((peer) => {
541
- logger.log('init', 'p2p transport peer connected', peer);
542
- this.analytics.track('event', 'DAppClient', 'octez.connect Wallet connected', {
543
- peerName: peer.name
712
+ const activeAccount = yield this.getActiveAccount();
713
+ const stopListening = () => {
714
+ const onError = (err) => logger.error('stopListeningForNewPeers', err);
715
+ if (this.postMessageTransport) {
716
+ this.postMessageTransport.stopListeningForNewPeers().catch(onError);
717
+ }
718
+ if (this.p2pTransport) {
719
+ this.p2pTransport.stopListeningForNewPeers().catch(onError);
720
+ }
721
+ if (this.walletConnectTransport) {
722
+ this.walletConnectTransport.stopListeningForNewPeers().catch(onError);
723
+ }
724
+ };
725
+ yield this.initInternalTransports();
726
+ if (!this.postMessageTransport ||
727
+ !this.p2pTransport ||
728
+ (!this.disableWalletConnect && !this.walletConnectTransport)) {
729
+ // Bare return here used to strand the promise forever -- no UI is
730
+ // emitted, no peer ever pairs, no abort path fires. Reject explicitly
731
+ // so callers see a deterministic error. (WalletConnect is exempt when
732
+ // disableWalletConnect is set -- its transport is intentionally absent.)
733
+ const noTransportError = {
734
+ type: octez_connect_types_1.BeaconMessageType.Error,
735
+ id: '',
736
+ senderId: '',
737
+ version: '2',
738
+ errorType: octez_connect_types_1.BeaconErrorType.UNKNOWN_ERROR
739
+ };
740
+ reject(noTransportError);
741
+ return;
742
+ }
743
+ this.postMessageTransport.connect().then().catch(console.error);
744
+ if (activeAccount && activeAccount.origin) {
745
+ const origin = activeAccount.origin.type;
746
+ // Select the transport that matches the active account
747
+ if (origin === octez_connect_types_1.Origin.EXTENSION) {
748
+ resolve(yield _super.init.call(this, this.postMessageTransport));
749
+ }
750
+ else if (origin === octez_connect_types_1.Origin.P2P) {
751
+ resolve(yield _super.init.call(this, this.p2pTransport));
752
+ }
753
+ else if (origin === octez_connect_types_1.Origin.WALLETCONNECT && this.walletConnectTransport) {
754
+ resolve(yield _super.init.call(this, this.walletConnectTransport));
755
+ }
756
+ }
757
+ else {
758
+ const p2pTransport = this.p2pTransport;
759
+ const postMessageTransport = this.postMessageTransport;
760
+ const walletConnectTransport = this.walletConnectTransport;
761
+ postMessageTransport
762
+ .listenForNewPeer((peer) => {
763
+ logger.log('init', 'postmessage transport peer connected', peer);
764
+ this.analytics.track('event', 'DAppClient', 'Extension connected', {
765
+ peerName: peer.name
766
+ });
767
+ this.events
768
+ .emit(events_1.BeaconEvent.PAIR_SUCCESS, peer)
769
+ .catch((emitError) => console.warn(emitError));
770
+ this.setActivePeer(peer).catch(console.error);
771
+ this.setTransport(this.postMessageTransport).catch(console.error);
772
+ stopListening();
773
+ resolve(octez_connect_types_1.TransportType.POST_MESSAGE);
774
+ })
775
+ .catch(console.error);
776
+ p2pTransport
777
+ .listenForNewPeer((peer) => {
778
+ logger.log('init', 'p2p transport peer connected', peer);
779
+ this.analytics.track('event', 'DAppClient', 'octez.connect Wallet connected', {
780
+ peerName: peer.name
781
+ });
782
+ this.events
783
+ .emit(events_1.BeaconEvent.PAIR_SUCCESS, peer)
784
+ .catch((emitError) => console.warn(emitError));
785
+ this.setActivePeer(peer).catch(console.error);
786
+ this.setTransport(this.p2pTransport).catch(console.error);
787
+ stopListening();
788
+ resolve(octez_connect_types_1.TransportType.P2P);
789
+ })
790
+ .catch(console.error);
791
+ walletConnectTransport === null || walletConnectTransport === void 0 ? void 0 : walletConnectTransport.listenForNewPeer((peer) => {
792
+ logger.log('init', 'walletconnect transport peer connected', peer);
793
+ this.analytics.track('event', 'DAppClient', 'WalletConnect Wallet connected', {
794
+ peerName: peer.name
795
+ });
796
+ this.events
797
+ .emit(events_1.BeaconEvent.PAIR_SUCCESS, peer)
798
+ .catch((emitError) => console.warn(emitError));
799
+ this.setActivePeer(peer).catch(console.error);
800
+ this.setTransport(this.walletConnectTransport).catch(console.error);
801
+ stopListening();
802
+ resolve(octez_connect_types_1.TransportType.WALLETCONNECT);
803
+ }).catch(console.error);
804
+ octez_connect_transport_postmessage_1.PostMessageTransport.getAvailableExtensions()
805
+ .then((extensions) => __awaiter(this, void 0, void 0, function* () {
806
+ this.analytics.track('event', 'DAppClient', 'Extensions detected', { extensions });
807
+ }))
808
+ .catch((error) => {
809
+ this._initPromise = undefined;
810
+ console.error(error);
544
811
  });
545
- this.events
546
- .emit(events_1.BeaconEvent.PAIR_SUCCESS, peer)
547
- .catch((emitError) => console.warn(emitError));
548
- this.setActivePeer(peer).catch(console.error);
549
- this.setTransport(this.p2pTransport).catch(console.error);
550
- stopListening();
551
- resolve(octez_connect_types_1.TransportType.P2P);
552
- })
553
- .catch(console.error);
554
- walletConnectTransport
555
- .listenForNewPeer((peer) => {
556
- logger.log('init', 'walletconnect transport peer connected', peer);
557
- this.analytics.track('event', 'DAppClient', 'WalletConnect Wallet connected', {
558
- peerName: peer.name
812
+ const abortHandler = () => __awaiter(this, void 0, void 0, function* () {
813
+ var _a;
814
+ logger.log('init', 'ABORTED');
815
+ this.sendMetrics('performance-metrics/save', yield this.buildPayload('connect', 'abort'));
816
+ yield Promise.all([
817
+ postMessageTransport.disconnect(),
818
+ // p2pTransport.disconnect(), do not abort connection manually
819
+ walletConnectTransport === null || walletConnectTransport === void 0 ? void 0 : walletConnectTransport.disconnect()
820
+ ]);
821
+ this.postMessageTransport = this.walletConnectTransport = this.p2pTransport = undefined;
822
+ this._activeAccount.isResolved() && this.clearActiveAccount();
823
+ // Reject _initPromise so any awaiter (makeRequest -> requestPermissions)
824
+ // unwinds via handleRequestError instead of hanging. _initReject
825
+ // also clears _initPromise as a side effect.
826
+ (_a = this._initReject) === null || _a === void 0 ? void 0 : _a.call(this, this.createAbortedError());
559
827
  });
828
+ const serializer = new octez_connect_core_1.Serializer();
829
+ const p2pPeerInfo = new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
830
+ try {
831
+ yield p2pTransport.connect();
832
+ }
833
+ catch (err) {
834
+ logger.error(err);
835
+ yield this.hideUI(['alert']); // hide pairing alert
836
+ setTimeout(() => this.events.emit(events_1.BeaconEvent.GENERIC_ERROR, err.message), 1000);
837
+ abortHandler().catch((abortErr) => logger.warn('init', 'abortHandler', abortErr));
838
+ resolve('');
839
+ return;
840
+ }
841
+ resolve(yield serializer.serialize(yield p2pTransport.getPairingRequestInfo()));
842
+ }));
843
+ const walletConnectPeerInfo = walletConnectTransport
844
+ ? createLazyPromise(() => walletConnectTransport
845
+ .getPairingRequestInfo()
846
+ .then((pairingRequestInfo) => pairingRequestInfo.uri)
847
+ .catch((error) => {
848
+ logger.warn('init', 'walletconnect pairing request failed', error);
849
+ return '';
850
+ }))
851
+ : undefined;
852
+ const postmessagePeerInfo = new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
853
+ resolve(yield serializer.serialize(yield postMessageTransport.getPairingRequestInfo()));
854
+ }));
560
855
  this.events
561
- .emit(events_1.BeaconEvent.PAIR_SUCCESS, peer)
856
+ .emit(events_1.BeaconEvent.PAIR_INIT, Object.assign(Object.assign({ p2pPeerInfo,
857
+ postmessagePeerInfo }, (walletConnectPeerInfo ? { walletConnectPeerInfo } : {})), { networkType: this.network.type, abortedHandler: abortHandler.bind(this), disclaimerText: this.disclaimerText, analytics: this.analytics, featuredWallets: this.featuredWallets, substratePairing }))
562
858
  .catch((emitError) => console.warn(emitError));
563
- this.setActivePeer(peer).catch(console.error);
564
- this.setTransport(this.walletConnectTransport).catch(console.error);
565
- stopListening();
566
- resolve(octez_connect_types_1.TransportType.WALLETCONNECT);
567
- })
568
- .catch(console.error);
569
- octez_connect_transport_postmessage_1.PostMessageTransport.getAvailableExtensions()
570
- .then((extensions) => __awaiter(this, void 0, void 0, function* () {
571
- this.analytics.track('event', 'DAppClient', 'Extensions detected', { extensions });
572
- }))
573
- .catch((error) => {
574
- this._initPromise = undefined;
575
- console.error(error);
576
- });
577
- const abortHandler = () => __awaiter(this, void 0, void 0, function* () {
578
- logger.log('init', 'ABORTED');
579
- this.sendMetrics('performance-metrics/save', yield this.buildPayload('connect', 'abort'));
580
- yield Promise.all([
581
- postMessageTransport.disconnect(),
582
- // p2pTransport.disconnect(), do not abort connection manually
583
- walletConnectTransport.disconnect()
584
- ]);
585
- this.postMessageTransport = this.walletConnectTransport = this.p2pTransport = undefined;
586
- this._activeAccount.isResolved() && this.clearActiveAccount();
587
- this._initPromise = undefined;
588
- });
589
- const serializer = new octez_connect_core_1.Serializer();
590
- const p2pPeerInfo = new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
591
- try {
592
- yield p2pTransport.connect();
593
- }
594
- catch (err) {
595
- logger.error(err);
596
- yield this.hideUI(['alert']); // hide pairing alert
597
- setTimeout(() => this.events.emit(events_1.BeaconEvent.GENERIC_ERROR, err.message), 1000);
598
- abortHandler();
599
- resolve('');
600
- return;
601
- }
602
- resolve(yield serializer.serialize(yield p2pTransport.getPairingRequestInfo()));
603
- }));
604
- const walletConnectPeerInfo = new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
605
- resolve((yield walletConnectTransport.getPairingRequestInfo()).uri);
606
- }));
607
- const postmessagePeerInfo = new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
608
- resolve(yield serializer.serialize(yield postMessageTransport.getPairingRequestInfo()));
609
- }));
610
- this.events
611
- .emit(events_1.BeaconEvent.PAIR_INIT, {
612
- p2pPeerInfo,
613
- postmessagePeerInfo,
614
- walletConnectPeerInfo,
615
- networkType: this.network.type,
616
- abortedHandler: abortHandler.bind(this),
617
- disclaimerText: this.disclaimerText,
618
- analytics: this.analytics,
619
- featuredWallets: this.featuredWallets,
620
- substratePairing
621
- })
622
- .catch((emitError) => console.warn(emitError));
859
+ }
623
860
  }
624
861
  }
862
+ catch (err) {
863
+ // An async-executor throw would otherwise be silently swallowed,
864
+ // leaving _initPromise pending and every awaiter stranded.
865
+ reject(err);
866
+ }
625
867
  }));
868
+ // Drop the _initReject reference whether init resolved or rejected, so the
869
+ // field is never stale. Using an explicit helper keeps the dangling
870
+ // observer chain out of statement position (which the lint rules forbid).
871
+ this.clearInitRejectOnSettle(this._initPromise);
626
872
  return this._initPromise;
627
873
  });
628
874
  }
875
+ /**
876
+ * Attach a settle observer to {@link _initPromise} that drops
877
+ * {@link _initReject} once the promise settles (resolved or rejected).
878
+ * Errors on the observer chain are swallowed; only the original
879
+ * {@link _initPromise} surfaces them to its awaiters.
880
+ */
881
+ clearInitRejectOnSettle(initPromise) {
882
+ initPromise
883
+ .finally(() => {
884
+ this._initReject = undefined;
885
+ this._initSubstratePairing = undefined;
886
+ })
887
+ .catch(() => {
888
+ // observer-only; original promise's rejection is delivered to awaiters
889
+ });
890
+ }
629
891
  /**
630
892
  * Returns the active account
631
893
  */
632
894
  getActiveAccount() {
633
895
  return __awaiter(this, void 0, void 0, function* () {
896
+ this.assertNotDestroyed();
634
897
  return this._activeAccount.promise;
635
898
  });
636
899
  }
900
+ retainStorageValidationPromise() {
901
+ // Constructor validation is intentionally fire-and-forget, but retaining
902
+ // the promise gives tests a deterministic hook without changing public API.
903
+ this.storageValidated.catch((err) => logger.error(err.message));
904
+ }
905
+ deactivateInvalidAccountState(reason) {
906
+ return __awaiter(this, void 0, void 0, function* () {
907
+ if (this.hasEmittedInvalidAccountDeactivated) {
908
+ return;
909
+ }
910
+ this.hasEmittedInvalidAccountDeactivated = true;
911
+ logger.log('deactivateInvalidAccountState', reason);
912
+ if (reason === 'missing_active_account') {
913
+ yield this.repairMissingActiveAccount();
914
+ }
915
+ else {
916
+ yield this.resetInvalidState(false);
917
+ }
918
+ yield this.events.emit(events_1.BeaconEvent.INVALID_ACCOUNT_DEACTIVATED, { reason });
919
+ });
920
+ }
921
+ repairMissingActiveAccount() {
922
+ return __awaiter(this, void 0, void 0, function* () {
923
+ this._activeAccount = octez_connect_utils_1.ExposedPromise.resolve(undefined);
924
+ yield this.storage.delete(octez_connect_types_1.StorageKey.ACTIVE_ACCOUNT);
925
+ yield this.setActivePeer(undefined);
926
+ });
927
+ }
928
+ hasStoredActiveAccountPointer() {
929
+ // LocalStorage-specific repair: that backend is where literal sentinel strings were produced.
930
+ if (typeof localStorage === 'undefined') {
931
+ return false;
932
+ }
933
+ try {
934
+ return localStorage.getItem(this.storage.getPrefixedKey(octez_connect_types_1.StorageKey.ACTIVE_ACCOUNT)) !== null;
935
+ }
936
+ catch (_a) {
937
+ return false;
938
+ }
939
+ }
637
940
  isInvalidState(account) {
638
941
  return __awaiter(this, void 0, void 0, function* () {
639
942
  const activeAccount = yield this._activeAccount.promise;
@@ -645,11 +948,15 @@ class DAppClient extends octez_connect_core_1.Client {
645
948
  resetInvalidState() {
646
949
  return __awaiter(this, arguments, void 0, function* (emit = true) {
647
950
  var _a, _b;
648
- this.accountManager.removeAllAccounts();
951
+ yield this.accountManager.removeAllAccounts();
649
952
  this._activeAccount = octez_connect_utils_1.ExposedPromise.resolve(undefined);
650
- this.storage.set(octez_connect_types_1.StorageKey.ACTIVE_ACCOUNT, undefined);
651
- emit && this.events.emit(events_1.BeaconEvent.INVALID_ACTIVE_ACCOUNT_STATE);
652
- !emit && this.hideUI(['alert']);
953
+ yield this.storage.delete(octez_connect_types_1.StorageKey.ACTIVE_ACCOUNT);
954
+ if (emit) {
955
+ yield this.events.emit(events_1.BeaconEvent.INVALID_ACTIVE_ACCOUNT_STATE);
956
+ }
957
+ else {
958
+ yield this.hideUI(['alert']);
959
+ }
653
960
  yield Promise.all([
654
961
  (_a = this.postMessageTransport) === null || _a === void 0 ? void 0 : _a.disconnect(),
655
962
  (_b = this.walletConnectTransport) === null || _b === void 0 ? void 0 : _b.disconnect()
@@ -668,7 +975,8 @@ class DAppClient extends octez_connect_core_1.Client {
668
975
  setActiveAccount(account) {
669
976
  return __awaiter(this, void 0, void 0, function* () {
670
977
  var _a;
671
- if (!this.isGetActiveAccountHandled) {
978
+ const activeAccountAlreadyCleared = !account && (yield this.isActiveAccountAlreadyCleared());
979
+ if (!activeAccountAlreadyCleared && !this.isGetActiveAccountHandled) {
672
980
  console.warn(`An active account has been received, but no active subscription was found for BeaconEvent.ACTIVE_ACCOUNT_SET.`);
673
981
  }
674
982
  if (account && this._activeAccount.isSettled() && (yield this.isInvalidState(account))) {
@@ -680,9 +988,9 @@ class DAppClient extends octez_connect_core_1.Client {
680
988
  }
681
989
  // when I'm resetting the activeAccount
682
990
  if (!account && this._activeAccount.isResolved() && (yield this.getActiveAccount())) {
683
- const transport = yield this.transport;
991
+ const transport = this._transport.isResolved() ? yield this.transport : undefined;
684
992
  const activeAccount = yield this.getActiveAccount();
685
- if (!transport || !activeAccount) {
993
+ if (!activeAccount) {
686
994
  return;
687
995
  }
688
996
  if (!this.debounceSetActiveAccount && transport instanceof octez_connect_transport_walletconnect_1.WalletConnectTransport) {
@@ -698,22 +1006,15 @@ class DAppClient extends octez_connect_core_1.Client {
698
1006
  type: 'DISCONNECT'
699
1007
  });
700
1008
  }
701
- Array.from(this.openRequests.entries())
702
- .filter(([id, _promise]) => id !== 'session_update')
703
- .forEach(([id, promise]) => {
704
- promise.reject({
705
- type: octez_connect_types_1.BeaconMessageType.Error,
706
- errorType: octez_connect_types_1.BeaconErrorType.ABORTED_ERROR,
707
- id,
708
- senderId: '',
709
- version: '2'
710
- });
711
- });
712
- this.openRequests.clear();
1009
+ this.abortOpenRequests();
713
1010
  this.debounceSetActiveAccount = false;
714
1011
  }
715
1012
  }
716
- if (this._activeAccount.isSettled()) {
1013
+ if (activeAccountAlreadyCleared) {
1014
+ // Keep peer and transport cleanup below idempotent, but do not write storage or emit
1015
+ // another ACTIVE_ACCOUNT_SET(undefined) for an already-empty account.
1016
+ }
1017
+ else if (this._activeAccount.isSettled()) {
717
1018
  // If the promise has already been resolved we need to create a new one.
718
1019
  this._activeAccount = octez_connect_utils_1.ExposedPromise.resolve(account);
719
1020
  }
@@ -754,11 +1055,42 @@ class DAppClient extends octez_connect_core_1.Client {
754
1055
  yield this.setActivePeer(undefined);
755
1056
  yield this.setTransport(undefined);
756
1057
  }
757
- yield this.storage.set(octez_connect_types_1.StorageKey.ACTIVE_ACCOUNT, account ? account.accountIdentifier : undefined);
758
- yield this.events.emit(events_1.BeaconEvent.ACTIVE_ACCOUNT_SET, account);
1058
+ if (account) {
1059
+ yield this.storage.set(octez_connect_types_1.StorageKey.ACTIVE_ACCOUNT, account.accountIdentifier);
1060
+ }
1061
+ else if (!activeAccountAlreadyCleared) {
1062
+ yield this.storage.delete(octez_connect_types_1.StorageKey.ACTIVE_ACCOUNT);
1063
+ }
1064
+ if (!activeAccountAlreadyCleared) {
1065
+ yield this.events.emit(events_1.BeaconEvent.ACTIVE_ACCOUNT_SET, account);
1066
+ }
759
1067
  return;
760
1068
  });
761
1069
  }
1070
+ isActiveAccountAlreadyCleared() {
1071
+ return __awaiter(this, void 0, void 0, function* () {
1072
+ return this._activeAccount.isResolved() && !(yield this.getActiveAccount());
1073
+ });
1074
+ }
1075
+ recoverActiveAccountFromAccountsChange() {
1076
+ return __awaiter(this, void 0, void 0, function* () {
1077
+ const activeAccountIdentifier = yield this.storage.get(octez_connect_types_1.StorageKey.ACTIVE_ACCOUNT);
1078
+ if (!activeAccountIdentifier || activeAccountIdentifier === 'undefined') {
1079
+ return;
1080
+ }
1081
+ if (this._activeAccount.isResolved()) {
1082
+ const activeAccount = yield this.getActiveAccount();
1083
+ if ((activeAccount === null || activeAccount === void 0 ? void 0 : activeAccount.accountIdentifier) === activeAccountIdentifier) {
1084
+ return;
1085
+ }
1086
+ }
1087
+ const account = yield this.getAccount(activeAccountIdentifier);
1088
+ if (!account) {
1089
+ return;
1090
+ }
1091
+ yield this.setActiveAccount(account);
1092
+ });
1093
+ }
762
1094
  /**
763
1095
  * Clear the active account
764
1096
  */
@@ -848,32 +1180,16 @@ class DAppClient extends octez_connect_core_1.Client {
848
1180
  };
849
1181
  });
850
1182
  }
851
- updateMetricsStorage(payload) {
852
- return __awaiter(this, void 0, void 0, function* () {
853
- const queue = yield this.beaconIDB.getAllKeys('metrics');
854
- if (queue.length >= 1000) {
855
- const key = queue.shift();
856
- this.beaconIDB.delete(key.toString(), 'metrics');
857
- }
858
- this.beaconIDB.set(String(Date.now()), payload, 'metrics');
859
- });
860
- }
861
- sendMetrics(uri, options, thenHandler, catchHandler) {
862
- if (!this.enableMetrics && uri === 'performance-metrics/save') {
863
- options && this.updateMetricsStorage(options.body);
864
- }
865
- if (!this.enableMetrics) {
866
- return;
867
- }
868
- fetch(`${octez_connect_core_1.BACKEND_URL}/${uri}`, options)
869
- .then((res) => thenHandler && thenHandler(res))
870
- .catch((err) => {
871
- console.warn('Network error encountered. Metrics sharing have been automatically disabled.');
872
- logger.error(err.message);
873
- this.enableMetrics = false; // in the event of a network error, stop sending metrics
874
- catchHandler && catchHandler(err);
875
- });
1183
+ // Metrics phone home to a Papers-controlled endpoint that the ECAD fork
1184
+ // does not control or ingest. The disabled path also queued payloads into
1185
+ // IndexedDB before the DB was ready, surfacing as unhandledRejection.
1186
+ // Hard-disabled here; full removal (option, storage key, IDB store,
1187
+ // BACKEND_URL constant, all call sites) tracked as a follow-up.
1188
+ /* 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. */
1189
+ sendMetrics(_uri, _options, _thenHandler, _catchHandler) {
1190
+ return;
876
1191
  }
1192
+ /* eslint-enable @typescript-eslint/no-unused-vars */
877
1193
  checkMakeRequest() {
878
1194
  return __awaiter(this, void 0, void 0, function* () {
879
1195
  const isResolved = this._transport.isResolved();
@@ -933,14 +1249,19 @@ class DAppClient extends octez_connect_core_1.Client {
933
1249
  * Remove all peers and all accounts that have been connected through those peers
934
1250
  */
935
1251
  removeAllPeers() {
936
- return __awaiter(this, arguments, void 0, function* (sendDisconnectToPeers = false) {
937
- const transport = yield this.transport;
1252
+ return __awaiter(this, arguments, void 0, function* (sendDisconnectToPeers = false, options = {}) {
1253
+ const transport = (yield this.transport);
938
1254
  const peers = yield transport.getPeers();
939
- const removePeerResult = transport.removeAllPeers();
1255
+ const removePeerResult = yield transport.removeAllPeers();
940
1256
  yield this.removeAccountsForPeers(peers);
941
1257
  if (sendDisconnectToPeers) {
942
1258
  const disconnectPromises = peers.map((peer) => this.sendDisconnectToPeer(peer, transport));
943
- yield Promise.all(disconnectPromises);
1259
+ if (options.ignoreDisconnectErrors) {
1260
+ yield Promise.allSettled(disconnectPromises);
1261
+ }
1262
+ else {
1263
+ yield Promise.all(disconnectPromises);
1264
+ }
944
1265
  }
945
1266
  return removePeerResult;
946
1267
  });
@@ -1046,13 +1367,15 @@ class DAppClient extends octez_connect_core_1.Client {
1046
1367
  this.sendMetrics('performance-metrics/save', yield this.buildPayload('connect', 'start'));
1047
1368
  const logId = `makeRequestV3 ${Date.now()}`;
1048
1369
  logger.time(true, logId);
1049
- const { message: response, connectionInfo } = yield this.makeRequestV3(request).catch((requestError) => __awaiter(this, void 0, void 0, function* () {
1050
- requestError.errorType === octez_connect_types_1.BeaconErrorType.ABORTED_ERROR
1051
- ? this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'abort'))
1052
- : this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'error'));
1053
- logger.time(false, logId);
1054
- throw yield this.handleRequestError(request, requestError);
1055
- }));
1370
+ let resolved;
1371
+ try {
1372
+ resolved = yield this.makeRequestV3(request);
1373
+ }
1374
+ catch (requestError) {
1375
+ yield this.runRequestErrorSideEffects(request, requestError, logId);
1376
+ throw requestError;
1377
+ }
1378
+ const { message: response, connectionInfo } = resolved;
1056
1379
  logger.time(false, logId);
1057
1380
  this.sendMetrics('performance-metrics/save', yield this.buildPayload('connect', 'start'));
1058
1381
  logger.log('RESPONSE V3', response, connectionInfo);
@@ -1114,14 +1437,18 @@ class DAppClient extends octez_connect_core_1.Client {
1114
1437
  const res = (yield this.checkMakeRequest())
1115
1438
  ? this.makeRequestV3(request)
1116
1439
  : this.makeRequestBC(request);
1117
- res.catch((requestError) => __awaiter(this, void 0, void 0, function* () {
1118
- requestError.errorType === octez_connect_types_1.BeaconErrorType.ABORTED_ERROR
1119
- ? this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'abort'))
1120
- : this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'error'));
1121
- logger.time(false, logId);
1122
- throw yield this.handleRequestError(request, requestError);
1123
- }));
1124
- const { message: response, connectionInfo } = (yield res);
1440
+ let resolved;
1441
+ try {
1442
+ resolved = yield res;
1443
+ }
1444
+ catch (requestError) {
1445
+ yield this.runRequestErrorSideEffects(request, requestError, logId);
1446
+ throw requestError;
1447
+ }
1448
+ if (!resolved) {
1449
+ throw new Error('Internal error: makeRequest returned no result');
1450
+ }
1451
+ const { message: response, connectionInfo } = resolved;
1125
1452
  logger.time(false, logId);
1126
1453
  this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'success'));
1127
1454
  yield blockchain.handleResponse({
@@ -1147,16 +1474,36 @@ class DAppClient extends octez_connect_core_1.Client {
1147
1474
  */
1148
1475
  requestPermissions(input) {
1149
1476
  return __awaiter(this, void 0, void 0, function* () {
1150
- if (input === null || input === void 0 ? void 0 : input.network) {
1151
- throw new Error('[BEACON] the "network" property is no longer accepted in input. Please provide it when instantiating DAppClient.');
1477
+ this.assertNotDestroyed();
1478
+ const scopes = this.getPermissionRequestScopes(input);
1479
+ const requestPermissionsKey = this.getPermissionRequestKey(scopes);
1480
+ if (this._requestPermissionsPromise) {
1481
+ if (this._requestPermissionsKey !== requestPermissionsKey) {
1482
+ throw new Error('Cannot start a permission request with different scopes while another permission request is pending');
1483
+ }
1484
+ return this._requestPermissionsPromise;
1485
+ }
1486
+ const requestPermissionsPromise = this.requestPermissionsInternal(scopes);
1487
+ this._requestPermissionsPromise = requestPermissionsPromise;
1488
+ this._requestPermissionsKey = requestPermissionsKey;
1489
+ try {
1490
+ return yield requestPermissionsPromise;
1491
+ }
1492
+ finally {
1493
+ if (this._requestPermissionsPromise === requestPermissionsPromise) {
1494
+ this._requestPermissionsPromise = undefined;
1495
+ this._requestPermissionsKey = undefined;
1496
+ }
1152
1497
  }
1498
+ });
1499
+ }
1500
+ requestPermissionsInternal(scopes) {
1501
+ return __awaiter(this, void 0, void 0, function* () {
1153
1502
  const request = {
1154
1503
  appMetadata: yield this.getOwnAppMetadata(),
1155
1504
  type: octez_connect_types_1.BeaconMessageType.PermissionRequest,
1156
1505
  network: this.network,
1157
- scopes: input && input.scopes
1158
- ? input.scopes
1159
- : [octez_connect_types_1.PermissionScope.OPERATION_REQUEST, octez_connect_types_1.PermissionScope.SIGN]
1506
+ scopes
1160
1507
  };
1161
1508
  this.analytics.track('event', 'DAppClient', 'Permission requested');
1162
1509
  this.sendMetrics('performance-metrics/save', yield this.buildPayload('connect', 'start'));
@@ -1165,14 +1512,18 @@ class DAppClient extends octez_connect_core_1.Client {
1165
1512
  const res = (yield this.checkMakeRequest()) || !(yield this.getActiveAccount())
1166
1513
  ? this.makeRequest(request, undefined, undefined)
1167
1514
  : this.makeRequestBC(request);
1168
- res.catch((requestError) => __awaiter(this, void 0, void 0, function* () {
1169
- requestError.errorType === octez_connect_types_1.BeaconErrorType.ABORTED_ERROR
1170
- ? this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'abort'))
1171
- : this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'error'));
1172
- logger.time(false, logId);
1173
- throw yield this.handleRequestError(request, requestError);
1174
- }));
1175
- const { message, connectionInfo } = (yield res);
1515
+ let resolved;
1516
+ try {
1517
+ resolved = yield res;
1518
+ }
1519
+ catch (requestError) {
1520
+ yield this.runRequestErrorSideEffects(request, requestError, logId);
1521
+ throw requestError;
1522
+ }
1523
+ if (!resolved) {
1524
+ throw new Error('Internal error: makeRequest returned no result');
1525
+ }
1526
+ const { message, connectionInfo } = resolved;
1176
1527
  logger.time(false, logId);
1177
1528
  this.sendMetrics('performance-metrics/save', yield this.buildPayload('connect', 'success'));
1178
1529
  logger.log('requestPermissions', '######## MESSAGE #######');
@@ -1195,6 +1546,17 @@ class DAppClient extends octez_connect_core_1.Client {
1195
1546
  return output;
1196
1547
  });
1197
1548
  }
1549
+ getPermissionRequestScopes(input) {
1550
+ if (input && 'network' in input) {
1551
+ throw new Error('[BEACON] the "network" property is no longer accepted in input. Please provide it when instantiating DAppClient.');
1552
+ }
1553
+ return input && input.scopes
1554
+ ? input.scopes
1555
+ : [octez_connect_types_1.PermissionScope.OPERATION_REQUEST, octez_connect_types_1.PermissionScope.SIGN];
1556
+ }
1557
+ getPermissionRequestKey(scopes) {
1558
+ return [...scopes].sort().join('|');
1559
+ }
1198
1560
  /**
1199
1561
  * Send a proof of event request to the wallet. The wallet will either accept or decline the challenge.
1200
1562
  * 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.
@@ -1222,14 +1584,18 @@ class DAppClient extends octez_connect_core_1.Client {
1222
1584
  const res = (yield this.checkMakeRequest())
1223
1585
  ? this.makeRequest(request)
1224
1586
  : this.makeRequestBC(request);
1225
- res.catch((requestError) => __awaiter(this, void 0, void 0, function* () {
1226
- requestError.errorType === octez_connect_types_1.BeaconErrorType.ABORTED_ERROR
1227
- ? this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'abort'))
1228
- : this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'error'));
1229
- logger.time(false, logId);
1230
- throw yield this.handleRequestError(request, requestError);
1231
- }));
1232
- const { message, connectionInfo } = (yield res);
1587
+ let resolved;
1588
+ try {
1589
+ resolved = yield res;
1590
+ }
1591
+ catch (requestError) {
1592
+ yield this.runRequestErrorSideEffects(request, requestError, logId);
1593
+ throw requestError;
1594
+ }
1595
+ if (!resolved) {
1596
+ throw new Error('Internal error: makeRequest returned no result');
1597
+ }
1598
+ const { message, connectionInfo } = resolved;
1233
1599
  logger.time(false, logId);
1234
1600
  this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'success'));
1235
1601
  this.analytics.track('event', 'DAppClient', `Proof of event challenge ${message.isAccepted ? 'accepted' : 'refused'}`, { address: activeAccount.address });
@@ -1266,14 +1632,18 @@ class DAppClient extends octez_connect_core_1.Client {
1266
1632
  const res = (yield this.checkMakeRequest())
1267
1633
  ? this.makeRequest(request)
1268
1634
  : this.makeRequestBC(request);
1269
- res.catch((requestError) => __awaiter(this, void 0, void 0, function* () {
1270
- requestError.errorType === octez_connect_types_1.BeaconErrorType.ABORTED_ERROR
1271
- ? this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'abort'))
1272
- : this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'error'));
1273
- logger.time(false, logId);
1274
- throw yield this.handleRequestError(request, requestError);
1275
- }));
1276
- const { message, connectionInfo } = (yield res);
1635
+ let resolved;
1636
+ try {
1637
+ resolved = yield res;
1638
+ }
1639
+ catch (requestError) {
1640
+ yield this.runRequestErrorSideEffects(request, requestError, logId);
1641
+ throw requestError;
1642
+ }
1643
+ if (!resolved) {
1644
+ throw new Error('Internal error: makeRequest returned no result');
1645
+ }
1646
+ const { message, connectionInfo } = resolved;
1277
1647
  logger.time(false, logId);
1278
1648
  this.analytics.track('event', 'DAppClient', `Simulated proof of event challenge ${!message.errorMessage ? 'accepted' : 'refused'}`, { address: activeAccount.address });
1279
1649
  yield this.notifySuccess(request, {
@@ -1335,14 +1705,18 @@ class DAppClient extends octez_connect_core_1.Client {
1335
1705
  const res = (yield this.checkMakeRequest())
1336
1706
  ? this.makeRequest(request)
1337
1707
  : this.makeRequestBC(request);
1338
- res.catch((requestError) => __awaiter(this, void 0, void 0, function* () {
1339
- requestError.errorType === octez_connect_types_1.BeaconErrorType.ABORTED_ERROR
1340
- ? this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'abort'))
1341
- : this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'error'));
1342
- logger.time(false, logId);
1343
- throw yield this.handleRequestError(request, requestError);
1344
- }));
1345
- const { message, connectionInfo } = (yield res);
1708
+ let resolved;
1709
+ try {
1710
+ resolved = yield res;
1711
+ }
1712
+ catch (requestError) {
1713
+ yield this.runRequestErrorSideEffects(request, requestError, logId);
1714
+ throw requestError;
1715
+ }
1716
+ if (!resolved) {
1717
+ throw new Error('Internal error: makeRequest returned no result');
1718
+ }
1719
+ const { message, connectionInfo } = resolved;
1346
1720
  logger.time(false, logId);
1347
1721
  this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'success'));
1348
1722
  yield this.notifySuccess(request, {
@@ -1432,14 +1806,18 @@ class DAppClient extends octez_connect_core_1.Client {
1432
1806
  const res = (yield this.checkMakeRequest())
1433
1807
  ? this.makeRequest(request)
1434
1808
  : this.makeRequestBC(request);
1435
- res.catch((requestError) => __awaiter(this, void 0, void 0, function* () {
1436
- requestError.errorType === octez_connect_types_1.BeaconErrorType.ABORTED_ERROR
1437
- ? this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'abort'))
1438
- : this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'error'));
1439
- logger.time(false, logId);
1440
- throw yield this.handleRequestError(request, requestError);
1441
- }));
1442
- const { message, connectionInfo } = (yield res);
1809
+ let resolved;
1810
+ try {
1811
+ resolved = yield res;
1812
+ }
1813
+ catch (requestError) {
1814
+ yield this.runRequestErrorSideEffects(request, requestError, logId);
1815
+ throw requestError;
1816
+ }
1817
+ if (!resolved) {
1818
+ throw new Error('Internal error: makeRequest returned no result');
1819
+ }
1820
+ const { message, connectionInfo } = resolved;
1443
1821
  logger.time(false, logId);
1444
1822
  this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'success'));
1445
1823
  yield this.notifySuccess(request, {
@@ -1482,14 +1860,18 @@ class DAppClient extends octez_connect_core_1.Client {
1482
1860
  const res = (yield this.checkMakeRequest())
1483
1861
  ? this.makeRequest(request)
1484
1862
  : this.makeRequestBC(request);
1485
- res.catch((requestError) => __awaiter(this, void 0, void 0, function* () {
1486
- requestError.errorType === octez_connect_types_1.BeaconErrorType.ABORTED_ERROR
1487
- ? this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'abort'))
1488
- : this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'error'));
1489
- logger.time(false, logId);
1490
- throw yield this.handleRequestError(request, requestError);
1491
- }));
1492
- const { message, connectionInfo } = (yield res);
1863
+ let resolved;
1864
+ try {
1865
+ resolved = yield res;
1866
+ }
1867
+ catch (requestError) {
1868
+ yield this.runRequestErrorSideEffects(request, requestError, logId);
1869
+ throw requestError;
1870
+ }
1871
+ if (!resolved) {
1872
+ throw new Error('Internal error: makeRequest returned no result');
1873
+ }
1874
+ const { message, connectionInfo } = resolved;
1493
1875
  logger.time(false, logId);
1494
1876
  this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'success'));
1495
1877
  yield this.notifySuccess(request, {
@@ -1563,13 +1945,17 @@ class DAppClient extends octez_connect_core_1.Client {
1563
1945
  */
1564
1946
  removeAccountsForPeers(peersToRemove) {
1565
1947
  return __awaiter(this, void 0, void 0, function* () {
1948
+ if (peersToRemove.length === 0) {
1949
+ return;
1950
+ }
1566
1951
  const peerIdsToRemove = peersToRemove.map((peer) => peer.senderId);
1567
1952
  return this.removeAccountsForPeerIds(peerIdsToRemove);
1568
1953
  });
1569
1954
  }
1570
1955
  removeAccountsForPeerIds(peerIds) {
1571
1956
  return __awaiter(this, void 0, void 0, function* () {
1572
- const accounts = yield this.accountManager.getAccounts();
1957
+ var _a;
1958
+ const accounts = (_a = (yield this.accountManager.getAccounts())) !== null && _a !== void 0 ? _a : [];
1573
1959
  // Remove all accounts with origin of the specified peer
1574
1960
  const accountsToRemove = accounts.filter((account) => peerIds.includes(account.senderId));
1575
1961
  const accountIdentifiersToRemove = accountsToRemove.map((accountInfo) => accountInfo.accountIdentifier);
@@ -1583,6 +1969,46 @@ class DAppClient extends octez_connect_core_1.Client {
1583
1969
  }
1584
1970
  });
1585
1971
  }
1972
+ /**
1973
+ * Run the standard request-error side effects (abort/error metric,
1974
+ * timer stop, {@link handleRequestError}) on a single awaited control
1975
+ * flow so callers can re-throw the original {@link ErrorResponse} without
1976
+ * leaking an UnhandledPromiseRejection.
1977
+ *
1978
+ * Why this exists: every request method historically used a fire-and-
1979
+ * forget `res.catch(handler)` whose handler did `throw await
1980
+ * this.handleRequestError(...)`. The catch returned a detached promise
1981
+ * nobody awaited; `handleRequestError`'s thrown wrapped error became an
1982
+ * unhandled rejection. The bug rarely surfaced before the dapp-init
1983
+ * promise was made rejectable, because pre-pairing rejection paths used
1984
+ * to hang instead of routing through these handlers. Once init started
1985
+ * rejecting reliably, the unhandled rejection started firing on every
1986
+ * WC session-proposal rejection. We swallow the wrapped throw here so
1987
+ * callers can re-throw the original `ErrorResponse`, matching the
1988
+ * post-pairing rejection contract at the openRequest path.
1989
+ *
1990
+ * @param request The request we sent
1991
+ * @param requestError The error we received
1992
+ * @param logId The {@link logger.time} label opened for this request
1993
+ */
1994
+ runRequestErrorSideEffects(request, rawError, logId) {
1995
+ return __awaiter(this, void 0, void 0, function* () {
1996
+ // V3 request shapes (PermissionRequestV3, BlockchainRequestV3) flow through
1997
+ // here too. They aren't part of BeaconRequestInputMessage, but
1998
+ // handleRequestError only inspects `.type` plus optional fields it already
1999
+ // guards on. Catch params are `unknown` under strict TS, same story.
2000
+ const typedRequest = request;
2001
+ const requestError = rawError;
2002
+ if (requestError.errorType === octez_connect_types_1.BeaconErrorType.ABORTED_ERROR) {
2003
+ this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'abort'));
2004
+ }
2005
+ else {
2006
+ this.sendMetrics('performance-metrics/save', yield this.buildPayload('message', 'error'));
2007
+ }
2008
+ logger.time(false, logId);
2009
+ yield this.handleRequestError(typedRequest, requestError).catch(() => undefined);
2010
+ });
2011
+ }
1586
2012
  /**
1587
2013
  * This message handles errors that we receive from the wallet.
1588
2014
  *
@@ -1765,20 +2191,10 @@ class DAppClient extends octez_connect_core_1.Client {
1765
2191
  }
1766
2192
  makeRequest(requestInput, skipResponse, otherTabMessageId) {
1767
2193
  return __awaiter(this, void 0, void 0, function* () {
1768
- var _a, _b, _c;
2194
+ var _a;
1769
2195
  const messageId = otherTabMessageId !== null && otherTabMessageId !== void 0 ? otherTabMessageId : (yield (0, octez_connect_utils_1.generateGUID)());
1770
- if (this._initPromise && this.isInitPending) {
1771
- yield Promise.all([
1772
- (_a = this.postMessageTransport) === null || _a === void 0 ? void 0 : _a.disconnect(),
1773
- (_b = this.walletConnectTransport) === null || _b === void 0 ? void 0 : _b.disconnect()
1774
- ]);
1775
- this._initPromise = undefined;
1776
- this.hideUI(['toast']);
1777
- }
1778
2196
  logger.log('makeRequest', 'starting');
1779
- this.isInitPending = true;
1780
2197
  yield this.init();
1781
- this.isInitPending = false;
1782
2198
  logger.log('makeRequest', 'after init');
1783
2199
  if (yield this.addRequestAndCheckIfRateLimited()) {
1784
2200
  this.events
@@ -1799,20 +2215,34 @@ class DAppClient extends octez_connect_core_1.Client {
1799
2215
  exposed = new octez_connect_utils_1.ExposedPromise();
1800
2216
  this.addOpenRequest(request.id, exposed);
1801
2217
  }
1802
- const payload = yield new octez_connect_core_1.Serializer().serialize(request);
1803
- const account = yield this.getActiveAccount();
1804
- const peer = yield this.getPeer(account);
1805
- const walletInfo = yield this.getWalletInfo(peer, account);
1806
- logger.log('makeRequest', 'sending message', request);
1807
2218
  try {
1808
- ;
1809
- (yield this.transport).send(payload, peer);
2219
+ const payload = yield new octez_connect_core_1.Serializer().serialize(request);
2220
+ const account = yield this.getActiveAccount();
2221
+ const peer = yield this.getPeer(account);
2222
+ const walletInfo = yield this.getWalletInfo(peer, account);
2223
+ logger.log('makeRequest', 'sending message', request);
2224
+ yield (yield this.transport).send(payload, peer);
1810
2225
  if (request.type !== octez_connect_types_1.BeaconMessageType.PermissionRequest ||
1811
2226
  (this._activeAccount.isResolved() && (yield this._activeAccount.promise))) {
1812
2227
  this.tryToAppSwitch();
1813
2228
  }
2229
+ if (!otherTabMessageId) {
2230
+ this.events
2231
+ .emit(beacon_message_events_1.messageEvents[requestInput.type].sent, {
2232
+ walletInfo: Object.assign(Object.assign({}, walletInfo), { name: (_a = walletInfo.name) !== null && _a !== void 0 ? _a : 'Wallet' }),
2233
+ extraInfo: {
2234
+ resetCallback: () => __awaiter(this, void 0, void 0, function* () {
2235
+ yield this.disconnect();
2236
+ })
2237
+ }
2238
+ })
2239
+ .catch((emitError) => logger.warn('makeRequest', emitError));
2240
+ }
1814
2241
  }
1815
2242
  catch (sendError) {
2243
+ if (!skipResponse) {
2244
+ this.clearOpenRequest(request.id);
2245
+ }
1816
2246
  this.events.emit(events_1.BeaconEvent.INTERNAL_ERROR, {
1817
2247
  text: 'Unable to send message. If this problem persists, please reset the connection and pair your wallet again.',
1818
2248
  buttons: [
@@ -1827,18 +2257,6 @@ class DAppClient extends octez_connect_core_1.Client {
1827
2257
  });
1828
2258
  throw sendError;
1829
2259
  }
1830
- if (!otherTabMessageId) {
1831
- this.events
1832
- .emit(beacon_message_events_1.messageEvents[requestInput.type].sent, {
1833
- walletInfo: Object.assign(Object.assign({}, walletInfo), { name: (_c = walletInfo.name) !== null && _c !== void 0 ? _c : 'Wallet' }),
1834
- extraInfo: {
1835
- resetCallback: () => __awaiter(this, void 0, void 0, function* () {
1836
- this.disconnect();
1837
- })
1838
- }
1839
- })
1840
- .catch((emitError) => console.warn(emitError));
1841
- }
1842
2260
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1843
2261
  return exposed === null || exposed === void 0 ? void 0 : exposed.promise; // TODO: fix type
1844
2262
  });
@@ -1853,20 +2271,10 @@ class DAppClient extends octez_connect_core_1.Client {
1853
2271
  */
1854
2272
  makeRequestV3(requestInput, otherTabMessageId) {
1855
2273
  return __awaiter(this, void 0, void 0, function* () {
1856
- var _a, _b, _c;
1857
- if (this._initPromise && this.isInitPending) {
1858
- yield Promise.all([
1859
- (_a = this.postMessageTransport) === null || _a === void 0 ? void 0 : _a.disconnect(),
1860
- (_b = this.walletConnectTransport) === null || _b === void 0 ? void 0 : _b.disconnect()
1861
- ]);
1862
- this._initPromise = undefined;
1863
- this.hideUI(['toast']);
1864
- }
2274
+ var _a;
1865
2275
  const messageId = otherTabMessageId !== null && otherTabMessageId !== void 0 ? otherTabMessageId : (yield (0, octez_connect_utils_1.generateGUID)());
1866
2276
  logger.log('makeRequest', 'starting');
1867
- this.isInitPending = true;
1868
2277
  yield this.init(undefined, true);
1869
- this.isInitPending = false;
1870
2278
  logger.log('makeRequest', 'after init');
1871
2279
  if (yield this.addRequestAndCheckIfRateLimited()) {
1872
2280
  this.events
@@ -1885,20 +2293,31 @@ class DAppClient extends octez_connect_core_1.Client {
1885
2293
  };
1886
2294
  const exposed = new octez_connect_utils_1.ExposedPromise();
1887
2295
  this.addOpenRequest(request.id, exposed);
1888
- const payload = yield new octez_connect_core_1.Serializer().serialize(request);
1889
- const account = yield this.getActiveAccount();
1890
- const peer = yield this.getPeer(account);
1891
- const walletInfo = yield this.getWalletInfo(peer, account);
1892
- logger.log('makeRequest', 'sending message', request);
1893
2296
  try {
1894
- ;
1895
- (yield this.transport).send(payload, peer);
2297
+ const payload = yield new octez_connect_core_1.Serializer().serialize(request);
2298
+ const account = yield this.getActiveAccount();
2299
+ const peer = yield this.getPeer(account);
2300
+ const walletInfo = yield this.getWalletInfo(peer, account);
2301
+ logger.log('makeRequest', 'sending message', request);
2302
+ yield (yield this.transport).send(payload, peer);
1896
2303
  if (request.message.type !== octez_connect_types_1.BeaconMessageType.PermissionRequest ||
1897
2304
  (this._activeAccount.isResolved() && (yield this._activeAccount.promise))) {
1898
2305
  this.tryToAppSwitch();
1899
2306
  }
2307
+ const index = requestInput.type;
2308
+ this.events
2309
+ .emit(beacon_message_events_1.messageEvents[index].sent, {
2310
+ walletInfo: Object.assign(Object.assign({}, walletInfo), { name: (_a = walletInfo.name) !== null && _a !== void 0 ? _a : 'Wallet' }),
2311
+ extraInfo: {
2312
+ resetCallback: () => __awaiter(this, void 0, void 0, function* () {
2313
+ yield this.disconnect();
2314
+ })
2315
+ }
2316
+ })
2317
+ .catch((emitError) => logger.warn('makeRequestV3', emitError));
1900
2318
  }
1901
2319
  catch (sendError) {
2320
+ this.clearOpenRequest(request.id);
1902
2321
  this.events.emit(events_1.BeaconEvent.INTERNAL_ERROR, {
1903
2322
  text: 'Unable to send message. If this problem persists, please reset the connection and pair your wallet again.',
1904
2323
  buttons: [
@@ -1913,17 +2332,6 @@ class DAppClient extends octez_connect_core_1.Client {
1913
2332
  });
1914
2333
  throw sendError;
1915
2334
  }
1916
- const index = requestInput.type;
1917
- this.events
1918
- .emit(beacon_message_events_1.messageEvents[index].sent, {
1919
- walletInfo: Object.assign(Object.assign({}, walletInfo), { name: (_c = walletInfo.name) !== null && _c !== void 0 ? _c : 'Wallet' }),
1920
- extraInfo: {
1921
- resetCallback: () => __awaiter(this, void 0, void 0, function* () {
1922
- this.disconnect();
1923
- })
1924
- }
1925
- })
1926
- .catch((emitError) => console.warn(emitError));
1927
2335
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1928
2336
  return exposed.promise; // TODO: fix type
1929
2337
  });
@@ -1966,27 +2374,149 @@ class DAppClient extends octez_connect_core_1.Client {
1966
2374
  return exposed.promise;
1967
2375
  });
1968
2376
  }
2377
+ /**
2378
+ * Disconnects all resolved transports (postMessage, P2P, WalletConnect), removes their peers,
2379
+ * and clears active account state. After calling, the DAppClient remains usable for a new
2380
+ * requestPermissions().
2381
+ */
1969
2382
  disconnect() {
2383
+ return __awaiter(this, arguments, void 0, function* (options = {}) {
2384
+ const { notifyPeers = true } = options;
2385
+ if (!this._disconnectPromise) {
2386
+ this._disconnectNotifyPeers = false;
2387
+ }
2388
+ this._disconnectNotifyPeers = this._disconnectNotifyPeers || notifyPeers;
2389
+ if (this._disconnectPromise) {
2390
+ return this._disconnectPromise;
2391
+ }
2392
+ this._disconnectPromise = (() => __awaiter(this, void 0, void 0, function* () { return this.disconnectInternal(); }))().finally(() => {
2393
+ this._disconnectPromise = undefined;
2394
+ this._disconnectNotifyPeers = false;
2395
+ });
2396
+ return this._disconnectPromise;
2397
+ });
2398
+ }
2399
+ disconnectInternal() {
1970
2400
  return __awaiter(this, void 0, void 0, function* () {
2401
+ this.abortPendingInit();
1971
2402
  if (!this._transport.isResolved()) {
1972
- throw new Error('No transport available.');
2403
+ const initializedTransports = this.getInitializedTransports();
2404
+ this.abortOpenRequests();
2405
+ yield this.removeAllPeersFromTransports(initializedTransports);
2406
+ yield this.clearActiveAccount();
2407
+ yield this.disconnectResolvedTransports(initializedTransports);
2408
+ this.clearInternalTransportReferences();
2409
+ return;
1973
2410
  }
1974
- const transport = yield this.transport;
2411
+ const transport = (yield this.transport);
1975
2412
  if (transport.connectionStatus === octez_connect_types_1.TransportStatus.NOT_CONNECTED) {
1976
- throw new Error('Not connected.');
2413
+ yield this.clearActiveAccount();
2414
+ this.abortOpenRequests();
2415
+ return;
1977
2416
  }
1978
2417
  yield this.createStateSnapshot();
1979
2418
  this.sendMetrics('performance-metrics/save', yield this.buildPayload('disconnect', 'start'));
2419
+ this.abortOpenRequests();
2420
+ const transports = this.getResolvedTransports(transport);
2421
+ yield this.removeAllPeersFromTransports(transports);
1980
2422
  yield this.clearActiveAccount();
1981
- if (!(transport instanceof octez_connect_transport_walletconnect_1.WalletConnectTransport)) {
1982
- yield transport.disconnect();
1983
- }
1984
- this.postMessageTransport = undefined;
1985
- this.p2pTransport = undefined;
1986
- this.walletConnectTransport = undefined;
2423
+ yield this.disconnectResolvedTransports(transports);
2424
+ this.clearInternalTransportReferences();
1987
2425
  this.sendMetrics('performance-metrics/save', yield this.buildPayload('disconnect', 'success'));
1988
2426
  });
1989
2427
  }
2428
+ getInitializedTransports() {
2429
+ const transports = [];
2430
+ const addTransport = (transport) => {
2431
+ if (transport && !transports.includes(transport)) {
2432
+ transports.push(transport);
2433
+ }
2434
+ };
2435
+ addTransport(this.postMessageTransport);
2436
+ addTransport(this.p2pTransport);
2437
+ addTransport(this.walletConnectTransport);
2438
+ return transports;
2439
+ }
2440
+ getResolvedTransports(selectedTransport) {
2441
+ const transports = this.getInitializedTransports();
2442
+ const addTransport = (transport) => {
2443
+ if (transport && !transports.includes(transport)) {
2444
+ transports.push(transport);
2445
+ }
2446
+ };
2447
+ addTransport(this.postMessageTransport);
2448
+ addTransport(this.p2pTransport);
2449
+ addTransport(this.walletConnectTransport);
2450
+ addTransport(selectedTransport);
2451
+ return transports;
2452
+ }
2453
+ clearInternalTransportReferences() {
2454
+ this.postMessageTransport = undefined;
2455
+ this.p2pTransport = undefined;
2456
+ this.walletConnectTransport = undefined;
2457
+ }
2458
+ removeAllPeersFromTransports(transports) {
2459
+ return __awaiter(this, void 0, void 0, function* () {
2460
+ const peersByTransport = yield Promise.all(transports.map((transport) => __awaiter(this, void 0, void 0, function* () {
2461
+ const transportPeers = yield transport.getPeers();
2462
+ return {
2463
+ transport,
2464
+ peers: transportPeers
2465
+ };
2466
+ })));
2467
+ yield Promise.all(peersByTransport.map(({ transport }) => transport.removeAllPeers()));
2468
+ const peersToRemove = peersByTransport.flatMap(({ peers: transportPeers }) => transportPeers);
2469
+ yield this.removeAccountsForPeers(peersToRemove);
2470
+ // If cleanup already removed peers, a late notify upgrade has nothing left to notify.
2471
+ const sendDisconnectToPeers = this._disconnectNotifyPeers;
2472
+ if (!sendDisconnectToPeers) {
2473
+ return;
2474
+ }
2475
+ yield Promise.allSettled(this.getUniquePeerNotifications(peersByTransport).map(({ transport, peer }) => this.sendDisconnectToPeer(peer, transport)));
2476
+ });
2477
+ }
2478
+ getUniquePeerNotifications(peersByTransport) {
2479
+ const notifications = [];
2480
+ const seen = new Set();
2481
+ peersByTransport.forEach(({ transport, peers: transportPeers }) => {
2482
+ transportPeers.forEach((peer) => {
2483
+ const key = this.getPeerNotificationKey(transport, peer);
2484
+ if (seen.has(key)) {
2485
+ return;
2486
+ }
2487
+ seen.add(key);
2488
+ notifications.push({ transport, peer });
2489
+ });
2490
+ });
2491
+ return notifications;
2492
+ }
2493
+ getPeerNotificationKey(transport, peer) {
2494
+ return [transport.type, peer.publicKey, peer.senderId, peer.id].filter(Boolean).join(':');
2495
+ }
2496
+ disconnectResolvedTransports(transports) {
2497
+ return __awaiter(this, void 0, void 0, function* () {
2498
+ const results = yield Promise.allSettled(transports.map((transport) => __awaiter(this, void 0, void 0, function* () {
2499
+ if (this.isTransportConnected(transport)) {
2500
+ yield transport.disconnect();
2501
+ }
2502
+ if (this.hasClientCleanup(transport)) {
2503
+ yield transport.doClientCleanup();
2504
+ }
2505
+ })));
2506
+ results.forEach((result) => {
2507
+ if (result.status === 'rejected') {
2508
+ logger.warn('disconnectResolvedTransports', result.reason);
2509
+ }
2510
+ });
2511
+ });
2512
+ }
2513
+ hasClientCleanup(transport) {
2514
+ const transportWithClientCleanup = transport;
2515
+ return typeof transportWithClientCleanup.doClientCleanup === 'function';
2516
+ }
2517
+ isTransportConnected(transport) {
2518
+ return transport.connectionStatus !== octez_connect_types_1.TransportStatus.NOT_CONNECTED;
2519
+ }
1990
2520
  /**
1991
2521
  * Adds a requests to the "openRequests" set so we know what messages have already been answered/handled.
1992
2522
  *
@@ -1995,7 +2525,64 @@ class DAppClient extends octez_connect_core_1.Client {
1995
2525
  */
1996
2526
  addOpenRequest(id, promise) {
1997
2527
  logger.log('addOpenRequest', this.name, `adding request ${id} and waiting for answer`);
2528
+ this.clearOpenRequestTimeout(id);
2529
+ this.openRequests.delete(id);
1998
2530
  this.openRequests.set(id, promise);
2531
+ this.addOpenRequestTimeout(id, promise);
2532
+ }
2533
+ addOpenRequestTimeout(id, promise) {
2534
+ if (id === SESSION_UPDATE_REQUEST_ID) {
2535
+ return;
2536
+ }
2537
+ if (!Number.isFinite(this.requestTimeoutMs) || this.requestTimeoutMs <= 0) {
2538
+ return;
2539
+ }
2540
+ const timeout = setTimeout(() => {
2541
+ if (this.openRequests.get(id) !== promise) {
2542
+ return;
2543
+ }
2544
+ const errorResponse = {
2545
+ id,
2546
+ type: octez_connect_types_1.BeaconMessageType.Error,
2547
+ errorType: octez_connect_types_1.BeaconErrorType.PEER_UNREACHABLE,
2548
+ senderId: '',
2549
+ version: '2'
2550
+ };
2551
+ promise.reject(errorResponse);
2552
+ this.clearOpenRequest(id);
2553
+ this.events.emit(events_1.BeaconEvent.CHANNEL_CLOSED).catch((emitError) => {
2554
+ logger.error('addOpenRequestTimeout', emitError);
2555
+ });
2556
+ }, this.requestTimeoutMs);
2557
+ this.openRequestTimeouts.set(id, timeout);
2558
+ }
2559
+ clearOpenRequest(id) {
2560
+ this.clearOpenRequestTimeout(id);
2561
+ this.openRequests.delete(id);
2562
+ this.openRequestsOtherTabs.delete(id);
2563
+ }
2564
+ clearOpenRequestTimeout(id) {
2565
+ const timeout = this.openRequestTimeouts.get(id);
2566
+ if (timeout) {
2567
+ clearTimeout(timeout);
2568
+ this.openRequestTimeouts.delete(id);
2569
+ }
2570
+ }
2571
+ clearAllOpenRequestTimeouts() {
2572
+ this.openRequestTimeouts.forEach((timeout) => clearTimeout(timeout));
2573
+ this.openRequestTimeouts.clear();
2574
+ }
2575
+ abortOpenRequests() {
2576
+ Array.from(this.openRequests.entries())
2577
+ .filter(([id]) => id !== SESSION_UPDATE_REQUEST_ID)
2578
+ .forEach(([id, promise]) => {
2579
+ promise.reject(this.createAbortedError(id));
2580
+ this.clearOpenRequest(id);
2581
+ });
2582
+ this.openRequests.clear();
2583
+ this.openRequestsOtherTabs.clear();
2584
+ this.acknowledgedRequests.clear();
2585
+ this.clearAllOpenRequestTimeouts();
1999
2586
  }
2000
2587
  sendNotificationWithAccessToken(notification) {
2001
2588
  return __awaiter(this, void 0, void 0, function* () {
@@ -2018,21 +2605,28 @@ class DAppClient extends octez_connect_core_1.Client {
2018
2605
  const signature = yield (0, octez_connect_utils_1.signMessage)(payloadBytes, {
2019
2606
  secretKey: Buffer.from(keypair.secretKey)
2020
2607
  });
2021
- const notificationResponse = yield axios_1.default.post(`${url}/send`, {
2022
- recipient,
2023
- title,
2024
- body,
2025
- timestamp,
2026
- payload,
2027
- accessToken,
2028
- protocolIdentifier,
2029
- sender: {
2030
- name: this.name,
2031
- publicKey,
2032
- signature
2033
- }
2608
+ const notificationResponse = yield fetch(`${url}/send`, {
2609
+ method: 'POST',
2610
+ headers: { 'Content-Type': 'application/json' },
2611
+ body: JSON.stringify({
2612
+ recipient,
2613
+ title,
2614
+ body,
2615
+ timestamp,
2616
+ payload,
2617
+ accessToken,
2618
+ protocolIdentifier,
2619
+ sender: {
2620
+ name: this.name,
2621
+ publicKey,
2622
+ signature
2623
+ }
2624
+ })
2034
2625
  });
2035
- return notificationResponse.data;
2626
+ if (!notificationResponse.ok) {
2627
+ throw new Error(`sendNotification failed: ${notificationResponse.status} ${notificationResponse.statusText}`);
2628
+ }
2629
+ return notificationResponse.json();
2036
2630
  });
2037
2631
  }
2038
2632
  onNewAccount(message, connectionInfo) {
@@ -2040,7 +2634,7 @@ class DAppClient extends octez_connect_core_1.Client {
2040
2634
  var _a, _b, _c;
2041
2635
  // TODO: Migration code. Remove sometime after 1.0.0 release.
2042
2636
  const tempPK = message.publicKey || message.pubkey || message.pubKey;
2043
- const publicKey = !!tempPK ? (0, octez_connect_utils_1.prefixPublicKey)(tempPK) : undefined;
2637
+ const publicKey = tempPK ? (0, octez_connect_utils_1.prefixPublicKey)(tempPK) : undefined;
2044
2638
  if (!publicKey && !message.address) {
2045
2639
  throw new Error('PublicKey or Address must be defined');
2046
2640
  }