@buoy-gg/sync-broker 7.0.40 → 7.0.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -764
- package/dist/index.mjs +1 -723
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,723 +1 @@
|
|
|
1
|
-
// src/protocol.ts
|
|
2
|
-
var SYNC_PROTOCOL_VERSION = 1;
|
|
3
|
-
function tierFromLicensePayload(payload) {
|
|
4
|
-
if (payload?.tier === "pro" || payload?.tier === "free") return payload.tier;
|
|
5
|
-
if (payload?.tier === "locked" || payload?.tier === "anonymous") return "locked";
|
|
6
|
-
return payload?.isPro === "1" || payload?.isPro === true ? "pro" : "locked";
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// src/watchRegistry.ts
|
|
10
|
-
var toWatchKey = (targetDeviceId, toolId) => JSON.stringify([targetDeviceId, toolId]);
|
|
11
|
-
var fromWatchKey = (key) => JSON.parse(key);
|
|
12
|
-
var WatchRegistry = class {
|
|
13
|
-
constructor() {
|
|
14
|
-
/** Per dashboard: the key it watches → the mode it asked for. */
|
|
15
|
-
this.byDashboard = /* @__PURE__ */ new Map();
|
|
16
|
-
this.streamCounts = /* @__PURE__ */ new Map();
|
|
17
|
-
this.captureCounts = /* @__PURE__ */ new Map();
|
|
18
|
-
}
|
|
19
|
-
counts(mode) {
|
|
20
|
-
return mode === "stream" ? this.streamCounts : this.captureCounts;
|
|
21
|
-
}
|
|
22
|
-
bump(mode, key, delta) {
|
|
23
|
-
const counts = this.counts(mode);
|
|
24
|
-
const next = (counts.get(key) ?? 0) + delta;
|
|
25
|
-
if (next > 0) counts.set(key, next);
|
|
26
|
-
else counts.delete(key);
|
|
27
|
-
}
|
|
28
|
-
/** The strongest mode any dashboard currently wants; null for nobody. */
|
|
29
|
-
effective(key) {
|
|
30
|
-
if ((this.streamCounts.get(key) ?? 0) > 0) return "stream";
|
|
31
|
-
if ((this.captureCounts.get(key) ?? 0) > 0) return "capture";
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
/** Every key with at least one watcher (for replay to a reconnecting device). */
|
|
35
|
-
activeKeys() {
|
|
36
|
-
return [
|
|
37
|
-
.../* @__PURE__ */ new Set([...this.streamCounts.keys(), ...this.captureCounts.keys()])
|
|
38
|
-
];
|
|
39
|
-
}
|
|
40
|
-
/**
|
|
41
|
-
* Record a watch. Returns the mode to send the device — always non-null,
|
|
42
|
-
* because the device answers a watch with a snapshot and that is how a
|
|
43
|
-
* dashboard joining a quiet tool gets its first paint. Re-watching at the
|
|
44
|
-
* same mode is therefore NOT a no-op at the socket layer.
|
|
45
|
-
*/
|
|
46
|
-
watch(dashboardId, key, mode) {
|
|
47
|
-
let held = this.byDashboard.get(dashboardId);
|
|
48
|
-
if (!held) {
|
|
49
|
-
held = /* @__PURE__ */ new Map();
|
|
50
|
-
this.byDashboard.set(dashboardId, held);
|
|
51
|
-
}
|
|
52
|
-
const previous = held.get(key);
|
|
53
|
-
if (previous !== mode) {
|
|
54
|
-
if (previous) this.bump(previous, key, -1);
|
|
55
|
-
this.bump(mode, key, 1);
|
|
56
|
-
held.set(key, mode);
|
|
57
|
-
}
|
|
58
|
-
return this.effective(key);
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Drop one dashboard's claim. Returns the new effective mode ONLY when it
|
|
62
|
-
* changed — the device is left alone while someone else still wants the
|
|
63
|
-
* same thing.
|
|
64
|
-
*/
|
|
65
|
-
unwatch(dashboardId, key) {
|
|
66
|
-
const held = this.byDashboard.get(dashboardId);
|
|
67
|
-
const mode = held?.get(key);
|
|
68
|
-
if (!held || !mode) return { changed: false, mode: this.effective(key) };
|
|
69
|
-
const before = this.effective(key);
|
|
70
|
-
held.delete(key);
|
|
71
|
-
this.bump(mode, key, -1);
|
|
72
|
-
const after = this.effective(key);
|
|
73
|
-
return { changed: after !== before, mode: after };
|
|
74
|
-
}
|
|
75
|
-
/** Release everything a disconnecting dashboard held. */
|
|
76
|
-
releaseDashboard(dashboardId) {
|
|
77
|
-
const held = this.byDashboard.get(dashboardId);
|
|
78
|
-
if (!held) return [];
|
|
79
|
-
this.byDashboard.delete(dashboardId);
|
|
80
|
-
const changes = [];
|
|
81
|
-
for (const [key, mode] of held) {
|
|
82
|
-
const before = this.effective(key);
|
|
83
|
-
this.bump(mode, key, -1);
|
|
84
|
-
const after = this.effective(key);
|
|
85
|
-
if (after !== before) changes.push({ key, mode: after });
|
|
86
|
-
}
|
|
87
|
-
return changes;
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
// src/socketHandle.ts
|
|
92
|
-
var BROKER_LOG_BUFFER_MAX = 200;
|
|
93
|
-
var MAX_OFFLINE_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
94
|
-
function redactAccountKeys(raw) {
|
|
95
|
-
if (!raw) return void 0;
|
|
96
|
-
try {
|
|
97
|
-
const value = JSON.parse(raw);
|
|
98
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
99
|
-
for (const key of ["BUOY_LICENSE_KEY", "BUOY_KEY", "EXPO_PUBLIC_BUOY_KEY", "VITE_BUOY_KEY", "REACT_APP_BUOY_KEY", "licenseKey"]) delete value[key];
|
|
100
|
-
return JSON.stringify(value);
|
|
101
|
-
} catch {
|
|
102
|
-
return void 0;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
function socketHandle({ io, canUseDesktop = () => false }) {
|
|
106
|
-
const LOG_PREFIX = "[SERVER]";
|
|
107
|
-
let users = [];
|
|
108
|
-
const allDevices = [];
|
|
109
|
-
let dashboardClients = [];
|
|
110
|
-
const deviceIdMap = /* @__PURE__ */ new Map();
|
|
111
|
-
const socketTiers = /* @__PURE__ */ new Map();
|
|
112
|
-
const activeConnectionsMap = /* @__PURE__ */ new Map();
|
|
113
|
-
const instanceIdMap = /* @__PURE__ */ new Map();
|
|
114
|
-
const deviceCapabilities = /* @__PURE__ */ new Map();
|
|
115
|
-
let watches = new WatchRegistry();
|
|
116
|
-
const brokerLogBuffer = [];
|
|
117
|
-
function brokerLog(level, message, data) {
|
|
118
|
-
console.log(`${LOG_PREFIX} ${message}`);
|
|
119
|
-
const entry = { ts: Date.now(), level, message, data };
|
|
120
|
-
brokerLogBuffer.push(entry);
|
|
121
|
-
if (brokerLogBuffer.length > BROKER_LOG_BUFFER_MAX) {
|
|
122
|
-
brokerLogBuffer.shift();
|
|
123
|
-
}
|
|
124
|
-
forwardToDashboards("broker-log", entry);
|
|
125
|
-
}
|
|
126
|
-
function liveInstances(deviceId) {
|
|
127
|
-
const instances = /* @__PURE__ */ new Set();
|
|
128
|
-
for (const [socketId, mappedDeviceId] of deviceIdMap) {
|
|
129
|
-
if (mappedDeviceId !== deviceId) continue;
|
|
130
|
-
const instance = instanceIdMap.get(socketId);
|
|
131
|
-
if (instance) instances.add(instance);
|
|
132
|
-
}
|
|
133
|
-
return instances.size;
|
|
134
|
-
}
|
|
135
|
-
function emitAllDevices() {
|
|
136
|
-
if (!canUseDesktop()) {
|
|
137
|
-
io.emit("all-devices-update", []);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
for (const device of [...users, ...allDevices]) {
|
|
141
|
-
if (device.deviceName === "Dashboard" || !device.isConnected) continue;
|
|
142
|
-
const tier = socketTiers.get(device.id) ?? "locked";
|
|
143
|
-
device.tier = tier === "pro" ? "pro" : tier === "free" ? "free" : "locked";
|
|
144
|
-
device.isPro = tier === "pro";
|
|
145
|
-
if (tier !== "free" && tier !== "pro") {
|
|
146
|
-
delete device.envVariables;
|
|
147
|
-
delete device.extraDeviceInfo;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
const now = Date.now();
|
|
151
|
-
for (const device of allDevices) {
|
|
152
|
-
if (!device.deviceId) continue;
|
|
153
|
-
const instances = liveInstances(device.deviceId);
|
|
154
|
-
const collision = instances > 1;
|
|
155
|
-
if (collision && !device.collision) {
|
|
156
|
-
brokerLog(
|
|
157
|
-
"warn",
|
|
158
|
-
`${instances} apps are registered as "${device.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.`,
|
|
159
|
-
{ deviceId: device.deviceId, instances }
|
|
160
|
-
);
|
|
161
|
-
} else if (!collision && device.collision) {
|
|
162
|
-
brokerLog("info", `Device id collision cleared for "${device.deviceId}"`, {
|
|
163
|
-
deviceId: device.deviceId
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
device.collision = collision;
|
|
167
|
-
}
|
|
168
|
-
for (let i = allDevices.length - 1; i >= 0; i--) {
|
|
169
|
-
const device = allDevices[i];
|
|
170
|
-
if (!device.isConnected && device.lastSeenAt && now - device.lastSeenAt > MAX_OFFLINE_AGE_MS) {
|
|
171
|
-
allDevices.splice(i, 1);
|
|
172
|
-
if (device.deviceId) {
|
|
173
|
-
deviceCapabilities.delete(device.deviceId);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
io.emit(
|
|
178
|
-
"all-devices-update",
|
|
179
|
-
allDevices.filter((device) => device.deviceName !== "Dashboard").map(({ licenseKey: _key, ...device }) => device)
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
function handleClose(id, reason) {
|
|
183
|
-
const user = users.find((user2) => user2.id === id);
|
|
184
|
-
const deviceId = deviceIdMap.get(id);
|
|
185
|
-
deviceIdMap.delete(id);
|
|
186
|
-
instanceIdMap.delete(id);
|
|
187
|
-
socketTiers.delete(id);
|
|
188
|
-
let survivor;
|
|
189
|
-
if (deviceId) {
|
|
190
|
-
for (const [socketId, mappedDeviceId] of deviceIdMap) {
|
|
191
|
-
if (mappedDeviceId === deviceId) survivor = socketId;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
if (deviceId && survivor) {
|
|
195
|
-
const userIndex = users.findIndex((u) => u.deviceId === deviceId);
|
|
196
|
-
if (userIndex !== -1 && users[userIndex].id === id) {
|
|
197
|
-
users[userIndex].id = survivor;
|
|
198
|
-
}
|
|
199
|
-
const deviceIndex = allDevices.findIndex((d) => d.deviceId === deviceId);
|
|
200
|
-
if (deviceIndex !== -1 && allDevices[deviceIndex].id === id) {
|
|
201
|
-
allDevices[deviceIndex].id = survivor;
|
|
202
|
-
}
|
|
203
|
-
if (activeConnectionsMap.get(deviceId) === id) {
|
|
204
|
-
activeConnectionsMap.set(deviceId, survivor);
|
|
205
|
-
}
|
|
206
|
-
console.log(
|
|
207
|
-
`${LOG_PREFIX} Client disconnected - ID: ${id}, Name: ${user?.deviceName || "Unknown"} - Device ${deviceId} still connected via ${survivor}`
|
|
208
|
-
);
|
|
209
|
-
} else {
|
|
210
|
-
users = users.filter((user2) => user2.id !== id);
|
|
211
|
-
console.log(
|
|
212
|
-
`${LOG_PREFIX} Client disconnected - ID: ${id}, Name: ${user?.deviceName || "Unknown"} - Removing user record`
|
|
213
|
-
);
|
|
214
|
-
if (deviceId) {
|
|
215
|
-
const deviceIndex = allDevices.findIndex(
|
|
216
|
-
(u) => u.deviceId === deviceId
|
|
217
|
-
);
|
|
218
|
-
if (deviceIndex !== -1) {
|
|
219
|
-
allDevices[deviceIndex].isConnected = false;
|
|
220
|
-
allDevices[deviceIndex].lastDisconnectReason = reason;
|
|
221
|
-
allDevices[deviceIndex].lastSeenAt = Date.now();
|
|
222
|
-
activeConnectionsMap.delete(deviceId);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
const wasDashboard = dashboardClients.includes(id);
|
|
227
|
-
dashboardClients = dashboardClients.filter((clientId) => clientId !== id);
|
|
228
|
-
releaseDashboardWatches(id);
|
|
229
|
-
const stillConnected = Boolean(deviceId && survivor);
|
|
230
|
-
brokerLog(
|
|
231
|
-
wasDashboard || stillConnected ? "debug" : "warn",
|
|
232
|
-
stillConnected ? `Socket closed for ${user?.deviceName || id} (${reason ?? "unknown"}) \u2014 device still connected via another socket` : `Disconnected: ${user?.deviceName || id} (${reason ?? "unknown"})`,
|
|
233
|
-
{ socketId: id, reason, wasDashboard, stillConnected }
|
|
234
|
-
);
|
|
235
|
-
emitAllDevices();
|
|
236
|
-
console.log(
|
|
237
|
-
`${LOG_PREFIX} Updated users list: ${users.map((u) => u.deviceName).join(", ")}`
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
function addNewUser({
|
|
241
|
-
id,
|
|
242
|
-
deviceName,
|
|
243
|
-
deviceId,
|
|
244
|
-
platform,
|
|
245
|
-
extraDeviceInfo,
|
|
246
|
-
envVariables,
|
|
247
|
-
connectionNonce
|
|
248
|
-
}) {
|
|
249
|
-
const isDashboard = deviceName === "Dashboard";
|
|
250
|
-
if (deviceId) {
|
|
251
|
-
deviceIdMap.set(id, deviceId);
|
|
252
|
-
activeConnectionsMap.set(deviceId, id);
|
|
253
|
-
const existingUserIndex = users.findIndex(
|
|
254
|
-
(user) => user.deviceId === deviceId
|
|
255
|
-
);
|
|
256
|
-
const existingDeviceIndex = allDevices.findIndex(
|
|
257
|
-
(device) => device.deviceId === deviceId
|
|
258
|
-
);
|
|
259
|
-
if (existingUserIndex !== -1) {
|
|
260
|
-
brokerLog(
|
|
261
|
-
"success",
|
|
262
|
-
`Reconnected: ${deviceName} (${platform || "unknown platform"})`,
|
|
263
|
-
{ socketId: id, deviceId }
|
|
264
|
-
);
|
|
265
|
-
users[existingUserIndex].id = id;
|
|
266
|
-
if (platform) {
|
|
267
|
-
users[existingUserIndex].platform = platform;
|
|
268
|
-
}
|
|
269
|
-
if (extraDeviceInfo) {
|
|
270
|
-
users[existingUserIndex].extraDeviceInfo = extraDeviceInfo;
|
|
271
|
-
}
|
|
272
|
-
if (envVariables) {
|
|
273
|
-
users[existingUserIndex].envVariables = envVariables;
|
|
274
|
-
}
|
|
275
|
-
users[existingUserIndex].isConnected = true;
|
|
276
|
-
users[existingUserIndex].connectionNonce = connectionNonce;
|
|
277
|
-
if (existingDeviceIndex !== -1) {
|
|
278
|
-
allDevices[existingDeviceIndex].id = id;
|
|
279
|
-
allDevices[existingDeviceIndex].isConnected = true;
|
|
280
|
-
allDevices[existingDeviceIndex].connectionNonce = connectionNonce;
|
|
281
|
-
allDevices[existingDeviceIndex].lastSeenAt = Date.now();
|
|
282
|
-
if (platform) {
|
|
283
|
-
allDevices[existingDeviceIndex].platform = platform;
|
|
284
|
-
}
|
|
285
|
-
if (extraDeviceInfo) {
|
|
286
|
-
allDevices[existingDeviceIndex].extraDeviceInfo = extraDeviceInfo;
|
|
287
|
-
}
|
|
288
|
-
if (envVariables) {
|
|
289
|
-
allDevices[existingDeviceIndex].envVariables = envVariables;
|
|
290
|
-
}
|
|
291
|
-
} else if (deviceName !== "Dashboard") {
|
|
292
|
-
allDevices.push({
|
|
293
|
-
id,
|
|
294
|
-
deviceName,
|
|
295
|
-
deviceId,
|
|
296
|
-
platform,
|
|
297
|
-
isConnected: true,
|
|
298
|
-
extraDeviceInfo,
|
|
299
|
-
envVariables,
|
|
300
|
-
lastSeenAt: Date.now()
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
emitAllDevices();
|
|
304
|
-
console.log(
|
|
305
|
-
`${LOG_PREFIX} Updated users list: ${users.map((u) => u.deviceName).join(", ")}`
|
|
306
|
-
);
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
const extractNumber = (name) => {
|
|
311
|
-
const match = name.match(/(\d+)$/);
|
|
312
|
-
return match ? parseInt(match[1], 10) : 1;
|
|
313
|
-
};
|
|
314
|
-
let highestNumber = 0;
|
|
315
|
-
users.forEach((user) => {
|
|
316
|
-
if (user.deviceName.startsWith(deviceName)) {
|
|
317
|
-
const number = extractNumber(user.deviceName);
|
|
318
|
-
if (number >= highestNumber) {
|
|
319
|
-
highestNumber = number;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
if (highestNumber > 0 && !isDashboard) {
|
|
324
|
-
const renamed = `${deviceName} #${highestNumber + 1}`;
|
|
325
|
-
brokerLog(
|
|
326
|
-
"warn",
|
|
327
|
-
`Duplicate device name "${deviceName}" \u2014 renamed to "${renamed}"`,
|
|
328
|
-
{ socketId: id, deviceId }
|
|
329
|
-
);
|
|
330
|
-
deviceName = renamed;
|
|
331
|
-
}
|
|
332
|
-
if (id) {
|
|
333
|
-
const newUser = {
|
|
334
|
-
id,
|
|
335
|
-
deviceName,
|
|
336
|
-
deviceId,
|
|
337
|
-
platform,
|
|
338
|
-
isConnected: true,
|
|
339
|
-
extraDeviceInfo,
|
|
340
|
-
envVariables,
|
|
341
|
-
connectionNonce,
|
|
342
|
-
lastSeenAt: Date.now()
|
|
343
|
-
};
|
|
344
|
-
users.push(newUser);
|
|
345
|
-
if (deviceName !== "Dashboard" && deviceId) {
|
|
346
|
-
const existingDeviceIndex = allDevices.findIndex(
|
|
347
|
-
(device) => device.deviceId === deviceId
|
|
348
|
-
);
|
|
349
|
-
if (existingDeviceIndex !== -1) {
|
|
350
|
-
allDevices[existingDeviceIndex] = { ...newUser };
|
|
351
|
-
} else {
|
|
352
|
-
allDevices.push({ ...newUser });
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
brokerLog(
|
|
356
|
-
isDashboard ? "debug" : "info",
|
|
357
|
-
`Connected: ${deviceName}${platform ? ` (${platform})` : ""}`,
|
|
358
|
-
{ socketId: id, deviceId }
|
|
359
|
-
);
|
|
360
|
-
if (isDashboard && id) {
|
|
361
|
-
dashboardClients.push(id);
|
|
362
|
-
console.log(`${LOG_PREFIX} Registered dashboard client with ID: ${id}`);
|
|
363
|
-
}
|
|
364
|
-
emitAllDevices();
|
|
365
|
-
console.log(
|
|
366
|
-
`${LOG_PREFIX} Updated device list: ${users.map((u) => u.deviceName).join(", ")}`
|
|
367
|
-
);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
function getDeviceFromDeviceId(deviceId) {
|
|
371
|
-
return allDevices.find((device) => device.deviceId === deviceId);
|
|
372
|
-
}
|
|
373
|
-
function handleUserMessage(message, deviceName) {
|
|
374
|
-
console.log(`${LOG_PREFIX} Message from ${deviceName}: ${message}`);
|
|
375
|
-
if (!canUseDesktop()) return;
|
|
376
|
-
io.emit("message", `Device ${deviceName} sent message: ${message}`);
|
|
377
|
-
}
|
|
378
|
-
function forwardToDashboards(event, message) {
|
|
379
|
-
if (!canUseDesktop()) return;
|
|
380
|
-
dashboardClients = dashboardClients.filter(
|
|
381
|
-
(id) => io.sockets.sockets.has(id)
|
|
382
|
-
);
|
|
383
|
-
if (dashboardClients.length === 0) {
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
for (const dashboardId of dashboardClients) {
|
|
387
|
-
io.sockets.sockets.get(dashboardId)?.emit(event, message);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
function withTargetUsers(targetDeviceId, action, actionName) {
|
|
391
|
-
if (!canUseDesktop()) return;
|
|
392
|
-
const admitted = (user) => {
|
|
393
|
-
const tier = socketTiers.get(user.id);
|
|
394
|
-
return tier === "free" || tier === "pro";
|
|
395
|
-
};
|
|
396
|
-
const deviceName = getDeviceFromDeviceId(targetDeviceId)?.deviceName;
|
|
397
|
-
if (targetDeviceId === "No devices available") {
|
|
398
|
-
console.log(
|
|
399
|
-
`${LOG_PREFIX} Skipping ${actionName} - No devices available`
|
|
400
|
-
);
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
if (targetDeviceId === "All") {
|
|
404
|
-
console.log(`${LOG_PREFIX} Broadcasting ${actionName} to all devices`);
|
|
405
|
-
const deviceUsers = users.filter(
|
|
406
|
-
(user) => user.deviceName !== "Dashboard"
|
|
407
|
-
);
|
|
408
|
-
console.log(
|
|
409
|
-
`${LOG_PREFIX} Sending to ${deviceUsers.length} devices: ${deviceUsers.map((u) => u.deviceName).join(", ")}`
|
|
410
|
-
);
|
|
411
|
-
deviceUsers.filter(admitted).forEach(action);
|
|
412
|
-
} else {
|
|
413
|
-
const targetUser = users.find((user) => user.deviceId === targetDeviceId);
|
|
414
|
-
if (targetUser && admitted(targetUser)) {
|
|
415
|
-
console.log(
|
|
416
|
-
`${LOG_PREFIX} Sending to target device ${targetUser.deviceName} (${targetUser.deviceId}) targetDeviceId: ${targetDeviceId}`
|
|
417
|
-
);
|
|
418
|
-
action(targetUser);
|
|
419
|
-
} else {
|
|
420
|
-
console.log(
|
|
421
|
-
`${LOG_PREFIX} Target device not found - DeviceId: ${targetDeviceId}, DeviceName: ${deviceName}`
|
|
422
|
-
);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
function emitWatchState(key, mode) {
|
|
427
|
-
const [targetDeviceId, toolId] = fromWatchKey(key);
|
|
428
|
-
const message = mode === null ? { toolId, targetDeviceId, watching: false, consolidated: true } : {
|
|
429
|
-
toolId,
|
|
430
|
-
targetDeviceId,
|
|
431
|
-
watching: true,
|
|
432
|
-
stream: mode === "stream",
|
|
433
|
-
consolidated: true
|
|
434
|
-
};
|
|
435
|
-
withTargetUsers(
|
|
436
|
-
targetDeviceId,
|
|
437
|
-
(user) => io.to(user.id).emit("devtool-watch", message),
|
|
438
|
-
`devtool ${mode === null ? "unwatch" : `watch(${mode})`} ${toolId}`
|
|
439
|
-
);
|
|
440
|
-
}
|
|
441
|
-
function handleDevtoolWatch(dashboardId, message) {
|
|
442
|
-
const key = toWatchKey(message.targetDeviceId, message.toolId);
|
|
443
|
-
if (message.watching) {
|
|
444
|
-
const mode2 = message.stream === false ? "capture" : "stream";
|
|
445
|
-
const effective = watches.watch(dashboardId, key, mode2);
|
|
446
|
-
console.log(
|
|
447
|
-
`${LOG_PREFIX} Dashboard ${dashboardId} watching "${message.toolId}" on ${message.targetDeviceId} (${mode2}, effective ${effective})`
|
|
448
|
-
);
|
|
449
|
-
emitWatchState(key, effective);
|
|
450
|
-
return;
|
|
451
|
-
}
|
|
452
|
-
const { changed, mode } = watches.unwatch(dashboardId, key);
|
|
453
|
-
if (!changed) return;
|
|
454
|
-
console.log(
|
|
455
|
-
mode === null ? `${LOG_PREFIX} Last dashboard stopped watching "${message.toolId}" on ${message.targetDeviceId}` : `${LOG_PREFIX} "${message.toolId}" on ${message.targetDeviceId} downgraded to ${mode}`
|
|
456
|
-
);
|
|
457
|
-
emitWatchState(key, mode);
|
|
458
|
-
}
|
|
459
|
-
function releaseDashboardWatches(dashboardId) {
|
|
460
|
-
for (const { key, mode } of watches.releaseDashboard(dashboardId)) {
|
|
461
|
-
emitWatchState(key, mode);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
io.on("connection", (socket) => {
|
|
465
|
-
const sendAccess = () => socket.emit("buoy:access", { protocol: 1, ready: canUseDesktop() });
|
|
466
|
-
sendAccess();
|
|
467
|
-
socket.on("buoy:access:request", sendAccess);
|
|
468
|
-
socket.use((_packet, next) => {
|
|
469
|
-
if (_packet[0] === "buoy:access:request") {
|
|
470
|
-
next();
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
473
|
-
if (!canUseDesktop()) {
|
|
474
|
-
sendAccess();
|
|
475
|
-
return;
|
|
476
|
-
}
|
|
477
|
-
const control = _packet[0] === "device-license";
|
|
478
|
-
if (control) {
|
|
479
|
-
const payload = _packet[1] ?? {};
|
|
480
|
-
socketTiers.set(socket.id, tierFromLicensePayload(payload));
|
|
481
|
-
}
|
|
482
|
-
const deviceAdmitted = socketTiers.get(socket.id) === "free" || socketTiers.get(socket.id) === "pro";
|
|
483
|
-
if (control || dashboardClients.includes(socket.id) || deviceAdmitted) next();
|
|
484
|
-
else sendAccess();
|
|
485
|
-
});
|
|
486
|
-
const {
|
|
487
|
-
deviceName,
|
|
488
|
-
deviceId,
|
|
489
|
-
platform,
|
|
490
|
-
extraDeviceInfo,
|
|
491
|
-
envVariables,
|
|
492
|
-
connectionNonce,
|
|
493
|
-
instanceId
|
|
494
|
-
} = socket.handshake.query;
|
|
495
|
-
if (typeof instanceId === "string" && instanceId) {
|
|
496
|
-
instanceIdMap.set(socket.id, instanceId);
|
|
497
|
-
}
|
|
498
|
-
console.log(
|
|
499
|
-
`${LOG_PREFIX} New connection - ID: ${socket.id}, Name: ${deviceName || "Unknown Device"}${deviceId ? `, DeviceId: ${deviceId}` : ""}${platform ? `, Platform: ${platform}` : ""}`
|
|
500
|
-
);
|
|
501
|
-
addNewUser({
|
|
502
|
-
id: socket.id,
|
|
503
|
-
deviceName: deviceName || "Unknown Device Name",
|
|
504
|
-
deviceId,
|
|
505
|
-
platform,
|
|
506
|
-
extraDeviceInfo: void 0,
|
|
507
|
-
envVariables: void 0,
|
|
508
|
-
connectionNonce
|
|
509
|
-
});
|
|
510
|
-
if (canUseDesktop() && deviceName === "Dashboard") {
|
|
511
|
-
for (const capabilities of deviceCapabilities.values()) {
|
|
512
|
-
socket.emit("devtool-capabilities", capabilities);
|
|
513
|
-
}
|
|
514
|
-
for (const entry of brokerLogBuffer) {
|
|
515
|
-
socket.emit("broker-log", entry);
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
socket.on(
|
|
519
|
-
"device-license",
|
|
520
|
-
(payload) => {
|
|
521
|
-
const nextTier = tierFromLicensePayload(payload ?? {});
|
|
522
|
-
const nextIsPro = nextTier === "pro";
|
|
523
|
-
socketTiers.set(socket.id, nextTier);
|
|
524
|
-
const user = users.find((u) => u.id === socket.id);
|
|
525
|
-
if (user) {
|
|
526
|
-
user.isPro = nextIsPro;
|
|
527
|
-
user.tier = nextTier;
|
|
528
|
-
delete user.licenseKey;
|
|
529
|
-
}
|
|
530
|
-
const did = user?.deviceId || deviceIdMap.get(socket.id);
|
|
531
|
-
if (nextTier !== "locked") {
|
|
532
|
-
for (const record of [...users, ...allDevices].filter((record2) => record2.id === socket.id)) {
|
|
533
|
-
record.extraDeviceInfo = redactAccountKeys(extraDeviceInfo);
|
|
534
|
-
record.envVariables = redactAccountKeys(envVariables);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
if (did) {
|
|
538
|
-
if (nextTier === "locked") deviceCapabilities.delete(did);
|
|
539
|
-
const device = allDevices.find((d) => d.deviceId === did);
|
|
540
|
-
if (device) {
|
|
541
|
-
device.isPro = nextIsPro;
|
|
542
|
-
device.tier = nextTier;
|
|
543
|
-
delete device.licenseKey;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
brokerLog(
|
|
547
|
-
"debug",
|
|
548
|
-
`device-license from ${user?.deviceName || socket.id}: tier=${nextTier}`,
|
|
549
|
-
{ socketId: socket.id }
|
|
550
|
-
);
|
|
551
|
-
emitAllDevices();
|
|
552
|
-
}
|
|
553
|
-
);
|
|
554
|
-
socket.on(
|
|
555
|
-
"disconnect",
|
|
556
|
-
(reason) => handleClose(socket.id, reason)
|
|
557
|
-
);
|
|
558
|
-
socket.on("message", (msg) => {
|
|
559
|
-
handleUserMessage(msg, deviceName || "Unknown");
|
|
560
|
-
});
|
|
561
|
-
socket.on("devtool-capabilities", (message) => {
|
|
562
|
-
if (!message || !Array.isArray(message.tools) || message.persistentDeviceId !== deviceIdMap.get(socket.id)) return;
|
|
563
|
-
console.log(
|
|
564
|
-
`${LOG_PREFIX} Capabilities from ${message.persistentDeviceId}: ${message.tools.map((tool) => `${tool.toolId}@v${tool.version}`).join(", ")}`
|
|
565
|
-
);
|
|
566
|
-
if (message.protocolVersion !== SYNC_PROTOCOL_VERSION) {
|
|
567
|
-
brokerLog(
|
|
568
|
-
"warn",
|
|
569
|
-
`Protocol version mismatch from ${message.persistentDeviceId}: device speaks v${message.protocolVersion}, broker speaks v${SYNC_PROTOCOL_VERSION} \u2014 update @buoy-gg packages or Buoy Desktop`,
|
|
570
|
-
{ deviceId: message.persistentDeviceId }
|
|
571
|
-
);
|
|
572
|
-
}
|
|
573
|
-
deviceCapabilities.set(message.persistentDeviceId, message);
|
|
574
|
-
forwardToDashboards("devtool-capabilities", message);
|
|
575
|
-
for (const key of watches.activeKeys()) {
|
|
576
|
-
const [targetDeviceId, toolId] = fromWatchKey(key);
|
|
577
|
-
if (targetDeviceId !== "All" && targetDeviceId !== message.persistentDeviceId) {
|
|
578
|
-
continue;
|
|
579
|
-
}
|
|
580
|
-
const mode = watches.effective(key);
|
|
581
|
-
if (!mode) continue;
|
|
582
|
-
socket.emit("devtool-watch", {
|
|
583
|
-
toolId,
|
|
584
|
-
targetDeviceId,
|
|
585
|
-
watching: true,
|
|
586
|
-
stream: mode === "stream",
|
|
587
|
-
consolidated: true
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
});
|
|
591
|
-
socket.on("devtool-sync", (message) => {
|
|
592
|
-
if (!message || message.persistentDeviceId !== deviceIdMap.get(socket.id)) return;
|
|
593
|
-
forwardToDashboards("devtool-sync", message);
|
|
594
|
-
});
|
|
595
|
-
socket.on(
|
|
596
|
-
"devtool-action-result",
|
|
597
|
-
(message) => {
|
|
598
|
-
if (!message || message.persistentDeviceId !== deviceIdMap.get(socket.id)) return;
|
|
599
|
-
console.log(
|
|
600
|
-
`${LOG_PREFIX} Action result from ${message.persistentDeviceId}: ${message.toolId}.${message.action} -> ${message.ok ? "ok" : `error: ${message.error}`}`
|
|
601
|
-
);
|
|
602
|
-
forwardToDashboards("devtool-action-result", message);
|
|
603
|
-
}
|
|
604
|
-
);
|
|
605
|
-
socket.on("devtool-watch", (message) => {
|
|
606
|
-
handleDevtoolWatch(socket.id, message);
|
|
607
|
-
});
|
|
608
|
-
socket.on("request-devtool-state", (message) => {
|
|
609
|
-
withTargetUsers(
|
|
610
|
-
message.targetDeviceId,
|
|
611
|
-
(user) => io.to(user.id).emit("request-devtool-state", message),
|
|
612
|
-
`devtool state request ${message.toolId}`
|
|
613
|
-
);
|
|
614
|
-
});
|
|
615
|
-
socket.on("forget-device", (payload) => {
|
|
616
|
-
const targetDeviceId = payload?.deviceId;
|
|
617
|
-
if (!dashboardClients.includes(socket.id)) return;
|
|
618
|
-
if (!targetDeviceId || activeConnectionsMap.has(targetDeviceId)) return;
|
|
619
|
-
const target = allDevices.find((d) => d.deviceId === targetDeviceId);
|
|
620
|
-
users = users.filter((u) => u.deviceId !== targetDeviceId);
|
|
621
|
-
const historyIndex = allDevices.findIndex(
|
|
622
|
-
(d) => d.deviceId === targetDeviceId
|
|
623
|
-
);
|
|
624
|
-
if (historyIndex !== -1) {
|
|
625
|
-
allDevices.splice(historyIndex, 1);
|
|
626
|
-
}
|
|
627
|
-
deviceCapabilities.delete(targetDeviceId);
|
|
628
|
-
for (const [socketId, mappedDeviceId] of deviceIdMap) {
|
|
629
|
-
if (mappedDeviceId === targetDeviceId) {
|
|
630
|
-
deviceIdMap.delete(socketId);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
brokerLog(
|
|
634
|
-
"info",
|
|
635
|
-
`Forgot device: ${target?.deviceName ?? targetDeviceId}`,
|
|
636
|
-
{ deviceId: targetDeviceId }
|
|
637
|
-
);
|
|
638
|
-
emitAllDevices();
|
|
639
|
-
});
|
|
640
|
-
socket.on("devtool-action", (message) => {
|
|
641
|
-
console.log(
|
|
642
|
-
`${LOG_PREFIX} Devtool action from dashboard: ${message.toolId}.${message.action} -> ${message.targetDeviceId}`
|
|
643
|
-
);
|
|
644
|
-
withTargetUsers(
|
|
645
|
-
message.targetDeviceId,
|
|
646
|
-
(user) => io.to(user.id).emit("devtool-action", message),
|
|
647
|
-
`devtool action ${message.toolId}.${message.action}`
|
|
648
|
-
);
|
|
649
|
-
});
|
|
650
|
-
});
|
|
651
|
-
return {
|
|
652
|
-
refreshAccess() {
|
|
653
|
-
const ready = canUseDesktop();
|
|
654
|
-
if (!ready) {
|
|
655
|
-
socketTiers.clear();
|
|
656
|
-
deviceCapabilities.clear();
|
|
657
|
-
brokerLogBuffer.length = 0;
|
|
658
|
-
watches = new WatchRegistry();
|
|
659
|
-
for (const device of [...users, ...allDevices]) {
|
|
660
|
-
delete device.licenseKey;
|
|
661
|
-
delete device.envVariables;
|
|
662
|
-
delete device.extraDeviceInfo;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
io.emit("buoy:access", { protocol: 1, ready });
|
|
666
|
-
emitAllDevices();
|
|
667
|
-
}
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
// src/createBroker.ts
|
|
672
|
-
import http from "http";
|
|
673
|
-
import { Server as SocketIOServer } from "socket.io";
|
|
674
|
-
var DEFAULT_BROKER_PORT = 42831;
|
|
675
|
-
var SOCKET_CONFIG = {
|
|
676
|
-
cors: {
|
|
677
|
-
origin: "*",
|
|
678
|
-
methods: ["GET", "POST"]
|
|
679
|
-
},
|
|
680
|
-
// Dev-tool snapshots are whole app-state dumps (a dehydrated React Query
|
|
681
|
-
// cache, a Redux/Zustand store, an aggregated event log, etc.) and a device
|
|
682
|
-
// pushes one per watched tool on connect. socket.io v3+ caps inbound messages
|
|
683
|
-
// at 1 MB by default and TERMINATES the connection ("transport error") when a
|
|
684
|
-
// single message exceeds it — which, with a dashboard watching all ~11 tools
|
|
685
|
-
// at once, makes a device's connection flap. These are local, trusted dev
|
|
686
|
-
// connections, so lift the cap well clear of any realistic state dump.
|
|
687
|
-
maxHttpBufferSize: 1e8,
|
|
688
|
-
// 100 MB
|
|
689
|
-
transports: ["websocket", "polling"],
|
|
690
|
-
pingTimeout: 3e4,
|
|
691
|
-
pingInterval: 25e3
|
|
692
|
-
};
|
|
693
|
-
function createBroker(options = {}) {
|
|
694
|
-
const port = options.port ?? DEFAULT_BROKER_PORT;
|
|
695
|
-
const httpServer = http.createServer((_req, res) => {
|
|
696
|
-
res.writeHead(200, { "content-type": "text/plain" });
|
|
697
|
-
res.end(`Buoy sync broker running on port ${port}`);
|
|
698
|
-
});
|
|
699
|
-
const io = new SocketIOServer(httpServer, {
|
|
700
|
-
...SOCKET_CONFIG,
|
|
701
|
-
...options.socketOptions
|
|
702
|
-
});
|
|
703
|
-
const routing = socketHandle({ io, canUseDesktop: options.canUseDesktop });
|
|
704
|
-
httpServer.on("error", (error) => {
|
|
705
|
-
options.onError?.(error);
|
|
706
|
-
});
|
|
707
|
-
httpServer.listen(port, () => {
|
|
708
|
-
options.onListening?.(port);
|
|
709
|
-
});
|
|
710
|
-
const close = () => new Promise((resolve) => {
|
|
711
|
-
io.close(() => {
|
|
712
|
-
httpServer.close(() => resolve());
|
|
713
|
-
});
|
|
714
|
-
});
|
|
715
|
-
return { io, httpServer, port, close, refreshAccess: routing.refreshAccess };
|
|
716
|
-
}
|
|
717
|
-
export {
|
|
718
|
-
DEFAULT_BROKER_PORT,
|
|
719
|
-
SOCKET_CONFIG,
|
|
720
|
-
SYNC_PROTOCOL_VERSION,
|
|
721
|
-
createBroker,
|
|
722
|
-
socketHandle
|
|
723
|
-
};
|
|
1
|
+
var U=1;function M(r){if(r?.tier==="pro"||r?.tier==="free")return r.tier;if(r?.tier==="locked"||r?.tier==="anonymous")return"locked";return r?.isPro==="1"||r?.isPro===true?"pro":"locked"}var k=(r,a)=>JSON.stringify([r,a]);var R=r=>JSON.parse(r);var B=class{constructor(){this.byDashboard=new Map;this.streamCounts=new Map;this.captureCounts=new Map}counts(r){return r==="stream"?this.streamCounts:this.captureCounts}bump(r,a,l){const c=this.counts(r);const s=(c.get(a)??0)+l;if(s>0)c.set(a,s);else c.delete(a)}effective(r){if((this.streamCounts.get(r)??0)>0)return"stream";if((this.captureCounts.get(r)??0)>0)return"capture";return null}activeKeys(){return[...new Set([...this.streamCounts.keys(),...this.captureCounts.keys()])]}watch(r,a,l){let c=this.byDashboard.get(r);if(!c){c=new Map;this.byDashboard.set(r,c)}const s=c.get(a);if(s!==l){if(s)this.bump(s,a,-1);this.bump(l,a,1);c.set(a,l)}return this.effective(a)}unwatch(r,a){const l=this.byDashboard.get(r);const c=l?.get(a);if(!l||!c)return{changed:false,mode:this.effective(a)};const s=this.effective(a);l.delete(a);this.bump(c,a,-1);const g=this.effective(a);return{changed:g!==s,mode:g}}releaseDashboard(r){const a=this.byDashboard.get(r);if(!a)return[];this.byDashboard.delete(r);const l=[];for(const[c,s]of a){const g=this.effective(c);this.bump(s,c,-1);const I=this.effective(c);if(I!==g)l.push({key:c,mode:I})}return l}};var H=200;var z=24*60*60*1e3;function T(r){if(!r)return void 0;try{const a=JSON.parse(r);if(!a||typeof a!=="object"||Array.isArray(a))return void 0;for(const l of["BUOY_LICENSE_KEY","BUOY_KEY","EXPO_PUBLIC_BUOY_KEY","VITE_BUOY_KEY","REACT_APP_BUOY_KEY","licenseKey"])delete a[l];return JSON.stringify(a)}catch{return void 0}}function L({io:r,canUseDesktop:a=()=>false}){const l="[SERVER]";let c=[];const s=[];let g=[];const I=new Map;const w=new Map;const x=new Map;const O=new Map;const C=new Map;let S=new B;const A=[];function y(e,t,o){console.log(`${l} ${t}`);const d={ts:Date.now(),level:e,message:t,data:o};A.push(d);if(A.length>H){A.shift()}E("broker-log",d)}function q(e){const t=new Set;for(const[o,d]of I){if(d!==e)continue;const u=O.get(o);if(u)t.add(u)}return t.size}function N(){if(!a()){r.emit("all-devices-update",[]);return}for(const t of[...c,...s]){if(t.deviceName==="Dashboard"||!t.isConnected)continue;const o=w.get(t.id)??"locked";t.tier=o==="pro"?"pro":o==="free"?"free":"locked";t.isPro=o==="pro";if(o!=="free"&&o!=="pro"){delete t.envVariables;delete t.extraDeviceInfo}}const e=Date.now();for(const t of s){if(!t.deviceId)continue;const o=q(t.deviceId);const d=o>1;if(d&&!t.collision){y("warn",`${o} 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:o})}else if(!d&&t.collision){y("info",`Device id collision cleared for "${t.deviceId}"`,{deviceId:t.deviceId})}t.collision=d}for(let t=s.length-1;t>=0;t--){const o=s[t];if(!o.isConnected&&o.lastSeenAt&&e-o.lastSeenAt>z){s.splice(t,1);if(o.deviceId){C.delete(o.deviceId)}}}r.emit("all-devices-update",s.filter(t=>t.deviceName!=="Dashboard").map(({licenseKey:t,...o})=>o))}function V(e,t){const o=c.find(h=>h.id===e);const d=I.get(e);I.delete(e);O.delete(e);w.delete(e);let u;if(d){for(const[h,b]of I){if(b===d)u=h}}if(d&&u){const h=c.findIndex(n=>n.deviceId===d);if(h!==-1&&c[h].id===e){c[h].id=u}const b=s.findIndex(n=>n.deviceId===d);if(b!==-1&&s[b].id===e){s[b].id=u}if(x.get(d)===e){x.set(d,u)}console.log(`${l} Client disconnected - ID: ${e}, Name: ${o?.deviceName||"Unknown"} - Device ${d} still connected via ${u}`)}else{c=c.filter(h=>h.id!==e);console.log(`${l} Client disconnected - ID: ${e}, Name: ${o?.deviceName||"Unknown"} - Removing user record`);if(d){const h=s.findIndex(b=>b.deviceId===d);if(h!==-1){s[h].isConnected=false;s[h].lastDisconnectReason=t;s[h].lastSeenAt=Date.now();x.delete(d)}}}const v=g.includes(e);g=g.filter(h=>h!==e);X(e);const $=Boolean(d&&u);y(v||$?"debug":"warn",$?`Socket closed for ${o?.deviceName||e} (${t??"unknown"}) \u2014 device still connected via another socket`:`Disconnected: ${o?.deviceName||e} (${t??"unknown"})`,{socketId:e,reason:t,wasDashboard:v,stillConnected:$});N();console.log(`${l} Updated users list: ${c.map(h=>h.deviceName).join(", ")}`)}function W({id:e,deviceName:t,deviceId:o,platform:d,extraDeviceInfo:u,envVariables:v,connectionNonce:$}){const h=t==="Dashboard";if(o){I.set(e,o);x.set(o,e);const i=c.findIndex(p=>p.deviceId===o);const f=s.findIndex(p=>p.deviceId===o);if(i!==-1){y("success",`Reconnected: ${t} (${d||"unknown platform"})`,{socketId:e,deviceId:o});c[i].id=e;if(d){c[i].platform=d}if(u){c[i].extraDeviceInfo=u}if(v){c[i].envVariables=v}c[i].isConnected=true;c[i].connectionNonce=$;if(f!==-1){s[f].id=e;s[f].isConnected=true;s[f].connectionNonce=$;s[f].lastSeenAt=Date.now();if(d){s[f].platform=d}if(u){s[f].extraDeviceInfo=u}if(v){s[f].envVariables=v}}else if(t!=="Dashboard"){s.push({id:e,deviceName:t,deviceId:o,platform:d,isConnected:true,extraDeviceInfo:u,envVariables:v,lastSeenAt:Date.now()})}N();console.log(`${l} Updated users list: ${c.map(p=>p.deviceName).join(", ")}`);return}}const b=i=>{const f=i.match(/(\d+)$/);return f?parseInt(f[1],10):1};let n=0;c.forEach(i=>{if(i.deviceName.startsWith(t)){const f=b(i.deviceName);if(f>=n){n=f}}});if(n>0&&!h){const i=`${t} #${n+1}`;y("warn",`Duplicate device name "${t}" \u2014 renamed to "${i}"`,{socketId:e,deviceId:o});t=i}if(e){const i={id:e,deviceName:t,deviceId:o,platform:d,isConnected:true,extraDeviceInfo:u,envVariables:v,connectionNonce:$,lastSeenAt:Date.now()};c.push(i);if(t!=="Dashboard"&&o){const f=s.findIndex(p=>p.deviceId===o);if(f!==-1){s[f]={...i}}else{s.push({...i})}}y(h?"debug":"info",`Connected: ${t}${d?` (${d})`:""}`,{socketId:e,deviceId:o});if(h&&e){g.push(e);console.log(`${l} Registered dashboard client with ID: ${e}`)}N();console.log(`${l} Updated device list: ${c.map(f=>f.deviceName).join(", ")}`)}}function j(e){return s.find(t=>t.deviceId===e)}function G(e,t){console.log(`${l} Message from ${t}: ${e}`);if(!a())return;r.emit("message",`Device ${t} sent message: ${e}`)}function E(e,t){if(!a())return;g=g.filter(o=>r.sockets.sockets.has(o));if(g.length===0){return}for(const o of g){r.sockets.sockets.get(o)?.emit(e,t)}}function _(e,t,o){if(!a())return;const d=v=>{const $=w.get(v.id);return $==="free"||$==="pro"};const u=j(e)?.deviceName;if(e==="No devices available"){console.log(`${l} Skipping ${o} - No devices available`);return}if(e==="All"){console.log(`${l} Broadcasting ${o} to all devices`);const v=c.filter($=>$.deviceName!=="Dashboard");console.log(`${l} Sending to ${v.length} devices: ${v.map($=>$.deviceName).join(", ")}`);v.filter(d).forEach(t)}else{const v=c.find($=>$.deviceId===e);if(v&&d(v)){console.log(`${l} Sending to target device ${v.deviceName} (${v.deviceId}) targetDeviceId: ${e}`);t(v)}else{console.log(`${l} Target device not found - DeviceId: ${e}, DeviceName: ${u}`)}}}function K(e,t){const[o,d]=R(e);const u=t===null?{toolId:d,targetDeviceId:o,watching:false,consolidated:true}:{toolId:d,targetDeviceId:o,watching:true,stream:t==="stream",consolidated:true};_(o,v=>r.to(v.id).emit("devtool-watch",u),`devtool ${t===null?"unwatch":`watch(${t})`} ${d}`)}function J(e,t){const o=k(t.targetDeviceId,t.toolId);if(t.watching){const v=t.stream===false?"capture":"stream";const $=S.watch(e,o,v);console.log(`${l} Dashboard ${e} watching "${t.toolId}" on ${t.targetDeviceId} (${v}, effective ${$})`);K(o,$);return}const{changed:d,mode:u}=S.unwatch(e,o);if(!d)return;console.log(u===null?`${l} Last dashboard stopped watching "${t.toolId}" on ${t.targetDeviceId}`:`${l} "${t.toolId}" on ${t.targetDeviceId} downgraded to ${u}`);K(o,u)}function X(e){for(const{key:t,mode:o}of S.releaseDashboard(e)){K(t,o)}}r.on("connection",e=>{const t=()=>e.emit("buoy:access",{protocol:1,ready:a()});t();e.on("buoy:access:request",t);e.use((n,i)=>{if(n[0]==="buoy:access:request"){i();return}if(!a()){t();return}const f=n[0]==="device-license";if(f){const D=n[1]??{};w.set(e.id,M(D))}const p=w.get(e.id)==="free"||w.get(e.id)==="pro";if(f||g.includes(e.id)||p)i();else t()});const{deviceName:o,deviceId:d,platform:u,extraDeviceInfo:v,envVariables:$,connectionNonce:h,instanceId:b}=e.handshake.query;if(typeof b==="string"&&b){O.set(e.id,b)}console.log(`${l} New connection - ID: ${e.id}, Name: ${o||"Unknown Device"}${d?`, DeviceId: ${d}`:""}${u?`, Platform: ${u}`:""}`);W({id:e.id,deviceName:o||"Unknown Device Name",deviceId:d,platform:u,extraDeviceInfo:void 0,envVariables:void 0,connectionNonce:h});if(a()&&o==="Dashboard"){for(const n of C.values()){e.emit("devtool-capabilities",n)}for(const n of A){e.emit("broker-log",n)}}e.on("device-license",n=>{const i=M(n??{});const f=i==="pro";w.set(e.id,i);const p=c.find(m=>m.id===e.id);if(p){p.isPro=f;p.tier=i;delete p.licenseKey}const D=p?.deviceId||I.get(e.id);if(i!=="locked"){for(const m of[...c,...s].filter(P=>P.id===e.id)){m.extraDeviceInfo=T(v);m.envVariables=T($)}}if(D){if(i==="locked")C.delete(D);const m=s.find(P=>P.deviceId===D);if(m){m.isPro=f;m.tier=i;delete m.licenseKey}}y("debug",`device-license from ${p?.deviceName||e.id}: tier=${i}`,{socketId:e.id});N()});e.on("disconnect",n=>V(e.id,n));e.on("message",n=>{G(n,o||"Unknown")});e.on("devtool-capabilities",n=>{if(!n||!Array.isArray(n.tools)||n.persistentDeviceId!==I.get(e.id))return;console.log(`${l} Capabilities from ${n.persistentDeviceId}: ${n.tools.map(i=>`${i.toolId}@v${i.version}`).join(", ")}`);if(n.protocolVersion!==U){y("warn",`Protocol version mismatch from ${n.persistentDeviceId}: device speaks v${n.protocolVersion}, broker speaks v${U} \u2014 update @buoy-gg packages or Buoy Desktop`,{deviceId:n.persistentDeviceId})}C.set(n.persistentDeviceId,n);E("devtool-capabilities",n);for(const i of S.activeKeys()){const[f,p]=R(i);if(f!=="All"&&f!==n.persistentDeviceId){continue}const D=S.effective(i);if(!D)continue;e.emit("devtool-watch",{toolId:p,targetDeviceId:f,watching:true,stream:D==="stream",consolidated:true})}});e.on("devtool-sync",n=>{if(!n||n.persistentDeviceId!==I.get(e.id))return;E("devtool-sync",n)});e.on("devtool-action-result",n=>{if(!n||n.persistentDeviceId!==I.get(e.id))return;console.log(`${l} Action result from ${n.persistentDeviceId}: ${n.toolId}.${n.action} -> ${n.ok?"ok":`error: ${n.error}`}`);E("devtool-action-result",n)});e.on("devtool-watch",n=>{J(e.id,n)});e.on("request-devtool-state",n=>{_(n.targetDeviceId,i=>r.to(i.id).emit("request-devtool-state",n),`devtool state request ${n.toolId}`)});e.on("forget-device",n=>{const i=n?.deviceId;if(!g.includes(e.id))return;if(!i||x.has(i))return;const f=s.find(D=>D.deviceId===i);c=c.filter(D=>D.deviceId!==i);const p=s.findIndex(D=>D.deviceId===i);if(p!==-1){s.splice(p,1)}C.delete(i);for(const[D,m]of I){if(m===i){I.delete(D)}}y("info",`Forgot device: ${f?.deviceName??i}`,{deviceId:i});N()});e.on("devtool-action",n=>{console.log(`${l} Devtool action from dashboard: ${n.toolId}.${n.action} -> ${n.targetDeviceId}`);_(n.targetDeviceId,i=>r.to(i.id).emit("devtool-action",n),`devtool action ${n.toolId}.${n.action}`)})});return{refreshAccess(){const e=a();if(!e){w.clear();C.clear();A.length=0;S=new B;for(const t of[...c,...s]){delete t.licenseKey;delete t.envVariables;delete t.extraDeviceInfo}}r.emit("buoy:access",{protocol:1,ready:e});N()}}}import Q from"http";import{Server as Z}from"socket.io";var Y=42831;var F={cors:{origin:"*",methods:["GET","POST"]},maxHttpBufferSize:1e8,transports:["websocket","polling"],pingTimeout:3e4,pingInterval:25e3};function ee(r={}){const a=r.port??Y;const l=Q.createServer((I,w)=>{w.writeHead(200,{"content-type":"text/plain"});w.end(`Buoy sync broker running on port ${a}`)});const c=new Z(l,{...F,...r.socketOptions});const s=L({io:c,canUseDesktop:r.canUseDesktop});l.on("error",I=>{r.onError?.(I)});l.listen(a,()=>{r.onListening?.(a)});const g=()=>new Promise(I=>{c.close(()=>{l.close(()=>I())})});return{io:c,httpServer:l,port:a,close:g,refreshAccess:s.refreshAccess}}export{Y as DEFAULT_BROKER_PORT,F as SOCKET_CONFIG,U as SYNC_PROTOCOL_VERSION,ee as createBroker,L as socketHandle};
|