@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.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 redirect may have arrived late");
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 usePhantom = Platform.OS === "ios" && !!redirectUri;
1191
- console.log(TAG3, `Platform: ${Platform.OS}, redirectUri: ${redirectUri ? "provided" : "not set"}, usePhantom: ${usePhantom}`);
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,