@buoy-gg/external-sync 7.0.36 → 7.0.38

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/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # @buoy-gg/external-sync
2
+
3
+ Connect a React Native app's Buoy tools to Buoy Desktop or MCP. This package provides the device-side socket connection, adapter synchronization, and remote-action handling. Most apps configure it through `FloatingDevTools` from `@buoy-gg/core`.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @buoy-gg/core @buoy-gg/external-sync @buoy-gg/network
9
+ ```
10
+
11
+ ## Account setup
12
+
13
+ Start in a development build with a Free or Pro Buoy account key. From the app directory:
14
+
15
+ ```bash
16
+ npx --package=@buoy-gg/core buoy login
17
+ ```
18
+
19
+ For Expo, initialize Buoy with the key written to `.env.local`:
20
+
21
+ ```tsx
22
+ import { Buoy } from "@buoy-gg/core";
23
+
24
+ Buoy.init({ licenseKey: process.env.EXPO_PUBLIC_BUOY_KEY });
25
+ ```
26
+
27
+ For React Native CLI, pass the key through your app's environment configuration; `.env.local` is not loaded automatically. Keep the menu inside the providers used by your app. Restart the development server after installing packages. See the [Quick Start](https://buoy.gg/buoy/latest/docs/quick-start) for the complete root component.
28
+
29
+ ## Connect your app
30
+
31
+ Mount the menu in your existing root component after account initialization:
32
+
33
+ ```tsx
34
+ import { FloatingDevTools } from "@buoy-gg/core";
35
+
36
+ <FloatingDevTools />;
37
+ ```
38
+
39
+ Open Desktop and sign in. In development, the app normally derives the broker address from Metro. If your network or Expo tunnel setup needs an explicit address, replace the menu above with:
40
+
41
+ ```tsx
42
+ <FloatingDevTools
43
+ externalSync={{ socketURL: "http://YOUR_COMPUTER_LAN_IP:42831" }}
44
+ />;
45
+ ```
46
+
47
+ `socketURL` has an uppercase URL. The device must be able to reach that address; a Metro tunnel does not automatically expose the broker. For Android over USB, follow the port-forwarding instructions in the [Desktop guide](https://buoy.gg/buoy/latest/docs/desktop).
48
+
49
+ Select the device in Desktop, trigger a fresh HTTP request in the app, and check that it appears in Network. If no device appears, check both account states, the broker address, and network connectivity. If the device appears but the panel is empty, check the tool's installation and capture path.
50
+
51
+ ## Headless and release builds
52
+
53
+ `<FloatingDevTools headless />` keeps the connection integration without the floating menu. It still needs account access and configured tools.
54
+
55
+ Release sync is disabled by default. For an authorized internal release build with Pro, explicitly configure it:
56
+
57
+ ```tsx
58
+ <FloatingDevTools
59
+ headless
60
+ externalSync={{
61
+ enableInRelease: true,
62
+ socketURL: "http://YOUR_COMPUTER_LAN_IP:42831",
63
+ }}
64
+ />;
65
+ ```
66
+
67
+ Apply your app's authorization checks before mounting this integration. Enabling sync does not make development-only tools work in release builds.
68
+
69
+ ## Custom integrations
70
+
71
+ The public exports include `useExternalSyncSocket`, `useExternalSync`, `ToolSyncAdapter`, URL helpers, sync-status subscriptions, and crash-flush helpers. An adapter supplies snapshots, subscriptions, and named actions. The bridge subscribes according to dashboard watch state and throttles updates; it also enforces device account admission.
72
+
73
+ Prefer the core integration unless you are building a custom host. A direct hook integration must manage socket and device identity, account initialization, adapter lifecycle, and action authorization. Use the exported option types and [custom-tool guide](https://buoy.gg/buoy/latest/docs/custom-tools).
74
+
75
+ ## Desktop and MCP
76
+
77
+ React Native connections require `@buoy-gg/external-sync`. Follow the [Desktop connection guide](https://buoy.gg/buoy/latest/docs/desktop), sign in to Desktop separately, and confirm the selected device. [MCP](https://buoy.gg/buoy/latest/docs/mcp) also needs a process account and Pro access.
78
+
79
+ Device data can travel over your LAN to the configured broker. Account validation makes network requests. See [Telemetry](https://buoy.gg/buoy/latest/docs/telemetry) for data-flow details.
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.attachDeviceSync = attachDeviceSync;
7
+ var _license = require("@buoy-gg/license");
7
8
  var _logger = require("./utils/logger");
8
9
  var _crashFlush = require("./crashFlush");
9
10
  var _emitBudget = require("./emitBudget");
@@ -29,11 +30,16 @@ function attachDeviceSync({
29
30
  throttleMs = 200,
30
31
  enableLogs = false
31
32
  }) {
33
+ let disposed = false;
34
+ let brokerReady = false;
35
+ let accessGeneration = 0;
36
+ const allowed = () => !disposed && brokerReady && socket.connected && _license.LicenseManager.hasAccountAccess();
32
37
  const unsubs = new Map();
33
38
  const lastEmit = {};
34
39
  const timers = {};
35
40
  const isForThisDevice = targetDeviceId => targetDeviceId === "All" || targetDeviceId === deviceId;
36
41
  const safeEmit = (event, message) => {
42
+ if (!allowed()) return;
37
43
  try {
38
44
  socket.emit(event, message);
39
45
  } catch (error) {
@@ -42,10 +48,16 @@ function attachDeviceSync({
42
48
  };
43
49
  const warnedOversized = new Set();
44
50
  const sendSnapshot = toolId => {
51
+ if (!allowed()) return;
45
52
  const tool = getTools()[toolId];
46
53
  if (!tool) return;
47
54
  lastEmit[toolId] = Date.now();
48
- const payload = tool.getSnapshot();
55
+ let payload;
56
+ try {
57
+ payload = tool.getSnapshot();
58
+ } catch {
59
+ return;
60
+ }
49
61
  const {
50
62
  over,
51
63
  approxBytes
@@ -108,6 +120,7 @@ function attachDeviceSync({
108
120
  /** The mode each currently-subscribed tool is wired for. */
109
121
  const activeModes = new Map();
110
122
  const watchTool = (toolId, mode) => {
123
+ if (!allowed()) return;
111
124
  const tool = getTools()[toolId];
112
125
  if (!tool) return;
113
126
  if (unsubs.has(toolId)) {
@@ -149,7 +162,7 @@ function attachDeviceSync({
149
162
  safeEmit("devtool-capabilities", message);
150
163
  };
151
164
  const handleWatch = message => {
152
- if (!isForThisDevice(message.targetDeviceId)) return;
165
+ if (!allowed() || !isForThisDevice(message.targetDeviceId)) return;
153
166
  if (message.watching) {
154
167
  // `stream` is only trustworthy once a broker has merged every
155
168
  // dashboard's request (see DevToolWatchMessage.consolidated). Without
@@ -168,11 +181,12 @@ function attachDeviceSync({
168
181
  }
169
182
  };
170
183
  const handleRequest = message => {
171
- if (!isForThisDevice(message.targetDeviceId)) return;
184
+ if (!allowed() || !isForThisDevice(message.targetDeviceId)) return;
172
185
  sendSnapshot(message.toolId);
173
186
  };
174
187
  const handleAction = async message => {
175
- if (!isForThisDevice(message.targetDeviceId)) return;
188
+ if (!allowed() || !isForThisDevice(message.targetDeviceId)) return;
189
+ const startedAt = accessGeneration;
176
190
  const {
177
191
  requestId,
178
192
  toolId,
@@ -200,6 +214,9 @@ function attachDeviceSync({
200
214
  } catch (error) {
201
215
  result.error = error instanceof Error ? error.message : String(error);
202
216
  }
217
+
218
+ // A subsequent admission cannot authorize work from the previous session.
219
+ if (startedAt !== accessGeneration || !allowed()) return;
203
220
  if (result.ok) {
204
221
  const {
205
222
  over,
@@ -214,20 +231,61 @@ function attachDeviceSync({
214
231
  result.timestamp = Date.now();
215
232
  safeEmit("devtool-action-result", result);
216
233
  };
217
- const handleConnect = () => {
234
+ const dropToolWork = () => {
235
+ accessGeneration += 1;
236
+ for (const toolId of [...unsubs.keys()]) unwatchTool(toolId);
237
+ watched.clear();
238
+ for (const timer of Object.values(timers)) clearTimeout(timer);
239
+ const buffered = socket;
240
+ if (buffered.sendBuffer) buffered.sendBuffer = buffered.sendBuffer.filter(packet => !["devtool-sync", "devtool-capabilities", "devtool-action-result", "device-license"].includes(String(packet.data?.[0])));
241
+ };
242
+ const announceAccount = () => {
243
+ const state = _license.LicenseManager.getState();
244
+ socket.emit("device-license", {
245
+ tier: state.tier,
246
+ isPro: state.isPro ? "1" : "0"
247
+ });
218
248
  sendCapabilities();
219
249
  };
220
- if (socket.connected) sendCapabilities();
250
+ const handleAccess = message => {
251
+ brokerReady = message?.protocol === 1 && message.ready === true;
252
+ if (!allowed()) dropToolWork();else announceAccount();
253
+ };
254
+ const offAccount = _license.LicenseManager.subscribe(() => {
255
+ if (!_license.LicenseManager.hasAccountAccess()) {
256
+ if (socket.connected) socket.emit("device-license", {
257
+ tier: "locked",
258
+ isPro: "0"
259
+ });
260
+ dropToolWork();
261
+ } else if (allowed()) announceAccount();
262
+ });
263
+ const handleConnect = () => {
264
+ brokerReady = false;
265
+ socket.emit("buoy:access:request");
266
+ };
267
+ const handleDisconnect = () => {
268
+ brokerReady = false;
269
+ dropToolWork();
270
+ };
271
+ socket.on("buoy:access", handleAccess);
221
272
  for (const [toolId, mode] of watched) {
222
273
  if (getTools()[toolId]) watchTool(toolId, mode);
223
274
  }
224
275
  socket.on("connect", handleConnect);
276
+ socket.on("disconnect", handleDisconnect);
225
277
  socket.on("devtool-watch", handleWatch);
226
278
  socket.on("request-devtool-state", handleRequest);
227
279
  socket.on("devtool-action", handleAction);
280
+ if (socket.connected) handleConnect();
228
281
  return () => {
282
+ disposed = true;
283
+ offAccount();
284
+ socket.off("buoy:access", handleAccess);
285
+ dropToolWork();
229
286
  (0, _crashFlush.unregisterToolFlusher)(flushNow);
230
287
  socket.off("connect", handleConnect);
288
+ socket.off("disconnect", handleDisconnect);
231
289
  socket.off("devtool-watch", handleWatch);
232
290
  socket.off("request-devtool-state", handleRequest);
233
291
  socket.off("devtool-action", handleAction);
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.watchBrokerAccess = watchBrokerAccess;
7
+ /** Wait for an account-aware broker before publishing any tool capabilities. */
8
+
9
+ function watchBrokerAccess(socket, update) {
10
+ const lock = () => update(false);
11
+ const request = () => {
12
+ lock();
13
+ socket.emit("buoy:access:request");
14
+ };
15
+ const access = message => {
16
+ const info = message;
17
+ update(info?.protocol === 1 && info?.ready === true);
18
+ };
19
+ socket.on("connect", request);
20
+ socket.on("disconnect", lock);
21
+ socket.on("buoy:access", access);
22
+ lock();
23
+ if (socket.connected) request();
24
+ return () => {
25
+ socket.off("connect", request);
26
+ socket.off("disconnect", lock);
27
+ socket.off("buoy:access", access);
28
+ };
29
+ }
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.useExternalSync = useExternalSync;
7
+ var _license = require("@buoy-gg/license");
7
8
  var _react = require("react");
8
9
  var _attachDeviceSync = require("./attachDeviceSync");
9
10
  /**
@@ -41,6 +42,8 @@ function useExternalSync({
41
42
  throttleMs = 200,
42
43
  enableLogs = false
43
44
  }) {
45
+ const account = (0, _license.useAccountAccess)();
46
+ const admitted = account.status === "admitted";
44
47
  const toolsRef = (0, _react.useRef)(tools);
45
48
  toolsRef.current = tools;
46
49
 
@@ -52,7 +55,10 @@ function useExternalSync({
52
55
  // object-reference change.
53
56
  const toolIdsKey = Object.keys(tools).sort().join(",");
54
57
  (0, _react.useEffect)(() => {
55
- if (!socket || !deviceId || !enabled) return;
58
+ if (!socket || !deviceId || !enabled || !admitted) {
59
+ watchedRef.current.clear();
60
+ return;
61
+ }
56
62
  return (0, _attachDeviceSync.attachDeviceSync)({
57
63
  socket,
58
64
  deviceId,
@@ -63,5 +69,5 @@ function useExternalSync({
63
69
  });
64
70
  // toolIdsKey stands in for `tools` (see comment above its definition).
65
71
  // eslint-disable-next-line react-hooks/exhaustive-deps
66
- }, [socket, deviceId, enabled, throttleMs, enableLogs, toolIdsKey]);
72
+ }, [socket, deviceId, enabled, admitted, throttleMs, enableLogs, toolIdsKey]);
67
73
  }
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.buildHandshakeQuery = buildHandshakeQuery;
7
7
  exports.getInstanceId = getInstanceId;
8
8
  exports.useExternalSyncSocket = useExternalSyncSocket;
9
+ var _brokerAccess = require("./brokerAccess");
9
10
  var _react = require("react");
10
11
  var _socket = require("socket.io-client");
11
12
  var _platformUtils = require("./utils/platformUtils");
@@ -22,6 +23,8 @@ const isDev = typeof __DEV__ !== "undefined" ? __DEV__ : false;
22
23
  */
23
24
  let globalSocketInstance = null;
24
25
  let currentSocketURL = "";
26
+ let currentDeviceId = "";
27
+ let currentPlatform = "";
25
28
 
26
29
  /**
27
30
  * Identifies a single socket INSTANCE. Sent in the handshake query and reused
@@ -52,12 +55,13 @@ function getInstanceId() {
52
55
  * runtime (see above). Pure, so the shape is unit-tested.
53
56
  */
54
57
  function buildHandshakeQuery(identity) {
58
+ const withoutAccountKey = values => values && Object.fromEntries(Object.entries(values).filter(([key]) => !["BUOY_LICENSE_KEY", "BUOY_KEY", "EXPO_PUBLIC_BUOY_KEY", "VITE_BUOY_KEY", "REACT_APP_BUOY_KEY", "licenseKey"].includes(key)));
55
59
  return {
56
60
  deviceName: identity.deviceName,
57
61
  deviceId: identity.persistentDeviceId,
58
62
  platform: identity.platform,
59
- extraDeviceInfo: JSON.stringify(identity.extraDeviceInfo),
60
- envVariables: JSON.stringify(identity.envVariables),
63
+ extraDeviceInfo: JSON.stringify(withoutAccountKey(identity.extraDeviceInfo)),
64
+ envVariables: JSON.stringify(withoutAccountKey(identity.envVariables)),
61
65
  connectionNonce: makeConnectionNonce(),
62
66
  instanceId: getInstanceId()
63
67
  };
@@ -83,7 +87,6 @@ function useExternalSyncSocket({
83
87
  const socketRef = (0, _react.useRef)(null);
84
88
  const [socket, setSocket] = (0, _react.useState)(null);
85
89
  const [isConnected, setIsConnected] = (0, _react.useState)(false);
86
- const initialized = (0, _react.useRef)(false);
87
90
  // Derived once per render; stable string, so effect deps stay quiet.
88
91
  const resolvedSocketURL = socketURL ?? (0, _getDefaultSocketURL.getDefaultSocketURL)();
89
92
  // Tracks whether we've already logged a connection error for the current
@@ -126,13 +129,11 @@ function useExternalSyncSocket({
126
129
  (0, _logger.log)(`${logPrefix} Socket connection timeout`, enableLogs, "error");
127
130
  };
128
131
 
129
- // Main socket initialization - runs only once
132
+ // Reuse the transport, but reattach listeners after every effect setup
133
+ // (including React Strict Mode replay and Fast Refresh).
130
134
  (0, _react.useEffect)(() => {
131
135
  if (!persistentDeviceId) return;
132
- if (initialized.current) return;
133
- initialized.current = true;
134
136
  const platformUrl = (0, _platformUtils.getPlatformSpecificURL)(resolvedSocketURL, platform);
135
- currentSocketURL = platformUrl;
136
137
  (0, _syncStatusStore.setSyncStatus)({
137
138
  targetURL: platformUrl,
138
139
  state: "connecting",
@@ -140,6 +141,10 @@ function useExternalSyncSocket({
140
141
  });
141
142
  (0, _logger.log)(`${logPrefix} Platform: ${platform}, using URL: ${platformUrl}`, enableLogs);
142
143
  try {
144
+ if (globalSocketInstance && (currentSocketURL !== platformUrl || currentDeviceId !== persistentDeviceId || currentPlatform !== platform)) {
145
+ globalSocketInstance.disconnect();
146
+ globalSocketInstance = null;
147
+ }
143
148
  if (!globalSocketInstance) {
144
149
  (0, _logger.log)(`${logPrefix} Creating new socket instance to ${platformUrl}`, enableLogs);
145
150
  globalSocketInstance = (0, _socket.io)(platformUrl, {
@@ -157,32 +162,34 @@ function useExternalSyncSocket({
157
162
  reconnectionDelayMax: 5000,
158
163
  transports: ["websocket"]
159
164
  });
165
+ currentSocketURL = platformUrl;
166
+ currentDeviceId = persistentDeviceId;
167
+ currentPlatform = platform;
160
168
  } else {
161
169
  (0, _logger.log)(`${logPrefix} Reusing existing socket instance to ${platformUrl}`, enableLogs);
162
170
  }
163
171
  socketRef.current = globalSocketInstance;
164
- setSocket(socketRef.current);
165
- socketRef.current.on("connect_error", onConnectError);
166
- socketRef.current.on("connect_timeout", onConnectTimeout);
167
- if (socketRef.current.connected) {
168
- setIsConnected(true);
172
+ const activeSocket = socketRef.current;
173
+ setSocket(activeSocket);
174
+ activeSocket.on("connect_error", onConnectError);
175
+ activeSocket.on("connect_timeout", onConnectTimeout);
176
+ setIsConnected(activeSocket.connected);
177
+ if (activeSocket.connected) {
169
178
  (0, _logger.log)(`${logPrefix} Socket already connected on init`, enableLogs);
170
179
  }
171
- socketRef.current.on("connect", onConnect);
172
- socketRef.current.on("disconnect", onDisconnect);
180
+ activeSocket.on("connect", onConnect);
181
+ activeSocket.on("disconnect", onDisconnect);
173
182
  return () => {
174
- if (socketRef.current) {
175
- (0, _logger.log)(`${logPrefix} Cleaning up socket event listeners`, enableLogs);
176
- socketRef.current.off("connect", onConnect);
177
- socketRef.current.off("disconnect", onDisconnect);
178
- socketRef.current.off("connect_error", onConnectError);
179
- socketRef.current.off("connect_timeout", onConnectTimeout);
180
- }
183
+ (0, _logger.log)(`${logPrefix} Cleaning up socket event listeners`, enableLogs);
184
+ activeSocket.off("connect", onConnect);
185
+ activeSocket.off("disconnect", onDisconnect);
186
+ activeSocket.off("connect_error", onConnectError);
187
+ activeSocket.off("connect_timeout", onConnectTimeout);
181
188
  };
182
189
  } catch (error) {
183
190
  (0, _logger.log)(`${logPrefix} Failed to initialize socket: ${error}`, enableLogs, "error");
184
191
  }
185
- }, [persistentDeviceId]);
192
+ }, [persistentDeviceId, resolvedSocketURL, platform]);
186
193
 
187
194
  // Update socket query parameters when deviceName changes
188
195
  (0, _react.useEffect)(() => {
@@ -196,43 +203,6 @@ function useExternalSyncSocket({
196
203
  };
197
204
  }
198
205
  }, [deviceName, logPrefix, persistentDeviceId, platform, enableLogs]);
199
-
200
- // Update socket URL when socketURL changes
201
- (0, _react.useEffect)(() => {
202
- const platformUrl = (0, _platformUtils.getPlatformSpecificURL)(resolvedSocketURL, platform);
203
- if (socketRef.current && currentSocketURL !== platformUrl && persistentDeviceId) {
204
- (0, _logger.log)(`${logPrefix} Socket URL changed from ${currentSocketURL} to ${platformUrl}`, enableLogs);
205
- try {
206
- socketRef.current.disconnect();
207
- currentSocketURL = platformUrl;
208
- (0, _syncStatusStore.setSyncStatus)({
209
- targetURL: platformUrl,
210
- state: "connecting",
211
- lastError: null
212
- });
213
- (0, _logger.log)(`${logPrefix} Creating new socket connection to ${platformUrl}`, enableLogs);
214
- globalSocketInstance = (0, _socket.io)(platformUrl, {
215
- autoConnect: true,
216
- query: buildHandshakeQuery({
217
- deviceName,
218
- persistentDeviceId,
219
- platform,
220
- extraDeviceInfo,
221
- envVariables
222
- }),
223
- reconnection: true,
224
- reconnectionAttempts: Infinity,
225
- reconnectionDelay: 1000,
226
- reconnectionDelayMax: 5000,
227
- transports: ["websocket"]
228
- });
229
- socketRef.current = globalSocketInstance;
230
- setSocket(socketRef.current);
231
- } catch (error) {
232
- (0, _logger.log)(`${logPrefix} Failed to update socket connection: ${error}`, enableLogs, "error");
233
- }
234
- }
235
- }, [resolvedSocketURL, deviceName, logPrefix, persistentDeviceId, platform, enableLogs]);
236
206
  function connect() {
237
207
  if (socketRef.current && !socketRef.current.connected) {
238
208
  (0, _logger.log)(`${logPrefix} Manually connecting to socket server`, enableLogs);
@@ -245,10 +215,27 @@ function useExternalSyncSocket({
245
215
  socketRef.current.disconnect();
246
216
  }
247
217
  }
218
+
219
+ // A connected transport is not permission to sync. Legacy desktop brokers
220
+ // never send this announcement, so updated SDKs withhold tools from them.
221
+ const [admittedSocket, setAdmittedSocket] = (0, _react.useState)(null);
222
+ (0, _react.useEffect)(() => {
223
+ if (!socket) return;
224
+ return (0, _brokerAccess.watchBrokerAccess)(socket, ready => {
225
+ setAdmittedSocket(ready ? socket : null);
226
+ (0, _syncStatusStore.setSyncStatus)(ready ? {
227
+ state: "connected",
228
+ lastError: null
229
+ } : {
230
+ state: "retrying",
231
+ lastError: "Update Buoy Desktop and sign in to enable desktop sync."
232
+ });
233
+ });
234
+ }, [socket]);
248
235
  return {
249
- socket,
236
+ socket: admittedSocket === socket ? socket : null,
250
237
  connect,
251
238
  disconnect,
252
- isConnected
239
+ isConnected: isConnected && admittedSocket === socket && socket !== null
253
240
  };
254
241
  }
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { LicenseManager } from "@buoy-gg/license";
3
4
  import { log } from "./utils/logger";
4
5
  import { registerToolFlusher, unregisterToolFlusher } from "./crashFlush";
5
6
  import { MAX_ACTION_RESULT_BYTES, MAX_SNAPSHOT_EMIT_BYTES, approxSizeOver } from "./emitBudget";
@@ -25,11 +26,16 @@ export function attachDeviceSync({
25
26
  throttleMs = 200,
26
27
  enableLogs = false
27
28
  }) {
29
+ let disposed = false;
30
+ let brokerReady = false;
31
+ let accessGeneration = 0;
32
+ const allowed = () => !disposed && brokerReady && socket.connected && LicenseManager.hasAccountAccess();
28
33
  const unsubs = new Map();
29
34
  const lastEmit = {};
30
35
  const timers = {};
31
36
  const isForThisDevice = targetDeviceId => targetDeviceId === "All" || targetDeviceId === deviceId;
32
37
  const safeEmit = (event, message) => {
38
+ if (!allowed()) return;
33
39
  try {
34
40
  socket.emit(event, message);
35
41
  } catch (error) {
@@ -38,10 +44,16 @@ export function attachDeviceSync({
38
44
  };
39
45
  const warnedOversized = new Set();
40
46
  const sendSnapshot = toolId => {
47
+ if (!allowed()) return;
41
48
  const tool = getTools()[toolId];
42
49
  if (!tool) return;
43
50
  lastEmit[toolId] = Date.now();
44
- const payload = tool.getSnapshot();
51
+ let payload;
52
+ try {
53
+ payload = tool.getSnapshot();
54
+ } catch {
55
+ return;
56
+ }
45
57
  const {
46
58
  over,
47
59
  approxBytes
@@ -104,6 +116,7 @@ export function attachDeviceSync({
104
116
  /** The mode each currently-subscribed tool is wired for. */
105
117
  const activeModes = new Map();
106
118
  const watchTool = (toolId, mode) => {
119
+ if (!allowed()) return;
107
120
  const tool = getTools()[toolId];
108
121
  if (!tool) return;
109
122
  if (unsubs.has(toolId)) {
@@ -145,7 +158,7 @@ export function attachDeviceSync({
145
158
  safeEmit("devtool-capabilities", message);
146
159
  };
147
160
  const handleWatch = message => {
148
- if (!isForThisDevice(message.targetDeviceId)) return;
161
+ if (!allowed() || !isForThisDevice(message.targetDeviceId)) return;
149
162
  if (message.watching) {
150
163
  // `stream` is only trustworthy once a broker has merged every
151
164
  // dashboard's request (see DevToolWatchMessage.consolidated). Without
@@ -164,11 +177,12 @@ export function attachDeviceSync({
164
177
  }
165
178
  };
166
179
  const handleRequest = message => {
167
- if (!isForThisDevice(message.targetDeviceId)) return;
180
+ if (!allowed() || !isForThisDevice(message.targetDeviceId)) return;
168
181
  sendSnapshot(message.toolId);
169
182
  };
170
183
  const handleAction = async message => {
171
- if (!isForThisDevice(message.targetDeviceId)) return;
184
+ if (!allowed() || !isForThisDevice(message.targetDeviceId)) return;
185
+ const startedAt = accessGeneration;
172
186
  const {
173
187
  requestId,
174
188
  toolId,
@@ -196,6 +210,9 @@ export function attachDeviceSync({
196
210
  } catch (error) {
197
211
  result.error = error instanceof Error ? error.message : String(error);
198
212
  }
213
+
214
+ // A subsequent admission cannot authorize work from the previous session.
215
+ if (startedAt !== accessGeneration || !allowed()) return;
199
216
  if (result.ok) {
200
217
  const {
201
218
  over,
@@ -210,20 +227,61 @@ export function attachDeviceSync({
210
227
  result.timestamp = Date.now();
211
228
  safeEmit("devtool-action-result", result);
212
229
  };
213
- const handleConnect = () => {
230
+ const dropToolWork = () => {
231
+ accessGeneration += 1;
232
+ for (const toolId of [...unsubs.keys()]) unwatchTool(toolId);
233
+ watched.clear();
234
+ for (const timer of Object.values(timers)) clearTimeout(timer);
235
+ const buffered = socket;
236
+ if (buffered.sendBuffer) buffered.sendBuffer = buffered.sendBuffer.filter(packet => !["devtool-sync", "devtool-capabilities", "devtool-action-result", "device-license"].includes(String(packet.data?.[0])));
237
+ };
238
+ const announceAccount = () => {
239
+ const state = LicenseManager.getState();
240
+ socket.emit("device-license", {
241
+ tier: state.tier,
242
+ isPro: state.isPro ? "1" : "0"
243
+ });
214
244
  sendCapabilities();
215
245
  };
216
- if (socket.connected) sendCapabilities();
246
+ const handleAccess = message => {
247
+ brokerReady = message?.protocol === 1 && message.ready === true;
248
+ if (!allowed()) dropToolWork();else announceAccount();
249
+ };
250
+ const offAccount = LicenseManager.subscribe(() => {
251
+ if (!LicenseManager.hasAccountAccess()) {
252
+ if (socket.connected) socket.emit("device-license", {
253
+ tier: "locked",
254
+ isPro: "0"
255
+ });
256
+ dropToolWork();
257
+ } else if (allowed()) announceAccount();
258
+ });
259
+ const handleConnect = () => {
260
+ brokerReady = false;
261
+ socket.emit("buoy:access:request");
262
+ };
263
+ const handleDisconnect = () => {
264
+ brokerReady = false;
265
+ dropToolWork();
266
+ };
267
+ socket.on("buoy:access", handleAccess);
217
268
  for (const [toolId, mode] of watched) {
218
269
  if (getTools()[toolId]) watchTool(toolId, mode);
219
270
  }
220
271
  socket.on("connect", handleConnect);
272
+ socket.on("disconnect", handleDisconnect);
221
273
  socket.on("devtool-watch", handleWatch);
222
274
  socket.on("request-devtool-state", handleRequest);
223
275
  socket.on("devtool-action", handleAction);
276
+ if (socket.connected) handleConnect();
224
277
  return () => {
278
+ disposed = true;
279
+ offAccount();
280
+ socket.off("buoy:access", handleAccess);
281
+ dropToolWork();
225
282
  unregisterToolFlusher(flushNow);
226
283
  socket.off("connect", handleConnect);
284
+ socket.off("disconnect", handleDisconnect);
227
285
  socket.off("devtool-watch", handleWatch);
228
286
  socket.off("request-devtool-state", handleRequest);
229
287
  socket.off("devtool-action", handleAction);
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+
3
+ /** Wait for an account-aware broker before publishing any tool capabilities. */
4
+
5
+ export function watchBrokerAccess(socket, update) {
6
+ const lock = () => update(false);
7
+ const request = () => {
8
+ lock();
9
+ socket.emit("buoy:access:request");
10
+ };
11
+ const access = message => {
12
+ const info = message;
13
+ update(info?.protocol === 1 && info?.ready === true);
14
+ };
15
+ socket.on("connect", request);
16
+ socket.on("disconnect", lock);
17
+ socket.on("buoy:access", access);
18
+ lock();
19
+ if (socket.connected) request();
20
+ return () => {
21
+ socket.off("connect", request);
22
+ socket.off("disconnect", lock);
23
+ socket.off("buoy:access", access);
24
+ };
25
+ }
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { useAccountAccess } from "@buoy-gg/license";
3
4
  import { useEffect, useRef } from "react";
4
5
  import { attachDeviceSync } from "./attachDeviceSync";
5
6
  /**
@@ -37,6 +38,8 @@ export function useExternalSync({
37
38
  throttleMs = 200,
38
39
  enableLogs = false
39
40
  }) {
41
+ const account = useAccountAccess();
42
+ const admitted = account.status === "admitted";
40
43
  const toolsRef = useRef(tools);
41
44
  toolsRef.current = tools;
42
45
 
@@ -48,7 +51,10 @@ export function useExternalSync({
48
51
  // object-reference change.
49
52
  const toolIdsKey = Object.keys(tools).sort().join(",");
50
53
  useEffect(() => {
51
- if (!socket || !deviceId || !enabled) return;
54
+ if (!socket || !deviceId || !enabled || !admitted) {
55
+ watchedRef.current.clear();
56
+ return;
57
+ }
52
58
  return attachDeviceSync({
53
59
  socket,
54
60
  deviceId,
@@ -59,5 +65,5 @@ export function useExternalSync({
59
65
  });
60
66
  // toolIdsKey stands in for `tools` (see comment above its definition).
61
67
  // eslint-disable-next-line react-hooks/exhaustive-deps
62
- }, [socket, deviceId, enabled, throttleMs, enableLogs, toolIdsKey]);
68
+ }, [socket, deviceId, enabled, admitted, throttleMs, enableLogs, toolIdsKey]);
63
69
  }
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ import { watchBrokerAccess } from "./brokerAccess";
3
4
  import { useEffect, useRef, useState } from "react";
4
5
  import { io as socketIO } from "socket.io-client";
5
6
  import { getPlatformSpecificURL } from "./utils/platformUtils";
@@ -16,6 +17,8 @@ const isDev = typeof __DEV__ !== "undefined" ? __DEV__ : false;
16
17
  */
17
18
  let globalSocketInstance = null;
18
19
  let currentSocketURL = "";
20
+ let currentDeviceId = "";
21
+ let currentPlatform = "";
19
22
 
20
23
  /**
21
24
  * Identifies a single socket INSTANCE. Sent in the handshake query and reused
@@ -46,12 +49,13 @@ export function getInstanceId() {
46
49
  * runtime (see above). Pure, so the shape is unit-tested.
47
50
  */
48
51
  export function buildHandshakeQuery(identity) {
52
+ const withoutAccountKey = values => values && Object.fromEntries(Object.entries(values).filter(([key]) => !["BUOY_LICENSE_KEY", "BUOY_KEY", "EXPO_PUBLIC_BUOY_KEY", "VITE_BUOY_KEY", "REACT_APP_BUOY_KEY", "licenseKey"].includes(key)));
49
53
  return {
50
54
  deviceName: identity.deviceName,
51
55
  deviceId: identity.persistentDeviceId,
52
56
  platform: identity.platform,
53
- extraDeviceInfo: JSON.stringify(identity.extraDeviceInfo),
54
- envVariables: JSON.stringify(identity.envVariables),
57
+ extraDeviceInfo: JSON.stringify(withoutAccountKey(identity.extraDeviceInfo)),
58
+ envVariables: JSON.stringify(withoutAccountKey(identity.envVariables)),
55
59
  connectionNonce: makeConnectionNonce(),
56
60
  instanceId: getInstanceId()
57
61
  };
@@ -77,7 +81,6 @@ export function useExternalSyncSocket({
77
81
  const socketRef = useRef(null);
78
82
  const [socket, setSocket] = useState(null);
79
83
  const [isConnected, setIsConnected] = useState(false);
80
- const initialized = useRef(false);
81
84
  // Derived once per render; stable string, so effect deps stay quiet.
82
85
  const resolvedSocketURL = socketURL ?? getDefaultSocketURL();
83
86
  // Tracks whether we've already logged a connection error for the current
@@ -120,13 +123,11 @@ export function useExternalSyncSocket({
120
123
  log(`${logPrefix} Socket connection timeout`, enableLogs, "error");
121
124
  };
122
125
 
123
- // Main socket initialization - runs only once
126
+ // Reuse the transport, but reattach listeners after every effect setup
127
+ // (including React Strict Mode replay and Fast Refresh).
124
128
  useEffect(() => {
125
129
  if (!persistentDeviceId) return;
126
- if (initialized.current) return;
127
- initialized.current = true;
128
130
  const platformUrl = getPlatformSpecificURL(resolvedSocketURL, platform);
129
- currentSocketURL = platformUrl;
130
131
  setSyncStatus({
131
132
  targetURL: platformUrl,
132
133
  state: "connecting",
@@ -134,6 +135,10 @@ export function useExternalSyncSocket({
134
135
  });
135
136
  log(`${logPrefix} Platform: ${platform}, using URL: ${platformUrl}`, enableLogs);
136
137
  try {
138
+ if (globalSocketInstance && (currentSocketURL !== platformUrl || currentDeviceId !== persistentDeviceId || currentPlatform !== platform)) {
139
+ globalSocketInstance.disconnect();
140
+ globalSocketInstance = null;
141
+ }
137
142
  if (!globalSocketInstance) {
138
143
  log(`${logPrefix} Creating new socket instance to ${platformUrl}`, enableLogs);
139
144
  globalSocketInstance = socketIO(platformUrl, {
@@ -151,32 +156,34 @@ export function useExternalSyncSocket({
151
156
  reconnectionDelayMax: 5000,
152
157
  transports: ["websocket"]
153
158
  });
159
+ currentSocketURL = platformUrl;
160
+ currentDeviceId = persistentDeviceId;
161
+ currentPlatform = platform;
154
162
  } else {
155
163
  log(`${logPrefix} Reusing existing socket instance to ${platformUrl}`, enableLogs);
156
164
  }
157
165
  socketRef.current = globalSocketInstance;
158
- setSocket(socketRef.current);
159
- socketRef.current.on("connect_error", onConnectError);
160
- socketRef.current.on("connect_timeout", onConnectTimeout);
161
- if (socketRef.current.connected) {
162
- setIsConnected(true);
166
+ const activeSocket = socketRef.current;
167
+ setSocket(activeSocket);
168
+ activeSocket.on("connect_error", onConnectError);
169
+ activeSocket.on("connect_timeout", onConnectTimeout);
170
+ setIsConnected(activeSocket.connected);
171
+ if (activeSocket.connected) {
163
172
  log(`${logPrefix} Socket already connected on init`, enableLogs);
164
173
  }
165
- socketRef.current.on("connect", onConnect);
166
- socketRef.current.on("disconnect", onDisconnect);
174
+ activeSocket.on("connect", onConnect);
175
+ activeSocket.on("disconnect", onDisconnect);
167
176
  return () => {
168
- if (socketRef.current) {
169
- log(`${logPrefix} Cleaning up socket event listeners`, enableLogs);
170
- socketRef.current.off("connect", onConnect);
171
- socketRef.current.off("disconnect", onDisconnect);
172
- socketRef.current.off("connect_error", onConnectError);
173
- socketRef.current.off("connect_timeout", onConnectTimeout);
174
- }
177
+ log(`${logPrefix} Cleaning up socket event listeners`, enableLogs);
178
+ activeSocket.off("connect", onConnect);
179
+ activeSocket.off("disconnect", onDisconnect);
180
+ activeSocket.off("connect_error", onConnectError);
181
+ activeSocket.off("connect_timeout", onConnectTimeout);
175
182
  };
176
183
  } catch (error) {
177
184
  log(`${logPrefix} Failed to initialize socket: ${error}`, enableLogs, "error");
178
185
  }
179
- }, [persistentDeviceId]);
186
+ }, [persistentDeviceId, resolvedSocketURL, platform]);
180
187
 
181
188
  // Update socket query parameters when deviceName changes
182
189
  useEffect(() => {
@@ -190,43 +197,6 @@ export function useExternalSyncSocket({
190
197
  };
191
198
  }
192
199
  }, [deviceName, logPrefix, persistentDeviceId, platform, enableLogs]);
193
-
194
- // Update socket URL when socketURL changes
195
- useEffect(() => {
196
- const platformUrl = getPlatformSpecificURL(resolvedSocketURL, platform);
197
- if (socketRef.current && currentSocketURL !== platformUrl && persistentDeviceId) {
198
- log(`${logPrefix} Socket URL changed from ${currentSocketURL} to ${platformUrl}`, enableLogs);
199
- try {
200
- socketRef.current.disconnect();
201
- currentSocketURL = platformUrl;
202
- setSyncStatus({
203
- targetURL: platformUrl,
204
- state: "connecting",
205
- lastError: null
206
- });
207
- log(`${logPrefix} Creating new socket connection to ${platformUrl}`, enableLogs);
208
- globalSocketInstance = socketIO(platformUrl, {
209
- autoConnect: true,
210
- query: buildHandshakeQuery({
211
- deviceName,
212
- persistentDeviceId,
213
- platform,
214
- extraDeviceInfo,
215
- envVariables
216
- }),
217
- reconnection: true,
218
- reconnectionAttempts: Infinity,
219
- reconnectionDelay: 1000,
220
- reconnectionDelayMax: 5000,
221
- transports: ["websocket"]
222
- });
223
- socketRef.current = globalSocketInstance;
224
- setSocket(socketRef.current);
225
- } catch (error) {
226
- log(`${logPrefix} Failed to update socket connection: ${error}`, enableLogs, "error");
227
- }
228
- }
229
- }, [resolvedSocketURL, deviceName, logPrefix, persistentDeviceId, platform, enableLogs]);
230
200
  function connect() {
231
201
  if (socketRef.current && !socketRef.current.connected) {
232
202
  log(`${logPrefix} Manually connecting to socket server`, enableLogs);
@@ -239,10 +209,27 @@ export function useExternalSyncSocket({
239
209
  socketRef.current.disconnect();
240
210
  }
241
211
  }
212
+
213
+ // A connected transport is not permission to sync. Legacy desktop brokers
214
+ // never send this announcement, so updated SDKs withhold tools from them.
215
+ const [admittedSocket, setAdmittedSocket] = useState(null);
216
+ useEffect(() => {
217
+ if (!socket) return;
218
+ return watchBrokerAccess(socket, ready => {
219
+ setAdmittedSocket(ready ? socket : null);
220
+ setSyncStatus(ready ? {
221
+ state: "connected",
222
+ lastError: null
223
+ } : {
224
+ state: "retrying",
225
+ lastError: "Update Buoy Desktop and sign in to enable desktop sync."
226
+ });
227
+ });
228
+ }, [socket]);
242
229
  return {
243
- socket,
230
+ socket: admittedSocket === socket ? socket : null,
244
231
  connect,
245
232
  disconnect,
246
- isConnected
233
+ isConnected: isConnected && admittedSocket === socket && socket !== null
247
234
  };
248
235
  }
@@ -1 +1 @@
1
- {"version":3,"file":"attachDeviceSync.d.ts","sourceRoot":"","sources":["../../src/attachDeviceSync.ts"],"names":[],"mappings":"AAOA,OAAO,EAQL,KAAK,eAAe,EACrB,MAAM,SAAS,CAAC;AAIjB,2DAA2D;AAC3D,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;IACrD,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,KAAK,OAAO,CAAC;IACnE,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,KAAK,OAAO,CAAC;CACrE,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE7C,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAChD;;;;OAIG;IACH,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,UAAgB,EAChB,UAAkB,GACnB,EAAE,uBAAuB,GAAG,MAAM,IAAI,CAqPtC"}
1
+ {"version":3,"file":"attachDeviceSync.d.ts","sourceRoot":"","sources":["../../src/attachDeviceSync.ts"],"names":[],"mappings":"AAQA,OAAO,EAQL,KAAK,eAAe,EACrB,MAAM,SAAS,CAAC;AAIjB,2DAA2D;AAC3D,MAAM,MAAM,UAAU,GAAG;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;IACrD,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,KAAK,OAAO,CAAC;IACnE,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,KAAK,OAAO,CAAC;CACrE,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE7C,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,UAAU,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAChD;;;;OAIG;IACH,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,UAAgB,EAChB,UAAkB,GACnB,EAAE,uBAAuB,GAAG,MAAM,IAAI,CAsStC"}
@@ -0,0 +1,9 @@
1
+ /** Wait for an account-aware broker before publishing any tool capabilities. */
2
+ export interface AccessSocket {
3
+ connected: boolean;
4
+ on(event: string, callback: (...args: any[]) => void): unknown;
5
+ off(event: string, callback: (...args: any[]) => void): unknown;
6
+ emit(event: string): unknown;
7
+ }
8
+ export declare function watchBrokerAccess(socket: AccessSocket, update: (ready: boolean) => void): () => void;
9
+ //# sourceMappingURL=brokerAccess.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brokerAccess.d.ts","sourceRoot":"","sources":["../../src/brokerAccess.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC;IAC/D,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC;IAChE,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAC9B;AACD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GAC/B,MAAM,IAAI,CAoBZ"}
@@ -1 +1 @@
1
- {"version":3,"file":"useExternalSync.d.ts","sourceRoot":"","sources":["../../src/useExternalSync.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,MAAM,EACN,QAAQ,EACR,OAAc,EACd,UAAgB,EAChB,UAAkB,GACnB,EAAE,oBAAoB,QA0BtB"}
1
+ {"version":3,"file":"useExternalSync.d.ts","sourceRoot":"","sources":["../../src/useExternalSync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAEpD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,MAAM,EACN,QAAQ,EACR,OAAc,EACd,UAAgB,EAChB,UAAkB,GACnB,EAAE,oBAAoB,QA4BtB"}
@@ -31,7 +31,7 @@ export declare function buildHandshakeQuery(identity: HandshakeIdentity): Record
31
31
  * - Connection state tracking
32
32
  */
33
33
  export declare function useExternalSyncSocket({ deviceName, socketURL, persistentDeviceId, extraDeviceInfo, envVariables, platform, enableLogs, }: UseExternalSyncSocketProps): {
34
- socket: Socket<import("@socket.io/component-emitter").DefaultEventsMap, import("@socket.io/component-emitter").DefaultEventsMap> | null;
34
+ socket: Socket | null;
35
35
  connect: () => void;
36
36
  disconnect: () => void;
37
37
  isConnected: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"useExternalSyncSocket.d.ts","sourceRoot":"","sources":["../../src/useExternalSyncSocket.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAM1D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAC;AA0B1D;;;;;;;GAOG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAItC;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUvF;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,UAAU,EACV,SAAS,EACT,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,QAAQ,EACR,UAAkB,GACnB,EAAE,0BAA0B;;;;;EA0O5B"}
1
+ {"version":3,"file":"useExternalSyncSocket.d.ts","sourceRoot":"","sources":["../../src/useExternalSyncSocket.ts"],"names":[],"mappings":"AAEA,OAAO,EAAkB,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAM1D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,SAAS,CAAC;AA4B1D;;;;;;;GAOG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAItC;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAavF;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,UAAU,EACV,SAAS,EACT,kBAAkB,EAClB,eAAe,EACf,YAAY,EACZ,QAAQ,EACR,UAAkB,GACnB,EAAE,0BAA0B,GAAG;IAC9B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;CACtB,CA4LA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buoy-gg/external-sync",
3
- "version": "7.0.36",
3
+ "version": "7.0.38",
4
4
  "description": "Generic device-to-desktop sync for Buoy dev tools",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -26,7 +26,8 @@
26
26
  ],
27
27
  "sideEffects": false,
28
28
  "dependencies": {
29
- "socket.io-client": "^4.8.1"
29
+ "socket.io-client": "^4.8.1",
30
+ "@buoy-gg/license": "^7.0.38"
30
31
  },
31
32
  "peerDependencies": {
32
33
  "react": "*",