@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 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;
@@ -560,7 +561,7 @@ var import_web34 = require("@solana/web3.js");
560
561
 
561
562
  // src/managed-wallet.tsx
562
563
  var import_react = require("react");
563
- var import_react_native5 = require("react-native");
564
+ var import_react_native4 = require("react-native");
564
565
 
565
566
  // src/wallet/mwa-adapter.ts
566
567
  var import_web3 = require("@solana/web3.js");
@@ -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 redirect may have arrived late");
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");
@@ -1029,58 +1119,11 @@ var PhantomDeeplinkAdapter = class {
1029
1119
  }
1030
1120
  };
1031
1121
 
1032
- // src/utils/device.ts
1033
- var import_react_native2 = require("react-native");
1034
- function isSolanaSeeker() {
1035
- try {
1036
- const Device = require("expo-device");
1037
- return Device.brand?.toLowerCase() === "solanamobile";
1038
- } catch {
1039
- return false;
1040
- }
1041
- }
1042
- async function getDeviceInfo() {
1043
- try {
1044
- const Device = require("expo-device");
1045
- return {
1046
- platform: import_react_native2.Platform.OS,
1047
- modelName: Device.modelName,
1048
- brand: Device.brand,
1049
- manufacturer: Device.manufacturer,
1050
- osName: Device.osName,
1051
- osVersion: Device.osVersion,
1052
- deviceType: Device.deviceType,
1053
- deviceName: Device.deviceName,
1054
- totalMemory: Device.totalMemory,
1055
- modelId: Device.modelId,
1056
- designName: Device.designName,
1057
- productName: Device.productName,
1058
- isDevice: Device.isDevice
1059
- };
1060
- } catch {
1061
- return {
1062
- platform: import_react_native2.Platform.OS,
1063
- modelName: null,
1064
- brand: null,
1065
- manufacturer: null,
1066
- osName: null,
1067
- osVersion: null,
1068
- deviceType: null,
1069
- deviceName: null,
1070
- totalMemory: null,
1071
- modelId: null,
1072
- designName: null,
1073
- productName: null,
1074
- isDevice: null
1075
- };
1076
- }
1077
- }
1078
-
1079
1122
  // src/ui/ConnectWalletScreen.tsx
1080
- var import_react_native4 = require("react-native");
1123
+ var import_react_native3 = require("react-native");
1081
1124
 
1082
1125
  // src/ui/theme.ts
1083
- var import_react_native3 = require("react-native");
1126
+ var import_react_native2 = require("react-native");
1084
1127
  var dark = {
1085
1128
  background: "#08080D",
1086
1129
  surface: "#111118",
@@ -1114,7 +1157,7 @@ var light = {
1114
1157
  errorBorder: "#FECACA"
1115
1158
  };
1116
1159
  function useDubsTheme() {
1117
- const scheme = (0, import_react_native3.useColorScheme)();
1160
+ const scheme = (0, import_react_native2.useColorScheme)();
1118
1161
  return scheme === "light" ? light : dark;
1119
1162
  }
1120
1163
  function mergeTheme(base, overrides) {
@@ -1134,44 +1177,44 @@ function ConnectWalletScreen({
1134
1177
  }) {
1135
1178
  const t = useDubsTheme();
1136
1179
  const accent = accentColor || t.accent;
1137
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native4.View, { style: [styles.container, { backgroundColor: t.background }], children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native4.View, { style: styles.content, children: [
1138
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native4.View, { style: styles.brandingSection, children: [
1180
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native3.View, { style: [styles.container, { backgroundColor: t.background }], children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native3.View, { style: styles.content, children: [
1181
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native3.View, { style: styles.brandingSection, children: [
1139
1182
  appIcon ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1140
- import_react_native4.Image,
1183
+ import_react_native3.Image,
1141
1184
  {
1142
1185
  source: { uri: appIcon },
1143
1186
  style: styles.logoImage
1144
1187
  }
1145
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native4.View, { style: [styles.logoCircle, { backgroundColor: accent }], children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native4.Text, { style: styles.logoText, children: appName.charAt(0).toUpperCase() }) }),
1146
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native4.Text, { style: [styles.appName, { color: t.text }], children: appName }),
1147
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native4.Text, { style: [styles.subtitle, { color: t.textMuted }], children: tagline || "Connect your Solana wallet to get started" })
1188
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native3.View, { style: [styles.logoCircle, { backgroundColor: accent }], children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native3.Text, { style: styles.logoText, children: appName.charAt(0).toUpperCase() }) }),
1189
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native3.Text, { style: [styles.appName, { color: t.text }], children: appName }),
1190
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native3.Text, { style: [styles.subtitle, { color: t.textMuted }], children: tagline || "Connect your Solana wallet to get started" })
1148
1191
  ] }),
1149
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native4.View, { style: styles.actionSection, children: [
1192
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native3.View, { style: styles.actionSection, children: [
1150
1193
  error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1151
- import_react_native4.View,
1194
+ import_react_native3.View,
1152
1195
  {
1153
1196
  style: [
1154
1197
  styles.errorBox,
1155
1198
  { backgroundColor: t.errorBg, borderColor: t.errorBorder }
1156
1199
  ],
1157
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native4.Text, { style: [styles.errorText, { color: t.errorText }], children: error })
1200
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native3.Text, { style: [styles.errorText, { color: t.errorText }], children: error })
1158
1201
  }
1159
1202
  ) : null,
1160
1203
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1161
- import_react_native4.TouchableOpacity,
1204
+ import_react_native3.TouchableOpacity,
1162
1205
  {
1163
1206
  style: [styles.connectButton, { backgroundColor: accent }],
1164
1207
  onPress: onConnect,
1165
1208
  disabled: connecting,
1166
1209
  activeOpacity: 0.8,
1167
- children: connecting ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native4.ActivityIndicator, { color: "#FFFFFF", size: "small" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native4.Text, { style: styles.connectButtonText, children: "Connect Wallet" })
1210
+ children: connecting ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native3.ActivityIndicator, { color: "#FFFFFF", size: "small" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native3.Text, { style: styles.connectButtonText, children: "Connect Wallet" })
1168
1211
  }
1169
1212
  ),
1170
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native4.Text, { style: [styles.hint, { color: t.textDim }], children: "Phantom, Solflare, or any Solana wallet" })
1213
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native3.Text, { style: [styles.hint, { color: t.textDim }], children: "Phantom, Solflare, or any Solana wallet" })
1171
1214
  ] })
1172
1215
  ] }) });
1173
1216
  }
1174
- var styles = import_react_native4.StyleSheet.create({
1217
+ var styles = import_react_native3.StyleSheet.create({
1175
1218
  container: {
1176
1219
  flex: 1,
1177
1220
  justifyContent: "center"
@@ -1256,6 +1299,7 @@ function getOrCreatePhantomAdapter(config) {
1256
1299
  redirectUri: config.redirectUri,
1257
1300
  appUrl: config.appUrl,
1258
1301
  cluster: config.cluster,
1302
+ storage: config.storage,
1259
1303
  onSessionChange: (session) => {
1260
1304
  if (session) {
1261
1305
  console.log(TAG3, "Phantom session changed \u2014 saving to storage, wallet:", session.walletPublicKey);
@@ -1293,9 +1337,8 @@ function ManagedWalletProvider({
1293
1337
  const [connecting, setConnecting] = (0, import_react.useState)(false);
1294
1338
  const [isReady, setIsReady] = (0, import_react.useState)(false);
1295
1339
  const [error, setError] = (0, import_react.useState)(null);
1296
- const seeker = import_react_native5.Platform.OS === "android" && isSolanaSeeker();
1297
- const usePhantom = !seeker && !!redirectUri;
1298
- console.log(TAG3, `Platform: ${import_react_native5.Platform.OS}, seeker: ${seeker}, redirectUri: ${redirectUri ? "provided" : "not set"}, usePhantom: ${usePhantom}`);
1340
+ const usePhantom = import_react_native4.Platform.OS === "ios" && !!redirectUri;
1341
+ console.log(TAG3, `Platform: ${import_react_native4.Platform.OS}, redirectUri: ${redirectUri ? "provided" : "not set"}, usePhantom: ${usePhantom}`);
1299
1342
  const adapterRef = (0, import_react.useRef)(null);
1300
1343
  const transactRef = (0, import_react.useRef)(null);
1301
1344
  if (!adapterRef.current) {
@@ -1345,6 +1388,21 @@ function ManagedWalletProvider({
1345
1388
  }
1346
1389
  return;
1347
1390
  }
1391
+ const coldStartUrl = phantom.consumeColdStartUrl();
1392
+ if (coldStartUrl) {
1393
+ try {
1394
+ console.log(TAG3, "Cold-start URL detected, attempting recovery");
1395
+ await phantom.completeConnectFromColdStart(coldStartUrl);
1396
+ if (!cancelled) {
1397
+ console.log(TAG3, "Cold-start recovery succeeded");
1398
+ setConnected(true);
1399
+ setIsReady(true);
1400
+ }
1401
+ return;
1402
+ } catch (err) {
1403
+ console.log(TAG3, "Cold-start recovery failed:", err instanceof Error ? err.message : err);
1404
+ }
1405
+ }
1348
1406
  try {
1349
1407
  const savedSession = await storage.getItem(STORAGE_KEYS.PHANTOM_SESSION);
1350
1408
  if (savedSession && !cancelled) {
@@ -1436,6 +1494,8 @@ function ManagedWalletProvider({
1436
1494
  });
1437
1495
  await storage.deleteItem(STORAGE_KEYS.JWT_TOKEN).catch(() => {
1438
1496
  });
1497
+ await storage.deleteItem(STORAGE_KEYS.PHANTOM_CONNECT_IN_FLIGHT).catch(() => {
1498
+ });
1439
1499
  setConnected(false);
1440
1500
  console.log(TAG3, "disconnect() \u2014 done");
1441
1501
  }, [adapter, storage, usePhantom]);
@@ -1838,6 +1898,53 @@ var import_bs583 = __toESM(require("bs58"));
1838
1898
  var import_react10 = require("react");
1839
1899
  var AuthContext = (0, import_react10.createContext)(null);
1840
1900
 
1901
+ // src/utils/device.ts
1902
+ var import_react_native5 = require("react-native");
1903
+ function isSolanaSeeker() {
1904
+ try {
1905
+ const Device = require("expo-device");
1906
+ return Device.brand?.toLowerCase() === "solanamobile";
1907
+ } catch {
1908
+ return false;
1909
+ }
1910
+ }
1911
+ async function getDeviceInfo() {
1912
+ try {
1913
+ const Device = require("expo-device");
1914
+ return {
1915
+ platform: import_react_native5.Platform.OS,
1916
+ modelName: Device.modelName,
1917
+ brand: Device.brand,
1918
+ manufacturer: Device.manufacturer,
1919
+ osName: Device.osName,
1920
+ osVersion: Device.osVersion,
1921
+ deviceType: Device.deviceType,
1922
+ deviceName: Device.deviceName,
1923
+ totalMemory: Device.totalMemory,
1924
+ modelId: Device.modelId,
1925
+ designName: Device.designName,
1926
+ productName: Device.productName,
1927
+ isDevice: Device.isDevice
1928
+ };
1929
+ } catch {
1930
+ return {
1931
+ platform: import_react_native5.Platform.OS,
1932
+ modelName: null,
1933
+ brand: null,
1934
+ manufacturer: null,
1935
+ osName: null,
1936
+ osVersion: null,
1937
+ deviceType: null,
1938
+ deviceName: null,
1939
+ totalMemory: null,
1940
+ modelId: null,
1941
+ designName: null,
1942
+ productName: null,
1943
+ isDevice: null
1944
+ };
1945
+ }
1946
+ }
1947
+
1841
1948
  // src/hooks/useAuth.ts
1842
1949
  function useAuth() {
1843
1950
  const sharedAuth = (0, import_react11.useContext)(AuthContext);