@tezos-x/octez.connect-dapp 0.9.0

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 (83) hide show
  1. package/LICENCE +19 -0
  2. package/README.md +16 -0
  3. package/dist/cjs/beacon-message-events.d.ts +9 -0
  4. package/dist/cjs/beacon-message-events.js +109 -0
  5. package/dist/cjs/beacon-message-events.js.map +1 -0
  6. package/dist/cjs/dapp-client/DAppClient.d.ts +282 -0
  7. package/dist/cjs/dapp-client/DAppClient.js +2073 -0
  8. package/dist/cjs/dapp-client/DAppClient.js.map +1 -0
  9. package/dist/cjs/dapp-client/DAppClientOptions.d.ts +117 -0
  10. package/dist/cjs/dapp-client/DAppClientOptions.js +3 -0
  11. package/dist/cjs/dapp-client/DAppClientOptions.js.map +1 -0
  12. package/dist/cjs/events.d.ts +209 -0
  13. package/dist/cjs/events.js +718 -0
  14. package/dist/cjs/events.js.map +1 -0
  15. package/dist/cjs/index.d.ts +15 -0
  16. package/dist/cjs/index.js +37 -0
  17. package/dist/cjs/index.js.map +1 -0
  18. package/dist/cjs/transports/DappP2PTransport.d.ts +14 -0
  19. package/dist/cjs/transports/DappP2PTransport.js +53 -0
  20. package/dist/cjs/transports/DappP2PTransport.js.map +1 -0
  21. package/dist/cjs/transports/DappPostMessageTransport.d.ts +14 -0
  22. package/dist/cjs/transports/DappPostMessageTransport.js +53 -0
  23. package/dist/cjs/transports/DappPostMessageTransport.js.map +1 -0
  24. package/dist/cjs/transports/DappWalletConnectTransport.d.ts +17 -0
  25. package/dist/cjs/transports/DappWalletConnectTransport.js +52 -0
  26. package/dist/cjs/transports/DappWalletConnectTransport.js.map +1 -0
  27. package/dist/cjs/utils/available-transports.d.ts +4 -0
  28. package/dist/cjs/utils/available-transports.js +12 -0
  29. package/dist/cjs/utils/available-transports.js.map +1 -0
  30. package/dist/cjs/utils/block-explorer.d.ts +24 -0
  31. package/dist/cjs/utils/block-explorer.js +24 -0
  32. package/dist/cjs/utils/block-explorer.js.map +1 -0
  33. package/dist/cjs/utils/get-instance.d.ts +4 -0
  34. package/dist/cjs/utils/get-instance.js +21 -0
  35. package/dist/cjs/utils/get-instance.js.map +1 -0
  36. package/dist/cjs/utils/shorten-string.d.ts +1 -0
  37. package/dist/cjs/utils/shorten-string.js +11 -0
  38. package/dist/cjs/utils/shorten-string.js.map +1 -0
  39. package/dist/cjs/utils/tzkt-blockexplorer.d.ts +12 -0
  40. package/dist/cjs/utils/tzkt-blockexplorer.js +59 -0
  41. package/dist/cjs/utils/tzkt-blockexplorer.js.map +1 -0
  42. package/dist/esm/beacon-message-events.d.ts +9 -0
  43. package/dist/esm/beacon-message-events.js +106 -0
  44. package/dist/esm/beacon-message-events.js.map +1 -0
  45. package/dist/esm/dapp-client/DAppClient.d.ts +282 -0
  46. package/dist/esm/dapp-client/DAppClient.js +2001 -0
  47. package/dist/esm/dapp-client/DAppClient.js.map +1 -0
  48. package/dist/esm/dapp-client/DAppClientOptions.d.ts +117 -0
  49. package/dist/esm/dapp-client/DAppClientOptions.js +2 -0
  50. package/dist/esm/dapp-client/DAppClientOptions.js.map +1 -0
  51. package/dist/esm/events.d.ts +209 -0
  52. package/dist/esm/events.js +702 -0
  53. package/dist/esm/events.js.map +1 -0
  54. package/dist/esm/index.d.ts +15 -0
  55. package/dist/esm/index.js +17 -0
  56. package/dist/esm/index.js.map +1 -0
  57. package/dist/esm/transports/DappP2PTransport.d.ts +14 -0
  58. package/dist/esm/transports/DappP2PTransport.js +34 -0
  59. package/dist/esm/transports/DappP2PTransport.js.map +1 -0
  60. package/dist/esm/transports/DappPostMessageTransport.d.ts +14 -0
  61. package/dist/esm/transports/DappPostMessageTransport.js +34 -0
  62. package/dist/esm/transports/DappPostMessageTransport.js.map +1 -0
  63. package/dist/esm/transports/DappWalletConnectTransport.d.ts +17 -0
  64. package/dist/esm/transports/DappWalletConnectTransport.js +35 -0
  65. package/dist/esm/transports/DappWalletConnectTransport.js.map +1 -0
  66. package/dist/esm/utils/available-transports.d.ts +4 -0
  67. package/dist/esm/utils/available-transports.js +9 -0
  68. package/dist/esm/utils/available-transports.js.map +1 -0
  69. package/dist/esm/utils/block-explorer.d.ts +24 -0
  70. package/dist/esm/utils/block-explorer.js +10 -0
  71. package/dist/esm/utils/block-explorer.js.map +1 -0
  72. package/dist/esm/utils/get-instance.d.ts +4 -0
  73. package/dist/esm/utils/get-instance.js +17 -0
  74. package/dist/esm/utils/get-instance.js.map +1 -0
  75. package/dist/esm/utils/shorten-string.d.ts +1 -0
  76. package/dist/esm/utils/shorten-string.js +7 -0
  77. package/dist/esm/utils/shorten-string.js.map +1 -0
  78. package/dist/esm/utils/tzkt-blockexplorer.d.ts +12 -0
  79. package/dist/esm/utils/tzkt-blockexplorer.js +43 -0
  80. package/dist/esm/utils/tzkt-blockexplorer.js.map +1 -0
  81. package/dist/octez.connect.dapp.min.js +1066 -0
  82. package/dist/walletbeacon.dapp.min.js +1066 -0
  83. package/package.json +45 -0
@@ -0,0 +1,2001 @@
1
+ import axios from 'axios';
2
+ import bs58check from 'bs58check';
3
+ import { BeaconEvent } from '../events';
4
+ import { TransportType, StorageKey, BeaconMessageType, PermissionScope, NetworkType, Origin, BeaconErrorType, SigningType, ColorMode, TransportStatus
5
+ // PermissionRequestV3
6
+ // RequestEncryptPayloadInput,
7
+ // EncryptPayloadResponseOutput,
8
+ // EncryptPayloadResponse,
9
+ // EncryptPayloadRequest
10
+ } from '@tezos-x/octez.connect-types';
11
+ import { Client, AppMetadataManager, Serializer, LocalStorage, getAccountIdentifier, getSenderId, Logger, StorageValidator, SDK_VERSION, IndexedDBStorage, MultiTabChannel, BACKEND_URL, getError } from '@tezos-x/octez.connect-core';
12
+ import { getAddressFromPublicKey, ExposedPromise, generateGUID, toHex, signMessage, CONTRACT_PREFIX, prefixPublicKey, isValidAddress, getKeypairFromSeed } from '@tezos-x/octez.connect-utils';
13
+ import { messageEvents } from '../beacon-message-events';
14
+ import { TzktBlockExplorer } from '../utils/tzkt-blockexplorer';
15
+ import { BeaconEventHandler } from '@tezos-x/octez.connect-dapp';
16
+ import { DappPostMessageTransport } from '../transports/DappPostMessageTransport';
17
+ import { DappP2PTransport } from '../transports/DappP2PTransport';
18
+ import { DappWalletConnectTransport } from '../transports/DappWalletConnectTransport';
19
+ import { PostMessageTransport } from '@tezos-x/octez.connect-transport-postmessage';
20
+ import { closeToast, getColorMode, setColorMode, setDesktopList, setExtensionList, setWebList, setiOSList, getiOSList, getDesktopList, getExtensionList, getWebList, isBrowser, isDesktop, isMobileOS, isIOS, currentOS } from '@tezos-x/octez.connect-ui';
21
+ import { WalletConnectTransport } from '@tezos-x/octez.connect-transport-walletconnect';
22
+ const logger = new Logger('DAppClient');
23
+ /**
24
+ * @publicapi
25
+ *
26
+ * The DAppClient has to be used in decentralized applications. It handles all the logic related to connecting to beacon-compatible
27
+ * wallets and sending requests.
28
+ *
29
+ * @category DApp
30
+ */
31
+ export class DAppClient extends Client {
32
+ /**
33
+ * The description of the app
34
+ */
35
+ description;
36
+ /**
37
+ * The block explorer used by the SDK
38
+ */
39
+ blockExplorer;
40
+ /**
41
+ * Automatically switch between apps on Mobile Devices (Enabled by Default)
42
+ */
43
+ enableAppSwitching;
44
+ /**
45
+ * Enable metrics tracking (Disabled by Default)
46
+ */
47
+ enableMetrics;
48
+ userId;
49
+ network;
50
+ events = new BeaconEventHandler();
51
+ postMessageTransport;
52
+ p2pTransport;
53
+ walletConnectTransport;
54
+ wcProjectId;
55
+ wcRelayUrl;
56
+ isGetActiveAccountHandled = false;
57
+ openRequestsOtherTabs = new Set();
58
+ /**
59
+ * A map of requests that are currently "open", meaning we have sent them to a wallet and are still awaiting a response.
60
+ */
61
+ openRequests = new Map();
62
+ /**
63
+ * The currently active account. For all requests that are associated to a specific request (operation request, signing request),
64
+ * the active account is used to determine the network and destination wallet
65
+ */
66
+ _activeAccount = new ExposedPromise();
67
+ /**
68
+ * The currently active peer. This is used to address a peer in case the active account is not set. (Eg. for permission requests)
69
+ */
70
+ _activePeer = new ExposedPromise();
71
+ _initPromise;
72
+ isInitPending = false;
73
+ activeAccountLoaded;
74
+ appMetadataManager;
75
+ disclaimerText;
76
+ errorMessages;
77
+ featuredWallets;
78
+ storageValidator;
79
+ beaconIDB = new IndexedDBStorage('beacon', ['bug_report', 'metrics']);
80
+ debounceSetActiveAccount = false;
81
+ multiTabChannel = new MultiTabChannel('octez.connect-sdk-channel', this.onBCMessageHandler.bind(this), this.onElectedLeaderhandler.bind(this));
82
+ constructor(config) {
83
+ super({
84
+ storage: config && config.storage ? config.storage : new LocalStorage(),
85
+ ...config
86
+ });
87
+ this.description = config.description;
88
+ this.wcProjectId = config.walletConnectOptions?.projectId || '24469fd0a06df227b6e5f7dc7de0ff4f';
89
+ this.wcRelayUrl = config.walletConnectOptions?.relayUrl;
90
+ this.featuredWallets = config.featuredWallets;
91
+ this.events = new BeaconEventHandler(config.eventHandlers, config.disableDefaultEvents ?? false);
92
+ this.blockExplorer = config.blockExplorer ?? new TzktBlockExplorer();
93
+ this.network = config.network ?? { type: config.preferredNetwork ?? NetworkType.MAINNET };
94
+ setColorMode(config.colorMode ?? ColorMode.LIGHT);
95
+ this.disclaimerText = config.disclaimerText;
96
+ this.errorMessages = config.errorMessages ?? {};
97
+ this.appMetadataManager = new AppMetadataManager(this.storage);
98
+ this.storageValidator = new StorageValidator(this.storage);
99
+ this.enableAppSwitching =
100
+ config.enableAppSwitching === undefined ? true : !!config.enableAppSwitching;
101
+ this.enableMetrics = config.enableMetrics ? true : false;
102
+ // Subscribe to storage changes and update the active account if it changes on other tabs
103
+ this.storage.subscribeToStorageChanged(async (event) => {
104
+ if (event.eventType === 'storageCleared') {
105
+ this.setActiveAccount(undefined);
106
+ return;
107
+ }
108
+ if (event.eventType === 'entryModified') {
109
+ if (event.key === this.storage.getPrefixedKey(StorageKey.ACTIVE_ACCOUNT)) {
110
+ const accountIdentifier = event.newValue;
111
+ if (!accountIdentifier || accountIdentifier === 'undefined') {
112
+ this.setActiveAccount(undefined);
113
+ }
114
+ else {
115
+ const account = await this.getAccount(accountIdentifier);
116
+ this.setActiveAccount(account);
117
+ }
118
+ return;
119
+ }
120
+ if (event.key === this.storage.getPrefixedKey(StorageKey.ENABLE_METRICS)) {
121
+ this.enableMetrics = !!(await this.storage.get(StorageKey.ENABLE_METRICS));
122
+ return;
123
+ }
124
+ if (event.key === this.storage.getPrefixedKey(StorageKey.BEACON_SDK_SECRET_SEED)) {
125
+ this._keyPair = new ExposedPromise();
126
+ this._beaconId = new ExposedPromise();
127
+ await this.initSDK();
128
+ return;
129
+ }
130
+ }
131
+ });
132
+ this.activeAccountLoaded = this.storage
133
+ .get(StorageKey.ACTIVE_ACCOUNT)
134
+ .then(async (activeAccountIdentifier) => {
135
+ if (activeAccountIdentifier) {
136
+ const account = await this.accountManager.getAccount(activeAccountIdentifier);
137
+ await this.setActiveAccount(account);
138
+ return account;
139
+ }
140
+ else {
141
+ await this.setActiveAccount(undefined);
142
+ return undefined;
143
+ }
144
+ })
145
+ .catch(async (storageError) => {
146
+ logger.error(storageError);
147
+ await this.resetInvalidState(false);
148
+ this.events.emit(BeaconEvent.INVALID_ACCOUNT_DEACTIVATED);
149
+ return undefined;
150
+ });
151
+ this.handleResponse = async (message, connectionInfo) => {
152
+ const typedMessage = message.version === '3'
153
+ ? message.message
154
+ : message;
155
+ let appMetadata = message.version === '3'
156
+ ? typedMessage.blockchainData?.appMetadata
157
+ : typedMessage.appMetadata;
158
+ if (!appMetadata && message.version === '3') {
159
+ const storedMetadata = await Promise.all([
160
+ this.storage.get(StorageKey.TRANSPORT_P2P_PEERS_DAPP),
161
+ this.storage.get(StorageKey.TRANSPORT_WALLETCONNECT_PEERS_DAPP),
162
+ this.storage.get(StorageKey.TRANSPORT_POSTMESSAGE_PEERS_DAPP)
163
+ ]);
164
+ for (const peers of storedMetadata) {
165
+ const peer = peers.find((peer) => peer.senderId === message.senderId);
166
+ if (!peer) {
167
+ continue;
168
+ }
169
+ const wallet = await this.getWalletInfo();
170
+ appMetadata = {
171
+ name: peer.name,
172
+ senderId: peer.senderId,
173
+ icon: wallet.icon
174
+ };
175
+ break;
176
+ }
177
+ }
178
+ if (this.openRequestsOtherTabs.has(message.id)) {
179
+ this.multiTabChannel.postMessage({
180
+ type: 'RESPONSE',
181
+ data: {
182
+ message,
183
+ connectionInfo
184
+ },
185
+ id: message.id
186
+ });
187
+ if (typedMessage.type !== BeaconMessageType.Acknowledge) {
188
+ this.openRequestsOtherTabs.delete(message.id);
189
+ }
190
+ return;
191
+ }
192
+ const openRequest = this.openRequests.get(message.id);
193
+ logger.log('### openRequest ###', openRequest);
194
+ logger.log('handleResponse', 'Received message', message, connectionInfo);
195
+ logger.log('### message ###', JSON.stringify(message));
196
+ logger.log('### connectionInfo ###', connectionInfo);
197
+ const handleDisconnect = async () => {
198
+ this.analytics.track('event', 'DAppClient', 'Disconnect received from Wallet');
199
+ const relevantTransport = connectionInfo.origin === Origin.P2P
200
+ ? this.p2pTransport
201
+ : connectionInfo.origin === Origin.WALLETCONNECT
202
+ ? this.walletConnectTransport
203
+ : (this.postMessageTransport ?? (await this.transport));
204
+ if (relevantTransport) {
205
+ const peers = await relevantTransport.getPeers();
206
+ const peer = peers.find((peerEl) => peerEl.senderId === message.senderId);
207
+ if (peer) {
208
+ await relevantTransport.removePeer(peer);
209
+ }
210
+ }
211
+ await this.removeAccountsForPeerIds([message.senderId]);
212
+ await this.events.emit(BeaconEvent.CHANNEL_CLOSED);
213
+ };
214
+ if (openRequest && typedMessage.type === BeaconMessageType.Acknowledge) {
215
+ this.analytics.track('event', 'DAppClient', 'Acknowledge received from Wallet');
216
+ logger.log('handleResponse', `acknowledge message received for ${message.id}`);
217
+ this.events
218
+ .emit(BeaconEvent.ACKNOWLEDGE_RECEIVED, {
219
+ message: typedMessage,
220
+ extraInfo: {},
221
+ walletInfo: await this.getWalletInfo()
222
+ })
223
+ .catch(console.error);
224
+ }
225
+ else if (openRequest) {
226
+ if (typedMessage.type === BeaconMessageType.PermissionResponse && appMetadata) {
227
+ await this.appMetadataManager.addAppMetadata(appMetadata);
228
+ }
229
+ if (typedMessage.type === BeaconMessageType.Error) {
230
+ openRequest.reject(typedMessage);
231
+ }
232
+ else {
233
+ openRequest.resolve({ message, connectionInfo });
234
+ }
235
+ this.openRequests.delete(typedMessage.id);
236
+ }
237
+ else {
238
+ if (typedMessage.type === BeaconMessageType.Disconnect) {
239
+ await handleDisconnect();
240
+ }
241
+ else if (typedMessage.type === BeaconMessageType.ChangeAccountRequest) {
242
+ await this.onNewAccount(typedMessage, connectionInfo);
243
+ }
244
+ }
245
+ if (this._transport.isResolved()) {
246
+ const transport = await this.transport;
247
+ if (transport instanceof WalletConnectTransport &&
248
+ !this.openRequests.has('session_update')) {
249
+ this.openRequests.set('session_update', new ExposedPromise());
250
+ }
251
+ }
252
+ };
253
+ this.storageValidator
254
+ .validate()
255
+ .then(async (isValid) => {
256
+ const account = await this.activeAccountLoaded;
257
+ if (!isValid) {
258
+ const info = await this.getWalletInfo(undefined, account, false);
259
+ info.type =
260
+ info.type === 'extension' && account?.origin.type === Origin.P2P ? 'mobile' : info.type;
261
+ await this.storage.set(StorageKey.LAST_SELECTED_WALLET, {
262
+ icon: info.icon ?? '',
263
+ key: info.name,
264
+ type: info.type ?? 'web',
265
+ name: info.name,
266
+ url: info.deeplink
267
+ });
268
+ const nowValid = await this.storageValidator.validate();
269
+ if (!nowValid) {
270
+ this.resetInvalidState(false);
271
+ }
272
+ }
273
+ if (account && account.origin.type !== 'p2p') {
274
+ this.init();
275
+ }
276
+ })
277
+ .catch((err) => logger.error(err.message));
278
+ this.sendMetrics('enable-metrics?' + this.addQueryParam('version', SDK_VERSION), undefined, (res) => {
279
+ if (!res.ok) {
280
+ res.status === 426
281
+ ? console.error('Metrics are no longer supported for this version, please upgrade.')
282
+ : console.warn('Network error encountered. Metrics sharing have been automatically disabled.');
283
+ }
284
+ this.enableMetrics = res.ok;
285
+ this.storage.set(StorageKey.ENABLE_METRICS, res.ok);
286
+ }, () => {
287
+ this.enableMetrics = false;
288
+ this.storage.set(StorageKey.ENABLE_METRICS, false);
289
+ });
290
+ this.initUserID().catch((err) => logger.error(err.message));
291
+ }
292
+ async checkIfBCLeaderExists() {
293
+ // broadcast channel does not work on mobile
294
+ if (isMobileOS(window)) {
295
+ return true;
296
+ }
297
+ const hasLeader = await this.multiTabChannel.hasLeader();
298
+ if (hasLeader) {
299
+ return this.multiTabChannel.isLeader();
300
+ }
301
+ await this.multiTabChannel.getLeadership();
302
+ return this.multiTabChannel.isLeader();
303
+ }
304
+ async onElectedLeaderhandler() {
305
+ if (!this._transport.isResolved()) {
306
+ return;
307
+ }
308
+ const tranport = await this.transport;
309
+ if (tranport.type !== TransportType.WALLETCONNECT) {
310
+ return;
311
+ }
312
+ if (tranport.connectionStatus === TransportStatus.CONNECTED) {
313
+ return;
314
+ }
315
+ await tranport.connect();
316
+ }
317
+ async onBCMessageHandler(message) {
318
+ switch (message.type) {
319
+ case BeaconMessageType.PermissionRequest:
320
+ case BeaconMessageType.OperationRequest:
321
+ case BeaconMessageType.SignPayloadRequest:
322
+ case BeaconMessageType.BroadcastRequest:
323
+ case BeaconMessageType.ProofOfEventChallengeRequest:
324
+ case BeaconMessageType.SimulatedProofOfEventChallengeRequest:
325
+ this.prepareRequest(message);
326
+ break;
327
+ case BeaconMessageType.BlockchainRequest:
328
+ this.prepareRequest(message, true);
329
+ break;
330
+ case 'RESPONSE':
331
+ this.handleResponse(message.data.message, message.data.connectionInfo);
332
+ break;
333
+ case 'DISCONNECT':
334
+ this._transport.isResolved() && this.disconnect();
335
+ break;
336
+ default:
337
+ logger.error('onBCMessageHandler', 'message type not recognized', message);
338
+ }
339
+ }
340
+ async prepareRequest(message, isV3 = false) {
341
+ if (!this.multiTabChannel.isLeader()) {
342
+ return;
343
+ }
344
+ // block until the transport is ready
345
+ const transport = (await this._transport.promise);
346
+ await transport.waitForResolution();
347
+ this.openRequestsOtherTabs.add(message.id);
348
+ isV3
349
+ ? this.makeRequestV3(message.data, message.id)
350
+ : this.makeRequest(message.data, false, message.id);
351
+ }
352
+ async createStateSnapshot() {
353
+ if (!localStorage || !this.enableMetrics) {
354
+ return;
355
+ }
356
+ const keys = Object.values(StorageKey).filter((key) => !key.includes('wc@2') && !key.includes('secret') && !key.includes('account'));
357
+ try {
358
+ for (const key of keys) {
359
+ await this.beaconIDB.set(key, this.storage.getPrefixedKey(key));
360
+ }
361
+ }
362
+ catch (err) {
363
+ logger.error('createStateSnapshot', err.message);
364
+ }
365
+ }
366
+ async initUserID() {
367
+ const id = await this.storage.get(StorageKey.USER_ID);
368
+ if (id) {
369
+ this.userId = id;
370
+ return;
371
+ }
372
+ this.userId = await generateGUID();
373
+ this.storage.set(StorageKey.USER_ID, this.userId);
374
+ }
375
+ async initInternalTransports() {
376
+ const seed = await this.storage.get(StorageKey.BEACON_SDK_SECRET_SEED);
377
+ if (!seed) {
378
+ throw new Error('Secret seed not found');
379
+ }
380
+ const keyPair = await getKeypairFromSeed(seed);
381
+ if (this.postMessageTransport || this.p2pTransport || this.walletConnectTransport) {
382
+ return;
383
+ }
384
+ this.postMessageTransport = new DappPostMessageTransport(this.name, keyPair, this.storage);
385
+ await this.addListener(this.postMessageTransport);
386
+ this.p2pTransport = new DappP2PTransport(this.name, keyPair, this.storage, this.matrixNodes, this.iconUrl, this.appUrl);
387
+ await this.addListener(this.p2pTransport);
388
+ const wcOptions = {
389
+ projectId: this.wcProjectId,
390
+ relayUrl: this.wcRelayUrl,
391
+ metadata: {
392
+ name: this.name,
393
+ description: this.description ?? '',
394
+ url: this.appUrl ?? '',
395
+ icons: this.iconUrl ? [this.iconUrl] : []
396
+ }
397
+ };
398
+ this.walletConnectTransport = new DappWalletConnectTransport(this.name, keyPair, this.storage, {
399
+ network: this.network.type,
400
+ opts: wcOptions
401
+ }, this.checkIfBCLeaderExists.bind(this));
402
+ this.initEvents();
403
+ await this.addListener(this.walletConnectTransport);
404
+ }
405
+ initEvents() {
406
+ if (!this.walletConnectTransport) {
407
+ return;
408
+ }
409
+ this.walletConnectTransport.setEventHandler("CLOSE_ALERT" /* ClientEvents.CLOSE_ALERT */, this.hideUI.bind(this, ['alert', 'toast']));
410
+ this.walletConnectTransport.setEventHandler("RESET_STATE" /* ClientEvents.RESET_STATE */, this.channelClosedHandler.bind(this));
411
+ this.walletConnectTransport.setEventHandler("WC_ACK_NOTIFICATION" /* ClientEvents.WC_ACK_NOTIFICATION */, this.wcToastHandler.bind(this));
412
+ this.walletConnectTransport.setEventHandler("ON_RELAYER_ERROR" /* ClientEvents.ON_RELAYER_ERROR */, this.onRelayerError.bind(this));
413
+ }
414
+ async onRelayerError() {
415
+ await this.resetInvalidState(false);
416
+ this.events.emit(BeaconEvent.RELAYER_ERROR);
417
+ }
418
+ async wcToastHandler(status) {
419
+ const walletInfo = await (async () => {
420
+ try {
421
+ return await this.getWalletInfo();
422
+ }
423
+ catch {
424
+ return { name: 'wallet' };
425
+ }
426
+ })();
427
+ await this.events.emit(BeaconEvent.HIDE_UI, ['alert']);
428
+ if (status === 'pending') {
429
+ this.events.emit(BeaconEvent.ACKNOWLEDGE_RECEIVED, {
430
+ message: {},
431
+ extraInfo: {},
432
+ walletInfo
433
+ });
434
+ }
435
+ else {
436
+ this.events.emit(BeaconEvent.PERMISSION_REQUEST_ERROR, {
437
+ errorResponse: { errorType: BeaconErrorType.ABORTED_ERROR },
438
+ walletInfo
439
+ });
440
+ }
441
+ }
442
+ async channelClosedHandler(type) {
443
+ const transport = await this.transport;
444
+ if (transport.type !== type) {
445
+ return;
446
+ }
447
+ await this.events.emit(BeaconEvent.CHANNEL_CLOSED);
448
+ this.setActiveAccount(undefined);
449
+ await this.disconnect();
450
+ }
451
+ /**
452
+ * Destroy the instance.
453
+ *
454
+ * WARNING: Call `destroy` whenever you no longer need dAppClient
455
+ * as it frees internal subscriptions to the transport and therefore the instance may no longer work properly.
456
+ * If you wish to disconnect your dApp, use `disconnect` instead.
457
+ */
458
+ async destroy() {
459
+ await this.createStateSnapshot();
460
+ await super.destroy();
461
+ }
462
+ async init(transport, substratePairing) {
463
+ if (this._initPromise) {
464
+ return this._initPromise;
465
+ }
466
+ try {
467
+ await this.activeAccountLoaded;
468
+ }
469
+ catch {
470
+ //
471
+ }
472
+ this._initPromise = new Promise(async (resolve) => {
473
+ if (transport) {
474
+ await this.addListener(transport);
475
+ resolve(await super.init(transport));
476
+ }
477
+ else if (this._transport.isSettled()) {
478
+ await (await this.transport).connect();
479
+ resolve(await super.init(await this.transport));
480
+ }
481
+ else {
482
+ const activeAccount = await this.getActiveAccount();
483
+ const stopListening = () => {
484
+ if (this.postMessageTransport) {
485
+ this.postMessageTransport.stopListeningForNewPeers().catch(console.error);
486
+ }
487
+ if (this.p2pTransport) {
488
+ this.p2pTransport.stopListeningForNewPeers().catch(console.error);
489
+ }
490
+ if (this.walletConnectTransport) {
491
+ this.walletConnectTransport.stopListeningForNewPeers().catch(console.error);
492
+ }
493
+ };
494
+ await this.initInternalTransports();
495
+ if (!this.postMessageTransport || !this.p2pTransport || !this.walletConnectTransport) {
496
+ return;
497
+ }
498
+ this.postMessageTransport.connect().then().catch(console.error);
499
+ if (activeAccount && activeAccount.origin) {
500
+ const origin = activeAccount.origin.type;
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
+ }
511
+ }
512
+ else {
513
+ const p2pTransport = this.p2pTransport;
514
+ const postMessageTransport = this.postMessageTransport;
515
+ const walletConnectTransport = this.walletConnectTransport;
516
+ postMessageTransport
517
+ .listenForNewPeer((peer) => {
518
+ logger.log('init', 'postmessage transport peer connected', peer);
519
+ this.analytics.track('event', 'DAppClient', 'Extension connected', {
520
+ peerName: peer.name
521
+ });
522
+ this.events
523
+ .emit(BeaconEvent.PAIR_SUCCESS, peer)
524
+ .catch((emitError) => console.warn(emitError));
525
+ this.setActivePeer(peer).catch(console.error);
526
+ this.setTransport(this.postMessageTransport).catch(console.error);
527
+ stopListening();
528
+ resolve(TransportType.POST_MESSAGE);
529
+ })
530
+ .catch(console.error);
531
+ p2pTransport
532
+ .listenForNewPeer((peer) => {
533
+ logger.log('init', 'p2p transport peer connected', peer);
534
+ this.analytics.track('event', 'DAppClient', 'octez.connect Wallet connected', {
535
+ peerName: peer.name
536
+ });
537
+ this.events
538
+ .emit(BeaconEvent.PAIR_SUCCESS, peer)
539
+ .catch((emitError) => console.warn(emitError));
540
+ this.setActivePeer(peer).catch(console.error);
541
+ this.setTransport(this.p2pTransport).catch(console.error);
542
+ stopListening();
543
+ resolve(TransportType.P2P);
544
+ })
545
+ .catch(console.error);
546
+ walletConnectTransport
547
+ .listenForNewPeer((peer) => {
548
+ logger.log('init', 'walletconnect transport peer connected', peer);
549
+ this.analytics.track('event', 'DAppClient', 'WalletConnect Wallet connected', {
550
+ peerName: peer.name
551
+ });
552
+ this.events
553
+ .emit(BeaconEvent.PAIR_SUCCESS, peer)
554
+ .catch((emitError) => console.warn(emitError));
555
+ this.setActivePeer(peer).catch(console.error);
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));
615
+ }
616
+ }
617
+ });
618
+ return this._initPromise;
619
+ }
620
+ /**
621
+ * Returns the active account
622
+ */
623
+ async getActiveAccount() {
624
+ return this._activeAccount.promise;
625
+ }
626
+ async isInvalidState(account) {
627
+ const activeAccount = await this._activeAccount.promise;
628
+ return !activeAccount
629
+ ? false
630
+ : activeAccount?.address !== account?.address && !this.isGetActiveAccountHandled;
631
+ }
632
+ async resetInvalidState(emit = true) {
633
+ this.accountManager.removeAllAccounts();
634
+ this._activeAccount = ExposedPromise.resolve(undefined);
635
+ this.storage.set(StorageKey.ACTIVE_ACCOUNT, undefined);
636
+ emit && this.events.emit(BeaconEvent.INVALID_ACTIVE_ACCOUNT_STATE);
637
+ !emit && this.hideUI(['alert']);
638
+ await Promise.all([
639
+ this.postMessageTransport?.disconnect(),
640
+ this.walletConnectTransport?.disconnect()
641
+ ]);
642
+ this.postMessageTransport = this.p2pTransport = this.walletConnectTransport = undefined;
643
+ await this.setActivePeer(undefined);
644
+ await this.setTransport(undefined);
645
+ this._initPromise = undefined;
646
+ }
647
+ /**
648
+ * Sets the active account
649
+ *
650
+ * @param account The account that will be set as the active account
651
+ */
652
+ async setActiveAccount(account) {
653
+ if (!this.isGetActiveAccountHandled) {
654
+ console.warn(`An active account has been received, but no active subscription was found for BeaconEvent.ACTIVE_ACCOUNT_SET.`);
655
+ }
656
+ if (account && this._activeAccount.isSettled() && (await this.isInvalidState(account))) {
657
+ const tranport = await this.transport;
658
+ if (tranport instanceof WalletConnectTransport && tranport.wasDisconnectedByWallet()) {
659
+ await this.resetInvalidState();
660
+ return;
661
+ }
662
+ }
663
+ // when I'm resetting the activeAccount
664
+ if (!account && this._activeAccount.isResolved() && (await this.getActiveAccount())) {
665
+ const transport = await this.transport;
666
+ const activeAccount = await this.getActiveAccount();
667
+ if (!transport || !activeAccount) {
668
+ return;
669
+ }
670
+ if (!this.debounceSetActiveAccount && transport instanceof WalletConnectTransport) {
671
+ this.debounceSetActiveAccount = true;
672
+ this._initPromise = undefined;
673
+ this.postMessageTransport = this.p2pTransport = this.walletConnectTransport = undefined;
674
+ if (this.multiTabChannel.isLeader() || isMobileOS(window)) {
675
+ await transport.disconnect();
676
+ this.openRequestsOtherTabs.clear();
677
+ }
678
+ else {
679
+ this.multiTabChannel.postMessage({
680
+ type: 'DISCONNECT'
681
+ });
682
+ }
683
+ Array.from(this.openRequests.entries())
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();
695
+ this.debounceSetActiveAccount = false;
696
+ }
697
+ }
698
+ if (this._activeAccount.isSettled()) {
699
+ // If the promise has already been resolved we need to create a new one.
700
+ this._activeAccount = ExposedPromise.resolve(account);
701
+ }
702
+ else {
703
+ this._activeAccount.resolve(account);
704
+ }
705
+ if (!this.isGetActiveAccountHandled && this._transport.isResolved()) {
706
+ const transport = await this.transport;
707
+ if (transport instanceof WalletConnectTransport && transport.wasDisconnectedByWallet()) {
708
+ await this.resetInvalidState();
709
+ return;
710
+ }
711
+ }
712
+ if (account) {
713
+ const origin = account.origin.type;
714
+ await this.initInternalTransports();
715
+ // Select the transport that matches the active account
716
+ if (origin === Origin.EXTENSION) {
717
+ await this.setTransport(this.postMessageTransport);
718
+ }
719
+ else if (origin === Origin.P2P) {
720
+ await this.setTransport(this.p2pTransport);
721
+ }
722
+ else if (origin === Origin.WALLETCONNECT) {
723
+ await this.setTransport(this.walletConnectTransport);
724
+ this.walletConnectTransport?.forceUpdate('INIT');
725
+ }
726
+ if (this._transport.isResolved()) {
727
+ const transport = await this.transport;
728
+ if (transport.connectionStatus === TransportStatus.NOT_CONNECTED) {
729
+ await transport.connect();
730
+ }
731
+ }
732
+ const peer = await this.getPeer(account);
733
+ await this.setActivePeer(peer);
734
+ }
735
+ else {
736
+ await this.setActivePeer(undefined);
737
+ await this.setTransport(undefined);
738
+ }
739
+ await this.storage.set(StorageKey.ACTIVE_ACCOUNT, account ? account.accountIdentifier : undefined);
740
+ await this.events.emit(BeaconEvent.ACTIVE_ACCOUNT_SET, account);
741
+ return;
742
+ }
743
+ /**
744
+ * Clear the active account
745
+ */
746
+ clearActiveAccount() {
747
+ return this.setActiveAccount();
748
+ }
749
+ async setColorMode(colorMode) {
750
+ return setColorMode(colorMode);
751
+ }
752
+ async getColorMode() {
753
+ return getColorMode();
754
+ }
755
+ /**
756
+ * @deprecated
757
+ *
758
+ * Use getOwnAppMetadata instead
759
+ */
760
+ async getAppMetadata() {
761
+ return this.getOwnAppMetadata();
762
+ }
763
+ async showPrepare() {
764
+ const walletInfo = await (async () => {
765
+ try {
766
+ return await this.getWalletInfo();
767
+ }
768
+ catch {
769
+ return undefined;
770
+ }
771
+ })();
772
+ await this.events.emit(BeaconEvent.SHOW_PREPARE, { walletInfo });
773
+ }
774
+ async hideUI(elements) {
775
+ await this.events.emit(BeaconEvent.HIDE_UI, elements);
776
+ }
777
+ async tryToAppSwitch() {
778
+ if (!isMobileOS(window) || !this.enableAppSwitching) {
779
+ return;
780
+ }
781
+ const wallet = await this.getWalletInfo();
782
+ if (wallet.type !== 'mobile' || !wallet.deeplink) {
783
+ return;
784
+ }
785
+ const link = isIOS(window) ? wallet.deeplink : `${wallet.deeplink}wc?uri=`;
786
+ if (!link?.length) {
787
+ return;
788
+ }
789
+ window.location = link;
790
+ }
791
+ addQueryParam(paramName, paramValue) {
792
+ return paramName + '=' + paramValue;
793
+ }
794
+ async buildPayload(action, status) {
795
+ const wallet = await this.storage.get(StorageKey.LAST_SELECTED_WALLET);
796
+ const transport = this._activeAccount.isResolved()
797
+ ? ((await this.getActiveAccount())?.origin.type ?? 'UNKNOWN')
798
+ : 'UNKNOWN';
799
+ return {
800
+ method: 'POST',
801
+ headers: {
802
+ 'Content-Type': 'application/json'
803
+ },
804
+ body: JSON.stringify({
805
+ userId: this.userId,
806
+ os: currentOS(),
807
+ walletName: wallet?.name ?? 'init',
808
+ walletType: wallet?.type ?? 'init',
809
+ sdkVersion: SDK_VERSION,
810
+ transport,
811
+ time: new Date(),
812
+ action,
813
+ status
814
+ })
815
+ };
816
+ }
817
+ async updateMetricsStorage(payload) {
818
+ const queue = await this.beaconIDB.getAllKeys('metrics');
819
+ if (queue.length >= 1000) {
820
+ const key = queue.shift();
821
+ this.beaconIDB.delete(key.toString(), 'metrics');
822
+ }
823
+ this.beaconIDB.set(String(Date.now()), payload, 'metrics');
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
+ });
840
+ }
841
+ async checkMakeRequest() {
842
+ const isResolved = this._transport.isResolved();
843
+ const isWCInstance = isResolved && (await this.transport) instanceof WalletConnectTransport;
844
+ await this.multiTabChannel.init();
845
+ const isLeader = this.multiTabChannel.isLeader();
846
+ return !isResolved || !isWCInstance || isLeader || isMobileOS(window);
847
+ }
848
+ /**
849
+ * Will remove the account from the local storage and set a new active account if necessary.
850
+ *
851
+ * @param accountIdentifier ID of the account
852
+ */
853
+ async removeAccount(accountIdentifier) {
854
+ const removeAccountResult = super.removeAccount(accountIdentifier);
855
+ const activeAccount = await this.getActiveAccount();
856
+ if (activeAccount && activeAccount.accountIdentifier === accountIdentifier) {
857
+ await this.setActiveAccount(undefined);
858
+ }
859
+ return removeAccountResult;
860
+ }
861
+ /**
862
+ * Remove all accounts and set active account to undefined
863
+ */
864
+ async removeAllAccounts() {
865
+ await super.removeAllAccounts();
866
+ await this.setActiveAccount(undefined);
867
+ }
868
+ /**
869
+ * Removes a peer and all the accounts that have been connected through that peer
870
+ *
871
+ * @param peer Peer to be removed
872
+ */
873
+ async removePeer(peer, sendDisconnectToPeer = false) {
874
+ const transport = await this.transport;
875
+ const removePeerResult = transport.removePeer(peer);
876
+ await this.removeAccountsForPeers([peer]);
877
+ if (sendDisconnectToPeer) {
878
+ await this.sendDisconnectToPeer(peer, transport);
879
+ }
880
+ return removePeerResult;
881
+ }
882
+ /**
883
+ * Remove all peers and all accounts that have been connected through those peers
884
+ */
885
+ async removeAllPeers(sendDisconnectToPeers = false) {
886
+ const transport = await this.transport;
887
+ const peers = await transport.getPeers();
888
+ const removePeerResult = transport.removeAllPeers();
889
+ await this.removeAccountsForPeers(peers);
890
+ if (sendDisconnectToPeers) {
891
+ const disconnectPromises = peers.map((peer) => this.sendDisconnectToPeer(peer, transport));
892
+ await Promise.all(disconnectPromises);
893
+ }
894
+ return removePeerResult;
895
+ }
896
+ /**
897
+ * Allows the user to subscribe to specific events that are fired in the SDK
898
+ *
899
+ * @param internalEvent The event to subscribe to
900
+ * @param eventCallback The callback that will be called when the event occurs
901
+ */
902
+ async subscribeToEvent(internalEvent, eventCallback) {
903
+ if (internalEvent === BeaconEvent.ACTIVE_ACCOUNT_SET) {
904
+ this.isGetActiveAccountHandled = true;
905
+ }
906
+ await this.events.on(internalEvent, eventCallback);
907
+ }
908
+ /**
909
+ * Check if we have permissions to send the specific message type to the active account.
910
+ * If no active account is set, only permission requests are allowed.
911
+ *
912
+ * @param type The type of the message
913
+ */
914
+ async checkPermissions(type) {
915
+ if ([
916
+ BeaconMessageType.PermissionRequest,
917
+ BeaconMessageType.ProofOfEventChallengeRequest,
918
+ BeaconMessageType.SimulatedProofOfEventChallengeRequest
919
+ ].includes(type)) {
920
+ return true;
921
+ }
922
+ const activeAccount = await this.getActiveAccount();
923
+ if (!activeAccount) {
924
+ throw await this.sendInternalError('No active account set!');
925
+ }
926
+ const permissions = activeAccount.scopes;
927
+ switch (type) {
928
+ case BeaconMessageType.OperationRequest:
929
+ return permissions.includes(PermissionScope.OPERATION_REQUEST);
930
+ case BeaconMessageType.SignPayloadRequest:
931
+ return permissions.includes(PermissionScope.SIGN);
932
+ // TODO: ENCRYPTION
933
+ // case BeaconMessageType.EncryptPayloadRequest:
934
+ // return permissions.includes(PermissionScope.ENCRYPT)
935
+ case BeaconMessageType.BroadcastRequest:
936
+ return true;
937
+ default:
938
+ return false;
939
+ }
940
+ }
941
+ async sendNotification(title, message, payload, protocolIdentifier) {
942
+ const activeAccount = await this.getActiveAccount();
943
+ if (!activeAccount ||
944
+ (activeAccount &&
945
+ !activeAccount.scopes.includes(PermissionScope.NOTIFICATION) &&
946
+ !activeAccount.notification)) {
947
+ throw new Error('notification permissions not given');
948
+ }
949
+ if (!activeAccount.notification?.token) {
950
+ throw new Error('No AccessToken');
951
+ }
952
+ const url = activeAccount.notification?.apiUrl;
953
+ if (!url) {
954
+ throw new Error('No Push URL set');
955
+ }
956
+ return this.sendNotificationWithAccessToken({
957
+ url,
958
+ recipient: activeAccount.address,
959
+ title,
960
+ body: message,
961
+ payload,
962
+ protocolIdentifier,
963
+ accessToken: activeAccount.notification?.token
964
+ });
965
+ }
966
+ blockchains = new Map();
967
+ addBlockchain(chain) {
968
+ this.blockchains.set(chain.identifier, chain);
969
+ chain.getWalletLists().then((walletLists) => {
970
+ setDesktopList(walletLists.desktopList);
971
+ setExtensionList(walletLists.extensionList);
972
+ setWebList(walletLists.webList);
973
+ setiOSList(walletLists.iOSList);
974
+ });
975
+ }
976
+ removeBlockchain(chainIdentifier) {
977
+ this.blockchains.delete(chainIdentifier);
978
+ }
979
+ async permissionRequest(input) {
980
+ logger.log('permissionRequest', input);
981
+ const blockchain = this.blockchains.get(input.blockchainIdentifier);
982
+ if (!blockchain) {
983
+ throw new Error(`Blockchain "${input.blockchainIdentifier}" not supported by dAppClient`);
984
+ }
985
+ const request = {
986
+ ...input,
987
+ type: BeaconMessageType.PermissionRequest,
988
+ blockchainData: {
989
+ ...input.blockchainData,
990
+ appMetadata: await this.getOwnAppMetadata()
991
+ }
992
+ };
993
+ logger.log('REQUESTION PERMIMISSION V3', 'xxx', request);
994
+ this.sendMetrics('performance-metrics/save', await this.buildPayload('connect', 'start'));
995
+ const logId = `makeRequestV3 ${Date.now()}`;
996
+ logger.time(true, logId);
997
+ const { message: response, connectionInfo } = await this.makeRequestV3(request).catch(async (requestError) => {
998
+ requestError.errorType === BeaconErrorType.ABORTED_ERROR
999
+ ? this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'abort'))
1000
+ : this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'error'));
1001
+ logger.time(false, logId);
1002
+ throw await this.handleRequestError(request, requestError);
1003
+ });
1004
+ logger.time(false, logId);
1005
+ this.sendMetrics('performance-metrics/save', await this.buildPayload('connect', 'start'));
1006
+ logger.log('RESPONSE V3', response, connectionInfo);
1007
+ const partialAccountInfos = await blockchain.getAccountInfosFromPermissionResponse(response.message);
1008
+ const accountInfo = {
1009
+ accountIdentifier: partialAccountInfos[0].accountId,
1010
+ senderId: response.senderId,
1011
+ origin: {
1012
+ type: connectionInfo.origin,
1013
+ id: connectionInfo.id
1014
+ },
1015
+ address: partialAccountInfos[0].address, // Store all addresses
1016
+ publicKey: partialAccountInfos[0].publicKey,
1017
+ scopes: response.message.blockchainData.scopes,
1018
+ connectedAt: new Date().getTime(),
1019
+ chainData: response.message.blockchainData
1020
+ };
1021
+ await this.accountManager.addAccount(accountInfo);
1022
+ await this.setActiveAccount(accountInfo);
1023
+ await blockchain.handleResponse({
1024
+ request,
1025
+ account: accountInfo,
1026
+ output: response,
1027
+ blockExplorer: this.blockExplorer,
1028
+ connectionContext: connectionInfo,
1029
+ walletInfo: await this.getWalletInfo()
1030
+ });
1031
+ await this.notifySuccess(request, {
1032
+ account: accountInfo,
1033
+ output: {
1034
+ address: partialAccountInfos[0].address,
1035
+ network: { type: 'substrate' },
1036
+ scopes: []
1037
+ },
1038
+ blockExplorer: this.blockExplorer,
1039
+ connectionContext: connectionInfo,
1040
+ walletInfo: await this.getWalletInfo()
1041
+ });
1042
+ // return output
1043
+ return response.message;
1044
+ }
1045
+ async request(input) {
1046
+ logger.log('request', input);
1047
+ const blockchain = this.blockchains.get(input.blockchainIdentifier);
1048
+ if (!blockchain) {
1049
+ throw new Error(`Blockchain "${blockchain}" not supported by dAppClient`);
1050
+ }
1051
+ await blockchain.validateRequest(input);
1052
+ const activeAccount = await this.getActiveAccount();
1053
+ if (!activeAccount) {
1054
+ throw await this.sendInternalError('No active account!');
1055
+ }
1056
+ const request = {
1057
+ ...input,
1058
+ type: BeaconMessageType.BlockchainRequest,
1059
+ accountId: activeAccount.accountIdentifier
1060
+ };
1061
+ this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'start'));
1062
+ const logId = `makeRequestV3 ${Date.now()}`;
1063
+ logger.time(true, logId);
1064
+ const res = (await this.checkMakeRequest())
1065
+ ? this.makeRequestV3(request)
1066
+ : this.makeRequestBC(request);
1067
+ res.catch(async (requestError) => {
1068
+ requestError.errorType === BeaconErrorType.ABORTED_ERROR
1069
+ ? this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'abort'))
1070
+ : this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'error'));
1071
+ logger.time(false, logId);
1072
+ throw await this.handleRequestError(request, requestError);
1073
+ });
1074
+ const { message: response, connectionInfo } = (await res);
1075
+ logger.time(false, logId);
1076
+ this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'success'));
1077
+ await blockchain.handleResponse({
1078
+ request,
1079
+ account: activeAccount,
1080
+ output: response,
1081
+ blockExplorer: this.blockExplorer,
1082
+ connectionContext: connectionInfo,
1083
+ walletInfo: await this.getWalletInfo()
1084
+ });
1085
+ await this.notifySuccess(request, {
1086
+ walletInfo: await this.getWalletInfo()
1087
+ });
1088
+ return response.message;
1089
+ }
1090
+ /**
1091
+ * Send a permission request to the DApp. This should be done as the first step. The wallet will respond
1092
+ * with an publicKey and permissions that were given. The account returned will be set as the "activeAccount"
1093
+ * and will be used for the following requests.
1094
+ *
1095
+ * @param input The message details we need to prepare the PermissionRequest message.
1096
+ */
1097
+ async requestPermissions(input) {
1098
+ if (input?.network) {
1099
+ throw new Error('[BEACON] the "network" property is no longer accepted in input. Please provide it when instantiating DAppClient.');
1100
+ }
1101
+ const request = {
1102
+ appMetadata: await this.getOwnAppMetadata(),
1103
+ type: BeaconMessageType.PermissionRequest,
1104
+ network: this.network,
1105
+ scopes: input && input.scopes
1106
+ ? input.scopes
1107
+ : [PermissionScope.OPERATION_REQUEST, PermissionScope.SIGN]
1108
+ };
1109
+ this.analytics.track('event', 'DAppClient', 'Permission requested');
1110
+ this.sendMetrics('performance-metrics/save', await this.buildPayload('connect', 'start'));
1111
+ const logId = `makeRequest ${Date.now()}`;
1112
+ logger.time(true, logId);
1113
+ const res = (await this.checkMakeRequest()) || !(await this.getActiveAccount())
1114
+ ? this.makeRequest(request, undefined, undefined)
1115
+ : this.makeRequestBC(request);
1116
+ res.catch(async (requestError) => {
1117
+ requestError.errorType === BeaconErrorType.ABORTED_ERROR
1118
+ ? this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'abort'))
1119
+ : this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'error'));
1120
+ logger.time(false, logId);
1121
+ throw await this.handleRequestError(request, requestError);
1122
+ });
1123
+ const { message, connectionInfo } = (await res);
1124
+ logger.time(false, logId);
1125
+ this.sendMetrics('performance-metrics/save', await this.buildPayload('connect', 'success'));
1126
+ logger.log('requestPermissions', '######## MESSAGE #######');
1127
+ logger.log('requestPermissions', message);
1128
+ const accountInfo = await this.onNewAccount(message, connectionInfo);
1129
+ logger.log('requestPermissions', '######## ACCOUNT INFO #######');
1130
+ logger.log('requestPermissions', JSON.stringify(accountInfo));
1131
+ await this.accountManager.addAccount(accountInfo);
1132
+ const output = {
1133
+ ...message,
1134
+ walletKey: accountInfo.walletKey,
1135
+ address: accountInfo.address,
1136
+ accountInfo
1137
+ };
1138
+ await this.notifySuccess(request, {
1139
+ account: accountInfo,
1140
+ output,
1141
+ blockExplorer: this.blockExplorer,
1142
+ connectionContext: connectionInfo,
1143
+ walletInfo: await this.getWalletInfo()
1144
+ });
1145
+ this.analytics.track('event', 'DAppClient', 'Permission received', {
1146
+ address: accountInfo.address
1147
+ });
1148
+ return output;
1149
+ }
1150
+ /**
1151
+ * Send a proof of event request to the wallet. The wallet will either accept or decline the challenge.
1152
+ * 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.
1153
+ * Once the challenge is stored, a challenge stored message will be sent to the wallet.
1154
+ * It's **highly recommended** to run a proof of event challenge to check the identity of an abstracted account
1155
+ *
1156
+ * @param input The message details we need to prepare the ProofOfEventChallenge message.
1157
+ */
1158
+ async requestProofOfEventChallenge(input) {
1159
+ const activeAccount = await this.getActiveAccount();
1160
+ if (!activeAccount)
1161
+ throw new Error('Please request permissions before doing a proof of event challenge');
1162
+ if (activeAccount.walletType !== 'abstracted_account' &&
1163
+ activeAccount.verificationType !== 'proof_of_event')
1164
+ throw new Error('This wallet is not an abstracted account and thus cannot perform proof of event');
1165
+ const request = {
1166
+ type: BeaconMessageType.ProofOfEventChallengeRequest,
1167
+ contractAddress: activeAccount.address,
1168
+ payload: input.payload
1169
+ };
1170
+ this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'start'));
1171
+ const logId = `makeRequest ${Date.now()}`;
1172
+ logger.time(true, logId);
1173
+ const res = (await this.checkMakeRequest())
1174
+ ? this.makeRequest(request)
1175
+ : this.makeRequestBC(request);
1176
+ res.catch(async (requestError) => {
1177
+ requestError.errorType === BeaconErrorType.ABORTED_ERROR
1178
+ ? this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'abort'))
1179
+ : this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'error'));
1180
+ logger.time(false, logId);
1181
+ throw await this.handleRequestError(request, requestError);
1182
+ });
1183
+ const { message, connectionInfo } = (await res);
1184
+ logger.time(false, logId);
1185
+ this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'success'));
1186
+ this.analytics.track('event', 'DAppClient', `Proof of event challenge ${message.isAccepted ? 'accepted' : 'refused'}`, { address: activeAccount.address });
1187
+ await this.notifySuccess(request, {
1188
+ account: activeAccount,
1189
+ output: message,
1190
+ blockExplorer: this.blockExplorer,
1191
+ connectionContext: connectionInfo,
1192
+ walletInfo: await this.getWalletInfo()
1193
+ });
1194
+ return message;
1195
+ }
1196
+ /**
1197
+ * Send a simulated proof of event request to the wallet. The wallet will either accept or decline the challenge.
1198
+ * It's the same than `requestProofOfEventChallenge` but rather than executing operations on the blockchain to prove the identity,
1199
+ * The wallet will return a list of operations that you'll be able to run on your side to verify the identity of the abstracted account
1200
+ * It's **highly recommended** to run a proof of event challenge to check the identity of an abstracted account
1201
+ *
1202
+ * @param input The message details we need to prepare the SimulatedProofOfEventChallenge message.
1203
+ */
1204
+ async requestSimulatedProofOfEventChallenge(input) {
1205
+ const activeAccount = await this.getActiveAccount();
1206
+ if (!activeAccount)
1207
+ throw new Error('Please request permissions before doing a proof of event challenge');
1208
+ if (activeAccount.walletType !== 'abstracted_account' &&
1209
+ activeAccount.verificationType !== 'proof_of_event') {
1210
+ throw new Error('This wallet is not an abstracted account and thus cannot perform a simulated proof of event');
1211
+ }
1212
+ const request = {
1213
+ type: BeaconMessageType.SimulatedProofOfEventChallengeRequest,
1214
+ contractAddress: activeAccount.address,
1215
+ ...input
1216
+ };
1217
+ const logId = `makeRequest ${Date.now()}`;
1218
+ logger.time(true, logId);
1219
+ const res = (await this.checkMakeRequest())
1220
+ ? this.makeRequest(request)
1221
+ : this.makeRequestBC(request);
1222
+ res.catch(async (requestError) => {
1223
+ requestError.errorType === BeaconErrorType.ABORTED_ERROR
1224
+ ? this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'abort'))
1225
+ : this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'error'));
1226
+ logger.time(false, logId);
1227
+ throw await this.handleRequestError(request, requestError);
1228
+ });
1229
+ const { message, connectionInfo } = (await res);
1230
+ logger.time(false, logId);
1231
+ this.analytics.track('event', 'DAppClient', `Simulated proof of event challenge ${!message.errorMessage ? 'accepted' : 'refused'}`, { address: activeAccount.address });
1232
+ await this.notifySuccess(request, {
1233
+ account: activeAccount,
1234
+ output: message,
1235
+ blockExplorer: this.blockExplorer,
1236
+ connectionContext: connectionInfo,
1237
+ walletInfo: await this.getWalletInfo()
1238
+ });
1239
+ return message;
1240
+ }
1241
+ /**
1242
+ * This method will send a "SignPayloadRequest" to the wallet. This method is meant to be used to sign
1243
+ * arbitrary data (eg. a string). It will return the signature in the format of "edsig..."
1244
+ *
1245
+ * @param input The message details we need to prepare the SignPayloadRequest message.
1246
+ */
1247
+ async requestSignPayload(input) {
1248
+ if (!input.payload) {
1249
+ throw await this.sendInternalError('Payload must be provided');
1250
+ }
1251
+ const activeAccount = await this.getActiveAccount();
1252
+ if (!activeAccount) {
1253
+ throw await this.sendInternalError('No active account!');
1254
+ }
1255
+ const payload = input.payload;
1256
+ if (typeof payload !== 'string') {
1257
+ throw new Error('Payload must be a string');
1258
+ }
1259
+ const signingType = (() => {
1260
+ switch (input.signingType) {
1261
+ case SigningType.OPERATION:
1262
+ if (!payload.startsWith('03')) {
1263
+ throw new Error('When using signing type "OPERATION", the payload must start with prefix "03"');
1264
+ }
1265
+ return SigningType.OPERATION;
1266
+ case SigningType.MICHELINE:
1267
+ if (!payload.startsWith('05')) {
1268
+ throw new Error('When using signing type "MICHELINE", the payload must start with prefix "05"');
1269
+ }
1270
+ return SigningType.MICHELINE;
1271
+ case SigningType.RAW:
1272
+ default:
1273
+ return SigningType.RAW;
1274
+ }
1275
+ })();
1276
+ this.analytics.track('event', 'DAppClient', 'Signature requested');
1277
+ const request = {
1278
+ type: BeaconMessageType.SignPayloadRequest,
1279
+ signingType,
1280
+ payload,
1281
+ sourceAddress: input.sourceAddress || activeAccount.address
1282
+ };
1283
+ this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'start'));
1284
+ const logId = `makeRequest ${Date.now()}`;
1285
+ logger.time(true, logId);
1286
+ const res = (await this.checkMakeRequest())
1287
+ ? this.makeRequest(request)
1288
+ : this.makeRequestBC(request);
1289
+ res.catch(async (requestError) => {
1290
+ requestError.errorType === BeaconErrorType.ABORTED_ERROR
1291
+ ? this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'abort'))
1292
+ : this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'error'));
1293
+ logger.time(false, logId);
1294
+ throw await this.handleRequestError(request, requestError);
1295
+ });
1296
+ const { message, connectionInfo } = (await res);
1297
+ logger.time(false, logId);
1298
+ this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'success'));
1299
+ await this.notifySuccess(request, {
1300
+ account: activeAccount,
1301
+ output: message,
1302
+ connectionContext: connectionInfo,
1303
+ walletInfo: await this.getWalletInfo()
1304
+ });
1305
+ this.analytics.track('event', 'DAppClient', 'Signature response');
1306
+ return message;
1307
+ }
1308
+ /**
1309
+ * This method will send an "EncryptPayloadRequest" to the wallet. This method is meant to be used to encrypt or decrypt
1310
+ * arbitrary data (eg. a string). It will return the encrypted or decrypted payload
1311
+ *
1312
+ * @param input The message details we need to prepare the EncryptPayloadRequest message.
1313
+ */
1314
+ // TODO: ENCRYPTION
1315
+ // public async requestEncryptPayload(
1316
+ // input: RequestEncryptPayloadInput
1317
+ // ): Promise<EncryptPayloadResponseOutput> {
1318
+ // if (!input.payload) {
1319
+ // throw await this.sendInternalError('Payload must be provided')
1320
+ // }
1321
+ // const activeAccount: AccountInfo | undefined = await this.getActiveAccount()
1322
+ // if (!activeAccount) {
1323
+ // throw await this.sendInternalError('No active account!')
1324
+ // }
1325
+ // const payload = input.payload
1326
+ // if (typeof payload !== 'string') {
1327
+ // throw new Error('Payload must be a string')
1328
+ // }
1329
+ // if (typeof input.encryptionCryptoOperation === 'undefined') {
1330
+ // throw new Error('encryptionCryptoOperation must be defined')
1331
+ // }
1332
+ // if (typeof input.encryptionType === 'undefined') {
1333
+ // throw new Error('encryptionType must be defined')
1334
+ // }
1335
+ // const request: EncryptPayloadRequestInput = {
1336
+ // type: BeaconMessageType.EncryptPayloadRequest,
1337
+ // cryptoOperation: input.encryptionCryptoOperation,
1338
+ // encryptionType: input.encryptionType,
1339
+ // payload,
1340
+ // sourceAddress: input.sourceAddress || activeAccount.address
1341
+ // }
1342
+ // const { message, connectionInfo } = await this.makeRequest<
1343
+ // EncryptPayloadRequest,
1344
+ // EncryptPayloadResponse
1345
+ // >(request).catch(async (requestError: ErrorResponse) => {
1346
+ // throw await this.handleRequestError(request, requestError)
1347
+ // })
1348
+ // await this.notifySuccess(request, {
1349
+ // account: activeAccount,
1350
+ // output: message,
1351
+ // connectionContext: connectionInfo,
1352
+ // walletInfo: await this.getWalletInfo()
1353
+ // })
1354
+ // return message
1355
+ // }
1356
+ /**
1357
+ * This method sends an OperationRequest to the wallet. This method should be used for all kinds of operations,
1358
+ * eg. transaction or delegation. Not all properties have to be provided. Data like "counter" and fees will be
1359
+ * fetched and calculated by the wallet (but they can still be provided if required).
1360
+ *
1361
+ * @param input The message details we need to prepare the OperationRequest message.
1362
+ */
1363
+ async requestOperation(input) {
1364
+ if (!input.operationDetails) {
1365
+ throw await this.sendInternalError('Operation details must be provided');
1366
+ }
1367
+ const activeAccount = await this.getActiveAccount();
1368
+ if (!activeAccount) {
1369
+ throw await this.sendInternalError('No active account!');
1370
+ }
1371
+ const request = {
1372
+ type: BeaconMessageType.OperationRequest,
1373
+ network: activeAccount.network || this.network,
1374
+ operationDetails: input.operationDetails,
1375
+ sourceAddress: activeAccount.address || ''
1376
+ };
1377
+ this.analytics.track('event', 'DAppClient', 'Operation requested');
1378
+ this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'start'));
1379
+ const logId = `makeRequest ${Date.now()}`;
1380
+ logger.time(true, logId);
1381
+ const res = (await this.checkMakeRequest())
1382
+ ? this.makeRequest(request)
1383
+ : this.makeRequestBC(request);
1384
+ res.catch(async (requestError) => {
1385
+ requestError.errorType === BeaconErrorType.ABORTED_ERROR
1386
+ ? this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'abort'))
1387
+ : this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'error'));
1388
+ logger.time(false, logId);
1389
+ throw await this.handleRequestError(request, requestError);
1390
+ });
1391
+ const { message, connectionInfo } = (await res);
1392
+ logger.time(false, logId);
1393
+ this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'success'));
1394
+ await this.notifySuccess(request, {
1395
+ account: activeAccount,
1396
+ output: message,
1397
+ blockExplorer: this.blockExplorer,
1398
+ connectionContext: connectionInfo,
1399
+ walletInfo: await this.getWalletInfo()
1400
+ });
1401
+ this.analytics.track('event', 'DAppClient', 'Operation response');
1402
+ return message;
1403
+ }
1404
+ /**
1405
+ * Sends a "BroadcastRequest" to the wallet. This method can be used to inject an already signed transaction
1406
+ * to the network.
1407
+ *
1408
+ * @param input The message details we need to prepare the BroadcastRequest message.
1409
+ */
1410
+ async requestBroadcast(input) {
1411
+ if (!input.signedTransaction) {
1412
+ throw await this.sendInternalError('Signed transaction must be provided');
1413
+ }
1414
+ // Add error message for deprecation of network
1415
+ // TODO: Remove when we remove deprecated preferredNetwork
1416
+ if (input.network !== undefined && this.network.type !== input.network?.type) {
1417
+ console.error('[BEACON] The network specified in the DAppClient constructor does not match the network set in the broadcast request. Please set the network in the constructor. Setting it during the Broadcast Request is deprecated.');
1418
+ }
1419
+ const request = {
1420
+ type: BeaconMessageType.BroadcastRequest,
1421
+ network: this.network,
1422
+ signedTransaction: input.signedTransaction
1423
+ };
1424
+ this.analytics.track('event', 'DAppClient', 'Broadcast requested');
1425
+ this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'start'));
1426
+ const logId = `makeRequest ${Date.now()}`;
1427
+ logger.time(true, logId);
1428
+ const res = (await this.checkMakeRequest())
1429
+ ? this.makeRequest(request)
1430
+ : this.makeRequestBC(request);
1431
+ res.catch(async (requestError) => {
1432
+ requestError.errorType === BeaconErrorType.ABORTED_ERROR
1433
+ ? this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'abort'))
1434
+ : this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'error'));
1435
+ logger.time(false, logId);
1436
+ throw await this.handleRequestError(request, requestError);
1437
+ });
1438
+ const { message, connectionInfo } = (await res);
1439
+ logger.time(false, logId);
1440
+ this.sendMetrics('performance-metrics/save', await this.buildPayload('message', 'success'));
1441
+ await this.notifySuccess(request, {
1442
+ network: this.network,
1443
+ output: message,
1444
+ blockExplorer: this.blockExplorer,
1445
+ connectionContext: connectionInfo,
1446
+ walletInfo: await this.getWalletInfo()
1447
+ });
1448
+ this.analytics.track('event', 'DAppClient', 'Broadcast response');
1449
+ return message;
1450
+ }
1451
+ async setActivePeer(peer) {
1452
+ if (this._activePeer.isSettled()) {
1453
+ // If the promise has already been resolved we need to create a new one.
1454
+ this._activePeer = ExposedPromise.resolve(peer);
1455
+ }
1456
+ else {
1457
+ this._activePeer.resolve(peer);
1458
+ }
1459
+ if (!peer) {
1460
+ return;
1461
+ }
1462
+ await this.initInternalTransports();
1463
+ if (peer.type === 'postmessage-pairing-response') {
1464
+ await this.setTransport(this.postMessageTransport);
1465
+ }
1466
+ else if (peer.type === 'p2p-pairing-response') {
1467
+ await this.setTransport(this.p2pTransport);
1468
+ }
1469
+ }
1470
+ /**
1471
+ * A "setter" for when the transport needs to be changed.
1472
+ */
1473
+ async setTransport(transport) {
1474
+ if (!transport) {
1475
+ this._initPromise = undefined;
1476
+ }
1477
+ const result = super.setTransport(transport);
1478
+ const event = transport ? { ...transport } : undefined;
1479
+ // remove keyPair, to prevent dApps from accidentaly leaking the privateKey
1480
+ if (event) {
1481
+ event.client = {
1482
+ ...event.client,
1483
+ keyPair: undefined
1484
+ };
1485
+ }
1486
+ await this.events.emit(BeaconEvent.ACTIVE_TRANSPORT_SET, event);
1487
+ return result;
1488
+ }
1489
+ /**
1490
+ * This method will emit an internal error message.
1491
+ *
1492
+ * @param errorMessage The error message to send.
1493
+ */
1494
+ async sendInternalError(errorMessage) {
1495
+ await this.events.emit(BeaconEvent.INTERNAL_ERROR, { text: errorMessage });
1496
+ throw new Error(errorMessage);
1497
+ }
1498
+ /**
1499
+ * This method will remove all accounts associated with a specific peer.
1500
+ *
1501
+ * @param peersToRemove An array of peers for which accounts should be removed
1502
+ */
1503
+ async removeAccountsForPeers(peersToRemove) {
1504
+ const peerIdsToRemove = peersToRemove.map((peer) => peer.senderId);
1505
+ return this.removeAccountsForPeerIds(peerIdsToRemove);
1506
+ }
1507
+ async removeAccountsForPeerIds(peerIds) {
1508
+ const accounts = await this.accountManager.getAccounts();
1509
+ // Remove all accounts with origin of the specified peer
1510
+ const accountsToRemove = accounts.filter((account) => peerIds.includes(account.senderId));
1511
+ const accountIdentifiersToRemove = accountsToRemove.map((accountInfo) => accountInfo.accountIdentifier);
1512
+ await this.accountManager.removeAccounts(accountIdentifiersToRemove);
1513
+ // Check if one of the accounts that was removed was the active account and if yes, set it to undefined
1514
+ const activeAccount = await this.getActiveAccount();
1515
+ if (activeAccount) {
1516
+ if (accountIdentifiersToRemove.includes(activeAccount.accountIdentifier)) {
1517
+ await this.setActiveAccount(undefined);
1518
+ }
1519
+ }
1520
+ }
1521
+ /**
1522
+ * This message handles errors that we receive from the wallet.
1523
+ *
1524
+ * @param request The request we sent
1525
+ * @param beaconError The error we received
1526
+ */
1527
+ async handleRequestError(request, beaconError) {
1528
+ logger.error('handleRequestError', 'error response', beaconError);
1529
+ if (beaconError.errorType) {
1530
+ const buttons = [];
1531
+ if (beaconError.errorType === BeaconErrorType.NO_PRIVATE_KEY_FOUND_ERROR) {
1532
+ const actionCallback = async () => {
1533
+ const operationRequest = request;
1534
+ // if the account we requested is not available, we remove it locally
1535
+ let accountInfo;
1536
+ if (operationRequest.sourceAddress && operationRequest.network) {
1537
+ const accountIdentifier = await getAccountIdentifier(operationRequest.sourceAddress, operationRequest.network);
1538
+ accountInfo = await this.getAccount(accountIdentifier);
1539
+ if (accountInfo) {
1540
+ await this.removeAccount(accountInfo.accountIdentifier);
1541
+ }
1542
+ }
1543
+ };
1544
+ buttons.push({ text: 'Remove account', actionCallback });
1545
+ }
1546
+ const peer = await this.getPeer();
1547
+ const activeAccount = await this.getActiveAccount();
1548
+ // If we sent a permission request, received an error and there is no active account, we need to reset the DAppClient.
1549
+ // This most likely means that the user rejected the first permission request after pairing a wallet, so we "forget" the paired wallet to allow the user to pair again.
1550
+ if (request.type === BeaconMessageType.PermissionRequest &&
1551
+ (await this.getActiveAccount()) === undefined) {
1552
+ this._initPromise = undefined;
1553
+ this.postMessageTransport = undefined;
1554
+ this.p2pTransport = undefined;
1555
+ this.walletConnectTransport = undefined;
1556
+ await this.setTransport();
1557
+ await this.setActivePeer();
1558
+ }
1559
+ this.events
1560
+ .emit(messageEvents[request.type].error, {
1561
+ errorResponse: beaconError,
1562
+ walletInfo: await this.getWalletInfo(peer, activeAccount),
1563
+ errorMessages: this.errorMessages
1564
+ }, buttons)
1565
+ .catch((emitError) => logger.error('handleRequestError', emitError));
1566
+ throw getError(beaconError.errorType, beaconError.errorData);
1567
+ }
1568
+ throw beaconError;
1569
+ }
1570
+ /**
1571
+ * This message will send an event when we receive a successful response to one of the requests we sent.
1572
+ *
1573
+ * @param request The request we sent
1574
+ * @param response The response we received
1575
+ */
1576
+ async notifySuccess(request, response) {
1577
+ this.events
1578
+ .emit(messageEvents[request.type].success, response)
1579
+ .catch((emitError) => console.warn(emitError));
1580
+ }
1581
+ async getWalletInfoFromStorage() {
1582
+ return await this.storage.get(StorageKey.LAST_SELECTED_WALLET);
1583
+ }
1584
+ async updateStorageWallet(walletInfo) {
1585
+ const wallet = await this.storage.get(StorageKey.LAST_SELECTED_WALLET);
1586
+ if (!wallet) {
1587
+ return;
1588
+ }
1589
+ wallet.name = walletInfo.name;
1590
+ wallet.icon = walletInfo.icon ?? wallet.icon;
1591
+ this.storage.set(StorageKey.LAST_SELECTED_WALLET, wallet);
1592
+ }
1593
+ async getWalletInfo(peer, account, readFromStorage = true) {
1594
+ const selectedAccount = account ? account : await this.getActiveAccount();
1595
+ const selectedPeer = peer ? peer : await this.getPeer(selectedAccount);
1596
+ let walletInfo;
1597
+ if (selectedAccount) {
1598
+ walletInfo = await this.appMetadataManager.getAppMetadata(selectedAccount.senderId);
1599
+ }
1600
+ let storageWallet;
1601
+ if (readFromStorage) {
1602
+ storageWallet = await this.getWalletInfoFromStorage();
1603
+ }
1604
+ if (!walletInfo) {
1605
+ walletInfo = {
1606
+ name: selectedPeer?.name ?? storageWallet?.key ?? '',
1607
+ icon: selectedPeer?.icon ?? storageWallet?.icon,
1608
+ type: storageWallet?.type
1609
+ };
1610
+ this.updateStorageWallet(walletInfo);
1611
+ }
1612
+ const lowerCaseCompare = (str1, str2) => {
1613
+ if (str1 && str2) {
1614
+ return str1.toLowerCase() === str2.toLowerCase();
1615
+ }
1616
+ return false;
1617
+ };
1618
+ const getOrgName = (name) => name.split(/[_\s]+/)[0];
1619
+ const apps = [
1620
+ ...getiOSList(),
1621
+ ...getWebList(),
1622
+ ...getDesktopList(),
1623
+ ...getExtensionList()
1624
+ ].filter((app) => lowerCaseCompare(getOrgName(app.key), getOrgName(walletInfo?.name ?? 'wallet')));
1625
+ // TODO: Remove once all wallets send the icon?
1626
+ const mobile = apps.find((app) => app.universalLink || app.key.includes('ios') || app.key.includes('mobile'));
1627
+ const browser = apps.find((app) => app.links);
1628
+ const desktop = apps.find((app) => app.downloadLink);
1629
+ const extension = apps.find((app) => app.id);
1630
+ const appTypeMap = {
1631
+ extension: { app: extension, type: 'extension' },
1632
+ desktop: { app: desktop, type: 'desktop' },
1633
+ mobile: { app: mobile, type: 'mobile' },
1634
+ web: { app: browser, type: 'web' }
1635
+ };
1636
+ const defaultType = () => {
1637
+ if (isBrowser(window) && browser)
1638
+ return { app: browser, type: 'web' };
1639
+ if (isDesktop(window) && desktop)
1640
+ return { app: desktop, type: 'desktop' };
1641
+ if (isBrowser(window) && extension)
1642
+ return { app: extension, type: 'extension' };
1643
+ if (mobile)
1644
+ return { app: mobile, type: 'mobile' };
1645
+ return { app: undefined, type: undefined };
1646
+ };
1647
+ const { app, type } = storageWallet ? appTypeMap[storageWallet.type] : defaultType();
1648
+ if (app) {
1649
+ let deeplink;
1650
+ if (app.hasOwnProperty('links')) {
1651
+ deeplink = app.links[selectedAccount?.network.type ?? this.network.type];
1652
+ }
1653
+ else if (app.hasOwnProperty('deepLink')) {
1654
+ deeplink = app.deepLink;
1655
+ }
1656
+ return {
1657
+ name: app?.name ?? walletInfo.name,
1658
+ icon: app?.logo ?? walletInfo.icon,
1659
+ deeplink,
1660
+ type: type
1661
+ };
1662
+ }
1663
+ return walletInfo;
1664
+ }
1665
+ async getPeer(account) {
1666
+ let peer;
1667
+ if (account) {
1668
+ logger.log('getPeer', 'We have an account', account);
1669
+ const postMessagePeers = (await this.postMessageTransport?.getPeers()) ?? [];
1670
+ const p2pPeers = (await this.p2pTransport?.getPeers()) ?? [];
1671
+ const walletConnectPeers = (await this.walletConnectTransport?.getPeers()) ?? [];
1672
+ const peers = [...postMessagePeers, ...p2pPeers, ...walletConnectPeers];
1673
+ logger.log('getPeer', 'Found peers', peers, account);
1674
+ peer = peers.find((peerEl) => peerEl.senderId === account.senderId);
1675
+ if (!peer) {
1676
+ // We could not find an exact match for a sender, so we most likely received it over a relay
1677
+ peer = peers.find((peerEl) => peerEl.id === account.origin.id);
1678
+ }
1679
+ }
1680
+ else {
1681
+ peer = await this._activePeer.promise;
1682
+ logger.log('getPeer', 'Active peer', peer);
1683
+ }
1684
+ return peer;
1685
+ }
1686
+ async makeRequest(requestInput, skipResponse, otherTabMessageId) {
1687
+ 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
+ logger.log('makeRequest', 'starting');
1697
+ this.isInitPending = true;
1698
+ await this.init();
1699
+ this.isInitPending = false;
1700
+ logger.log('makeRequest', 'after init');
1701
+ if (await this.addRequestAndCheckIfRateLimited()) {
1702
+ this.events
1703
+ .emit(BeaconEvent.LOCAL_RATE_LIMIT_REACHED)
1704
+ .catch((emitError) => console.warn(emitError));
1705
+ throw new Error('rate limit reached');
1706
+ }
1707
+ if (!(await this.checkPermissions(requestInput.type))) {
1708
+ this.events.emit(BeaconEvent.NO_PERMISSIONS).catch((emitError) => console.warn(emitError));
1709
+ throw new Error('No permissions to send this request to wallet!');
1710
+ }
1711
+ if (!this.beaconId) {
1712
+ throw await this.sendInternalError('octez.connect ID not defined');
1713
+ }
1714
+ const request = {
1715
+ id: messageId,
1716
+ version: '2', // This is the old version
1717
+ senderId: await getSenderId(await this.beaconId),
1718
+ ...requestInput
1719
+ };
1720
+ let exposed;
1721
+ if (!skipResponse) {
1722
+ exposed = new ExposedPromise();
1723
+ this.addOpenRequest(request.id, exposed);
1724
+ }
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
+ try {
1731
+ ;
1732
+ (await this.transport).send(payload, peer);
1733
+ if (request.type !== BeaconMessageType.PermissionRequest ||
1734
+ (this._activeAccount.isResolved() && (await this._activeAccount.promise))) {
1735
+ this.tryToAppSwitch();
1736
+ }
1737
+ }
1738
+ catch (sendError) {
1739
+ this.events.emit(BeaconEvent.INTERNAL_ERROR, {
1740
+ text: 'Unable to send message. If this problem persists, please reset the connection and pair your wallet again.',
1741
+ buttons: [
1742
+ {
1743
+ text: 'Reset Connection',
1744
+ actionCallback: async () => {
1745
+ closeToast();
1746
+ this.disconnect();
1747
+ }
1748
+ }
1749
+ ]
1750
+ });
1751
+ throw sendError;
1752
+ }
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
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1769
+ return exposed?.promise; // TODO: fix type
1770
+ }
1771
+ /**
1772
+ * This method handles sending of requests to the DApp. It makes sure that the DAppClient is initialized and connected
1773
+ * to the transport. After that rate limits and permissions will be checked, an ID is attached and the request is sent
1774
+ * to the DApp over the transport.
1775
+ *
1776
+ * @param requestInput The BeaconMessage to be sent to the wallet
1777
+ * @param account The account that the message will be sent to
1778
+ */
1779
+ 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
+ const messageId = otherTabMessageId ?? (await generateGUID());
1789
+ logger.log('makeRequest', 'starting');
1790
+ this.isInitPending = true;
1791
+ await this.init(undefined, true);
1792
+ this.isInitPending = false;
1793
+ logger.log('makeRequest', 'after init');
1794
+ if (await this.addRequestAndCheckIfRateLimited()) {
1795
+ this.events
1796
+ .emit(BeaconEvent.LOCAL_RATE_LIMIT_REACHED)
1797
+ .catch((emitError) => console.warn(emitError));
1798
+ throw new Error('rate limit reached');
1799
+ }
1800
+ if (!this.beaconId) {
1801
+ throw await this.sendInternalError('octez.connect ID not defined');
1802
+ }
1803
+ const request = {
1804
+ id: messageId,
1805
+ version: '3',
1806
+ senderId: await getSenderId(await this.beaconId),
1807
+ message: requestInput
1808
+ };
1809
+ const exposed = new ExposedPromise();
1810
+ 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
+ try {
1817
+ ;
1818
+ (await this.transport).send(payload, peer);
1819
+ if (request.message.type !== BeaconMessageType.PermissionRequest ||
1820
+ (this._activeAccount.isResolved() && (await this._activeAccount.promise))) {
1821
+ this.tryToAppSwitch();
1822
+ }
1823
+ }
1824
+ catch (sendError) {
1825
+ this.events.emit(BeaconEvent.INTERNAL_ERROR, {
1826
+ text: 'Unable to send message. If this problem persists, please reset the connection and pair your wallet again.',
1827
+ buttons: [
1828
+ {
1829
+ text: 'Reset Connection',
1830
+ actionCallback: async () => {
1831
+ closeToast();
1832
+ this.disconnect();
1833
+ }
1834
+ }
1835
+ ]
1836
+ });
1837
+ throw sendError;
1838
+ }
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
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1854
+ return exposed.promise; // TODO: fix type
1855
+ }
1856
+ async makeRequestBC(request) {
1857
+ if (!this._transport.isResolved()) {
1858
+ return;
1859
+ }
1860
+ const transport = await this.transport;
1861
+ if (transport.type !== TransportType.WALLETCONNECT) {
1862
+ return;
1863
+ }
1864
+ if (await this.addRequestAndCheckIfRateLimited()) {
1865
+ this.events
1866
+ .emit(BeaconEvent.LOCAL_RATE_LIMIT_REACHED)
1867
+ .catch((emitError) => console.warn(emitError));
1868
+ throw new Error('rate limit reached');
1869
+ }
1870
+ const id = await generateGUID();
1871
+ this.multiTabChannel.postMessage({
1872
+ type: request.type,
1873
+ data: request,
1874
+ id
1875
+ });
1876
+ if (request.type !== BeaconMessageType.PermissionRequest ||
1877
+ (this._activeAccount.isResolved() && (await this._activeAccount.promise))) {
1878
+ this.tryToAppSwitch();
1879
+ }
1880
+ this.events
1881
+ .emit(messageEvents[BeaconMessageType.PermissionRequest].sent, {
1882
+ walletInfo: await this.getWalletInfo(),
1883
+ extraInfo: {
1884
+ resetCallback: () => this.disconnect()
1885
+ }
1886
+ })
1887
+ .catch((emitError) => console.warn(emitError));
1888
+ const exposed = new ExposedPromise();
1889
+ this.addOpenRequest(id, exposed);
1890
+ return exposed.promise;
1891
+ }
1892
+ async disconnect() {
1893
+ if (!this._transport.isResolved()) {
1894
+ throw new Error('No transport available.');
1895
+ }
1896
+ const transport = await this.transport;
1897
+ if (transport.connectionStatus === TransportStatus.NOT_CONNECTED) {
1898
+ throw new Error('Not connected.');
1899
+ }
1900
+ await this.createStateSnapshot();
1901
+ this.sendMetrics('performance-metrics/save', await this.buildPayload('disconnect', 'start'));
1902
+ await this.clearActiveAccount();
1903
+ if (!(transport instanceof WalletConnectTransport)) {
1904
+ await transport.disconnect();
1905
+ }
1906
+ this.postMessageTransport = undefined;
1907
+ this.p2pTransport = undefined;
1908
+ this.walletConnectTransport = undefined;
1909
+ this.sendMetrics('performance-metrics/save', await this.buildPayload('disconnect', 'success'));
1910
+ }
1911
+ /**
1912
+ * Adds a requests to the "openRequests" set so we know what messages have already been answered/handled.
1913
+ *
1914
+ * @param id The ID of the message
1915
+ * @param promise A promise that resolves once the response for that specific message is received
1916
+ */
1917
+ addOpenRequest(id, promise) {
1918
+ logger.log('addOpenRequest', this.name, `adding request ${id} and waiting for answer`);
1919
+ this.openRequests.set(id, promise);
1920
+ }
1921
+ async sendNotificationWithAccessToken(notification) {
1922
+ const { url, recipient, title, body, payload, protocolIdentifier, accessToken } = notification;
1923
+ const timestamp = new Date().toISOString();
1924
+ const keypair = await this.keyPair;
1925
+ const rawPublicKey = keypair.publicKey;
1926
+ const prefix = Buffer.from(new Uint8Array([13, 15, 37, 217]));
1927
+ const publicKey = bs58check.encode(Buffer.concat([prefix, Buffer.from(rawPublicKey)]));
1928
+ const constructedString = [
1929
+ 'Tezos Signed Message: ',
1930
+ recipient,
1931
+ title,
1932
+ body,
1933
+ timestamp,
1934
+ payload
1935
+ ].join(' ');
1936
+ const bytes = toHex(constructedString);
1937
+ const payloadBytes = '05' + '01' + bytes.length.toString(16).padStart(8, '0') + bytes;
1938
+ const signature = await signMessage(payloadBytes, {
1939
+ secretKey: Buffer.from(keypair.secretKey)
1940
+ });
1941
+ const notificationResponse = await axios.post(`${url}/send`, {
1942
+ recipient,
1943
+ title,
1944
+ body,
1945
+ timestamp,
1946
+ payload,
1947
+ accessToken,
1948
+ protocolIdentifier,
1949
+ sender: {
1950
+ name: this.name,
1951
+ publicKey,
1952
+ signature
1953
+ }
1954
+ });
1955
+ return notificationResponse.data;
1956
+ }
1957
+ async onNewAccount(message, connectionInfo) {
1958
+ // TODO: Migration code. Remove sometime after 1.0.0 release.
1959
+ const tempPK = message.publicKey || message.pubkey || message.pubKey;
1960
+ const publicKey = !!tempPK ? prefixPublicKey(tempPK) : undefined;
1961
+ if (!publicKey && !message.address) {
1962
+ throw new Error('PublicKey or Address must be defined');
1963
+ }
1964
+ const address = message.address ?? (await getAddressFromPublicKey(publicKey));
1965
+ if (!isValidAddress(address)) {
1966
+ throw new Error(`Invalid address: "${address}"`);
1967
+ }
1968
+ if (message.walletType === 'abstracted_account' &&
1969
+ address.substring(0, 3) !== CONTRACT_PREFIX) {
1970
+ throw new Error(`Invalid abstracted account address "${address}", it should be a ${CONTRACT_PREFIX} address`);
1971
+ }
1972
+ logger.log('######## MESSAGE #######');
1973
+ logger.log('onNewAccount', message);
1974
+ const walletKey = (await this.storage.get(StorageKey.LAST_SELECTED_WALLET))?.key;
1975
+ const accountInfo = {
1976
+ accountIdentifier: await getAccountIdentifier(address, message.network),
1977
+ senderId: message.senderId,
1978
+ origin: {
1979
+ type: connectionInfo.origin,
1980
+ id: connectionInfo.id
1981
+ },
1982
+ walletKey,
1983
+ address,
1984
+ publicKey,
1985
+ network: message.network,
1986
+ scopes: message.scopes,
1987
+ threshold: message.threshold,
1988
+ notification: message.notification,
1989
+ connectedAt: new Date().getTime(),
1990
+ walletType: message.walletType ?? 'implicit',
1991
+ verificationType: message.verificationType,
1992
+ ...(message.verificationType === 'proof_of_event' ? { hasVerifiedChallenge: false } : {})
1993
+ };
1994
+ logger.log('accountInfo', '######## ACCOUNT INFO #######');
1995
+ logger.log('accountInfo', accountInfo);
1996
+ await this.accountManager.addAccount(accountInfo);
1997
+ await this.setActiveAccount(accountInfo);
1998
+ return accountInfo;
1999
+ }
2000
+ }
2001
+ //# sourceMappingURL=DAppClient.js.map