@phantom/browser-sdk 1.0.0-beta.2 → 1.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +208 -116
- package/dist/index.d.ts +99 -29
- package/dist/index.js +761 -277
- package/dist/index.mjs +758 -274
- package/package.json +10 -9
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
import { AddressType } from "@phantom/client";
|
|
3
|
+
|
|
1
4
|
// src/providers/injected/index.ts
|
|
2
|
-
import { AddressType as
|
|
5
|
+
import { AddressType as AddressType4 } from "@phantom/client";
|
|
3
6
|
import { createPhantom, createExtensionPlugin } from "@phantom/browser-injected-sdk";
|
|
4
7
|
import { createSolanaPlugin } from "@phantom/browser-injected-sdk/solana";
|
|
5
8
|
import { createEthereumPlugin } from "@phantom/browser-injected-sdk/ethereum";
|
|
@@ -83,7 +86,7 @@ var DebugCategory = {
|
|
|
83
86
|
|
|
84
87
|
// src/providers/injected/chains/SolanaChain.ts
|
|
85
88
|
import { EventEmitter } from "eventemitter3";
|
|
86
|
-
import { AddressType } from "@phantom/client";
|
|
89
|
+
import { AddressType as AddressType2 } from "@phantom/client";
|
|
87
90
|
import { Buffer } from "buffer";
|
|
88
91
|
var InjectedSolanaChain = class {
|
|
89
92
|
constructor(phantom, callbacks) {
|
|
@@ -108,7 +111,7 @@ var InjectedSolanaChain = class {
|
|
|
108
111
|
return Promise.reject(new Error("Provider not connected. Call provider connect first."));
|
|
109
112
|
}
|
|
110
113
|
const addresses = this.callbacks.getAddresses();
|
|
111
|
-
const solanaAddress = addresses.find((addr) => addr.addressType ===
|
|
114
|
+
const solanaAddress = addresses.find((addr) => addr.addressType === AddressType2.solana);
|
|
112
115
|
if (!solanaAddress) {
|
|
113
116
|
return Promise.reject(new Error("Solana not enabled for this provider"));
|
|
114
117
|
}
|
|
@@ -142,8 +145,27 @@ var InjectedSolanaChain = class {
|
|
|
142
145
|
const result = await this.phantom.solana.signAndSendTransaction(transaction);
|
|
143
146
|
return { signature: result.signature };
|
|
144
147
|
}
|
|
145
|
-
signAllTransactions(
|
|
146
|
-
|
|
148
|
+
async signAllTransactions(transactions) {
|
|
149
|
+
if (!this.callbacks.isConnected()) {
|
|
150
|
+
return Promise.reject(new Error("Provider not connected. Call provider connect first."));
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const result = await this.phantom.solana.signAllTransactions(transactions);
|
|
154
|
+
return result;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
return Promise.reject(error);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async signAndSendAllTransactions(transactions) {
|
|
160
|
+
if (!this.callbacks.isConnected()) {
|
|
161
|
+
return Promise.reject(new Error("Provider not connected. Call provider connect first."));
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const result = await this.phantom.solana.signAndSendAllTransactions(transactions);
|
|
165
|
+
return { signatures: result.signatures };
|
|
166
|
+
} catch (error) {
|
|
167
|
+
return Promise.reject(error);
|
|
168
|
+
}
|
|
147
169
|
}
|
|
148
170
|
switchNetwork(_network) {
|
|
149
171
|
return Promise.resolve();
|
|
@@ -169,7 +191,7 @@ var InjectedSolanaChain = class {
|
|
|
169
191
|
this.eventEmitter.emit("accountChanged", publicKey);
|
|
170
192
|
});
|
|
171
193
|
this.callbacks.on("connect", (data) => {
|
|
172
|
-
const solanaAddress = data.addresses?.find((addr) => addr.addressType ===
|
|
194
|
+
const solanaAddress = data.addresses?.find((addr) => addr.addressType === AddressType2.solana);
|
|
173
195
|
if (solanaAddress) {
|
|
174
196
|
this.updateConnectionState(true, solanaAddress.address);
|
|
175
197
|
}
|
|
@@ -180,7 +202,7 @@ var InjectedSolanaChain = class {
|
|
|
180
202
|
}
|
|
181
203
|
syncInitialState() {
|
|
182
204
|
if (this.callbacks.isConnected()) {
|
|
183
|
-
const solanaAddress = this.callbacks.getAddresses().find((addr) => addr.addressType ===
|
|
205
|
+
const solanaAddress = this.callbacks.getAddresses().find((addr) => addr.addressType === AddressType2.solana);
|
|
184
206
|
if (solanaAddress) {
|
|
185
207
|
this.updateConnectionState(true, solanaAddress.address);
|
|
186
208
|
}
|
|
@@ -201,7 +223,7 @@ var InjectedSolanaChain = class {
|
|
|
201
223
|
|
|
202
224
|
// src/providers/injected/chains/EthereumChain.ts
|
|
203
225
|
import { EventEmitter as EventEmitter2 } from "eventemitter3";
|
|
204
|
-
import { AddressType as
|
|
226
|
+
import { AddressType as AddressType3 } from "@phantom/client";
|
|
205
227
|
var InjectedEthereumChain = class {
|
|
206
228
|
constructor(phantom, callbacks) {
|
|
207
229
|
this._connected = false;
|
|
@@ -239,7 +261,7 @@ var InjectedEthereumChain = class {
|
|
|
239
261
|
return Promise.reject(new Error("Provider not connected. Call provider connect first."));
|
|
240
262
|
}
|
|
241
263
|
const addresses = this.callbacks.getAddresses();
|
|
242
|
-
const ethAddresses = addresses.filter((addr) => addr.addressType ===
|
|
264
|
+
const ethAddresses = addresses.filter((addr) => addr.addressType === AddressType3.ethereum).map((addr) => addr.address);
|
|
243
265
|
this.updateConnectionState(true, ethAddresses);
|
|
244
266
|
return Promise.resolve(ethAddresses);
|
|
245
267
|
}
|
|
@@ -260,8 +282,9 @@ var InjectedEthereumChain = class {
|
|
|
260
282
|
return await this.phantom.ethereum.sendTransaction(transaction);
|
|
261
283
|
}
|
|
262
284
|
async switchChain(chainId) {
|
|
263
|
-
|
|
264
|
-
this.
|
|
285
|
+
const hexChainId = typeof chainId === "string" ? chainId.toLowerCase().startsWith("0x") ? chainId : `0x${parseInt(chainId, 10).toString(16)}` : `0x${chainId.toString(16)}`;
|
|
286
|
+
await this.phantom.ethereum.switchChain(hexChainId);
|
|
287
|
+
this._chainId = hexChainId;
|
|
265
288
|
this.eventEmitter.emit("chainChanged", this._chainId);
|
|
266
289
|
}
|
|
267
290
|
async getChainId() {
|
|
@@ -294,7 +317,7 @@ var InjectedEthereumChain = class {
|
|
|
294
317
|
this.eventEmitter.emit("chainChanged", chainId);
|
|
295
318
|
});
|
|
296
319
|
this.callbacks.on("connect", (data) => {
|
|
297
|
-
const ethAddresses = data.addresses?.filter((addr) => addr.addressType ===
|
|
320
|
+
const ethAddresses = data.addresses?.filter((addr) => addr.addressType === AddressType3.ethereum)?.map((addr) => addr.address) || [];
|
|
298
321
|
if (ethAddresses.length > 0) {
|
|
299
322
|
this.updateConnectionState(true, ethAddresses);
|
|
300
323
|
}
|
|
@@ -305,7 +328,7 @@ var InjectedEthereumChain = class {
|
|
|
305
328
|
}
|
|
306
329
|
syncInitialState() {
|
|
307
330
|
if (this.callbacks.isConnected()) {
|
|
308
|
-
const ethAddresses = this.callbacks.getAddresses().filter((addr) => addr.addressType ===
|
|
331
|
+
const ethAddresses = this.callbacks.getAddresses().filter((addr) => addr.addressType === AddressType3.ethereum).map((addr) => addr.address);
|
|
309
332
|
if (ethAddresses.length > 0) {
|
|
310
333
|
this.updateConnectionState(true, ethAddresses);
|
|
311
334
|
}
|
|
@@ -325,6 +348,8 @@ var InjectedEthereumChain = class {
|
|
|
325
348
|
};
|
|
326
349
|
|
|
327
350
|
// src/providers/injected/index.ts
|
|
351
|
+
var MANUAL_DISCONNECT_KEY = "phantom-injected-manual-disconnect";
|
|
352
|
+
var MANUAL_DISCONNECT_VALUE = "true";
|
|
328
353
|
var InjectedProvider = class {
|
|
329
354
|
constructor(config) {
|
|
330
355
|
this.connected = false;
|
|
@@ -337,11 +362,11 @@ var InjectedProvider = class {
|
|
|
337
362
|
this.addressTypes = config.addressTypes;
|
|
338
363
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Address types configured", { addressTypes: this.addressTypes });
|
|
339
364
|
const plugins = [createExtensionPlugin()];
|
|
340
|
-
if (this.addressTypes.includes(
|
|
365
|
+
if (this.addressTypes.includes(AddressType4.solana)) {
|
|
341
366
|
plugins.push(createSolanaPlugin());
|
|
342
367
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Solana plugin added");
|
|
343
368
|
}
|
|
344
|
-
if (this.addressTypes.includes(
|
|
369
|
+
if (this.addressTypes.includes(AddressType4.ethereum)) {
|
|
345
370
|
plugins.push(createEthereumPlugin());
|
|
346
371
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Ethereum plugin added");
|
|
347
372
|
}
|
|
@@ -352,10 +377,10 @@ var InjectedProvider = class {
|
|
|
352
377
|
});
|
|
353
378
|
this.phantom = createPhantom({ plugins });
|
|
354
379
|
const callbacks = this.createCallbacks();
|
|
355
|
-
if (this.addressTypes.includes(
|
|
380
|
+
if (this.addressTypes.includes(AddressType4.solana)) {
|
|
356
381
|
this._solanaChain = new InjectedSolanaChain(this.phantom, callbacks);
|
|
357
382
|
}
|
|
358
|
-
if (this.addressTypes.includes(
|
|
383
|
+
if (this.addressTypes.includes(AddressType4.ethereum)) {
|
|
359
384
|
this._ethereumChain = new InjectedEthereumChain(this.phantom, callbacks);
|
|
360
385
|
}
|
|
361
386
|
debug.info(DebugCategory.INJECTED_PROVIDER, "InjectedProvider initialized");
|
|
@@ -364,7 +389,7 @@ var InjectedProvider = class {
|
|
|
364
389
|
* Access to Solana chain operations
|
|
365
390
|
*/
|
|
366
391
|
get solana() {
|
|
367
|
-
if (!this.addressTypes.includes(
|
|
392
|
+
if (!this.addressTypes.includes(AddressType4.solana)) {
|
|
368
393
|
throw new Error("Solana not enabled for this provider");
|
|
369
394
|
}
|
|
370
395
|
if (!this._solanaChain) {
|
|
@@ -376,7 +401,7 @@ var InjectedProvider = class {
|
|
|
376
401
|
* Access to Ethereum chain operations
|
|
377
402
|
*/
|
|
378
403
|
get ethereum() {
|
|
379
|
-
if (!this.addressTypes.includes(
|
|
404
|
+
if (!this.addressTypes.includes(AddressType4.ethereum)) {
|
|
380
405
|
throw new Error("Ethereum not enabled for this provider");
|
|
381
406
|
}
|
|
382
407
|
if (!this._ethereumChain) {
|
|
@@ -387,9 +412,11 @@ var InjectedProvider = class {
|
|
|
387
412
|
async connect(authOptions) {
|
|
388
413
|
debug.info(DebugCategory.INJECTED_PROVIDER, "Starting injected provider connect", {
|
|
389
414
|
addressTypes: this.addressTypes,
|
|
390
|
-
|
|
391
|
-
// Note: authOptions are ignored for injected provider
|
|
415
|
+
provider: authOptions.provider
|
|
392
416
|
});
|
|
417
|
+
if (authOptions.provider !== "injected") {
|
|
418
|
+
throw new Error(`Invalid provider for injected connection: ${authOptions.provider}. Must be "injected"`);
|
|
419
|
+
}
|
|
393
420
|
this.emit("connect_start", {
|
|
394
421
|
source: "manual-connect",
|
|
395
422
|
providerType: "injected"
|
|
@@ -406,35 +433,45 @@ var InjectedProvider = class {
|
|
|
406
433
|
}
|
|
407
434
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Phantom extension detected");
|
|
408
435
|
const connectedAddresses = [];
|
|
409
|
-
if (this.addressTypes.includes(
|
|
436
|
+
if (this.addressTypes.includes(AddressType4.solana)) {
|
|
410
437
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Attempting Solana connection");
|
|
411
438
|
try {
|
|
412
439
|
const publicKey = await this.phantom.solana.connect();
|
|
413
440
|
if (publicKey) {
|
|
414
441
|
connectedAddresses.push({
|
|
415
|
-
addressType:
|
|
442
|
+
addressType: AddressType4.solana,
|
|
416
443
|
address: publicKey
|
|
417
444
|
});
|
|
418
445
|
debug.info(DebugCategory.INJECTED_PROVIDER, "Solana connected successfully", { address: publicKey });
|
|
419
446
|
}
|
|
420
447
|
} catch (err) {
|
|
421
|
-
debug.warn(DebugCategory.INJECTED_PROVIDER, "Failed to connect Solana", { error: err });
|
|
448
|
+
debug.warn(DebugCategory.INJECTED_PROVIDER, "Failed to connect Solana, stopping", { error: err });
|
|
449
|
+
this.emit("connect_error", {
|
|
450
|
+
error: err instanceof Error ? err.message : "Failed to connect",
|
|
451
|
+
source: "manual-connect"
|
|
452
|
+
});
|
|
453
|
+
throw err;
|
|
422
454
|
}
|
|
423
455
|
}
|
|
424
|
-
if (this.addressTypes.includes(
|
|
456
|
+
if (this.addressTypes.includes(AddressType4.ethereum)) {
|
|
425
457
|
try {
|
|
426
458
|
const accounts = await this.phantom.ethereum.connect();
|
|
427
459
|
if (accounts && accounts.length > 0) {
|
|
428
460
|
connectedAddresses.push(
|
|
429
461
|
...accounts.map((address) => ({
|
|
430
|
-
addressType:
|
|
462
|
+
addressType: AddressType4.ethereum,
|
|
431
463
|
address
|
|
432
464
|
}))
|
|
433
465
|
);
|
|
434
466
|
debug.info(DebugCategory.INJECTED_PROVIDER, "Ethereum connected successfully", { addresses: accounts });
|
|
435
467
|
}
|
|
436
468
|
} catch (err) {
|
|
437
|
-
debug.warn(DebugCategory.INJECTED_PROVIDER, "Failed to connect Ethereum", { error: err });
|
|
469
|
+
debug.warn(DebugCategory.INJECTED_PROVIDER, "Failed to connect Ethereum, stopping", { error: err });
|
|
470
|
+
this.emit("connect_error", {
|
|
471
|
+
error: err instanceof Error ? err.message : "Failed to connect",
|
|
472
|
+
source: "manual-connect"
|
|
473
|
+
});
|
|
474
|
+
throw err;
|
|
438
475
|
}
|
|
439
476
|
}
|
|
440
477
|
if (connectedAddresses.length === 0) {
|
|
@@ -447,14 +484,22 @@ var InjectedProvider = class {
|
|
|
447
484
|
}
|
|
448
485
|
this.addresses = connectedAddresses;
|
|
449
486
|
this.connected = true;
|
|
487
|
+
const authUserId = await this.getAuthUserId("manual-connect");
|
|
488
|
+
try {
|
|
489
|
+
localStorage.removeItem(MANUAL_DISCONNECT_KEY);
|
|
490
|
+
debug.log(DebugCategory.INJECTED_PROVIDER, "Cleared manual disconnect flag - auto-reconnect enabled");
|
|
491
|
+
} catch (error) {
|
|
492
|
+
debug.warn(DebugCategory.INJECTED_PROVIDER, "Failed to clear manual disconnect flag", { error });
|
|
493
|
+
}
|
|
450
494
|
const result = {
|
|
451
495
|
addresses: this.addresses,
|
|
452
|
-
status: "completed"
|
|
453
|
-
|
|
496
|
+
status: "completed",
|
|
497
|
+
authUserId
|
|
454
498
|
};
|
|
455
499
|
this.emit("connect", {
|
|
456
500
|
addresses: this.addresses,
|
|
457
|
-
source: "manual-connect"
|
|
501
|
+
source: "manual-connect",
|
|
502
|
+
authUserId
|
|
458
503
|
});
|
|
459
504
|
return result;
|
|
460
505
|
} catch (error) {
|
|
@@ -469,7 +514,7 @@ var InjectedProvider = class {
|
|
|
469
514
|
}
|
|
470
515
|
async disconnect() {
|
|
471
516
|
debug.info(DebugCategory.INJECTED_PROVIDER, "Starting injected provider disconnect");
|
|
472
|
-
if (this.addressTypes.includes(
|
|
517
|
+
if (this.addressTypes.includes(AddressType4.solana)) {
|
|
473
518
|
try {
|
|
474
519
|
await this.phantom.solana.disconnect();
|
|
475
520
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Solana disconnected successfully");
|
|
@@ -477,18 +522,117 @@ var InjectedProvider = class {
|
|
|
477
522
|
debug.warn(DebugCategory.INJECTED_PROVIDER, "Failed to disconnect Solana", { error: err });
|
|
478
523
|
}
|
|
479
524
|
}
|
|
480
|
-
if (this.addressTypes.includes(
|
|
525
|
+
if (this.addressTypes.includes(AddressType4.ethereum)) {
|
|
481
526
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Ethereum disconnected (no-op)");
|
|
482
527
|
}
|
|
483
528
|
this.browserInjectedCleanupFunctions.forEach((cleanup) => cleanup());
|
|
484
529
|
this.browserInjectedCleanupFunctions = [];
|
|
485
530
|
this.connected = false;
|
|
486
531
|
this.addresses = [];
|
|
532
|
+
try {
|
|
533
|
+
localStorage.setItem(MANUAL_DISCONNECT_KEY, MANUAL_DISCONNECT_VALUE);
|
|
534
|
+
debug.log(DebugCategory.INJECTED_PROVIDER, "Set manual disconnect flag to prevent auto-reconnect");
|
|
535
|
+
} catch (error) {
|
|
536
|
+
debug.warn(DebugCategory.INJECTED_PROVIDER, "Failed to set manual disconnect flag", { error });
|
|
537
|
+
}
|
|
487
538
|
this.emit("disconnect", {
|
|
488
539
|
source: "manual-disconnect"
|
|
489
540
|
});
|
|
490
541
|
debug.info(DebugCategory.INJECTED_PROVIDER, "Injected provider disconnected successfully");
|
|
491
542
|
}
|
|
543
|
+
/**
|
|
544
|
+
* Attempt auto-connection using onlyIfTrusted parameter
|
|
545
|
+
* This will only connect if the dApp is already trusted by the user
|
|
546
|
+
* Should be called after setting up event listeners
|
|
547
|
+
*/
|
|
548
|
+
async autoConnect() {
|
|
549
|
+
debug.log(DebugCategory.INJECTED_PROVIDER, "Attempting auto-connect with onlyIfTrusted=true");
|
|
550
|
+
try {
|
|
551
|
+
const manualDisconnect = localStorage.getItem(MANUAL_DISCONNECT_KEY);
|
|
552
|
+
if (manualDisconnect === MANUAL_DISCONNECT_VALUE) {
|
|
553
|
+
debug.log(DebugCategory.INJECTED_PROVIDER, "Skipping auto-connect: user previously disconnected manually");
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
} catch (error) {
|
|
557
|
+
debug.warn(DebugCategory.INJECTED_PROVIDER, "Failed to check manual disconnect flag", { error });
|
|
558
|
+
}
|
|
559
|
+
this.emit("connect_start", {
|
|
560
|
+
source: "auto-connect",
|
|
561
|
+
providerType: "injected"
|
|
562
|
+
});
|
|
563
|
+
try {
|
|
564
|
+
if (!this.phantom.extension?.isInstalled?.()) {
|
|
565
|
+
debug.warn(DebugCategory.INJECTED_PROVIDER, "Phantom wallet extension not found for auto-connect");
|
|
566
|
+
this.emit("connect_error", {
|
|
567
|
+
error: "Phantom wallet not found",
|
|
568
|
+
source: "auto-connect"
|
|
569
|
+
});
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
const connectedAddresses = [];
|
|
573
|
+
if (this.addressTypes.includes(AddressType4.solana)) {
|
|
574
|
+
debug.log(DebugCategory.INJECTED_PROVIDER, "Attempting Solana auto-connect");
|
|
575
|
+
try {
|
|
576
|
+
const publicKey = await this.phantom.solana.connect({ onlyIfTrusted: true });
|
|
577
|
+
if (publicKey) {
|
|
578
|
+
connectedAddresses.push({
|
|
579
|
+
addressType: AddressType4.solana,
|
|
580
|
+
address: publicKey
|
|
581
|
+
});
|
|
582
|
+
debug.info(DebugCategory.INJECTED_PROVIDER, "Solana auto-connected successfully", { address: publicKey });
|
|
583
|
+
}
|
|
584
|
+
} catch (err) {
|
|
585
|
+
debug.log(DebugCategory.INJECTED_PROVIDER, "Solana auto-connect failed (expected if not trusted)", { error: err });
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
if (this.addressTypes.includes(AddressType4.ethereum)) {
|
|
589
|
+
debug.log(DebugCategory.INJECTED_PROVIDER, "Attempting Ethereum auto-connect");
|
|
590
|
+
try {
|
|
591
|
+
const accounts = await this.phantom.ethereum.connect({ onlyIfTrusted: true });
|
|
592
|
+
if (accounts && accounts.length > 0) {
|
|
593
|
+
connectedAddresses.push(
|
|
594
|
+
...accounts.map((address) => ({
|
|
595
|
+
addressType: AddressType4.ethereum,
|
|
596
|
+
address
|
|
597
|
+
}))
|
|
598
|
+
);
|
|
599
|
+
debug.info(DebugCategory.INJECTED_PROVIDER, "Ethereum auto-connected successfully", { addresses: accounts });
|
|
600
|
+
}
|
|
601
|
+
} catch (err) {
|
|
602
|
+
debug.log(DebugCategory.INJECTED_PROVIDER, "Ethereum auto-connect failed (expected if not trusted)", { error: err });
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (connectedAddresses.length === 0) {
|
|
606
|
+
debug.log(DebugCategory.INJECTED_PROVIDER, "Auto-connect failed: no trusted connections available");
|
|
607
|
+
this.emit("connect_error", {
|
|
608
|
+
error: "No trusted connections available",
|
|
609
|
+
source: "auto-connect"
|
|
610
|
+
});
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
this.addresses = connectedAddresses;
|
|
614
|
+
this.connected = true;
|
|
615
|
+
const authUserId = await this.getAuthUserId("auto-connect");
|
|
616
|
+
this.emit("connect", {
|
|
617
|
+
addresses: this.addresses,
|
|
618
|
+
source: "auto-connect",
|
|
619
|
+
authUserId
|
|
620
|
+
});
|
|
621
|
+
debug.info(DebugCategory.INJECTED_PROVIDER, "Auto-connect successful", {
|
|
622
|
+
addressCount: connectedAddresses.length,
|
|
623
|
+
addresses: connectedAddresses.map((addr) => ({ type: addr.addressType, address: addr.address.substring(0, 8) + "..." })),
|
|
624
|
+
authUserId
|
|
625
|
+
});
|
|
626
|
+
} catch (error) {
|
|
627
|
+
debug.log(DebugCategory.INJECTED_PROVIDER, "Auto-connect failed with error", {
|
|
628
|
+
error: error instanceof Error ? error.message : String(error)
|
|
629
|
+
});
|
|
630
|
+
this.emit("connect_error", {
|
|
631
|
+
error: error instanceof Error ? error.message : "Auto-connect failed",
|
|
632
|
+
source: "auto-connect"
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
}
|
|
492
636
|
getAddresses() {
|
|
493
637
|
return this.addresses;
|
|
494
638
|
}
|
|
@@ -512,6 +656,27 @@ var InjectedProvider = class {
|
|
|
512
656
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Getting supported autoConfirm chains");
|
|
513
657
|
return await this.phantom.autoConfirm.autoConfirmSupportedChains();
|
|
514
658
|
}
|
|
659
|
+
/**
|
|
660
|
+
* Helper method to get authUserId from window.phantom.app.getUser()
|
|
661
|
+
* Returns undefined if the method is not available or fails
|
|
662
|
+
*/
|
|
663
|
+
async getAuthUserId(context) {
|
|
664
|
+
try {
|
|
665
|
+
if (window.phantom?.app?.getUser) {
|
|
666
|
+
const userInfo = await window.phantom.app.getUser();
|
|
667
|
+
const authUserId = userInfo?.authUserId;
|
|
668
|
+
if (authUserId) {
|
|
669
|
+
debug.log(DebugCategory.INJECTED_PROVIDER, `Retrieved authUserId from window.phantom.app.getUser() during ${context}`, {
|
|
670
|
+
authUserId
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
return authUserId;
|
|
674
|
+
}
|
|
675
|
+
} catch (error) {
|
|
676
|
+
debug.log(DebugCategory.INJECTED_PROVIDER, `Failed to get user info during ${context} (method may not be supported)`, { error });
|
|
677
|
+
}
|
|
678
|
+
return void 0;
|
|
679
|
+
}
|
|
515
680
|
// Event management methods - implementing unified event interface
|
|
516
681
|
on(event, callback) {
|
|
517
682
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Adding event listener", { event });
|
|
@@ -552,98 +717,108 @@ var InjectedProvider = class {
|
|
|
552
717
|
}
|
|
553
718
|
setupBrowserInjectedEvents() {
|
|
554
719
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Setting up browser-injected-sdk event listeners");
|
|
555
|
-
if (this.addressTypes.includes(
|
|
720
|
+
if (this.addressTypes.includes(AddressType4.solana)) {
|
|
556
721
|
this.setupSolanaEvents();
|
|
557
722
|
}
|
|
558
|
-
if (this.addressTypes.includes(
|
|
723
|
+
if (this.addressTypes.includes(AddressType4.ethereum)) {
|
|
559
724
|
this.setupEthereumEvents();
|
|
560
725
|
}
|
|
561
726
|
}
|
|
562
727
|
setupSolanaEvents() {
|
|
563
728
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Setting up Solana event listeners");
|
|
564
|
-
const handleSolanaConnect = (publicKey) => {
|
|
729
|
+
const handleSolanaConnect = async (publicKey) => {
|
|
565
730
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Solana connect event received", { publicKey });
|
|
566
|
-
const solanaAddress = { addressType:
|
|
567
|
-
if (!this.addresses.find((addr) => addr.addressType ===
|
|
731
|
+
const solanaAddress = { addressType: AddressType4.solana, address: publicKey };
|
|
732
|
+
if (!this.addresses.find((addr) => addr.addressType === AddressType4.solana)) {
|
|
568
733
|
this.addresses.push(solanaAddress);
|
|
569
734
|
}
|
|
570
735
|
this.connected = true;
|
|
736
|
+
const authUserId = await this.getAuthUserId("Solana connect event");
|
|
571
737
|
this.emit("connect", {
|
|
572
738
|
addresses: this.addresses,
|
|
573
|
-
source: "injected-extension"
|
|
739
|
+
source: "injected-extension",
|
|
740
|
+
authUserId
|
|
574
741
|
});
|
|
575
742
|
};
|
|
576
743
|
const handleSolanaDisconnect = () => {
|
|
577
744
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Solana disconnect event received");
|
|
578
|
-
this.addresses = this.addresses.filter((addr) => addr.addressType !==
|
|
579
|
-
this.connected =
|
|
745
|
+
this.addresses = this.addresses.filter((addr) => addr.addressType !== AddressType4.solana);
|
|
746
|
+
this.connected = false;
|
|
580
747
|
this.emit("disconnect", {
|
|
581
748
|
source: "injected-extension"
|
|
582
749
|
});
|
|
583
750
|
};
|
|
584
|
-
const handleSolanaAccountChanged = (publicKey) => {
|
|
751
|
+
const handleSolanaAccountChanged = async (publicKey) => {
|
|
585
752
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Solana account changed event received", { publicKey });
|
|
586
|
-
const solanaIndex = this.addresses.findIndex((addr) => addr.addressType ===
|
|
753
|
+
const solanaIndex = this.addresses.findIndex((addr) => addr.addressType === AddressType4.solana);
|
|
587
754
|
if (solanaIndex >= 0) {
|
|
588
|
-
this.addresses[solanaIndex] = { addressType:
|
|
755
|
+
this.addresses[solanaIndex] = { addressType: AddressType4.solana, address: publicKey };
|
|
589
756
|
} else {
|
|
590
|
-
this.addresses.push({ addressType:
|
|
757
|
+
this.addresses.push({ addressType: AddressType4.solana, address: publicKey });
|
|
591
758
|
}
|
|
759
|
+
const authUserId = await this.getAuthUserId("Solana account changed event");
|
|
592
760
|
this.emit("connect", {
|
|
593
761
|
addresses: this.addresses,
|
|
594
|
-
source: "injected-extension-account-change"
|
|
762
|
+
source: "injected-extension-account-change",
|
|
763
|
+
authUserId
|
|
595
764
|
});
|
|
596
765
|
};
|
|
597
766
|
const cleanupConnect = this.phantom.solana.addEventListener("connect", handleSolanaConnect);
|
|
598
767
|
const cleanupDisconnect = this.phantom.solana.addEventListener("disconnect", handleSolanaDisconnect);
|
|
599
|
-
const cleanupAccountChanged = this.phantom.solana.addEventListener(
|
|
600
|
-
"accountChanged",
|
|
601
|
-
handleSolanaAccountChanged
|
|
602
|
-
);
|
|
768
|
+
const cleanupAccountChanged = this.phantom.solana.addEventListener("accountChanged", handleSolanaAccountChanged);
|
|
603
769
|
this.browserInjectedCleanupFunctions.push(cleanupConnect, cleanupDisconnect, cleanupAccountChanged);
|
|
604
770
|
}
|
|
605
771
|
setupEthereumEvents() {
|
|
606
772
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Setting up Ethereum event listeners");
|
|
607
|
-
const handleEthereumConnect = (accounts) => {
|
|
773
|
+
const handleEthereumConnect = async (accounts) => {
|
|
608
774
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Ethereum connect event received", { accounts });
|
|
609
|
-
this.addresses = this.addresses.filter((addr) => addr.addressType !==
|
|
775
|
+
this.addresses = this.addresses.filter((addr) => addr.addressType !== AddressType4.ethereum);
|
|
610
776
|
if (accounts && accounts.length > 0) {
|
|
611
777
|
this.addresses.push(
|
|
612
778
|
...accounts.map((address) => ({
|
|
613
|
-
addressType:
|
|
779
|
+
addressType: AddressType4.ethereum,
|
|
614
780
|
address
|
|
615
781
|
}))
|
|
616
782
|
);
|
|
617
783
|
}
|
|
618
784
|
this.connected = this.addresses.length > 0;
|
|
785
|
+
const authUserId = await this.getAuthUserId("Ethereum connect event");
|
|
619
786
|
this.emit("connect", {
|
|
620
787
|
addresses: this.addresses,
|
|
621
|
-
source: "injected-extension"
|
|
788
|
+
source: "injected-extension",
|
|
789
|
+
authUserId
|
|
622
790
|
});
|
|
623
791
|
};
|
|
624
792
|
const handleEthereumDisconnect = () => {
|
|
625
793
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Ethereum disconnect event received");
|
|
626
|
-
this.addresses = this.addresses.filter((addr) => addr.addressType !==
|
|
627
|
-
this.connected =
|
|
794
|
+
this.addresses = this.addresses.filter((addr) => addr.addressType !== AddressType4.ethereum);
|
|
795
|
+
this.connected = false;
|
|
628
796
|
this.emit("disconnect", {
|
|
629
797
|
source: "injected-extension"
|
|
630
798
|
});
|
|
631
799
|
};
|
|
632
|
-
const handleEthereumAccountsChanged = (accounts) => {
|
|
800
|
+
const handleEthereumAccountsChanged = async (accounts) => {
|
|
633
801
|
debug.log(DebugCategory.INJECTED_PROVIDER, "Ethereum accounts changed event received", { accounts });
|
|
634
|
-
this.addresses = this.addresses.filter((addr) => addr.addressType !==
|
|
802
|
+
this.addresses = this.addresses.filter((addr) => addr.addressType !== AddressType4.ethereum);
|
|
635
803
|
if (accounts && accounts.length > 0) {
|
|
636
804
|
this.addresses.push(
|
|
637
805
|
...accounts.map((address) => ({
|
|
638
|
-
addressType:
|
|
806
|
+
addressType: AddressType4.ethereum,
|
|
639
807
|
address
|
|
640
808
|
}))
|
|
641
809
|
);
|
|
810
|
+
const authUserId = await this.getAuthUserId("Ethereum accounts changed event");
|
|
811
|
+
this.emit("connect", {
|
|
812
|
+
addresses: this.addresses,
|
|
813
|
+
source: "injected-extension-account-change",
|
|
814
|
+
authUserId
|
|
815
|
+
});
|
|
816
|
+
} else {
|
|
817
|
+
this.connected = false;
|
|
818
|
+
this.emit("disconnect", {
|
|
819
|
+
source: "injected-extension-account-change"
|
|
820
|
+
});
|
|
642
821
|
}
|
|
643
|
-
this.emit("connect", {
|
|
644
|
-
addresses: this.addresses,
|
|
645
|
-
source: "injected-extension-account-change"
|
|
646
|
-
});
|
|
647
822
|
};
|
|
648
823
|
const cleanupConnect = this.phantom.ethereum.addEventListener("connect", handleEthereumConnect);
|
|
649
824
|
const cleanupDisconnect = this.phantom.ethereum.addEventListener("disconnect", handleEthereumDisconnect);
|
|
@@ -656,7 +831,7 @@ var InjectedProvider = class {
|
|
|
656
831
|
createCallbacks() {
|
|
657
832
|
return {
|
|
658
833
|
connect: async () => {
|
|
659
|
-
const result = await this.connect();
|
|
834
|
+
const result = await this.connect({ provider: "injected" });
|
|
660
835
|
return result.addresses;
|
|
661
836
|
},
|
|
662
837
|
disconnect: async () => {
|
|
@@ -761,6 +936,47 @@ var BrowserStorage = class {
|
|
|
761
936
|
};
|
|
762
937
|
});
|
|
763
938
|
}
|
|
939
|
+
async getShouldClearPreviousSession() {
|
|
940
|
+
debug.log(DebugCategory.STORAGE, "Getting shouldClearPreviousSession flag from IndexedDB");
|
|
941
|
+
const db = await this.getDB();
|
|
942
|
+
return new Promise((resolve, reject) => {
|
|
943
|
+
const transaction = db.transaction([this.storeName], "readonly");
|
|
944
|
+
const store = transaction.objectStore(this.storeName);
|
|
945
|
+
const request = store.get("shouldClearPreviousSession");
|
|
946
|
+
request.onsuccess = () => {
|
|
947
|
+
const shouldClear = request.result ?? false;
|
|
948
|
+
debug.log(DebugCategory.STORAGE, "Retrieved shouldClearPreviousSession flag from IndexedDB", {
|
|
949
|
+
shouldClear
|
|
950
|
+
});
|
|
951
|
+
resolve(shouldClear);
|
|
952
|
+
};
|
|
953
|
+
request.onerror = () => {
|
|
954
|
+
debug.error(DebugCategory.STORAGE, "Failed to get shouldClearPreviousSession flag from IndexedDB", {
|
|
955
|
+
error: request.error
|
|
956
|
+
});
|
|
957
|
+
reject(request.error);
|
|
958
|
+
};
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
async setShouldClearPreviousSession(should) {
|
|
962
|
+
debug.log(DebugCategory.STORAGE, "Setting shouldClearPreviousSession flag in IndexedDB", { should });
|
|
963
|
+
const db = await this.getDB();
|
|
964
|
+
return new Promise((resolve, reject) => {
|
|
965
|
+
const transaction = db.transaction([this.storeName], "readwrite");
|
|
966
|
+
const store = transaction.objectStore(this.storeName);
|
|
967
|
+
const request = store.put(should, "shouldClearPreviousSession");
|
|
968
|
+
request.onsuccess = () => {
|
|
969
|
+
debug.log(DebugCategory.STORAGE, "Successfully set shouldClearPreviousSession flag in IndexedDB");
|
|
970
|
+
resolve();
|
|
971
|
+
};
|
|
972
|
+
request.onerror = () => {
|
|
973
|
+
debug.error(DebugCategory.STORAGE, "Failed to set shouldClearPreviousSession flag in IndexedDB", {
|
|
974
|
+
error: request.error
|
|
975
|
+
});
|
|
976
|
+
reject(request.error);
|
|
977
|
+
};
|
|
978
|
+
});
|
|
979
|
+
}
|
|
764
980
|
};
|
|
765
981
|
|
|
766
982
|
// src/providers/embedded/adapters/url-params.ts
|
|
@@ -772,15 +988,160 @@ var BrowserURLParamsAccessor = class {
|
|
|
772
988
|
};
|
|
773
989
|
var browserUrlParamsAccessor = new BrowserURLParamsAccessor();
|
|
774
990
|
|
|
775
|
-
// src/
|
|
776
|
-
|
|
777
|
-
|
|
991
|
+
// src/providers/embedded/adapters/auth.ts
|
|
992
|
+
import { DEFAULT_AUTH_URL } from "@phantom/constants";
|
|
993
|
+
|
|
994
|
+
// src/utils/browser-detection.ts
|
|
995
|
+
function parseBrowserFromUserAgent(userAgent, hasBraveAPI) {
|
|
996
|
+
let name = "unknown";
|
|
997
|
+
let version = "unknown";
|
|
998
|
+
if (!userAgent || typeof userAgent !== "string") {
|
|
999
|
+
return { name, version, userAgent: "unknown" };
|
|
1000
|
+
}
|
|
1001
|
+
try {
|
|
1002
|
+
if (userAgent.includes("Edg/")) {
|
|
1003
|
+
name = "edge";
|
|
1004
|
+
const match = userAgent.match(/Edg\/([0-9]+(?:\.[0-9]+)*)/);
|
|
1005
|
+
if (match)
|
|
1006
|
+
version = match[1].split(".")[0];
|
|
1007
|
+
} else if (userAgent.includes("OPR/") || userAgent.includes("Opera/")) {
|
|
1008
|
+
name = "opera";
|
|
1009
|
+
const match = userAgent.match(/(?:OPR|Opera)\/([0-9]+(?:\.[0-9]+)*)/);
|
|
1010
|
+
if (match)
|
|
1011
|
+
version = match[1].split(".")[0];
|
|
1012
|
+
} else if (userAgent.includes("SamsungBrowser/")) {
|
|
1013
|
+
name = "samsung";
|
|
1014
|
+
const match = userAgent.match(/SamsungBrowser\/([0-9]+(?:\.[0-9]+)*)/);
|
|
1015
|
+
if (match)
|
|
1016
|
+
version = match[1].split(".")[0];
|
|
1017
|
+
} else if (userAgent.includes("DuckDuckGo/")) {
|
|
1018
|
+
name = "duckduckgo";
|
|
1019
|
+
const match = userAgent.match(/DuckDuckGo\/([0-9]+(?:\.[0-9]+)*)/);
|
|
1020
|
+
if (match)
|
|
1021
|
+
version = match[1].split(".")[0];
|
|
1022
|
+
} else if (userAgent.includes("Chrome/") && hasBraveAPI) {
|
|
1023
|
+
name = "brave";
|
|
1024
|
+
const match = userAgent.match(/Chrome\/([0-9]+(?:\.[0-9]+)*)/);
|
|
1025
|
+
if (match)
|
|
1026
|
+
version = match[1].split(".")[0];
|
|
1027
|
+
} else if (userAgent.includes("Mobile/") || userAgent.includes("Android")) {
|
|
1028
|
+
if (userAgent.includes("Chrome/")) {
|
|
1029
|
+
name = "chrome-mobile";
|
|
1030
|
+
const match = userAgent.match(/Chrome\/([0-9]+(?:\.[0-9]+)*)/);
|
|
1031
|
+
if (match)
|
|
1032
|
+
version = match[1].split(".")[0];
|
|
1033
|
+
} else if (userAgent.includes("Firefox/")) {
|
|
1034
|
+
name = "firefox-mobile";
|
|
1035
|
+
const match = userAgent.match(/Firefox\/([0-9]+(?:\.[0-9]+)*)/);
|
|
1036
|
+
if (match)
|
|
1037
|
+
version = match[1].split(".")[0];
|
|
1038
|
+
} else if (userAgent.includes("Safari/") && userAgent.includes("Mobile/")) {
|
|
1039
|
+
name = "safari-mobile";
|
|
1040
|
+
const match = userAgent.match(/Version\/([0-9]+(?:\.[0-9]+)*)/);
|
|
1041
|
+
if (match)
|
|
1042
|
+
version = match[1].split(".")[0];
|
|
1043
|
+
} else {
|
|
1044
|
+
name = "mobile";
|
|
1045
|
+
}
|
|
1046
|
+
} else if (userAgent.includes("Chrome/")) {
|
|
1047
|
+
name = "chrome";
|
|
1048
|
+
const match = userAgent.match(/Chrome\/([0-9]+(?:\.[0-9]+)*)/);
|
|
1049
|
+
if (match)
|
|
1050
|
+
version = match[1].split(".")[0];
|
|
1051
|
+
} else if (userAgent.includes("Firefox/")) {
|
|
1052
|
+
name = "firefox";
|
|
1053
|
+
const match = userAgent.match(/Firefox\/([0-9]+(?:\.[0-9]+)*)/);
|
|
1054
|
+
if (match)
|
|
1055
|
+
version = match[1].split(".")[0];
|
|
1056
|
+
} else if (userAgent.includes("Safari/") && !userAgent.includes("Chrome/")) {
|
|
1057
|
+
name = "safari";
|
|
1058
|
+
const match = userAgent.match(/Version\/([0-9]+(?:\.[0-9]+)*)/);
|
|
1059
|
+
if (match)
|
|
1060
|
+
version = match[1].split(".")[0];
|
|
1061
|
+
}
|
|
1062
|
+
if (name === "unknown") {
|
|
1063
|
+
const patterns = [
|
|
1064
|
+
{ regex: /Chrome\/([0-9]+)/, name: "chrome" },
|
|
1065
|
+
{ regex: /Firefox\/([0-9]+)/, name: "firefox" },
|
|
1066
|
+
{ regex: /Safari\/([0-9]+)/, name: "safari" },
|
|
1067
|
+
{ regex: /Edge\/([0-9]+)/, name: "edge" },
|
|
1068
|
+
{ regex: /Opera\/([0-9]+)/, name: "opera" }
|
|
1069
|
+
];
|
|
1070
|
+
for (const pattern of patterns) {
|
|
1071
|
+
const match = userAgent.match(pattern.regex);
|
|
1072
|
+
if (match) {
|
|
1073
|
+
name = pattern.name;
|
|
1074
|
+
version = match[1];
|
|
1075
|
+
break;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
} catch (error) {
|
|
1080
|
+
}
|
|
1081
|
+
return { name, version, userAgent };
|
|
1082
|
+
}
|
|
1083
|
+
function detectBrowser() {
|
|
1084
|
+
if (typeof window === "undefined" || !window.navigator?.userAgent) {
|
|
1085
|
+
return { name: "unknown", version: "unknown", userAgent: "unknown" };
|
|
1086
|
+
}
|
|
1087
|
+
const userAgent = window.navigator.userAgent;
|
|
1088
|
+
const hasBraveAPI = !!navigator.brave;
|
|
1089
|
+
return parseBrowserFromUserAgent(userAgent, hasBraveAPI);
|
|
1090
|
+
}
|
|
1091
|
+
function getPlatformName() {
|
|
1092
|
+
const { name, version } = detectBrowser();
|
|
1093
|
+
return version !== "unknown" ? `${name}-v${version}` : name;
|
|
1094
|
+
}
|
|
1095
|
+
function getBrowserDisplayName() {
|
|
1096
|
+
const { name, version } = detectBrowser();
|
|
1097
|
+
const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
1098
|
+
return version !== "unknown" ? `${capitalizedName} ${version}` : capitalizedName;
|
|
1099
|
+
}
|
|
1100
|
+
function isMobileDevice() {
|
|
1101
|
+
if (typeof window === "undefined" || !window.navigator?.userAgent) {
|
|
1102
|
+
return false;
|
|
1103
|
+
}
|
|
1104
|
+
const userAgent = window.navigator.userAgent.toLowerCase();
|
|
1105
|
+
const mobilePatterns = [
|
|
1106
|
+
/android/,
|
|
1107
|
+
/iphone|ipad|ipod/,
|
|
1108
|
+
/blackberry/,
|
|
1109
|
+
/windows phone/,
|
|
1110
|
+
/mobile/,
|
|
1111
|
+
/tablet/,
|
|
1112
|
+
/silk/,
|
|
1113
|
+
/kindle/,
|
|
1114
|
+
/opera mini/,
|
|
1115
|
+
/opera mobi/
|
|
1116
|
+
];
|
|
1117
|
+
const isMobileUA = mobilePatterns.some((pattern) => pattern.test(userAgent));
|
|
1118
|
+
let isSmallScreen = false;
|
|
1119
|
+
try {
|
|
1120
|
+
isSmallScreen = window.screen.width <= 768 || window.screen.height <= 768;
|
|
1121
|
+
} catch (error) {
|
|
1122
|
+
isSmallScreen = false;
|
|
1123
|
+
}
|
|
1124
|
+
let isTouchDevice = false;
|
|
1125
|
+
try {
|
|
1126
|
+
isTouchDevice = "ontouchstart" in window || navigator.maxTouchPoints > 0;
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
isTouchDevice = false;
|
|
1129
|
+
}
|
|
1130
|
+
return isMobileUA || isSmallScreen && isTouchDevice;
|
|
1131
|
+
}
|
|
778
1132
|
|
|
779
1133
|
// src/providers/embedded/adapters/auth.ts
|
|
780
1134
|
var BrowserAuthProvider = class {
|
|
781
1135
|
constructor(urlParamsAccessor) {
|
|
782
1136
|
this.urlParamsAccessor = urlParamsAccessor;
|
|
783
1137
|
}
|
|
1138
|
+
getValidatedCurrentUrl() {
|
|
1139
|
+
const currentUrl = window.location.href;
|
|
1140
|
+
if (!currentUrl.startsWith("http:") && !currentUrl.startsWith("https:")) {
|
|
1141
|
+
throw new Error("Invalid URL protocol - only HTTP/HTTPS URLs are supported");
|
|
1142
|
+
}
|
|
1143
|
+
return currentUrl;
|
|
1144
|
+
}
|
|
784
1145
|
authenticate(options) {
|
|
785
1146
|
return new Promise((resolve) => {
|
|
786
1147
|
if ("jwtToken" in options) {
|
|
@@ -788,22 +1149,24 @@ var BrowserAuthProvider = class {
|
|
|
788
1149
|
}
|
|
789
1150
|
const phantomOptions = options;
|
|
790
1151
|
debug.info(DebugCategory.PHANTOM_CONNECT_AUTH, "Starting Phantom Connect authentication", {
|
|
791
|
-
|
|
792
|
-
parentOrganizationId: phantomOptions.parentOrganizationId,
|
|
1152
|
+
publicKey: phantomOptions.publicKey,
|
|
793
1153
|
appId: phantomOptions.appId,
|
|
794
1154
|
provider: phantomOptions.provider,
|
|
795
|
-
authUrl: phantomOptions.authUrl
|
|
796
|
-
hasCustomData: !!phantomOptions.customAuthData
|
|
1155
|
+
authUrl: phantomOptions.authUrl
|
|
797
1156
|
});
|
|
798
1157
|
const baseUrl = phantomOptions.authUrl || DEFAULT_AUTH_URL;
|
|
799
1158
|
debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Using auth URL", { baseUrl });
|
|
800
1159
|
const params = new URLSearchParams({
|
|
801
|
-
|
|
802
|
-
parent_organization_id: phantomOptions.parentOrganizationId,
|
|
1160
|
+
public_key: phantomOptions.publicKey,
|
|
803
1161
|
app_id: phantomOptions.appId,
|
|
804
|
-
redirect_uri: phantomOptions.redirectUrl || (typeof window !== "undefined" ?
|
|
1162
|
+
redirect_uri: phantomOptions.redirectUrl || (typeof window !== "undefined" ? this.getValidatedCurrentUrl() : ""),
|
|
805
1163
|
session_id: phantomOptions.sessionId,
|
|
806
|
-
|
|
1164
|
+
// OAuth session management - defaults to allow refresh unless explicitly clearing after logout
|
|
1165
|
+
clear_previous_session: (phantomOptions.clearPreviousSession ?? false).toString(),
|
|
1166
|
+
allow_refresh: (phantomOptions.allowRefresh ?? true).toString(),
|
|
1167
|
+
sdk_version: "1.0.0-beta.21",
|
|
1168
|
+
sdk_type: "browser",
|
|
1169
|
+
platform: detectBrowser().name
|
|
807
1170
|
});
|
|
808
1171
|
if (phantomOptions.provider) {
|
|
809
1172
|
debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Provider specified, will skip selection", {
|
|
@@ -814,13 +1177,8 @@ var BrowserAuthProvider = class {
|
|
|
814
1177
|
debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "No provider specified, defaulting to Google");
|
|
815
1178
|
params.append("provider", "google");
|
|
816
1179
|
}
|
|
817
|
-
if (phantomOptions.customAuthData) {
|
|
818
|
-
debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Adding custom auth data");
|
|
819
|
-
params.append("authData", JSON.stringify(phantomOptions.customAuthData));
|
|
820
|
-
}
|
|
821
1180
|
const authContext = {
|
|
822
|
-
|
|
823
|
-
parentOrganizationId: phantomOptions.parentOrganizationId,
|
|
1181
|
+
publicKey: phantomOptions.publicKey,
|
|
824
1182
|
appId: phantomOptions.appId,
|
|
825
1183
|
provider: phantomOptions.provider,
|
|
826
1184
|
sessionId: phantomOptions.sessionId
|
|
@@ -829,6 +1187,9 @@ var BrowserAuthProvider = class {
|
|
|
829
1187
|
debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Stored auth context in session storage", { authContext });
|
|
830
1188
|
const authUrl = `${baseUrl}?${params.toString()}`;
|
|
831
1189
|
debug.info(DebugCategory.PHANTOM_CONNECT_AUTH, "Redirecting to Phantom Connect", { authUrl });
|
|
1190
|
+
if (!authUrl.startsWith("https:") && !authUrl.startsWith("http://localhost")) {
|
|
1191
|
+
throw new Error("Invalid auth URL - only HTTPS URLs are allowed for authentication");
|
|
1192
|
+
}
|
|
832
1193
|
window.location.href = authUrl;
|
|
833
1194
|
resolve();
|
|
834
1195
|
});
|
|
@@ -884,10 +1245,32 @@ var BrowserAuthProvider = class {
|
|
|
884
1245
|
sessionId,
|
|
885
1246
|
accountDerivationIndex: accountDerivationIndex ? parseInt(accountDerivationIndex) : void 0
|
|
886
1247
|
});
|
|
1248
|
+
const organizationId = this.urlParamsAccessor.getParam("organization_id");
|
|
1249
|
+
const expiresInMs = this.urlParamsAccessor.getParam("expires_in_ms");
|
|
1250
|
+
const authUserId = this.urlParamsAccessor.getParam("auth_user_id");
|
|
1251
|
+
debug.log(DebugCategory.PHANTOM_CONNECT_AUTH, "Auth redirect parameters", {
|
|
1252
|
+
walletId,
|
|
1253
|
+
organizationId,
|
|
1254
|
+
sessionId,
|
|
1255
|
+
accountDerivationIndex,
|
|
1256
|
+
expiresInMs,
|
|
1257
|
+
authUserId
|
|
1258
|
+
});
|
|
1259
|
+
if (!organizationId) {
|
|
1260
|
+
debug.error(DebugCategory.PHANTOM_CONNECT_AUTH, "Missing organization_id in auth response");
|
|
1261
|
+
throw new Error("Missing organization_id in auth response");
|
|
1262
|
+
}
|
|
1263
|
+
if (organizationId.startsWith("temp-")) {
|
|
1264
|
+
debug.warn(DebugCategory.PHANTOM_CONNECT_AUTH, "Received temporary organization_id, server may not be configured properly", {
|
|
1265
|
+
organizationId
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
887
1268
|
return {
|
|
888
1269
|
walletId,
|
|
889
|
-
|
|
890
|
-
accountDerivationIndex: accountDerivationIndex ? parseInt(accountDerivationIndex) :
|
|
1270
|
+
organizationId,
|
|
1271
|
+
accountDerivationIndex: accountDerivationIndex ? parseInt(accountDerivationIndex) : 0,
|
|
1272
|
+
expiresInMs: expiresInMs ? parseInt(expiresInMs) : 0,
|
|
1273
|
+
authUserId: authUserId || void 0
|
|
891
1274
|
};
|
|
892
1275
|
} catch (error) {
|
|
893
1276
|
sessionStorage.removeItem("phantom-auth-context");
|
|
@@ -896,6 +1279,105 @@ var BrowserAuthProvider = class {
|
|
|
896
1279
|
}
|
|
897
1280
|
};
|
|
898
1281
|
|
|
1282
|
+
// src/providers/embedded/adapters/phantom-app.ts
|
|
1283
|
+
import { isPhantomExtensionInstalled as isPhantomExtensionInstalled2 } from "@phantom/browser-injected-sdk";
|
|
1284
|
+
|
|
1285
|
+
// src/isPhantomLoginAvailable.ts
|
|
1286
|
+
import { isPhantomExtensionInstalled } from "@phantom/browser-injected-sdk";
|
|
1287
|
+
async function isPhantomLoginAvailable(timeoutMs = 3e3) {
|
|
1288
|
+
const extensionInstalled = await waitForExtension(timeoutMs);
|
|
1289
|
+
if (!extensionInstalled) {
|
|
1290
|
+
return false;
|
|
1291
|
+
}
|
|
1292
|
+
try {
|
|
1293
|
+
if (!window.phantom?.app?.features || typeof window.phantom.app.features !== "function") {
|
|
1294
|
+
return false;
|
|
1295
|
+
}
|
|
1296
|
+
const response = await window.phantom.app.features();
|
|
1297
|
+
if (!Array.isArray(response.features)) {
|
|
1298
|
+
return false;
|
|
1299
|
+
}
|
|
1300
|
+
return response.features.includes("phantom_login");
|
|
1301
|
+
} catch (error) {
|
|
1302
|
+
console.error("Error checking Phantom extension features", error);
|
|
1303
|
+
return false;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
async function waitForExtension(timeoutMs) {
|
|
1307
|
+
return new Promise((resolve) => {
|
|
1308
|
+
const startTime = Date.now();
|
|
1309
|
+
const checkInterval = 100;
|
|
1310
|
+
const checkForExtension = () => {
|
|
1311
|
+
try {
|
|
1312
|
+
if (isPhantomExtensionInstalled()) {
|
|
1313
|
+
resolve(true);
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
} catch (error) {
|
|
1317
|
+
}
|
|
1318
|
+
const elapsed = Date.now() - startTime;
|
|
1319
|
+
if (elapsed >= timeoutMs) {
|
|
1320
|
+
resolve(false);
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
setTimeout(checkForExtension, checkInterval);
|
|
1324
|
+
};
|
|
1325
|
+
checkForExtension();
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
// src/providers/embedded/adapters/phantom-app.ts
|
|
1330
|
+
var BrowserPhantomAppProvider = class {
|
|
1331
|
+
/**
|
|
1332
|
+
* Check if the Phantom extension is installed in the browser
|
|
1333
|
+
*/
|
|
1334
|
+
isAvailable() {
|
|
1335
|
+
return isPhantomExtensionInstalled2();
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Authenticate using the Phantom browser extension
|
|
1339
|
+
*/
|
|
1340
|
+
async authenticate(options) {
|
|
1341
|
+
if (!this.isAvailable()) {
|
|
1342
|
+
throw new Error(
|
|
1343
|
+
"Phantom extension is not installed. Please install the Phantom browser extension to use this authentication method."
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
const loginAvailable = await isPhantomLoginAvailable();
|
|
1347
|
+
if (!loginAvailable) {
|
|
1348
|
+
throw new Error(
|
|
1349
|
+
"Phantom Login is not available. Please update your Phantom extension to use this authentication method."
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
try {
|
|
1353
|
+
if (!window.phantom?.app?.login) {
|
|
1354
|
+
throw new Error("Phantom extension login method not found");
|
|
1355
|
+
}
|
|
1356
|
+
const result = await window.phantom.app.login({
|
|
1357
|
+
publicKey: options.publicKey,
|
|
1358
|
+
appId: options.appId,
|
|
1359
|
+
sessionId: options.sessionId
|
|
1360
|
+
});
|
|
1361
|
+
if (!result || !result.walletId || !result.organizationId) {
|
|
1362
|
+
throw new Error("Invalid authentication response from Phantom extension");
|
|
1363
|
+
}
|
|
1364
|
+
return {
|
|
1365
|
+
walletId: result.walletId,
|
|
1366
|
+
organizationId: result.organizationId,
|
|
1367
|
+
provider: "phantom",
|
|
1368
|
+
accountDerivationIndex: result.accountDerivationIndex ?? 0,
|
|
1369
|
+
expiresInMs: result.expiresInMs ?? 0,
|
|
1370
|
+
authUserId: result.authUserId
|
|
1371
|
+
};
|
|
1372
|
+
} catch (error) {
|
|
1373
|
+
if (error instanceof Error) {
|
|
1374
|
+
throw error;
|
|
1375
|
+
}
|
|
1376
|
+
throw new Error(`Phantom extension authentication failed: ${String(error)}`);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
};
|
|
1380
|
+
|
|
899
1381
|
// src/providers/embedded/adapters/logger.ts
|
|
900
1382
|
var BrowserLogger = class {
|
|
901
1383
|
info(category, message, data) {
|
|
@@ -912,131 +1394,38 @@ var BrowserLogger = class {
|
|
|
912
1394
|
}
|
|
913
1395
|
};
|
|
914
1396
|
|
|
915
|
-
// src/utils/browser-detection.ts
|
|
916
|
-
function parseBrowserFromUserAgent(userAgent, hasBraveAPI) {
|
|
917
|
-
let name = "unknown";
|
|
918
|
-
let version = "unknown";
|
|
919
|
-
if (!userAgent || typeof userAgent !== "string") {
|
|
920
|
-
return { name, version };
|
|
921
|
-
}
|
|
922
|
-
try {
|
|
923
|
-
if (userAgent.includes("Edg/")) {
|
|
924
|
-
name = "edge";
|
|
925
|
-
const match = userAgent.match(/Edg\/([0-9]+(?:\.[0-9]+)*)/);
|
|
926
|
-
if (match)
|
|
927
|
-
version = match[1].split(".")[0];
|
|
928
|
-
} else if (userAgent.includes("OPR/") || userAgent.includes("Opera/")) {
|
|
929
|
-
name = "opera";
|
|
930
|
-
const match = userAgent.match(/(?:OPR|Opera)\/([0-9]+(?:\.[0-9]+)*)/);
|
|
931
|
-
if (match)
|
|
932
|
-
version = match[1].split(".")[0];
|
|
933
|
-
} else if (userAgent.includes("SamsungBrowser/")) {
|
|
934
|
-
name = "samsung";
|
|
935
|
-
const match = userAgent.match(/SamsungBrowser\/([0-9]+(?:\.[0-9]+)*)/);
|
|
936
|
-
if (match)
|
|
937
|
-
version = match[1].split(".")[0];
|
|
938
|
-
} else if (userAgent.includes("DuckDuckGo/")) {
|
|
939
|
-
name = "duckduckgo";
|
|
940
|
-
const match = userAgent.match(/DuckDuckGo\/([0-9]+(?:\.[0-9]+)*)/);
|
|
941
|
-
if (match)
|
|
942
|
-
version = match[1].split(".")[0];
|
|
943
|
-
} else if (userAgent.includes("Chrome/") && hasBraveAPI) {
|
|
944
|
-
name = "brave";
|
|
945
|
-
const match = userAgent.match(/Chrome\/([0-9]+(?:\.[0-9]+)*)/);
|
|
946
|
-
if (match)
|
|
947
|
-
version = match[1].split(".")[0];
|
|
948
|
-
} else if (userAgent.includes("Mobile/") || userAgent.includes("Android")) {
|
|
949
|
-
if (userAgent.includes("Chrome/")) {
|
|
950
|
-
name = "chrome-mobile";
|
|
951
|
-
const match = userAgent.match(/Chrome\/([0-9]+(?:\.[0-9]+)*)/);
|
|
952
|
-
if (match)
|
|
953
|
-
version = match[1].split(".")[0];
|
|
954
|
-
} else if (userAgent.includes("Firefox/")) {
|
|
955
|
-
name = "firefox-mobile";
|
|
956
|
-
const match = userAgent.match(/Firefox\/([0-9]+(?:\.[0-9]+)*)/);
|
|
957
|
-
if (match)
|
|
958
|
-
version = match[1].split(".")[0];
|
|
959
|
-
} else if (userAgent.includes("Safari/") && userAgent.includes("Mobile/")) {
|
|
960
|
-
name = "safari-mobile";
|
|
961
|
-
const match = userAgent.match(/Version\/([0-9]+(?:\.[0-9]+)*)/);
|
|
962
|
-
if (match)
|
|
963
|
-
version = match[1].split(".")[0];
|
|
964
|
-
} else {
|
|
965
|
-
name = "mobile";
|
|
966
|
-
}
|
|
967
|
-
} else if (userAgent.includes("Chrome/")) {
|
|
968
|
-
name = "chrome";
|
|
969
|
-
const match = userAgent.match(/Chrome\/([0-9]+(?:\.[0-9]+)*)/);
|
|
970
|
-
if (match)
|
|
971
|
-
version = match[1].split(".")[0];
|
|
972
|
-
} else if (userAgent.includes("Firefox/")) {
|
|
973
|
-
name = "firefox";
|
|
974
|
-
const match = userAgent.match(/Firefox\/([0-9]+(?:\.[0-9]+)*)/);
|
|
975
|
-
if (match)
|
|
976
|
-
version = match[1].split(".")[0];
|
|
977
|
-
} else if (userAgent.includes("Safari/") && !userAgent.includes("Chrome/")) {
|
|
978
|
-
name = "safari";
|
|
979
|
-
const match = userAgent.match(/Version\/([0-9]+(?:\.[0-9]+)*)/);
|
|
980
|
-
if (match)
|
|
981
|
-
version = match[1].split(".")[0];
|
|
982
|
-
}
|
|
983
|
-
if (name === "unknown") {
|
|
984
|
-
const patterns = [
|
|
985
|
-
{ regex: /Chrome\/([0-9]+)/, name: "chrome" },
|
|
986
|
-
{ regex: /Firefox\/([0-9]+)/, name: "firefox" },
|
|
987
|
-
{ regex: /Safari\/([0-9]+)/, name: "safari" },
|
|
988
|
-
{ regex: /Edge\/([0-9]+)/, name: "edge" },
|
|
989
|
-
{ regex: /Opera\/([0-9]+)/, name: "opera" }
|
|
990
|
-
];
|
|
991
|
-
for (const pattern of patterns) {
|
|
992
|
-
const match = userAgent.match(pattern.regex);
|
|
993
|
-
if (match) {
|
|
994
|
-
name = pattern.name;
|
|
995
|
-
version = match[1];
|
|
996
|
-
break;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
} catch (error) {
|
|
1001
|
-
}
|
|
1002
|
-
return { name, version };
|
|
1003
|
-
}
|
|
1004
|
-
function detectBrowser() {
|
|
1005
|
-
if (typeof window === "undefined" || !window.navigator?.userAgent) {
|
|
1006
|
-
return { name: "unknown", version: "unknown" };
|
|
1007
|
-
}
|
|
1008
|
-
const userAgent = window.navigator.userAgent;
|
|
1009
|
-
const hasBraveAPI = !!navigator.brave;
|
|
1010
|
-
return parseBrowserFromUserAgent(userAgent, hasBraveAPI);
|
|
1011
|
-
}
|
|
1012
|
-
function getPlatformName() {
|
|
1013
|
-
const { name, version } = detectBrowser();
|
|
1014
|
-
return version !== "unknown" ? `${name}-v${version}` : name;
|
|
1015
|
-
}
|
|
1016
|
-
function getBrowserDisplayName() {
|
|
1017
|
-
const { name, version } = detectBrowser();
|
|
1018
|
-
const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
1019
|
-
return version !== "unknown" ? `${capitalizedName} ${version}` : capitalizedName;
|
|
1020
|
-
}
|
|
1021
|
-
|
|
1022
1397
|
// src/providers/embedded/index.ts
|
|
1398
|
+
import { ANALYTICS_HEADERS } from "@phantom/constants";
|
|
1023
1399
|
var EmbeddedProvider = class extends CoreEmbeddedProvider {
|
|
1024
1400
|
constructor(config) {
|
|
1025
1401
|
debug.log(DebugCategory.EMBEDDED_PROVIDER, "Initializing Browser EmbeddedProvider", { config });
|
|
1026
1402
|
const urlParamsAccessor = new BrowserURLParamsAccessor();
|
|
1027
1403
|
const stamper = new IndexedDbStamper({
|
|
1028
|
-
dbName: `phantom-embedded-sdk-${config.
|
|
1404
|
+
dbName: `phantom-embedded-sdk-${config.appId}`,
|
|
1029
1405
|
storeName: "crypto-keys",
|
|
1030
1406
|
keyName: "signing-key"
|
|
1031
1407
|
});
|
|
1032
1408
|
const platformName = getPlatformName();
|
|
1409
|
+
const { name: browserName, version } = detectBrowser();
|
|
1033
1410
|
const platform = {
|
|
1034
1411
|
storage: new BrowserStorage(),
|
|
1035
1412
|
authProvider: new BrowserAuthProvider(urlParamsAccessor),
|
|
1413
|
+
phantomAppProvider: new BrowserPhantomAppProvider(),
|
|
1036
1414
|
urlParamsAccessor,
|
|
1037
1415
|
stamper,
|
|
1038
|
-
name: platformName
|
|
1416
|
+
name: platformName,
|
|
1039
1417
|
// Use detected browser name and version for identification
|
|
1418
|
+
analyticsHeaders: {
|
|
1419
|
+
[ANALYTICS_HEADERS.SDK_TYPE]: "browser",
|
|
1420
|
+
[ANALYTICS_HEADERS.PLATFORM]: browserName,
|
|
1421
|
+
// firefox, chrome, safari, etc.
|
|
1422
|
+
[ANALYTICS_HEADERS.PLATFORM_VERSION]: version,
|
|
1423
|
+
// Full user agent for more detailed info
|
|
1424
|
+
[ANALYTICS_HEADERS.APP_ID]: config.appId,
|
|
1425
|
+
[ANALYTICS_HEADERS.WALLET_TYPE]: config.embeddedWalletType,
|
|
1426
|
+
[ANALYTICS_HEADERS.SDK_VERSION]: "1.0.0-beta.21"
|
|
1427
|
+
// Replaced at build time
|
|
1428
|
+
}
|
|
1040
1429
|
};
|
|
1041
1430
|
debug.log(DebugCategory.EMBEDDED_PROVIDER, "Detected platform", { platformName });
|
|
1042
1431
|
const logger = new BrowserLogger();
|
|
@@ -1045,6 +1434,26 @@ var EmbeddedProvider = class extends CoreEmbeddedProvider {
|
|
|
1045
1434
|
}
|
|
1046
1435
|
};
|
|
1047
1436
|
|
|
1437
|
+
// src/ProviderManager.ts
|
|
1438
|
+
import { DEFAULT_WALLET_API_URL, DEFAULT_EMBEDDED_WALLET_TYPE, DEFAULT_AUTH_URL as DEFAULT_AUTH_URL2 } from "@phantom/constants";
|
|
1439
|
+
|
|
1440
|
+
// src/utils/auth-callback.ts
|
|
1441
|
+
function isAuthFailureCallback(searchParams) {
|
|
1442
|
+
if (typeof window === "undefined" && !searchParams)
|
|
1443
|
+
return false;
|
|
1444
|
+
const params = searchParams || new URLSearchParams(window.location.search);
|
|
1445
|
+
const responseType = params.get("response_type");
|
|
1446
|
+
const sessionId = params.get("session_id");
|
|
1447
|
+
return responseType === "failure" && !!sessionId;
|
|
1448
|
+
}
|
|
1449
|
+
function isAuthCallbackUrl(searchParams) {
|
|
1450
|
+
if (typeof window === "undefined" && !searchParams)
|
|
1451
|
+
return false;
|
|
1452
|
+
const params = searchParams || new URLSearchParams(window.location.search);
|
|
1453
|
+
const sessionId = params.get("session_id");
|
|
1454
|
+
return !!(sessionId && (params.has("response_type") || params.has("wallet_id")));
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1048
1457
|
// src/ProviderManager.ts
|
|
1049
1458
|
var ProviderManager = class {
|
|
1050
1459
|
// Track which providers have forwarding set up
|
|
@@ -1052,7 +1461,6 @@ var ProviderManager = class {
|
|
|
1052
1461
|
this.providers = /* @__PURE__ */ new Map();
|
|
1053
1462
|
this.currentProvider = null;
|
|
1054
1463
|
this.currentProviderKey = null;
|
|
1055
|
-
this.walletId = null;
|
|
1056
1464
|
// Event management for forwarding provider events
|
|
1057
1465
|
this.eventListeners = /* @__PURE__ */ new Map();
|
|
1058
1466
|
this.providerForwardingSetup = /* @__PURE__ */ new WeakSet();
|
|
@@ -1064,6 +1472,15 @@ var ProviderManager = class {
|
|
|
1064
1472
|
currentProviderKey: this.currentProviderKey
|
|
1065
1473
|
});
|
|
1066
1474
|
}
|
|
1475
|
+
getValidatedCurrentUrl() {
|
|
1476
|
+
if (typeof window === "undefined")
|
|
1477
|
+
return "";
|
|
1478
|
+
const currentUrl = window.location.href;
|
|
1479
|
+
if (!currentUrl.startsWith("http:") && !currentUrl.startsWith("https:")) {
|
|
1480
|
+
throw new Error("Invalid URL protocol - only HTTP/HTTPS URLs are supported");
|
|
1481
|
+
}
|
|
1482
|
+
return currentUrl;
|
|
1483
|
+
}
|
|
1067
1484
|
/**
|
|
1068
1485
|
* Switch to a different provider type
|
|
1069
1486
|
*/
|
|
@@ -1079,7 +1496,6 @@ var ProviderManager = class {
|
|
|
1079
1496
|
}
|
|
1080
1497
|
this.currentProvider = this.providers.get(key);
|
|
1081
1498
|
this.currentProviderKey = key;
|
|
1082
|
-
this.walletId = null;
|
|
1083
1499
|
this.ensureProviderEventForwarding();
|
|
1084
1500
|
return this.currentProvider;
|
|
1085
1501
|
}
|
|
@@ -1095,7 +1511,8 @@ var ProviderManager = class {
|
|
|
1095
1511
|
getCurrentProviderInfo() {
|
|
1096
1512
|
if (!this.currentProviderKey)
|
|
1097
1513
|
return null;
|
|
1098
|
-
const
|
|
1514
|
+
const parts = this.currentProviderKey.split("-");
|
|
1515
|
+
const [type, embeddedWalletType] = parts;
|
|
1099
1516
|
return {
|
|
1100
1517
|
type,
|
|
1101
1518
|
embeddedWalletType
|
|
@@ -1103,27 +1520,51 @@ var ProviderManager = class {
|
|
|
1103
1520
|
}
|
|
1104
1521
|
/**
|
|
1105
1522
|
* Connect using the current provider
|
|
1523
|
+
* Automatically switches provider based on authOptions.provider
|
|
1106
1524
|
*/
|
|
1107
1525
|
async connect(authOptions) {
|
|
1108
1526
|
debug.info(DebugCategory.PROVIDER_MANAGER, "Starting connection", {
|
|
1109
1527
|
currentProviderKey: this.currentProviderKey,
|
|
1110
|
-
authOptions:
|
|
1528
|
+
authOptions: { provider: authOptions.provider, hasJwtToken: !!authOptions.jwtToken }
|
|
1111
1529
|
});
|
|
1530
|
+
const requestedProvider = authOptions.provider;
|
|
1531
|
+
let targetProviderType = null;
|
|
1532
|
+
if (requestedProvider === "injected") {
|
|
1533
|
+
targetProviderType = "injected";
|
|
1534
|
+
} else if (["google", "apple", "jwt", "phantom"].includes(requestedProvider)) {
|
|
1535
|
+
targetProviderType = "embedded";
|
|
1536
|
+
}
|
|
1537
|
+
if (targetProviderType) {
|
|
1538
|
+
const currentInfo = this.getCurrentProviderInfo();
|
|
1539
|
+
if (currentInfo?.type !== targetProviderType) {
|
|
1540
|
+
debug.log(DebugCategory.PROVIDER_MANAGER, "Auto-switching provider based on auth options", {
|
|
1541
|
+
from: currentInfo?.type,
|
|
1542
|
+
to: targetProviderType,
|
|
1543
|
+
requestedProvider
|
|
1544
|
+
});
|
|
1545
|
+
const switchOptions = {};
|
|
1546
|
+
if (targetProviderType === "embedded") {
|
|
1547
|
+
switchOptions.embeddedWalletType = currentInfo?.embeddedWalletType || this.config.embeddedWalletType;
|
|
1548
|
+
}
|
|
1549
|
+
this.switchProvider(targetProviderType, switchOptions);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1112
1552
|
if (!this.currentProvider) {
|
|
1113
1553
|
debug.error(DebugCategory.PROVIDER_MANAGER, "No provider selected");
|
|
1114
1554
|
throw new Error("No provider selected");
|
|
1115
1555
|
}
|
|
1116
1556
|
debug.log(DebugCategory.PROVIDER_MANAGER, "Delegating to provider connect method");
|
|
1117
1557
|
const result = await this.currentProvider.connect(authOptions);
|
|
1118
|
-
|
|
1558
|
+
const providerInfo = this.getCurrentProviderInfo();
|
|
1559
|
+
result.providerType = providerInfo?.type;
|
|
1119
1560
|
debug.log(DebugCategory.PROVIDER_MANAGER, "Connection successful, saving preferences", {
|
|
1120
|
-
|
|
1121
|
-
|
|
1561
|
+
addressCount: result.addresses?.length || 0,
|
|
1562
|
+
providerType: result.providerType
|
|
1122
1563
|
});
|
|
1123
1564
|
this.saveProviderPreference();
|
|
1124
1565
|
debug.info(DebugCategory.PROVIDER_MANAGER, "Connect completed", {
|
|
1125
|
-
|
|
1126
|
-
|
|
1566
|
+
addresses: result.addresses,
|
|
1567
|
+
providerType: result.providerType
|
|
1127
1568
|
});
|
|
1128
1569
|
return result;
|
|
1129
1570
|
}
|
|
@@ -1134,7 +1575,6 @@ var ProviderManager = class {
|
|
|
1134
1575
|
if (!this.currentProvider)
|
|
1135
1576
|
return;
|
|
1136
1577
|
await this.currentProvider.disconnect();
|
|
1137
|
-
this.walletId = null;
|
|
1138
1578
|
}
|
|
1139
1579
|
/**
|
|
1140
1580
|
* Get addresses from current provider
|
|
@@ -1152,10 +1592,69 @@ var ProviderManager = class {
|
|
|
1152
1592
|
return this.currentProvider?.isConnected() ?? false;
|
|
1153
1593
|
}
|
|
1154
1594
|
/**
|
|
1155
|
-
*
|
|
1595
|
+
* Attempt auto-connect with fallback strategy
|
|
1596
|
+
* Tries embedded provider first if it exists, then injected provider
|
|
1597
|
+
* Returns true if any provider successfully connected
|
|
1156
1598
|
*/
|
|
1157
|
-
|
|
1158
|
-
|
|
1599
|
+
async autoConnect() {
|
|
1600
|
+
debug.log(DebugCategory.PROVIDER_MANAGER, "Starting auto-connect with fallback strategy");
|
|
1601
|
+
if (isAuthFailureCallback()) {
|
|
1602
|
+
debug.warn(DebugCategory.PROVIDER_MANAGER, "Auth failure detected in URL, skipping autoConnect fallback");
|
|
1603
|
+
return false;
|
|
1604
|
+
}
|
|
1605
|
+
const embeddedWalletType = this.config.embeddedWalletType || "user-wallet";
|
|
1606
|
+
const embeddedKey = this.getProviderKey("embedded", embeddedWalletType);
|
|
1607
|
+
if (this.providers.has(embeddedKey)) {
|
|
1608
|
+
debug.log(DebugCategory.PROVIDER_MANAGER, "Trying auto-connect with existing embedded provider");
|
|
1609
|
+
const embeddedProvider = this.providers.get(embeddedKey);
|
|
1610
|
+
try {
|
|
1611
|
+
const previousProvider = this.currentProvider;
|
|
1612
|
+
const previousKey = this.currentProviderKey;
|
|
1613
|
+
this.currentProvider = embeddedProvider;
|
|
1614
|
+
this.currentProviderKey = embeddedKey;
|
|
1615
|
+
this.ensureProviderEventForwarding();
|
|
1616
|
+
await embeddedProvider.autoConnect();
|
|
1617
|
+
if (embeddedProvider.isConnected()) {
|
|
1618
|
+
debug.info(DebugCategory.PROVIDER_MANAGER, "Embedded auto-connect successful");
|
|
1619
|
+
this.saveProviderPreference();
|
|
1620
|
+
return true;
|
|
1621
|
+
} else {
|
|
1622
|
+
debug.log(DebugCategory.PROVIDER_MANAGER, "Embedded provider did not connect, restoring previous provider");
|
|
1623
|
+
this.currentProvider = previousProvider;
|
|
1624
|
+
this.currentProviderKey = previousKey;
|
|
1625
|
+
}
|
|
1626
|
+
} catch (error) {
|
|
1627
|
+
debug.log(DebugCategory.PROVIDER_MANAGER, "Embedded auto-connect failed", {
|
|
1628
|
+
error: error.message
|
|
1629
|
+
});
|
|
1630
|
+
if (isAuthCallbackUrl()) {
|
|
1631
|
+
debug.log(DebugCategory.PROVIDER_MANAGER, "In auth callback URL, not attempting injected fallback");
|
|
1632
|
+
return false;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
const injectedKey = this.getProviderKey("injected");
|
|
1637
|
+
if (this.providers.has(injectedKey)) {
|
|
1638
|
+
debug.log(DebugCategory.PROVIDER_MANAGER, "Trying auto-connect with existing injected provider");
|
|
1639
|
+
const injectedProvider = this.providers.get(injectedKey);
|
|
1640
|
+
try {
|
|
1641
|
+
this.currentProvider = injectedProvider;
|
|
1642
|
+
this.currentProviderKey = injectedKey;
|
|
1643
|
+
this.ensureProviderEventForwarding();
|
|
1644
|
+
await injectedProvider.autoConnect();
|
|
1645
|
+
if (injectedProvider.isConnected()) {
|
|
1646
|
+
debug.info(DebugCategory.PROVIDER_MANAGER, "Injected auto-connect successful");
|
|
1647
|
+
this.saveProviderPreference();
|
|
1648
|
+
return true;
|
|
1649
|
+
}
|
|
1650
|
+
} catch (error) {
|
|
1651
|
+
debug.log(DebugCategory.PROVIDER_MANAGER, "Injected auto-connect failed", {
|
|
1652
|
+
error: error.message
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
debug.log(DebugCategory.PROVIDER_MANAGER, "Auto-connect failed for all existing providers");
|
|
1657
|
+
return false;
|
|
1159
1658
|
}
|
|
1160
1659
|
/**
|
|
1161
1660
|
* Add event listener - stores callback and ensures current provider forwards events to ProviderManager
|
|
@@ -1238,12 +1737,22 @@ var ProviderManager = class {
|
|
|
1238
1737
|
}
|
|
1239
1738
|
/**
|
|
1240
1739
|
* Set default provider based on initial config
|
|
1740
|
+
* Creates both embedded and injected providers for autoConnect fallback
|
|
1241
1741
|
*/
|
|
1242
1742
|
setDefaultProvider() {
|
|
1243
1743
|
const defaultType = this.config.providerType || "embedded";
|
|
1244
|
-
const defaultEmbeddedType = this.config.embeddedWalletType || "
|
|
1245
|
-
this.
|
|
1246
|
-
|
|
1744
|
+
const defaultEmbeddedType = this.config.embeddedWalletType || "user-wallet";
|
|
1745
|
+
if (this.config.appId) {
|
|
1746
|
+
debug.log(DebugCategory.PROVIDER_MANAGER, "Creating embedded provider");
|
|
1747
|
+
this.createProvider("embedded", defaultEmbeddedType);
|
|
1748
|
+
}
|
|
1749
|
+
debug.log(DebugCategory.PROVIDER_MANAGER, "Creating injected provider");
|
|
1750
|
+
this.createProvider("injected");
|
|
1751
|
+
const switchOptions = {};
|
|
1752
|
+
if (defaultType === "embedded") {
|
|
1753
|
+
switchOptions.embeddedWalletType = defaultEmbeddedType;
|
|
1754
|
+
}
|
|
1755
|
+
this.switchProvider(defaultType, switchOptions);
|
|
1247
1756
|
}
|
|
1248
1757
|
/**
|
|
1249
1758
|
* Create a provider instance
|
|
@@ -1255,22 +1764,27 @@ var ProviderManager = class {
|
|
|
1255
1764
|
let provider;
|
|
1256
1765
|
if (type === "injected") {
|
|
1257
1766
|
provider = new InjectedProvider({
|
|
1258
|
-
|
|
1259
|
-
addressTypes: this.config.addressTypes
|
|
1767
|
+
addressTypes: this.config.addressTypes || [AddressType.solana]
|
|
1260
1768
|
});
|
|
1261
|
-
} else {
|
|
1262
|
-
if (!this.config.
|
|
1263
|
-
throw new Error("
|
|
1769
|
+
} else if (type === "embedded") {
|
|
1770
|
+
if (!this.config.appId) {
|
|
1771
|
+
throw new Error("appId is required for embedded provider");
|
|
1264
1772
|
}
|
|
1773
|
+
const apiBaseUrl = this.config.apiBaseUrl || DEFAULT_WALLET_API_URL;
|
|
1774
|
+
const authUrl = this.config.authOptions?.authUrl || DEFAULT_AUTH_URL2;
|
|
1265
1775
|
provider = new EmbeddedProvider({
|
|
1266
|
-
apiBaseUrl
|
|
1267
|
-
organizationId: this.config.appId,
|
|
1776
|
+
apiBaseUrl,
|
|
1268
1777
|
appId: this.config.appId,
|
|
1269
|
-
authOptions:
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1778
|
+
authOptions: {
|
|
1779
|
+
...this.config.authOptions || {},
|
|
1780
|
+
authUrl,
|
|
1781
|
+
redirectUrl: this.config.authOptions?.redirectUrl || this.getValidatedCurrentUrl()
|
|
1782
|
+
},
|
|
1783
|
+
embeddedWalletType: embeddedWalletType || DEFAULT_EMBEDDED_WALLET_TYPE,
|
|
1784
|
+
addressTypes: this.config.addressTypes || [AddressType.solana]
|
|
1273
1785
|
});
|
|
1786
|
+
} else {
|
|
1787
|
+
throw new Error(`Unsupported provider type: ${type}`);
|
|
1274
1788
|
}
|
|
1275
1789
|
this.providers.set(key, provider);
|
|
1276
1790
|
}
|
|
@@ -1280,8 +1794,10 @@ var ProviderManager = class {
|
|
|
1280
1794
|
getProviderKey(type, embeddedWalletType) {
|
|
1281
1795
|
if (type === "injected") {
|
|
1282
1796
|
return "injected";
|
|
1797
|
+
} else if (type === "embedded") {
|
|
1798
|
+
return `embedded-${embeddedWalletType || "app-wallet"}`;
|
|
1283
1799
|
}
|
|
1284
|
-
|
|
1800
|
+
throw new Error(`Unsupported provider type: ${type}`);
|
|
1285
1801
|
}
|
|
1286
1802
|
/**
|
|
1287
1803
|
* Save provider preference to localStorage
|
|
@@ -1296,35 +1812,17 @@ var ProviderManager = class {
|
|
|
1296
1812
|
console.error("Failed to save provider preference:", error);
|
|
1297
1813
|
}
|
|
1298
1814
|
}
|
|
1299
|
-
/**
|
|
1300
|
-
* Restore provider preference from localStorage
|
|
1301
|
-
*/
|
|
1302
|
-
/*
|
|
1303
|
-
private restoreProviderPreference(): void {
|
|
1304
|
-
try {
|
|
1305
|
-
const saved = localStorage.getItem("phantom-provider-preference");
|
|
1306
|
-
if (saved) {
|
|
1307
|
-
const preference: ProviderPreference = JSON.parse(saved);
|
|
1308
|
-
this.switchProvider(preference.type, {
|
|
1309
|
-
embeddedWalletType: preference.embeddedWalletType,
|
|
1310
|
-
});
|
|
1311
|
-
}
|
|
1312
|
-
} catch (error) {
|
|
1313
|
-
// Ignore localStorage errors - just use default provider
|
|
1314
|
-
console.error("Failed to restore provider preference:", error);
|
|
1315
|
-
}
|
|
1316
|
-
}*/
|
|
1317
1815
|
};
|
|
1318
1816
|
|
|
1319
1817
|
// src/waitForPhantomExtension.ts
|
|
1320
|
-
import { isPhantomExtensionInstalled } from "@phantom/browser-injected-sdk";
|
|
1818
|
+
import { isPhantomExtensionInstalled as isPhantomExtensionInstalled3 } from "@phantom/browser-injected-sdk";
|
|
1321
1819
|
async function waitForPhantomExtension(timeoutMs = 3e3) {
|
|
1322
1820
|
return new Promise((resolve) => {
|
|
1323
1821
|
const startTime = Date.now();
|
|
1324
1822
|
const checkInterval = 100;
|
|
1325
1823
|
const checkForExtension = () => {
|
|
1326
1824
|
try {
|
|
1327
|
-
if (
|
|
1825
|
+
if (isPhantomExtensionInstalled3()) {
|
|
1328
1826
|
resolve(true);
|
|
1329
1827
|
return;
|
|
1330
1828
|
}
|
|
@@ -1342,6 +1840,7 @@ async function waitForPhantomExtension(timeoutMs = 3e3) {
|
|
|
1342
1840
|
}
|
|
1343
1841
|
|
|
1344
1842
|
// src/BrowserSDK.ts
|
|
1843
|
+
import { DEFAULT_EMBEDDED_WALLET_TYPE as DEFAULT_EMBEDDED_WALLET_TYPE2 } from "@phantom/constants";
|
|
1345
1844
|
var BrowserSDK = class {
|
|
1346
1845
|
constructor(config) {
|
|
1347
1846
|
debug.info(DebugCategory.BROWSER_SDK, "Initializing BrowserSDK", {
|
|
@@ -1353,7 +1852,7 @@ var BrowserSDK = class {
|
|
|
1353
1852
|
debug.error(DebugCategory.BROWSER_SDK, "Invalid providerType", { providerType: config.providerType });
|
|
1354
1853
|
throw new Error(`Invalid providerType: ${config.providerType}. Must be "injected" or "embedded".`);
|
|
1355
1854
|
}
|
|
1356
|
-
const embeddedWalletType = config.embeddedWalletType ||
|
|
1855
|
+
const embeddedWalletType = config.embeddedWalletType || DEFAULT_EMBEDDED_WALLET_TYPE2;
|
|
1357
1856
|
if (config.providerType === "embedded" && !["app-wallet", "user-wallet"].includes(embeddedWalletType)) {
|
|
1358
1857
|
debug.error(DebugCategory.BROWSER_SDK, "Invalid embeddedWalletType", {
|
|
1359
1858
|
embeddedWalletType: config.embeddedWalletType
|
|
@@ -1395,7 +1894,6 @@ var BrowserSDK = class {
|
|
|
1395
1894
|
const result = await this.providerManager.connect(options);
|
|
1396
1895
|
debug.info(DebugCategory.BROWSER_SDK, "Connection successful", {
|
|
1397
1896
|
addressCount: result.addresses.length,
|
|
1398
|
-
walletId: result.walletId,
|
|
1399
1897
|
status: result.status
|
|
1400
1898
|
});
|
|
1401
1899
|
return result;
|
|
@@ -1417,22 +1915,6 @@ var BrowserSDK = class {
|
|
|
1417
1915
|
throw error;
|
|
1418
1916
|
}
|
|
1419
1917
|
}
|
|
1420
|
-
/**
|
|
1421
|
-
* Switch between provider types (injected vs embedded)
|
|
1422
|
-
*/
|
|
1423
|
-
async switchProvider(type, options) {
|
|
1424
|
-
debug.info(DebugCategory.BROWSER_SDK, "Switching provider", { type, options });
|
|
1425
|
-
try {
|
|
1426
|
-
await this.providerManager.switchProvider(type, options);
|
|
1427
|
-
debug.info(DebugCategory.BROWSER_SDK, "Provider switch successful", { type });
|
|
1428
|
-
} catch (error) {
|
|
1429
|
-
debug.error(DebugCategory.BROWSER_SDK, "Provider switch failed", {
|
|
1430
|
-
type,
|
|
1431
|
-
error: error.message
|
|
1432
|
-
});
|
|
1433
|
-
throw error;
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
1918
|
// ===== STATE QUERIES =====
|
|
1437
1919
|
/**
|
|
1438
1920
|
* Check if the SDK is connected to a wallet
|
|
@@ -1452,12 +1934,6 @@ var BrowserSDK = class {
|
|
|
1452
1934
|
getCurrentProviderInfo() {
|
|
1453
1935
|
return this.providerManager.getCurrentProviderInfo();
|
|
1454
1936
|
}
|
|
1455
|
-
/**
|
|
1456
|
-
* Get the wallet ID (for embedded wallets)
|
|
1457
|
-
*/
|
|
1458
|
-
getWalletId() {
|
|
1459
|
-
return this.providerManager.getWalletId();
|
|
1460
|
-
}
|
|
1461
1937
|
// ===== UTILITY METHODS =====
|
|
1462
1938
|
/**
|
|
1463
1939
|
* Check if Phantom extension is installed
|
|
@@ -1484,17 +1960,17 @@ var BrowserSDK = class {
|
|
|
1484
1960
|
/**
|
|
1485
1961
|
* Attempt auto-connection using existing session
|
|
1486
1962
|
* Should be called after setting up event listeners
|
|
1487
|
-
*
|
|
1963
|
+
* Tries embedded provider first, then injected provider as fallback
|
|
1488
1964
|
*/
|
|
1489
1965
|
async autoConnect() {
|
|
1490
|
-
debug.log(DebugCategory.BROWSER_SDK, "Attempting auto-connect");
|
|
1491
|
-
const
|
|
1492
|
-
if (
|
|
1493
|
-
|
|
1494
|
-
} else {
|
|
1495
|
-
debug.warn(DebugCategory.BROWSER_SDK, "Current provider does not support auto-connect", {
|
|
1966
|
+
debug.log(DebugCategory.BROWSER_SDK, "Attempting auto-connect with fallback strategy");
|
|
1967
|
+
const result = await this.providerManager.autoConnect();
|
|
1968
|
+
if (result) {
|
|
1969
|
+
debug.info(DebugCategory.BROWSER_SDK, "Auto-connect successful", {
|
|
1496
1970
|
providerType: this.getCurrentProviderInfo()?.type
|
|
1497
1971
|
});
|
|
1972
|
+
} else {
|
|
1973
|
+
debug.log(DebugCategory.BROWSER_SDK, "Auto-connect failed for all providers");
|
|
1498
1974
|
}
|
|
1499
1975
|
}
|
|
1500
1976
|
/**
|
|
@@ -1638,8 +2114,15 @@ var BrowserSDK = class {
|
|
|
1638
2114
|
}
|
|
1639
2115
|
};
|
|
1640
2116
|
|
|
1641
|
-
// src/
|
|
1642
|
-
|
|
2117
|
+
// src/utils/deeplink.ts
|
|
2118
|
+
function getDeeplinkToPhantom(ref) {
|
|
2119
|
+
if (!window.location.href.startsWith("http:") && !window.location.href.startsWith("https:")) {
|
|
2120
|
+
throw new Error("Invalid URL protocol - only HTTP/HTTPS URLs are supported for deeplinks");
|
|
2121
|
+
}
|
|
2122
|
+
const currentUrl = encodeURIComponent(window.location.href);
|
|
2123
|
+
const refParam = ref ? `?ref=${encodeURIComponent(ref)}` : "";
|
|
2124
|
+
return `https://phantom.app/ul/browse/${currentUrl}${refParam}`;
|
|
2125
|
+
}
|
|
1643
2126
|
|
|
1644
2127
|
// src/index.ts
|
|
1645
2128
|
import { NetworkId } from "@phantom/constants";
|
|
@@ -1647,15 +2130,16 @@ import { AddressType as AddressType5 } from "@phantom/client";
|
|
|
1647
2130
|
export {
|
|
1648
2131
|
AddressType5 as AddressType,
|
|
1649
2132
|
BrowserSDK,
|
|
1650
|
-
DEFAULT_AUTH_URL,
|
|
1651
|
-
DEFAULT_WALLET_API_URL,
|
|
1652
2133
|
DebugCategory,
|
|
1653
2134
|
DebugLevel,
|
|
1654
2135
|
NetworkId,
|
|
1655
2136
|
debug,
|
|
1656
2137
|
detectBrowser,
|
|
1657
2138
|
getBrowserDisplayName,
|
|
2139
|
+
getDeeplinkToPhantom,
|
|
1658
2140
|
getPlatformName,
|
|
2141
|
+
isMobileDevice,
|
|
2142
|
+
isPhantomLoginAvailable,
|
|
1659
2143
|
parseBrowserFromUserAgent,
|
|
1660
2144
|
waitForPhantomExtension
|
|
1661
2145
|
};
|