@scp-dev/broker 0.2.0 → 0.3.0
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.ts +2 -0
- package/dist/index.js +65 -9
- package/package.json +3 -3
- package/src/bridge.ts +10 -3
- package/src/server.ts +73 -6
package/dist/index.d.ts
CHANGED
|
@@ -334,6 +334,8 @@ interface BrokerOptions {
|
|
|
334
334
|
link?: LinkFactory | undefined;
|
|
335
335
|
/** how often presence that decays without a request (a hook-only endpoint) is checked for changes (default 60 s) */
|
|
336
336
|
presenceEveryMs?: number | undefined;
|
|
337
|
+
/** how often the broker checks it still owns its socket and lock, stopping once another broker or a wiped state directory took them (default 2 s) */
|
|
338
|
+
ownershipEveryMs?: number | undefined;
|
|
337
339
|
/** where a judge model's key_env is read (default the broker's environment) */
|
|
338
340
|
env?: Record<string, string | undefined> | undefined;
|
|
339
341
|
/** the judge model client for a policy's `judge.model` (default the provider's HTTP API) */
|
package/dist/index.js
CHANGED
|
@@ -706,7 +706,8 @@ function startBridge(options) {
|
|
|
706
706
|
byMachine.set(view.machine, [...byMachine.get(view.machine) ?? [], view]);
|
|
707
707
|
}
|
|
708
708
|
if (full) {
|
|
709
|
-
|
|
709
|
+
core.dropMachine(machine);
|
|
710
|
+
for (const view of core.remoteViews()) if (view.machine !== account && !byMachine.has(view.machine)) core.dropMachine(view.machine);
|
|
710
711
|
}
|
|
711
712
|
for (const [name, list] of byMachine) {
|
|
712
713
|
try {
|
|
@@ -807,7 +808,8 @@ function startBridge(options) {
|
|
|
807
808
|
if (typeof params.machine !== "string" || params.machine === machine) return;
|
|
808
809
|
if (typeof params.own === "boolean") core.setOther(params.machine, !params.own);
|
|
809
810
|
try {
|
|
810
|
-
|
|
811
|
+
if (params.released === true) core.dropMachine(params.machine);
|
|
812
|
+
else core.mirror(params.machine, params.endpoints, { full: params.full === true });
|
|
811
813
|
} catch (error) {
|
|
812
814
|
log(`relay bridge: the endpoints of ${params.machine} were not applied: ${error instanceof Error ? error.message : String(error)}`);
|
|
813
815
|
}
|
|
@@ -1224,6 +1226,7 @@ function openDatabase(path) {
|
|
|
1224
1226
|
// src/server.ts
|
|
1225
1227
|
var SWEEP_MS = 10 * 6e4;
|
|
1226
1228
|
var PRESENCE_MS = 6e4;
|
|
1229
|
+
var OWNERSHIP_MS = 2e3;
|
|
1227
1230
|
var OBSERVER_METHODS = /* @__PURE__ */ new Set(["list", "snapshot", "watch", "log", "policy", "policy_check", "cloud_status", "bridge_relink"]);
|
|
1228
1231
|
var SCOPE_METHODS = /* @__PURE__ */ new Set([
|
|
1229
1232
|
"rules",
|
|
@@ -1304,6 +1307,7 @@ async function startBroker(options) {
|
|
|
1304
1307
|
let judge;
|
|
1305
1308
|
let bridge = null;
|
|
1306
1309
|
let listener;
|
|
1310
|
+
let socketIno;
|
|
1307
1311
|
try {
|
|
1308
1312
|
privateDir(dirname2(socket));
|
|
1309
1313
|
db = openDatabase(join4(dir, "relay.db"));
|
|
@@ -1320,6 +1324,7 @@ async function startBroker(options) {
|
|
|
1320
1324
|
alive: running
|
|
1321
1325
|
});
|
|
1322
1326
|
core.migrate();
|
|
1327
|
+
core.relabelMachine();
|
|
1323
1328
|
core.resetConnections();
|
|
1324
1329
|
core.sweep();
|
|
1325
1330
|
judge = new JudgeGate({ sql, core, now: options.now ?? Date.now, env: options.env ?? process.env, client: options.judgeClient, log, wake });
|
|
@@ -1356,6 +1361,7 @@ async function startBroker(options) {
|
|
|
1356
1361
|
});
|
|
1357
1362
|
listener.on("error", (error) => log(`relay broker: the socket listener failed: ${error.message}`));
|
|
1358
1363
|
chmodSync2(socket, 384);
|
|
1364
|
+
socketIno = lstatSync2(socket).ino;
|
|
1359
1365
|
} catch (error) {
|
|
1360
1366
|
listener?.close();
|
|
1361
1367
|
db?.close();
|
|
@@ -1369,16 +1375,39 @@ async function startBroker(options) {
|
|
|
1369
1375
|
judge.sweep();
|
|
1370
1376
|
}, SWEEP_MS);
|
|
1371
1377
|
const known = /* @__PURE__ */ new Map();
|
|
1372
|
-
|
|
1378
|
+
const wakes = /* @__PURE__ */ new Map();
|
|
1379
|
+
for (const view of localViews()) {
|
|
1380
|
+
known.set(view.id, view.presence);
|
|
1381
|
+
wakes.set(view.id, view.wake);
|
|
1382
|
+
}
|
|
1373
1383
|
const presenceTimer = setInterval(() => {
|
|
1374
1384
|
core.endExited();
|
|
1375
1385
|
const live = /* @__PURE__ */ new Set();
|
|
1376
1386
|
for (const view of localViews()) {
|
|
1377
1387
|
live.add(view.id);
|
|
1378
1388
|
if ((known.get(view.id) ?? "offline") !== view.presence) presenceChanged(view);
|
|
1389
|
+
else if (wakes.has(view.id) && wakes.get(view.id) !== view.wake) bridge?.changed(view.id);
|
|
1390
|
+
wakes.set(view.id, view.wake);
|
|
1379
1391
|
}
|
|
1380
1392
|
for (const id of known.keys()) if (!live.has(id)) known.delete(id);
|
|
1393
|
+
for (const id of wakes.keys()) if (!live.has(id)) wakes.delete(id);
|
|
1381
1394
|
}, options.presenceEveryMs ?? PRESENCE_MS);
|
|
1395
|
+
const ownership = setInterval(() => {
|
|
1396
|
+
if (closing || ownsState()) return;
|
|
1397
|
+
log(`relay broker: ${socket} or its lock was removed or taken over; stopping`);
|
|
1398
|
+
void stop();
|
|
1399
|
+
}, options.ownershipEveryMs ?? OWNERSHIP_MS);
|
|
1400
|
+
ownership.unref?.();
|
|
1401
|
+
function ownsSocket() {
|
|
1402
|
+
return lstatSync2(socket, { throwIfNoEntry: false })?.ino === socketIno;
|
|
1403
|
+
}
|
|
1404
|
+
function ownsState() {
|
|
1405
|
+
try {
|
|
1406
|
+
return ownsSocket() && readFileSync6(lock.path, "utf8") === lock.content;
|
|
1407
|
+
} catch {
|
|
1408
|
+
return false;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1382
1411
|
const remoteHooks = {
|
|
1383
1412
|
wake,
|
|
1384
1413
|
fleet: notifyFleet,
|
|
@@ -1467,6 +1496,26 @@ async function startBroker(options) {
|
|
|
1467
1496
|
function localViews() {
|
|
1468
1497
|
return core.list({ all: true }).filter((view) => core.machineOf(view.id) === null);
|
|
1469
1498
|
}
|
|
1499
|
+
function setWaker(s, endpoint) {
|
|
1500
|
+
const was = s.data.waker;
|
|
1501
|
+
if (was === endpoint) return;
|
|
1502
|
+
s.data.waker = endpoint;
|
|
1503
|
+
if (was) {
|
|
1504
|
+
const wake2 = core.whoami(was).wake;
|
|
1505
|
+
core.waker(was, false, { grace: s.data.transient === true });
|
|
1506
|
+
if (!presenceMoved(was) && core.whoami(was).wake !== wake2) bridge?.changed(was);
|
|
1507
|
+
}
|
|
1508
|
+
if (endpoint) {
|
|
1509
|
+
core.waker(endpoint, true);
|
|
1510
|
+
presenceMoved(endpoint);
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
function presenceMoved(id) {
|
|
1514
|
+
const view = core.whoami(id);
|
|
1515
|
+
if ((known.get(id) ?? "offline") === view.presence) return false;
|
|
1516
|
+
presenceChanged(view);
|
|
1517
|
+
return true;
|
|
1518
|
+
}
|
|
1470
1519
|
function presenceChanged(view) {
|
|
1471
1520
|
known.set(view.id, view.presence);
|
|
1472
1521
|
notifyFleet({ type: "presence", endpoint: view.address, presence: view.presence, harness: view.harness, labels: view.labels });
|
|
@@ -1618,6 +1667,7 @@ async function startBroker(options) {
|
|
|
1618
1667
|
s.data.closed = true;
|
|
1619
1668
|
clearTimeout(s.data.lineTimer);
|
|
1620
1669
|
connections.delete(s);
|
|
1670
|
+
if (!closing) setWaker(s, void 0);
|
|
1621
1671
|
if (s.data.endpoint && !closing && !s.data.transient && !s.data.lineage) {
|
|
1622
1672
|
const before = core.whoami(s.data.endpoint).presence;
|
|
1623
1673
|
const settled = core.disconnect(s.data.endpoint, { settle: !s.data.brief });
|
|
@@ -1911,7 +1961,8 @@ async function startBroker(options) {
|
|
|
1911
1961
|
case "wake": {
|
|
1912
1962
|
if (s.data.lineage && !s.data.own) throw new RelayError3("unauthorized", "only the endpoint's own session says how it wakes");
|
|
1913
1963
|
const changed = core.setWake(me, params.via, hookOf(s));
|
|
1914
|
-
|
|
1964
|
+
setWaker(s, params.via !== "none" && core.whoami(me).wake === params.via ? me : void 0);
|
|
1965
|
+
if (!presenceMoved(me) && changed) bridge?.changed(me);
|
|
1915
1966
|
return { changed };
|
|
1916
1967
|
}
|
|
1917
1968
|
case "rename": {
|
|
@@ -1948,11 +1999,13 @@ async function startBroker(options) {
|
|
|
1948
1999
|
return view;
|
|
1949
2000
|
}
|
|
1950
2001
|
case "event": {
|
|
2002
|
+
const wake2 = core.whoami(me).wake;
|
|
1951
2003
|
const row = core.event(me, params, hookOf(s));
|
|
1952
2004
|
notifyFleet({ type: "activity", ...row });
|
|
1953
2005
|
bridge?.activity(me, row);
|
|
1954
2006
|
const after = core.whoami(me);
|
|
1955
2007
|
if ((known.get(me) ?? "offline") !== after.presence) presenceChanged(after);
|
|
2008
|
+
else if (after.wake !== wake2) bridge?.changed(me);
|
|
1956
2009
|
return row;
|
|
1957
2010
|
}
|
|
1958
2011
|
case "inject": {
|
|
@@ -2214,6 +2267,7 @@ async function startBroker(options) {
|
|
|
2214
2267
|
closing = true;
|
|
2215
2268
|
clearInterval(sweeper);
|
|
2216
2269
|
clearInterval(presenceTimer);
|
|
2270
|
+
clearInterval(ownership);
|
|
2217
2271
|
await bridge?.stop();
|
|
2218
2272
|
if (idleTimer) clearTimeout(idleTimer);
|
|
2219
2273
|
for (const set of [...waiters.values()]) for (const fn of [...set]) fn();
|
|
@@ -2221,11 +2275,13 @@ async function startBroker(options) {
|
|
|
2221
2275
|
while (pending > 0 && Date.now() < deadline) await sleep2(5);
|
|
2222
2276
|
await Promise.race([judge.settled(), sleep2(Math.max(0, deadline - Date.now()))]);
|
|
2223
2277
|
for (const s of connections) s.end();
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2278
|
+
if (ownsSocket()) {
|
|
2279
|
+
listener.close();
|
|
2280
|
+
try {
|
|
2281
|
+
unlinkSync2(socket);
|
|
2282
|
+
} catch {
|
|
2283
|
+
}
|
|
2284
|
+
} else listener.unref();
|
|
2229
2285
|
if (socket !== join4(dir, "relay.sock")) {
|
|
2230
2286
|
try {
|
|
2231
2287
|
rmdirSync(dirname2(socket));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scp-dev/broker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Swarm Communication Protocol local broker: a unix socket JSON-RPC daemon over the relay core, its client and the bridge to an account hub.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://scp.dev",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"typecheck": "tsc -p ."
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@scp-dev/core": "0.
|
|
41
|
-
"@scp-dev/hub": "0.
|
|
40
|
+
"@scp-dev/core": "0.2.0",
|
|
41
|
+
"@scp-dev/hub": "0.6.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"tsup": "^8.5.1"
|
package/src/bridge.ts
CHANGED
|
@@ -300,14 +300,20 @@ export function startBridge(options: BridgeOptions): Bridge {
|
|
|
300
300
|
for (const fn of waitingForLink.splice(0)) fn()
|
|
301
301
|
}
|
|
302
302
|
|
|
303
|
-
/**
|
|
303
|
+
/**
|
|
304
|
+
* the other machines' endpoints, grouped by machine; with `full`, a known machine the hub no longer lists was released and its rows go,
|
|
305
|
+
* as do rows mirrored under this machine's own name from before it was renamed back to it
|
|
306
|
+
*/
|
|
304
307
|
function applyRemote(views: RemoteView[], full: boolean) {
|
|
305
308
|
const byMachine = new Map<string, RemoteView[]>()
|
|
306
309
|
for (const view of views) {
|
|
307
310
|
if (view.machine === machine) continue
|
|
308
311
|
byMachine.set(view.machine, [...(byMachine.get(view.machine) ?? []), view])
|
|
309
312
|
}
|
|
310
|
-
if (full)
|
|
313
|
+
if (full) {
|
|
314
|
+
core.dropMachine(machine)
|
|
315
|
+
for (const view of core.remoteViews()) if (view.machine !== account && !byMachine.has(view.machine)) core.dropMachine(view.machine)
|
|
316
|
+
}
|
|
311
317
|
for (const [name, list] of byMachine) {
|
|
312
318
|
try {
|
|
313
319
|
core.mirror(name, list, { full })
|
|
@@ -423,7 +429,8 @@ export function startBridge(options: BridgeOptions): Bridge {
|
|
|
423
429
|
if (typeof params.machine !== "string" || params.machine === machine) return
|
|
424
430
|
if (typeof params.own === "boolean") core.setOther(params.machine, !params.own)
|
|
425
431
|
try {
|
|
426
|
-
|
|
432
|
+
if (params.released === true) core.dropMachine(params.machine)
|
|
433
|
+
else core.mirror(params.machine, params.endpoints, { full: params.full === true })
|
|
427
434
|
} catch (error) {
|
|
428
435
|
log(`relay bridge: the endpoints of ${params.machine} were not applied: ${error instanceof Error ? error.message : String(error)}`)
|
|
429
436
|
}
|
package/src/server.ts
CHANGED
|
@@ -64,6 +64,8 @@ export interface BrokerOptions {
|
|
|
64
64
|
link?: LinkFactory | undefined
|
|
65
65
|
/** how often presence that decays without a request (a hook-only endpoint) is checked for changes (default 60 s) */
|
|
66
66
|
presenceEveryMs?: number | undefined
|
|
67
|
+
/** how often the broker checks it still owns its socket and lock, stopping once another broker or a wiped state directory took them (default 2 s) */
|
|
68
|
+
ownershipEveryMs?: number | undefined
|
|
67
69
|
/** where a judge model's key_env is read (default the broker's environment) */
|
|
68
70
|
env?: Record<string, string | undefined> | undefined
|
|
69
71
|
/** the judge model client for a policy's `judge.model` (default the provider's HTTP API) */
|
|
@@ -114,6 +116,8 @@ interface Connection {
|
|
|
114
116
|
process?: string
|
|
115
117
|
/** a one-shot CLI command's connection: its comings and goings are not fleet presence events */
|
|
116
118
|
brief?: boolean
|
|
119
|
+
/** the endpoint whose wake this connection set: it counts as its waker until it closes or sets none */
|
|
120
|
+
waker?: string | undefined
|
|
117
121
|
cwd?: string | undefined
|
|
118
122
|
watching: boolean
|
|
119
123
|
closed: boolean
|
|
@@ -126,6 +130,7 @@ interface Connection {
|
|
|
126
130
|
|
|
127
131
|
const SWEEP_MS = 10 * 60_000
|
|
128
132
|
const PRESENCE_MS = 60_000
|
|
133
|
+
const OWNERSHIP_MS = 2_000
|
|
129
134
|
const OBSERVER_METHODS = new Set(["list", "snapshot", "watch", "log", "policy", "policy_check", "cloud_status", "bridge_relink"])
|
|
130
135
|
const SCOPE_METHODS = new Set([
|
|
131
136
|
"rules",
|
|
@@ -219,6 +224,7 @@ export async function startBroker(options: BrokerOptions): Promise<Broker> {
|
|
|
219
224
|
let judge!: JudgeGate
|
|
220
225
|
let bridge: Bridge | null = null
|
|
221
226
|
let listener!: Server
|
|
227
|
+
let socketIno: number | bigint | undefined
|
|
222
228
|
try {
|
|
223
229
|
privateDir(dirname(socket))
|
|
224
230
|
db = openDatabase(join(dir, "relay.db"))
|
|
@@ -235,6 +241,7 @@ export async function startBroker(options: BrokerOptions): Promise<Broker> {
|
|
|
235
241
|
alive: running,
|
|
236
242
|
})
|
|
237
243
|
core.migrate()
|
|
244
|
+
core.relabelMachine()
|
|
238
245
|
core.resetConnections()
|
|
239
246
|
core.sweep()
|
|
240
247
|
judge = new JudgeGate({ sql, core, now: options.now ?? Date.now, env: options.env ?? process.env, client: options.judgeClient, log, wake })
|
|
@@ -271,6 +278,7 @@ export async function startBroker(options: BrokerOptions): Promise<Broker> {
|
|
|
271
278
|
})
|
|
272
279
|
listener.on("error", (error) => log(`relay broker: the socket listener failed: ${error.message}`))
|
|
273
280
|
chmodSync(socket, 0o600)
|
|
281
|
+
socketIno = lstatSync(socket).ino
|
|
274
282
|
} catch (error) {
|
|
275
283
|
listener?.close()
|
|
276
284
|
db?.close()
|
|
@@ -284,16 +292,43 @@ export async function startBroker(options: BrokerOptions): Promise<Broker> {
|
|
|
284
292
|
judge.sweep()
|
|
285
293
|
}, SWEEP_MS)
|
|
286
294
|
const known = new Map<string, Presence>()
|
|
287
|
-
|
|
295
|
+
const wakes = new Map<string, string | null | undefined>()
|
|
296
|
+
for (const view of localViews()) {
|
|
297
|
+
known.set(view.id, view.presence)
|
|
298
|
+
wakes.set(view.id, view.wake)
|
|
299
|
+
}
|
|
288
300
|
const presenceTimer = setInterval(() => {
|
|
289
301
|
core.endExited()
|
|
290
302
|
const live = new Set<string>()
|
|
291
303
|
for (const view of localViews()) {
|
|
292
304
|
live.add(view.id)
|
|
293
305
|
if ((known.get(view.id) ?? "offline") !== view.presence) presenceChanged(view)
|
|
306
|
+
else if (wakes.has(view.id) && wakes.get(view.id) !== view.wake) bridge?.changed(view.id)
|
|
307
|
+
wakes.set(view.id, view.wake)
|
|
294
308
|
}
|
|
295
309
|
for (const id of known.keys()) if (!live.has(id)) known.delete(id)
|
|
310
|
+
for (const id of wakes.keys()) if (!live.has(id)) wakes.delete(id)
|
|
296
311
|
}, options.presenceEveryMs ?? PRESENCE_MS)
|
|
312
|
+
const ownership = setInterval(() => {
|
|
313
|
+
if (closing || ownsState()) return
|
|
314
|
+
log(`relay broker: ${socket} or its lock was removed or taken over; stopping`)
|
|
315
|
+
void stop()
|
|
316
|
+
}, options.ownershipEveryMs ?? OWNERSHIP_MS)
|
|
317
|
+
ownership.unref?.()
|
|
318
|
+
|
|
319
|
+
/** whether the socket path is still this broker's listener */
|
|
320
|
+
function ownsSocket(): boolean {
|
|
321
|
+
return lstatSync(socket, { throwIfNoEntry: false })?.ino === socketIno
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** whether the socket path and the lock are still this broker's */
|
|
325
|
+
function ownsState(): boolean {
|
|
326
|
+
try {
|
|
327
|
+
return ownsSocket() && readFileSync(lock.path, "utf8") === lock.content
|
|
328
|
+
} catch {
|
|
329
|
+
return false
|
|
330
|
+
}
|
|
331
|
+
}
|
|
297
332
|
const remoteHooks: BridgeHooks = {
|
|
298
333
|
wake,
|
|
299
334
|
fleet: notifyFleet,
|
|
@@ -399,6 +434,30 @@ export async function startBroker(options: BrokerOptions): Promise<Broker> {
|
|
|
399
434
|
return core.list({ all: true }).filter((view) => core.machineOf(view.id) === null)
|
|
400
435
|
}
|
|
401
436
|
|
|
437
|
+
/** counts `s` as the waker of `endpoint`, or of none, and reports the presence that moves */
|
|
438
|
+
function setWaker(s: Conn<Connection>, endpoint: string | undefined) {
|
|
439
|
+
const was = s.data.waker
|
|
440
|
+
if (was === endpoint) return
|
|
441
|
+
s.data.waker = endpoint
|
|
442
|
+
if (was) {
|
|
443
|
+
const wake = core.whoami(was).wake
|
|
444
|
+
core.waker(was, false, { grace: s.data.transient === true })
|
|
445
|
+
if (!presenceMoved(was) && core.whoami(was).wake !== wake) bridge?.changed(was)
|
|
446
|
+
}
|
|
447
|
+
if (endpoint) {
|
|
448
|
+
core.waker(endpoint, true)
|
|
449
|
+
presenceMoved(endpoint)
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** reports an endpoint's presence when it is not the one last reported; whether it was not */
|
|
454
|
+
function presenceMoved(id: string): boolean {
|
|
455
|
+
const view = core.whoami(id)
|
|
456
|
+
if ((known.get(id) ?? "offline") === view.presence) return false
|
|
457
|
+
presenceChanged(view)
|
|
458
|
+
return true
|
|
459
|
+
}
|
|
460
|
+
|
|
402
461
|
/** an endpoint's presence changed outside a connection's own hello or close: watchers and the other machines hear it */
|
|
403
462
|
function presenceChanged(view: EndpointView) {
|
|
404
463
|
known.set(view.id, view.presence)
|
|
@@ -583,6 +642,7 @@ export async function startBroker(options: BrokerOptions): Promise<Broker> {
|
|
|
583
642
|
s.data.closed = true
|
|
584
643
|
clearTimeout(s.data.lineTimer)
|
|
585
644
|
connections.delete(s)
|
|
645
|
+
if (!closing) setWaker(s, undefined)
|
|
586
646
|
if (s.data.endpoint && !closing && !s.data.transient && !s.data.lineage) {
|
|
587
647
|
const before = core.whoami(s.data.endpoint).presence
|
|
588
648
|
const settled = core.disconnect(s.data.endpoint, { settle: !s.data.brief })
|
|
@@ -891,7 +951,8 @@ export async function startBroker(options: BrokerOptions): Promise<Broker> {
|
|
|
891
951
|
case "wake": {
|
|
892
952
|
if (s.data.lineage && !s.data.own) throw new RelayError("unauthorized", "only the endpoint's own session says how it wakes")
|
|
893
953
|
const changed = core.setWake(me, params.via, hookOf(s))
|
|
894
|
-
|
|
954
|
+
setWaker(s, params.via !== "none" && core.whoami(me).wake === params.via ? me : undefined)
|
|
955
|
+
if (!presenceMoved(me) && changed) bridge?.changed(me)
|
|
895
956
|
return { changed }
|
|
896
957
|
}
|
|
897
958
|
case "rename": {
|
|
@@ -928,11 +989,13 @@ export async function startBroker(options: BrokerOptions): Promise<Broker> {
|
|
|
928
989
|
return view
|
|
929
990
|
}
|
|
930
991
|
case "event": {
|
|
992
|
+
const wake = core.whoami(me).wake
|
|
931
993
|
const row = core.event(me, params, hookOf(s))
|
|
932
994
|
notifyFleet({ type: "activity", ...row })
|
|
933
995
|
bridge?.activity(me, row)
|
|
934
996
|
const after = core.whoami(me)
|
|
935
997
|
if ((known.get(me) ?? "offline") !== after.presence) presenceChanged(after)
|
|
998
|
+
else if (after.wake !== wake) bridge?.changed(me)
|
|
936
999
|
return row
|
|
937
1000
|
}
|
|
938
1001
|
case "inject": {
|
|
@@ -1210,6 +1273,7 @@ export async function startBroker(options: BrokerOptions): Promise<Broker> {
|
|
|
1210
1273
|
closing = true
|
|
1211
1274
|
clearInterval(sweeper)
|
|
1212
1275
|
clearInterval(presenceTimer)
|
|
1276
|
+
clearInterval(ownership)
|
|
1213
1277
|
await bridge?.stop()
|
|
1214
1278
|
if (idleTimer) clearTimeout(idleTimer)
|
|
1215
1279
|
for (const set of [...waiters.values()]) for (const fn of [...set]) fn()
|
|
@@ -1217,10 +1281,13 @@ export async function startBroker(options: BrokerOptions): Promise<Broker> {
|
|
|
1217
1281
|
while (pending > 0 && Date.now() < deadline) await sleep(5)
|
|
1218
1282
|
await Promise.race([judge.settled(), sleep(Math.max(0, deadline - Date.now()))])
|
|
1219
1283
|
for (const s of connections) s.end()
|
|
1220
|
-
listener
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1284
|
+
// closing a listener unlinks its path, which another broker may own by now
|
|
1285
|
+
if (ownsSocket()) {
|
|
1286
|
+
listener.close()
|
|
1287
|
+
try {
|
|
1288
|
+
unlinkSync(socket)
|
|
1289
|
+
} catch {}
|
|
1290
|
+
} else listener.unref()
|
|
1224
1291
|
if (socket !== join(dir, "relay.sock")) {
|
|
1225
1292
|
try {
|
|
1226
1293
|
rmdirSync(dirname(socket))
|