@dubsdotapp/expo 0.2.22 → 0.2.23
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/dist/index.d.mts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +110 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +110 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/managed-wallet.tsx +20 -0
- package/src/storage.ts +1 -0
- package/src/wallet/phantom-deeplink/deeplink-handler.ts +10 -1
- package/src/wallet/phantom-deeplink/phantom-deeplink-adapter.ts +115 -0
package/dist/index.d.mts
CHANGED
|
@@ -430,6 +430,7 @@ declare const STORAGE_KEYS: {
|
|
|
430
430
|
readonly MWA_AUTH_TOKEN: "dubs_mwa_auth_token";
|
|
431
431
|
readonly JWT_TOKEN: "dubs_jwt_token";
|
|
432
432
|
readonly PHANTOM_SESSION: "dubs_phantom_session";
|
|
433
|
+
readonly PHANTOM_CONNECT_IN_FLIGHT: "dubs_phantom_connect_in_flight";
|
|
433
434
|
};
|
|
434
435
|
/**
|
|
435
436
|
* Creates a TokenStorage backed by expo-secure-store.
|
|
@@ -622,6 +623,8 @@ interface PhantomDeeplinkAdapterConfig {
|
|
|
622
623
|
timeout?: number;
|
|
623
624
|
/** Called when the Phantom session changes (save/clear for persistence) */
|
|
624
625
|
onSessionChange?: (session: PhantomSession | null) => void;
|
|
626
|
+
/** Storage for persisting in-flight connect state (cold-start recovery on Android) */
|
|
627
|
+
storage?: TokenStorage;
|
|
625
628
|
}
|
|
626
629
|
/**
|
|
627
630
|
* Phantom wallet adapter using deeplinks (works on iOS and Android).
|
|
@@ -649,6 +652,17 @@ declare class PhantomDeeplinkAdapter implements WalletAdapter {
|
|
|
649
652
|
getSession(): PhantomSession | null;
|
|
650
653
|
connect(): Promise<void>;
|
|
651
654
|
disconnect(): void;
|
|
655
|
+
/**
|
|
656
|
+
* Return and clear the stashed cold-start URL from the deeplink handler.
|
|
657
|
+
* Used by ManagedWalletProvider to detect an in-flight connect on cold start.
|
|
658
|
+
*/
|
|
659
|
+
consumeColdStartUrl(): string | null;
|
|
660
|
+
/**
|
|
661
|
+
* Complete a connect flow that was interrupted by the OS killing the app (Android).
|
|
662
|
+
* Loads the saved in-flight keypair from storage, decrypts the Phantom response
|
|
663
|
+
* from the cold-start URL, and restores the session.
|
|
664
|
+
*/
|
|
665
|
+
completeConnectFromColdStart(url: string): Promise<void>;
|
|
652
666
|
signTransaction(transaction: Transaction): Promise<Transaction>;
|
|
653
667
|
signMessage(message: Uint8Array): Promise<Uint8Array>;
|
|
654
668
|
/** Remove the Linking event listener. Call when the adapter is no longer needed. */
|
package/dist/index.d.ts
CHANGED
|
@@ -430,6 +430,7 @@ declare const STORAGE_KEYS: {
|
|
|
430
430
|
readonly MWA_AUTH_TOKEN: "dubs_mwa_auth_token";
|
|
431
431
|
readonly JWT_TOKEN: "dubs_jwt_token";
|
|
432
432
|
readonly PHANTOM_SESSION: "dubs_phantom_session";
|
|
433
|
+
readonly PHANTOM_CONNECT_IN_FLIGHT: "dubs_phantom_connect_in_flight";
|
|
433
434
|
};
|
|
434
435
|
/**
|
|
435
436
|
* Creates a TokenStorage backed by expo-secure-store.
|
|
@@ -622,6 +623,8 @@ interface PhantomDeeplinkAdapterConfig {
|
|
|
622
623
|
timeout?: number;
|
|
623
624
|
/** Called when the Phantom session changes (save/clear for persistence) */
|
|
624
625
|
onSessionChange?: (session: PhantomSession | null) => void;
|
|
626
|
+
/** Storage for persisting in-flight connect state (cold-start recovery on Android) */
|
|
627
|
+
storage?: TokenStorage;
|
|
625
628
|
}
|
|
626
629
|
/**
|
|
627
630
|
* Phantom wallet adapter using deeplinks (works on iOS and Android).
|
|
@@ -649,6 +652,17 @@ declare class PhantomDeeplinkAdapter implements WalletAdapter {
|
|
|
649
652
|
getSession(): PhantomSession | null;
|
|
650
653
|
connect(): Promise<void>;
|
|
651
654
|
disconnect(): void;
|
|
655
|
+
/**
|
|
656
|
+
* Return and clear the stashed cold-start URL from the deeplink handler.
|
|
657
|
+
* Used by ManagedWalletProvider to detect an in-flight connect on cold start.
|
|
658
|
+
*/
|
|
659
|
+
consumeColdStartUrl(): string | null;
|
|
660
|
+
/**
|
|
661
|
+
* Complete a connect flow that was interrupted by the OS killing the app (Android).
|
|
662
|
+
* Loads the saved in-flight keypair from storage, decrypts the Phantom response
|
|
663
|
+
* from the cold-start URL, and restores the session.
|
|
664
|
+
*/
|
|
665
|
+
completeConnectFromColdStart(url: string): Promise<void>;
|
|
652
666
|
signTransaction(transaction: Transaction): Promise<Transaction>;
|
|
653
667
|
signMessage(message: Uint8Array): Promise<Uint8Array>;
|
|
654
668
|
/** Remove the Linking event listener. Call when the adapter is no longer needed. */
|
package/dist/index.js
CHANGED
|
@@ -522,7 +522,8 @@ var DubsClient = class {
|
|
|
522
522
|
var STORAGE_KEYS = {
|
|
523
523
|
MWA_AUTH_TOKEN: "dubs_mwa_auth_token",
|
|
524
524
|
JWT_TOKEN: "dubs_jwt_token",
|
|
525
|
-
PHANTOM_SESSION: "dubs_phantom_session"
|
|
525
|
+
PHANTOM_SESSION: "dubs_phantom_session",
|
|
526
|
+
PHANTOM_CONNECT_IN_FLIGHT: "dubs_phantom_connect_in_flight"
|
|
526
527
|
};
|
|
527
528
|
function createSecureStoreStorage() {
|
|
528
529
|
let SecureStore = null;
|
|
@@ -699,6 +700,7 @@ var DeeplinkHandler = class {
|
|
|
699
700
|
constructor(redirectBase) {
|
|
700
701
|
this.pending = /* @__PURE__ */ new Map();
|
|
701
702
|
this.subscription = null;
|
|
703
|
+
this._coldStartUrl = null;
|
|
702
704
|
this.redirectBase = redirectBase.replace(/\/+$/, "");
|
|
703
705
|
console.log(TAG, "Created with redirectBase:", this.redirectBase);
|
|
704
706
|
}
|
|
@@ -808,9 +810,16 @@ var DeeplinkHandler = class {
|
|
|
808
810
|
this.pending.delete(id);
|
|
809
811
|
req.resolve({ params });
|
|
810
812
|
} else {
|
|
811
|
-
console.log(TAG, "No pending requests to resolve \u2014
|
|
813
|
+
console.log(TAG, "No pending requests to resolve \u2014 stashing as cold-start URL");
|
|
814
|
+
this._coldStartUrl = url;
|
|
812
815
|
}
|
|
813
816
|
}
|
|
817
|
+
/** Return and clear the stashed cold-start URL (if any). */
|
|
818
|
+
getColdStartUrl() {
|
|
819
|
+
const url = this._coldStartUrl;
|
|
820
|
+
this._coldStartUrl = null;
|
|
821
|
+
return url;
|
|
822
|
+
}
|
|
814
823
|
/** Extract the request ID from the dubs_rid query parameter. */
|
|
815
824
|
extractRequestId(url) {
|
|
816
825
|
try {
|
|
@@ -902,6 +911,18 @@ var PhantomDeeplinkAdapter = class {
|
|
|
902
911
|
redirect_link: redirectLink,
|
|
903
912
|
app_url: appUrl
|
|
904
913
|
});
|
|
914
|
+
if (this.config.storage) {
|
|
915
|
+
console.log(TAG2, "Saving in-flight connect state to storage");
|
|
916
|
+
await this.config.storage.setItem(
|
|
917
|
+
STORAGE_KEYS.PHANTOM_CONNECT_IN_FLIGHT,
|
|
918
|
+
JSON.stringify({
|
|
919
|
+
dappPublicKey: dappPubBase58,
|
|
920
|
+
dappSecretKey: import_bs582.default.encode(this._dappKeyPair.secretKey),
|
|
921
|
+
requestId,
|
|
922
|
+
createdAt: Date.now()
|
|
923
|
+
})
|
|
924
|
+
);
|
|
925
|
+
}
|
|
905
926
|
const url = `https://phantom.app/ul/v1/connect?${params.toString()}`;
|
|
906
927
|
console.log(TAG2, "Opening Phantom connect deeplink...");
|
|
907
928
|
const response = await this.handler.send(url, requestId, this.timeout);
|
|
@@ -929,6 +950,8 @@ var PhantomDeeplinkAdapter = class {
|
|
|
929
950
|
this._connected = true;
|
|
930
951
|
console.log(TAG2, "Connected! Wallet:", this._publicKey.toBase58());
|
|
931
952
|
this.config.onSessionChange?.(this.getSession());
|
|
953
|
+
this.config.storage?.deleteItem(STORAGE_KEYS.PHANTOM_CONNECT_IN_FLIGHT).catch(() => {
|
|
954
|
+
});
|
|
932
955
|
}
|
|
933
956
|
disconnect() {
|
|
934
957
|
console.log(TAG2, "disconnect() \u2014 clearing state, was connected:", this._connected, "wallet:", this._publicKey?.toBase58());
|
|
@@ -941,6 +964,73 @@ var PhantomDeeplinkAdapter = class {
|
|
|
941
964
|
this.config.onSessionChange?.(null);
|
|
942
965
|
console.log(TAG2, "Disconnected");
|
|
943
966
|
}
|
|
967
|
+
/**
|
|
968
|
+
* Return and clear the stashed cold-start URL from the deeplink handler.
|
|
969
|
+
* Used by ManagedWalletProvider to detect an in-flight connect on cold start.
|
|
970
|
+
*/
|
|
971
|
+
consumeColdStartUrl() {
|
|
972
|
+
return this.handler.getColdStartUrl();
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Complete a connect flow that was interrupted by the OS killing the app (Android).
|
|
976
|
+
* Loads the saved in-flight keypair from storage, decrypts the Phantom response
|
|
977
|
+
* from the cold-start URL, and restores the session.
|
|
978
|
+
*/
|
|
979
|
+
async completeConnectFromColdStart(url) {
|
|
980
|
+
if (!this.config.storage) {
|
|
981
|
+
throw new Error("Cannot recover cold-start connect: no storage configured");
|
|
982
|
+
}
|
|
983
|
+
console.log(TAG2, "completeConnectFromColdStart() \u2014 attempting recovery");
|
|
984
|
+
const raw = await this.config.storage.getItem(STORAGE_KEYS.PHANTOM_CONNECT_IN_FLIGHT);
|
|
985
|
+
if (!raw) {
|
|
986
|
+
throw new Error("No in-flight connect state found in storage");
|
|
987
|
+
}
|
|
988
|
+
const inFlight = JSON.parse(raw);
|
|
989
|
+
const age = Date.now() - inFlight.createdAt;
|
|
990
|
+
if (age > 12e4) {
|
|
991
|
+
console.log(TAG2, `In-flight state expired (${Math.round(age / 1e3)}s old), clearing`);
|
|
992
|
+
await this.config.storage.deleteItem(STORAGE_KEYS.PHANTOM_CONNECT_IN_FLIGHT).catch(() => {
|
|
993
|
+
});
|
|
994
|
+
throw new Error("In-flight connect state expired");
|
|
995
|
+
}
|
|
996
|
+
console.log(TAG2, `In-flight state age: ${Math.round(age / 1e3)}s, requestId: ${inFlight.requestId}`);
|
|
997
|
+
this._dappKeyPair = {
|
|
998
|
+
publicKey: import_bs582.default.decode(inFlight.dappPublicKey),
|
|
999
|
+
secretKey: import_bs582.default.decode(inFlight.dappSecretKey)
|
|
1000
|
+
};
|
|
1001
|
+
const parsed = new URL(url);
|
|
1002
|
+
const params = {};
|
|
1003
|
+
parsed.searchParams.forEach((value, key) => {
|
|
1004
|
+
params[key] = value;
|
|
1005
|
+
});
|
|
1006
|
+
if (params.errorCode) {
|
|
1007
|
+
const errorMessage = params.errorMessage ? decodeURIComponent(params.errorMessage) : `Phantom error code: ${params.errorCode}`;
|
|
1008
|
+
await this.config.storage.deleteItem(STORAGE_KEYS.PHANTOM_CONNECT_IN_FLIGHT).catch(() => {
|
|
1009
|
+
});
|
|
1010
|
+
throw new Error(errorMessage);
|
|
1011
|
+
}
|
|
1012
|
+
const phantomPubBase58 = params.phantom_encryption_public_key;
|
|
1013
|
+
if (!phantomPubBase58) {
|
|
1014
|
+
await this.config.storage.deleteItem(STORAGE_KEYS.PHANTOM_CONNECT_IN_FLIGHT).catch(() => {
|
|
1015
|
+
});
|
|
1016
|
+
throw new Error("Phantom did not return an encryption public key");
|
|
1017
|
+
}
|
|
1018
|
+
console.log(TAG2, "Cold-start: Phantom public key:", phantomPubBase58);
|
|
1019
|
+
this._phantomPublicKey = import_bs582.default.decode(phantomPubBase58);
|
|
1020
|
+
this._sharedSecret = deriveSharedSecret(
|
|
1021
|
+
this._dappKeyPair.secretKey,
|
|
1022
|
+
this._phantomPublicKey
|
|
1023
|
+
);
|
|
1024
|
+
const data = decryptPayload(params.data, params.nonce, this._sharedSecret);
|
|
1025
|
+
console.log(TAG2, "Cold-start: Decrypted connect data \u2014 public_key:", data.public_key);
|
|
1026
|
+
this._sessionToken = data.session;
|
|
1027
|
+
this._publicKey = new import_web32.PublicKey(data.public_key);
|
|
1028
|
+
this._connected = true;
|
|
1029
|
+
console.log(TAG2, "Cold-start recovery complete! Wallet:", this._publicKey.toBase58());
|
|
1030
|
+
this.config.onSessionChange?.(this.getSession());
|
|
1031
|
+
await this.config.storage.deleteItem(STORAGE_KEYS.PHANTOM_CONNECT_IN_FLIGHT).catch(() => {
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
944
1034
|
async signTransaction(transaction) {
|
|
945
1035
|
this.assertConnected();
|
|
946
1036
|
console.log(TAG2, "signTransaction() \u2014 serializing transaction");
|
|
@@ -1256,6 +1346,7 @@ function getOrCreatePhantomAdapter(config) {
|
|
|
1256
1346
|
redirectUri: config.redirectUri,
|
|
1257
1347
|
appUrl: config.appUrl,
|
|
1258
1348
|
cluster: config.cluster,
|
|
1349
|
+
storage: config.storage,
|
|
1259
1350
|
onSessionChange: (session) => {
|
|
1260
1351
|
if (session) {
|
|
1261
1352
|
console.log(TAG3, "Phantom session changed \u2014 saving to storage, wallet:", session.walletPublicKey);
|
|
@@ -1345,6 +1436,21 @@ function ManagedWalletProvider({
|
|
|
1345
1436
|
}
|
|
1346
1437
|
return;
|
|
1347
1438
|
}
|
|
1439
|
+
const coldStartUrl = phantom.consumeColdStartUrl();
|
|
1440
|
+
if (coldStartUrl) {
|
|
1441
|
+
try {
|
|
1442
|
+
console.log(TAG3, "Cold-start URL detected, attempting recovery");
|
|
1443
|
+
await phantom.completeConnectFromColdStart(coldStartUrl);
|
|
1444
|
+
if (!cancelled) {
|
|
1445
|
+
console.log(TAG3, "Cold-start recovery succeeded");
|
|
1446
|
+
setConnected(true);
|
|
1447
|
+
setIsReady(true);
|
|
1448
|
+
}
|
|
1449
|
+
return;
|
|
1450
|
+
} catch (err) {
|
|
1451
|
+
console.log(TAG3, "Cold-start recovery failed:", err instanceof Error ? err.message : err);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1348
1454
|
try {
|
|
1349
1455
|
const savedSession = await storage.getItem(STORAGE_KEYS.PHANTOM_SESSION);
|
|
1350
1456
|
if (savedSession && !cancelled) {
|
|
@@ -1436,6 +1542,8 @@ function ManagedWalletProvider({
|
|
|
1436
1542
|
});
|
|
1437
1543
|
await storage.deleteItem(STORAGE_KEYS.JWT_TOKEN).catch(() => {
|
|
1438
1544
|
});
|
|
1545
|
+
await storage.deleteItem(STORAGE_KEYS.PHANTOM_CONNECT_IN_FLIGHT).catch(() => {
|
|
1546
|
+
});
|
|
1439
1547
|
setConnected(false);
|
|
1440
1548
|
console.log(TAG3, "disconnect() \u2014 done");
|
|
1441
1549
|
}, [adapter, storage, usePhantom]);
|