@buoy-gg/external-sync 7.0.41 → 7.0.43
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 -84
- package/lib/commonjs/utils/getDefaultSocketURL.web.js +1 -27
- package/lib/commonjs/utils/logger.js +1 -24
- package/lib/commonjs/utils/platformUtils.js +1 -50
- package/lib/commonjs/utils/platformUtils.web.js +1 -9
- 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 -76
- package/lib/module/utils/getDefaultSocketURL.web.js +1 -19
- package/lib/module/utils/logger.js +1 -20
- package/lib/module/utils/platformUtils.js +1 -45
- package/lib/module/utils/platformUtils.web.js +1 -5
- package/lib/typescript/realNow.d.ts +27 -0
- package/lib/typescript/syncStatusStore.d.ts +0 -7
- package/lib/web/index.mjs +1 -2
- package/package.json +3 -6
- 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/getDefaultSocketURL.web.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/typescript/utils/platformUtils.web.d.ts.map +0 -1
|
@@ -1,294 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.attachDeviceSync = attachDeviceSync;
|
|
7
|
-
var _license = require("@buoy-gg/license");
|
|
8
|
-
var _logger = require("./utils/logger");
|
|
9
|
-
var _crashFlush = require("./crashFlush");
|
|
10
|
-
var _emitBudget = require("./emitBudget");
|
|
11
|
-
var _types = require("./types");
|
|
12
|
-
const LOG = "[ExternalSync]";
|
|
13
|
-
|
|
14
|
-
/** Minimal socket surface the device-side bridge needs. */
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* "capture" records into the tool's store without pushing snapshots; "stream"
|
|
18
|
-
* also emits on every change. See `DevToolWatchMessage.stream`.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Device-side sync bridge (no React). A tool's adapter is only subscribed
|
|
23
|
-
* while a dashboard is watching it (`devtool-watch`).
|
|
24
|
-
*/
|
|
25
|
-
function attachDeviceSync({
|
|
26
|
-
socket,
|
|
27
|
-
deviceId,
|
|
28
|
-
getTools,
|
|
29
|
-
watched,
|
|
30
|
-
throttleMs = 200,
|
|
31
|
-
enableLogs = false
|
|
32
|
-
}) {
|
|
33
|
-
let disposed = false;
|
|
34
|
-
let brokerReady = false;
|
|
35
|
-
let accessGeneration = 0;
|
|
36
|
-
const allowed = () => !disposed && brokerReady && socket.connected && _license.LicenseManager.hasAccountAccess();
|
|
37
|
-
const unsubs = new Map();
|
|
38
|
-
const lastEmit = {};
|
|
39
|
-
const timers = {};
|
|
40
|
-
const isForThisDevice = targetDeviceId => targetDeviceId === "All" || targetDeviceId === deviceId;
|
|
41
|
-
const safeEmit = (event, message) => {
|
|
42
|
-
if (!allowed()) return;
|
|
43
|
-
try {
|
|
44
|
-
socket.emit(event, message);
|
|
45
|
-
} catch (error) {
|
|
46
|
-
(0, _logger.log)(`${LOG} Failed to emit "${event}": ${error}. Payload must be JSON-serializable.`, enableLogs, "error");
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
const warnedOversized = new Set();
|
|
50
|
-
const sendSnapshot = toolId => {
|
|
51
|
-
if (!allowed()) return;
|
|
52
|
-
const tool = getTools()[toolId];
|
|
53
|
-
if (!tool) return;
|
|
54
|
-
lastEmit[toolId] = Date.now();
|
|
55
|
-
let payload;
|
|
56
|
-
try {
|
|
57
|
-
payload = tool.getSnapshot();
|
|
58
|
-
} catch {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
const {
|
|
62
|
-
over,
|
|
63
|
-
approxBytes
|
|
64
|
-
} = (0, _emitBudget.approxSizeOver)(payload, _emitBudget.MAX_SNAPSHOT_EMIT_BYTES);
|
|
65
|
-
if (over) {
|
|
66
|
-
if (!warnedOversized.has(toolId)) {
|
|
67
|
-
warnedOversized.add(toolId);
|
|
68
|
-
console.warn(`${LOG} "${toolId}" snapshot ≈${(approxBytes / 1e6).toFixed(1)}MB exceeds the ${_emitBudget.MAX_SNAPSHOT_EMIT_BYTES / 1e6}MB sync budget — dropped (dashboard will be stale for this tool). The adapter should ship a wire-form snapshot (summaries + on-demand detail).`);
|
|
69
|
-
}
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const message = {
|
|
73
|
-
toolId,
|
|
74
|
-
persistentDeviceId: deviceId,
|
|
75
|
-
timestamp: lastEmit[toolId],
|
|
76
|
-
version: tool.version,
|
|
77
|
-
kind: "snapshot",
|
|
78
|
-
payload
|
|
79
|
-
};
|
|
80
|
-
safeEmit("devtool-sync", message);
|
|
81
|
-
};
|
|
82
|
-
const scheduleSnapshot = toolId => {
|
|
83
|
-
if (timers[toolId]) return;
|
|
84
|
-
const elapsed = Date.now() - (lastEmit[toolId] ?? 0);
|
|
85
|
-
if (elapsed >= throttleMs) {
|
|
86
|
-
sendSnapshot(toolId);
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
timers[toolId] = setTimeout(() => {
|
|
90
|
-
delete timers[toolId];
|
|
91
|
-
sendSnapshot(toolId);
|
|
92
|
-
}, throttleMs - elapsed);
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Emit a tool's snapshot immediately, cancelling any throttled push.
|
|
97
|
-
* The escape hatch for "the tree is about to die, get this out now" — see
|
|
98
|
-
* ./crashFlush. Ignores watch MODE: a crash flush is worth sending even to a
|
|
99
|
-
* dashboard that only asked to record.
|
|
100
|
-
*
|
|
101
|
-
* It does NOT ignore "is anyone there". Building a snapshot is the expensive
|
|
102
|
-
* part of this tool (deep-copying every captured entry, then walking it to
|
|
103
|
-
* price it), and `socket.emit` on a disconnected socket queues into
|
|
104
|
-
* socket.io's unbounded send buffer. Without these two guards, an app logging
|
|
105
|
-
* errors in a loop with no dashboard attached would pay that cost several
|
|
106
|
-
* times a second and grow the buffer until it ran out of memory — in a
|
|
107
|
-
* customer's app, for no observer.
|
|
108
|
-
*/
|
|
109
|
-
const flushNow = toolId => {
|
|
110
|
-
if (!socket.connected) return;
|
|
111
|
-
if (!unsubs.has(toolId)) return; // nobody is watching this tool
|
|
112
|
-
if (timers[toolId]) {
|
|
113
|
-
clearTimeout(timers[toolId]);
|
|
114
|
-
delete timers[toolId];
|
|
115
|
-
}
|
|
116
|
-
sendSnapshot(toolId);
|
|
117
|
-
};
|
|
118
|
-
(0, _crashFlush.registerToolFlusher)(flushNow);
|
|
119
|
-
|
|
120
|
-
/** The mode each currently-subscribed tool is wired for. */
|
|
121
|
-
const activeModes = new Map();
|
|
122
|
-
const watchTool = (toolId, mode) => {
|
|
123
|
-
if (!allowed()) return;
|
|
124
|
-
const tool = getTools()[toolId];
|
|
125
|
-
if (!tool) return;
|
|
126
|
-
if (unsubs.has(toolId)) {
|
|
127
|
-
if (activeModes.get(toolId) === mode) return;
|
|
128
|
-
// Mode changed (a streaming dashboard left, or arrived) — rewire, but
|
|
129
|
-
// do NOT drop the subscription in between: releasing it would take the
|
|
130
|
-
// store's subscriber count to 0 and stop capture mid-flight.
|
|
131
|
-
const previous = unsubs.get(toolId);
|
|
132
|
-
unsubs.set(toolId, tool.subscribe(mode === "stream" ? () => scheduleSnapshot(toolId) : () => {}));
|
|
133
|
-
activeModes.set(toolId, mode);
|
|
134
|
-
previous?.();
|
|
135
|
-
(0, _logger.log)(`${LOG} "${toolId}" watch mode → ${mode}`, enableLogs);
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
unsubs.set(toolId, tool.subscribe(mode === "stream" ? () => scheduleSnapshot(toolId) : () => {}));
|
|
139
|
-
activeModes.set(toolId, mode);
|
|
140
|
-
(0, _logger.log)(`${LOG} Watching "${toolId}" (${mode})`, enableLogs);
|
|
141
|
-
};
|
|
142
|
-
const unwatchTool = toolId => {
|
|
143
|
-
unsubs.get(toolId)?.();
|
|
144
|
-
unsubs.delete(toolId);
|
|
145
|
-
activeModes.delete(toolId);
|
|
146
|
-
if (timers[toolId]) {
|
|
147
|
-
clearTimeout(timers[toolId]);
|
|
148
|
-
delete timers[toolId];
|
|
149
|
-
}
|
|
150
|
-
(0, _logger.log)(`${LOG} Stopped watching "${toolId}"`, enableLogs);
|
|
151
|
-
};
|
|
152
|
-
const sendCapabilities = () => {
|
|
153
|
-
const message = {
|
|
154
|
-
persistentDeviceId: deviceId,
|
|
155
|
-
protocolVersion: _types.SYNC_PROTOCOL_VERSION,
|
|
156
|
-
tools: Object.entries(getTools()).map(([toolId, tool]) => ({
|
|
157
|
-
toolId,
|
|
158
|
-
version: tool.version,
|
|
159
|
-
actions: Object.keys(tool.actions ?? {})
|
|
160
|
-
}))
|
|
161
|
-
};
|
|
162
|
-
safeEmit("devtool-capabilities", message);
|
|
163
|
-
};
|
|
164
|
-
const handleWatch = message => {
|
|
165
|
-
if (!allowed() || !isForThisDevice(message.targetDeviceId)) return;
|
|
166
|
-
if (message.watching) {
|
|
167
|
-
// `stream` is only trustworthy once a broker has merged every
|
|
168
|
-
// dashboard's request (see DevToolWatchMessage.consolidated). Without
|
|
169
|
-
// that, an unmerged capture request could quiet a dashboard that is
|
|
170
|
-
// actively painting, so default to streaming.
|
|
171
|
-
const mode = message.consolidated === true && message.stream === false ? "capture" : "stream";
|
|
172
|
-
watched.set(message.toolId, mode);
|
|
173
|
-
watchTool(message.toolId, mode);
|
|
174
|
-
// Only a streaming audience gets the priming snapshot. A capture-only
|
|
175
|
-
// client reads with `request-devtool-state` when it actually wants one;
|
|
176
|
-
// pushing here would hand it the very payload it asked not to receive.
|
|
177
|
-
if (mode === "stream") sendSnapshot(message.toolId);
|
|
178
|
-
} else {
|
|
179
|
-
watched.delete(message.toolId);
|
|
180
|
-
unwatchTool(message.toolId);
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
const handleRequest = message => {
|
|
184
|
-
if (!allowed() || !isForThisDevice(message.targetDeviceId)) return;
|
|
185
|
-
sendSnapshot(message.toolId);
|
|
186
|
-
};
|
|
187
|
-
const handleAction = async message => {
|
|
188
|
-
if (!allowed() || !isForThisDevice(message.targetDeviceId)) return;
|
|
189
|
-
const startedAt = accessGeneration;
|
|
190
|
-
const {
|
|
191
|
-
requestId,
|
|
192
|
-
toolId,
|
|
193
|
-
action,
|
|
194
|
-
params
|
|
195
|
-
} = message;
|
|
196
|
-
const result = {
|
|
197
|
-
requestId,
|
|
198
|
-
toolId,
|
|
199
|
-
action,
|
|
200
|
-
persistentDeviceId: deviceId,
|
|
201
|
-
timestamp: Date.now(),
|
|
202
|
-
ok: false
|
|
203
|
-
};
|
|
204
|
-
const tool = getTools()[toolId];
|
|
205
|
-
const fn = tool?.actions?.[action];
|
|
206
|
-
if (!fn) {
|
|
207
|
-
result.error = tool ? `Unknown action "${action}" on tool "${toolId}"` : `Unknown tool "${toolId}"`;
|
|
208
|
-
safeEmit("devtool-action-result", result);
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
try {
|
|
212
|
-
result.result = await fn(params);
|
|
213
|
-
result.ok = true;
|
|
214
|
-
} catch (error) {
|
|
215
|
-
result.error = error instanceof Error ? error.message : String(error);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// A subsequent admission cannot authorize work from the previous session.
|
|
219
|
-
if (startedAt !== accessGeneration || !allowed()) return;
|
|
220
|
-
if (result.ok) {
|
|
221
|
-
const {
|
|
222
|
-
over,
|
|
223
|
-
approxBytes
|
|
224
|
-
} = (0, _emitBudget.approxSizeOver)(result.result, _emitBudget.MAX_ACTION_RESULT_BYTES);
|
|
225
|
-
if (over) {
|
|
226
|
-
result.ok = false;
|
|
227
|
-
result.result = undefined;
|
|
228
|
-
result.error = `Result ≈${(approxBytes / 1e6).toFixed(1)}MB exceeds the ${_emitBudget.MAX_ACTION_RESULT_BYTES / 1e6}MB sync budget. Narrow the request (e.g. includeValues:false, a limit, or a more specific action).`;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
result.timestamp = Date.now();
|
|
232
|
-
safeEmit("devtool-action-result", result);
|
|
233
|
-
};
|
|
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
|
-
});
|
|
248
|
-
sendCapabilities();
|
|
249
|
-
};
|
|
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);
|
|
272
|
-
for (const [toolId, mode] of watched) {
|
|
273
|
-
if (getTools()[toolId]) watchTool(toolId, mode);
|
|
274
|
-
}
|
|
275
|
-
socket.on("connect", handleConnect);
|
|
276
|
-
socket.on("disconnect", handleDisconnect);
|
|
277
|
-
socket.on("devtool-watch", handleWatch);
|
|
278
|
-
socket.on("request-devtool-state", handleRequest);
|
|
279
|
-
socket.on("devtool-action", handleAction);
|
|
280
|
-
if (socket.connected) handleConnect();
|
|
281
|
-
return () => {
|
|
282
|
-
disposed = true;
|
|
283
|
-
offAccount();
|
|
284
|
-
socket.off("buoy:access", handleAccess);
|
|
285
|
-
dropToolWork();
|
|
286
|
-
(0, _crashFlush.unregisterToolFlusher)(flushNow);
|
|
287
|
-
socket.off("connect", handleConnect);
|
|
288
|
-
socket.off("disconnect", handleDisconnect);
|
|
289
|
-
socket.off("devtool-watch", handleWatch);
|
|
290
|
-
socket.off("request-devtool-state", handleRequest);
|
|
291
|
-
socket.off("devtool-action", handleAction);
|
|
292
|
-
for (const toolId of [...unsubs.keys()]) unwatchTool(toolId);
|
|
293
|
-
};
|
|
294
|
-
}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.attachDeviceSync=attachDeviceSync;var _license=require("@buoy-gg/license");var _logger=require("./utils/logger");var _crashFlush=require("./crashFlush");var _emitBudget=require("./emitBudget");var _types=require("./types");var _realNow=require("./realNow");const LOG="[ExternalSync]";function attachDeviceSync({socket:r,deviceId:p,getTools:u,watched:h,throttleMs:M=200,enableLogs:v=false}){let O=false;let y=false;let w=0;const o=()=>!O&&y&&r.connected&&_license.LicenseManager.hasAccountAccess();const c=new Map;const _={};const i={};const T=e=>e==="All"||e===p;const S=(e,t)=>{if(!o())return;try{r.emit(e,t)}catch(n){(0,_logger.log)(`${LOG} Failed to emit "${e}": ${n}. Payload must be JSON-serializable.`,v,"error")}};const B=new Set;const l=e=>{if(!o())return;const t=u()[e];if(!t)return;_[e]=(0,_realNow.realNow)();let n;try{n=t.getSnapshot()}catch{return}const{over:a,approxBytes:f}=(0,_emitBudget.approxSizeOver)(n,_emitBudget.MAX_SNAPSHOT_EMIT_BYTES);if(a){if(!B.has(e)){B.add(e);console.warn(`${LOG} "${e}" snapshot \u2248${(f/1e6).toFixed(1)}MB exceeds the ${_emitBudget.MAX_SNAPSHOT_EMIT_BYTES/1e6}MB sync budget \u2014 dropped (dashboard will be stale for this tool). The adapter should ship a wire-form snapshot (summaries + on-demand detail).`)}return}const A={toolId:e,persistentDeviceId:p,timestamp:_[e],version:t.version,kind:"snapshot",payload:n};S("devtool-sync",A)};const N=e=>{if(i[e])return;const t=(0,_realNow.realNow)()-(_[e]??0);if(t>=M){l(e);return}i[e]=setTimeout(()=>{delete i[e];l(e)},M-t)};const E=e=>{if(!r.connected)return;if(!c.has(e))return;if(i[e]){clearTimeout(i[e]);delete i[e]}l(e)};(0,_crashFlush.registerToolFlusher)(E);const m=new Map;const q=(e,t)=>{if(!o())return;const n=u()[e];if(!n)return;if(c.has(e)){if(m.get(e)===t)return;const a=c.get(e);c.set(e,n.subscribe(t==="stream"?()=>N(e):()=>{}));m.set(e,t);a?.();(0,_logger.log)(`${LOG} "${e}" watch mode \u2192 ${t}`,v);return}c.set(e,n.subscribe(t==="stream"?()=>N(e):()=>{}));m.set(e,t);(0,_logger.log)(`${LOG} Watching "${e}" (${t})`,v)};const $=e=>{c.get(e)?.();c.delete(e);m.delete(e);if(i[e]){clearTimeout(i[e]);delete i[e]}(0,_logger.log)(`${LOG} Stopped watching "${e}"`,v)};const j=()=>{const e={persistentDeviceId:p,protocolVersion:_types.SYNC_PROTOCOL_VERSION,tools:Object.entries(u()).map(([t,n])=>({toolId:t,version:n.version,actions:Object.keys(n.actions??{})}))};S("devtool-capabilities",e)};const x=e=>{if(!o()||!T(e.targetDeviceId))return;if(e.watching){const t=e.consolidated===true&&e.stream===false?"capture":"stream";h.set(e.toolId,t);q(e.toolId,t);if(t==="stream")l(e.toolId)}else{h.delete(e.toolId);$(e.toolId)}};const D=e=>{if(!o()||!T(e.targetDeviceId))return;l(e.toolId)};const F=async e=>{if(!o()||!T(e.targetDeviceId))return;const t=w;const{requestId:n,toolId:a,action:f,params:A}=e;const s={requestId:n,toolId:a,action:f,persistentDeviceId:p,timestamp:(0,_realNow.realNow)(),ok:false};const L=u()[a];const Y=L?.actions?.[f];if(!Y){s.error=L?`Unknown action "${f}" on tool "${a}"`:`Unknown tool "${a}"`;S("devtool-action-result",s);return}try{s.result=await Y(A);s.ok=true}catch(d){s.error=d instanceof Error?d.message:String(d)}if(t!==w||!o())return;if(s.ok){const{over:d,approxBytes:U}=(0,_emitBudget.approxSizeOver)(s.result,_emitBudget.MAX_ACTION_RESULT_BYTES);if(d){s.ok=false;s.result=void 0;s.error=`Result \u2248${(U/1e6).toFixed(1)}MB exceeds the ${_emitBudget.MAX_ACTION_RESULT_BYTES/1e6}MB sync budget. Narrow the request (e.g. includeValues:false, a limit, or a more specific action).`}}s.timestamp=(0,_realNow.realNow)();S("devtool-action-result",s)};const b=()=>{w+=1;for(const t of[...c.keys()])$(t);h.clear();for(const t of Object.values(i))clearTimeout(t);const e=r;if(e.sendBuffer)e.sendBuffer=e.sendBuffer.filter(t=>!["devtool-sync","devtool-capabilities","devtool-action-result","device-license"].includes(String(t.data?.[0])))};const P=()=>{const e=_license.LicenseManager.getState();r.emit("device-license",{tier:e.tier,isPro:e.isPro?"1":"0"});j()};const R=e=>{y=e?.protocol===1&&e.ready===true;if(!o())b();else P()};const z=_license.LicenseManager.subscribe(()=>{if(!_license.LicenseManager.hasAccountAccess()){if(r.connected)r.emit("device-license",{tier:"locked",isPro:"0"});b()}else if(o())P()});const g=()=>{y=false;r.emit("buoy:access:request")};const C=()=>{y=false;b()};r.on("buoy:access",R);for(const[e,t]of h){if(u()[e])q(e,t)}r.on("connect",g);r.on("disconnect",C);r.on("devtool-watch",x);r.on("request-devtool-state",D);r.on("devtool-action",F);if(r.connected)g();return()=>{O=true;z();r.off("buoy:access",R);b();(0,_crashFlush.unregisterToolFlusher)(E);r.off("connect",g);r.off("disconnect",C);r.off("devtool-watch",x);r.off("request-devtool-state",D);r.off("devtool-action",F);for(const e of[...c.keys()])$(e)}}
|
|
@@ -1,29 +1 @@
|
|
|
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
|
-
}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.watchBrokerAccess=watchBrokerAccess;function watchBrokerAccess(c,n){const e=()=>n(false);const o=()=>{e();c.emit("buoy:access:request")};const s=t=>{const r=t;n(r?.protocol===1&&r?.ready===true)};c.on("connect",o);c.on("disconnect",e);c.on("buoy:access",s);e();if(c.connected)o();return()=>{c.off("connect",o);c.off("disconnect",e);c.off("buoy:access",s)}}
|
|
@@ -1,73 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.flushToolSyncNow = flushToolSyncNow;
|
|
7
|
-
exports.registerToolFlusher = registerToolFlusher;
|
|
8
|
-
exports.unregisterToolFlusher = unregisterToolFlusher;
|
|
9
|
-
/**
|
|
10
|
-
* crashFlush — a global "push this tool's snapshot RIGHT NOW" seam.
|
|
11
|
-
*
|
|
12
|
-
* Why a global and not an import: the device sync bridge lives in the React
|
|
13
|
-
* tree (`useExternalSync`), but the moment worth capturing most — a fatal
|
|
14
|
-
* render error — is the moment that tree dies. React unmounts the root right
|
|
15
|
-
* after the error is reported, which detaches the bridge and cancels the
|
|
16
|
-
* normal throttled snapshot push, so the crash never reaches the dashboard or
|
|
17
|
-
* the MCP server. The app then looks like it simply stopped answering.
|
|
18
|
-
*
|
|
19
|
-
* A tool that knows it is about to lose the tree (see @buoy-gg/console's fatal
|
|
20
|
-
* hook) calls {@link flushToolSyncNow} synchronously from its error handler,
|
|
21
|
-
* while the socket is still attached, and the snapshot goes out immediately.
|
|
22
|
-
*
|
|
23
|
-
* It is a global because the tools deliberately do NOT depend on this package
|
|
24
|
-
* (their adapters are duck-typed and picked up by auto-discovery), so there is
|
|
25
|
-
* no import edge to hang a registry on. Callers must treat it as best-effort:
|
|
26
|
-
* absent bridge = no-op.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
const FLUSH_KEY = "__buoyFlushToolSync";
|
|
30
|
-
|
|
31
|
-
/** Emit `toolId`'s current snapshot now, bypassing the throttle. */
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* A SET, not a single slot: React can mount a second provider (or re-run the
|
|
35
|
-
* attach effect setup before the old one's cleanup) so two bridges are briefly
|
|
36
|
-
* live at once. A last-writer-wins slot would let the second registration
|
|
37
|
-
* silently disable the first's crash flush — failing in exactly the moment it
|
|
38
|
-
* is the only thing that gets the crash out, with no error and no log.
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
function flushers() {
|
|
42
|
-
const g = globalThis;
|
|
43
|
-
if (!g[FLUSH_KEY]) g[FLUSH_KEY] = new Set();
|
|
44
|
-
return g[FLUSH_KEY];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/** Install a bridge's flusher. Called by `attachDeviceSync`. */
|
|
48
|
-
function registerToolFlusher(flush) {
|
|
49
|
-
flushers().add(flush);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** Remove one bridge's flusher, leaving any others intact. */
|
|
53
|
-
function unregisterToolFlusher(flush) {
|
|
54
|
-
flushers().delete(flush);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Push a tool's snapshot immediately through every attached sync bridge.
|
|
59
|
-
* Never throws — a crash path must not crash.
|
|
60
|
-
*/
|
|
61
|
-
function flushToolSyncNow(toolId) {
|
|
62
|
-
try {
|
|
63
|
-
for (const flush of flushers()) {
|
|
64
|
-
try {
|
|
65
|
-
flush(toolId);
|
|
66
|
-
} catch {
|
|
67
|
-
// One bad bridge must not stop the others.
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
} catch {
|
|
71
|
-
// Best-effort by design.
|
|
72
|
-
}
|
|
73
|
-
}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.flushToolSyncNow=flushToolSyncNow;exports.registerToolFlusher=registerToolFlusher;exports.unregisterToolFlusher=unregisterToolFlusher;const FLUSH_KEY="__buoyFlushToolSync";function flushers(){const o=globalThis;if(!o[FLUSH_KEY])o[FLUSH_KEY]=new Set;return o[FLUSH_KEY]}function registerToolFlusher(o){flushers().add(o)}function unregisterToolFlusher(o){flushers().delete(o)}function flushToolSyncNow(o){try{for(const e of flushers()){try{e(o)}catch{}}}catch{}}
|
|
@@ -1,57 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.MAX_SNAPSHOT_EMIT_BYTES = exports.MAX_ACTION_RESULT_BYTES = void 0;
|
|
7
|
-
exports.approxSizeOver = approxSizeOver;
|
|
8
|
-
/**
|
|
9
|
-
* Emit-layer byte budgets — the choke-point guard.
|
|
10
|
-
*
|
|
11
|
-
* Every adapter's snapshot and every action result funnels through
|
|
12
|
-
* socket.emit, whose serializer walks the ENTIRE payload synchronously on
|
|
13
|
-
* the JS thread (and JSON.stringify does not honor structural sharing).
|
|
14
|
-
* Per-adapter guards get missed — the redux adapter shipping raw state
|
|
15
|
-
* trees froze and native-OOM-crashed enterprise apps whenever a dashboard
|
|
16
|
-
* was connected. One budget here closes every adapter's hole at once:
|
|
17
|
-
* oversized payloads are dropped (snapshots) or replaced with an error
|
|
18
|
-
* (action results) instead of being serialized.
|
|
19
|
-
*
|
|
20
|
-
* The estimator is O(limit) — early-abort walk, O(1) for giant strings —
|
|
21
|
-
* never O(payload). Copied per-package by design (see events/redux copies).
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
/** Max approx bytes for one tool snapshot emit. */
|
|
25
|
-
const MAX_SNAPSHOT_EMIT_BYTES = exports.MAX_SNAPSHOT_EMIT_BYTES = 2 * 1024 * 1024;
|
|
26
|
-
/** Max approx bytes for one action-result emit (explicit requests get more). */
|
|
27
|
-
const MAX_ACTION_RESULT_BYTES = exports.MAX_ACTION_RESULT_BYTES = 8 * 1024 * 1024;
|
|
28
|
-
function approxSizeOver(value, limit) {
|
|
29
|
-
let bytes = 0;
|
|
30
|
-
const stack = [value];
|
|
31
|
-
const seen = new Set();
|
|
32
|
-
while (stack.length > 0) {
|
|
33
|
-
if (bytes > limit) return {
|
|
34
|
-
over: true,
|
|
35
|
-
approxBytes: bytes
|
|
36
|
-
};
|
|
37
|
-
const v = stack.pop();
|
|
38
|
-
if (v === null || v === undefined) bytes += 4;else if (typeof v === "string") bytes += v.length + 2;else if (typeof v === "number") bytes += 8;else if (typeof v === "boolean") bytes += 5;else if (typeof v === "object") {
|
|
39
|
-
if (seen.has(v)) continue;
|
|
40
|
-
seen.add(v);
|
|
41
|
-
if (Array.isArray(v)) {
|
|
42
|
-
bytes += 2 + v.length;
|
|
43
|
-
for (let i = 0; i < v.length; i++) stack.push(v[i]);
|
|
44
|
-
} else {
|
|
45
|
-
bytes += 2;
|
|
46
|
-
for (const k in v) {
|
|
47
|
-
bytes += k.length + 4;
|
|
48
|
-
stack.push(v[k]);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
} else bytes += 4;
|
|
52
|
-
}
|
|
53
|
-
return {
|
|
54
|
-
over: bytes > limit,
|
|
55
|
-
approxBytes: bytes
|
|
56
|
-
};
|
|
57
|
-
}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.MAX_SNAPSHOT_EMIT_BYTES=exports.MAX_ACTION_RESULT_BYTES=void 0;exports.approxSizeOver=approxSizeOver;const MAX_SNAPSHOT_EMIT_BYTES=exports.MAX_SNAPSHOT_EMIT_BYTES=2*1024*1024;const MAX_ACTION_RESULT_BYTES=exports.MAX_ACTION_RESULT_BYTES=8*1024*1024;function approxSizeOver(p,s){let t=0;const r=[p];const n=new Set;while(r.length>0){if(t>s)return{over:true,approxBytes:t};const e=r.pop();if(e===null||e===void 0)t+=4;else if(typeof e==="string")t+=e.length+2;else if(typeof e==="number")t+=8;else if(typeof e==="boolean")t+=5;else if(typeof e==="object"){if(n.has(e))continue;n.add(e);if(Array.isArray(e)){t+=2+e.length;for(let o=0;o<e.length;o++)r.push(e[o])}else{t+=2;for(const o in e){t+=o.length+4;r.push(e[o])}}}else t+=4}return{over:t>s,approxBytes:t}}
|
package/lib/commonjs/index.js
CHANGED
|
@@ -1,83 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
Object.defineProperty(exports, "DEFAULT_BROKER_PORT", {
|
|
7
|
-
enumerable: true,
|
|
8
|
-
get: function () {
|
|
9
|
-
return _getDefaultSocketURL.DEFAULT_BROKER_PORT;
|
|
10
|
-
}
|
|
11
|
-
});
|
|
12
|
-
Object.defineProperty(exports, "SYNC_PROTOCOL_VERSION", {
|
|
13
|
-
enumerable: true,
|
|
14
|
-
get: function () {
|
|
15
|
-
return _types.SYNC_PROTOCOL_VERSION;
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
Object.defineProperty(exports, "flushToolSyncNow", {
|
|
19
|
-
enumerable: true,
|
|
20
|
-
get: function () {
|
|
21
|
-
return _crashFlush.flushToolSyncNow;
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
Object.defineProperty(exports, "getDefaultSocketURL", {
|
|
25
|
-
enumerable: true,
|
|
26
|
-
get: function () {
|
|
27
|
-
return _getDefaultSocketURL.getDefaultSocketURL;
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
Object.defineProperty(exports, "getMetroHost", {
|
|
31
|
-
enumerable: true,
|
|
32
|
-
get: function () {
|
|
33
|
-
return _getDefaultSocketURL.getMetroHost;
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
Object.defineProperty(exports, "getSyncStatus", {
|
|
37
|
-
enumerable: true,
|
|
38
|
-
get: function () {
|
|
39
|
-
return _syncStatusStore.getSyncStatus;
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
Object.defineProperty(exports, "registerToolFlusher", {
|
|
43
|
-
enumerable: true,
|
|
44
|
-
get: function () {
|
|
45
|
-
return _crashFlush.registerToolFlusher;
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
Object.defineProperty(exports, "setSyncStatus", {
|
|
49
|
-
enumerable: true,
|
|
50
|
-
get: function () {
|
|
51
|
-
return _syncStatusStore.setSyncStatus;
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
Object.defineProperty(exports, "subscribeSyncStatus", {
|
|
55
|
-
enumerable: true,
|
|
56
|
-
get: function () {
|
|
57
|
-
return _syncStatusStore.subscribeSyncStatus;
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
Object.defineProperty(exports, "unregisterToolFlusher", {
|
|
61
|
-
enumerable: true,
|
|
62
|
-
get: function () {
|
|
63
|
-
return _crashFlush.unregisterToolFlusher;
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
Object.defineProperty(exports, "useExternalSync", {
|
|
67
|
-
enumerable: true,
|
|
68
|
-
get: function () {
|
|
69
|
-
return _useExternalSync.useExternalSync;
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
Object.defineProperty(exports, "useExternalSyncSocket", {
|
|
73
|
-
enumerable: true,
|
|
74
|
-
get: function () {
|
|
75
|
-
return _useExternalSyncSocket.useExternalSyncSocket;
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
var _useExternalSync = require("./useExternalSync");
|
|
79
|
-
var _useExternalSyncSocket = require("./useExternalSyncSocket");
|
|
80
|
-
var _getDefaultSocketURL = require("./utils/getDefaultSocketURL");
|
|
81
|
-
var _crashFlush = require("./crashFlush");
|
|
82
|
-
var _syncStatusStore = require("./syncStatusStore");
|
|
83
|
-
var _types = require("./types");
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});Object.defineProperty(exports,"DEFAULT_BROKER_PORT",{enumerable:true,get:function(){return _getDefaultSocketURL.DEFAULT_BROKER_PORT}});Object.defineProperty(exports,"SYNC_PROTOCOL_VERSION",{enumerable:true,get:function(){return _types.SYNC_PROTOCOL_VERSION}});Object.defineProperty(exports,"flushToolSyncNow",{enumerable:true,get:function(){return _crashFlush.flushToolSyncNow}});Object.defineProperty(exports,"getDefaultSocketURL",{enumerable:true,get:function(){return _getDefaultSocketURL.getDefaultSocketURL}});Object.defineProperty(exports,"getMetroHost",{enumerable:true,get:function(){return _getDefaultSocketURL.getMetroHost}});Object.defineProperty(exports,"getSyncStatus",{enumerable:true,get:function(){return _syncStatusStore.getSyncStatus}});Object.defineProperty(exports,"registerToolFlusher",{enumerable:true,get:function(){return _crashFlush.registerToolFlusher}});Object.defineProperty(exports,"setSyncStatus",{enumerable:true,get:function(){return _syncStatusStore.setSyncStatus}});Object.defineProperty(exports,"subscribeSyncStatus",{enumerable:true,get:function(){return _syncStatusStore.subscribeSyncStatus}});Object.defineProperty(exports,"unregisterToolFlusher",{enumerable:true,get:function(){return _crashFlush.unregisterToolFlusher}});Object.defineProperty(exports,"useExternalSync",{enumerable:true,get:function(){return _useExternalSync.useExternalSync}});Object.defineProperty(exports,"useExternalSyncSocket",{enumerable:true,get:function(){return _useExternalSyncSocket.useExternalSyncSocket}});var _useExternalSync=require("./useExternalSync");var _useExternalSyncSocket=require("./useExternalSyncSocket");var _getDefaultSocketURL=require("./utils/getDefaultSocketURL");var _crashFlush=require("./crashFlush");var _syncStatusStore=require("./syncStatusStore");var _types=require("./types");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.pinSocketTimers=pinSocketTimers;exports.realClearTimeout=realClearTimeout;exports.realNow=realNow;exports.realSetTimeout=realSetTimeout;exports.withRealClock=withRealClock;function realNow(){const e=globalThis.__buoyRealNow;return typeof e==="function"?e():Date.now()}function timers(){const e=globalThis.__buoyRealTimers;return e??globalThis}function realSetTimeout(e,t){return timers().setTimeout(e,t)}function realClearTimeout(e){timers().clearTimeout(e)}function withRealClock(e){const t=globalThis.__buoyWithRealClock;return typeof t==="function"?t(e):e()}const CLOCK_READING_METHODS=["_resetPingTimeout","_hasPingExpired"];function pinSocketTimers(e){const t=e?.io;if(!t||t.__buoyRealTimers)return;const i=o=>{if(!o)return;o.setTimeoutFn=realSetTimeout;o.clearTimeoutFn=realClearTimeout;for(const n of CLOCK_READING_METHODS){const r=o[n];if(typeof r!=="function"||r.__buoyReal)continue;const l=function(...s){return withRealClock(()=>r.apply(this,s))};l.__buoyReal=true;o[n]=l}};i(t);i(t.engine);const u=t.open;if(typeof u==="function"){t.open=function(...o){const n=u.apply(this,o);i(this?.engine);return n}}t.__buoyRealTimers=true}
|
|
@@ -1,40 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.getSyncStatus = getSyncStatus;
|
|
7
|
-
exports.setSyncStatus = setSyncStatus;
|
|
8
|
-
exports.subscribeSyncStatus = subscribeSyncStatus;
|
|
9
|
-
/**
|
|
10
|
-
* Module-level desktop-sync status, written by useExternalSyncSocket and
|
|
11
|
-
* read by the floating menu's settings modal (via useSyncExternalStore).
|
|
12
|
-
* Lives here rather than in core because only the socket hook knows the
|
|
13
|
-
* platform-rewritten URL and every connection transition — including for
|
|
14
|
-
* apps that call the hook directly instead of using zero-config sync.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
let status = {
|
|
18
|
-
targetURL: null,
|
|
19
|
-
state: "idle",
|
|
20
|
-
lastError: null,
|
|
21
|
-
since: 0
|
|
22
|
-
};
|
|
23
|
-
const listeners = new Set();
|
|
24
|
-
function setSyncStatus(patch) {
|
|
25
|
-
status = {
|
|
26
|
-
...status,
|
|
27
|
-
...patch,
|
|
28
|
-
since: Date.now()
|
|
29
|
-
};
|
|
30
|
-
listeners.forEach(listener => listener());
|
|
31
|
-
}
|
|
32
|
-
function getSyncStatus() {
|
|
33
|
-
return status;
|
|
34
|
-
}
|
|
35
|
-
function subscribeSyncStatus(listener) {
|
|
36
|
-
listeners.add(listener);
|
|
37
|
-
return () => {
|
|
38
|
-
listeners.delete(listener);
|
|
39
|
-
};
|
|
40
|
-
}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.getSyncStatus=getSyncStatus;exports.setSyncStatus=setSyncStatus;exports.subscribeSyncStatus=subscribeSyncStatus;var _realNow=require("./realNow");let status={targetURL:null,state:"idle",lastError:null,since:0};const listeners=new Set;function setSyncStatus(t){status={...status,...t,since:(0,_realNow.realNow)()};listeners.forEach(e=>e())}function getSyncStatus(){return status}function subscribeSyncStatus(t){listeners.add(t);return()=>{listeners.delete(t)}}
|