Skip to content

Commit 1206c0c

Browse files
committed
Implement platform and storage utilities for cross-environment compatibility
- Introduced `platformUtils.ts` with functions for platform detection, custom storage management, and URL handling for socket connections. - Updated `useMySocket.ts` to utilize the new storage interface and platform utilities, enhancing device ID management. - Created `index.ts` to export the main hook and platform utilities for easier access.
1 parent 86a2143 commit 1206c0c

File tree

3 files changed

+192
-34
lines changed

3 files changed

+192
-34
lines changed

src/new-sync/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Export the main hook
2+
export { useMySocket as useQuerySyncSocket } from "./useMySocket";
3+
4+
// Export platform utilities for advanced customization
5+
export {
6+
getPlatform,
7+
setPlatformOverride,
8+
getStorage,
9+
setCustomStorage,
10+
getPlatformSpecificURL,
11+
type StorageInterface,
12+
} from "./platformUtils";

src/new-sync/platformUtils.ts

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/**
2+
* Platform and storage utilities that work across different environments (React Native, web, etc.)
3+
*/
4+
5+
// User-defined overrides
6+
let platformOverride: { os: string; name: string } | null = null;
7+
let customStorageImplementation: StorageInterface | null = null;
8+
9+
// Try to detect if we're in a React Native environment
10+
export const isReactNative = (): boolean => {
11+
try {
12+
return (
13+
typeof navigator !== "undefined" && navigator.product === "ReactNative"
14+
);
15+
} catch (e) {
16+
return false;
17+
}
18+
};
19+
20+
// Platform detection
21+
export const getPlatform = (): { os: string; name: string } => {
22+
// Return user-defined override if available
23+
if (platformOverride) {
24+
return platformOverride;
25+
}
26+
27+
try {
28+
// Try to use React Native Platform if available
29+
if (isReactNative()) {
30+
try {
31+
// Dynamic import to avoid bundling issues
32+
const { Platform } = require("react-native");
33+
const os = Platform.OS;
34+
const name = os.charAt(0).toUpperCase() + os.slice(1);
35+
return { os, name };
36+
} catch (e) {
37+
console.warn("Failed to import Platform from react-native:", e);
38+
}
39+
}
40+
41+
// Fallback to browser detection
42+
const userAgent =
43+
typeof navigator !== "undefined" ? navigator.userAgent : "";
44+
if (/android/i.test(userAgent)) {
45+
return { os: "android", name: "Android" };
46+
}
47+
if (/iPad|iPhone|iPod/.test(userAgent)) {
48+
return { os: "ios", name: "iOS" };
49+
}
50+
if (/Windows/.test(userAgent)) {
51+
return { os: "windows", name: "Windows" };
52+
}
53+
if (/Mac/.test(userAgent)) {
54+
return { os: "macos", name: "MacOS" };
55+
}
56+
if (/Linux/.test(userAgent)) {
57+
return { os: "linux", name: "Linux" };
58+
}
59+
60+
// Default to web if we can't detect the platform
61+
return { os: "web", name: "Web" };
62+
} catch (e) {
63+
console.warn("Error detecting platform:", e);
64+
return { os: "unknown", name: "Unknown" };
65+
}
66+
};
67+
68+
/**
69+
* Override platform detection with a custom value
70+
* Useful for testing or when the automatic detection isn't working properly
71+
*/
72+
export const setPlatformOverride = (
73+
platform: { os: string; name: string } | null
74+
): void => {
75+
platformOverride = platform;
76+
};
77+
78+
// Storage interface similar to AsyncStorage
79+
export interface StorageInterface {
80+
getItem: (key: string) => Promise<string | null>;
81+
setItem: (key: string, value: string) => Promise<void>;
82+
removeItem: (key: string) => Promise<void>;
83+
}
84+
85+
/**
86+
* Set a custom storage implementation
87+
* Use this if you need to provide your own storage solution
88+
*/
89+
export const setCustomStorage = (storage: StorageInterface | null): void => {
90+
customStorageImplementation = storage;
91+
};
92+
93+
// Storage implementation
94+
export const getStorage = (): StorageInterface => {
95+
// Return user-defined storage if available
96+
if (customStorageImplementation) {
97+
return customStorageImplementation;
98+
}
99+
100+
// Try to use React Native AsyncStorage if available
101+
if (isReactNative()) {
102+
try {
103+
// Dynamic import to avoid bundling issues
104+
const AsyncStorage =
105+
require("@react-native-async-storage/async-storage").default;
106+
return AsyncStorage;
107+
} catch (e) {
108+
console.warn("Failed to import AsyncStorage from react-native:", e);
109+
}
110+
}
111+
112+
// Fallback to browser localStorage with an async wrapper
113+
if (typeof localStorage !== "undefined") {
114+
return {
115+
getItem: async (key: string): Promise<string | null> => {
116+
return localStorage.getItem(key);
117+
},
118+
setItem: async (key: string, value: string): Promise<void> => {
119+
localStorage.setItem(key, value);
120+
},
121+
removeItem: async (key: string): Promise<void> => {
122+
localStorage.removeItem(key);
123+
},
124+
};
125+
}
126+
127+
// Memory fallback if nothing else is available
128+
console.warn("No persistent storage available, using in-memory storage");
129+
const memoryStorage: Record<string, string> = {};
130+
return {
131+
getItem: async (key: string): Promise<string | null> => {
132+
return memoryStorage[key] || null;
133+
},
134+
setItem: async (key: string, value: string): Promise<void> => {
135+
memoryStorage[key] = value;
136+
},
137+
removeItem: async (key: string): Promise<void> => {
138+
delete memoryStorage[key];
139+
},
140+
};
141+
};
142+
143+
/**
144+
* Get platform-specific URL for socket connection
145+
* On Android emulator, we need to replace localhost with 10.0.2.2
146+
*/
147+
export const getPlatformSpecificURL = (baseUrl: string): string => {
148+
try {
149+
const platform = getPlatform();
150+
const url = new URL(baseUrl);
151+
152+
// For Android emulator, replace hostname with 10.0.2.2
153+
if (
154+
platform.os === "android" &&
155+
(url.hostname === "localhost" || url.hostname === "127.0.0.1")
156+
) {
157+
url.hostname = "10.0.2.2";
158+
return url.toString();
159+
}
160+
161+
// For other platforms, use as provided
162+
return baseUrl;
163+
} catch (e) {
164+
console.warn("Error getting platform-specific URL:", e);
165+
return baseUrl;
166+
}
167+
};

src/new-sync/useMySocket.ts

Lines changed: 13 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import { useEffect, useRef, useState } from "react";
2-
import { Platform } from "react-native";
32
import { io as socketIO, Socket } from "socket.io-client";
4-
import AsyncStorage from "@react-native-async-storage/async-storage";
53

64
import { User } from "./User";
5+
import {
6+
getStorage,
7+
getPlatform,
8+
getPlatformSpecificURL,
9+
} from "./platformUtils";
710

811
interface Props {
912
deviceName: string; // Unique name to identify the device
@@ -39,8 +42,11 @@ const getOrCreateDeviceId = async (): Promise<string> => {
3942
return deviceId;
4043
}
4144

42-
// Try to get from AsyncStorage
43-
const storedId = await AsyncStorage.getItem(DEVICE_ID_STORAGE_KEY);
45+
// Get the storage implementation
46+
const storage = getStorage();
47+
48+
// Try to get from storage
49+
const storedId = await storage.getItem(DEVICE_ID_STORAGE_KEY);
4450

4551
if (storedId) {
4652
deviceId = storedId;
@@ -49,44 +55,18 @@ const getOrCreateDeviceId = async (): Promise<string> => {
4955

5056
// Generate and store a new ID if not found
5157
const newId = generateDeviceId();
52-
await AsyncStorage.setItem(DEVICE_ID_STORAGE_KEY, newId);
58+
await storage.setItem(DEVICE_ID_STORAGE_KEY, newId);
5359
deviceId = newId;
5460
return newId;
5561
} catch (error) {
5662
console.error("Failed to get/create device ID:", error);
57-
// Fallback to a temporary ID if AsyncStorage fails
63+
// Fallback to a temporary ID if storage fails
5864
const tempId = generateDeviceId();
5965
deviceId = tempId;
6066
return tempId;
6167
}
6268
};
6369

64-
/**
65-
* Determines the correct socket URL based on the current platform
66-
* According to Socket.IO docs: https://socket.io/how-to/use-with-react-native
67-
*
68-
* @param baseUrl The base URL provided (may contain port)
69-
* @returns The platform-specific URL
70-
*/
71-
function getPlatformSpecificURL(baseUrl: string): string {
72-
// Extract protocol, hostname and any additional parts (like port)
73-
const url = new URL(baseUrl);
74-
75-
// For iOS simulator, use localhost
76-
if (Platform.OS === "ios") {
77-
return baseUrl; // localhost works fine for iOS simulator
78-
}
79-
80-
// For Android emulator, replace hostname with 10.0.2.2
81-
if (Platform.OS === "android") {
82-
url.hostname = "10.0.2.2";
83-
return url.toString();
84-
}
85-
86-
// For web or real device, use as provided
87-
return baseUrl;
88-
}
89-
9070
/**
9171
* Hook that handles socket connection for device-dashboard communication
9272
*
@@ -111,8 +91,7 @@ export function useMySocket({ deviceName, socketURL }: Props) {
11191
const logPrefix = `[${deviceName}]`;
11292

11393
// Get the current platform
114-
const currentPlatform =
115-
Platform.OS.charAt(0).toUpperCase() + Platform.OS.slice(1); // Capitalize first letter
94+
const { name: currentPlatform } = getPlatform();
11695

11796
// Define event handlers at function root level to satisfy linter
11897
const onConnect = () => {

0 commit comments

Comments
 (0)