@buoy-gg/external-sync 7.0.40 → 7.0.42

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.
Files changed (48) hide show
  1. package/lib/commonjs/attachDeviceSync.js +1 -294
  2. package/lib/commonjs/brokerAccess.js +1 -29
  3. package/lib/commonjs/crashFlush.js +1 -73
  4. package/lib/commonjs/emitBudget.js +1 -57
  5. package/lib/commonjs/index.js +1 -83
  6. package/lib/commonjs/realNow.js +1 -0
  7. package/lib/commonjs/syncStatusStore.js +1 -40
  8. package/lib/commonjs/types.js +1 -97
  9. package/lib/commonjs/useExternalSync.js +1 -73
  10. package/lib/commonjs/useExternalSyncSocket.js +1 -241
  11. package/lib/commonjs/utils/getDefaultSocketURL.js +1 -80
  12. package/lib/commonjs/utils/getDefaultSocketURL.web.js +1 -0
  13. package/lib/commonjs/utils/logger.js +1 -24
  14. package/lib/commonjs/utils/platformUtils.js +1 -50
  15. package/lib/commonjs/utils/platformUtils.web.js +1 -0
  16. package/lib/module/attachDeviceSync.js +1 -290
  17. package/lib/module/brokerAccess.js +1 -25
  18. package/lib/module/crashFlush.js +1 -67
  19. package/lib/module/emitBudget.js +1 -52
  20. package/lib/module/index.js +1 -29
  21. package/lib/module/realNow.js +1 -0
  22. package/lib/module/syncStatusStore.js +1 -34
  23. package/lib/module/types.js +1 -93
  24. package/lib/module/useExternalSync.js +1 -69
  25. package/lib/module/useExternalSyncSocket.js +1 -235
  26. package/lib/module/utils/getDefaultSocketURL.js +1 -72
  27. package/lib/module/utils/getDefaultSocketURL.web.js +1 -0
  28. package/lib/module/utils/logger.js +1 -20
  29. package/lib/module/utils/platformUtils.js +1 -45
  30. package/lib/module/utils/platformUtils.web.js +1 -0
  31. package/lib/typescript/realNow.d.ts +27 -0
  32. package/lib/typescript/syncStatusStore.d.ts +0 -7
  33. package/lib/typescript/utils/getDefaultSocketURL.web.d.ts +6 -0
  34. package/lib/typescript/utils/platformUtils.web.d.ts +3 -0
  35. package/lib/web/index.mjs +1 -0
  36. package/package.json +32 -10
  37. package/lib/typescript/attachDeviceSync.d.ts.map +0 -1
  38. package/lib/typescript/brokerAccess.d.ts.map +0 -1
  39. package/lib/typescript/crashFlush.d.ts.map +0 -1
  40. package/lib/typescript/emitBudget.d.ts.map +0 -1
  41. package/lib/typescript/index.d.ts.map +0 -1
  42. package/lib/typescript/syncStatusStore.d.ts.map +0 -1
  43. package/lib/typescript/types.d.ts.map +0 -1
  44. package/lib/typescript/useExternalSync.d.ts.map +0 -1
  45. package/lib/typescript/useExternalSyncSocket.d.ts.map +0 -1
  46. package/lib/typescript/utils/getDefaultSocketURL.d.ts.map +0 -1
  47. package/lib/typescript/utils/logger.d.ts.map +0 -1
  48. package/lib/typescript/utils/platformUtils.d.ts.map +0 -1
@@ -1,97 +1 @@
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
- /**
65
- * Server-consolidated signal that a tool gained/lost its audience.
66
- *
67
- * `watching` arms CAPTURE: the device subscribes to the tool, which is what
68
- * makes its store start recording. `stream` decides whether the device also
69
- * PUSHES a snapshot on every change (throttled), which is the expensive half —
70
- * it serializes the whole payload on the JS thread ~5x/sec.
71
- *
72
- * Those are separate because their audiences are: a dashboard renders every
73
- * snapshot, so it wants both. A request/response client (MCP) reads on demand
74
- * via `request-devtool-state` and would throw the stream away — but it still
75
- * needs the device to be RECORDING between its reads, or it only ever sees an
76
- * empty list and concludes the app made no requests.
77
- */
78
-
79
- /** Explicit request for a tool's current state (works even while unwatched). */
80
-
81
- /** Invokes a registered action on the device. */
82
-
83
- // ============================================================
84
- // HOOK PROPS
85
- // ============================================================
86
-
87
- // ============================================================
88
- // PLATFORM
89
- // ============================================================
90
-
91
- /**
92
- * Valid platform operating systems that can be used with React Native
93
- */
94
-
95
- // ============================================================
96
- // SOCKET HOOK PROPS
97
- // ============================================================
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.SYNC_PROTOCOL_VERSION=void 0;const SYNC_PROTOCOL_VERSION=exports.SYNC_PROTOCOL_VERSION=1;
@@ -1,73 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.useExternalSync = useExternalSync;
7
- var _license = require("@buoy-gg/license");
8
- var _react = require("react");
9
- var _attachDeviceSync = require("./attachDeviceSync");
10
- /**
11
- * Device-side sync bridge: syncs any number of dev tools to the desktop
12
- * dashboard and executes actions invoked remotely.
13
- *
14
- * Backpressure: a tool's adapter is only subscribed while a dashboard is
15
- * watching it (`devtool-watch`), so unwatched tools cost nothing. The server
16
- * re-sends current watch state whenever it receives `devtool-capabilities`,
17
- * which the device announces on every (re)connect.
18
- *
19
- * Usage:
20
- * ```ts
21
- * useExternalSync({
22
- * tools: {
23
- * storage: {
24
- * version: 1,
25
- * getSnapshot: () => storageEventStore.getEvents(),
26
- * subscribe: (cb) => storageEventStore.subscribeToEvents(cb),
27
- * actions: {
28
- * clearEvents: () => storageEventStore.clearEvents(),
29
- * },
30
- * },
31
- * },
32
- * socket,
33
- * deviceId: 'my-device-id',
34
- * });
35
- * ```
36
- */
37
- function useExternalSync({
38
- tools,
39
- socket,
40
- deviceId,
41
- enabled = true,
42
- throttleMs = 200,
43
- enableLogs = false
44
- }) {
45
- const account = (0, _license.useAccountAccess)();
46
- const admitted = account.status === "admitted";
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 Map());
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 || !admitted) {
59
- watchedRef.current.clear();
60
- return;
61
- }
62
- return (0, _attachDeviceSync.attachDeviceSync)({
63
- socket,
64
- deviceId,
65
- getTools: () => toolsRef.current,
66
- watched: watchedRef.current,
67
- throttleMs,
68
- enableLogs
69
- });
70
- // toolIdsKey stands in for `tools` (see comment above its definition).
71
- // eslint-disable-next-line react-hooks/exhaustive-deps
72
- }, [socket, deviceId, enabled, admitted, throttleMs, enableLogs, toolIdsKey]);
73
- }
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.useExternalSync=useExternalSync;var _license=require("@buoy-gg/license");var _react=require("react");var _attachDeviceSync=require("./attachDeviceSync");function useExternalSync({tools:e,socket:t,deviceId:c,enabled:r=true,throttleMs:n=200,enableLogs:u=false}){const o=(0,_license.useAccountAccess)();const s=o.status==="admitted";const a=(0,_react.useRef)(e);a.current=e;const i=(0,_react.useRef)(new Map);const f=Object.keys(e).sort().join(",");(0,_react.useEffect)(()=>{if(!t||!c||!r||!s){i.current.clear();return}return(0,_attachDeviceSync.attachDeviceSync)({socket:t,deviceId:c,getTools:()=>a.current,watched:i.current,throttleMs:n,enableLogs:u})},[t,c,r,s,n,u,f])}
@@ -1,241 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.buildHandshakeQuery = buildHandshakeQuery;
7
- exports.getInstanceId = getInstanceId;
8
- exports.useExternalSyncSocket = useExternalSyncSocket;
9
- var _brokerAccess = require("./brokerAccess");
10
- var _react = require("react");
11
- var _socket = require("socket.io-client");
12
- var _platformUtils = require("./utils/platformUtils");
13
- var _getDefaultSocketURL = require("./utils/getDefaultSocketURL");
14
- var _logger = require("./utils/logger");
15
- var _syncStatusStore = require("./syncStatusStore");
16
- // True only in development. Guarded so it doesn't throw in environments
17
- // (web/electron bundles) where __DEV__ may be undefined.
18
- const isDev = typeof __DEV__ !== "undefined" ? __DEV__ : false;
19
-
20
- /**
21
- * Singleton socket instance that persists across component renders.
22
- * Multiple components share the same socket connection.
23
- */
24
- let globalSocketInstance = null;
25
- let currentSocketURL = "";
26
- let currentDeviceId = "";
27
- let currentPlatform = "";
28
-
29
- /**
30
- * Identifies a single socket INSTANCE. Sent in the handshake query and reused
31
- * for the life of the instance, so it survives socket.io reconnects but
32
- * changes whenever we create a brand-new socket (remount / Fast Refresh /
33
- * URL change). The dashboard uses this to tell a flapping connection that's
34
- * merely reconnecting (stable nonce) from an app that keeps recreating its
35
- * socket (changing nonce).
36
- */
37
- const makeConnectionNonce = () => `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
38
-
39
- /**
40
- * Identifies this JS RUNTIME, not this socket. Held on globalThis so every
41
- * socket the runtime opens (Fast Refresh, remounts, a double-mounted dev
42
- * tools layer) carries the same value, while a second app that pinned the
43
- * same deviceId carries a different one. That difference is the only way
44
- * the broker can tell "one device, many sockets" from "many devices, one
45
- * id" — it flags the latter as a collision in the device list.
46
- */
47
- function getInstanceId() {
48
- const g = globalThis;
49
- if (!g.__buoyInstanceId) g.__buoyInstanceId = makeConnectionNonce();
50
- return g.__buoyInstanceId;
51
- }
52
- /**
53
- * The socket.io handshake query — the ONE place the device says who it is.
54
- * `connectionNonce` is fresh per socket instance; `instanceId` is fixed per
55
- * runtime (see above). Pure, so the shape is unit-tested.
56
- */
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)));
59
- return {
60
- deviceName: identity.deviceName,
61
- deviceId: identity.persistentDeviceId,
62
- platform: identity.platform,
63
- extraDeviceInfo: JSON.stringify(withoutAccountKey(identity.extraDeviceInfo)),
64
- envVariables: JSON.stringify(withoutAccountKey(identity.envVariables)),
65
- connectionNonce: makeConnectionNonce(),
66
- instanceId: getInstanceId()
67
- };
68
- }
69
-
70
- /**
71
- * Hook that manages a Socket.IO connection for device-to-dashboard communication.
72
- *
73
- * Features:
74
- * - Singleton pattern for socket connection
75
- * - Platform-specific URL handling (Android emulator -> 10.0.2.2)
76
- * - Connection state tracking
77
- */
78
- function useExternalSyncSocket({
79
- deviceName,
80
- socketURL,
81
- persistentDeviceId,
82
- extraDeviceInfo,
83
- envVariables,
84
- platform,
85
- enableLogs = false
86
- }) {
87
- const socketRef = (0, _react.useRef)(null);
88
- const [socket, setSocket] = (0, _react.useState)(null);
89
- const [isConnected, setIsConnected] = (0, _react.useState)(false);
90
- // Derived once per render; stable string, so effect deps stay quiet.
91
- const resolvedSocketURL = socketURL ?? (0, _getDefaultSocketURL.getDefaultSocketURL)();
92
- // Tracks whether we've already logged a connection error for the current
93
- // outage, so the reconnection backoff doesn't spam an error on every retry.
94
- const hasLoggedConnectError = (0, _react.useRef)(false);
95
- const logPrefix = `[${deviceName}]`;
96
- const onConnect = () => {
97
- (0, _logger.log)(`${logPrefix} Socket connected successfully`, enableLogs);
98
- hasLoggedConnectError.current = false;
99
- setIsConnected(true);
100
- (0, _syncStatusStore.setSyncStatus)({
101
- state: "connected",
102
- lastError: null
103
- });
104
- };
105
- const onDisconnect = reason => {
106
- (0, _logger.log)(`${logPrefix} Socket disconnected. Reason: ${reason}`, enableLogs);
107
- setIsConnected(false);
108
- (0, _syncStatusStore.setSyncStatus)({
109
- state: "retrying"
110
- });
111
- };
112
- const onConnectError = error => {
113
- (0, _syncStatusStore.setSyncStatus)({
114
- state: "retrying",
115
- lastError: error.message
116
- });
117
- // Never surface this in production builds.
118
- if (!isDev) return;
119
- // Reconnection retries indefinitely; only log the first failure of an
120
- // outage to avoid flooding the console until the server comes back up.
121
- if (hasLoggedConnectError.current) return;
122
- hasLoggedConnectError.current = true;
123
- // A localhost target on an Android device means a USB (adb reverse)
124
- // setup — name the exact command instead of the generic hint.
125
- const hint = platform === "android" && /localhost|127\.0\.0\.1/.test(currentSocketURL) ? `If this device is connected over USB, run: adb reverse tcp:42831 tcp:42831. ` : `If it's running on another machine or port, pass socketURL via the externalSync prop. `;
126
- (0, _logger.log)(`[buoy] desktop sync: can't reach ${currentSocketURL} (is Buoy Desktop running?). ` + `Retrying in the background. ${hint}(${error.message})`, enableLogs, "warn");
127
- };
128
- const onConnectTimeout = () => {
129
- (0, _logger.log)(`${logPrefix} Socket connection timeout`, enableLogs, "error");
130
- };
131
-
132
- // Reuse the transport, but reattach listeners after every effect setup
133
- // (including React Strict Mode replay and Fast Refresh).
134
- (0, _react.useEffect)(() => {
135
- if (!persistentDeviceId) return;
136
- const platformUrl = (0, _platformUtils.getPlatformSpecificURL)(resolvedSocketURL, platform);
137
- (0, _syncStatusStore.setSyncStatus)({
138
- targetURL: platformUrl,
139
- state: "connecting",
140
- lastError: null
141
- });
142
- (0, _logger.log)(`${logPrefix} Platform: ${platform}, using URL: ${platformUrl}`, enableLogs);
143
- try {
144
- if (globalSocketInstance && (currentSocketURL !== platformUrl || currentDeviceId !== persistentDeviceId || currentPlatform !== platform)) {
145
- globalSocketInstance.disconnect();
146
- globalSocketInstance = null;
147
- }
148
- if (!globalSocketInstance) {
149
- (0, _logger.log)(`${logPrefix} Creating new socket instance to ${platformUrl}`, enableLogs);
150
- globalSocketInstance = (0, _socket.io)(platformUrl, {
151
- autoConnect: true,
152
- query: buildHandshakeQuery({
153
- deviceName,
154
- persistentDeviceId,
155
- platform,
156
- extraDeviceInfo,
157
- envVariables
158
- }),
159
- reconnection: true,
160
- reconnectionAttempts: Infinity,
161
- reconnectionDelay: 1000,
162
- reconnectionDelayMax: 5000,
163
- transports: ["websocket"]
164
- });
165
- currentSocketURL = platformUrl;
166
- currentDeviceId = persistentDeviceId;
167
- currentPlatform = platform;
168
- } else {
169
- (0, _logger.log)(`${logPrefix} Reusing existing socket instance to ${platformUrl}`, enableLogs);
170
- }
171
- socketRef.current = globalSocketInstance;
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) {
178
- (0, _logger.log)(`${logPrefix} Socket already connected on init`, enableLogs);
179
- }
180
- activeSocket.on("connect", onConnect);
181
- activeSocket.on("disconnect", onDisconnect);
182
- return () => {
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);
188
- };
189
- } catch (error) {
190
- (0, _logger.log)(`${logPrefix} Failed to initialize socket: ${error}`, enableLogs, "error");
191
- }
192
- }, [persistentDeviceId, resolvedSocketURL, platform]);
193
-
194
- // Update socket query parameters when deviceName changes
195
- (0, _react.useEffect)(() => {
196
- if (socketRef.current && socketRef.current.io.opts.query && persistentDeviceId) {
197
- (0, _logger.log)(`${logPrefix} Updating device name in socket connection`, enableLogs);
198
- socketRef.current.io.opts.query = {
199
- ...socketRef.current.io.opts.query,
200
- deviceName,
201
- deviceId: persistentDeviceId,
202
- platform
203
- };
204
- }
205
- }, [deviceName, logPrefix, persistentDeviceId, platform, enableLogs]);
206
- function connect() {
207
- if (socketRef.current && !socketRef.current.connected) {
208
- (0, _logger.log)(`${logPrefix} Manually connecting to socket server`, enableLogs);
209
- socketRef.current.connect();
210
- }
211
- }
212
- function disconnect() {
213
- if (socketRef.current && socketRef.current.connected) {
214
- (0, _logger.log)(`${logPrefix} Manually disconnecting from socket server`, enableLogs);
215
- socketRef.current.disconnect();
216
- }
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]);
235
- return {
236
- socket: admittedSocket === socket ? socket : null,
237
- connect,
238
- disconnect,
239
- isConnected: isConnected && admittedSocket === socket && socket !== null
240
- };
241
- }
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.buildHandshakeQuery=buildHandshakeQuery;exports.getInstanceId=getInstanceId;exports.useExternalSyncSocket=useExternalSyncSocket;var _brokerAccess=require("./brokerAccess");var _react=require("react");var _socket=require("socket.io-client");var _platformUtils=require("./utils/platformUtils");var _getDefaultSocketURL=require("./utils/getDefaultSocketURL");var _logger=require("./utils/logger");var _syncStatusStore=require("./syncStatusStore");var _realNow=require("./realNow");const isDev=typeof __DEV__!=="undefined"?__DEV__:false;let globalSocketInstance=null;let currentSocketURL="";let currentDeviceId="";let currentPlatform="";let socketUsers=0;let releaseTimer=null;const RELEASE_GRACE_MS=2e3;const makeConnectionNonce=()=>`${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`;function getInstanceId(){const c=globalThis;if(!c.__buoyInstanceId)c.__buoyInstanceId=makeConnectionNonce();return c.__buoyInstanceId}function buildHandshakeQuery(c){const l=s=>s&&Object.fromEntries(Object.entries(s).filter(([a])=>!["BUOY_LICENSE_KEY","BUOY_KEY","EXPO_PUBLIC_BUOY_KEY","VITE_BUOY_KEY","REACT_APP_BUOY_KEY","licenseKey"].includes(a)));return{deviceName:c.deviceName,deviceId:c.persistentDeviceId,platform:c.platform,extraDeviceInfo:JSON.stringify(l(c.extraDeviceInfo)),envVariables:JSON.stringify(l(c.envVariables)),connectionNonce:makeConnectionNonce(),instanceId:getInstanceId()}}function useExternalSyncSocket({deviceName:c,socketURL:l,persistentDeviceId:s,extraDeviceInfo:a,envVariables:$,platform:u,enableLogs:t=false}){const r=(0,_react.useRef)(null);const[i,U]=(0,_react.useState)(null);const[v,f]=(0,_react.useState)(false);const S=l??(0,_getDefaultSocketURL.getDefaultSocketURL)();const d=(0,_react.useRef)(false);const o=`[${c}]`;const k=()=>{(0,_logger.log)(`${o} Socket connected successfully`,t);d.current=false;f(true);(0,_syncStatusStore.setSyncStatus)({state:"connected",lastError:null})};const g=n=>{(0,_logger.log)(`${o} Socket disconnected. Reason: ${n}`,t);f(false);(0,_syncStatusStore.setSyncStatus)({state:"retrying"})};const _=n=>{(0,_syncStatusStore.setSyncStatus)({state:"retrying",lastError:n.message});if(!isDev)return;if(d.current)return;d.current=true;const e=u==="android"&&/localhost|127\.0\.0\.1/.test(currentSocketURL)?`If this device is connected over USB, run: adb reverse tcp:42831 tcp:42831. `:`If it's running on another machine or port, pass socketURL via the externalSync prop. `;(0,_logger.log)(`[buoy] desktop sync: can't reach ${currentSocketURL} (is Buoy Desktop running?). Retrying in the background. ${e}(${n.message})`,t,"warn")};const y=()=>{(0,_logger.log)(`${o} Socket connection timeout`,t,"error")};(0,_react.useEffect)(()=>{if(!s)return;const n=(0,_platformUtils.getPlatformSpecificURL)(S,u);(0,_syncStatusStore.setSyncStatus)({targetURL:n,state:"connecting",lastError:null});(0,_logger.log)(`${o} Platform: ${u}, using URL: ${n}`,t);try{if(globalSocketInstance&&(currentSocketURL!==n||currentDeviceId!==s||currentPlatform!==u)){globalSocketInstance.disconnect();globalSocketInstance=null}if(!globalSocketInstance){(0,_logger.log)(`${o} Creating new socket instance to ${n}`,t);globalSocketInstance=(0,_socket.io)(n,{autoConnect:true,query:buildHandshakeQuery({deviceName:c,persistentDeviceId:s,platform:u,extraDeviceInfo:a,envVariables:$}),reconnection:true,reconnectionAttempts:Infinity,reconnectionDelay:1e3,reconnectionDelayMax:5e3,transports:["websocket"]});(0,_realNow.pinSocketTimers)(globalSocketInstance);currentSocketURL=n;currentDeviceId=s;currentPlatform=u}else{(0,_logger.log)(`${o} Reusing existing socket instance to ${n}`,t)}r.current=globalSocketInstance;const e=r.current;socketUsers++;if(releaseTimer){(0,_realNow.realClearTimeout)(releaseTimer);releaseTimer=null}U(e);e.on("connect_error",_);e.on("connect_timeout",y);f(e.connected);if(e.connected){(0,_logger.log)(`${o} Socket already connected on init`,t)}e.on("connect",k);e.on("disconnect",g);return()=>{(0,_logger.log)(`${o} Cleaning up socket event listeners`,t);e.off("connect",k);e.off("disconnect",g);e.off("connect_error",_);e.off("connect_timeout",y);socketUsers--;if(socketUsers===0){releaseTimer=(0,_realNow.realSetTimeout)(()=>{releaseTimer=null;if(socketUsers>0||globalSocketInstance!==e)return;(0,_logger.log)(`${o} No longer used; disconnecting`,t);e.disconnect();globalSocketInstance=null;(0,_syncStatusStore.setSyncStatus)({state:"idle",lastError:null})},RELEASE_GRACE_MS)}}}catch(e){(0,_logger.log)(`${o} Failed to initialize socket: ${e}`,t,"error")}},[s,S,u]);(0,_react.useEffect)(()=>{if(r.current&&r.current.io.opts.query&&s){(0,_logger.log)(`${o} Updating device name in socket connection`,t);r.current.io.opts.query={...r.current.io.opts.query,deviceName:c,deviceId:s,platform:u}}},[c,o,s,u,t]);function m(){if(r.current&&!r.current.connected){(0,_logger.log)(`${o} Manually connecting to socket server`,t);r.current.connect()}}function I(){if(r.current&&r.current.connected){(0,_logger.log)(`${o} Manually disconnecting from socket server`,t);r.current.disconnect()}}const[E,R]=(0,_react.useState)(null);(0,_react.useEffect)(()=>{if(!i)return;return(0,_brokerAccess.watchBrokerAccess)(i,n=>{R(n?i:null);(0,_syncStatusStore.setSyncStatus)(n?{state:"connected",lastError:null}:{state:"retrying",lastError:"Update Buoy Desktop and sign in to enable desktop sync."})})},[i]);return{socket:E===i?i:null,connect:m,disconnect:I,isConnected:v&&E===i&&i!==null}}
@@ -1,80 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.DEFAULT_BROKER_PORT = void 0;
7
- exports.getDefaultSocketURL = getDefaultSocketURL;
8
- exports.getMetroHost = getMetroHost;
9
- exports.hostFromScriptURL = hostFromScriptURL;
10
- exports.socketURLForHost = socketURLForHost;
11
- /**
12
- * Default broker-URL derivation. Kept in its own module (and never imported
13
- * from types.ts) because the deep react-native require below must not reach
14
- * non-Metro bundles — the desktop app imports this package's types only.
15
- */
16
-
17
- /** Port Buoy Desktop's broker listens on. */
18
- const DEFAULT_BROKER_PORT = exports.DEFAULT_BROKER_PORT = 42831;
19
- const HOST_RE = /^https?:\/\/([^:/]+)/;
20
-
21
- /**
22
- * Host out of a bundle's scriptURL, or null when there isn't one.
23
- *
24
- * Release bundles are loaded from disk (`file:///.../main.jsbundle`), so this
25
- * returns null and callers fall back to localhost — correct for simulators,
26
- * emulators, and Android over USB (`adb reverse tcp:42831 tcp:42831`). A
27
- * physical iOS device in a release build has no derivable host at all and
28
- * must be given an explicit socketURL.
29
- */
30
- function hostFromScriptURL(scriptURL) {
31
- return scriptURL?.match(HOST_RE)?.[1] ?? null;
32
- }
33
-
34
- /** Broker URL for a host, falling back to localhost when none is known. */
35
- function socketURLForHost(host) {
36
- return `http://${host ?? "localhost"}:${DEFAULT_BROKER_PORT}`;
37
- }
38
-
39
- /**
40
- * Host of the Metro dev server that served this bundle — the machine most
41
- * likely running Buoy Desktop. On a physical device this is the computer's
42
- * LAN IP, where "localhost" would be the phone itself.
43
- *
44
- * scriptURL is the only source that works everywhere we've tested:
45
- * NativeModules.SourceCode.scriptURL is undefined on bridgeless, and
46
- * expo-constants hostUri/experienceUrl/linkingUri are all undefined in
47
- * Expo dev-client builds. Returns null on web or if RN internals move.
48
- */
49
- function getMetroHost() {
50
- try {
51
- // Requires stay lexically inside try{} so Metro treats them as optional;
52
- // react-native-web has neither path and falls through to null.
53
- const NativeSourceCode =
54
- // eslint-disable-next-line @typescript-eslint/no-var-requires
55
- require("react-native/Libraries/NativeModules/specs/NativeSourceCode").default;
56
- const host = hostFromScriptURL(NativeSourceCode?.getConstants?.().scriptURL);
57
- if (host) return host;
58
- } catch {
59
- // web / RN internals moved — try the old-architecture module
60
- }
61
- try {
62
- const {
63
- NativeModules
64
- } = require("react-native");
65
- const host = hostFromScriptURL(NativeModules?.SourceCode?.getConstants?.().scriptURL ?? NativeModules?.SourceCode?.scriptURL);
66
- if (host) return host;
67
- } catch {
68
- // not React Native
69
- }
70
- return null;
71
- }
72
-
73
- /**
74
- * Default Buoy Desktop broker URL: the Metro host (falling back to
75
- * localhost) on port 42831. Android-emulator 10.0.2.2 rewriting is applied
76
- * later by getPlatformSpecificURL, so localhost still works there.
77
- */
78
- function getDefaultSocketURL() {
79
- return socketURLForHost(getMetroHost());
80
- }
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.DEFAULT_BROKER_PORT=void 0;exports.getDefaultSocketURL=getDefaultSocketURL;exports.getMetroHost=getMetroHost;exports.hostFromScriptURL=hostFromScriptURL;exports.socketURLForHost=socketURLForHost;const DEFAULT_BROKER_PORT=exports.DEFAULT_BROKER_PORT=42831;const HOST_RE=/^https?:\/\/([^:/]+)/;function hostFromScriptURL(t){return t?.match(HOST_RE)?.[1]??null}function socketURLForHost(t){return`http://${t??"localhost"}:${DEFAULT_BROKER_PORT}`}function getMetroHost(){try{const{TurboModuleRegistry:t}=require("react-native");const e=t?.get?.("SourceCode");const o=hostFromScriptURL(e?.getConstants?.().scriptURL);if(o)return o}catch{}try{const{NativeModules:t}=require("react-native");const e=hostFromScriptURL(t?.SourceCode?.getConstants?.().scriptURL??t?.SourceCode?.scriptURL);if(e)return e}catch{}return null}function getDefaultSocketURL(){return socketURLForHost(getMetroHost())}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.DEFAULT_BROKER_PORT=void 0;exports.getDefaultSocketURL=getDefaultSocketURL;exports.getMetroHost=getMetroHost;exports.hostFromScriptURL=hostFromScriptURL;exports.socketURLForHost=socketURLForHost;const DEFAULT_BROKER_PORT=exports.DEFAULT_BROKER_PORT=42831;function hostFromScriptURL(t){try{return t&&/^https?:/.test(t)?new URL(t).hostname:null}catch{return null}}function socketURLForHost(t){return`http://${t??"localhost"}:${DEFAULT_BROKER_PORT}`}function getMetroHost(){return null}function getDefaultSocketURL(){return socketURLForHost(typeof location==="undefined"?null:location.hostname)}
@@ -1,24 +1 @@
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
- }
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.log=log;function log(o,e,r="log"){if(!e&&r==="log")return;switch(r){case"warn":console.warn(o);break;case"error":console.error(o);break;default:console.log(o)}}
@@ -1,50 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.getPlatformSpecificURL = void 0;
7
- /**
8
- * True only on an actual Android EMULATOR, detected via the build fingerprint
9
- * (same heuristic react-native-device-info uses), with no native dependency.
10
- * Physical devices must NOT get the 10.0.2.2 rewrite: over USB (`adb reverse`)
11
- * the Metro-derived host is "localhost", and rewriting it to the
12
- * emulator-only 10.0.2.2 alias breaks the connection — keeping localhost lets
13
- * `adb reverse tcp:42831 tcp:42831` forward it to the dev machine.
14
- *
15
- * The react-native require stays lexically inside try{} (Metro optional-dep
16
- * rule) and this module is never imported from types.ts, so non-Metro
17
- * bundles (the desktop app) are unaffected.
18
- */
19
- const isAndroidEmulator = () => {
20
- try {
21
- const {
22
- Platform
23
- } = require("react-native");
24
- if (Platform.OS !== "android") return false;
25
- const fingerprint = Platform.constants?.Fingerprint ?? "";
26
- return /generic|emulator|goldfish|ranchu|sdk_gphone|vbox/i.test(fingerprint);
27
- } catch {
28
- return false;
29
- }
30
- };
31
-
32
- /**
33
- * Get platform-specific URL for socket connection.
34
- * On the Android emulator (only), replaces localhost with 10.0.2.2 —
35
- * the emulator's alias for the host machine's loopback.
36
- */
37
- const getPlatformSpecificURL = (baseUrl, platform) => {
38
- try {
39
- const url = new URL(baseUrl);
40
- if (platform === "android" && (url.hostname === "localhost" || url.hostname === "127.0.0.1") && isAndroidEmulator()) {
41
- url.hostname = "10.0.2.2";
42
- return url.toString();
43
- }
44
- return baseUrl;
45
- } catch (e) {
46
- console.warn("Error getting platform-specific URL:", e);
47
- return baseUrl;
48
- }
49
- };
50
- exports.getPlatformSpecificURL = getPlatformSpecificURL;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.getPlatformSpecificURL=void 0;const isAndroidEmulator=()=>{try{const{Platform:t}=require("react-native");if(t.OS!=="android")return false;const e=t.constants?.Fingerprint??"";return/generic|emulator|goldfish|ranchu|sdk_gphone|vbox/i.test(e)}catch{return false}};const getPlatformSpecificURL=(t,e)=>{try{const r=new URL(t);if(e==="android"&&(r.hostname==="localhost"||r.hostname==="127.0.0.1")&&isAndroidEmulator()){r.hostname="10.0.2.2";return r.toString()}return t}catch(r){console.warn("Error getting platform-specific URL:",r);return t}};exports.getPlatformSpecificURL=getPlatformSpecificURL;
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.getPlatformSpecificURL=getPlatformSpecificURL;function getPlatformSpecificURL(e,t){return e}