@buoy-gg/external-sync 7.0.75 → 7.0.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,13 +13,15 @@ npm install @buoy-gg/core @buoy-gg/external-sync @buoy-gg/network
13
13
 
14
14
  ## Account setup
15
15
 
16
- Start in a development build with a Free or Pro Buoy account key. From the app directory:
16
+ Start in a development build. You need a Free or Pro Buoy account. Sign in from your app's folder:
17
17
 
18
18
  ```bash
19
19
  npx --package=@buoy-gg/core buoy login
20
20
  ```
21
21
 
22
- For Expo, initialize Buoy with the key written to `.env.local`:
22
+ Your browser opens buoy.gg. Click Allow. The CLI then writes a dev token. It works in dev builds for 30 days.
23
+
24
+ On Expo, the token goes in `.env.development.local`. Buoy reads it by itself in dev builds. You can also pass it in yourself:
23
25
 
24
26
  ```tsx
25
27
  import { Buoy } from "@buoy-gg/core";
@@ -27,7 +29,7 @@ import { Buoy } from "@buoy-gg/core";
27
29
  Buoy.init({ licenseKey: process.env.EXPO_PUBLIC_BUOY_KEY });
28
30
  ```
29
31
 
30
- For React Native CLI, pass the key through your app's environment configuration; `.env.local` is not loaded automatically. Keep the menu inside the providers used by your app. Restart the development server after installing packages. See the [Quick Start](https://buoy.gg/buoy/latest/docs/quick-start) for the complete root component.
32
+ On React Native CLI, the token goes in `.env.local` as `BUOY_KEY`. React Native does not load that file by itself. Pass the token in with your app's own env setup. Keep the menu inside the providers used by your app. Restart the development server after installing packages. See the [Quick Start](https://buoy.gg/buoy/latest/docs/quick-start) for the complete root component.
31
33
 
32
34
  ## Connect your app
33
35
 
@@ -77,6 +79,6 @@ Prefer the core integration unless you are building a custom host. A direct hook
77
79
 
78
80
  ## Desktop and MCP
79
81
 
80
- React Native connections require `@buoy-gg/external-sync`. Follow the [Desktop connection guide](https://buoy.gg/buoy/latest/docs/desktop), sign in to Desktop separately, and confirm the selected device. [MCP](https://buoy.gg/buoy/latest/docs/mcp) also needs a process account and Pro access.
82
+ React Native connections require `@buoy-gg/external-sync`. Follow the [Desktop connection guide](https://buoy.gg/buoy/latest/docs/desktop), sign in to Desktop separately, and confirm the selected device. [MCP](https://buoy.gg/buoy/latest/docs/mcp) uses the sign-in from `buoy login` on your computer. Free has the basic MCP. Pro adds the full MCP.
81
83
 
82
84
  Device data can travel over your LAN to the configured broker. Account validation makes network requests. See [Telemetry](https://buoy.gg/buoy/latest/docs/telemetry) for data-flow details.
@@ -1 +1 @@
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","NEXT_PUBLIC_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:U,platform:i,enableLogs:n=false}){const r=(0,_react.useRef)(null);const[u,$]=(0,_react.useState)(null);const[I,f]=(0,_react.useState)(false);const S=l??(0,_getDefaultSocketURL.getDefaultSocketURL)();const d=(0,_react.useRef)(false);const o=`[${c}]`;const _=()=>{(0,_logger.log)(`${o} Socket connected successfully`,n);d.current=false;f(true);(0,_syncStatusStore.setSyncStatus)({state:"connected",lastError:null})};const g=t=>{(0,_logger.log)(`${o} Socket disconnected. Reason: ${t}`,n);f(false);(0,_syncStatusStore.setSyncStatus)({state:"retrying"})};const k=t=>{(0,_syncStatusStore.setSyncStatus)({state:"retrying",lastError:t.message});if(!isDev)return;if(d.current)return;d.current=true;const e=i==="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}(${t.message})`,n,"warn")};const y=()=>{(0,_logger.log)(`${o} Socket connection timeout`,n,"error")};(0,_react.useEffect)(()=>{if(!s)return;const t=(0,_platformUtils.getPlatformSpecificURL)(S,i);(0,_syncStatusStore.setSyncStatus)({targetURL:t,state:"connecting",lastError:null});(0,_logger.log)(`${o} Platform: ${i}, using URL: ${t}`,n);try{if(globalSocketInstance&&(currentSocketURL!==t||currentDeviceId!==s||currentPlatform!==i)){globalSocketInstance.disconnect();globalSocketInstance=null}if(!globalSocketInstance){(0,_logger.log)(`${o} Creating new socket instance to ${t}`,n);try{const m=globalThis;(m.__BUOY_INTERNAL_HOSTS__??=new Set).add(new URL(t).host.toLowerCase())}catch{}globalSocketInstance=(0,_socket.io)(t,{autoConnect:true,query:buildHandshakeQuery({deviceName:c,persistentDeviceId:s,platform:i,extraDeviceInfo:a,envVariables:U}),reconnection:true,reconnectionAttempts:Infinity,reconnectionDelay:1e3,reconnectionDelayMax:5e3,transports:["websocket"]});(0,_realNow.pinSocketTimers)(globalSocketInstance);currentSocketURL=t;currentDeviceId=s;currentPlatform=i}else{(0,_logger.log)(`${o} Reusing existing socket instance to ${t}`,n)}r.current=globalSocketInstance;const e=r.current;socketUsers++;if(releaseTimer){(0,_realNow.realClearTimeout)(releaseTimer);releaseTimer=null}$(e);e.on("connect_error",k);e.on("connect_timeout",y);f(e.connected);if(e.connected){(0,_logger.log)(`${o} Socket already connected on init`,n)}e.on("connect",_);e.on("disconnect",g);return()=>{(0,_logger.log)(`${o} Cleaning up socket event listeners`,n);e.off("connect",_);e.off("disconnect",g);e.off("connect_error",k);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`,n);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}`,n,"error")}},[s,S,i]);(0,_react.useEffect)(()=>{if(r.current&&r.current.io.opts.query&&s){(0,_logger.log)(`${o} Updating device name in socket connection`,n);r.current.io.opts.query={...r.current.io.opts.query,deviceName:c,deviceId:s,platform:i}}},[c,o,s,i,n]);function R(){if(r.current&&!r.current.connected){(0,_logger.log)(`${o} Manually connecting to socket server`,n);r.current.connect()}}function h(){if(r.current&&r.current.connected){(0,_logger.log)(`${o} Manually disconnecting from socket server`,n);r.current.disconnect()}}const[E,v]=(0,_react.useState)(null);(0,_react.useEffect)(()=>{if(!u)return;return(0,_brokerAccess.watchBrokerAccess)(u,t=>{v(t?u:null);(0,_syncStatusStore.setSyncStatus)(t?{state:"connected",lastError:null}:{state:"retrying",lastError:"Update Buoy Desktop and sign in to enable desktop sync."})})},[u]);return{socket:E===u?u:null,connect:R,disconnect:h,isConnected:I&&E===u&&u!==null}}
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.buildHandshakeQuery=buildHandshakeQuery;exports.closeWithPage=closeWithPage;exports.getInstanceId=getInstanceId;exports.useExternalSyncSocket=useExternalSyncSocket;exports.withPage=withPage;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 e=globalThis;if(!e.__buoyInstanceId)e.__buoyInstanceId=makeConnectionNonce();return e.__buoyInstanceId}function buildHandshakeQuery(e){const u=t=>t&&Object.fromEntries(Object.entries(t).filter(([l])=>!["BUOY_LICENSE_KEY","BUOY_KEY","EXPO_PUBLIC_BUOY_KEY","VITE_BUOY_KEY","NEXT_PUBLIC_BUOY_KEY","REACT_APP_BUOY_KEY","licenseKey"].includes(l)));return{deviceName:e.deviceName,deviceId:e.persistentDeviceId,platform:e.platform,extraDeviceInfo:JSON.stringify(u(e.extraDeviceInfo)),envVariables:JSON.stringify(u(e.envVariables)),connectionNonce:makeConnectionNonce(),instanceId:getInstanceId()}}function withPage(e,u,t=globalThis.location){if(u!=="web"||!t?.host)return e;const l=t.pathname&&t.pathname!=="/"?t.pathname:"";return`${e} \xB7 ${t.host}${l.length>24?`${l.slice(0,23)}\u2026`:l}`}function closeWithPage(e,u){e.addEventListener("pagehide",()=>u()?.disconnect());e.addEventListener("pageshow",t=>{if(t.persisted)u()?.connect()})}let pageHooked=false;function disconnectWithPage(){const e=globalThis;if(pageHooked||!e.document||typeof e.addEventListener!=="function")return;pageHooked=true;closeWithPage(e,()=>globalSocketInstance)}function useExternalSyncSocket({deviceName:e,socketURL:u,persistentDeviceId:t,extraDeviceInfo:l,envVariables:h,platform:i,enableLogs:c=false}){const s=(0,_react.useRef)(null);const[a,$]=(0,_react.useState)(null);const[U,f]=(0,_react.useState)(false);const S=u??(0,_getDefaultSocketURL.getDefaultSocketURL)();const d=(0,_react.useRef)(false);const r=`[${e}]`;const g=()=>{(0,_logger.log)(`${r} Socket connected successfully`,c);d.current=false;f(true);(0,_syncStatusStore.setSyncStatus)({state:"connected",lastError:null})};const _=o=>{(0,_logger.log)(`${r} Socket disconnected. Reason: ${o}`,c);f(false);(0,_syncStatusStore.setSyncStatus)({state:"retrying"})};const k=o=>{(0,_syncStatusStore.setSyncStatus)({state:"retrying",lastError:o.message});if(!isDev)return;if(d.current)return;d.current=true;const n=i==="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. ${n}(${o.message})`,c,"warn")};const y=()=>{(0,_logger.log)(`${r} Socket connection timeout`,c,"error")};(0,_react.useEffect)(()=>{if(!t)return;const o=(0,_platformUtils.getPlatformSpecificURL)(S,i);(0,_syncStatusStore.setSyncStatus)({targetURL:o,state:"connecting",lastError:null});(0,_logger.log)(`${r} Platform: ${i}, using URL: ${o}`,c);try{if(globalSocketInstance&&(currentSocketURL!==o||currentDeviceId!==t||currentPlatform!==i)){globalSocketInstance.disconnect();globalSocketInstance=null}if(!globalSocketInstance){(0,_logger.log)(`${r} Creating new socket instance to ${o}`,c);try{const I=globalThis;(I.__BUOY_INTERNAL_HOSTS__??=new Set).add(new URL(o).host.toLowerCase())}catch{}globalSocketInstance=(0,_socket.io)(o,{autoConnect:true,query:buildHandshakeQuery({deviceName:withPage(e,i),persistentDeviceId:t,platform:i,extraDeviceInfo:l,envVariables:h}),reconnection:true,reconnectionAttempts:Infinity,reconnectionDelay:1e3,reconnectionDelayMax:5e3,transports:["websocket"]});(0,_realNow.pinSocketTimers)(globalSocketInstance);disconnectWithPage();currentSocketURL=o;currentDeviceId=t;currentPlatform=i}else{(0,_logger.log)(`${r} Reusing existing socket instance to ${o}`,c)}s.current=globalSocketInstance;const n=s.current;socketUsers++;if(releaseTimer){(0,_realNow.realClearTimeout)(releaseTimer);releaseTimer=null}$(n);n.on("connect_error",k);n.on("connect_timeout",y);f(n.connected);if(n.connected){(0,_logger.log)(`${r} Socket already connected on init`,c)}n.on("connect",g);n.on("disconnect",_);return()=>{(0,_logger.log)(`${r} Cleaning up socket event listeners`,c);n.off("connect",g);n.off("disconnect",_);n.off("connect_error",k);n.off("connect_timeout",y);socketUsers--;if(socketUsers===0){releaseTimer=(0,_realNow.realSetTimeout)(()=>{releaseTimer=null;if(socketUsers>0||globalSocketInstance!==n)return;(0,_logger.log)(`${r} No longer used; disconnecting`,c);n.disconnect();globalSocketInstance=null;(0,_syncStatusStore.setSyncStatus)({state:"idle",lastError:null})},RELEASE_GRACE_MS)}}}catch(n){(0,_logger.log)(`${r} Failed to initialize socket: ${n}`,c,"error")}},[t,S,i]);(0,_react.useEffect)(()=>{if(s.current&&s.current.io.opts.query&&t){(0,_logger.log)(`${r} Updating device name in socket connection`,c);s.current.io.opts.query={...s.current.io.opts.query,deviceName:withPage(e,i),deviceId:t,platform:i}}},[e,r,t,i,c]);function v(){if(s.current&&!s.current.connected){(0,_logger.log)(`${r} Manually connecting to socket server`,c);s.current.connect()}}function m(){if(s.current&&s.current.connected){(0,_logger.log)(`${r} Manually disconnecting from socket server`,c);s.current.disconnect()}}const[E,p]=(0,_react.useState)(null);(0,_react.useEffect)(()=>{if(!a)return;return(0,_brokerAccess.watchBrokerAccess)(a,o=>{p(o?a:null);(0,_syncStatusStore.setSyncStatus)(o?{state:"connected",lastError:null}:{state:"retrying",lastError:"Update Buoy Desktop and sign in to enable desktop sync."})})},[a]);return{socket:E===a?a:null,connect:v,disconnect:m,isConnected:U&&E===a&&a!==null}}
@@ -1 +1 @@
1
- "use strict";import{watchBrokerAccess as D}from"./brokerAccess";import{useEffect as $,useRef as O,useState as g}from"react";import{io as M}from"socket.io-client";import{getPlatformSpecificURL as V}from"./utils/platformUtils";import{getDefaultSocketURL as q}from"./utils/getDefaultSocketURL";import{log as r}from"./utils/logger";import{setSyncStatus as f}from"./syncStatusStore";import{pinSocketTimers as b,realClearTimeout as j,realSetTimeout as H}from"./realNow";const J=typeof __DEV__!=="undefined"?__DEV__:false;let l=null;let k="";let B="";let Y="";let m=0;let d=null;const X=2e3;const T=()=>`${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`;export function getInstanceId(){const o=globalThis;if(!o.__buoyInstanceId)o.__buoyInstanceId=T();return o.__buoyInstanceId}export function buildHandshakeQuery(o){const _=i=>i&&Object.fromEntries(Object.entries(i).filter(([S])=>!["BUOY_LICENSE_KEY","BUOY_KEY","EXPO_PUBLIC_BUOY_KEY","VITE_BUOY_KEY","NEXT_PUBLIC_BUOY_KEY","REACT_APP_BUOY_KEY","licenseKey"].includes(S)));return{deviceName:o.deviceName,deviceId:o.persistentDeviceId,platform:o.platform,extraDeviceInfo:JSON.stringify(_(o.extraDeviceInfo)),envVariables:JSON.stringify(_(o.envVariables)),connectionNonce:T(),instanceId:getInstanceId()}}export function useExternalSyncSocket({deviceName:o,socketURL:_,persistentDeviceId:i,extraDeviceInfo:S,envVariables:v,platform:u,enableLogs:e=false}){const s=O(null);const[a,x]=g(null);const[w,E]=g(false);const U=_??q();const y=O(false);const c=`[${o}]`;const p=()=>{r(`${c} Socket connected successfully`,e);y.current=false;E(true);f({state:"connected",lastError:null})};const I=n=>{r(`${c} Socket disconnected. Reason: ${n}`,e);E(false);f({state:"retrying"})};const h=n=>{f({state:"retrying",lastError:n.message});if(!J)return;if(y.current)return;y.current=true;const t=u==="android"&&/localhost|127\.0\.0\.1/.test(k)?`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. `;r(`[buoy] desktop sync: can't reach ${k} (is Buoy Desktop running?). Retrying in the background. ${t}(${n.message})`,e,"warn")};const C=()=>{r(`${c} Socket connection timeout`,e,"error")};$(()=>{if(!i)return;const n=V(U,u);f({targetURL:n,state:"connecting",lastError:null});r(`${c} Platform: ${u}, using URL: ${n}`,e);try{if(l&&(k!==n||B!==i||Y!==u)){l.disconnect();l=null}if(!l){r(`${c} Creating new socket instance to ${n}`,e);try{const N=globalThis;(N.__BUOY_INTERNAL_HOSTS__??=new Set).add(new URL(n).host.toLowerCase())}catch{}l=M(n,{autoConnect:true,query:buildHandshakeQuery({deviceName:o,persistentDeviceId:i,platform:u,extraDeviceInfo:S,envVariables:v}),reconnection:true,reconnectionAttempts:Infinity,reconnectionDelay:1e3,reconnectionDelayMax:5e3,transports:["websocket"]});b(l);k=n;B=i;Y=u}else{r(`${c} Reusing existing socket instance to ${n}`,e)}s.current=l;const t=s.current;m++;if(d){j(d);d=null}x(t);t.on("connect_error",h);t.on("connect_timeout",C);E(t.connected);if(t.connected){r(`${c} Socket already connected on init`,e)}t.on("connect",p);t.on("disconnect",I);return()=>{r(`${c} Cleaning up socket event listeners`,e);t.off("connect",p);t.off("disconnect",I);t.off("connect_error",h);t.off("connect_timeout",C);m--;if(m===0){d=H(()=>{d=null;if(m>0||l!==t)return;r(`${c} No longer used; disconnecting`,e);t.disconnect();l=null;f({state:"idle",lastError:null})},X)}}}catch(t){r(`${c} Failed to initialize socket: ${t}`,e,"error")}},[i,U,u]);$(()=>{if(s.current&&s.current.io.opts.query&&i){r(`${c} Updating device name in socket connection`,e);s.current.io.opts.query={...s.current.io.opts.query,deviceName:o,deviceId:i,platform:u}}},[o,c,i,u,e]);function A(){if(s.current&&!s.current.connected){r(`${c} Manually connecting to socket server`,e);s.current.connect()}}function P(){if(s.current&&s.current.connected){r(`${c} Manually disconnecting from socket server`,e);s.current.disconnect()}}const[R,K]=g(null);$(()=>{if(!a)return;return D(a,n=>{K(n?a:null);f(n?{state:"connected",lastError:null}:{state:"retrying",lastError:"Update Buoy Desktop and sign in to enable desktop sync."})})},[a]);return{socket:R===a?a:null,connect:A,disconnect:P,isConnected:w&&R===a&&a!==null}}
1
+ "use strict";import{watchBrokerAccess as b}from"./brokerAccess";import{useEffect as y,useRef as v,useState as $}from"react";import{io as M}from"socket.io-client";import{getPlatformSpecificURL as V}from"./utils/platformUtils";import{getDefaultSocketURL as q}from"./utils/getDefaultSocketURL";import{log as s}from"./utils/logger";import{setSyncStatus as _}from"./syncStatusStore";import{pinSocketTimers as L,realClearTimeout as H,realSetTimeout as j}from"./realNow";const J=typeof __DEV__!=="undefined"?__DEV__:false;let a=null;let m="";let O="";let T="";let E=0;let k=null;const W=2e3;const B=()=>`${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`;export function getInstanceId(){const t=globalThis;if(!t.__buoyInstanceId)t.__buoyInstanceId=B();return t.__buoyInstanceId}export function buildHandshakeQuery(t){const l=n=>n&&Object.fromEntries(Object.entries(n).filter(([d])=>!["BUOY_LICENSE_KEY","BUOY_KEY","EXPO_PUBLIC_BUOY_KEY","VITE_BUOY_KEY","NEXT_PUBLIC_BUOY_KEY","REACT_APP_BUOY_KEY","licenseKey"].includes(d)));return{deviceName:t.deviceName,deviceId:t.persistentDeviceId,platform:t.platform,extraDeviceInfo:JSON.stringify(l(t.extraDeviceInfo)),envVariables:JSON.stringify(l(t.envVariables)),connectionNonce:B(),instanceId:getInstanceId()}}export function withPage(t,l,n=globalThis.location){if(l!=="web"||!n?.host)return t;const d=n.pathname&&n.pathname!=="/"?n.pathname:"";return`${t} \xB7 ${n.host}${d.length>24?`${d.slice(0,23)}\u2026`:d}`}export function closeWithPage(t,l){t.addEventListener("pagehide",()=>l()?.disconnect());t.addEventListener("pageshow",n=>{if(n.persisted)l()?.connect()})}let Y=false;function X(){const t=globalThis;if(Y||!t.document||typeof t.addEventListener!=="function")return;Y=true;closeWithPage(t,()=>a)}export function useExternalSyncSocket({deviceName:t,socketURL:l,persistentDeviceId:n,extraDeviceInfo:d,envVariables:w,platform:u,enableLogs:c=false}){const i=v(null);const[f,x]=$(null);const[P,h]=$(false);const g=l??q();const S=v(false);const r=`[${t}]`;const p=()=>{s(`${r} Socket connected successfully`,c);S.current=false;h(true);_({state:"connected",lastError:null})};const U=o=>{s(`${r} Socket disconnected. Reason: ${o}`,c);h(false);_({state:"retrying"})};const I=o=>{_({state:"retrying",lastError:o.message});if(!J)return;if(S.current)return;S.current=true;const e=u==="android"&&/localhost|127\.0\.0\.1/.test(m)?`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. `;s(`[buoy] desktop sync: can't reach ${m} (is Buoy Desktop running?). Retrying in the background. ${e}(${o.message})`,c,"warn")};const C=()=>{s(`${r} Socket connection timeout`,c,"error")};y(()=>{if(!n)return;const o=V(g,u);_({targetURL:o,state:"connecting",lastError:null});s(`${r} Platform: ${u}, using URL: ${o}`,c);try{if(a&&(m!==o||O!==n||T!==u)){a.disconnect();a=null}if(!a){s(`${r} Creating new socket instance to ${o}`,c);try{const D=globalThis;(D.__BUOY_INTERNAL_HOSTS__??=new Set).add(new URL(o).host.toLowerCase())}catch{}a=M(o,{autoConnect:true,query:buildHandshakeQuery({deviceName:withPage(t,u),persistentDeviceId:n,platform:u,extraDeviceInfo:d,envVariables:w}),reconnection:true,reconnectionAttempts:Infinity,reconnectionDelay:1e3,reconnectionDelayMax:5e3,transports:["websocket"]});L(a);X();m=o;O=n;T=u}else{s(`${r} Reusing existing socket instance to ${o}`,c)}i.current=a;const e=i.current;E++;if(k){H(k);k=null}x(e);e.on("connect_error",I);e.on("connect_timeout",C);h(e.connected);if(e.connected){s(`${r} Socket already connected on init`,c)}e.on("connect",p);e.on("disconnect",U);return()=>{s(`${r} Cleaning up socket event listeners`,c);e.off("connect",p);e.off("disconnect",U);e.off("connect_error",I);e.off("connect_timeout",C);E--;if(E===0){k=j(()=>{k=null;if(E>0||a!==e)return;s(`${r} No longer used; disconnecting`,c);e.disconnect();a=null;_({state:"idle",lastError:null})},W)}}}catch(e){s(`${r} Failed to initialize socket: ${e}`,c,"error")}},[n,g,u]);y(()=>{if(i.current&&i.current.io.opts.query&&n){s(`${r} Updating device name in socket connection`,c);i.current.io.opts.query={...i.current.io.opts.query,deviceName:withPage(t,u),deviceId:n,platform:u}}},[t,r,n,u,c]);function A(){if(i.current&&!i.current.connected){s(`${r} Manually connecting to socket server`,c);i.current.connect()}}function N(){if(i.current&&i.current.connected){s(`${r} Manually disconnecting from socket server`,c);i.current.disconnect()}}const[R,K]=$(null);y(()=>{if(!f)return;return b(f,o=>{K(o?f:null);_(o?{state:"connected",lastError:null}:{state:"retrying",lastError:"Update Buoy Desktop and sign in to enable desktop sync."})})},[f]);return{socket:R===f?f:null,connect:A,disconnect:N,isConnected:P&&R===f&&f!==null}}
@@ -22,6 +22,29 @@ export interface HandshakeIdentity {
22
22
  * runtime (see above). Pure, so the shape is unit-tested.
23
23
  */
24
24
  export declare function buildHandshakeQuery(identity: HandshakeIdentity): Record<string, string>;
25
+ /**
26
+ * A browser tab's name also says which page it is on ("localhost:3013/games").
27
+ * Tabs of one app, and one app on two dev servers, were told apart only by a
28
+ * 4-character tail. The path is where the tab was when it connected.
29
+ */
30
+ export declare function withPage(deviceName: string, platform: string, page?: {
31
+ host?: string;
32
+ pathname?: string;
33
+ } | undefined): string;
34
+ /**
35
+ * A page that closes or goes away says goodbye at once. Before, the broker
36
+ * kept a closed tab listed as connected until its ping timed out (about a
37
+ * minute), and calls to it failed with "may be crashing".
38
+ */
39
+ type PageTarget = {
40
+ addEventListener: (type: string, listener: (event: {
41
+ persisted?: boolean;
42
+ }) => void) => void;
43
+ };
44
+ export declare function closeWithPage(page: PageTarget, getSocket: () => {
45
+ connect(): unknown;
46
+ disconnect(): unknown;
47
+ } | null): void;
25
48
  /**
26
49
  * Hook that manages a Socket.IO connection for device-to-dashboard communication.
27
50
  *
@@ -36,4 +59,5 @@ export declare function useExternalSyncSocket({ deviceName, socketURL, persisten
36
59
  disconnect: () => void;
37
60
  isConnected: boolean;
38
61
  };
62
+ export {};
39
63
  //# sourceMappingURL=useExternalSyncSocket.d.ts.map
package/lib/web/index.mjs CHANGED
@@ -1 +1 @@
1
- "use client";var oe=(()=>{try{let e=globalThis.Capacitor;if(e&&typeof e.isNativePlatform=="function"&&e.isNativePlatform())return e.DEBUG===true}catch{}return process.env.NODE_ENV!=="production"})();typeof window<"u"&&typeof globalThis.global>"u"&&(globalThis.global=globalThis);import{useAccountAccess as be}from"@buoy-gg/license/web";import{useEffect as ne,useRef as re}from"react";import{LicenseManager as x}from"@buoy-gg/license/web";function p(e,n,o="log"){if(!(!n&&o==="log"))switch(o){case"warn":console.warn("[Buoy]",e);break;case"error":console.error("[Buoy]",e);break;default:console.log("[Buoy]",e)}}var z="__buoyFlushToolSync";function H(){let e=globalThis;return e[z]||(e[z]=new Set),e[z]}function ce(e){H().add(e)}function ie(e){H().delete(e)}function ve(e){try{for(let n of H())try{n(e)}catch{}}catch{}}function se(e,n){let o=0,s=[e],l=new Set;for(;s.length>0;){if(o>n)return{over:true,approxBytes:o};let r=s.pop();if(r==null)o+=4;else if(typeof r=="string")o+=r.length+2;else if(typeof r=="number")o+=8;else if(typeof r=="boolean")o+=5;else if(typeof r=="object"){if(l.has(r))continue;if(l.add(r),Array.isArray(r)){o+=2+r.length;for(let c=0;c<r.length;c++)s.push(r[c])}else{o+=2;for(let c in r)o+=c.length+4,s.push(r[c])}}else o+=4}return{over:o>n,approxBytes:o}}var _e=1;function L(){let e=globalThis.__buoyRealNow;return typeof e=="function"?e():Date.now()}function ae(){return globalThis.__buoyRealTimers??globalThis}function le(e,n){return ae().setTimeout(e,n)}function ue(e){ae().clearTimeout(e)}function Ie(e){let n=globalThis.__buoyWithRealClock;return typeof n=="function"?n(e):e()}var $e=["_resetPingTimeout","_hasPingExpired"];function ke(e){let n=e?.io;if(!n||n.__buoyRealTimers)return;let o=l=>{if(l){l.setTimeoutFn=le,l.clearTimeoutFn=ue;for(let r of $e){let c=l[r];if(typeof c!="function"||c.__buoyReal)continue;let a=function(...y){return Ie(()=>c.apply(this,y))};a.__buoyReal=true,l[r]=a}}};o(n),o(n.engine);let s=n.open;typeof s=="function"&&(n.open=function(...l){let r=s.apply(this,l);return o(this?.engine),r}),n.__buoyRealTimers=true}var P="[ExternalSync]";function Se({socket:e,deviceId:n,getTools:o,watched:s,throttleMs:l=200,enableLogs:r=false}){let c=false,a=false,y=0,g=()=>!c&&a&&e.connected&&x.hasAccountAccess(),m=new Map,_={},b={},S=t=>t==="All"||t===n,f=(t,i)=>{if(g())try{e.emit(t,i)}catch(h){p(`${P} Failed to emit "${t}": ${h}. Payload must be JSON-serializable.`,r,"error")}},B=new Set,$=t=>{if(!g())return;let i=o()[t];if(!i)return;_[t]=L();let h;try{h=i.getSnapshot()}catch{return}let{over:k,approxBytes:U}=se(h,2097152);if(k){B.has(t)||(B.add(t),console.warn("[Buoy]",`${P} "${t}" snapshot \u2248${(U/1e6).toFixed(1)}MB exceeds the ${2097152/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}let j={toolId:t,persistentDeviceId:n,timestamp:_[t],version:i.version,kind:"snapshot",payload:h};f("devtool-sync",j)},R=t=>{if(b[t])return;let i=L()-(_[t]??0);if(i>=l){$(t);return}b[t]=setTimeout(()=>{delete b[t],$(t)},l-i)},O=t=>{e.connected&&m.has(t)&&(b[t]&&(clearTimeout(b[t]),delete b[t]),$(t))};ce(O);let w=new Map,A=(t,i)=>{if(!g())return;let h=o()[t];if(h){if(m.has(t)){if(w.get(t)===i)return;let k=m.get(t);m.set(t,h.subscribe(i==="stream"?()=>R(t):()=>{})),w.set(t,i),k?.(),p(`${P} "${t}" watch mode \u2192 ${i}`,r);return}m.set(t,h.subscribe(i==="stream"?()=>R(t):()=>{})),w.set(t,i),p(`${P} Watching "${t}" (${i})`,r)}},E=t=>{m.get(t)?.(),m.delete(t),w.delete(t),b[t]&&(clearTimeout(b[t]),delete b[t]),p(`${P} Stopped watching "${t}"`,r)},F=()=>{let t={persistentDeviceId:n,protocolVersion:1,tools:Object.entries(o()).map(([i,h])=>({toolId:i,version:h.version,actions:Object.keys(h.actions??{})}))};f("devtool-capabilities",t)},d=t=>{if(!(!g()||!S(t.targetDeviceId)))if(t.watching){let i=t.consolidated===true&&t.stream===false?"capture":"stream";s.set(t.toolId,i),A(t.toolId,i),i==="stream"&&$(t.toolId)}else s.delete(t.toolId),E(t.toolId)},u=t=>{!g()||!S(t.targetDeviceId)||$(t.toolId)},N=async t=>{if(!g()||!S(t.targetDeviceId))return;let i=y,{requestId:h,toolId:k,action:U,params:j}=t,v={requestId:h,toolId:k,action:U,persistentDeviceId:n,timestamp:L(),ok:false},ee=o()[k],te=ee?.actions?.[U];if(!te){v.error=ee?`Unknown action "${U}" on tool "${k}"`:`Unknown tool "${k}"`,f("devtool-action-result",v);return}try{v.result=await te(j),v.ok=true}catch(D){v.error=D instanceof Error?D.message:String(D)}if(!(i!==y||!g())){if(v.ok){let{over:D,approxBytes:me}=se(v.result,8388608);D&&(v.ok=false,v.result=void 0,v.error=`Result \u2248${(me/1e6).toFixed(1)}MB exceeds the ${8388608/1e6}MB sync budget. Narrow the request (e.g. includeValues:false, a limit, or a more specific action).`)}v.timestamp=L(),f("devtool-action-result",v)}},C=()=>{y+=1;for(let i of[...m.keys()])E(i);s.clear();for(let i of Object.values(b))clearTimeout(i);let t=e;t.sendBuffer&&(t.sendBuffer=t.sendBuffer.filter(i=>!["devtool-sync","devtool-capabilities","devtool-action-result","device-license"].includes(String(i.data?.[0]))))},V=()=>{let t=x.getState();if(t.tier!=="pro"&&(t.isInitialized===false||t.isValidating===true)){t.isInitialized===false&&x.initialize().then(()=>{g()&&V()},()=>{});return}e.emit("device-license",{tier:t.tier,isPro:t.isPro?"1":"0"}),F()},Q=t=>{a=t?.protocol===1&&t.ready===true,g()?V():C()},he=x.subscribe(()=>{x.hasAccountAccess()?g()&&V():(e.connected&&e.emit("device-license",{tier:"locked",isPro:"0"}),C())}),K=()=>{a=false,e.emit("buoy:access:request")},Z=()=>{a=false,C()};e.on("buoy:access",Q);for(let[t,i]of s)o()[t]&&A(t,i);return e.on("connect",K),e.on("disconnect",Z),e.on("devtool-watch",d),e.on("request-devtool-state",u),e.on("devtool-action",N),e.connected&&K(),()=>{c=true,he(),e.off("buoy:access",Q),C(),ie(O),e.off("connect",K),e.off("disconnect",Z),e.off("devtool-watch",d),e.off("request-devtool-state",u),e.off("devtool-action",N);for(let t of[...m.keys()])E(t)}}function Te({tools:e,socket:n,deviceId:o,enabled:s=true,throttleMs:l=200,enableLogs:r=false}){let c=be(),a=c.status==="admitted",y=re(e);y.current=e;let g=re(new Map);ne(()=>{if(!n||c.status!=="locked")return;let _=()=>n.emit("device-license",{tier:"locked",isPro:"0"});return n.connected&&_(),n.on("connect",_),()=>{n.off("connect",_)}},[n,c.status]);let m=Object.keys(e).sort().join(",");ne(()=>{if(!n||!o||!s||!a){g.current.clear();return}return Se({socket:n,deviceId:o,getTools:()=>y.current,watched:g.current,throttleMs:l,enableLogs:r})},[n,o,s,a,l,r,m])}function we(e,n){let o=()=>n(false),s=()=>{o(),e.emit("buoy:access:request")},l=r=>{let c=r;n(c?.protocol===1&&c?.ready===true)};return e.on("connect",s),e.on("disconnect",o),e.on("buoy:access",l),o(),e.connected&&s(),()=>{e.off("connect",s),e.off("disconnect",o),e.off("buoy:access",l)}}import{useEffect as J,useRef as fe,useState as W}from"react";import{io as Ee}from"socket.io-client";function Be(e,n){return e}var Re=42831;function Oe(e){return`http://${e??"localhost"}:42831`}function Ne(){return null}function de(){return Oe(typeof location>"u"?null:location.hostname)}var X={targetURL:null,state:"idle",lastError:null,since:0},G=new Set;function T(e){X={...X,...e,since:L()},G.forEach(n=>n())}function Ue(){return X}function De(e){return G.add(e),()=>{G.delete(e)}}var xe=typeof oe<"u"?oe:false,I=null,Y="",pe="",ye="",q=0,M=null,Le=2e3,ge=()=>`${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`;function Pe(){let e=globalThis;return e.__buoyInstanceId||(e.__buoyInstanceId=ge()),e.__buoyInstanceId}function Me(e){let n=o=>o&&Object.fromEntries(Object.entries(o).filter(([s])=>!["BUOY_LICENSE_KEY","BUOY_KEY","EXPO_PUBLIC_BUOY_KEY","VITE_BUOY_KEY","NEXT_PUBLIC_BUOY_KEY","REACT_APP_BUOY_KEY","licenseKey"].includes(s)));return{deviceName:e.deviceName,deviceId:e.persistentDeviceId,platform:e.platform,extraDeviceInfo:JSON.stringify(n(e.extraDeviceInfo)),envVariables:JSON.stringify(n(e.envVariables)),connectionNonce:ge(),instanceId:Pe()}}function Ae({deviceName:e,socketURL:n,persistentDeviceId:o,extraDeviceInfo:s,envVariables:l,platform:r,enableLogs:c=false}){let a=fe(null),[y,g]=W(null),[m,_]=W(false),b=n??de(),S=fe(false),f=`[${e}]`,B=()=>{p(`${f} Socket connected successfully`,c),S.current=false,_(true),T({state:"connected",lastError:null})},$=d=>{p(`${f} Socket disconnected. Reason: ${d}`,c),_(false),T({state:"retrying"})},R=d=>{if(T({state:"retrying",lastError:d.message}),!xe||S.current)return;S.current=true;let u=r==="android"&&/localhost|127\.0\.0\.1/.test(Y)?"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. ";p(`[buoy] desktop sync: can't reach ${Y} (is Buoy Desktop running?). Retrying in the background. ${u}(${d.message})`,c,"warn")},O=()=>{p(`${f} Socket connection timeout`,c,"error")};J(()=>{if(!o)return;let d=Be(b,r);T({targetURL:d,state:"connecting",lastError:null}),p(`${f} Platform: ${r}, using URL: ${d}`,c);try{if(I&&(Y!==d||pe!==o||ye!==r)&&(I.disconnect(),I=null),I)p(`${f} Reusing existing socket instance to ${d}`,c);else{p(`${f} Creating new socket instance to ${d}`,c);try{let N=globalThis;(N.__BUOY_INTERNAL_HOSTS__??(N.__BUOY_INTERNAL_HOSTS__=new Set)).add(new URL(d).host.toLowerCase())}catch{}I=Ee(d,{autoConnect:true,query:Me({deviceName:e,persistentDeviceId:o,platform:r,extraDeviceInfo:s,envVariables:l}),reconnection:true,reconnectionAttempts:1/0,reconnectionDelay:1e3,reconnectionDelayMax:5e3,transports:["websocket"]}),ke(I),Y=d,pe=o,ye=r}a.current=I;let u=a.current;return q++,M&&(ue(M),M=null),g(u),u.on("connect_error",R),u.on("connect_timeout",O),_(u.connected),u.connected&&p(`${f} Socket already connected on init`,c),u.on("connect",B),u.on("disconnect",$),()=>{p(`${f} Cleaning up socket event listeners`,c),u.off("connect",B),u.off("disconnect",$),u.off("connect_error",R),u.off("connect_timeout",O),q--,q===0&&(M=le(()=>{M=null,!(q>0||I!==u)&&(p(`${f} No longer used; disconnecting`,c),u.disconnect(),I=null,T({state:"idle",lastError:null}))},Le))}}catch(u){p(`${f} Failed to initialize socket: ${u}`,c,"error")}},[o,b,r]),J(()=>{a.current&&a.current.io.opts.query&&o&&(p(`${f} Updating device name in socket connection`,c),a.current.io.opts.query={...a.current.io.opts.query,deviceName:e,deviceId:o,platform:r})},[e,f,o,r,c]);function w(){a.current&&!a.current.connected&&(p(`${f} Manually connecting to socket server`,c),a.current.connect())}function A(){a.current&&a.current.connected&&(p(`${f} Manually disconnecting from socket server`,c),a.current.disconnect())}let[E,F]=W(null);return J(()=>{if(y)return we(y,d=>{F(d?y:null),T(d?{state:"connected",lastError:null}:{state:"retrying",lastError:"Update Buoy Desktop and sign in to enable desktop sync."})})},[y]),{socket:E===y?y:null,connect:w,disconnect:A,isConnected:m&&E===y&&y!==null}}export{Re as DEFAULT_BROKER_PORT,_e as SYNC_PROTOCOL_VERSION,ve as flushToolSyncNow,de as getDefaultSocketURL,Ne as getMetroHost,Ue as getSyncStatus,ce as registerToolFlusher,T as setSyncStatus,De as subscribeSyncStatus,ie as unregisterToolFlusher,Te as useExternalSync,Ae as useExternalSyncSocket};
1
+ "use client";var ne=(()=>{try{let e=globalThis.Capacitor;if(e&&typeof e.isNativePlatform=="function"&&e.isNativePlatform())return e.DEBUG===true}catch{}return process.env.NODE_ENV!=="production"})();typeof window<"u"&&typeof globalThis.global>"u"&&(globalThis.global=globalThis);import{useAccountAccess as _e}from"@buoy-gg/license/web";import{useEffect as oe,useRef as re}from"react";import{LicenseManager as L}from"@buoy-gg/license/web";function p(e,o,n="log"){if(!(!o&&n==="log"))switch(n){case"warn":console.warn("[Buoy]",e);break;case"error":console.error("[Buoy]",e);break;default:console.log("[Buoy]",e)}}var z="__buoyFlushToolSync";function H(){let e=globalThis;return e[z]||(e[z]=new Set),e[z]}function ce(e){H().add(e)}function ie(e){H().delete(e)}function $e(e){try{for(let o of H())try{o(e)}catch{}}catch{}}function se(e,o){let n=0,s=[e],l=new Set;for(;s.length>0;){if(n>o)return{over:true,approxBytes:n};let r=s.pop();if(r==null)n+=4;else if(typeof r=="string")n+=r.length+2;else if(typeof r=="number")n+=8;else if(typeof r=="boolean")n+=5;else if(typeof r=="object"){if(l.has(r))continue;if(l.add(r),Array.isArray(r)){n+=2+r.length;for(let c=0;c<r.length;c++)s.push(r[c])}else{n+=2;for(let c in r)n+=c.length+4,s.push(r[c])}}else n+=4}return{over:n>o,approxBytes:n}}var Ie=1;function x(){let e=globalThis.__buoyRealNow;return typeof e=="function"?e():Date.now()}function ae(){return globalThis.__buoyRealTimers??globalThis}function le(e,o){return ae().setTimeout(e,o)}function ue(e){ae().clearTimeout(e)}function Se(e){let o=globalThis.__buoyWithRealClock;return typeof o=="function"?o(e):e()}var ke=["_resetPingTimeout","_hasPingExpired"];function Te(e){let o=e?.io;if(!o||o.__buoyRealTimers)return;let n=l=>{if(l){l.setTimeoutFn=le,l.clearTimeoutFn=ue;for(let r of ke){let c=l[r];if(typeof c!="function"||c.__buoyReal)continue;let a=function(...y){return Se(()=>c.apply(this,y))};a.__buoyReal=true,l[r]=a}}};n(o),n(o.engine);let s=o.open;typeof s=="function"&&(o.open=function(...l){let r=s.apply(this,l);return n(this?.engine),r}),o.__buoyRealTimers=true}var P="[ExternalSync]";function Ee({socket:e,deviceId:o,getTools:n,watched:s,throttleMs:l=200,enableLogs:r=false}){let c=false,a=false,y=0,h=()=>!c&&a&&e.connected&&L.hasAccountAccess(),m=new Map,_={},v={},k=t=>t==="All"||t===o,f=(t,i)=>{if(h())try{e.emit(t,i)}catch(g){p(`${P} Failed to emit "${t}": ${g}. Payload must be JSON-serializable.`,r,"error")}},B=new Set,I=t=>{if(!h())return;let i=n()[t];if(!i)return;_[t]=x();let g;try{g=i.getSnapshot()}catch{return}let{over:S,approxBytes:U}=se(g,2097152);if(S){B.has(t)||(B.add(t),console.warn("[Buoy]",`${P} "${t}" snapshot \u2248${(U/1e6).toFixed(1)}MB exceeds the ${2097152/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}let j={toolId:t,persistentDeviceId:o,timestamp:_[t],version:i.version,kind:"snapshot",payload:g};f("devtool-sync",j)},R=t=>{if(v[t])return;let i=x()-(_[t]??0);if(i>=l){I(t);return}v[t]=setTimeout(()=>{delete v[t],I(t)},l-i)},O=t=>{e.connected&&m.has(t)&&(v[t]&&(clearTimeout(v[t]),delete v[t]),I(t))};ce(O);let E=new Map,A=(t,i)=>{if(!h())return;let g=n()[t];if(g){if(m.has(t)){if(E.get(t)===i)return;let S=m.get(t);m.set(t,g.subscribe(i==="stream"?()=>R(t):()=>{})),E.set(t,i),S?.(),p(`${P} "${t}" watch mode \u2192 ${i}`,r);return}m.set(t,g.subscribe(i==="stream"?()=>R(t):()=>{})),E.set(t,i),p(`${P} Watching "${t}" (${i})`,r)}},w=t=>{m.get(t)?.(),m.delete(t),E.delete(t),v[t]&&(clearTimeout(v[t]),delete v[t]),p(`${P} Stopped watching "${t}"`,r)},F=()=>{let t={persistentDeviceId:o,protocolVersion:1,tools:Object.entries(n()).map(([i,g])=>({toolId:i,version:g.version,actions:Object.keys(g.actions??{})}))};f("devtool-capabilities",t)},d=t=>{if(!(!h()||!k(t.targetDeviceId)))if(t.watching){let i=t.consolidated===true&&t.stream===false?"capture":"stream";s.set(t.toolId,i),A(t.toolId,i),i==="stream"&&I(t.toolId)}else s.delete(t.toolId),w(t.toolId)},u=t=>{!h()||!k(t.targetDeviceId)||I(t.toolId)},N=async t=>{if(!h()||!k(t.targetDeviceId))return;let i=y,{requestId:g,toolId:S,action:U,params:j}=t,b={requestId:g,toolId:S,action:U,persistentDeviceId:o,timestamp:x(),ok:false},ee=n()[S],te=ee?.actions?.[U];if(!te){b.error=ee?`Unknown action "${U}" on tool "${S}"`:`Unknown tool "${S}"`,f("devtool-action-result",b);return}try{b.result=await te(j),b.ok=true}catch(D){b.error=D instanceof Error?D.message:String(D)}if(!(i!==y||!h())){if(b.ok){let{over:D,approxBytes:be}=se(b.result,8388608);D&&(b.ok=false,b.result=void 0,b.error=`Result \u2248${(be/1e6).toFixed(1)}MB exceeds the ${8388608/1e6}MB sync budget. Narrow the request (e.g. includeValues:false, a limit, or a more specific action).`)}b.timestamp=x(),f("devtool-action-result",b)}},Y=()=>{y+=1;for(let i of[...m.keys()])w(i);s.clear();for(let i of Object.values(v))clearTimeout(i);let t=e;t.sendBuffer&&(t.sendBuffer=t.sendBuffer.filter(i=>!["devtool-sync","devtool-capabilities","devtool-action-result","device-license"].includes(String(i.data?.[0]))))},V=()=>{let t=L.getState();if(t.tier!=="pro"&&(t.isInitialized===false||t.isValidating===true)){t.isInitialized===false&&L.initialize().then(()=>{h()&&V()},()=>{});return}e.emit("device-license",{tier:t.tier,isPro:t.isPro?"1":"0"}),F()},Q=t=>{a=t?.protocol===1&&t.ready===true,h()?V():Y()},ve=L.subscribe(()=>{L.hasAccountAccess()?h()&&V():(e.connected&&e.emit("device-license",{tier:"locked",isPro:"0"}),Y())}),K=()=>{a=false,e.emit("buoy:access:request")},Z=()=>{a=false,Y()};e.on("buoy:access",Q);for(let[t,i]of s)n()[t]&&A(t,i);return e.on("connect",K),e.on("disconnect",Z),e.on("devtool-watch",d),e.on("request-devtool-state",u),e.on("devtool-action",N),e.connected&&K(),()=>{c=true,ve(),e.off("buoy:access",Q),Y(),ie(O),e.off("connect",K),e.off("disconnect",Z),e.off("devtool-watch",d),e.off("request-devtool-state",u),e.off("devtool-action",N);for(let t of[...m.keys()])w(t)}}function we({tools:e,socket:o,deviceId:n,enabled:s=true,throttleMs:l=200,enableLogs:r=false}){let c=_e(),a=c.status==="admitted",y=re(e);y.current=e;let h=re(new Map);oe(()=>{if(!o||c.status!=="locked")return;let _=()=>o.emit("device-license",{tier:"locked",isPro:"0"});return o.connected&&_(),o.on("connect",_),()=>{o.off("connect",_)}},[o,c.status]);let m=Object.keys(e).sort().join(",");oe(()=>{if(!o||!n||!s||!a){h.current.clear();return}return Ee({socket:o,deviceId:n,getTools:()=>y.current,watched:h.current,throttleMs:l,enableLogs:r})},[o,n,s,a,l,r,m])}function Be(e,o){let n=()=>o(false),s=()=>{n(),e.emit("buoy:access:request")},l=r=>{let c=r;o(c?.protocol===1&&c?.ready===true)};return e.on("connect",s),e.on("disconnect",n),e.on("buoy:access",l),n(),e.connected&&s(),()=>{e.off("connect",s),e.off("disconnect",n),e.off("buoy:access",l)}}import{useEffect as J,useRef as fe,useState as W}from"react";import{io as Re}from"socket.io-client";function Oe(e,o){return e}var Ne=42831;function Ue(e){return`http://${e??"localhost"}:42831`}function De(){return null}function de(){return Ue(typeof location>"u"?null:location.hostname)}var X={targetURL:null,state:"idle",lastError:null,since:0},G=new Set;function T(e){X={...X,...e,since:x()},G.forEach(o=>o())}function Le(){return X}function xe(e){return G.add(e),()=>{G.delete(e)}}var Pe=typeof ne<"u"?ne:false,$=null,C="",pe="",ye="",q=0,M=null,Me=2e3,he=()=>`${Date.now().toString(36)}-${Math.random().toString(36).slice(2,10)}`;function Ae(){let e=globalThis;return e.__buoyInstanceId||(e.__buoyInstanceId=he()),e.__buoyInstanceId}function Ye(e){let o=n=>n&&Object.fromEntries(Object.entries(n).filter(([s])=>!["BUOY_LICENSE_KEY","BUOY_KEY","EXPO_PUBLIC_BUOY_KEY","VITE_BUOY_KEY","NEXT_PUBLIC_BUOY_KEY","REACT_APP_BUOY_KEY","licenseKey"].includes(s)));return{deviceName:e.deviceName,deviceId:e.persistentDeviceId,platform:e.platform,extraDeviceInfo:JSON.stringify(o(e.extraDeviceInfo)),envVariables:JSON.stringify(o(e.envVariables)),connectionNonce:he(),instanceId:Ae()}}function ge(e,o,n=globalThis.location){if(o!=="web"||!n?.host)return e;let s=n.pathname&&n.pathname!=="/"?n.pathname:"";return`${e} \xB7 ${n.host}${s.length>24?`${s.slice(0,23)}\u2026`:s}`}function Ce(e,o){e.addEventListener("pagehide",()=>o()?.disconnect()),e.addEventListener("pageshow",n=>{n.persisted&&o()?.connect()})}var me=false;function qe(){let e=globalThis;me||!e.document||typeof e.addEventListener!="function"||(me=true,Ce(e,()=>$))}function Fe({deviceName:e,socketURL:o,persistentDeviceId:n,extraDeviceInfo:s,envVariables:l,platform:r,enableLogs:c=false}){let a=fe(null),[y,h]=W(null),[m,_]=W(false),v=o??de(),k=fe(false),f=`[${e}]`,B=()=>{p(`${f} Socket connected successfully`,c),k.current=false,_(true),T({state:"connected",lastError:null})},I=d=>{p(`${f} Socket disconnected. Reason: ${d}`,c),_(false),T({state:"retrying"})},R=d=>{if(T({state:"retrying",lastError:d.message}),!Pe||k.current)return;k.current=true;let u=r==="android"&&/localhost|127\.0\.0\.1/.test(C)?"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. ";p(`[buoy] desktop sync: can't reach ${C} (is Buoy Desktop running?). Retrying in the background. ${u}(${d.message})`,c,"warn")},O=()=>{p(`${f} Socket connection timeout`,c,"error")};J(()=>{if(!n)return;let d=Oe(v,r);T({targetURL:d,state:"connecting",lastError:null}),p(`${f} Platform: ${r}, using URL: ${d}`,c);try{if($&&(C!==d||pe!==n||ye!==r)&&($.disconnect(),$=null),$)p(`${f} Reusing existing socket instance to ${d}`,c);else{p(`${f} Creating new socket instance to ${d}`,c);try{let N=globalThis;(N.__BUOY_INTERNAL_HOSTS__??(N.__BUOY_INTERNAL_HOSTS__=new Set)).add(new URL(d).host.toLowerCase())}catch{}$=Re(d,{autoConnect:true,query:Ye({deviceName:ge(e,r),persistentDeviceId:n,platform:r,extraDeviceInfo:s,envVariables:l}),reconnection:true,reconnectionAttempts:1/0,reconnectionDelay:1e3,reconnectionDelayMax:5e3,transports:["websocket"]}),Te($),qe(),C=d,pe=n,ye=r}a.current=$;let u=a.current;return q++,M&&(ue(M),M=null),h(u),u.on("connect_error",R),u.on("connect_timeout",O),_(u.connected),u.connected&&p(`${f} Socket already connected on init`,c),u.on("connect",B),u.on("disconnect",I),()=>{p(`${f} Cleaning up socket event listeners`,c),u.off("connect",B),u.off("disconnect",I),u.off("connect_error",R),u.off("connect_timeout",O),q--,q===0&&(M=le(()=>{M=null,!(q>0||$!==u)&&(p(`${f} No longer used; disconnecting`,c),u.disconnect(),$=null,T({state:"idle",lastError:null}))},Me))}}catch(u){p(`${f} Failed to initialize socket: ${u}`,c,"error")}},[n,v,r]),J(()=>{a.current&&a.current.io.opts.query&&n&&(p(`${f} Updating device name in socket connection`,c),a.current.io.opts.query={...a.current.io.opts.query,deviceName:ge(e,r),deviceId:n,platform:r})},[e,f,n,r,c]);function E(){a.current&&!a.current.connected&&(p(`${f} Manually connecting to socket server`,c),a.current.connect())}function A(){a.current&&a.current.connected&&(p(`${f} Manually disconnecting from socket server`,c),a.current.disconnect())}let[w,F]=W(null);return J(()=>{if(y)return Be(y,d=>{F(d?y:null),T(d?{state:"connected",lastError:null}:{state:"retrying",lastError:"Update Buoy Desktop and sign in to enable desktop sync."})})},[y]),{socket:w===y?y:null,connect:E,disconnect:A,isConnected:m&&w===y&&y!==null}}export{Ne as DEFAULT_BROKER_PORT,Ie as SYNC_PROTOCOL_VERSION,$e as flushToolSyncNow,de as getDefaultSocketURL,De as getMetroHost,Le as getSyncStatus,ce as registerToolFlusher,T as setSyncStatus,xe as subscribeSyncStatus,ie as unregisterToolFlusher,we as useExternalSync,Fe as useExternalSyncSocket};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buoy-gg/external-sync",
3
- "version": "7.0.75",
3
+ "version": "7.0.77",
4
4
  "description": "Generic device-to-desktop sync for Buoy dev tools",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -34,8 +34,8 @@
34
34
  ],
35
35
  "sideEffects": false,
36
36
  "dependencies": {
37
- "socket.io-client": "^4.8.1",
38
- "@buoy-gg/license": "^7.0.75"
37
+ "socket.io-client": "^4.8.4",
38
+ "@buoy-gg/license": "^7.0.77"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "react": "*"