@buoy-gg/sync-broker 7.0.64 → 7.0.66
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/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -226,11 +226,11 @@ interface DevToolActionMessage {
|
|
|
226
226
|
type BuoyTier = "free" | "pro";
|
|
227
227
|
/**
|
|
228
228
|
* What the broker knows about a device's account: its tier once the device
|
|
229
|
-
* has announced an admitted account
|
|
230
|
-
* a tier
|
|
231
|
-
*
|
|
229
|
+
* has announced an admitted account. New sockets are "checking" until they
|
|
230
|
+
* report a tier or ten seconds pass. A device reports "locked" when its
|
|
231
|
+
* account access ends.
|
|
232
232
|
*/
|
|
233
|
-
type DeviceTier = BuoyTier | "locked";
|
|
233
|
+
type DeviceTier = BuoyTier | "locked" | "checking";
|
|
234
234
|
/**
|
|
235
235
|
* A connected client (device or dashboard) as tracked by the broker.
|
|
236
236
|
* Mirrors the desktop dashboard's `User` type; the dashboard UI carries a few
|
package/dist/index.d.ts
CHANGED
|
@@ -226,11 +226,11 @@ interface DevToolActionMessage {
|
|
|
226
226
|
type BuoyTier = "free" | "pro";
|
|
227
227
|
/**
|
|
228
228
|
* What the broker knows about a device's account: its tier once the device
|
|
229
|
-
* has announced an admitted account
|
|
230
|
-
* a tier
|
|
231
|
-
*
|
|
229
|
+
* has announced an admitted account. New sockets are "checking" until they
|
|
230
|
+
* report a tier or ten seconds pass. A device reports "locked" when its
|
|
231
|
+
* account access ends.
|
|
232
232
|
*/
|
|
233
|
-
type DeviceTier = BuoyTier | "locked";
|
|
233
|
+
type DeviceTier = BuoyTier | "locked" | "checking";
|
|
234
234
|
/**
|
|
235
235
|
* A connected client (device or dashboard) as tracked by the broker.
|
|
236
236
|
* Mirrors the desktop dashboard's `User` type; the dashboard UI carries a few
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf;var __hasOwnProp=Object.prototype.hasOwnProperty;var __export=(o,n)=>{for(var a in n)__defProp(o,a,{get:n[a],enumerable:true})};var __copyProps=(o,n,a,u)=>{if(n&&typeof n==="object"||typeof n==="function"){for(let d of __getOwnPropNames(n))if(!__hasOwnProp.call(o,d)&&d!==a)__defProp(o,d,{get:()=>n[d],enumerable:!(u=__getOwnPropDesc(n,d))||u.enumerable})}return o};var __toESM=(o,n,a)=>(a=o!=null?__create(__getProtoOf(o)):{},__copyProps(n||!o||!o.__esModule?__defProp(a,"default",{value:o,enumerable:true}):a,o));var __toCommonJS=o=>__copyProps(__defProp({},"__esModule",{value:true}),o);var index_exports={};__export(index_exports,{DASHBOARD_AUTH_VERSION:()=>DASHBOARD_AUTH_VERSION,DASHBOARD_TOKEN_REQUIRED:()=>DASHBOARD_TOKEN_REQUIRED,DEFAULT_BROKER_PORT:()=>DEFAULT_BROKER_PORT,ORIGIN_NOT_ALLOWED:()=>ORIGIN_NOT_ALLOWED,SOCKET_CONFIG:()=>SOCKET_CONFIG,SYNC_PROTOCOL_VERSION:()=>SYNC_PROTOCOL_VERSION,brokerTokenPath:()=>brokerTokenPath,buoyConfigDir:()=>buoyConfigDir,createBroker:()=>createBroker,createDashboardToken:()=>createDashboardToken,isLocalOrigin:()=>isLocalOrigin,readBrokerToken:()=>readBrokerToken,socketHandle:()=>socketHandle,tokenMatches:()=>tokenMatches,writeBrokerToken:()=>writeBrokerToken});module.exports=__toCommonJS(index_exports);var SYNC_PROTOCOL_VERSION=1;function tierFromLicensePayload(o){if(o?.tier==="pro"||o?.tier==="free")return o.tier;if(o?.tier==="locked"||o?.tier==="anonymous")return"locked";return o?.isPro==="1"||o?.isPro===true?"pro":"locked"}var toWatchKey=(o,n)=>JSON.stringify([o,n]);var fromWatchKey=o=>JSON.parse(o);var WatchRegistry=class{constructor(){this.byDashboard=new Map;this.streamCounts=new Map;this.captureCounts=new Map}counts(o){return o==="stream"?this.streamCounts:this.captureCounts}bump(o,n,a){const u=this.counts(o);const d=(u.get(n)??0)+a;if(d>0)u.set(n,d);else u.delete(n)}effective(o){if((this.streamCounts.get(o)??0)>0)return"stream";if((this.captureCounts.get(o)??0)>0)return"capture";return null}activeKeys(){return[...new Set([...this.streamCounts.keys(),...this.captureCounts.keys()])]}watch(o,n,a){let u=this.byDashboard.get(o);if(!u){u=new Map;this.byDashboard.set(o,u)}const d=u.get(n);if(d!==a){if(d)this.bump(d,n,-1);this.bump(a,n,1);u.set(n,a)}return this.effective(n)}unwatch(o,n){const a=this.byDashboard.get(o);const u=a?.get(n);if(!a||!u)return{changed:false,mode:this.effective(n)};const d=this.effective(n);a.delete(n);this.bump(u,n,-1);const h=this.effective(n);return{changed:h!==d,mode:h}}releaseDashboard(o){const n=this.byDashboard.get(o);if(!n)return[];this.byDashboard.delete(o);const a=[];for(const[u,d]of n){const h=this.effective(u);this.bump(d,u,-1);const f=this.effective(u);if(f!==h)a.push({key:u,mode:f})}return a}};var import_crypto=require("crypto");var DASHBOARD_AUTH_VERSION=1;var DASHBOARD_TOKEN_REQUIRED="buoy:dashboard-token-required: this broker only accepts dashboards that present its token. Update Buoy Desktop and @buoy-gg/mcp.";var ORIGIN_NOT_ALLOWED="buoy:origin-not-allowed: this site isn't allowed to connect to Buoy. Approve it in Buoy Desktop.";function createDashboardToken(){return(0,import_crypto.randomBytes)(32).toString("hex")}function tokenMatches(o,n){if(typeof n!=="string"||!o||!n)return false;const a=Buffer.from(o);const u=Buffer.from(n);return a.length===u.length&&(0,import_crypto.timingSafeEqual)(a,u)}function isPrivateIPv4(o){const n=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(o);if(!n)return false;const[a,u]=[Number(n[1]),Number(n[2])];return a===127||a===10||a===172&&u>=16&&u<=31||a===192&&u===168||a===169&&u===254}function isLocalHostname(o){if(o==="localhost"||o.endsWith(".localhost"))return true;if(o.endsWith(".local")||o.endsWith(".test"))return true;if(isPrivateIPv4(o))return true;if(o==="::1")return true;const n=/^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(o);if(n)return isPrivateIPv4(n[1]);return/^f[cd][0-9a-f]{0,2}:/.test(o)||/^fe[89ab][0-9a-f]?:/.test(o)}function isLocalOrigin(o){let n;try{n=new URL(o)}catch{return false}if(n.protocol==="file:")return true;const a=n.hostname.replace(/^\[|\]$/g,"").toLowerCase();if(n.protocol==="tauri:"||n.protocol==="capacitor:"||n.protocol==="ionic:"){return a==="localhost"}if(n.protocol!=="http:"&&n.protocol!=="https:")return false;return isLocalHostname(a)}var ORIGIN_ASK_TIMEOUT_MS=12e4;var BROKER_LOG_BUFFER_MAX=200;var MAX_OFFLINE_AGE_MS=24*60*60*1e3;function redactAccountKeys(o){if(!o)return void 0;try{const n=JSON.parse(o);if(!n||typeof n!=="object"||Array.isArray(n))return void 0;for(const a of["BUOY_LICENSE_KEY","BUOY_KEY","EXPO_PUBLIC_BUOY_KEY","VITE_BUOY_KEY","NEXT_PUBLIC_BUOY_KEY","REACT_APP_BUOY_KEY","licenseKey"])delete n[a];return JSON.stringify(n)}catch{return void 0}}function socketHandle({io:o,canUseDesktop:n=()=>false,dashboardToken:a,originPolicy:u}){const d="[SERVER]";let h=[];const f=[];let _=[];const $=new Map;const O=new Map;const A=new Map;const C=new Map;const E=new Map;let S=new WatchRegistry;const k=[];function y(e,t,i){console.log(`${d} ${t}`);const s={ts:Date.now(),level:e,message:t,data:i};k.push(s);if(k.length>BROKER_LOG_BUFFER_MAX){k.shift()}R("broker-log",s)}function M(e){const t=new Set;for(const[i,s]of $){if(s!==e)continue;const v=C.get(i);if(v)t.add(v)}return t.size}function T(){if(!n()){P("all-devices-update",[]);return}for(const t of[...h,...f]){if(t.deviceName==="Dashboard"||!t.isConnected)continue;const i=O.get(t.id)??"locked";t.tier=i==="pro"?"pro":i==="free"?"free":"locked";t.isPro=i==="pro";if(i!=="free"&&i!=="pro"){delete t.envVariables;delete t.extraDeviceInfo}}const e=Date.now();for(const t of f){if(!t.deviceId)continue;const i=M(t.deviceId);const s=i>1;if(s&&!t.collision){y("warn",`${i} apps are registered as "${t.deviceId}" \u2014 actions sent to it land on an arbitrary one. Give each device its own externalSync.deviceId, or leave it unset for a per-install default.`,{deviceId:t.deviceId,instances:i})}else if(!s&&t.collision){y("info",`Device id collision cleared for "${t.deviceId}"`,{deviceId:t.deviceId})}t.collision=s}for(let t=f.length-1;t>=0;t--){const i=f[t];if(!i.isConnected&&i.lastSeenAt&&e-i.lastSeenAt>MAX_OFFLINE_AGE_MS){f.splice(t,1);if(i.deviceId){E.delete(i.deviceId)}}}P("all-devices-update",f.filter(t=>t.deviceName!=="Dashboard").map(({licenseKey:t,...i})=>i))}function j(e,t){const i=h.find(p=>p.id===e);const s=$.get(e);$.delete(e);C.delete(e);O.delete(e);let v;if(s){for(const[p,g]of $){if(g===s)v=p}}if(s&&v){const p=h.findIndex(w=>w.deviceId===s);if(p!==-1&&h[p].id===e){h[p].id=v}const g=f.findIndex(w=>w.deviceId===s);if(g!==-1&&f[g].id===e){f[g].id=v}if(A.get(s)===e){A.set(s,v)}console.log(`${d} Client disconnected - ID: ${e}, Name: ${i?.deviceName||"Unknown"} - Device ${s} still connected via ${v}`)}else{h=h.filter(p=>p.id!==e);console.log(`${d} Client disconnected - ID: ${e}, Name: ${i?.deviceName||"Unknown"} - Removing user record`);if(s){const p=f.findIndex(g=>g.deviceId===s);if(p!==-1){f[p].isConnected=false;f[p].lastDisconnectReason=t;f[p].lastSeenAt=Date.now();A.delete(s)}}}const l=_.includes(e);_=_.filter(p=>p!==e);G(e);const I=Boolean(s&&v);y(l||I?"debug":"warn",I?`Socket closed for ${i?.deviceName||e} (${t??"unknown"}) \u2014 device still connected via another socket`:`Disconnected: ${i?.deviceName||e} (${t??"unknown"})`,{socketId:e,reason:t,wasDashboard:l,stillConnected:I});T();console.log(`${d} Updated users list: ${h.map(p=>p.deviceName).join(", ")}`)}function q({id:e,deviceName:t,deviceId:i,platform:s,extraDeviceInfo:v,envVariables:l,connectionNonce:I},p){if(i){$.set(e,i);A.set(i,e);const r=h.findIndex(b=>b.deviceId===i);const c=f.findIndex(b=>b.deviceId===i);if(r!==-1){y("success",`Reconnected: ${t} (${s||"unknown platform"})`,{socketId:e,deviceId:i});h[r].id=e;if(s){h[r].platform=s}if(v){h[r].extraDeviceInfo=v}if(l){h[r].envVariables=l}h[r].isConnected=true;h[r].connectionNonce=I;if(c!==-1){f[c].id=e;f[c].isConnected=true;f[c].connectionNonce=I;f[c].lastSeenAt=Date.now();if(s){f[c].platform=s}if(v){f[c].extraDeviceInfo=v}if(l){f[c].envVariables=l}}else if(t!=="Dashboard"){f.push({id:e,deviceName:t,deviceId:i,platform:s,isConnected:true,extraDeviceInfo:v,envVariables:l,lastSeenAt:Date.now()})}T();console.log(`${d} Updated users list: ${h.map(b=>b.deviceName).join(", ")}`);return}}const g=r=>{const c=r.match(/(\d+)$/);return c?parseInt(c[1],10):1};let w=0;h.forEach(r=>{if(r.deviceName.startsWith(t)){const c=g(r.deviceName);if(c>=w){w=c}}});if(w>0&&!p){const r=`${t} #${w+1}`;y("warn",`Duplicate device name "${t}" \u2014 renamed to "${r}"`,{socketId:e,deviceId:i});t=r}if(e){const r={id:e,deviceName:t,deviceId:i,platform:s,isConnected:true,extraDeviceInfo:v,envVariables:l,connectionNonce:I,lastSeenAt:Date.now()};h.push(r);if(t!=="Dashboard"&&i){const c=f.findIndex(b=>b.deviceId===i);if(c!==-1){f[c]={...r}}else{f.push({...r})}}y(p?"debug":"info",`Connected: ${t}${s?` (${s})`:""}`,{socketId:e,deviceId:i});if(p&&e){_.push(e);console.log(`${d} Registered dashboard client with ID: ${e}`)}T();console.log(`${d} Updated device list: ${h.map(c=>c.deviceName).join(", ")}`)}}function F(e){return f.find(t=>t.deviceId===e)}function H(e,t){console.log(`${d} Message from ${t}: ${e}`)}function R(e,t){if(!n())return;P(e,t)}function P(e,t){_=_.filter(i=>o.sockets.sockets.has(i));for(const i of _){o.sockets.sockets.get(i)?.emit(e,t)}}function B(e){return _.includes(e)}const U=new Map;function Y(e,t){let i=U.get(e);if(!i){let s;const v=new Promise(l=>{s=setTimeout(()=>l(false),ORIGIN_ASK_TIMEOUT_MS)});i=Promise.race([t(e).catch(()=>false),v]).finally(()=>{clearTimeout(s);U.delete(e)});U.set(e,i)}return i}o.use((e,t)=>{const{auth:i,query:s,headers:v}=e.handshake;if(tokenMatches(a,i?.dashboardToken)){t();return}if(s?.deviceName==="Dashboard"){y("warn","Refused a dashboard connection without the broker token (old Buoy Desktop or @buoy-gg/mcp?)",{address:e.handshake.address});t(new Error(DASHBOARD_TOKEN_REQUIRED));return}const l=typeof v?.origin==="string"?v.origin:void 0;if(!l||isLocalOrigin(l)){t();return}const I=u?.remembered(l);if(I===true){t();return}const p=()=>{y("warn",`Refused a connection from ${l}. Approve the site in Buoy Desktop to let it connect.`,{origin:l});t(new Error(ORIGIN_NOT_ALLOWED))};if(I===false||!u?.ask){p();return}void Y(l,u.ask).then(g=>g?t():p())});function L(e,t,i){if(!n())return;const s=l=>{const I=O.get(l.id);return I==="free"||I==="pro"};const v=F(e)?.deviceName;if(e==="No devices available"){console.log(`${d} Skipping ${i} - No devices available`);return}if(e==="All"){console.log(`${d} Broadcasting ${i} to all devices`);const l=h.filter(I=>I.deviceName!=="Dashboard");console.log(`${d} Sending to ${l.length} devices: ${l.map(I=>I.deviceName).join(", ")}`);l.filter(s).forEach(t)}else{const l=h.find(I=>I.deviceId===e);if(l&&s(l)){console.log(`${d} Sending to target device ${l.deviceName} (${l.deviceId}) targetDeviceId: ${e}`);t(l)}else{console.log(`${d} Target device not found - DeviceId: ${e}, DeviceName: ${v}`)}}}function x(e,t){const[i,s]=fromWatchKey(e);const v=t===null?{toolId:s,targetDeviceId:i,watching:false,consolidated:true}:{toolId:s,targetDeviceId:i,watching:true,stream:t==="stream",consolidated:true};L(i,l=>o.to(l.id).emit("devtool-watch",v),`devtool ${t===null?"unwatch":`watch(${t})`} ${s}`)}function W(e,t){const i=toWatchKey(t.targetDeviceId,t.toolId);if(t.watching){const l=t.stream===false?"capture":"stream";const I=S.watch(e,i,l);console.log(`${d} Dashboard ${e} watching "${t.toolId}" on ${t.targetDeviceId} (${l}, effective ${I})`);x(i,I);return}const{changed:s,mode:v}=S.unwatch(e,i);if(!s)return;console.log(v===null?`${d} Last dashboard stopped watching "${t.toolId}" on ${t.targetDeviceId}`:`${d} "${t.toolId}" on ${t.targetDeviceId} downgraded to ${v}`);x(i,v)}function G(e){for(const{key:t,mode:i}of S.releaseDashboard(e)){x(t,i)}}o.on("connection",e=>{const t=tokenMatches(a,e.handshake.auth?.dashboardToken);if(!t&&e.handshake.query?.deviceName==="Dashboard"){e.emit("buoy:auth-error",{reason:DASHBOARD_TOKEN_REQUIRED});e.disconnect(true);return}const i=()=>e.emit("buoy:access",{protocol:1,ready:n()});i();e.on("buoy:access:request",i);e.use((r,c)=>{if(r[0]==="buoy:access:request"){c();return}if(!n()){i();return}const b=r[0]==="device-license";if(b){const D=r[1]??{};O.set(e.id,tierFromLicensePayload(D))}const m=O.get(e.id)==="free"||O.get(e.id)==="pro";if(b||_.includes(e.id)||m)c();else i()});const{deviceName:s,deviceId:v,platform:l,extraDeviceInfo:I,envVariables:p,connectionNonce:g,instanceId:w}=e.handshake.query;if(typeof w==="string"&&w){C.set(e.id,w)}console.log(`${d} New connection - ID: ${e.id}, Name: ${s||"Unknown Device"}${v?`, DeviceId: ${v}`:""}${l?`, Platform: ${l}`:""}`);q({id:e.id,deviceName:s||"Unknown Device Name",deviceId:v,platform:l,extraDeviceInfo:void 0,envVariables:void 0,connectionNonce:g},t);if(n()&&t){for(const r of E.values()){e.emit("devtool-capabilities",r)}for(const r of k){e.emit("broker-log",r)}}e.on("device-license",r=>{const c=tierFromLicensePayload(r??{});const b=c==="pro";O.set(e.id,c);const m=h.find(N=>N.id===e.id);if(m){m.isPro=b;m.tier=c;delete m.licenseKey}const D=m?.deviceId||$.get(e.id);if(c!=="locked"){for(const N of[...h,...f].filter(K=>K.id===e.id)){N.extraDeviceInfo=redactAccountKeys(I);N.envVariables=redactAccountKeys(p)}}if(D){if(c==="locked")E.delete(D);const N=f.find(K=>K.deviceId===D);if(N){N.isPro=b;N.tier=c;delete N.licenseKey}}y("debug",`device-license from ${m?.deviceName||e.id}: tier=${c}`,{socketId:e.id});T()});e.on("disconnect",r=>j(e.id,r));e.on("message",r=>{H(r,s||"Unknown")});e.on("devtool-capabilities",r=>{if(!r||!Array.isArray(r.tools)||r.persistentDeviceId!==$.get(e.id))return;console.log(`${d} Capabilities from ${r.persistentDeviceId}: ${r.tools.map(c=>`${c.toolId}@v${c.version}`).join(", ")}`);if(r.protocolVersion!==SYNC_PROTOCOL_VERSION){y("warn",`Protocol version mismatch from ${r.persistentDeviceId}: device speaks v${r.protocolVersion}, broker speaks v${SYNC_PROTOCOL_VERSION} \u2014 update @buoy-gg packages or Buoy Desktop`,{deviceId:r.persistentDeviceId})}E.set(r.persistentDeviceId,r);R("devtool-capabilities",r);for(const c of S.activeKeys()){const[b,m]=fromWatchKey(c);if(b!=="All"&&b!==r.persistentDeviceId){continue}const D=S.effective(c);if(!D)continue;e.emit("devtool-watch",{toolId:m,targetDeviceId:b,watching:true,stream:D==="stream",consolidated:true})}});e.on("devtool-sync",r=>{if(!r||r.persistentDeviceId!==$.get(e.id))return;R("devtool-sync",r)});e.on("devtool-action-result",r=>{if(!r||r.persistentDeviceId!==$.get(e.id))return;console.log(`${d} Action result from ${r.persistentDeviceId}: ${r.toolId}.${r.action} -> ${r.ok?"ok":`error: ${r.error}`}`);R("devtool-action-result",r)});e.on("devtool-watch",r=>{if(!B(e.id))return;W(e.id,r)});e.on("request-devtool-state",r=>{if(!B(e.id))return;L(r.targetDeviceId,c=>o.to(c.id).emit("request-devtool-state",r),`devtool state request ${r.toolId}`)});e.on("forget-device",r=>{const c=r?.deviceId;if(!_.includes(e.id))return;if(!c||A.has(c))return;const b=f.find(D=>D.deviceId===c);h=h.filter(D=>D.deviceId!==c);const m=f.findIndex(D=>D.deviceId===c);if(m!==-1){f.splice(m,1)}E.delete(c);for(const[D,N]of $){if(N===c){$.delete(D)}}y("info",`Forgot device: ${b?.deviceName??c}`,{deviceId:c});T()});e.on("devtool-action",r=>{if(!B(e.id))return;console.log(`${d} Devtool action from dashboard: ${r.toolId}.${r.action} -> ${r.targetDeviceId}`);L(r.targetDeviceId,c=>o.to(c.id).emit("devtool-action",r),`devtool action ${r.toolId}.${r.action}`)})});return{refreshAccess(){const e=n();if(!e){O.clear();E.clear();k.length=0;S=new WatchRegistry;for(const t of[...h,...f]){delete t.licenseKey;delete t.envVariables;delete t.extraDeviceInfo}}o.emit("buoy:access",{protocol:1,ready:e});T()}}}var import_http=__toESM(require("http"));var import_socket=require("socket.io");var DEFAULT_BROKER_PORT=42831;var SOCKET_CONFIG={cors:{origin:"*",methods:["GET","POST"]},maxHttpBufferSize:1e8,transports:["websocket","polling"],pingTimeout:3e4,pingInterval:25e3};function createBroker(o={}){const n=o.port??DEFAULT_BROKER_PORT;const a=import_http.default.createServer(($,O)=>{O.writeHead(200,{"content-type":"text/plain"});O.end(`Buoy sync broker running on port ${n}`)});const u=new import_socket.Server(a,{...SOCKET_CONFIG,...o.socketOptions});const d=o.dashboardToken??createDashboardToken();const h=socketHandle({io:u,canUseDesktop:o.canUseDesktop,dashboardToken:d,originPolicy:o.originPolicy});a.on("error",$=>{o.onError?.($)});const f=()=>o.onListening?.(n);if(o.host)a.listen(n,o.host,f);else a.listen(n,f);const _=()=>new Promise($=>{u.close(()=>{a.close(()=>$())})});return{io:u,httpServer:a,port:n,dashboardToken:d,close:_,refreshAccess:h.refreshAccess}}var import_fs=__toESM(require("fs"));var import_os=__toESM(require("os"));var import_path=__toESM(require("path"));function buoyConfigDir(){if(process.platform==="win32"){const o=process.env.APPDATA||import_path.default.join(import_os.default.homedir(),"AppData","Roaming");return import_path.default.join(o,"Buoy")}return import_path.default.join(import_os.default.homedir(),".buoy")}var DEFAULT_PORT=42831;function brokerTokenPath(o=DEFAULT_PORT){const n=o===DEFAULT_PORT?"broker-token":`broker-token-${o}`;return import_path.default.join(buoyConfigDir(),n)}function writeBrokerToken(o,n=brokerTokenPath()){import_fs.default.mkdirSync(import_path.default.dirname(n),{recursive:true,mode:448});const a=`${n}.${process.pid}.tmp`;import_fs.default.writeFileSync(a,o,{mode:384});import_fs.default.renameSync(a,n);if(process.platform!=="win32"){try{import_fs.default.chmodSync(import_path.default.dirname(n),448)}catch{}}}function readBrokerToken(o=brokerTokenPath()){try{const n=import_fs.default.readFileSync(o,"utf8").trim();return n||null}catch{return null}}
|
|
1
|
+
"use strict";var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf;var __hasOwnProp=Object.prototype.hasOwnProperty;var __export=(o,n)=>{for(var a in n)__defProp(o,a,{get:n[a],enumerable:true})};var __copyProps=(o,n,a,u)=>{if(n&&typeof n==="object"||typeof n==="function"){for(let d of __getOwnPropNames(n))if(!__hasOwnProp.call(o,d)&&d!==a)__defProp(o,d,{get:()=>n[d],enumerable:!(u=__getOwnPropDesc(n,d))||u.enumerable})}return o};var __toESM=(o,n,a)=>(a=o!=null?__create(__getProtoOf(o)):{},__copyProps(n||!o||!o.__esModule?__defProp(a,"default",{value:o,enumerable:true}):a,o));var __toCommonJS=o=>__copyProps(__defProp({},"__esModule",{value:true}),o);var index_exports={};__export(index_exports,{DASHBOARD_AUTH_VERSION:()=>DASHBOARD_AUTH_VERSION,DASHBOARD_TOKEN_REQUIRED:()=>DASHBOARD_TOKEN_REQUIRED,DEFAULT_BROKER_PORT:()=>DEFAULT_BROKER_PORT,ORIGIN_NOT_ALLOWED:()=>ORIGIN_NOT_ALLOWED,SOCKET_CONFIG:()=>SOCKET_CONFIG,SYNC_PROTOCOL_VERSION:()=>SYNC_PROTOCOL_VERSION,brokerTokenPath:()=>brokerTokenPath,buoyConfigDir:()=>buoyConfigDir,createBroker:()=>createBroker,createDashboardToken:()=>createDashboardToken,isLocalOrigin:()=>isLocalOrigin,readBrokerToken:()=>readBrokerToken,socketHandle:()=>socketHandle,tokenMatches:()=>tokenMatches,writeBrokerToken:()=>writeBrokerToken});module.exports=__toCommonJS(index_exports);var SYNC_PROTOCOL_VERSION=1;function tierFromLicensePayload(o){if(o?.tier==="pro"||o?.tier==="free")return o.tier;if(o?.tier==="locked"||o?.tier==="anonymous")return"locked";return o?.isPro==="1"||o?.isPro===true?"pro":"locked"}var toWatchKey=(o,n)=>JSON.stringify([o,n]);var fromWatchKey=o=>JSON.parse(o);var WatchRegistry=class{constructor(){this.byDashboard=new Map;this.streamCounts=new Map;this.captureCounts=new Map}counts(o){return o==="stream"?this.streamCounts:this.captureCounts}bump(o,n,a){const u=this.counts(o);const d=(u.get(n)??0)+a;if(d>0)u.set(n,d);else u.delete(n)}effective(o){if((this.streamCounts.get(o)??0)>0)return"stream";if((this.captureCounts.get(o)??0)>0)return"capture";return null}activeKeys(){return[...new Set([...this.streamCounts.keys(),...this.captureCounts.keys()])]}watch(o,n,a){let u=this.byDashboard.get(o);if(!u){u=new Map;this.byDashboard.set(o,u)}const d=u.get(n);if(d!==a){if(d)this.bump(d,n,-1);this.bump(a,n,1);u.set(n,a)}return this.effective(n)}unwatch(o,n){const a=this.byDashboard.get(o);const u=a?.get(n);if(!a||!u)return{changed:false,mode:this.effective(n)};const d=this.effective(n);a.delete(n);this.bump(u,n,-1);const h=this.effective(n);return{changed:h!==d,mode:h}}releaseDashboard(o){const n=this.byDashboard.get(o);if(!n)return[];this.byDashboard.delete(o);const a=[];for(const[u,d]of n){const h=this.effective(u);this.bump(d,u,-1);const f=this.effective(u);if(f!==h)a.push({key:u,mode:f})}return a}};var import_crypto=require("crypto");var DASHBOARD_AUTH_VERSION=1;var DASHBOARD_TOKEN_REQUIRED="buoy:dashboard-token-required: this broker only accepts dashboards that present its token. Update Buoy Desktop and @buoy-gg/mcp.";var ORIGIN_NOT_ALLOWED="buoy:origin-not-allowed: this site isn't allowed to connect to Buoy. Approve it in Buoy Desktop.";function createDashboardToken(){return(0,import_crypto.randomBytes)(32).toString("hex")}function tokenMatches(o,n){if(typeof n!=="string"||!o||!n)return false;const a=Buffer.from(o);const u=Buffer.from(n);return a.length===u.length&&(0,import_crypto.timingSafeEqual)(a,u)}function isPrivateIPv4(o){const n=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(o);if(!n)return false;const[a,u]=[Number(n[1]),Number(n[2])];return a===127||a===10||a===172&&u>=16&&u<=31||a===192&&u===168||a===169&&u===254}function isLocalHostname(o){if(o==="localhost"||o.endsWith(".localhost"))return true;if(o.endsWith(".local")||o.endsWith(".test"))return true;if(isPrivateIPv4(o))return true;if(o==="::1")return true;const n=/^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(o);if(n)return isPrivateIPv4(n[1]);return/^f[cd][0-9a-f]{0,2}:/.test(o)||/^fe[89ab][0-9a-f]?:/.test(o)}function isLocalOrigin(o){let n;try{n=new URL(o)}catch{return false}if(n.protocol==="file:")return true;const a=n.hostname.replace(/^\[|\]$/g,"").toLowerCase();if(n.protocol==="tauri:"||n.protocol==="capacitor:"||n.protocol==="ionic:"){return a==="localhost"}if(n.protocol!=="http:"&&n.protocol!=="https:")return false;return isLocalHostname(a)}var ORIGIN_ASK_TIMEOUT_MS=12e4;var BROKER_LOG_BUFFER_MAX=200;var LICENSE_REPORT_WAIT_MS=1e4;var MAX_OFFLINE_AGE_MS=24*60*60*1e3;function redactAccountKeys(o){if(!o)return void 0;try{const n=JSON.parse(o);if(!n||typeof n!=="object"||Array.isArray(n))return void 0;for(const a of["BUOY_LICENSE_KEY","BUOY_KEY","EXPO_PUBLIC_BUOY_KEY","VITE_BUOY_KEY","NEXT_PUBLIC_BUOY_KEY","REACT_APP_BUOY_KEY","licenseKey"])delete n[a];return JSON.stringify(n)}catch{return void 0}}function socketHandle({io:o,canUseDesktop:n=()=>false,dashboardToken:a,originPolicy:u}){const d="[SERVER]";let h=[];const f=[];let w=[];const $=new Map;const D=new Map;const A=new Map;function C(e){clearTimeout(A.get(e));A.delete(e)}const R=new Map;const B=new Map;const N=new Map;let S=new WatchRegistry;const k=[];function y(e,t,i){console.log(`${d} ${t}`);const s={ts:Date.now(),level:e,message:t,data:i};k.push(s);if(k.length>BROKER_LOG_BUFFER_MAX){k.shift()}P("broker-log",s)}function q(e){const t=new Set;for(const[i,s]of $){if(s!==e)continue;const v=B.get(i);if(v)t.add(v)}return t.size}function E(){if(!n()){L("all-devices-update",[]);return}for(const t of[...h,...f]){if(t.deviceName==="Dashboard"||!t.isConnected)continue;const i=D.get(t.id)??"locked";t.tier=i==="pro"||i==="free"||i==="checking"?i:"locked";t.isPro=i==="pro";if(i!=="free"&&i!=="pro"){delete t.envVariables;delete t.extraDeviceInfo}}const e=Date.now();for(const t of f){if(!t.deviceId)continue;const i=q(t.deviceId);const s=i>1;if(s&&!t.collision){y("warn",`${i} apps are registered as "${t.deviceId}" \u2014 actions sent to it land on an arbitrary one. Give each device its own externalSync.deviceId, or leave it unset for a per-install default.`,{deviceId:t.deviceId,instances:i})}else if(!s&&t.collision){y("info",`Device id collision cleared for "${t.deviceId}"`,{deviceId:t.deviceId})}t.collision=s}for(let t=f.length-1;t>=0;t--){const i=f[t];if(!i.isConnected&&i.lastSeenAt&&e-i.lastSeenAt>MAX_OFFLINE_AGE_MS){f.splice(t,1);if(i.deviceId){N.delete(i.deviceId)}}}L("all-devices-update",f.filter(t=>t.deviceName!=="Dashboard").map(({licenseKey:t,...i})=>i))}function F(e,t){const i=h.find(p=>p.id===e);const s=$.get(e);$.delete(e);B.delete(e);C(e);D.delete(e);let v;if(s){for(const[p,m]of $){if(m===s)v=p}}if(s&&v){const p=h.findIndex(O=>O.deviceId===s);if(p!==-1&&h[p].id===e){h[p].id=v}const m=f.findIndex(O=>O.deviceId===s);if(m!==-1&&f[m].id===e){f[m].id=v}if(R.get(s)===e){R.set(s,v)}console.log(`${d} Client disconnected - ID: ${e}, Name: ${i?.deviceName||"Unknown"} - Device ${s} still connected via ${v}`)}else{h=h.filter(p=>p.id!==e);console.log(`${d} Client disconnected - ID: ${e}, Name: ${i?.deviceName||"Unknown"} - Removing user record`);if(s){const p=f.findIndex(m=>m.deviceId===s);if(p!==-1){f[p].isConnected=false;f[p].lastDisconnectReason=t;f[p].lastSeenAt=Date.now();R.delete(s)}}}const l=w.includes(e);w=w.filter(p=>p!==e);J(e);const I=Boolean(s&&v);y(l||I?"debug":"warn",I?`Socket closed for ${i?.deviceName||e} (${t??"unknown"}) \u2014 device still connected via another socket`:`Disconnected: ${i?.deviceName||e} (${t??"unknown"})`,{socketId:e,reason:t,wasDashboard:l,stillConnected:I});E();console.log(`${d} Updated users list: ${h.map(p=>p.deviceName).join(", ")}`)}function H({id:e,deviceName:t,deviceId:i,platform:s,extraDeviceInfo:v,envVariables:l,connectionNonce:I},p){if(i){$.set(e,i);R.set(i,e);const r=h.findIndex(b=>b.deviceId===i);const c=f.findIndex(b=>b.deviceId===i);if(r!==-1){y("success",`Reconnected: ${t} (${s||"unknown platform"})`,{socketId:e,deviceId:i});h[r].id=e;if(s){h[r].platform=s}if(v){h[r].extraDeviceInfo=v}if(l){h[r].envVariables=l}h[r].isConnected=true;h[r].connectionNonce=I;if(c!==-1){f[c].id=e;f[c].isConnected=true;f[c].connectionNonce=I;f[c].lastSeenAt=Date.now();if(s){f[c].platform=s}if(v){f[c].extraDeviceInfo=v}if(l){f[c].envVariables=l}}else if(t!=="Dashboard"){f.push({id:e,deviceName:t,deviceId:i,platform:s,isConnected:true,extraDeviceInfo:v,envVariables:l,lastSeenAt:Date.now()})}E();console.log(`${d} Updated users list: ${h.map(b=>b.deviceName).join(", ")}`);return}}const m=r=>{const c=r.match(/(\d+)$/);return c?parseInt(c[1],10):1};let O=0;h.forEach(r=>{if(r.deviceName.startsWith(t)){const c=m(r.deviceName);if(c>=O){O=c}}});if(O>0&&!p){const r=`${t} #${O+1}`;y("warn",`Duplicate device name "${t}" \u2014 renamed to "${r}"`,{socketId:e,deviceId:i});t=r}if(e){const r={id:e,deviceName:t,deviceId:i,platform:s,isConnected:true,extraDeviceInfo:v,envVariables:l,connectionNonce:I,lastSeenAt:Date.now()};h.push(r);if(t!=="Dashboard"&&i){const c=f.findIndex(b=>b.deviceId===i);if(c!==-1){f[c]={...r}}else{f.push({...r})}}y(p?"debug":"info",`Connected: ${t}${s?` (${s})`:""}`,{socketId:e,deviceId:i});if(p&&e){w.push(e);console.log(`${d} Registered dashboard client with ID: ${e}`)}E();console.log(`${d} Updated device list: ${h.map(c=>c.deviceName).join(", ")}`)}}function Y(e){return f.find(t=>t.deviceId===e)}function W(e,t){console.log(`${d} Message from ${t}: ${e}`)}function P(e,t){if(!n())return;L(e,t)}function L(e,t){w=w.filter(i=>o.sockets.sockets.has(i));for(const i of w){o.sockets.sockets.get(i)?.emit(e,t)}}function U(e){return w.includes(e)}const M=new Map;function G(e,t){let i=M.get(e);if(!i){let s;const v=new Promise(l=>{s=setTimeout(()=>l(false),ORIGIN_ASK_TIMEOUT_MS)});i=Promise.race([t(e).catch(()=>false),v]).finally(()=>{clearTimeout(s);M.delete(e)});M.set(e,i)}return i}o.use((e,t)=>{const{auth:i,query:s,headers:v}=e.handshake;if(tokenMatches(a,i?.dashboardToken)){t();return}if(s?.deviceName==="Dashboard"){y("warn","Refused a dashboard connection without the broker token (old Buoy Desktop or @buoy-gg/mcp?)",{address:e.handshake.address});t(new Error(DASHBOARD_TOKEN_REQUIRED));return}const l=typeof v?.origin==="string"?v.origin:void 0;if(!l||isLocalOrigin(l)){t();return}const I=u?.remembered(l);if(I===true){t();return}const p=()=>{y("warn",`Refused a connection from ${l}. Approve the site in Buoy Desktop to let it connect.`,{origin:l});t(new Error(ORIGIN_NOT_ALLOWED))};if(I===false||!u?.ask){p();return}void G(l,u.ask).then(m=>m?t():p())});function x(e,t,i){if(!n())return;const s=l=>{const I=D.get(l.id);return I==="free"||I==="pro"};const v=Y(e)?.deviceName;if(e==="No devices available"){console.log(`${d} Skipping ${i} - No devices available`);return}if(e==="All"){console.log(`${d} Broadcasting ${i} to all devices`);const l=h.filter(I=>I.deviceName!=="Dashboard");console.log(`${d} Sending to ${l.length} devices: ${l.map(I=>I.deviceName).join(", ")}`);l.filter(s).forEach(t)}else{const l=h.find(I=>I.deviceId===e);if(l&&s(l)){console.log(`${d} Sending to target device ${l.deviceName} (${l.deviceId}) targetDeviceId: ${e}`);t(l)}else{console.log(`${d} Target device not found - DeviceId: ${e}, DeviceName: ${v}`)}}}function K(e,t){const[i,s]=fromWatchKey(e);const v=t===null?{toolId:s,targetDeviceId:i,watching:false,consolidated:true}:{toolId:s,targetDeviceId:i,watching:true,stream:t==="stream",consolidated:true};x(i,l=>o.to(l.id).emit("devtool-watch",v),`devtool ${t===null?"unwatch":`watch(${t})`} ${s}`)}function V(e,t){const i=toWatchKey(t.targetDeviceId,t.toolId);if(t.watching){const l=t.stream===false?"capture":"stream";const I=S.watch(e,i,l);console.log(`${d} Dashboard ${e} watching "${t.toolId}" on ${t.targetDeviceId} (${l}, effective ${I})`);K(i,I);return}const{changed:s,mode:v}=S.unwatch(e,i);if(!s)return;console.log(v===null?`${d} Last dashboard stopped watching "${t.toolId}" on ${t.targetDeviceId}`:`${d} "${t.toolId}" on ${t.targetDeviceId} downgraded to ${v}`);K(i,v)}function J(e){for(const{key:t,mode:i}of S.releaseDashboard(e)){K(t,i)}}o.on("connection",e=>{const t=tokenMatches(a,e.handshake.auth?.dashboardToken);if(!t&&e.handshake.query?.deviceName==="Dashboard"){e.emit("buoy:auth-error",{reason:DASHBOARD_TOKEN_REQUIRED});e.disconnect(true);return}const i=()=>e.emit("buoy:access",{protocol:1,ready:n()});i();e.on("buoy:access:request",i);e.use((r,c)=>{if(r[0]==="buoy:access:request"){c();return}if(!n()){i();return}const b=r[0]==="device-license";if(b){const g=r[1]??{};C(e.id);D.set(e.id,tierFromLicensePayload(g))}const _=D.get(e.id)==="free"||D.get(e.id)==="pro";if(b||w.includes(e.id)||_)c();else i()});const{deviceName:s,deviceId:v,platform:l,extraDeviceInfo:I,envVariables:p,connectionNonce:m,instanceId:O}=e.handshake.query;if(typeof O==="string"&&O){B.set(e.id,O)}console.log(`${d} New connection - ID: ${e.id}, Name: ${s||"Unknown Device"}${v?`, DeviceId: ${v}`:""}${l?`, Platform: ${l}`:""}`);if(!t&&n()){D.set(e.id,"checking");const r=setTimeout(()=>{A.delete(e.id);if(D.get(e.id)!=="checking")return;D.set(e.id,"locked");E()},LICENSE_REPORT_WAIT_MS);r.unref?.();A.set(e.id,r)}H({id:e.id,deviceName:s||"Unknown Device Name",deviceId:v,platform:l,extraDeviceInfo:void 0,envVariables:void 0,connectionNonce:m},t);if(n()&&t){for(const r of N.values()){e.emit("devtool-capabilities",r)}for(const r of k){e.emit("broker-log",r)}}e.on("device-license",r=>{const c=tierFromLicensePayload(r??{});const b=c==="pro";C(e.id);D.set(e.id,c);const _=h.find(T=>T.id===e.id);if(_){_.isPro=b;_.tier=c;delete _.licenseKey}const g=_?.deviceId||$.get(e.id);if(c!=="locked"){for(const T of[...h,...f].filter(j=>j.id===e.id)){T.extraDeviceInfo=redactAccountKeys(I);T.envVariables=redactAccountKeys(p)}}if(g){if(c==="locked")N.delete(g);const T=f.find(j=>j.deviceId===g);if(T){T.isPro=b;T.tier=c;delete T.licenseKey}}y("debug",`device-license from ${_?.deviceName||e.id}: tier=${c}`,{socketId:e.id});E()});e.on("disconnect",r=>F(e.id,r));e.on("message",r=>{W(r,s||"Unknown")});e.on("devtool-capabilities",r=>{if(!r||!Array.isArray(r.tools)||r.persistentDeviceId!==$.get(e.id))return;console.log(`${d} Capabilities from ${r.persistentDeviceId}: ${r.tools.map(c=>`${c.toolId}@v${c.version}`).join(", ")}`);if(r.protocolVersion!==SYNC_PROTOCOL_VERSION){y("warn",`Protocol version mismatch from ${r.persistentDeviceId}: device speaks v${r.protocolVersion}, broker speaks v${SYNC_PROTOCOL_VERSION} \u2014 update @buoy-gg packages or Buoy Desktop`,{deviceId:r.persistentDeviceId})}N.set(r.persistentDeviceId,r);P("devtool-capabilities",r);for(const c of S.activeKeys()){const[b,_]=fromWatchKey(c);if(b!=="All"&&b!==r.persistentDeviceId){continue}const g=S.effective(c);if(!g)continue;e.emit("devtool-watch",{toolId:_,targetDeviceId:b,watching:true,stream:g==="stream",consolidated:true})}});e.on("devtool-sync",r=>{if(!r||r.persistentDeviceId!==$.get(e.id))return;P("devtool-sync",r)});e.on("devtool-action-result",r=>{if(!r||r.persistentDeviceId!==$.get(e.id))return;console.log(`${d} Action result from ${r.persistentDeviceId}: ${r.toolId}.${r.action} -> ${r.ok?"ok":`error: ${r.error}`}`);P("devtool-action-result",r)});e.on("devtool-watch",r=>{if(!U(e.id))return;V(e.id,r)});e.on("request-devtool-state",r=>{if(!U(e.id))return;x(r.targetDeviceId,c=>o.to(c.id).emit("request-devtool-state",r),`devtool state request ${r.toolId}`)});e.on("forget-device",r=>{const c=r?.deviceId;if(!w.includes(e.id))return;if(!c||R.has(c))return;const b=f.find(g=>g.deviceId===c);h=h.filter(g=>g.deviceId!==c);const _=f.findIndex(g=>g.deviceId===c);if(_!==-1){f.splice(_,1)}N.delete(c);for(const[g,T]of $){if(T===c){$.delete(g)}}y("info",`Forgot device: ${b?.deviceName??c}`,{deviceId:c});E()});e.on("devtool-action",r=>{if(!U(e.id))return;console.log(`${d} Devtool action from dashboard: ${r.toolId}.${r.action} -> ${r.targetDeviceId}`);x(r.targetDeviceId,c=>o.to(c.id).emit("devtool-action",r),`devtool action ${r.toolId}.${r.action}`)})});return{refreshAccess(){const e=n();if(!e){for(const t of A.keys())C(t);D.clear();N.clear();k.length=0;S=new WatchRegistry;for(const t of[...h,...f]){delete t.licenseKey;delete t.envVariables;delete t.extraDeviceInfo}}o.emit("buoy:access",{protocol:1,ready:e});E()}}}var import_http=__toESM(require("http"));var import_socket=require("socket.io");var DEFAULT_BROKER_PORT=42831;var SOCKET_CONFIG={cors:{origin:"*",methods:["GET","POST"]},maxHttpBufferSize:1e8,transports:["websocket","polling"],pingTimeout:3e4,pingInterval:25e3};function createBroker(o={}){const n=o.port??DEFAULT_BROKER_PORT;const a=import_http.default.createServer(($,D)=>{D.writeHead(200,{"content-type":"text/plain"});D.end(`Buoy sync broker running on port ${n}`)});const u=new import_socket.Server(a,{...SOCKET_CONFIG,...o.socketOptions});const d=o.dashboardToken??createDashboardToken();const h=socketHandle({io:u,canUseDesktop:o.canUseDesktop,dashboardToken:d,originPolicy:o.originPolicy});a.on("error",$=>{o.onError?.($)});const f=()=>o.onListening?.(n);if(o.host)a.listen(n,o.host,f);else a.listen(n,f);const w=()=>new Promise($=>{u.close(()=>{a.close(()=>$())})});return{io:u,httpServer:a,port:n,dashboardToken:d,close:w,refreshAccess:h.refreshAccess}}var import_fs=__toESM(require("fs"));var import_os=__toESM(require("os"));var import_path=__toESM(require("path"));function buoyConfigDir(){if(process.platform==="win32"){const o=process.env.APPDATA||import_path.default.join(import_os.default.homedir(),"AppData","Roaming");return import_path.default.join(o,"Buoy")}return import_path.default.join(import_os.default.homedir(),".buoy")}var DEFAULT_PORT=42831;function brokerTokenPath(o=DEFAULT_PORT){const n=o===DEFAULT_PORT?"broker-token":`broker-token-${o}`;return import_path.default.join(buoyConfigDir(),n)}function writeBrokerToken(o,n=brokerTokenPath()){import_fs.default.mkdirSync(import_path.default.dirname(n),{recursive:true,mode:448});const a=`${n}.${process.pid}.tmp`;import_fs.default.writeFileSync(a,o,{mode:384});import_fs.default.renameSync(a,n);if(process.platform!=="win32"){try{import_fs.default.chmodSync(import_path.default.dirname(n),448)}catch{}}}function readBrokerToken(o=brokerTokenPath()){try{const n=import_fs.default.readFileSync(o,"utf8").trim();return n||null}catch{return null}}
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var Y=1;function G(o){if(o?.tier==="pro"||o?.tier==="free")return o.tier;if(o?.tier==="locked"||o?.tier==="anonymous")return"locked";return o?.isPro==="1"||o?.isPro===true?"pro":"locked"}var he=(o,i)=>JSON.stringify([o,i]);var V=o=>JSON.parse(o);var H=class{constructor(){this.byDashboard=new Map;this.streamCounts=new Map;this.captureCounts=new Map}counts(o){return o==="stream"?this.streamCounts:this.captureCounts}bump(o,i,d){const v=this.counts(o);const f=(v.get(i)??0)+d;if(f>0)v.set(i,f);else v.delete(i)}effective(o){if((this.streamCounts.get(o)??0)>0)return"stream";if((this.captureCounts.get(o)??0)>0)return"capture";return null}activeKeys(){return[...new Set([...this.streamCounts.keys(),...this.captureCounts.keys()])]}watch(o,i,d){let v=this.byDashboard.get(o);if(!v){v=new Map;this.byDashboard.set(o,v)}const f=v.get(i);if(f!==d){if(f)this.bump(f,i,-1);this.bump(d,i,1);v.set(i,d)}return this.effective(i)}unwatch(o,i){const d=this.byDashboard.get(o);const v=d?.get(i);if(!d||!v)return{changed:false,mode:this.effective(i)};const f=this.effective(i);d.delete(i);this.bump(v,i,-1);const h=this.effective(i);return{changed:h!==f,mode:h}}releaseDashboard(o){const i=this.byDashboard.get(o);if(!i)return[];this.byDashboard.delete(o);const d=[];for(const[v,f]of i){const h=this.effective(v);this.bump(f,v,-1);const l=this.effective(v);if(l!==h)d.push({key:v,mode:l})}return d}};import{randomBytes as pe,timingSafeEqual as $e}from"crypto";var Ie=1;var q="buoy:dashboard-token-required: this broker only accepts dashboards that present its token. Update Buoy Desktop and @buoy-gg/mcp.";var X="buoy:origin-not-allowed: this site isn't allowed to connect to Buoy. Approve it in Buoy Desktop.";function J(){return pe(32).toString("hex")}function W(o,i){if(typeof i!=="string"||!o||!i)return false;const d=Buffer.from(o);const v=Buffer.from(i);return d.length===v.length&&$e(d,v)}function z(o){const i=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(o);if(!i)return false;const[d,v]=[Number(i[1]),Number(i[2])];return d===127||d===10||d===172&&v>=16&&v<=31||d===192&&v===168||d===169&&v===254}function me(o){if(o==="localhost"||o.endsWith(".localhost"))return true;if(o.endsWith(".local")||o.endsWith(".test"))return true;if(z(o))return true;if(o==="::1")return true;const i=/^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(o);if(i)return z(i[1]);return/^f[cd][0-9a-f]{0,2}:/.test(o)||/^fe[89ab][0-9a-f]?:/.test(o)}function Q(o){let i;try{i=new URL(o)}catch{return false}if(i.protocol==="file:")return true;const d=i.hostname.replace(/^\[|\]$/g,"").toLowerCase();if(i.protocol==="tauri:"||i.protocol==="capacitor:"||i.protocol==="ionic:"){return d==="localhost"}if(i.protocol!=="http:"&&i.protocol!=="https:")return false;return me(d)}var be=12e4;var ge=200;var De=24*60*60*1e3;function Z(o){if(!o)return void 0;try{const i=JSON.parse(o);if(!i||typeof i!=="object"||Array.isArray(i))return void 0;for(const d of["BUOY_LICENSE_KEY","BUOY_KEY","EXPO_PUBLIC_BUOY_KEY","VITE_BUOY_KEY","NEXT_PUBLIC_BUOY_KEY","REACT_APP_BUOY_KEY","licenseKey"])delete i[d];return JSON.stringify(i)}catch{return void 0}}function ee({io:o,canUseDesktop:i=()=>false,dashboardToken:d,originPolicy:v}){const f="[SERVER]";let h=[];const l=[];let w=[];const m=new Map;const S=new Map;const T=new Map;const P=new Map;const O=new Map;let E=new H;const B=[];function N(e,t,r){console.log(`${f} ${t}`);const s={ts:Date.now(),level:e,message:t,data:r};B.push(s);if(B.length>ge){B.shift()}R("broker-log",s)}function ce(e){const t=new Set;for(const[r,s]of m){if(s!==e)continue;const u=P.get(r);if(u)t.add(u)}return t.size}function _(){if(!i()){U("all-devices-update",[]);return}for(const t of[...h,...l]){if(t.deviceName==="Dashboard"||!t.isConnected)continue;const r=S.get(t.id)??"locked";t.tier=r==="pro"?"pro":r==="free"?"free":"locked";t.isPro=r==="pro";if(r!=="free"&&r!=="pro"){delete t.envVariables;delete t.extraDeviceInfo}}const e=Date.now();for(const t of l){if(!t.deviceId)continue;const r=ce(t.deviceId);const s=r>1;if(s&&!t.collision){N("warn",`${r} apps are registered as "${t.deviceId}" \u2014 actions sent to it land on an arbitrary one. Give each device its own externalSync.deviceId, or leave it unset for a per-install default.`,{deviceId:t.deviceId,instances:r})}else if(!s&&t.collision){N("info",`Device id collision cleared for "${t.deviceId}"`,{deviceId:t.deviceId})}t.collision=s}for(let t=l.length-1;t>=0;t--){const r=l[t];if(!r.isConnected&&r.lastSeenAt&&e-r.lastSeenAt>De){l.splice(t,1);if(r.deviceId){O.delete(r.deviceId)}}}U("all-devices-update",l.filter(t=>t.deviceName!=="Dashboard").map(({licenseKey:t,...r})=>r))}function se(e,t){const r=h.find(p=>p.id===e);const s=m.get(e);m.delete(e);P.delete(e);S.delete(e);let u;if(s){for(const[p,g]of m){if(g===s)u=p}}if(s&&u){const p=h.findIndex(y=>y.deviceId===s);if(p!==-1&&h[p].id===e){h[p].id=u}const g=l.findIndex(y=>y.deviceId===s);if(g!==-1&&l[g].id===e){l[g].id=u}if(T.get(s)===e){T.set(s,u)}console.log(`${f} Client disconnected - ID: ${e}, Name: ${r?.deviceName||"Unknown"} - Device ${s} still connected via ${u}`)}else{h=h.filter(p=>p.id!==e);console.log(`${f} Client disconnected - ID: ${e}, Name: ${r?.deviceName||"Unknown"} - Removing user record`);if(s){const p=l.findIndex(g=>g.deviceId===s);if(p!==-1){l[p].isConnected=false;l[p].lastDisconnectReason=t;l[p].lastSeenAt=Date.now();T.delete(s)}}}const a=w.includes(e);w=w.filter(p=>p!==e);ve(e);const $=Boolean(s&&u);N(a||$?"debug":"warn",$?`Socket closed for ${r?.deviceName||e} (${t??"unknown"}) \u2014 device still connected via another socket`:`Disconnected: ${r?.deviceName||e} (${t??"unknown"})`,{socketId:e,reason:t,wasDashboard:a,stillConnected:$});_();console.log(`${f} Updated users list: ${h.map(p=>p.deviceName).join(", ")}`)}function de({id:e,deviceName:t,deviceId:r,platform:s,extraDeviceInfo:u,envVariables:a,connectionNonce:$},p){if(r){m.set(e,r);T.set(r,e);const n=h.findIndex(I=>I.deviceId===r);const c=l.findIndex(I=>I.deviceId===r);if(n!==-1){N("success",`Reconnected: ${t} (${s||"unknown platform"})`,{socketId:e,deviceId:r});h[n].id=e;if(s){h[n].platform=s}if(u){h[n].extraDeviceInfo=u}if(a){h[n].envVariables=a}h[n].isConnected=true;h[n].connectionNonce=$;if(c!==-1){l[c].id=e;l[c].isConnected=true;l[c].connectionNonce=$;l[c].lastSeenAt=Date.now();if(s){l[c].platform=s}if(u){l[c].extraDeviceInfo=u}if(a){l[c].envVariables=a}}else if(t!=="Dashboard"){l.push({id:e,deviceName:t,deviceId:r,platform:s,isConnected:true,extraDeviceInfo:u,envVariables:a,lastSeenAt:Date.now()})}_();console.log(`${f} Updated users list: ${h.map(I=>I.deviceName).join(", ")}`);return}}const g=n=>{const c=n.match(/(\d+)$/);return c?parseInt(c[1],10):1};let y=0;h.forEach(n=>{if(n.deviceName.startsWith(t)){const c=g(n.deviceName);if(c>=y){y=c}}});if(y>0&&!p){const n=`${t} #${y+1}`;N("warn",`Duplicate device name "${t}" \u2014 renamed to "${n}"`,{socketId:e,deviceId:r});t=n}if(e){const n={id:e,deviceName:t,deviceId:r,platform:s,isConnected:true,extraDeviceInfo:u,envVariables:a,connectionNonce:$,lastSeenAt:Date.now()};h.push(n);if(t!=="Dashboard"&&r){const c=l.findIndex(I=>I.deviceId===r);if(c!==-1){l[c]={...n}}else{l.push({...n})}}N(p?"debug":"info",`Connected: ${t}${s?` (${s})`:""}`,{socketId:e,deviceId:r});if(p&&e){w.push(e);console.log(`${f} Registered dashboard client with ID: ${e}`)}_();console.log(`${f} Updated device list: ${h.map(c=>c.deviceName).join(", ")}`)}}function ae(e){return l.find(t=>t.deviceId===e)}function le(e,t){console.log(`${f} Message from ${t}: ${e}`)}function R(e,t){if(!i())return;U(e,t)}function U(e,t){w=w.filter(r=>o.sockets.sockets.has(r));for(const r of w){o.sockets.sockets.get(r)?.emit(e,t)}}function x(e){return w.includes(e)}const K=new Map;function fe(e,t){let r=K.get(e);if(!r){let s;const u=new Promise(a=>{s=setTimeout(()=>a(false),be)});r=Promise.race([t(e).catch(()=>false),u]).finally(()=>{clearTimeout(s);K.delete(e)});K.set(e,r)}return r}o.use((e,t)=>{const{auth:r,query:s,headers:u}=e.handshake;if(W(d,r?.dashboardToken)){t();return}if(s?.deviceName==="Dashboard"){N("warn","Refused a dashboard connection without the broker token (old Buoy Desktop or @buoy-gg/mcp?)",{address:e.handshake.address});t(new Error(q));return}const a=typeof u?.origin==="string"?u.origin:void 0;if(!a||Q(a)){t();return}const $=v?.remembered(a);if($===true){t();return}const p=()=>{N("warn",`Refused a connection from ${a}. Approve the site in Buoy Desktop to let it connect.`,{origin:a});t(new Error(X))};if($===false||!v?.ask){p();return}void fe(a,v.ask).then(g=>g?t():p())});function L(e,t,r){if(!i())return;const s=a=>{const $=S.get(a.id);return $==="free"||$==="pro"};const u=ae(e)?.deviceName;if(e==="No devices available"){console.log(`${f} Skipping ${r} - No devices available`);return}if(e==="All"){console.log(`${f} Broadcasting ${r} to all devices`);const a=h.filter($=>$.deviceName!=="Dashboard");console.log(`${f} Sending to ${a.length} devices: ${a.map($=>$.deviceName).join(", ")}`);a.filter(s).forEach(t)}else{const a=h.find($=>$.deviceId===e);if(a&&s(a)){console.log(`${f} Sending to target device ${a.deviceName} (${a.deviceId}) targetDeviceId: ${e}`);t(a)}else{console.log(`${f} Target device not found - DeviceId: ${e}, DeviceName: ${u}`)}}}function M(e,t){const[r,s]=V(e);const u=t===null?{toolId:s,targetDeviceId:r,watching:false,consolidated:true}:{toolId:s,targetDeviceId:r,watching:true,stream:t==="stream",consolidated:true};L(r,a=>o.to(a.id).emit("devtool-watch",u),`devtool ${t===null?"unwatch":`watch(${t})`} ${s}`)}function ue(e,t){const r=he(t.targetDeviceId,t.toolId);if(t.watching){const a=t.stream===false?"capture":"stream";const $=E.watch(e,r,a);console.log(`${f} Dashboard ${e} watching "${t.toolId}" on ${t.targetDeviceId} (${a}, effective ${$})`);M(r,$);return}const{changed:s,mode:u}=E.unwatch(e,r);if(!s)return;console.log(u===null?`${f} Last dashboard stopped watching "${t.toolId}" on ${t.targetDeviceId}`:`${f} "${t.toolId}" on ${t.targetDeviceId} downgraded to ${u}`);M(r,u)}function ve(e){for(const{key:t,mode:r}of E.releaseDashboard(e)){M(t,r)}}o.on("connection",e=>{const t=W(d,e.handshake.auth?.dashboardToken);if(!t&&e.handshake.query?.deviceName==="Dashboard"){e.emit("buoy:auth-error",{reason:q});e.disconnect(true);return}const r=()=>e.emit("buoy:access",{protocol:1,ready:i()});r();e.on("buoy:access:request",r);e.use((n,c)=>{if(n[0]==="buoy:access:request"){c();return}if(!i()){r();return}const I=n[0]==="device-license";if(I){const b=n[1]??{};S.set(e.id,G(b))}const D=S.get(e.id)==="free"||S.get(e.id)==="pro";if(I||w.includes(e.id)||D)c();else r()});const{deviceName:s,deviceId:u,platform:a,extraDeviceInfo:$,envVariables:p,connectionNonce:g,instanceId:y}=e.handshake.query;if(typeof y==="string"&&y){P.set(e.id,y)}console.log(`${f} New connection - ID: ${e.id}, Name: ${s||"Unknown Device"}${u?`, DeviceId: ${u}`:""}${a?`, Platform: ${a}`:""}`);de({id:e.id,deviceName:s||"Unknown Device Name",deviceId:u,platform:a,extraDeviceInfo:void 0,envVariables:void 0,connectionNonce:g},t);if(i()&&t){for(const n of O.values()){e.emit("devtool-capabilities",n)}for(const n of B){e.emit("broker-log",n)}}e.on("device-license",n=>{const c=G(n??{});const I=c==="pro";S.set(e.id,c);const D=h.find(A=>A.id===e.id);if(D){D.isPro=I;D.tier=c;delete D.licenseKey}const b=D?.deviceId||m.get(e.id);if(c!=="locked"){for(const A of[...h,...l].filter(F=>F.id===e.id)){A.extraDeviceInfo=Z($);A.envVariables=Z(p)}}if(b){if(c==="locked")O.delete(b);const A=l.find(F=>F.deviceId===b);if(A){A.isPro=I;A.tier=c;delete A.licenseKey}}N("debug",`device-license from ${D?.deviceName||e.id}: tier=${c}`,{socketId:e.id});_()});e.on("disconnect",n=>se(e.id,n));e.on("message",n=>{le(n,s||"Unknown")});e.on("devtool-capabilities",n=>{if(!n||!Array.isArray(n.tools)||n.persistentDeviceId!==m.get(e.id))return;console.log(`${f} Capabilities from ${n.persistentDeviceId}: ${n.tools.map(c=>`${c.toolId}@v${c.version}`).join(", ")}`);if(n.protocolVersion!==Y){N("warn",`Protocol version mismatch from ${n.persistentDeviceId}: device speaks v${n.protocolVersion}, broker speaks v${Y} \u2014 update @buoy-gg packages or Buoy Desktop`,{deviceId:n.persistentDeviceId})}O.set(n.persistentDeviceId,n);R("devtool-capabilities",n);for(const c of E.activeKeys()){const[I,D]=V(c);if(I!=="All"&&I!==n.persistentDeviceId){continue}const b=E.effective(c);if(!b)continue;e.emit("devtool-watch",{toolId:D,targetDeviceId:I,watching:true,stream:b==="stream",consolidated:true})}});e.on("devtool-sync",n=>{if(!n||n.persistentDeviceId!==m.get(e.id))return;R("devtool-sync",n)});e.on("devtool-action-result",n=>{if(!n||n.persistentDeviceId!==m.get(e.id))return;console.log(`${f} Action result from ${n.persistentDeviceId}: ${n.toolId}.${n.action} -> ${n.ok?"ok":`error: ${n.error}`}`);R("devtool-action-result",n)});e.on("devtool-watch",n=>{if(!x(e.id))return;ue(e.id,n)});e.on("request-devtool-state",n=>{if(!x(e.id))return;L(n.targetDeviceId,c=>o.to(c.id).emit("request-devtool-state",n),`devtool state request ${n.toolId}`)});e.on("forget-device",n=>{const c=n?.deviceId;if(!w.includes(e.id))return;if(!c||T.has(c))return;const I=l.find(b=>b.deviceId===c);h=h.filter(b=>b.deviceId!==c);const D=l.findIndex(b=>b.deviceId===c);if(D!==-1){l.splice(D,1)}O.delete(c);for(const[b,A]of m){if(A===c){m.delete(b)}}N("info",`Forgot device: ${I?.deviceName??c}`,{deviceId:c});_()});e.on("devtool-action",n=>{if(!x(e.id))return;console.log(`${f} Devtool action from dashboard: ${n.toolId}.${n.action} -> ${n.targetDeviceId}`);L(n.targetDeviceId,c=>o.to(c.id).emit("devtool-action",n),`devtool action ${n.toolId}.${n.action}`)})});return{refreshAccess(){const e=i();if(!e){S.clear();O.clear();B.length=0;E=new H;for(const t of[...h,...l]){delete t.licenseKey;delete t.envVariables;delete t.extraDeviceInfo}}o.emit("buoy:access",{protocol:1,ready:e});_()}}}import we from"http";import{Server as ye}from"socket.io";var te=42831;var ne={cors:{origin:"*",methods:["GET","POST"]},maxHttpBufferSize:1e8,transports:["websocket","polling"],pingTimeout:3e4,pingInterval:25e3};function Se(o={}){const i=o.port??te;const d=we.createServer((m,S)=>{S.writeHead(200,{"content-type":"text/plain"});S.end(`Buoy sync broker running on port ${i}`)});const v=new ye(d,{...ne,...o.socketOptions});const f=o.dashboardToken??J();const h=ee({io:v,canUseDesktop:o.canUseDesktop,dashboardToken:f,originPolicy:o.originPolicy});d.on("error",m=>{o.onError?.(m)});const l=()=>o.onListening?.(i);if(o.host)d.listen(i,o.host,l);else d.listen(i,l);const w=()=>new Promise(m=>{v.close(()=>{d.close(()=>m())})});return{io:v,httpServer:d,port:i,dashboardToken:f,close:w,refreshAccess:h.refreshAccess}}import k from"fs";import oe from"os";import C from"path";function ie(){if(process.platform==="win32"){const o=process.env.APPDATA||C.join(oe.homedir(),"AppData","Roaming");return C.join(o,"Buoy")}return C.join(oe.homedir(),".buoy")}var re=42831;function j(o=re){const i=o===re?"broker-token":`broker-token-${o}`;return C.join(ie(),i)}function Ne(o,i=j()){k.mkdirSync(C.dirname(i),{recursive:true,mode:448});const d=`${i}.${process.pid}.tmp`;k.writeFileSync(d,o,{mode:384});k.renameSync(d,i);if(process.platform!=="win32"){try{k.chmodSync(C.dirname(i),448)}catch{}}}function Ae(o=j()){try{const i=k.readFileSync(o,"utf8").trim();return i||null}catch{return null}}export{Ie as DASHBOARD_AUTH_VERSION,q as DASHBOARD_TOKEN_REQUIRED,te as DEFAULT_BROKER_PORT,X as ORIGIN_NOT_ALLOWED,ne as SOCKET_CONFIG,Y as SYNC_PROTOCOL_VERSION,j as brokerTokenPath,ie as buoyConfigDir,Se as createBroker,J as createDashboardToken,Q as isLocalOrigin,Ae as readBrokerToken,ee as socketHandle,W as tokenMatches,Ne as writeBrokerToken};
|
|
1
|
+
var q=1;function H(o){if(o?.tier==="pro"||o?.tier==="free")return o.tier;if(o?.tier==="locked"||o?.tier==="anonymous")return"locked";return o?.isPro==="1"||o?.isPro===true?"pro":"locked"}var $e=(o,i)=>JSON.stringify([o,i]);var X=o=>JSON.parse(o);var J=class{constructor(){this.byDashboard=new Map;this.streamCounts=new Map;this.captureCounts=new Map}counts(o){return o==="stream"?this.streamCounts:this.captureCounts}bump(o,i,d){const v=this.counts(o);const f=(v.get(i)??0)+d;if(f>0)v.set(i,f);else v.delete(i)}effective(o){if((this.streamCounts.get(o)??0)>0)return"stream";if((this.captureCounts.get(o)??0)>0)return"capture";return null}activeKeys(){return[...new Set([...this.streamCounts.keys(),...this.captureCounts.keys()])]}watch(o,i,d){let v=this.byDashboard.get(o);if(!v){v=new Map;this.byDashboard.set(o,v)}const f=v.get(i);if(f!==d){if(f)this.bump(f,i,-1);this.bump(d,i,1);v.set(i,d)}return this.effective(i)}unwatch(o,i){const d=this.byDashboard.get(o);const v=d?.get(i);if(!d||!v)return{changed:false,mode:this.effective(i)};const f=this.effective(i);d.delete(i);this.bump(v,i,-1);const h=this.effective(i);return{changed:h!==f,mode:h}}releaseDashboard(o){const i=this.byDashboard.get(o);if(!i)return[];this.byDashboard.delete(o);const d=[];for(const[v,f]of i){const h=this.effective(v);this.bump(f,v,-1);const l=this.effective(v);if(l!==h)d.push({key:v,mode:l})}return d}};import{randomBytes as me,timingSafeEqual as Ie}from"crypto";var be=1;var j="buoy:dashboard-token-required: this broker only accepts dashboards that present its token. Update Buoy Desktop and @buoy-gg/mcp.";var z="buoy:origin-not-allowed: this site isn't allowed to connect to Buoy. Approve it in Buoy Desktop.";function Q(){return me(32).toString("hex")}function G(o,i){if(typeof i!=="string"||!o||!i)return false;const d=Buffer.from(o);const v=Buffer.from(i);return d.length===v.length&&Ie(d,v)}function Z(o){const i=/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(o);if(!i)return false;const[d,v]=[Number(i[1]),Number(i[2])];return d===127||d===10||d===172&&v>=16&&v<=31||d===192&&v===168||d===169&&v===254}function ge(o){if(o==="localhost"||o.endsWith(".localhost"))return true;if(o.endsWith(".local")||o.endsWith(".test"))return true;if(Z(o))return true;if(o==="::1")return true;const i=/^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(o);if(i)return Z(i[1]);return/^f[cd][0-9a-f]{0,2}:/.test(o)||/^fe[89ab][0-9a-f]?:/.test(o)}function ee(o){let i;try{i=new URL(o)}catch{return false}if(i.protocol==="file:")return true;const d=i.hostname.replace(/^\[|\]$/g,"").toLowerCase();if(i.protocol==="tauri:"||i.protocol==="capacitor:"||i.protocol==="ionic:"){return d==="localhost"}if(i.protocol!=="http:"&&i.protocol!=="https:")return false;return ge(d)}var De=12e4;var we=200;var ye=1e4;var Se=24*60*60*1e3;function te(o){if(!o)return void 0;try{const i=JSON.parse(o);if(!i||typeof i!=="object"||Array.isArray(i))return void 0;for(const d of["BUOY_LICENSE_KEY","BUOY_KEY","EXPO_PUBLIC_BUOY_KEY","VITE_BUOY_KEY","NEXT_PUBLIC_BUOY_KEY","REACT_APP_BUOY_KEY","licenseKey"])delete i[d];return JSON.stringify(i)}catch{return void 0}}function ne({io:o,canUseDesktop:i=()=>false,dashboardToken:d,originPolicy:v}){const f="[SERVER]";let h=[];const l=[];let y=[];const I=new Map;const g=new Map;const C=new Map;function k(e){clearTimeout(C.get(e));C.delete(e)}const B=new Map;const x=new Map;const O=new Map;let T=new J;const R=[];function N(e,t,r){console.log(`${f} ${t}`);const s={ts:Date.now(),level:e,message:t,data:r};R.push(s);if(R.length>we){R.shift()}U("broker-log",s)}function de(e){const t=new Set;for(const[r,s]of I){if(s!==e)continue;const u=x.get(r);if(u)t.add(u)}return t.size}function E(){if(!i()){L("all-devices-update",[]);return}for(const t of[...h,...l]){if(t.deviceName==="Dashboard"||!t.isConnected)continue;const r=g.get(t.id)??"locked";t.tier=r==="pro"||r==="free"||r==="checking"?r:"locked";t.isPro=r==="pro";if(r!=="free"&&r!=="pro"){delete t.envVariables;delete t.extraDeviceInfo}}const e=Date.now();for(const t of l){if(!t.deviceId)continue;const r=de(t.deviceId);const s=r>1;if(s&&!t.collision){N("warn",`${r} apps are registered as "${t.deviceId}" \u2014 actions sent to it land on an arbitrary one. Give each device its own externalSync.deviceId, or leave it unset for a per-install default.`,{deviceId:t.deviceId,instances:r})}else if(!s&&t.collision){N("info",`Device id collision cleared for "${t.deviceId}"`,{deviceId:t.deviceId})}t.collision=s}for(let t=l.length-1;t>=0;t--){const r=l[t];if(!r.isConnected&&r.lastSeenAt&&e-r.lastSeenAt>Se){l.splice(t,1);if(r.deviceId){O.delete(r.deviceId)}}}L("all-devices-update",l.filter(t=>t.deviceName!=="Dashboard").map(({licenseKey:t,...r})=>r))}function ae(e,t){const r=h.find(p=>p.id===e);const s=I.get(e);I.delete(e);x.delete(e);k(e);g.delete(e);let u;if(s){for(const[p,D]of I){if(D===s)u=p}}if(s&&u){const p=h.findIndex(S=>S.deviceId===s);if(p!==-1&&h[p].id===e){h[p].id=u}const D=l.findIndex(S=>S.deviceId===s);if(D!==-1&&l[D].id===e){l[D].id=u}if(B.get(s)===e){B.set(s,u)}console.log(`${f} Client disconnected - ID: ${e}, Name: ${r?.deviceName||"Unknown"} - Device ${s} still connected via ${u}`)}else{h=h.filter(p=>p.id!==e);console.log(`${f} Client disconnected - ID: ${e}, Name: ${r?.deviceName||"Unknown"} - Removing user record`);if(s){const p=l.findIndex(D=>D.deviceId===s);if(p!==-1){l[p].isConnected=false;l[p].lastDisconnectReason=t;l[p].lastSeenAt=Date.now();B.delete(s)}}}const a=y.includes(e);y=y.filter(p=>p!==e);pe(e);const $=Boolean(s&&u);N(a||$?"debug":"warn",$?`Socket closed for ${r?.deviceName||e} (${t??"unknown"}) \u2014 device still connected via another socket`:`Disconnected: ${r?.deviceName||e} (${t??"unknown"})`,{socketId:e,reason:t,wasDashboard:a,stillConnected:$});E();console.log(`${f} Updated users list: ${h.map(p=>p.deviceName).join(", ")}`)}function le({id:e,deviceName:t,deviceId:r,platform:s,extraDeviceInfo:u,envVariables:a,connectionNonce:$},p){if(r){I.set(e,r);B.set(r,e);const n=h.findIndex(m=>m.deviceId===r);const c=l.findIndex(m=>m.deviceId===r);if(n!==-1){N("success",`Reconnected: ${t} (${s||"unknown platform"})`,{socketId:e,deviceId:r});h[n].id=e;if(s){h[n].platform=s}if(u){h[n].extraDeviceInfo=u}if(a){h[n].envVariables=a}h[n].isConnected=true;h[n].connectionNonce=$;if(c!==-1){l[c].id=e;l[c].isConnected=true;l[c].connectionNonce=$;l[c].lastSeenAt=Date.now();if(s){l[c].platform=s}if(u){l[c].extraDeviceInfo=u}if(a){l[c].envVariables=a}}else if(t!=="Dashboard"){l.push({id:e,deviceName:t,deviceId:r,platform:s,isConnected:true,extraDeviceInfo:u,envVariables:a,lastSeenAt:Date.now()})}E();console.log(`${f} Updated users list: ${h.map(m=>m.deviceName).join(", ")}`);return}}const D=n=>{const c=n.match(/(\d+)$/);return c?parseInt(c[1],10):1};let S=0;h.forEach(n=>{if(n.deviceName.startsWith(t)){const c=D(n.deviceName);if(c>=S){S=c}}});if(S>0&&!p){const n=`${t} #${S+1}`;N("warn",`Duplicate device name "${t}" \u2014 renamed to "${n}"`,{socketId:e,deviceId:r});t=n}if(e){const n={id:e,deviceName:t,deviceId:r,platform:s,isConnected:true,extraDeviceInfo:u,envVariables:a,connectionNonce:$,lastSeenAt:Date.now()};h.push(n);if(t!=="Dashboard"&&r){const c=l.findIndex(m=>m.deviceId===r);if(c!==-1){l[c]={...n}}else{l.push({...n})}}N(p?"debug":"info",`Connected: ${t}${s?` (${s})`:""}`,{socketId:e,deviceId:r});if(p&&e){y.push(e);console.log(`${f} Registered dashboard client with ID: ${e}`)}E();console.log(`${f} Updated device list: ${h.map(c=>c.deviceName).join(", ")}`)}}function fe(e){return l.find(t=>t.deviceId===e)}function ue(e,t){console.log(`${f} Message from ${t}: ${e}`)}function U(e,t){if(!i())return;L(e,t)}function L(e,t){y=y.filter(r=>o.sockets.sockets.has(r));for(const r of y){o.sockets.sockets.get(r)?.emit(e,t)}}function M(e){return y.includes(e)}const K=new Map;function ve(e,t){let r=K.get(e);if(!r){let s;const u=new Promise(a=>{s=setTimeout(()=>a(false),De)});r=Promise.race([t(e).catch(()=>false),u]).finally(()=>{clearTimeout(s);K.delete(e)});K.set(e,r)}return r}o.use((e,t)=>{const{auth:r,query:s,headers:u}=e.handshake;if(G(d,r?.dashboardToken)){t();return}if(s?.deviceName==="Dashboard"){N("warn","Refused a dashboard connection without the broker token (old Buoy Desktop or @buoy-gg/mcp?)",{address:e.handshake.address});t(new Error(j));return}const a=typeof u?.origin==="string"?u.origin:void 0;if(!a||ee(a)){t();return}const $=v?.remembered(a);if($===true){t();return}const p=()=>{N("warn",`Refused a connection from ${a}. Approve the site in Buoy Desktop to let it connect.`,{origin:a});t(new Error(z))};if($===false||!v?.ask){p();return}void ve(a,v.ask).then(D=>D?t():p())});function F(e,t,r){if(!i())return;const s=a=>{const $=g.get(a.id);return $==="free"||$==="pro"};const u=fe(e)?.deviceName;if(e==="No devices available"){console.log(`${f} Skipping ${r} - No devices available`);return}if(e==="All"){console.log(`${f} Broadcasting ${r} to all devices`);const a=h.filter($=>$.deviceName!=="Dashboard");console.log(`${f} Sending to ${a.length} devices: ${a.map($=>$.deviceName).join(", ")}`);a.filter(s).forEach(t)}else{const a=h.find($=>$.deviceId===e);if(a&&s(a)){console.log(`${f} Sending to target device ${a.deviceName} (${a.deviceId}) targetDeviceId: ${e}`);t(a)}else{console.log(`${f} Target device not found - DeviceId: ${e}, DeviceName: ${u}`)}}}function Y(e,t){const[r,s]=X(e);const u=t===null?{toolId:s,targetDeviceId:r,watching:false,consolidated:true}:{toolId:s,targetDeviceId:r,watching:true,stream:t==="stream",consolidated:true};F(r,a=>o.to(a.id).emit("devtool-watch",u),`devtool ${t===null?"unwatch":`watch(${t})`} ${s}`)}function he(e,t){const r=$e(t.targetDeviceId,t.toolId);if(t.watching){const a=t.stream===false?"capture":"stream";const $=T.watch(e,r,a);console.log(`${f} Dashboard ${e} watching "${t.toolId}" on ${t.targetDeviceId} (${a}, effective ${$})`);Y(r,$);return}const{changed:s,mode:u}=T.unwatch(e,r);if(!s)return;console.log(u===null?`${f} Last dashboard stopped watching "${t.toolId}" on ${t.targetDeviceId}`:`${f} "${t.toolId}" on ${t.targetDeviceId} downgraded to ${u}`);Y(r,u)}function pe(e){for(const{key:t,mode:r}of T.releaseDashboard(e)){Y(t,r)}}o.on("connection",e=>{const t=G(d,e.handshake.auth?.dashboardToken);if(!t&&e.handshake.query?.deviceName==="Dashboard"){e.emit("buoy:auth-error",{reason:j});e.disconnect(true);return}const r=()=>e.emit("buoy:access",{protocol:1,ready:i()});r();e.on("buoy:access:request",r);e.use((n,c)=>{if(n[0]==="buoy:access:request"){c();return}if(!i()){r();return}const m=n[0]==="device-license";if(m){const b=n[1]??{};k(e.id);g.set(e.id,H(b))}const w=g.get(e.id)==="free"||g.get(e.id)==="pro";if(m||y.includes(e.id)||w)c();else r()});const{deviceName:s,deviceId:u,platform:a,extraDeviceInfo:$,envVariables:p,connectionNonce:D,instanceId:S}=e.handshake.query;if(typeof S==="string"&&S){x.set(e.id,S)}console.log(`${f} New connection - ID: ${e.id}, Name: ${s||"Unknown Device"}${u?`, DeviceId: ${u}`:""}${a?`, Platform: ${a}`:""}`);if(!t&&i()){g.set(e.id,"checking");const n=setTimeout(()=>{C.delete(e.id);if(g.get(e.id)!=="checking")return;g.set(e.id,"locked");E()},ye);n.unref?.();C.set(e.id,n)}le({id:e.id,deviceName:s||"Unknown Device Name",deviceId:u,platform:a,extraDeviceInfo:void 0,envVariables:void 0,connectionNonce:D},t);if(i()&&t){for(const n of O.values()){e.emit("devtool-capabilities",n)}for(const n of R){e.emit("broker-log",n)}}e.on("device-license",n=>{const c=H(n??{});const m=c==="pro";k(e.id);g.set(e.id,c);const w=h.find(A=>A.id===e.id);if(w){w.isPro=m;w.tier=c;delete w.licenseKey}const b=w?.deviceId||I.get(e.id);if(c!=="locked"){for(const A of[...h,...l].filter(W=>W.id===e.id)){A.extraDeviceInfo=te($);A.envVariables=te(p)}}if(b){if(c==="locked")O.delete(b);const A=l.find(W=>W.deviceId===b);if(A){A.isPro=m;A.tier=c;delete A.licenseKey}}N("debug",`device-license from ${w?.deviceName||e.id}: tier=${c}`,{socketId:e.id});E()});e.on("disconnect",n=>ae(e.id,n));e.on("message",n=>{ue(n,s||"Unknown")});e.on("devtool-capabilities",n=>{if(!n||!Array.isArray(n.tools)||n.persistentDeviceId!==I.get(e.id))return;console.log(`${f} Capabilities from ${n.persistentDeviceId}: ${n.tools.map(c=>`${c.toolId}@v${c.version}`).join(", ")}`);if(n.protocolVersion!==q){N("warn",`Protocol version mismatch from ${n.persistentDeviceId}: device speaks v${n.protocolVersion}, broker speaks v${q} \u2014 update @buoy-gg packages or Buoy Desktop`,{deviceId:n.persistentDeviceId})}O.set(n.persistentDeviceId,n);U("devtool-capabilities",n);for(const c of T.activeKeys()){const[m,w]=X(c);if(m!=="All"&&m!==n.persistentDeviceId){continue}const b=T.effective(c);if(!b)continue;e.emit("devtool-watch",{toolId:w,targetDeviceId:m,watching:true,stream:b==="stream",consolidated:true})}});e.on("devtool-sync",n=>{if(!n||n.persistentDeviceId!==I.get(e.id))return;U("devtool-sync",n)});e.on("devtool-action-result",n=>{if(!n||n.persistentDeviceId!==I.get(e.id))return;console.log(`${f} Action result from ${n.persistentDeviceId}: ${n.toolId}.${n.action} -> ${n.ok?"ok":`error: ${n.error}`}`);U("devtool-action-result",n)});e.on("devtool-watch",n=>{if(!M(e.id))return;he(e.id,n)});e.on("request-devtool-state",n=>{if(!M(e.id))return;F(n.targetDeviceId,c=>o.to(c.id).emit("request-devtool-state",n),`devtool state request ${n.toolId}`)});e.on("forget-device",n=>{const c=n?.deviceId;if(!y.includes(e.id))return;if(!c||B.has(c))return;const m=l.find(b=>b.deviceId===c);h=h.filter(b=>b.deviceId!==c);const w=l.findIndex(b=>b.deviceId===c);if(w!==-1){l.splice(w,1)}O.delete(c);for(const[b,A]of I){if(A===c){I.delete(b)}}N("info",`Forgot device: ${m?.deviceName??c}`,{deviceId:c});E()});e.on("devtool-action",n=>{if(!M(e.id))return;console.log(`${f} Devtool action from dashboard: ${n.toolId}.${n.action} -> ${n.targetDeviceId}`);F(n.targetDeviceId,c=>o.to(c.id).emit("devtool-action",n),`devtool action ${n.toolId}.${n.action}`)})});return{refreshAccess(){const e=i();if(!e){for(const t of C.keys())k(t);g.clear();O.clear();R.length=0;T=new J;for(const t of[...h,...l]){delete t.licenseKey;delete t.envVariables;delete t.extraDeviceInfo}}o.emit("buoy:access",{protocol:1,ready:e});E()}}}import Ne from"http";import{Server as Ae}from"socket.io";var oe=42831;var ie={cors:{origin:"*",methods:["GET","POST"]},maxHttpBufferSize:1e8,transports:["websocket","polling"],pingTimeout:3e4,pingInterval:25e3};function Ee(o={}){const i=o.port??oe;const d=Ne.createServer((I,g)=>{g.writeHead(200,{"content-type":"text/plain"});g.end(`Buoy sync broker running on port ${i}`)});const v=new Ae(d,{...ie,...o.socketOptions});const f=o.dashboardToken??Q();const h=ne({io:v,canUseDesktop:o.canUseDesktop,dashboardToken:f,originPolicy:o.originPolicy});d.on("error",I=>{o.onError?.(I)});const l=()=>o.onListening?.(i);if(o.host)d.listen(i,o.host,l);else d.listen(i,l);const y=()=>new Promise(I=>{v.close(()=>{d.close(()=>I())})});return{io:v,httpServer:d,port:i,dashboardToken:f,close:y,refreshAccess:h.refreshAccess}}import P from"fs";import re from"os";import _ from"path";function ce(){if(process.platform==="win32"){const o=process.env.APPDATA||_.join(re.homedir(),"AppData","Roaming");return _.join(o,"Buoy")}return _.join(re.homedir(),".buoy")}var se=42831;function V(o=se){const i=o===se?"broker-token":`broker-token-${o}`;return _.join(ce(),i)}function Oe(o,i=V()){P.mkdirSync(_.dirname(i),{recursive:true,mode:448});const d=`${i}.${process.pid}.tmp`;P.writeFileSync(d,o,{mode:384});P.renameSync(d,i);if(process.platform!=="win32"){try{P.chmodSync(_.dirname(i),448)}catch{}}}function Te(o=V()){try{const i=P.readFileSync(o,"utf8").trim();return i||null}catch{return null}}export{be as DASHBOARD_AUTH_VERSION,j as DASHBOARD_TOKEN_REQUIRED,oe as DEFAULT_BROKER_PORT,z as ORIGIN_NOT_ALLOWED,ie as SOCKET_CONFIG,q as SYNC_PROTOCOL_VERSION,V as brokerTokenPath,ce as buoyConfigDir,Ee as createBroker,Q as createDashboardToken,ee as isLocalOrigin,Te as readBrokerToken,ne as socketHandle,G as tokenMatches,Oe as writeBrokerToken};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buoy-gg/sync-broker",
|
|
3
|
-
"version": "7.0.
|
|
3
|
+
"version": "7.0.66",
|
|
4
4
|
"description": "Headless Socket.IO broker for the Buoy devtool sync protocol (v1) — routes messages between devices and dashboard/MCP clients",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|