@jonathanyee/devtunnel-ctl 0.0.0-stage → 0.1.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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +281 -2
  3. package/dist/auth.d.ts +23 -0
  4. package/dist/auth.js +150 -0
  5. package/dist/auth.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.js +206 -0
  8. package/dist/cli.js.map +1 -0
  9. package/dist/config.d.ts +22 -0
  10. package/dist/config.js +94 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/core.d.ts +16 -0
  13. package/dist/core.js +112 -0
  14. package/dist/core.js.map +1 -0
  15. package/dist/daemon.d.ts +31 -0
  16. package/dist/daemon.js +153 -0
  17. package/dist/daemon.js.map +1 -0
  18. package/dist/forwarding.d.ts +15 -0
  19. package/dist/forwarding.js +87 -0
  20. package/dist/forwarding.js.map +1 -0
  21. package/dist/launchd.d.ts +10 -0
  22. package/dist/launchd.js +131 -0
  23. package/dist/launchd.js.map +1 -0
  24. package/dist/lock.d.ts +10 -0
  25. package/dist/lock.js +110 -0
  26. package/dist/lock.js.map +1 -0
  27. package/dist/log.d.ts +26 -0
  28. package/dist/log.js +67 -0
  29. package/dist/log.js.map +1 -0
  30. package/dist/main.d.ts +2 -0
  31. package/dist/main.js +5 -0
  32. package/dist/main.js.map +1 -0
  33. package/dist/paths.d.ts +4 -0
  34. package/dist/paths.js +39 -0
  35. package/dist/paths.js.map +1 -0
  36. package/dist/probe.d.ts +1 -0
  37. package/dist/probe.js +45 -0
  38. package/dist/probe.js.map +1 -0
  39. package/dist/recovery.d.ts +77 -0
  40. package/dist/recovery.js +265 -0
  41. package/dist/recovery.js.map +1 -0
  42. package/dist/rpc.d.ts +30 -0
  43. package/dist/rpc.js +208 -0
  44. package/dist/rpc.js.map +1 -0
  45. package/dist/sdk-transport.d.ts +29 -0
  46. package/dist/sdk-transport.js +239 -0
  47. package/dist/sdk-transport.js.map +1 -0
  48. package/npm-shrinkwrap.json +809 -0
  49. package/package.json +31 -4
package/dist/daemon.js ADDED
@@ -0,0 +1,153 @@
1
+ import { join } from 'node:path';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { Store, validateConfig } from "./config.js";
4
+ import { TokenProvider } from "./auth.js";
5
+ import { SdkTransport } from "./sdk-transport.js";
6
+ import { Forwarder } from "./forwarding.js";
7
+ import { probeSsh } from "./probe.js";
8
+ import { Recovery } from "./recovery.js";
9
+ import { RpcServer } from "./rpc.js";
10
+ import { Log } from "./log.js";
11
+ import { DaemonLock } from "./lock.js";
12
+ import { bounded, Fault, safeFault } from "./core.js";
13
+ export class Daemon {
14
+ store;
15
+ options;
16
+ log;
17
+ lock;
18
+ instance = randomUUID();
19
+ config;
20
+ tokens;
21
+ transport;
22
+ rpc;
23
+ controllers = new Map();
24
+ mutations = Promise.resolve();
25
+ queued = 0;
26
+ stopping = false;
27
+ stopPromise;
28
+ constructor(store, options = {}) {
29
+ this.store = store;
30
+ this.options = options;
31
+ this.log = new Log(store.dir);
32
+ this.lock = new DaemonLock(store.dir);
33
+ }
34
+ async start() {
35
+ await this.store.init();
36
+ await this.lock.acquire();
37
+ try {
38
+ await this.log.init();
39
+ this.config = await this.store.load();
40
+ this.tokens = new TokenProvider(this.config.helperPath);
41
+ this.transport = this.options.transport?.(this.tokens) ?? new SdkTransport(this.tokens);
42
+ this.rpc = new RpcServer(join(this.store.dir, 'control.sock'), req => this.handle(req), fault => this.writeLog({ event: 'control-error', message: fault.message }));
43
+ await this.rpc.listen();
44
+ for (const profile of this.config.profiles)
45
+ this.create(profile);
46
+ await this.log.write({ event: 'daemon-ready', message: 'Private control socket ready.' });
47
+ }
48
+ catch (error) {
49
+ await this.stop();
50
+ throw error;
51
+ }
52
+ }
53
+ writeLog(entry) {
54
+ void this.log.write(entry).catch(() => {
55
+ const fault = new Fault('internal', 'Cannot maintain bounded private service logs; stopping daemon.');
56
+ this.options.fatal?.(fault);
57
+ void this.stop().catch(stopError => this.options.fatal?.(safeFault(stopError)));
58
+ });
59
+ }
60
+ create(profile) {
61
+ const forwarder = new Forwarder(profile.localPort);
62
+ const controller = new Recovery(profile, {
63
+ transport: this.transport, forwarder, probe: signal => probeSsh(profile.localPort, signal),
64
+ log: status => this.logStatus(status),
65
+ });
66
+ this.controllers.set(profile.name, controller);
67
+ controller.start();
68
+ }
69
+ logStatus(status) {
70
+ this.writeLog({ event: 'state', profile: status.name, state: status.state, attempt: status.attempt,
71
+ relayConnected: status.relayConnected,
72
+ ...(status.cause ? { message: status.cause.message } : {}),
73
+ ...(status.lastHealthyAt !== undefined ? { lastHealthyAt: status.lastHealthyAt } : {}),
74
+ ...(status.nextRetryAt !== undefined ? { nextRetryAt: status.nextRetryAt } : {}),
75
+ });
76
+ }
77
+ handle(req) {
78
+ if (this.stopping)
79
+ return Promise.reject(new Fault('daemon-unavailable', 'Daemon is shutting down.'));
80
+ if (req.command === 'status')
81
+ return Promise.resolve({ pid: process.pid, profiles: [...this.controllers.values()].map(c => c.status()) });
82
+ if (req.command === 'logs')
83
+ return Promise.resolve({ instance: this.instance, entries: this.log.snapshot() });
84
+ if (this.queued >= 32)
85
+ return Promise.reject(new Fault('throttled', 'Too many pending configuration changes.'));
86
+ this.queued++;
87
+ const mutation = this.mutations.then(async () => {
88
+ if (this.stopping)
89
+ throw new Fault('daemon-unavailable', 'Daemon is shutting down.');
90
+ const config = this.config;
91
+ if (req.command === 'add') {
92
+ const next = validateConfig({ ...config, profiles: [...config.profiles, req.profile] });
93
+ await this.store.save(next);
94
+ this.config = next;
95
+ if (!this.stopping)
96
+ this.create(req.profile);
97
+ return { saved: true, name: req.profile.name };
98
+ }
99
+ const profiles = config.profiles.filter(p => req.name === 'all' || p.name === req.name);
100
+ if (!profiles.length)
101
+ throw new Fault('invalid-config', 'No matching profile. Use profile list to see configured names.');
102
+ if (req.command !== 'reconnect') {
103
+ const paused = req.command === 'pause';
104
+ const names = new Set(profiles.map(p => p.name));
105
+ const next = validateConfig({ ...config, profiles: config.profiles.map(p => names.has(p.name) ? { ...p, paused } : p) });
106
+ await this.store.save(next);
107
+ this.config = next;
108
+ }
109
+ if (!this.stopping) {
110
+ await Promise.all(profiles.map(async (profile) => {
111
+ const controller = this.controllers.get(profile.name);
112
+ if (req.command === 'pause')
113
+ await controller.pause();
114
+ else if (req.command === 'resume')
115
+ controller.resume();
116
+ else {
117
+ this.tokens.invalidate(profile);
118
+ controller.reconnect();
119
+ }
120
+ }));
121
+ }
122
+ return { profiles: profiles.map(p => this.controllers.get(p.name).status()) };
123
+ });
124
+ this.mutations = mutation.catch(() => { });
125
+ return mutation.finally(() => { this.queued--; });
126
+ }
127
+ stop() {
128
+ if (this.stopPromise)
129
+ return this.stopPromise;
130
+ this.stopping = true;
131
+ this.stopPromise = (async () => {
132
+ const failures = [];
133
+ const attempt = async (action) => {
134
+ try {
135
+ await action();
136
+ }
137
+ catch (error) {
138
+ failures.push(safeFault(error));
139
+ }
140
+ };
141
+ await attempt(() => this.rpc?.close() ?? Promise.resolve());
142
+ await attempt(() => bounded(() => this.mutations, 2500));
143
+ await Promise.all([...this.controllers.values()].map(controller => attempt(() => bounded(() => controller.stop(), 3000))));
144
+ this.tokens?.clear();
145
+ await attempt(() => bounded(() => this.log.flush(), 1000));
146
+ await attempt(() => this.lock.release());
147
+ if (failures.length)
148
+ throw new Fault('internal', `Daemon shutdown had ${failures.length} bounded cleanup failure(s).`);
149
+ })();
150
+ return this.stopPromise;
151
+ }
152
+ }
153
+ //# sourceMappingURL=daemon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../src/daemon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { Duplex } from 'node:stream';
2
+ export type OpenStream = (signal: AbortSignal) => Promise<Duplex>;
3
+ export declare class Forwarder {
4
+ readonly port: number;
5
+ private readonly openTimeoutMs;
6
+ private servers;
7
+ private target;
8
+ private generation;
9
+ private readonly sockets;
10
+ constructor(port: number, openTimeoutMs?: number);
11
+ setTarget(target: OpenStream | undefined): void;
12
+ listen(): Promise<void>;
13
+ private accept;
14
+ close(): Promise<void>;
15
+ }
@@ -0,0 +1,87 @@
1
+ import net from 'node:net';
2
+ import { bounded, Fault, safeFault } from "./core.js";
3
+ export class Forwarder {
4
+ port;
5
+ openTimeoutMs;
6
+ servers = [];
7
+ target;
8
+ generation = 0;
9
+ sockets = new Set();
10
+ constructor(port, openTimeoutMs = 3000) { this.port = port; this.openTimeoutMs = openTimeoutMs; }
11
+ setTarget(target) {
12
+ this.generation++;
13
+ this.target = target;
14
+ for (const socket of this.sockets)
15
+ socket.destroy();
16
+ }
17
+ async listen() {
18
+ if (this.servers.length)
19
+ return;
20
+ try {
21
+ for (const host of ['127.0.0.1', '::1']) {
22
+ const server = net.createServer({ allowHalfOpen: true }, socket => this.accept(socket));
23
+ this.servers.push(server);
24
+ await new Promise((resolve, reject) => {
25
+ const error = (e) => { server.off('listening', listening); reject(e); };
26
+ const listening = () => { server.off('error', error); resolve(); };
27
+ server.once('error', error);
28
+ server.once('listening', listening);
29
+ server.listen({ port: this.port, host, ipv6Only: true, exclusive: true });
30
+ });
31
+ server.on('error', () => { this.setTarget(undefined); });
32
+ }
33
+ }
34
+ catch (error) {
35
+ await this.close();
36
+ throw safeFault(error);
37
+ }
38
+ }
39
+ accept(socket) {
40
+ socket.on('error', () => { socket.destroy(); });
41
+ if (!this.target || this.sockets.size >= 256) {
42
+ socket.destroy();
43
+ return;
44
+ }
45
+ this.sockets.add(socket);
46
+ socket.setNoDelay(true);
47
+ const controller = new AbortController();
48
+ let stream;
49
+ socket.once('close', () => {
50
+ this.sockets.delete(socket);
51
+ controller.abort();
52
+ stream?.destroy();
53
+ });
54
+ const target = this.target, generation = this.generation;
55
+ void bounded(target, this.openTimeoutMs, controller.signal, late => { late.destroy(); }).then(result => {
56
+ stream = result;
57
+ if (socket.destroyed || generation !== this.generation) {
58
+ result.destroy();
59
+ socket.destroy();
60
+ return;
61
+ }
62
+ let remoteEnded = false;
63
+ result.once('end', () => { remoteEnded = true; });
64
+ result.on('error', () => { socket.destroy(); result.destroy(); });
65
+ result.once('close', () => {
66
+ // pipe() drains queued writes on EOF; destroying the socket here would truncate them.
67
+ if (!remoteEnded)
68
+ socket.destroy();
69
+ });
70
+ socket.pipe(result);
71
+ result.pipe(socket);
72
+ }, () => socket.destroy());
73
+ }
74
+ async close() {
75
+ this.setTarget(undefined);
76
+ const servers = this.servers.splice(0);
77
+ await bounded(() => Promise.all(servers.map(server => new Promise((resolve, reject) => {
78
+ server.close(error => {
79
+ if (error && 'code' in error && error.code !== 'ERR_SERVER_NOT_RUNNING')
80
+ reject(error);
81
+ else
82
+ resolve();
83
+ });
84
+ }))).then(() => { }), 1000);
85
+ }
86
+ }
87
+ //# sourceMappingURL=forwarding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forwarding.js","sourceRoot":"","sources":["../src/forwarding.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { Store } from './config.ts';
2
+ interface AgentPaths {
3
+ label: string;
4
+ node: string;
5
+ entry: string;
6
+ state: string;
7
+ }
8
+ export declare function launchAgent(paths: AgentPaths): string;
9
+ export declare function service(action: string, store: Store, entry: string): Promise<unknown>;
10
+ export {};
@@ -0,0 +1,131 @@
1
+ import { isAbsolute, join } from 'node:path';
2
+ import { homedir } from 'node:os';
3
+ import { realpath, mkdir, writeFile, lstat, unlink } from 'node:fs/promises';
4
+ import { createHash } from 'node:crypto';
5
+ import { spawn } from 'node:child_process';
6
+ import { bounded, Fault } from "./core.js";
7
+ import { DaemonLock } from "./lock.js";
8
+ import { request } from "./rpc.js";
9
+ import { executable } from "./paths.js";
10
+ const xml = (value) => value.replace(/[<>&"']/g, ch => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;', "'": '&apos;' })[ch]);
11
+ export function launchAgent(paths) {
12
+ if (![paths.node, paths.entry, paths.state].every(p => isAbsolute(p) && !/[\0\r\n]/.test(p))) {
13
+ throw new Fault('invalid-config', 'launchd requires absolute runtime, entrypoint and state paths.');
14
+ }
15
+ return `<?xml version="1.0" encoding="UTF-8"?>
16
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
17
+ <plist version="1.0"><dict>
18
+ <key>Label</key><string>${xml(paths.label)}</string>
19
+ <key>ProgramArguments</key><array>${[paths.node, paths.entry, '--state-dir', paths.state, 'daemon'].map(arg => `<string>${xml(arg)}</string>`).join('')}</array>
20
+ <key>RunAtLoad</key><true/>
21
+ <key>KeepAlive</key><true/>
22
+ <key>ThrottleInterval</key><integer>15</integer>
23
+ <key>ProcessType</key><string>Background</string>
24
+ <key>StandardOutPath</key><string>/dev/null</string>
25
+ <key>StandardErrorPath</key><string>/dev/null</string>
26
+ </dict></plist>
27
+ `;
28
+ }
29
+ async function launchctl(args, allowAbsent = false) {
30
+ return bounded(signal => new Promise((resolve, reject) => {
31
+ const child = spawn('/bin/launchctl', args, { shell: false, stdio: ['ignore', 'pipe', 'pipe'] });
32
+ let bytes = 0;
33
+ const abort = () => { child.kill('SIGKILL'); };
34
+ signal.addEventListener('abort', abort, { once: true });
35
+ child.stdout.on('data', b => { bytes += b.length; if (bytes > 65536)
36
+ abort(); });
37
+ child.stderr.on('data', b => { bytes += b.length; if (bytes > 65536)
38
+ abort(); });
39
+ child.on('error', () => { signal.removeEventListener('abort', abort); reject(new Fault('internal', 'Cannot execute launchctl.')); });
40
+ child.on('close', code => {
41
+ signal.removeEventListener('abort', abort);
42
+ if (code === 0)
43
+ resolve({ loaded: true });
44
+ else if (allowAbsent && code === 113)
45
+ resolve({ loaded: false });
46
+ else
47
+ reject(new Fault('internal', 'launchctl failed. Check the service registration with launchctl print gui/$(id -u)/LABEL; no persistent action is assumed successful.'));
48
+ });
49
+ }), 5000);
50
+ }
51
+ export async function service(action, store, entry) {
52
+ if (process.platform !== 'darwin' || process.getuid?.() === 0)
53
+ throw new Fault('invalid-config', 'Service management requires a non-root macOS user.');
54
+ const label = `local.devtunnel-ctl.${createHash('sha256').update(store.dir).digest('hex').slice(0, 12)}`;
55
+ const directory = join(homedir(), 'Library', 'LaunchAgents');
56
+ const path = join(directory, label + '.plist');
57
+ const target = `gui/${process.getuid()}`;
58
+ if (action === 'install') {
59
+ const lock = new DaemonLock(store.dir);
60
+ await lock.acquire();
61
+ try {
62
+ const config = await store.load();
63
+ const node = await executable(process.execPath), main = await realpath(entry), helper = await executable(config.helperPath);
64
+ await mkdir(directory, { recursive: true, mode: 0o700 });
65
+ try {
66
+ await writeFile(path, launchAgent({ label, node, entry: main, state: store.dir }), { flag: 'wx', mode: 0o600 });
67
+ }
68
+ catch (error) {
69
+ if (error instanceof Error && 'code' in error && error.code === 'EEXIST') {
70
+ throw new Fault('invalid-config', 'Service is already installed. Stop and uninstall it before reinstalling paths.');
71
+ }
72
+ throw error;
73
+ }
74
+ try {
75
+ await store.save({ ...config, helperPath: helper });
76
+ }
77
+ catch (error) {
78
+ await unlink(path);
79
+ throw error;
80
+ }
81
+ return { installed: true, loaded: false, label, path, node, entry: main, helper };
82
+ }
83
+ finally {
84
+ await lock.release();
85
+ }
86
+ }
87
+ if (action === 'status') {
88
+ let installed = true;
89
+ try {
90
+ await lstat(path);
91
+ }
92
+ catch (error) {
93
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
94
+ installed = false;
95
+ else
96
+ throw error;
97
+ }
98
+ const result = await launchctl(['print', `${target}/${label}`], true);
99
+ if (!result.loaded)
100
+ return { label, installed, loaded: false, responsive: false };
101
+ try {
102
+ const status = await request(join(store.dir, 'control.sock'), { v: 1, command: 'status' });
103
+ return { label, installed, loaded: true, responsive: true, status };
104
+ }
105
+ catch {
106
+ return { label, installed, loaded: true, responsive: false,
107
+ detail: 'Registered service is unresponsive. Stop it, then run daemon in a terminal to see its sanitized startup error.' };
108
+ }
109
+ }
110
+ const info = await lstat(path);
111
+ if (!info.isFile() || info.isSymbolicLink() || info.uid !== process.getuid?.() || (info.mode & 0o077)) {
112
+ throw new Fault('invalid-config', 'Service plist is not a private owned file.');
113
+ }
114
+ if (action === 'start') {
115
+ await launchctl(['bootstrap', target, path]);
116
+ return { loaded: true, label };
117
+ }
118
+ if (action === 'stop') {
119
+ await launchctl(['bootout', `${target}/${label}`]);
120
+ return { loaded: false, label };
121
+ }
122
+ if (action === 'uninstall') {
123
+ const result = await launchctl(['print', `${target}/${label}`], true);
124
+ if (result.loaded)
125
+ await launchctl(['bootout', `${target}/${label}`]);
126
+ await unlink(path);
127
+ return { installed: false, loaded: false, label };
128
+ }
129
+ throw new Fault('invalid-config', 'Use service install, start, stop, uninstall or status.');
130
+ }
131
+ //# sourceMappingURL=launchd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launchd.js","sourceRoot":"","sources":["../src/launchd.ts"],"names":[],"mappings":"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"}
package/dist/lock.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export declare class DaemonLock {
2
+ private readonly path;
3
+ private readonly candidate;
4
+ private readonly nonce;
5
+ private owned;
6
+ constructor(dir: string);
7
+ acquire(): Promise<void>;
8
+ private reclaim;
9
+ release(): Promise<void>;
10
+ }
package/dist/lock.js ADDED
@@ -0,0 +1,110 @@
1
+ import { mkdir, writeFile, readFile, lstat, unlink, rmdir, rename, readdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+ import { Fault, record } from "./core.js";
5
+ import { privatePath } from "./paths.js";
6
+ export class DaemonLock {
7
+ path;
8
+ candidate;
9
+ nonce = randomUUID();
10
+ owned = false;
11
+ constructor(dir) {
12
+ this.path = join(dir, 'daemon.lock');
13
+ this.candidate = join(dir, `.daemon-lock-${this.nonce}`);
14
+ }
15
+ async acquire() {
16
+ await mkdir(this.candidate, { mode: 0o700 });
17
+ const marker = `owner-${this.nonce}.json`;
18
+ try {
19
+ await writeFile(join(this.candidate, marker), JSON.stringify({ pid: process.pid, nonce: this.nonce }), { flag: 'wx', mode: 0o600 });
20
+ for (let attempt = 0; attempt < 5; attempt++) {
21
+ try {
22
+ // A populated candidate atomically replaces only an absent or empty directory.
23
+ await rename(this.candidate, this.path);
24
+ this.owned = true;
25
+ return;
26
+ }
27
+ catch (error) {
28
+ if (!(error instanceof Error && 'code' in error &&
29
+ (error.code === 'ENOTEMPTY' || error.code === 'EEXIST')))
30
+ throw error;
31
+ }
32
+ if (!await this.reclaim())
33
+ throw new Fault('daemon-unavailable', 'A live daemon owns the state lock.');
34
+ }
35
+ throw new Fault('daemon-unavailable', 'Daemon lock is changing; retry after the other starter finishes.');
36
+ }
37
+ finally {
38
+ if (!this.owned) {
39
+ await unlink(join(this.candidate, marker));
40
+ await rmdir(this.candidate);
41
+ }
42
+ }
43
+ }
44
+ async reclaim() {
45
+ try {
46
+ await privatePath(this.path, true);
47
+ const names = await readdir(this.path);
48
+ if (!names.length)
49
+ return true;
50
+ if (names.length !== 1 || !/^owner-[a-z0-9-]{1,64}\.json$/.test(names[0])) {
51
+ throw new Fault('daemon-unavailable', 'Unrecognized daemon lock contents; refusing automatic cleanup.');
52
+ }
53
+ const marker = join(this.path, names[0]);
54
+ await privatePath(marker);
55
+ if ((await lstat(marker)).size > 1024)
56
+ throw new Fault('daemon-unavailable', 'Invalid daemon lock record.');
57
+ const owner = JSON.parse(await readFile(marker, 'utf8'));
58
+ if (!record(owner) || !Number.isSafeInteger(owner.pid) || Number(owner.pid) <= 0 ||
59
+ typeof owner.nonce !== 'string' || names[0] !== `owner-${owner.nonce}.json`) {
60
+ throw new Fault('daemon-unavailable', 'Invalid daemon lock record.');
61
+ }
62
+ try {
63
+ process.kill(Number(owner.pid), 0);
64
+ return false;
65
+ }
66
+ catch (error) {
67
+ if (!(error instanceof Error && 'code' in error && error.code === 'ESRCH'))
68
+ return false;
69
+ }
70
+ // Unique marker names keep a racing reclaimer from deleting a new owner's record.
71
+ await unlink(marker);
72
+ try {
73
+ await rmdir(this.path);
74
+ }
75
+ catch (error) {
76
+ if (!(error instanceof Error && 'code' in error &&
77
+ (error.code === 'ENOENT' || error.code === 'ENOTEMPTY' || error.code === 'EEXIST')))
78
+ throw error;
79
+ }
80
+ return true;
81
+ }
82
+ catch (error) {
83
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
84
+ return true;
85
+ throw error;
86
+ }
87
+ }
88
+ async release() {
89
+ if (!this.owned)
90
+ return;
91
+ const owner = join(this.path, `owner-${this.nonce}.json`);
92
+ const info = await lstat(owner);
93
+ if (info.isSymbolicLink() || !info.isFile())
94
+ throw new Fault('internal', 'Daemon lock ownership changed.');
95
+ const value = JSON.parse(await readFile(owner, 'utf8'));
96
+ if (value.nonce !== this.nonce)
97
+ throw new Fault('internal', 'Daemon lock ownership changed.');
98
+ await unlink(owner);
99
+ try {
100
+ await rmdir(this.path);
101
+ }
102
+ catch (error) {
103
+ if (!(error instanceof Error && 'code' in error &&
104
+ (error.code === 'ENOENT' || error.code === 'ENOTEMPTY' || error.code === 'EEXIST')))
105
+ throw error;
106
+ }
107
+ this.owned = false;
108
+ }
109
+ }
110
+ //# sourceMappingURL=lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.js","sourceRoot":"","sources":["../src/lock.ts"],"names":[],"mappings":"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"}
package/dist/log.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ export interface LogEntry {
2
+ event: string;
3
+ profile?: string;
4
+ state?: string;
5
+ message?: string;
6
+ attempt?: number;
7
+ relayConnected?: boolean;
8
+ lastHealthyAt?: number;
9
+ nextRetryAt?: number;
10
+ }
11
+ export declare class Log {
12
+ private readonly dir;
13
+ private readonly maxBytes;
14
+ private pending;
15
+ private tail;
16
+ private sequence;
17
+ private readonly recent;
18
+ constructor(dir: string, maxBytes?: number);
19
+ init(): Promise<void>;
20
+ write(entry: LogEntry): Promise<void>;
21
+ flush(): Promise<void>;
22
+ snapshot(): {
23
+ sequence: number;
24
+ line: string;
25
+ }[];
26
+ }