@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.
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 -84
  12. package/lib/commonjs/utils/getDefaultSocketURL.web.js +1 -27
  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 -9
  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 -76
  27. package/lib/module/utils/getDefaultSocketURL.web.js +1 -19
  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 -5
  31. package/lib/typescript/realNow.d.ts +27 -0
  32. package/lib/typescript/syncStatusStore.d.ts +0 -7
  33. package/lib/web/index.mjs +1 -2
  34. package/package.json +3 -6
  35. package/lib/typescript/attachDeviceSync.d.ts.map +0 -1
  36. package/lib/typescript/brokerAccess.d.ts.map +0 -1
  37. package/lib/typescript/crashFlush.d.ts.map +0 -1
  38. package/lib/typescript/emitBudget.d.ts.map +0 -1
  39. package/lib/typescript/index.d.ts.map +0 -1
  40. package/lib/typescript/syncStatusStore.d.ts.map +0 -1
  41. package/lib/typescript/types.d.ts.map +0 -1
  42. package/lib/typescript/useExternalSync.d.ts.map +0 -1
  43. package/lib/typescript/useExternalSyncSocket.d.ts.map +0 -1
  44. package/lib/typescript/utils/getDefaultSocketURL.d.ts.map +0 -1
  45. package/lib/typescript/utils/getDefaultSocketURL.web.d.ts.map +0 -1
  46. package/lib/typescript/utils/logger.d.ts.map +0 -1
  47. package/lib/typescript/utils/platformUtils.d.ts.map +0 -1
  48. 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}}
@@ -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)}}