@buoy-gg/external-sync 2.1.15

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.
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+
3
+ import { useEffect, useRef } from "react";
4
+ import { log } from "./utils/logger";
5
+ import { SYNC_PROTOCOL_VERSION } from "./types";
6
+ const LOG = "[ExternalSync]";
7
+
8
+ /**
9
+ * Device-side sync bridge: syncs any number of dev tools to the desktop
10
+ * dashboard and executes actions invoked remotely.
11
+ *
12
+ * Backpressure: a tool's adapter is only subscribed while a dashboard is
13
+ * watching it (`devtool-watch`), so unwatched tools cost nothing. The server
14
+ * re-sends current watch state whenever it receives `devtool-capabilities`,
15
+ * which the device announces on every (re)connect.
16
+ *
17
+ * Usage:
18
+ * ```ts
19
+ * useExternalSync({
20
+ * tools: {
21
+ * storage: {
22
+ * version: 1,
23
+ * getSnapshot: () => storageEventStore.getEvents(),
24
+ * subscribe: (cb) => storageEventStore.subscribeToEvents(cb),
25
+ * actions: {
26
+ * clearEvents: () => storageEventStore.clearEvents(),
27
+ * },
28
+ * },
29
+ * },
30
+ * socket,
31
+ * deviceId: 'my-device-id',
32
+ * });
33
+ * ```
34
+ */
35
+ export function useExternalSync({
36
+ tools,
37
+ socket,
38
+ deviceId,
39
+ enabled = true,
40
+ throttleMs = 200,
41
+ enableLogs = false
42
+ }) {
43
+ const toolsRef = useRef(tools);
44
+ toolsRef.current = tools;
45
+
46
+ // Watch state persists across effect re-runs (e.g. when tools are
47
+ // added/removed) so an already-watched tool stays subscribed.
48
+ const watchedRef = useRef(new Set());
49
+
50
+ // Re-run the effect only when tools are added/removed, not on every
51
+ // object-reference change.
52
+ const toolIdsKey = Object.keys(tools).sort().join(",");
53
+ useEffect(() => {
54
+ if (!socket || !deviceId || !enabled) return;
55
+ const unsubs = new Map();
56
+ const lastEmit = {};
57
+ const timers = {};
58
+ const isForThisDevice = targetDeviceId => targetDeviceId === "All" || targetDeviceId === deviceId;
59
+ const safeEmit = (event, message) => {
60
+ try {
61
+ socket.emit(event, message);
62
+ } catch (error) {
63
+ log(`${LOG} Failed to emit "${event}": ${error}. Payload must be JSON-serializable.`, enableLogs, "error");
64
+ }
65
+ };
66
+ const sendSnapshot = toolId => {
67
+ const tool = toolsRef.current[toolId];
68
+ if (!tool) return;
69
+ lastEmit[toolId] = Date.now();
70
+ const message = {
71
+ toolId,
72
+ persistentDeviceId: deviceId,
73
+ timestamp: lastEmit[toolId],
74
+ version: tool.version,
75
+ kind: "snapshot",
76
+ payload: tool.getSnapshot()
77
+ };
78
+ safeEmit("devtool-sync", message);
79
+ };
80
+
81
+ // Leading + trailing throttle: emit immediately when outside the window,
82
+ // otherwise schedule a flush for the window's end so the dashboard always
83
+ // converges to the latest state.
84
+ const scheduleSnapshot = toolId => {
85
+ if (timers[toolId]) return;
86
+ const elapsed = Date.now() - (lastEmit[toolId] ?? 0);
87
+ if (elapsed >= throttleMs) {
88
+ sendSnapshot(toolId);
89
+ return;
90
+ }
91
+ timers[toolId] = setTimeout(() => {
92
+ delete timers[toolId];
93
+ sendSnapshot(toolId);
94
+ }, throttleMs - elapsed);
95
+ };
96
+ const watchTool = toolId => {
97
+ const tool = toolsRef.current[toolId];
98
+ if (!tool || unsubs.has(toolId)) return;
99
+ unsubs.set(toolId, tool.subscribe(() => scheduleSnapshot(toolId)));
100
+ log(`${LOG} Watching "${toolId}"`, enableLogs);
101
+ };
102
+ const unwatchTool = toolId => {
103
+ unsubs.get(toolId)?.();
104
+ unsubs.delete(toolId);
105
+ if (timers[toolId]) {
106
+ clearTimeout(timers[toolId]);
107
+ delete timers[toolId];
108
+ }
109
+ log(`${LOG} Stopped watching "${toolId}"`, enableLogs);
110
+ };
111
+ const sendCapabilities = () => {
112
+ const message = {
113
+ persistentDeviceId: deviceId,
114
+ protocolVersion: SYNC_PROTOCOL_VERSION,
115
+ tools: Object.entries(toolsRef.current).map(([toolId, tool]) => ({
116
+ toolId,
117
+ version: tool.version,
118
+ actions: Object.keys(tool.actions ?? {})
119
+ }))
120
+ };
121
+ safeEmit("devtool-capabilities", message);
122
+ };
123
+ const handleWatch = message => {
124
+ if (!isForThisDevice(message.targetDeviceId)) return;
125
+ if (message.watching) {
126
+ watchedRef.current.add(message.toolId);
127
+ watchTool(message.toolId);
128
+ sendSnapshot(message.toolId);
129
+ } else {
130
+ watchedRef.current.delete(message.toolId);
131
+ unwatchTool(message.toolId);
132
+ }
133
+ };
134
+ const handleRequest = message => {
135
+ if (!isForThisDevice(message.targetDeviceId)) return;
136
+ sendSnapshot(message.toolId);
137
+ };
138
+ const handleAction = async message => {
139
+ if (!isForThisDevice(message.targetDeviceId)) return;
140
+ const {
141
+ requestId,
142
+ toolId,
143
+ action,
144
+ params
145
+ } = message;
146
+ const result = {
147
+ requestId,
148
+ toolId,
149
+ action,
150
+ persistentDeviceId: deviceId,
151
+ timestamp: Date.now(),
152
+ ok: false
153
+ };
154
+ const tool = toolsRef.current[toolId];
155
+ const fn = tool?.actions?.[action];
156
+ if (!fn) {
157
+ result.error = tool ? `Unknown action "${action}" on tool "${toolId}"` : `Unknown tool "${toolId}"`;
158
+ safeEmit("devtool-action-result", result);
159
+ return;
160
+ }
161
+ try {
162
+ result.result = await fn(params);
163
+ result.ok = true;
164
+ } catch (error) {
165
+ result.error = error instanceof Error ? error.message : String(error);
166
+ }
167
+ result.timestamp = Date.now();
168
+ safeEmit("devtool-action-result", result);
169
+ };
170
+ const handleConnect = () => {
171
+ // The server replies with current watch state for this device.
172
+ sendCapabilities();
173
+ };
174
+ if (socket.connected) sendCapabilities();
175
+
176
+ // Restore subscriptions for tools that were watched before this effect
177
+ // re-ran (or that arrived after the watch message).
178
+ for (const toolId of watchedRef.current) {
179
+ if (toolsRef.current[toolId]) watchTool(toolId);
180
+ }
181
+ socket.on("connect", handleConnect);
182
+ socket.on("devtool-watch", handleWatch);
183
+ socket.on("request-devtool-state", handleRequest);
184
+ socket.on("devtool-action", handleAction);
185
+ return () => {
186
+ socket.off("connect", handleConnect);
187
+ socket.off("devtool-watch", handleWatch);
188
+ socket.off("request-devtool-state", handleRequest);
189
+ socket.off("devtool-action", handleAction);
190
+ for (const toolId of [...unsubs.keys()]) unwatchTool(toolId);
191
+ };
192
+ // toolIdsKey stands in for `tools` (see comment above its definition).
193
+ // eslint-disable-next-line react-hooks/exhaustive-deps
194
+ }, [socket, deviceId, enabled, throttleMs, enableLogs, toolIdsKey]);
195
+ }
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { io as socketIO } from "socket.io-client";
5
+ import { getPlatformSpecificURL } from "./utils/platformUtils";
6
+ import { log } from "./utils/logger";
7
+ /**
8
+ * Singleton socket instance that persists across component renders.
9
+ * Multiple components share the same socket connection.
10
+ */
11
+ let globalSocketInstance = null;
12
+ let currentSocketURL = "";
13
+
14
+ /**
15
+ * Hook that manages a Socket.IO connection for device-to-dashboard communication.
16
+ *
17
+ * Features:
18
+ * - Singleton pattern for socket connection
19
+ * - Platform-specific URL handling (Android emulator -> 10.0.2.2)
20
+ * - Connection state tracking
21
+ */
22
+ export function useExternalSyncSocket({
23
+ deviceName,
24
+ socketURL,
25
+ persistentDeviceId,
26
+ extraDeviceInfo,
27
+ envVariables,
28
+ platform,
29
+ enableLogs = false
30
+ }) {
31
+ const socketRef = useRef(null);
32
+ const [socket, setSocket] = useState(null);
33
+ const [isConnected, setIsConnected] = useState(false);
34
+ const initialized = useRef(false);
35
+ const logPrefix = `[${deviceName}]`;
36
+ const onConnect = () => {
37
+ log(`${logPrefix} Socket connected successfully`, enableLogs);
38
+ setIsConnected(true);
39
+ };
40
+ const onDisconnect = reason => {
41
+ log(`${logPrefix} Socket disconnected. Reason: ${reason}`, enableLogs);
42
+ setIsConnected(false);
43
+ };
44
+ const onConnectError = error => {
45
+ log(`${logPrefix} Socket connection error: ${error.message}`, enableLogs, "error");
46
+ };
47
+ const onConnectTimeout = () => {
48
+ log(`${logPrefix} Socket connection timeout`, enableLogs, "error");
49
+ };
50
+
51
+ // Main socket initialization - runs only once
52
+ useEffect(() => {
53
+ if (!persistentDeviceId) return;
54
+ if (initialized.current) return;
55
+ initialized.current = true;
56
+ const platformUrl = getPlatformSpecificURL(socketURL, platform);
57
+ currentSocketURL = platformUrl;
58
+ log(`${logPrefix} Platform: ${platform}, using URL: ${platformUrl}`, enableLogs);
59
+ try {
60
+ if (!globalSocketInstance) {
61
+ log(`${logPrefix} Creating new socket instance to ${platformUrl}`, enableLogs);
62
+ globalSocketInstance = socketIO(platformUrl, {
63
+ autoConnect: true,
64
+ query: {
65
+ deviceName,
66
+ deviceId: persistentDeviceId,
67
+ platform,
68
+ extraDeviceInfo: JSON.stringify(extraDeviceInfo),
69
+ envVariables: JSON.stringify(envVariables)
70
+ },
71
+ reconnection: false,
72
+ transports: ["websocket"]
73
+ });
74
+ } else {
75
+ log(`${logPrefix} Reusing existing socket instance to ${platformUrl}`, enableLogs);
76
+ }
77
+ socketRef.current = globalSocketInstance;
78
+ setSocket(socketRef.current);
79
+ socketRef.current.on("connect_error", onConnectError);
80
+ socketRef.current.on("connect_timeout", onConnectTimeout);
81
+ if (socketRef.current.connected) {
82
+ setIsConnected(true);
83
+ log(`${logPrefix} Socket already connected on init`, enableLogs);
84
+ }
85
+ socketRef.current.on("connect", onConnect);
86
+ socketRef.current.on("disconnect", onDisconnect);
87
+ return () => {
88
+ if (socketRef.current) {
89
+ log(`${logPrefix} Cleaning up socket event listeners`, enableLogs);
90
+ socketRef.current.off("connect", onConnect);
91
+ socketRef.current.off("disconnect", onDisconnect);
92
+ socketRef.current.off("connect_error", onConnectError);
93
+ socketRef.current.off("connect_timeout", onConnectTimeout);
94
+ }
95
+ };
96
+ } catch (error) {
97
+ log(`${logPrefix} Failed to initialize socket: ${error}`, enableLogs, "error");
98
+ }
99
+ }, [persistentDeviceId]);
100
+
101
+ // Update socket query parameters when deviceName changes
102
+ useEffect(() => {
103
+ if (socketRef.current && socketRef.current.io.opts.query && persistentDeviceId) {
104
+ log(`${logPrefix} Updating device name in socket connection`, enableLogs);
105
+ socketRef.current.io.opts.query = {
106
+ ...socketRef.current.io.opts.query,
107
+ deviceName,
108
+ deviceId: persistentDeviceId,
109
+ platform
110
+ };
111
+ }
112
+ }, [deviceName, logPrefix, persistentDeviceId, platform, enableLogs]);
113
+
114
+ // Update socket URL when socketURL changes
115
+ useEffect(() => {
116
+ const platformUrl = getPlatformSpecificURL(socketURL, platform);
117
+ if (socketRef.current && currentSocketURL !== platformUrl && persistentDeviceId) {
118
+ log(`${logPrefix} Socket URL changed from ${currentSocketURL} to ${platformUrl}`, enableLogs);
119
+ try {
120
+ socketRef.current.disconnect();
121
+ currentSocketURL = platformUrl;
122
+ log(`${logPrefix} Creating new socket connection to ${platformUrl}`, enableLogs);
123
+ globalSocketInstance = socketIO(platformUrl, {
124
+ autoConnect: true,
125
+ query: {
126
+ deviceName,
127
+ deviceId: persistentDeviceId,
128
+ platform,
129
+ extraDeviceInfo: JSON.stringify(extraDeviceInfo),
130
+ envVariables: JSON.stringify(envVariables)
131
+ },
132
+ reconnection: false,
133
+ transports: ["websocket"]
134
+ });
135
+ socketRef.current = globalSocketInstance;
136
+ setSocket(socketRef.current);
137
+ } catch (error) {
138
+ log(`${logPrefix} Failed to update socket connection: ${error}`, enableLogs, "error");
139
+ }
140
+ }
141
+ }, [socketURL, deviceName, logPrefix, persistentDeviceId, platform, enableLogs]);
142
+ function connect() {
143
+ if (socketRef.current && !socketRef.current.connected) {
144
+ log(`${logPrefix} Manually connecting to socket server`, enableLogs);
145
+ socketRef.current.connect();
146
+ }
147
+ }
148
+ function disconnect() {
149
+ if (socketRef.current && socketRef.current.connected) {
150
+ log(`${logPrefix} Manually disconnecting from socket server`, enableLogs);
151
+ socketRef.current.disconnect();
152
+ }
153
+ }
154
+ return {
155
+ socket,
156
+ connect,
157
+ disconnect,
158
+ isConnected
159
+ };
160
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Helper function for controlled logging.
5
+ * Only shows logs when enableLogs is true.
6
+ * Always shows warnings and errors regardless of enableLogs setting.
7
+ */
8
+ export function log(message, enableLogs, type = "log") {
9
+ if (!enableLogs && type === "log") return;
10
+ switch (type) {
11
+ case "warn":
12
+ console.warn(message);
13
+ break;
14
+ case "error":
15
+ console.error(message);
16
+ break;
17
+ default:
18
+ console.log(message);
19
+ }
20
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Get platform-specific URL for socket connection.
5
+ * On Android emulator, replaces localhost with 10.0.2.2.
6
+ */
7
+ export const getPlatformSpecificURL = (baseUrl, platform) => {
8
+ try {
9
+ const url = new URL(baseUrl);
10
+ if (platform === "android" && (url.hostname === "localhost" || url.hostname === "127.0.0.1")) {
11
+ url.hostname = "10.0.2.2";
12
+ return url.toString();
13
+ }
14
+ return baseUrl;
15
+ } catch (e) {
16
+ console.warn("Error getting platform-specific URL:", e);
17
+ return baseUrl;
18
+ }
19
+ };
@@ -0,0 +1,5 @@
1
+ export { useExternalSync } from "./useExternalSync";
2
+ export { useExternalSyncSocket } from "./useExternalSyncSocket";
3
+ export { SYNC_PROTOCOL_VERSION } from "./types";
4
+ export type { ToolSyncAdapter, ToolAction, ToolCapability, SyncKind, UseExternalSyncProps, UseExternalSyncSocketProps, DeviceCapabilitiesMessage, DevToolSyncMessage, DevToolActionMessage, DevToolActionResultMessage, DevToolWatchMessage, DevToolRequestMessage, PlatformOS, } from "./types";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAKhE,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAKhD,YAAY,EACV,eAAe,EACf,UAAU,EACV,cAAc,EACd,QAAQ,EACR,oBAAoB,EACpB,0BAA0B,EAC1B,yBAAyB,EACzB,kBAAkB,EAClB,oBAAoB,EACpB,0BAA0B,EAC1B,mBAAmB,EACnB,qBAAqB,EACrB,UAAU,GACX,MAAM,SAAS,CAAC"}
@@ -0,0 +1,114 @@
1
+ import type { Socket } from "socket.io-client";
2
+ /** Version of the envelope/event contract itself (not tool payloads). */
3
+ export declare const SYNC_PROTOCOL_VERSION = 1;
4
+ /** What a sync payload represents. "events"/"diff" reserved for future use. */
5
+ export type SyncKind = "snapshot";
6
+ /** A remote-invocable action. Thrown errors are reported back to the dashboard. */
7
+ export type ToolAction = (params: unknown) => unknown | Promise<unknown>;
8
+ /**
9
+ * The contract each dev tool implements to become remotely syncable.
10
+ * Works with BaseEventStore or any pattern exposing subscribe + getSnapshot.
11
+ *
12
+ * Payloads (snapshots and action results) must be JSON-serializable —
13
+ * no functions, circular references, or class instances.
14
+ */
15
+ export interface ToolSyncAdapter {
16
+ /** Version of this tool's payload shape. Bump on breaking changes. */
17
+ version: number;
18
+ /** Current full state — sent when a dashboard starts watching or requests state. */
19
+ getSnapshot: () => unknown;
20
+ /**
21
+ * Fires on state changes. Only called while at least one dashboard is
22
+ * watching this tool; the bridge subscribes/unsubscribes on demand so
23
+ * stores with auto start/stop lifecycles stay idle when unwatched.
24
+ */
25
+ subscribe: (onChange: () => void) => () => void;
26
+ /** Remote-invocable actions, keyed by action name. */
27
+ actions?: Record<string, ToolAction>;
28
+ }
29
+ /** Announces available tools + actions on connect and when tools change. */
30
+ export interface DeviceCapabilitiesMessage {
31
+ persistentDeviceId: string;
32
+ protocolVersion: number;
33
+ tools: ToolCapability[];
34
+ }
35
+ export interface ToolCapability {
36
+ toolId: string;
37
+ version: number;
38
+ actions: string[];
39
+ }
40
+ /** Carries a tool's state to the dashboard. */
41
+ export interface DevToolSyncMessage {
42
+ toolId: string;
43
+ persistentDeviceId: string;
44
+ timestamp: number;
45
+ /** Tool payload version (ToolSyncAdapter.version). */
46
+ version: number;
47
+ kind: SyncKind;
48
+ payload: unknown;
49
+ }
50
+ /** Response to a `devtool-action` request. */
51
+ export interface DevToolActionResultMessage {
52
+ requestId: string;
53
+ toolId: string;
54
+ action: string;
55
+ persistentDeviceId: string;
56
+ timestamp: number;
57
+ ok: boolean;
58
+ /** Present when ok. JSON-serializable value returned by the action. */
59
+ result?: unknown;
60
+ /** Present when not ok. */
61
+ error?: string;
62
+ }
63
+ /** Server-consolidated signal that a tool gained/lost its audience. */
64
+ export interface DevToolWatchMessage {
65
+ toolId: string;
66
+ targetDeviceId: string;
67
+ watching: boolean;
68
+ }
69
+ /** Explicit request for a tool's current state (works even while unwatched). */
70
+ export interface DevToolRequestMessage {
71
+ toolId: string;
72
+ targetDeviceId: string;
73
+ }
74
+ /** Invokes a registered action on the device. */
75
+ export interface DevToolActionMessage {
76
+ requestId: string;
77
+ toolId: string;
78
+ targetDeviceId: string;
79
+ action: string;
80
+ params?: unknown;
81
+ }
82
+ export interface UseExternalSyncProps {
83
+ /** Map of toolId -> adapter. Each key becomes the toolId in messages. */
84
+ tools: Record<string, ToolSyncAdapter>;
85
+ /** Connected socket instance (from useExternalSyncSocket). */
86
+ socket: Socket | null;
87
+ /** Persistent device identifier. */
88
+ deviceId: string;
89
+ /** Enable/disable sync. @default true */
90
+ enabled?: boolean;
91
+ /**
92
+ * Minimum ms between snapshot emits per tool. Trailing-edge: a change
93
+ * arriving inside the window is flushed when the window ends, so the
94
+ * dashboard always converges to the latest state. @default 200
95
+ */
96
+ throttleMs?: number;
97
+ /** @default false */
98
+ enableLogs?: boolean;
99
+ }
100
+ /**
101
+ * Valid platform operating systems that can be used with React Native
102
+ */
103
+ export type PlatformOS = "ios" | "android" | "web" | "windows" | "macos" | "native" | "tv" | "tvos" | "visionos" | "maccatalyst";
104
+ export interface UseExternalSyncSocketProps {
105
+ deviceName: string;
106
+ socketURL: string;
107
+ persistentDeviceId: string | null;
108
+ extraDeviceInfo?: Record<string, string>;
109
+ envVariables?: Record<string, string>;
110
+ platform: PlatformOS;
111
+ /** @default false */
112
+ enableLogs?: boolean;
113
+ }
114
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { type UseExternalSyncProps } from "./types";
2
+ /**
3
+ * Device-side sync bridge: syncs any number of dev tools to the desktop
4
+ * dashboard and executes actions invoked remotely.
5
+ *
6
+ * Backpressure: a tool's adapter is only subscribed while a dashboard is
7
+ * watching it (`devtool-watch`), so unwatched tools cost nothing. The server
8
+ * re-sends current watch state whenever it receives `devtool-capabilities`,
9
+ * which the device announces on every (re)connect.
10
+ *
11
+ * Usage:
12
+ * ```ts
13
+ * useExternalSync({
14
+ * tools: {
15
+ * storage: {
16
+ * version: 1,
17
+ * getSnapshot: () => storageEventStore.getEvents(),
18
+ * subscribe: (cb) => storageEventStore.subscribeToEvents(cb),
19
+ * actions: {
20
+ * clearEvents: () => storageEventStore.clearEvents(),
21
+ * },
22
+ * },
23
+ * },
24
+ * socket,
25
+ * deviceId: 'my-device-id',
26
+ * });
27
+ * ```
28
+ */
29
+ export declare function useExternalSync({ tools, socket, deviceId, enabled, throttleMs, enableLogs, }: UseExternalSyncProps): void;
30
+ //# sourceMappingURL=useExternalSync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useExternalSync.d.ts","sourceRoot":"","sources":["../../src/useExternalSync.ts"],"names":[],"mappings":"AAEA,OAAO,EAQL,KAAK,oBAAoB,EAC1B,MAAM,SAAS,CAAC;AAIjB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,MAAM,EACN,QAAQ,EACR,OAAc,EACd,UAAgB,EAChB,UAAkB,GACnB,EAAE,oBAAoB,QAgLtB"}
@@ -0,0 +1,17 @@
1
+ import { Socket } from "socket.io-client";
2
+ import type { UseExternalSyncSocketProps } from "./types";
3
+ /**
4
+ * Hook that manages a Socket.IO connection for device-to-dashboard communication.
5
+ *
6
+ * Features:
7
+ * - Singleton pattern for socket connection
8
+ * - Platform-specific URL handling (Android emulator -> 10.0.2.2)
9
+ * - Connection state tracking
10
+ */
11
+ export declare function useExternalSyncSocket({ deviceName, socketURL, persistentDeviceId, extraDeviceInfo, envVariables, platform, enableLogs, }: UseExternalSyncSocketProps): {
12
+ socket: Socket<import("@socket.io/component-emitter").DefaultEventsMap, import("@socket.io/component-emitter").DefaultEventsMap> | null;
13
+ connect: () => void;
14
+ disconnect: () => void;
15
+ isConnected: boolean;
16
+ };
17
+ //# sourceMappingURL=useExternalSyncSocket.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useExternalSyncSocket.d.ts","sourceRoot":"","sources":["../../src/useExternalSyncSocket.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAI1D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAC;AAS1D;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,UAAU,EACV,SAAS,EACT,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,QAAQ,EACR,UAAkB,GACnB,EAAE,0BAA0B;;;;;EAoM5B"}
@@ -0,0 +1,8 @@
1
+ export type LogType = "log" | "warn" | "error";
2
+ /**
3
+ * Helper function for controlled logging.
4
+ * Only shows logs when enableLogs is true.
5
+ * Always shows warnings and errors regardless of enableLogs setting.
6
+ */
7
+ export declare function log(message: string, enableLogs: boolean, type?: LogType): void;
8
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;AAE/C;;;;GAIG;AACH,wBAAgB,GAAG,CACjB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,OAAO,EACnB,IAAI,GAAE,OAAe,GACpB,IAAI,CAYN"}
@@ -0,0 +1,7 @@
1
+ import type { PlatformOS } from "../types";
2
+ /**
3
+ * Get platform-specific URL for socket connection.
4
+ * On Android emulator, replaces localhost with 10.0.2.2.
5
+ */
6
+ export declare const getPlatformSpecificURL: (baseUrl: string, platform: PlatformOS) => string;
7
+ //# sourceMappingURL=platformUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platformUtils.d.ts","sourceRoot":"","sources":["../../../src/utils/platformUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GACjC,SAAS,MAAM,EACf,UAAU,UAAU,KACnB,MAiBF,CAAC"}