@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.
- package/LICENSE +58 -0
- package/lib/commonjs/index.js +26 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/types.js +84 -0
- package/lib/commonjs/useExternalSync.js +199 -0
- package/lib/commonjs/useExternalSyncSocket.js +164 -0
- package/lib/commonjs/utils/logger.js +24 -0
- package/lib/commonjs/utils/platformUtils.js +24 -0
- package/lib/module/index.js +16 -0
- package/lib/module/types.js +80 -0
- package/lib/module/useExternalSync.js +195 -0
- package/lib/module/useExternalSyncSocket.js +160 -0
- package/lib/module/utils/logger.js +20 -0
- package/lib/module/utils/platformUtils.js +19 -0
- package/lib/typescript/index.d.ts +5 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +114 -0
- package/lib/typescript/types.d.ts.map +1 -0
- package/lib/typescript/useExternalSync.d.ts +30 -0
- package/lib/typescript/useExternalSync.d.ts.map +1 -0
- package/lib/typescript/useExternalSyncSocket.d.ts +17 -0
- package/lib/typescript/useExternalSyncSocket.d.ts.map +1 -0
- package/lib/typescript/utils/logger.d.ts +8 -0
- package/lib/typescript/utils/logger.d.ts.map +1 -0
- package/lib/typescript/utils/platformUtils.d.ts +7 -0
- package/lib/typescript/utils/platformUtils.d.ts.map +1 -0
- package/package.json +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
PROPRIETARY SOFTWARE LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-present Buoy. All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and its source code are proprietary and confidential.
|
|
6
|
+
|
|
7
|
+
NOTICE: This is NOT open source software. This software is licensed,
|
|
8
|
+
not sold, and is protected by copyright laws and international treaties.
|
|
9
|
+
|
|
10
|
+
TERMS AND CONDITIONS:
|
|
11
|
+
|
|
12
|
+
1. LICENSE GRANT
|
|
13
|
+
Subject to the terms of this Agreement and payment of applicable fees,
|
|
14
|
+
Buoy grants you a limited, non-exclusive, non-transferable license
|
|
15
|
+
to use the compiled software packages in your applications.
|
|
16
|
+
|
|
17
|
+
2. RESTRICTIONS
|
|
18
|
+
You may NOT:
|
|
19
|
+
- Copy, modify, or distribute the source code
|
|
20
|
+
- Reverse engineer, decompile, or disassemble the software
|
|
21
|
+
- Remove or alter any proprietary notices or labels
|
|
22
|
+
- Sublicense, rent, lease, or lend the software
|
|
23
|
+
- Use the software to create competing products
|
|
24
|
+
- Share access credentials with unauthorized parties
|
|
25
|
+
|
|
26
|
+
3. OWNERSHIP
|
|
27
|
+
React Buoy retains all right, title, and interest in the software,
|
|
28
|
+
including all intellectual property rights. This license does not
|
|
29
|
+
grant you any rights to trademarks or service marks.
|
|
30
|
+
|
|
31
|
+
4. TERMINATION
|
|
32
|
+
This license is effective until terminated. Your rights under this
|
|
33
|
+
license will terminate automatically without notice if you fail to
|
|
34
|
+
comply with any of its terms. Upon termination, you must cease all
|
|
35
|
+
use and destroy all copies of the software.
|
|
36
|
+
|
|
37
|
+
5. DISCLAIMER OF WARRANTIES
|
|
38
|
+
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND,
|
|
39
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
40
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
|
|
41
|
+
|
|
42
|
+
6. LIMITATION OF LIABILITY
|
|
43
|
+
IN NO EVENT SHALL BUOY BE LIABLE FOR ANY INDIRECT, INCIDENTAL,
|
|
44
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN
|
|
45
|
+
CONNECTION WITH THIS LICENSE OR THE USE OF THE SOFTWARE.
|
|
46
|
+
|
|
47
|
+
7. GOVERNING LAW
|
|
48
|
+
This Agreement shall be governed by and construed in accordance with
|
|
49
|
+
the laws of the United States, without regard to its conflict of
|
|
50
|
+
law provisions.
|
|
51
|
+
|
|
52
|
+
For licensing inquiries and subscription information:
|
|
53
|
+
- Website: https://buoy.gg
|
|
54
|
+
- Email: AustinLovesWorking@gmail.com
|
|
55
|
+
|
|
56
|
+
Unauthorized reproduction or distribution of this software, or any
|
|
57
|
+
portion of it, may result in severe civil and criminal penalties,
|
|
58
|
+
and will be prosecuted to the maximum extent possible under the law.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
Object.defineProperty(exports, "SYNC_PROTOCOL_VERSION", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _types.SYNC_PROTOCOL_VERSION;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
Object.defineProperty(exports, "useExternalSync", {
|
|
13
|
+
enumerable: true,
|
|
14
|
+
get: function () {
|
|
15
|
+
return _useExternalSync.useExternalSync;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
Object.defineProperty(exports, "useExternalSyncSocket", {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: function () {
|
|
21
|
+
return _useExternalSyncSocket.useExternalSyncSocket;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
var _useExternalSync = require("./useExternalSync");
|
|
25
|
+
var _useExternalSyncSocket = require("./useExternalSyncSocket");
|
|
26
|
+
var _types = require("./types");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.SYNC_PROTOCOL_VERSION = void 0;
|
|
7
|
+
// ============================================================
|
|
8
|
+
// DEVTOOL SYNC PROTOCOL (v1)
|
|
9
|
+
//
|
|
10
|
+
// Topology: device is a Socket.IO client; the desktop app hosts
|
|
11
|
+
// the server (broker) and one or more dashboard UIs. The server
|
|
12
|
+
// routes messages between dashboards and devices.
|
|
13
|
+
//
|
|
14
|
+
// Device -> server/dashboard events:
|
|
15
|
+
// "devtool-capabilities" DeviceCapabilitiesMessage
|
|
16
|
+
// "devtool-sync" DevToolSyncMessage
|
|
17
|
+
// "devtool-action-result" DevToolActionResultMessage
|
|
18
|
+
//
|
|
19
|
+
// Dashboard/server -> device events:
|
|
20
|
+
// "devtool-watch" DevToolWatchMessage
|
|
21
|
+
// "request-devtool-state" DevToolRequestMessage
|
|
22
|
+
// "devtool-action" DevToolActionMessage
|
|
23
|
+
//
|
|
24
|
+
// Backpressure contract: the device sends nothing for a tool until
|
|
25
|
+
// it receives `devtool-watch {watching: true}` for it. The SERVER is
|
|
26
|
+
// responsible for consolidating watch state across dashboards (and
|
|
27
|
+
// for sending `watching: false` when the last watching dashboard
|
|
28
|
+
// closes a panel or disconnects). The device just obeys.
|
|
29
|
+
// ============================================================
|
|
30
|
+
|
|
31
|
+
/** Version of the envelope/event contract itself (not tool payloads). */
|
|
32
|
+
const SYNC_PROTOCOL_VERSION = exports.SYNC_PROTOCOL_VERSION = 1;
|
|
33
|
+
|
|
34
|
+
/** What a sync payload represents. "events"/"diff" reserved for future use. */
|
|
35
|
+
|
|
36
|
+
// ============================================================
|
|
37
|
+
// TOOL ADAPTER
|
|
38
|
+
// ============================================================
|
|
39
|
+
|
|
40
|
+
/** A remote-invocable action. Thrown errors are reported back to the dashboard. */
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The contract each dev tool implements to become remotely syncable.
|
|
44
|
+
* Works with BaseEventStore or any pattern exposing subscribe + getSnapshot.
|
|
45
|
+
*
|
|
46
|
+
* Payloads (snapshots and action results) must be JSON-serializable —
|
|
47
|
+
* no functions, circular references, or class instances.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
// ============================================================
|
|
51
|
+
// DEVICE -> DASHBOARD MESSAGES
|
|
52
|
+
// ============================================================
|
|
53
|
+
|
|
54
|
+
/** Announces available tools + actions on connect and when tools change. */
|
|
55
|
+
|
|
56
|
+
/** Carries a tool's state to the dashboard. */
|
|
57
|
+
|
|
58
|
+
/** Response to a `devtool-action` request. */
|
|
59
|
+
|
|
60
|
+
// ============================================================
|
|
61
|
+
// DASHBOARD -> DEVICE MESSAGES
|
|
62
|
+
// ============================================================
|
|
63
|
+
|
|
64
|
+
/** Server-consolidated signal that a tool gained/lost its audience. */
|
|
65
|
+
|
|
66
|
+
/** Explicit request for a tool's current state (works even while unwatched). */
|
|
67
|
+
|
|
68
|
+
/** Invokes a registered action on the device. */
|
|
69
|
+
|
|
70
|
+
// ============================================================
|
|
71
|
+
// HOOK PROPS
|
|
72
|
+
// ============================================================
|
|
73
|
+
|
|
74
|
+
// ============================================================
|
|
75
|
+
// PLATFORM
|
|
76
|
+
// ============================================================
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Valid platform operating systems that can be used with React Native
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
// ============================================================
|
|
83
|
+
// SOCKET HOOK PROPS
|
|
84
|
+
// ============================================================
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.useExternalSync = useExternalSync;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _logger = require("./utils/logger");
|
|
9
|
+
var _types = require("./types");
|
|
10
|
+
const LOG = "[ExternalSync]";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Device-side sync bridge: syncs any number of dev tools to the desktop
|
|
14
|
+
* dashboard and executes actions invoked remotely.
|
|
15
|
+
*
|
|
16
|
+
* Backpressure: a tool's adapter is only subscribed while a dashboard is
|
|
17
|
+
* watching it (`devtool-watch`), so unwatched tools cost nothing. The server
|
|
18
|
+
* re-sends current watch state whenever it receives `devtool-capabilities`,
|
|
19
|
+
* which the device announces on every (re)connect.
|
|
20
|
+
*
|
|
21
|
+
* Usage:
|
|
22
|
+
* ```ts
|
|
23
|
+
* useExternalSync({
|
|
24
|
+
* tools: {
|
|
25
|
+
* storage: {
|
|
26
|
+
* version: 1,
|
|
27
|
+
* getSnapshot: () => storageEventStore.getEvents(),
|
|
28
|
+
* subscribe: (cb) => storageEventStore.subscribeToEvents(cb),
|
|
29
|
+
* actions: {
|
|
30
|
+
* clearEvents: () => storageEventStore.clearEvents(),
|
|
31
|
+
* },
|
|
32
|
+
* },
|
|
33
|
+
* },
|
|
34
|
+
* socket,
|
|
35
|
+
* deviceId: 'my-device-id',
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
function useExternalSync({
|
|
40
|
+
tools,
|
|
41
|
+
socket,
|
|
42
|
+
deviceId,
|
|
43
|
+
enabled = true,
|
|
44
|
+
throttleMs = 200,
|
|
45
|
+
enableLogs = false
|
|
46
|
+
}) {
|
|
47
|
+
const toolsRef = (0, _react.useRef)(tools);
|
|
48
|
+
toolsRef.current = tools;
|
|
49
|
+
|
|
50
|
+
// Watch state persists across effect re-runs (e.g. when tools are
|
|
51
|
+
// added/removed) so an already-watched tool stays subscribed.
|
|
52
|
+
const watchedRef = (0, _react.useRef)(new Set());
|
|
53
|
+
|
|
54
|
+
// Re-run the effect only when tools are added/removed, not on every
|
|
55
|
+
// object-reference change.
|
|
56
|
+
const toolIdsKey = Object.keys(tools).sort().join(",");
|
|
57
|
+
(0, _react.useEffect)(() => {
|
|
58
|
+
if (!socket || !deviceId || !enabled) return;
|
|
59
|
+
const unsubs = new Map();
|
|
60
|
+
const lastEmit = {};
|
|
61
|
+
const timers = {};
|
|
62
|
+
const isForThisDevice = targetDeviceId => targetDeviceId === "All" || targetDeviceId === deviceId;
|
|
63
|
+
const safeEmit = (event, message) => {
|
|
64
|
+
try {
|
|
65
|
+
socket.emit(event, message);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
(0, _logger.log)(`${LOG} Failed to emit "${event}": ${error}. Payload must be JSON-serializable.`, enableLogs, "error");
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const sendSnapshot = toolId => {
|
|
71
|
+
const tool = toolsRef.current[toolId];
|
|
72
|
+
if (!tool) return;
|
|
73
|
+
lastEmit[toolId] = Date.now();
|
|
74
|
+
const message = {
|
|
75
|
+
toolId,
|
|
76
|
+
persistentDeviceId: deviceId,
|
|
77
|
+
timestamp: lastEmit[toolId],
|
|
78
|
+
version: tool.version,
|
|
79
|
+
kind: "snapshot",
|
|
80
|
+
payload: tool.getSnapshot()
|
|
81
|
+
};
|
|
82
|
+
safeEmit("devtool-sync", message);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Leading + trailing throttle: emit immediately when outside the window,
|
|
86
|
+
// otherwise schedule a flush for the window's end so the dashboard always
|
|
87
|
+
// converges to the latest state.
|
|
88
|
+
const scheduleSnapshot = toolId => {
|
|
89
|
+
if (timers[toolId]) return;
|
|
90
|
+
const elapsed = Date.now() - (lastEmit[toolId] ?? 0);
|
|
91
|
+
if (elapsed >= throttleMs) {
|
|
92
|
+
sendSnapshot(toolId);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
timers[toolId] = setTimeout(() => {
|
|
96
|
+
delete timers[toolId];
|
|
97
|
+
sendSnapshot(toolId);
|
|
98
|
+
}, throttleMs - elapsed);
|
|
99
|
+
};
|
|
100
|
+
const watchTool = toolId => {
|
|
101
|
+
const tool = toolsRef.current[toolId];
|
|
102
|
+
if (!tool || unsubs.has(toolId)) return;
|
|
103
|
+
unsubs.set(toolId, tool.subscribe(() => scheduleSnapshot(toolId)));
|
|
104
|
+
(0, _logger.log)(`${LOG} Watching "${toolId}"`, enableLogs);
|
|
105
|
+
};
|
|
106
|
+
const unwatchTool = toolId => {
|
|
107
|
+
unsubs.get(toolId)?.();
|
|
108
|
+
unsubs.delete(toolId);
|
|
109
|
+
if (timers[toolId]) {
|
|
110
|
+
clearTimeout(timers[toolId]);
|
|
111
|
+
delete timers[toolId];
|
|
112
|
+
}
|
|
113
|
+
(0, _logger.log)(`${LOG} Stopped watching "${toolId}"`, enableLogs);
|
|
114
|
+
};
|
|
115
|
+
const sendCapabilities = () => {
|
|
116
|
+
const message = {
|
|
117
|
+
persistentDeviceId: deviceId,
|
|
118
|
+
protocolVersion: _types.SYNC_PROTOCOL_VERSION,
|
|
119
|
+
tools: Object.entries(toolsRef.current).map(([toolId, tool]) => ({
|
|
120
|
+
toolId,
|
|
121
|
+
version: tool.version,
|
|
122
|
+
actions: Object.keys(tool.actions ?? {})
|
|
123
|
+
}))
|
|
124
|
+
};
|
|
125
|
+
safeEmit("devtool-capabilities", message);
|
|
126
|
+
};
|
|
127
|
+
const handleWatch = message => {
|
|
128
|
+
if (!isForThisDevice(message.targetDeviceId)) return;
|
|
129
|
+
if (message.watching) {
|
|
130
|
+
watchedRef.current.add(message.toolId);
|
|
131
|
+
watchTool(message.toolId);
|
|
132
|
+
sendSnapshot(message.toolId);
|
|
133
|
+
} else {
|
|
134
|
+
watchedRef.current.delete(message.toolId);
|
|
135
|
+
unwatchTool(message.toolId);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const handleRequest = message => {
|
|
139
|
+
if (!isForThisDevice(message.targetDeviceId)) return;
|
|
140
|
+
sendSnapshot(message.toolId);
|
|
141
|
+
};
|
|
142
|
+
const handleAction = async message => {
|
|
143
|
+
if (!isForThisDevice(message.targetDeviceId)) return;
|
|
144
|
+
const {
|
|
145
|
+
requestId,
|
|
146
|
+
toolId,
|
|
147
|
+
action,
|
|
148
|
+
params
|
|
149
|
+
} = message;
|
|
150
|
+
const result = {
|
|
151
|
+
requestId,
|
|
152
|
+
toolId,
|
|
153
|
+
action,
|
|
154
|
+
persistentDeviceId: deviceId,
|
|
155
|
+
timestamp: Date.now(),
|
|
156
|
+
ok: false
|
|
157
|
+
};
|
|
158
|
+
const tool = toolsRef.current[toolId];
|
|
159
|
+
const fn = tool?.actions?.[action];
|
|
160
|
+
if (!fn) {
|
|
161
|
+
result.error = tool ? `Unknown action "${action}" on tool "${toolId}"` : `Unknown tool "${toolId}"`;
|
|
162
|
+
safeEmit("devtool-action-result", result);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
result.result = await fn(params);
|
|
167
|
+
result.ok = true;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
result.error = error instanceof Error ? error.message : String(error);
|
|
170
|
+
}
|
|
171
|
+
result.timestamp = Date.now();
|
|
172
|
+
safeEmit("devtool-action-result", result);
|
|
173
|
+
};
|
|
174
|
+
const handleConnect = () => {
|
|
175
|
+
// The server replies with current watch state for this device.
|
|
176
|
+
sendCapabilities();
|
|
177
|
+
};
|
|
178
|
+
if (socket.connected) sendCapabilities();
|
|
179
|
+
|
|
180
|
+
// Restore subscriptions for tools that were watched before this effect
|
|
181
|
+
// re-ran (or that arrived after the watch message).
|
|
182
|
+
for (const toolId of watchedRef.current) {
|
|
183
|
+
if (toolsRef.current[toolId]) watchTool(toolId);
|
|
184
|
+
}
|
|
185
|
+
socket.on("connect", handleConnect);
|
|
186
|
+
socket.on("devtool-watch", handleWatch);
|
|
187
|
+
socket.on("request-devtool-state", handleRequest);
|
|
188
|
+
socket.on("devtool-action", handleAction);
|
|
189
|
+
return () => {
|
|
190
|
+
socket.off("connect", handleConnect);
|
|
191
|
+
socket.off("devtool-watch", handleWatch);
|
|
192
|
+
socket.off("request-devtool-state", handleRequest);
|
|
193
|
+
socket.off("devtool-action", handleAction);
|
|
194
|
+
for (const toolId of [...unsubs.keys()]) unwatchTool(toolId);
|
|
195
|
+
};
|
|
196
|
+
// toolIdsKey stands in for `tools` (see comment above its definition).
|
|
197
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
198
|
+
}, [socket, deviceId, enabled, throttleMs, enableLogs, toolIdsKey]);
|
|
199
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.useExternalSyncSocket = useExternalSyncSocket;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
var _socket = require("socket.io-client");
|
|
9
|
+
var _platformUtils = require("./utils/platformUtils");
|
|
10
|
+
var _logger = require("./utils/logger");
|
|
11
|
+
/**
|
|
12
|
+
* Singleton socket instance that persists across component renders.
|
|
13
|
+
* Multiple components share the same socket connection.
|
|
14
|
+
*/
|
|
15
|
+
let globalSocketInstance = null;
|
|
16
|
+
let currentSocketURL = "";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Hook that manages a Socket.IO connection for device-to-dashboard communication.
|
|
20
|
+
*
|
|
21
|
+
* Features:
|
|
22
|
+
* - Singleton pattern for socket connection
|
|
23
|
+
* - Platform-specific URL handling (Android emulator -> 10.0.2.2)
|
|
24
|
+
* - Connection state tracking
|
|
25
|
+
*/
|
|
26
|
+
function useExternalSyncSocket({
|
|
27
|
+
deviceName,
|
|
28
|
+
socketURL,
|
|
29
|
+
persistentDeviceId,
|
|
30
|
+
extraDeviceInfo,
|
|
31
|
+
envVariables,
|
|
32
|
+
platform,
|
|
33
|
+
enableLogs = false
|
|
34
|
+
}) {
|
|
35
|
+
const socketRef = (0, _react.useRef)(null);
|
|
36
|
+
const [socket, setSocket] = (0, _react.useState)(null);
|
|
37
|
+
const [isConnected, setIsConnected] = (0, _react.useState)(false);
|
|
38
|
+
const initialized = (0, _react.useRef)(false);
|
|
39
|
+
const logPrefix = `[${deviceName}]`;
|
|
40
|
+
const onConnect = () => {
|
|
41
|
+
(0, _logger.log)(`${logPrefix} Socket connected successfully`, enableLogs);
|
|
42
|
+
setIsConnected(true);
|
|
43
|
+
};
|
|
44
|
+
const onDisconnect = reason => {
|
|
45
|
+
(0, _logger.log)(`${logPrefix} Socket disconnected. Reason: ${reason}`, enableLogs);
|
|
46
|
+
setIsConnected(false);
|
|
47
|
+
};
|
|
48
|
+
const onConnectError = error => {
|
|
49
|
+
(0, _logger.log)(`${logPrefix} Socket connection error: ${error.message}`, enableLogs, "error");
|
|
50
|
+
};
|
|
51
|
+
const onConnectTimeout = () => {
|
|
52
|
+
(0, _logger.log)(`${logPrefix} Socket connection timeout`, enableLogs, "error");
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Main socket initialization - runs only once
|
|
56
|
+
(0, _react.useEffect)(() => {
|
|
57
|
+
if (!persistentDeviceId) return;
|
|
58
|
+
if (initialized.current) return;
|
|
59
|
+
initialized.current = true;
|
|
60
|
+
const platformUrl = (0, _platformUtils.getPlatformSpecificURL)(socketURL, platform);
|
|
61
|
+
currentSocketURL = platformUrl;
|
|
62
|
+
(0, _logger.log)(`${logPrefix} Platform: ${platform}, using URL: ${platformUrl}`, enableLogs);
|
|
63
|
+
try {
|
|
64
|
+
if (!globalSocketInstance) {
|
|
65
|
+
(0, _logger.log)(`${logPrefix} Creating new socket instance to ${platformUrl}`, enableLogs);
|
|
66
|
+
globalSocketInstance = (0, _socket.io)(platformUrl, {
|
|
67
|
+
autoConnect: true,
|
|
68
|
+
query: {
|
|
69
|
+
deviceName,
|
|
70
|
+
deviceId: persistentDeviceId,
|
|
71
|
+
platform,
|
|
72
|
+
extraDeviceInfo: JSON.stringify(extraDeviceInfo),
|
|
73
|
+
envVariables: JSON.stringify(envVariables)
|
|
74
|
+
},
|
|
75
|
+
reconnection: false,
|
|
76
|
+
transports: ["websocket"]
|
|
77
|
+
});
|
|
78
|
+
} else {
|
|
79
|
+
(0, _logger.log)(`${logPrefix} Reusing existing socket instance to ${platformUrl}`, enableLogs);
|
|
80
|
+
}
|
|
81
|
+
socketRef.current = globalSocketInstance;
|
|
82
|
+
setSocket(socketRef.current);
|
|
83
|
+
socketRef.current.on("connect_error", onConnectError);
|
|
84
|
+
socketRef.current.on("connect_timeout", onConnectTimeout);
|
|
85
|
+
if (socketRef.current.connected) {
|
|
86
|
+
setIsConnected(true);
|
|
87
|
+
(0, _logger.log)(`${logPrefix} Socket already connected on init`, enableLogs);
|
|
88
|
+
}
|
|
89
|
+
socketRef.current.on("connect", onConnect);
|
|
90
|
+
socketRef.current.on("disconnect", onDisconnect);
|
|
91
|
+
return () => {
|
|
92
|
+
if (socketRef.current) {
|
|
93
|
+
(0, _logger.log)(`${logPrefix} Cleaning up socket event listeners`, enableLogs);
|
|
94
|
+
socketRef.current.off("connect", onConnect);
|
|
95
|
+
socketRef.current.off("disconnect", onDisconnect);
|
|
96
|
+
socketRef.current.off("connect_error", onConnectError);
|
|
97
|
+
socketRef.current.off("connect_timeout", onConnectTimeout);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
} catch (error) {
|
|
101
|
+
(0, _logger.log)(`${logPrefix} Failed to initialize socket: ${error}`, enableLogs, "error");
|
|
102
|
+
}
|
|
103
|
+
}, [persistentDeviceId]);
|
|
104
|
+
|
|
105
|
+
// Update socket query parameters when deviceName changes
|
|
106
|
+
(0, _react.useEffect)(() => {
|
|
107
|
+
if (socketRef.current && socketRef.current.io.opts.query && persistentDeviceId) {
|
|
108
|
+
(0, _logger.log)(`${logPrefix} Updating device name in socket connection`, enableLogs);
|
|
109
|
+
socketRef.current.io.opts.query = {
|
|
110
|
+
...socketRef.current.io.opts.query,
|
|
111
|
+
deviceName,
|
|
112
|
+
deviceId: persistentDeviceId,
|
|
113
|
+
platform
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}, [deviceName, logPrefix, persistentDeviceId, platform, enableLogs]);
|
|
117
|
+
|
|
118
|
+
// Update socket URL when socketURL changes
|
|
119
|
+
(0, _react.useEffect)(() => {
|
|
120
|
+
const platformUrl = (0, _platformUtils.getPlatformSpecificURL)(socketURL, platform);
|
|
121
|
+
if (socketRef.current && currentSocketURL !== platformUrl && persistentDeviceId) {
|
|
122
|
+
(0, _logger.log)(`${logPrefix} Socket URL changed from ${currentSocketURL} to ${platformUrl}`, enableLogs);
|
|
123
|
+
try {
|
|
124
|
+
socketRef.current.disconnect();
|
|
125
|
+
currentSocketURL = platformUrl;
|
|
126
|
+
(0, _logger.log)(`${logPrefix} Creating new socket connection to ${platformUrl}`, enableLogs);
|
|
127
|
+
globalSocketInstance = (0, _socket.io)(platformUrl, {
|
|
128
|
+
autoConnect: true,
|
|
129
|
+
query: {
|
|
130
|
+
deviceName,
|
|
131
|
+
deviceId: persistentDeviceId,
|
|
132
|
+
platform,
|
|
133
|
+
extraDeviceInfo: JSON.stringify(extraDeviceInfo),
|
|
134
|
+
envVariables: JSON.stringify(envVariables)
|
|
135
|
+
},
|
|
136
|
+
reconnection: false,
|
|
137
|
+
transports: ["websocket"]
|
|
138
|
+
});
|
|
139
|
+
socketRef.current = globalSocketInstance;
|
|
140
|
+
setSocket(socketRef.current);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
(0, _logger.log)(`${logPrefix} Failed to update socket connection: ${error}`, enableLogs, "error");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}, [socketURL, deviceName, logPrefix, persistentDeviceId, platform, enableLogs]);
|
|
146
|
+
function connect() {
|
|
147
|
+
if (socketRef.current && !socketRef.current.connected) {
|
|
148
|
+
(0, _logger.log)(`${logPrefix} Manually connecting to socket server`, enableLogs);
|
|
149
|
+
socketRef.current.connect();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function disconnect() {
|
|
153
|
+
if (socketRef.current && socketRef.current.connected) {
|
|
154
|
+
(0, _logger.log)(`${logPrefix} Manually disconnecting from socket server`, enableLogs);
|
|
155
|
+
socketRef.current.disconnect();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
socket,
|
|
160
|
+
connect,
|
|
161
|
+
disconnect,
|
|
162
|
+
isConnected
|
|
163
|
+
};
|
|
164
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.log = log;
|
|
7
|
+
/**
|
|
8
|
+
* Helper function for controlled logging.
|
|
9
|
+
* Only shows logs when enableLogs is true.
|
|
10
|
+
* Always shows warnings and errors regardless of enableLogs setting.
|
|
11
|
+
*/
|
|
12
|
+
function log(message, enableLogs, type = "log") {
|
|
13
|
+
if (!enableLogs && type === "log") return;
|
|
14
|
+
switch (type) {
|
|
15
|
+
case "warn":
|
|
16
|
+
console.warn(message);
|
|
17
|
+
break;
|
|
18
|
+
case "error":
|
|
19
|
+
console.error(message);
|
|
20
|
+
break;
|
|
21
|
+
default:
|
|
22
|
+
console.log(message);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.getPlatformSpecificURL = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Get platform-specific URL for socket connection.
|
|
9
|
+
* On Android emulator, replaces localhost with 10.0.2.2.
|
|
10
|
+
*/
|
|
11
|
+
const getPlatformSpecificURL = (baseUrl, platform) => {
|
|
12
|
+
try {
|
|
13
|
+
const url = new URL(baseUrl);
|
|
14
|
+
if (platform === "android" && (url.hostname === "localhost" || url.hostname === "127.0.0.1")) {
|
|
15
|
+
url.hostname = "10.0.2.2";
|
|
16
|
+
return url.toString();
|
|
17
|
+
}
|
|
18
|
+
return baseUrl;
|
|
19
|
+
} catch (e) {
|
|
20
|
+
console.warn("Error getting platform-specific URL:", e);
|
|
21
|
+
return baseUrl;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
exports.getPlatformSpecificURL = getPlatformSpecificURL;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// ============================================================
|
|
4
|
+
// HOOKS
|
|
5
|
+
// ============================================================
|
|
6
|
+
export { useExternalSync } from "./useExternalSync";
|
|
7
|
+
export { useExternalSyncSocket } from "./useExternalSyncSocket";
|
|
8
|
+
|
|
9
|
+
// ============================================================
|
|
10
|
+
// PROTOCOL
|
|
11
|
+
// ============================================================
|
|
12
|
+
export { SYNC_PROTOCOL_VERSION } from "./types";
|
|
13
|
+
|
|
14
|
+
// ============================================================
|
|
15
|
+
// TYPES
|
|
16
|
+
// ============================================================
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// ============================================================
|
|
4
|
+
// DEVTOOL SYNC PROTOCOL (v1)
|
|
5
|
+
//
|
|
6
|
+
// Topology: device is a Socket.IO client; the desktop app hosts
|
|
7
|
+
// the server (broker) and one or more dashboard UIs. The server
|
|
8
|
+
// routes messages between dashboards and devices.
|
|
9
|
+
//
|
|
10
|
+
// Device -> server/dashboard events:
|
|
11
|
+
// "devtool-capabilities" DeviceCapabilitiesMessage
|
|
12
|
+
// "devtool-sync" DevToolSyncMessage
|
|
13
|
+
// "devtool-action-result" DevToolActionResultMessage
|
|
14
|
+
//
|
|
15
|
+
// Dashboard/server -> device events:
|
|
16
|
+
// "devtool-watch" DevToolWatchMessage
|
|
17
|
+
// "request-devtool-state" DevToolRequestMessage
|
|
18
|
+
// "devtool-action" DevToolActionMessage
|
|
19
|
+
//
|
|
20
|
+
// Backpressure contract: the device sends nothing for a tool until
|
|
21
|
+
// it receives `devtool-watch {watching: true}` for it. The SERVER is
|
|
22
|
+
// responsible for consolidating watch state across dashboards (and
|
|
23
|
+
// for sending `watching: false` when the last watching dashboard
|
|
24
|
+
// closes a panel or disconnects). The device just obeys.
|
|
25
|
+
// ============================================================
|
|
26
|
+
|
|
27
|
+
/** Version of the envelope/event contract itself (not tool payloads). */
|
|
28
|
+
export const SYNC_PROTOCOL_VERSION = 1;
|
|
29
|
+
|
|
30
|
+
/** What a sync payload represents. "events"/"diff" reserved for future use. */
|
|
31
|
+
|
|
32
|
+
// ============================================================
|
|
33
|
+
// TOOL ADAPTER
|
|
34
|
+
// ============================================================
|
|
35
|
+
|
|
36
|
+
/** A remote-invocable action. Thrown errors are reported back to the dashboard. */
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The contract each dev tool implements to become remotely syncable.
|
|
40
|
+
* Works with BaseEventStore or any pattern exposing subscribe + getSnapshot.
|
|
41
|
+
*
|
|
42
|
+
* Payloads (snapshots and action results) must be JSON-serializable —
|
|
43
|
+
* no functions, circular references, or class instances.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
// ============================================================
|
|
47
|
+
// DEVICE -> DASHBOARD MESSAGES
|
|
48
|
+
// ============================================================
|
|
49
|
+
|
|
50
|
+
/** Announces available tools + actions on connect and when tools change. */
|
|
51
|
+
|
|
52
|
+
/** Carries a tool's state to the dashboard. */
|
|
53
|
+
|
|
54
|
+
/** Response to a `devtool-action` request. */
|
|
55
|
+
|
|
56
|
+
// ============================================================
|
|
57
|
+
// DASHBOARD -> DEVICE MESSAGES
|
|
58
|
+
// ============================================================
|
|
59
|
+
|
|
60
|
+
/** Server-consolidated signal that a tool gained/lost its audience. */
|
|
61
|
+
|
|
62
|
+
/** Explicit request for a tool's current state (works even while unwatched). */
|
|
63
|
+
|
|
64
|
+
/** Invokes a registered action on the device. */
|
|
65
|
+
|
|
66
|
+
// ============================================================
|
|
67
|
+
// HOOK PROPS
|
|
68
|
+
// ============================================================
|
|
69
|
+
|
|
70
|
+
// ============================================================
|
|
71
|
+
// PLATFORM
|
|
72
|
+
// ============================================================
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Valid platform operating systems that can be used with React Native
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
// ============================================================
|
|
79
|
+
// SOCKET HOOK PROPS
|
|
80
|
+
// ============================================================
|