@dubsdotapp/expo 0.2.21 → 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 +20 -1
- package/dist/index.d.ts +20 -1
- package/dist/index.js +179 -60
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +162 -44
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/managed-wallet.tsx +26 -4
- package/src/storage.ts +1 -0
- package/src/utils/device.ts +13 -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 } from "react-native";
|
|
498
|
+
import { Platform as Platform2 } 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,6 +1053,53 @@ var PhantomDeeplinkAdapter = class {
|
|
|
963
1053
|
}
|
|
964
1054
|
};
|
|
965
1055
|
|
|
1056
|
+
// src/utils/device.ts
|
|
1057
|
+
import { Platform } from "react-native";
|
|
1058
|
+
function isSolanaSeeker() {
|
|
1059
|
+
try {
|
|
1060
|
+
const Device = __require("expo-device");
|
|
1061
|
+
return Device.brand?.toLowerCase() === "solanamobile";
|
|
1062
|
+
} catch {
|
|
1063
|
+
return false;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
async function getDeviceInfo() {
|
|
1067
|
+
try {
|
|
1068
|
+
const Device = __require("expo-device");
|
|
1069
|
+
return {
|
|
1070
|
+
platform: Platform.OS,
|
|
1071
|
+
modelName: Device.modelName,
|
|
1072
|
+
brand: Device.brand,
|
|
1073
|
+
manufacturer: Device.manufacturer,
|
|
1074
|
+
osName: Device.osName,
|
|
1075
|
+
osVersion: Device.osVersion,
|
|
1076
|
+
deviceType: Device.deviceType,
|
|
1077
|
+
deviceName: Device.deviceName,
|
|
1078
|
+
totalMemory: Device.totalMemory,
|
|
1079
|
+
modelId: Device.modelId,
|
|
1080
|
+
designName: Device.designName,
|
|
1081
|
+
productName: Device.productName,
|
|
1082
|
+
isDevice: Device.isDevice
|
|
1083
|
+
};
|
|
1084
|
+
} catch {
|
|
1085
|
+
return {
|
|
1086
|
+
platform: Platform.OS,
|
|
1087
|
+
modelName: null,
|
|
1088
|
+
brand: null,
|
|
1089
|
+
manufacturer: null,
|
|
1090
|
+
osName: null,
|
|
1091
|
+
osVersion: null,
|
|
1092
|
+
deviceType: null,
|
|
1093
|
+
deviceName: null,
|
|
1094
|
+
totalMemory: null,
|
|
1095
|
+
modelId: null,
|
|
1096
|
+
designName: null,
|
|
1097
|
+
productName: null,
|
|
1098
|
+
isDevice: null
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
|
|
966
1103
|
// src/ui/ConnectWalletScreen.tsx
|
|
967
1104
|
import {
|
|
968
1105
|
View,
|
|
@@ -1150,6 +1287,7 @@ function getOrCreatePhantomAdapter(config) {
|
|
|
1150
1287
|
redirectUri: config.redirectUri,
|
|
1151
1288
|
appUrl: config.appUrl,
|
|
1152
1289
|
cluster: config.cluster,
|
|
1290
|
+
storage: config.storage,
|
|
1153
1291
|
onSessionChange: (session) => {
|
|
1154
1292
|
if (session) {
|
|
1155
1293
|
console.log(TAG3, "Phantom session changed \u2014 saving to storage, wallet:", session.walletPublicKey);
|
|
@@ -1187,8 +1325,9 @@ function ManagedWalletProvider({
|
|
|
1187
1325
|
const [connecting, setConnecting] = useState(false);
|
|
1188
1326
|
const [isReady, setIsReady] = useState(false);
|
|
1189
1327
|
const [error, setError] = useState(null);
|
|
1190
|
-
const
|
|
1191
|
-
|
|
1328
|
+
const seeker = Platform2.OS === "android" && isSolanaSeeker();
|
|
1329
|
+
const usePhantom = !seeker && !!redirectUri;
|
|
1330
|
+
console.log(TAG3, `Platform: ${Platform2.OS}, seeker: ${seeker}, redirectUri: ${redirectUri ? "provided" : "not set"}, usePhantom: ${usePhantom}`);
|
|
1192
1331
|
const adapterRef = useRef(null);
|
|
1193
1332
|
const transactRef = useRef(null);
|
|
1194
1333
|
if (!adapterRef.current) {
|
|
@@ -1238,6 +1377,21 @@ function ManagedWalletProvider({
|
|
|
1238
1377
|
}
|
|
1239
1378
|
return;
|
|
1240
1379
|
}
|
|
1380
|
+
const coldStartUrl = phantom.consumeColdStartUrl();
|
|
1381
|
+
if (coldStartUrl) {
|
|
1382
|
+
try {
|
|
1383
|
+
console.log(TAG3, "Cold-start URL detected, attempting recovery");
|
|
1384
|
+
await phantom.completeConnectFromColdStart(coldStartUrl);
|
|
1385
|
+
if (!cancelled) {
|
|
1386
|
+
console.log(TAG3, "Cold-start recovery succeeded");
|
|
1387
|
+
setConnected(true);
|
|
1388
|
+
setIsReady(true);
|
|
1389
|
+
}
|
|
1390
|
+
return;
|
|
1391
|
+
} catch (err) {
|
|
1392
|
+
console.log(TAG3, "Cold-start recovery failed:", err instanceof Error ? err.message : err);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1241
1395
|
try {
|
|
1242
1396
|
const savedSession = await storage.getItem(STORAGE_KEYS.PHANTOM_SESSION);
|
|
1243
1397
|
if (savedSession && !cancelled) {
|
|
@@ -1329,6 +1483,8 @@ function ManagedWalletProvider({
|
|
|
1329
1483
|
});
|
|
1330
1484
|
await storage.deleteItem(STORAGE_KEYS.JWT_TOKEN).catch(() => {
|
|
1331
1485
|
});
|
|
1486
|
+
await storage.deleteItem(STORAGE_KEYS.PHANTOM_CONNECT_IN_FLIGHT).catch(() => {
|
|
1487
|
+
});
|
|
1332
1488
|
setConnected(false);
|
|
1333
1489
|
console.log(TAG3, "disconnect() \u2014 done");
|
|
1334
1490
|
}, [adapter, storage, usePhantom]);
|
|
@@ -1744,45 +1900,6 @@ import bs583 from "bs58";
|
|
|
1744
1900
|
import { createContext as createContext2 } from "react";
|
|
1745
1901
|
var AuthContext = createContext2(null);
|
|
1746
1902
|
|
|
1747
|
-
// src/utils/device.ts
|
|
1748
|
-
import { Platform as Platform2 } from "react-native";
|
|
1749
|
-
async function getDeviceInfo() {
|
|
1750
|
-
try {
|
|
1751
|
-
const Device = __require("expo-device");
|
|
1752
|
-
return {
|
|
1753
|
-
platform: Platform2.OS,
|
|
1754
|
-
modelName: Device.modelName,
|
|
1755
|
-
brand: Device.brand,
|
|
1756
|
-
manufacturer: Device.manufacturer,
|
|
1757
|
-
osName: Device.osName,
|
|
1758
|
-
osVersion: Device.osVersion,
|
|
1759
|
-
deviceType: Device.deviceType,
|
|
1760
|
-
deviceName: Device.deviceName,
|
|
1761
|
-
totalMemory: Device.totalMemory,
|
|
1762
|
-
modelId: Device.modelId,
|
|
1763
|
-
designName: Device.designName,
|
|
1764
|
-
productName: Device.productName,
|
|
1765
|
-
isDevice: Device.isDevice
|
|
1766
|
-
};
|
|
1767
|
-
} catch {
|
|
1768
|
-
return {
|
|
1769
|
-
platform: Platform2.OS,
|
|
1770
|
-
modelName: null,
|
|
1771
|
-
brand: null,
|
|
1772
|
-
manufacturer: null,
|
|
1773
|
-
osName: null,
|
|
1774
|
-
osVersion: null,
|
|
1775
|
-
deviceType: null,
|
|
1776
|
-
deviceName: null,
|
|
1777
|
-
totalMemory: null,
|
|
1778
|
-
modelId: null,
|
|
1779
|
-
designName: null,
|
|
1780
|
-
productName: null,
|
|
1781
|
-
isDevice: null
|
|
1782
|
-
};
|
|
1783
|
-
}
|
|
1784
|
-
}
|
|
1785
|
-
|
|
1786
1903
|
// src/hooks/useAuth.ts
|
|
1787
1904
|
function useAuth() {
|
|
1788
1905
|
const sharedAuth = useContext2(AuthContext);
|
|
@@ -3666,6 +3783,7 @@ export {
|
|
|
3666
3783
|
UserProfileCard,
|
|
3667
3784
|
createSecureStoreStorage,
|
|
3668
3785
|
getDeviceInfo,
|
|
3786
|
+
isSolanaSeeker,
|
|
3669
3787
|
mergeTheme,
|
|
3670
3788
|
parseSolanaError,
|
|
3671
3789
|
signAndSendBase64Transaction,
|