@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.
- package/lib/commonjs/attachDeviceSync.js +1 -294
- package/lib/commonjs/brokerAccess.js +1 -29
- package/lib/commonjs/crashFlush.js +1 -73
- package/lib/commonjs/emitBudget.js +1 -57
- package/lib/commonjs/index.js +1 -83
- package/lib/commonjs/realNow.js +1 -0
- package/lib/commonjs/syncStatusStore.js +1 -40
- package/lib/commonjs/types.js +1 -97
- package/lib/commonjs/useExternalSync.js +1 -73
- package/lib/commonjs/useExternalSyncSocket.js +1 -241
- package/lib/commonjs/utils/getDefaultSocketURL.js +1 -80
- package/lib/commonjs/utils/getDefaultSocketURL.web.js +1 -0
- package/lib/commonjs/utils/logger.js +1 -24
- package/lib/commonjs/utils/platformUtils.js +1 -50
- package/lib/commonjs/utils/platformUtils.web.js +1 -0
- package/lib/module/attachDeviceSync.js +1 -290
- package/lib/module/brokerAccess.js +1 -25
- package/lib/module/crashFlush.js +1 -67
- package/lib/module/emitBudget.js +1 -52
- package/lib/module/index.js +1 -29
- package/lib/module/realNow.js +1 -0
- package/lib/module/syncStatusStore.js +1 -34
- package/lib/module/types.js +1 -93
- package/lib/module/useExternalSync.js +1 -69
- package/lib/module/useExternalSyncSocket.js +1 -235
- package/lib/module/utils/getDefaultSocketURL.js +1 -72
- package/lib/module/utils/getDefaultSocketURL.web.js +1 -0
- package/lib/module/utils/logger.js +1 -20
- package/lib/module/utils/platformUtils.js +1 -45
- package/lib/module/utils/platformUtils.web.js +1 -0
- package/lib/typescript/realNow.d.ts +27 -0
- package/lib/typescript/syncStatusStore.d.ts +0 -7
- package/lib/typescript/utils/getDefaultSocketURL.web.d.ts +6 -0
- package/lib/typescript/utils/platformUtils.web.d.ts +3 -0
- package/lib/web/index.mjs +1 -0
- package/package.json +32 -10
- package/lib/typescript/attachDeviceSync.d.ts.map +0 -1
- package/lib/typescript/brokerAccess.d.ts.map +0 -1
- package/lib/typescript/crashFlush.d.ts.map +0 -1
- package/lib/typescript/emitBudget.d.ts.map +0 -1
- package/lib/typescript/index.d.ts.map +0 -1
- package/lib/typescript/syncStatusStore.d.ts.map +0 -1
- package/lib/typescript/types.d.ts.map +0 -1
- package/lib/typescript/useExternalSync.d.ts.map +0 -1
- package/lib/typescript/useExternalSyncSocket.d.ts.map +0 -1
- package/lib/typescript/utils/getDefaultSocketURL.d.ts.map +0 -1
- package/lib/typescript/utils/logger.d.ts.map +0 -1
- package/lib/typescript/utils/platformUtils.d.ts.map +0 -1
package/lib/commonjs/types.js
CHANGED
|
@@ -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}
|