@dubsdotapp/expo 0.2.22 → 0.2.24
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 +176 -69
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +160 -53
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/managed-wallet.tsx +24 -6
- 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.mjs
CHANGED
|
@@ -456,7 +456,8 @@ var DubsClient = class {
|
|
|
456
456
|
var STORAGE_KEYS = {
|
|
457
457
|
MWA_AUTH_TOKEN: "dubs_mwa_auth_token",
|
|
458
458
|
JWT_TOKEN: "dubs_jwt_token",
|
|
459
|
-
PHANTOM_SESSION: "dubs_phantom_session"
|
|
459
|
+
PHANTOM_SESSION: "dubs_phantom_session",
|
|
460
|
+
PHANTOM_CONNECT_IN_FLIGHT: "dubs_phantom_connect_in_flight"
|
|
460
461
|
};
|
|
461
462
|
function createSecureStoreStorage() {
|
|
462
463
|
let SecureStore = null;
|
|
@@ -494,7 +495,7 @@ import { Connection as Connection2 } from "@solana/web3.js";
|
|
|
494
495
|
|
|
495
496
|
// src/managed-wallet.tsx
|
|
496
497
|
import { createContext, useContext, useState, useEffect, useRef, useCallback } from "react";
|
|
497
|
-
import { Platform
|
|
498
|
+
import { Platform } from "react-native";
|
|
498
499
|
|
|
499
500
|
// src/wallet/mwa-adapter.ts
|
|
500
501
|
import { PublicKey } from "@solana/web3.js";
|
|
@@ -633,6 +634,7 @@ var DeeplinkHandler = class {
|
|
|
633
634
|
constructor(redirectBase) {
|
|
634
635
|
this.pending = /* @__PURE__ */ new Map();
|
|
635
636
|
this.subscription = null;
|
|
637
|
+
this._coldStartUrl = null;
|
|
636
638
|
this.redirectBase = redirectBase.replace(/\/+$/, "");
|
|
637
639
|
console.log(TAG, "Created with redirectBase:", this.redirectBase);
|
|
638
640
|
}
|
|
@@ -742,9 +744,16 @@ var DeeplinkHandler = class {
|
|
|
742
744
|
this.pending.delete(id);
|
|
743
745
|
req.resolve({ params });
|
|
744
746
|
} else {
|
|
745
|
-
console.log(TAG, "No pending requests to resolve \u2014
|
|
747
|
+
console.log(TAG, "No pending requests to resolve \u2014 stashing as cold-start URL");
|
|
748
|
+
this._coldStartUrl = url;
|
|
746
749
|
}
|
|
747
750
|
}
|
|
751
|
+
/** Return and clear the stashed cold-start URL (if any). */
|
|
752
|
+
getColdStartUrl() {
|
|
753
|
+
const url = this._coldStartUrl;
|
|
754
|
+
this._coldStartUrl = null;
|
|
755
|
+
return url;
|
|
756
|
+
}
|
|
748
757
|
/** Extract the request ID from the dubs_rid query parameter. */
|
|
749
758
|
extractRequestId(url) {
|
|
750
759
|
try {
|
|
@@ -836,6 +845,18 @@ var PhantomDeeplinkAdapter = class {
|
|
|
836
845
|
redirect_link: redirectLink,
|
|
837
846
|
app_url: appUrl
|
|
838
847
|
});
|
|
848
|
+
if (this.config.storage) {
|
|
849
|
+
console.log(TAG2, "Saving in-flight connect state to storage");
|
|
850
|
+
await this.config.storage.setItem(
|
|
851
|
+
STORAGE_KEYS.PHANTOM_CONNECT_IN_FLIGHT,
|
|
852
|
+
JSON.stringify({
|
|
853
|
+
dappPublicKey: dappPubBase58,
|
|
854
|
+
dappSecretKey: bs582.encode(this._dappKeyPair.secretKey),
|
|
855
|
+
requestId,
|
|
856
|
+
createdAt: Date.now()
|
|
857
|
+
})
|
|
858
|
+
);
|
|
859
|
+
}
|
|
839
860
|
const url = `https://phantom.app/ul/v1/connect?${params.toString()}`;
|
|
840
861
|
console.log(TAG2, "Opening Phantom connect deeplink...");
|
|
841
862
|
const response = await this.handler.send(url, requestId, this.timeout);
|
|
@@ -863,6 +884,8 @@ var PhantomDeeplinkAdapter = class {
|
|
|
863
884
|
this._connected = true;
|
|
864
885
|
console.log(TAG2, "Connected! Wallet:", this._publicKey.toBase58());
|
|
865
886
|
this.config.onSessionChange?.(this.getSession());
|
|
887
|
+
this.config.storage?.deleteItem(STORAGE_KEYS.PHANTOM_CONNECT_IN_FLIGHT).catch(() => {
|
|
888
|
+
});
|
|
866
889
|
}
|
|
867
890
|
disconnect() {
|
|
868
891
|
console.log(TAG2, "disconnect() \u2014 clearing state, was connected:", this._connected, "wallet:", this._publicKey?.toBase58());
|
|
@@ -875,6 +898,73 @@ var PhantomDeeplinkAdapter = class {
|
|
|
875
898
|
this.config.onSessionChange?.(null);
|
|
876
899
|
console.log(TAG2, "Disconnected");
|
|
877
900
|
}
|
|
901
|
+
/**
|
|
902
|
+
* Return and clear the stashed cold-start URL from the deeplink handler.
|
|
903
|
+
* Used by ManagedWalletProvider to detect an in-flight connect on cold start.
|
|
904
|
+
*/
|
|
905
|
+
consumeColdStartUrl() {
|
|
906
|
+
return this.handler.getColdStartUrl();
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Complete a connect flow that was interrupted by the OS killing the app (Android).
|
|
910
|
+
* Loads the saved in-flight keypair from storage, decrypts the Phantom response
|
|
911
|
+
* from the cold-start URL, and restores the session.
|
|
912
|
+
*/
|
|
913
|
+
async completeConnectFromColdStart(url) {
|
|
914
|
+
if (!this.config.storage) {
|
|
915
|
+
throw new Error("Cannot recover cold-start connect: no storage configured");
|
|
916
|
+
}
|
|
917
|
+
console.log(TAG2, "completeConnectFromColdStart() \u2014 attempting recovery");
|
|
918
|
+
const raw = await this.config.storage.getItem(STORAGE_KEYS.PHANTOM_CONNECT_IN_FLIGHT);
|
|
919
|
+
if (!raw) {
|
|
920
|
+
throw new Error("No in-flight connect state found in storage");
|
|
921
|
+
}
|
|
922
|
+
const inFlight = JSON.parse(raw);
|
|
923
|
+
const age = Date.now() - inFlight.createdAt;
|
|
924
|
+
if (age > 12e4) {
|
|
925
|
+
console.log(TAG2, `In-flight state expired (${Math.round(age / 1e3)}s old), clearing`);
|
|
926
|
+
await this.config.storage.deleteItem(STORAGE_KEYS.PHANTOM_CONNECT_IN_FLIGHT).catch(() => {
|
|
927
|
+
});
|
|
928
|
+
throw new Error("In-flight connect state expired");
|
|
929
|
+
}
|
|
930
|
+
console.log(TAG2, `In-flight state age: ${Math.round(age / 1e3)}s, requestId: ${inFlight.requestId}`);
|
|
931
|
+
this._dappKeyPair = {
|
|
932
|
+
publicKey: bs582.decode(inFlight.dappPublicKey),
|
|
933
|
+
secretKey: bs582.decode(inFlight.dappSecretKey)
|
|
934
|
+
};
|
|
935
|
+
const parsed = new URL(url);
|
|
936
|
+
const params = {};
|
|
937
|
+
parsed.searchParams.forEach((value, key) => {
|
|
938
|
+
params[key] = value;
|
|
939
|
+
});
|
|
940
|
+
if (params.errorCode) {
|
|
941
|
+
const errorMessage = params.errorMessage ? decodeURIComponent(params.errorMessage) : `Phantom error code: ${params.errorCode}`;
|
|
942
|
+
await this.config.storage.deleteItem(STORAGE_KEYS.PHANTOM_CONNECT_IN_FLIGHT).catch(() => {
|
|
943
|
+
});
|
|
944
|
+
throw new Error(errorMessage);
|
|
945
|
+
}
|
|
946
|
+
const phantomPubBase58 = params.phantom_encryption_public_key;
|
|
947
|
+
if (!phantomPubBase58) {
|
|
948
|
+
await this.config.storage.deleteItem(STORAGE_KEYS.PHANTOM_CONNECT_IN_FLIGHT).catch(() => {
|
|
949
|
+
});
|
|
950
|
+
throw new Error("Phantom did not return an encryption public key");
|
|
951
|
+
}
|
|
952
|
+
console.log(TAG2, "Cold-start: Phantom public key:", phantomPubBase58);
|
|
953
|
+
this._phantomPublicKey = bs582.decode(phantomPubBase58);
|
|
954
|
+
this._sharedSecret = deriveSharedSecret(
|
|
955
|
+
this._dappKeyPair.secretKey,
|
|
956
|
+
this._phantomPublicKey
|
|
957
|
+
);
|
|
958
|
+
const data = decryptPayload(params.data, params.nonce, this._sharedSecret);
|
|
959
|
+
console.log(TAG2, "Cold-start: Decrypted connect data \u2014 public_key:", data.public_key);
|
|
960
|
+
this._sessionToken = data.session;
|
|
961
|
+
this._publicKey = new PublicKey2(data.public_key);
|
|
962
|
+
this._connected = true;
|
|
963
|
+
console.log(TAG2, "Cold-start recovery complete! Wallet:", this._publicKey.toBase58());
|
|
964
|
+
this.config.onSessionChange?.(this.getSession());
|
|
965
|
+
await this.config.storage.deleteItem(STORAGE_KEYS.PHANTOM_CONNECT_IN_FLIGHT).catch(() => {
|
|
966
|
+
});
|
|
967
|
+
}
|
|
878
968
|
async signTransaction(transaction) {
|
|
879
969
|
this.assertConnected();
|
|
880
970
|
console.log(TAG2, "signTransaction() \u2014 serializing transaction");
|
|
@@ -963,53 +1053,6 @@ var PhantomDeeplinkAdapter = class {
|
|
|
963
1053
|
}
|
|
964
1054
|
};
|
|
965
1055
|
|
|
966
|
-
// src/utils/device.ts
|
|
967
|
-
import { Platform } from "react-native";
|
|
968
|
-
function isSolanaSeeker() {
|
|
969
|
-
try {
|
|
970
|
-
const Device = __require("expo-device");
|
|
971
|
-
return Device.brand?.toLowerCase() === "solanamobile";
|
|
972
|
-
} catch {
|
|
973
|
-
return false;
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
async function getDeviceInfo() {
|
|
977
|
-
try {
|
|
978
|
-
const Device = __require("expo-device");
|
|
979
|
-
return {
|
|
980
|
-
platform: Platform.OS,
|
|
981
|
-
modelName: Device.modelName,
|
|
982
|
-
brand: Device.brand,
|
|
983
|
-
manufacturer: Device.manufacturer,
|
|
984
|
-
osName: Device.osName,
|
|
985
|
-
osVersion: Device.osVersion,
|
|
986
|
-
deviceType: Device.deviceType,
|
|
987
|
-
deviceName: Device.deviceName,
|
|
988
|
-
totalMemory: Device.totalMemory,
|
|
989
|
-
modelId: Device.modelId,
|
|
990
|
-
designName: Device.designName,
|
|
991
|
-
productName: Device.productName,
|
|
992
|
-
isDevice: Device.isDevice
|
|
993
|
-
};
|
|
994
|
-
} catch {
|
|
995
|
-
return {
|
|
996
|
-
platform: Platform.OS,
|
|
997
|
-
modelName: null,
|
|
998
|
-
brand: null,
|
|
999
|
-
manufacturer: null,
|
|
1000
|
-
osName: null,
|
|
1001
|
-
osVersion: null,
|
|
1002
|
-
deviceType: null,
|
|
1003
|
-
deviceName: null,
|
|
1004
|
-
totalMemory: null,
|
|
1005
|
-
modelId: null,
|
|
1006
|
-
designName: null,
|
|
1007
|
-
productName: null,
|
|
1008
|
-
isDevice: null
|
|
1009
|
-
};
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
1056
|
// src/ui/ConnectWalletScreen.tsx
|
|
1014
1057
|
import {
|
|
1015
1058
|
View,
|
|
@@ -1197,6 +1240,7 @@ function getOrCreatePhantomAdapter(config) {
|
|
|
1197
1240
|
redirectUri: config.redirectUri,
|
|
1198
1241
|
appUrl: config.appUrl,
|
|
1199
1242
|
cluster: config.cluster,
|
|
1243
|
+
storage: config.storage,
|
|
1200
1244
|
onSessionChange: (session) => {
|
|
1201
1245
|
if (session) {
|
|
1202
1246
|
console.log(TAG3, "Phantom session changed \u2014 saving to storage, wallet:", session.walletPublicKey);
|
|
@@ -1234,9 +1278,8 @@ function ManagedWalletProvider({
|
|
|
1234
1278
|
const [connecting, setConnecting] = useState(false);
|
|
1235
1279
|
const [isReady, setIsReady] = useState(false);
|
|
1236
1280
|
const [error, setError] = useState(null);
|
|
1237
|
-
const
|
|
1238
|
-
|
|
1239
|
-
console.log(TAG3, `Platform: ${Platform2.OS}, seeker: ${seeker}, redirectUri: ${redirectUri ? "provided" : "not set"}, usePhantom: ${usePhantom}`);
|
|
1281
|
+
const usePhantom = Platform.OS === "ios" && !!redirectUri;
|
|
1282
|
+
console.log(TAG3, `Platform: ${Platform.OS}, redirectUri: ${redirectUri ? "provided" : "not set"}, usePhantom: ${usePhantom}`);
|
|
1240
1283
|
const adapterRef = useRef(null);
|
|
1241
1284
|
const transactRef = useRef(null);
|
|
1242
1285
|
if (!adapterRef.current) {
|
|
@@ -1286,6 +1329,21 @@ function ManagedWalletProvider({
|
|
|
1286
1329
|
}
|
|
1287
1330
|
return;
|
|
1288
1331
|
}
|
|
1332
|
+
const coldStartUrl = phantom.consumeColdStartUrl();
|
|
1333
|
+
if (coldStartUrl) {
|
|
1334
|
+
try {
|
|
1335
|
+
console.log(TAG3, "Cold-start URL detected, attempting recovery");
|
|
1336
|
+
await phantom.completeConnectFromColdStart(coldStartUrl);
|
|
1337
|
+
if (!cancelled) {
|
|
1338
|
+
console.log(TAG3, "Cold-start recovery succeeded");
|
|
1339
|
+
setConnected(true);
|
|
1340
|
+
setIsReady(true);
|
|
1341
|
+
}
|
|
1342
|
+
return;
|
|
1343
|
+
} catch (err) {
|
|
1344
|
+
console.log(TAG3, "Cold-start recovery failed:", err instanceof Error ? err.message : err);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1289
1347
|
try {
|
|
1290
1348
|
const savedSession = await storage.getItem(STORAGE_KEYS.PHANTOM_SESSION);
|
|
1291
1349
|
if (savedSession && !cancelled) {
|
|
@@ -1377,6 +1435,8 @@ function ManagedWalletProvider({
|
|
|
1377
1435
|
});
|
|
1378
1436
|
await storage.deleteItem(STORAGE_KEYS.JWT_TOKEN).catch(() => {
|
|
1379
1437
|
});
|
|
1438
|
+
await storage.deleteItem(STORAGE_KEYS.PHANTOM_CONNECT_IN_FLIGHT).catch(() => {
|
|
1439
|
+
});
|
|
1380
1440
|
setConnected(false);
|
|
1381
1441
|
console.log(TAG3, "disconnect() \u2014 done");
|
|
1382
1442
|
}, [adapter, storage, usePhantom]);
|
|
@@ -1792,6 +1852,53 @@ import bs583 from "bs58";
|
|
|
1792
1852
|
import { createContext as createContext2 } from "react";
|
|
1793
1853
|
var AuthContext = createContext2(null);
|
|
1794
1854
|
|
|
1855
|
+
// src/utils/device.ts
|
|
1856
|
+
import { Platform as Platform2 } from "react-native";
|
|
1857
|
+
function isSolanaSeeker() {
|
|
1858
|
+
try {
|
|
1859
|
+
const Device = __require("expo-device");
|
|
1860
|
+
return Device.brand?.toLowerCase() === "solanamobile";
|
|
1861
|
+
} catch {
|
|
1862
|
+
return false;
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
async function getDeviceInfo() {
|
|
1866
|
+
try {
|
|
1867
|
+
const Device = __require("expo-device");
|
|
1868
|
+
return {
|
|
1869
|
+
platform: Platform2.OS,
|
|
1870
|
+
modelName: Device.modelName,
|
|
1871
|
+
brand: Device.brand,
|
|
1872
|
+
manufacturer: Device.manufacturer,
|
|
1873
|
+
osName: Device.osName,
|
|
1874
|
+
osVersion: Device.osVersion,
|
|
1875
|
+
deviceType: Device.deviceType,
|
|
1876
|
+
deviceName: Device.deviceName,
|
|
1877
|
+
totalMemory: Device.totalMemory,
|
|
1878
|
+
modelId: Device.modelId,
|
|
1879
|
+
designName: Device.designName,
|
|
1880
|
+
productName: Device.productName,
|
|
1881
|
+
isDevice: Device.isDevice
|
|
1882
|
+
};
|
|
1883
|
+
} catch {
|
|
1884
|
+
return {
|
|
1885
|
+
platform: Platform2.OS,
|
|
1886
|
+
modelName: null,
|
|
1887
|
+
brand: null,
|
|
1888
|
+
manufacturer: null,
|
|
1889
|
+
osName: null,
|
|
1890
|
+
osVersion: null,
|
|
1891
|
+
deviceType: null,
|
|
1892
|
+
deviceName: null,
|
|
1893
|
+
totalMemory: null,
|
|
1894
|
+
modelId: null,
|
|
1895
|
+
designName: null,
|
|
1896
|
+
productName: null,
|
|
1897
|
+
isDevice: null
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1795
1902
|
// src/hooks/useAuth.ts
|
|
1796
1903
|
function useAuth() {
|
|
1797
1904
|
const sharedAuth = useContext2(AuthContext);
|