@rome-os/node 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -5
- package/dist/entry.js +133 -48
- package/dist/help.js +47 -8
- package/dist/index.d.ts +2 -5
- package/dist/index.js +2 -5
- package/package.json +7 -6
- package/dist/actions.d.ts +0 -20
- package/dist/actions.js +0 -18
- package/dist/client.d.ts +0 -29
- package/dist/client.js +0 -143
- package/dist/cloud.d.ts +0 -7
- package/dist/cloud.js +0 -53
- package/dist/connect.d.ts +0 -2
- package/dist/connect.js +0 -114
- package/dist/connector.d.ts +0 -24
- package/dist/connector.js +0 -148
- package/dist/daemon.d.ts +0 -11
- package/dist/daemon.js +0 -208
- package/dist/executor.d.ts +0 -5
- package/dist/executor.js +0 -121
- package/dist/local.d.ts +0 -9
- package/dist/local.js +0 -33
- package/dist/login.d.ts +0 -6
- package/dist/login.js +0 -100
- package/dist/protocol.d.ts +0 -30
- package/dist/protocol.js +0 -40
- package/dist/socket.d.ts +0 -2
- package/dist/socket.js +0 -27
- package/dist/storage.d.ts +0 -2
- package/dist/storage.js +0 -37
package/dist/client.js
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { byteLength, CLOSE, gatewayMessage, MAX_CLIENT_BUFFER_BYTES, MAX_MESSAGE_BYTES, outboundEnvelope, } from "./protocol.js";
|
|
2
|
-
export function connectGateway(options) {
|
|
3
|
-
let url = new URL(options.gatewayUrl);
|
|
4
|
-
if (url.protocol !== "wss:" || url.username || url.password || url.search || url.hash)
|
|
5
|
-
throw new Error("A credential-free WSS URL is required");
|
|
6
|
-
if (!/^(?:romedev_|romemob_)[A-Za-z0-9_-]{43}$/.test(options.deviceToken))
|
|
7
|
-
throw new Error("Invalid device token format");
|
|
8
|
-
let stopped = false;
|
|
9
|
-
let socket = null;
|
|
10
|
-
let attempt = 0;
|
|
11
|
-
let timer;
|
|
12
|
-
let connectingTimer;
|
|
13
|
-
let stableTimer;
|
|
14
|
-
const status = (value) => options.onStatus?.(value);
|
|
15
|
-
const schedule = () => {
|
|
16
|
-
if (stopped || timer)
|
|
17
|
-
return;
|
|
18
|
-
status("retrying");
|
|
19
|
-
const delay = Math.min(60_000, 1000 * 2 ** Math.min(attempt++, 6));
|
|
20
|
-
timer = setTimeout(() => {
|
|
21
|
-
timer = undefined;
|
|
22
|
-
void open();
|
|
23
|
-
}, delay * (0.75 + (options.random ?? Math.random)() * 0.5));
|
|
24
|
-
};
|
|
25
|
-
const open = async () => {
|
|
26
|
-
if (stopped)
|
|
27
|
-
return;
|
|
28
|
-
status("connecting");
|
|
29
|
-
let current;
|
|
30
|
-
try {
|
|
31
|
-
if (options.beforeConnect) {
|
|
32
|
-
const next = await options.beforeConnect();
|
|
33
|
-
if (stopped)
|
|
34
|
-
return;
|
|
35
|
-
if (next === null) {
|
|
36
|
-
stopped = true;
|
|
37
|
-
status("revoked");
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
url = new URL(next);
|
|
41
|
-
if (url.protocol !== "wss:" || url.username || url.password || url.search || url.hash)
|
|
42
|
-
throw new Error("A credential-free WSS URL is required");
|
|
43
|
-
}
|
|
44
|
-
current = options.createSocket(url.toString(), `Bearer ${options.deviceToken}`);
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
schedule();
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
socket = current;
|
|
51
|
-
let finished = false;
|
|
52
|
-
const finish = (code) => {
|
|
53
|
-
if (finished || socket !== current)
|
|
54
|
-
return;
|
|
55
|
-
finished = true;
|
|
56
|
-
clearTimeout(connectingTimer);
|
|
57
|
-
clearTimeout(stableTimer);
|
|
58
|
-
socket = null;
|
|
59
|
-
if (code === CLOSE.revoked || code === CLOSE.superseded) {
|
|
60
|
-
stopped = true;
|
|
61
|
-
status(code === CLOSE.revoked ? "revoked" : "superseded");
|
|
62
|
-
}
|
|
63
|
-
else
|
|
64
|
-
schedule();
|
|
65
|
-
};
|
|
66
|
-
connectingTimer = setTimeout(() => {
|
|
67
|
-
finish();
|
|
68
|
-
current.close();
|
|
69
|
-
}, 10_000);
|
|
70
|
-
current.addEventListener("open", () => {
|
|
71
|
-
if (stopped || socket !== current) {
|
|
72
|
-
current.close();
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
clearTimeout(connectingTimer);
|
|
76
|
-
stableTimer = setTimeout(() => {
|
|
77
|
-
attempt = 0;
|
|
78
|
-
}, 30_000);
|
|
79
|
-
status("online");
|
|
80
|
-
});
|
|
81
|
-
current.addEventListener("message", ({ data }) => {
|
|
82
|
-
if (stopped ||
|
|
83
|
-
socket !== current ||
|
|
84
|
-
typeof data !== "string" ||
|
|
85
|
-
byteLength(data) > MAX_MESSAGE_BYTES)
|
|
86
|
-
return;
|
|
87
|
-
let message;
|
|
88
|
-
try {
|
|
89
|
-
message = gatewayMessage(JSON.parse(data));
|
|
90
|
-
}
|
|
91
|
-
catch {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
if (message)
|
|
95
|
-
options.onMessage(message);
|
|
96
|
-
});
|
|
97
|
-
current.addEventListener("close", ({ code }) => finish(code));
|
|
98
|
-
current.addEventListener("error", () => {
|
|
99
|
-
finish();
|
|
100
|
-
current.close();
|
|
101
|
-
});
|
|
102
|
-
};
|
|
103
|
-
void open();
|
|
104
|
-
return {
|
|
105
|
-
send(envelope) {
|
|
106
|
-
if (stopped || !socket || socket.readyState !== 1 || !outboundEnvelope(envelope))
|
|
107
|
-
return false;
|
|
108
|
-
let text;
|
|
109
|
-
try {
|
|
110
|
-
text = JSON.stringify(envelope);
|
|
111
|
-
if (!outboundEnvelope(JSON.parse(text)))
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
catch {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
const size = byteLength(text);
|
|
118
|
-
if (size > MAX_MESSAGE_BYTES)
|
|
119
|
-
return false;
|
|
120
|
-
if (socket.bufferedAmount + size > MAX_CLIENT_BUFFER_BYTES) {
|
|
121
|
-
socket.close(CLOSE.outputLimit, "outbound buffer limit");
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
try {
|
|
125
|
-
socket.send(text);
|
|
126
|
-
return true;
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
return false;
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
stop() {
|
|
133
|
-
stopped = true;
|
|
134
|
-
clearTimeout(timer);
|
|
135
|
-
clearTimeout(connectingTimer);
|
|
136
|
-
clearTimeout(stableTimer);
|
|
137
|
-
const previous = socket;
|
|
138
|
-
socket = null;
|
|
139
|
-
previous?.close(1000, "client stopped");
|
|
140
|
-
status("stopped");
|
|
141
|
-
},
|
|
142
|
-
};
|
|
143
|
-
}
|
package/dist/cloud.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export declare class CloudError extends Error {
|
|
2
|
-
readonly code: string;
|
|
3
|
-
constructor(code: string);
|
|
4
|
-
}
|
|
5
|
-
export declare function cloudOrigin(value: string): string;
|
|
6
|
-
export declare function cloudRequest(origin: string, path: string, token: string, method?: string): Promise<unknown>;
|
|
7
|
-
export declare function gatewayConfig(origin: string, token: string): Promise<string>;
|
package/dist/cloud.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { isRecord } from "./actions.js";
|
|
2
|
-
export class CloudError extends Error {
|
|
3
|
-
code;
|
|
4
|
-
constructor(code) {
|
|
5
|
-
super(code === "invalid_device_session"
|
|
6
|
-
? "Device authorization is invalid."
|
|
7
|
-
: "Rome Cloud is unavailable. Retry later.");
|
|
8
|
-
this.code = code;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
export function cloudOrigin(value) {
|
|
12
|
-
const url = new URL(value);
|
|
13
|
-
if (url.username ||
|
|
14
|
-
url.password ||
|
|
15
|
-
url.search ||
|
|
16
|
-
url.hash ||
|
|
17
|
-
url.pathname !== "/" ||
|
|
18
|
-
(url.protocol !== "https:" &&
|
|
19
|
-
!(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))))
|
|
20
|
-
throw new Error("Use an HTTPS Cloud origin. HTTP is allowed only on loopback.");
|
|
21
|
-
return url.origin;
|
|
22
|
-
}
|
|
23
|
-
export async function cloudRequest(origin, path, token, method = "GET") {
|
|
24
|
-
let response;
|
|
25
|
-
try {
|
|
26
|
-
response = await fetch(new URL(path, cloudOrigin(origin)), {
|
|
27
|
-
method,
|
|
28
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
29
|
-
redirect: "error",
|
|
30
|
-
signal: AbortSignal.timeout(10_000),
|
|
31
|
-
cache: "no-store",
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
throw new CloudError("cloud_unavailable");
|
|
36
|
-
}
|
|
37
|
-
const body = await response.json().catch(() => null);
|
|
38
|
-
if (!response.ok) {
|
|
39
|
-
if (response.status === 401 && isRecord(body) && body.error === "invalid_device_session")
|
|
40
|
-
throw new CloudError("invalid_device_session");
|
|
41
|
-
throw new CloudError("cloud_unavailable");
|
|
42
|
-
}
|
|
43
|
-
return body;
|
|
44
|
-
}
|
|
45
|
-
export async function gatewayConfig(origin, token) {
|
|
46
|
-
const body = await cloudRequest(origin, "/v1/gateway/config", token);
|
|
47
|
-
if (!isRecord(body) || typeof body.gatewayUrl !== "string")
|
|
48
|
-
throw new CloudError("invalid_response");
|
|
49
|
-
const url = new URL(body.gatewayUrl);
|
|
50
|
-
if (url.protocol !== "wss:" || url.username || url.password || url.search || url.hash)
|
|
51
|
-
throw new CloudError("invalid_response");
|
|
52
|
-
return url.toString();
|
|
53
|
-
}
|
package/dist/connect.d.ts
DELETED
package/dist/connect.js
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { hostname } from "node:os";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { rm } from "node:fs/promises";
|
|
4
|
-
import { setTimeout as delay } from "node:timers/promises";
|
|
5
|
-
import { isRecord } from "./actions.js";
|
|
6
|
-
import { cloudOrigin, cloudRequest, CloudError, gatewayConfig } from "./cloud.js";
|
|
7
|
-
import { connectGateway } from "./client.js";
|
|
8
|
-
import { createNodeSocket } from "./socket.js";
|
|
9
|
-
import { loginDevice } from "./login.js";
|
|
10
|
-
import { readPrivateJson, writePrivateJson } from "./storage.js";
|
|
11
|
-
import { createExecutor } from "./executor.js";
|
|
12
|
-
import { validId } from "./protocol.js";
|
|
13
|
-
import { configRoot } from "./local.js";
|
|
14
|
-
export async function connectComputer(origin, name, signal) {
|
|
15
|
-
const cloudUrl = cloudOrigin(origin);
|
|
16
|
-
const path = join(configRoot(), "credential.json");
|
|
17
|
-
const stored = await readPrivateJson(path);
|
|
18
|
-
let session = isRecord(stored) &&
|
|
19
|
-
stored.cloudUrl === cloudUrl &&
|
|
20
|
-
typeof stored.token === "string" &&
|
|
21
|
-
/^romedev_[A-Za-z0-9_-]{43}$/.test(stored.token) &&
|
|
22
|
-
validId(stored.deviceId) &&
|
|
23
|
-
typeof stored.name === "string"
|
|
24
|
-
? { cloudUrl, token: stored.token, deviceId: stored.deviceId, name: stored.name }
|
|
25
|
-
: null;
|
|
26
|
-
let gatewayUrl = "";
|
|
27
|
-
let attempt = 0;
|
|
28
|
-
while (!signal.aborted) {
|
|
29
|
-
if (!session) {
|
|
30
|
-
process.stderr.write("While connect is running, Rome instances in your account can execute programs and read or change files as your OS user.\n");
|
|
31
|
-
session = { ...(await loginDevice(cloudUrl, name, signal)), name };
|
|
32
|
-
await writePrivateJson(path, session);
|
|
33
|
-
}
|
|
34
|
-
try {
|
|
35
|
-
gatewayUrl = await gatewayConfig(cloudUrl, session.token);
|
|
36
|
-
break;
|
|
37
|
-
}
|
|
38
|
-
catch (error) {
|
|
39
|
-
if (error instanceof CloudError && error.code === "invalid_device_session") {
|
|
40
|
-
await rm(path, { force: true });
|
|
41
|
-
session = null;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
process.stderr.write("Rome Cloud is unavailable; keeping credentials and retrying.\n");
|
|
45
|
-
await delay(Math.min(60_000, 1000 * 2 ** Math.min(attempt++, 6)), undefined, {
|
|
46
|
-
signal,
|
|
47
|
-
}).catch(() => { });
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (signal.aborted || !session)
|
|
51
|
-
return;
|
|
52
|
-
const devices = await cloudRequest(cloudUrl, "/api/account/devices", session.token).catch(() => null);
|
|
53
|
-
if (isRecord(devices) && Array.isArray(devices.items)) {
|
|
54
|
-
const device = devices.items.find((item) => isRecord(item) && item.id === session?.deviceId);
|
|
55
|
-
if (isRecord(device) && typeof device.device_name === "string") {
|
|
56
|
-
session.name = device.device_name;
|
|
57
|
-
await writePrivateJson(path, session);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
if (signal.aborted)
|
|
61
|
-
return;
|
|
62
|
-
const active = session;
|
|
63
|
-
process.stderr.write(`Device: ${active.name}\nDevice ID: ${active.deviceId}\n`);
|
|
64
|
-
await new Promise((resolve, reject) => {
|
|
65
|
-
const executor = createExecutor(active.name, (message) => connection.send(message), [
|
|
66
|
-
active.token,
|
|
67
|
-
]);
|
|
68
|
-
const finish = (error) => {
|
|
69
|
-
signal.removeEventListener("abort", abort);
|
|
70
|
-
executor.disconnect();
|
|
71
|
-
connection.stop();
|
|
72
|
-
if (error)
|
|
73
|
-
reject(error);
|
|
74
|
-
else
|
|
75
|
-
resolve();
|
|
76
|
-
};
|
|
77
|
-
const abort = () => finish();
|
|
78
|
-
const connection = connectGateway({
|
|
79
|
-
gatewayUrl,
|
|
80
|
-
deviceToken: active.token,
|
|
81
|
-
createSocket: createNodeSocket,
|
|
82
|
-
beforeConnect: async () => {
|
|
83
|
-
try {
|
|
84
|
-
return await gatewayConfig(cloudUrl, active.token);
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
if (error instanceof CloudError && error.code === "invalid_device_session")
|
|
88
|
-
return null;
|
|
89
|
-
throw error;
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
onMessage: (message) => {
|
|
93
|
-
if ("from" in message)
|
|
94
|
-
void executor.receive(message);
|
|
95
|
-
},
|
|
96
|
-
onStatus: (status) => {
|
|
97
|
-
process.stderr.write(`Connection: ${status}\n`);
|
|
98
|
-
if (status !== "online" && status !== "connecting")
|
|
99
|
-
executor.disconnect();
|
|
100
|
-
if (status === "revoked" || status === "superseded") {
|
|
101
|
-
queueMicrotask(() => finish(new Error(status === "revoked"
|
|
102
|
-
? "Authorization revoked. Run rome-node connect again to authorize."
|
|
103
|
-
: "Another connection replaced this device. This process has stopped.")));
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
108
|
-
if (signal.aborted)
|
|
109
|
-
abort();
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
export const defaultDeviceName = () => hostname()
|
|
113
|
-
.replace(/[\u0000-\u001f\u007f]/g, "")
|
|
114
|
-
.slice(0, 80) || "Computer";
|
package/dist/connector.d.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { type ActionResponse } from "./actions.js";
|
|
2
|
-
import { connectGateway } from "./client.js";
|
|
3
|
-
import type { CallerCredential } from "./local.js";
|
|
4
|
-
export interface DeviceConnectorOptions {
|
|
5
|
-
credential: CallerCredential;
|
|
6
|
-
connect?: typeof connectGateway;
|
|
7
|
-
waitMs?: number;
|
|
8
|
-
}
|
|
9
|
-
export declare class DeviceConnector {
|
|
10
|
-
private options;
|
|
11
|
-
private connection?;
|
|
12
|
-
private initializing?;
|
|
13
|
-
private status;
|
|
14
|
-
private stopped;
|
|
15
|
-
private pending;
|
|
16
|
-
private ready;
|
|
17
|
-
constructor(options: DeviceConnectorOptions);
|
|
18
|
-
private ensure;
|
|
19
|
-
private initialize;
|
|
20
|
-
list(): Promise<unknown>;
|
|
21
|
-
private receive;
|
|
22
|
-
run(target: string, action: string, args: unknown): Promise<ActionResponse>;
|
|
23
|
-
stop(): void;
|
|
24
|
-
}
|
package/dist/connector.js
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { actionError, isRecord, parseResponse, } from "./actions.js";
|
|
3
|
-
import { connectGateway } from "./client.js";
|
|
4
|
-
import { cloudRequest, CloudError, gatewayConfig } from "./cloud.js";
|
|
5
|
-
import { byteLength, MAX_MESSAGE_BYTES, validId } from "./protocol.js";
|
|
6
|
-
import { createNodeSocket } from "./socket.js";
|
|
7
|
-
export class DeviceConnector {
|
|
8
|
-
options;
|
|
9
|
-
connection;
|
|
10
|
-
initializing;
|
|
11
|
-
status = "stopped";
|
|
12
|
-
stopped = false;
|
|
13
|
-
pending = new Map();
|
|
14
|
-
ready = new Set();
|
|
15
|
-
constructor(options) {
|
|
16
|
-
this.options = options;
|
|
17
|
-
}
|
|
18
|
-
async ensure() {
|
|
19
|
-
if (this.stopped || this.status === "superseded" || this.status === "revoked")
|
|
20
|
-
throw new Error("connector_stopped");
|
|
21
|
-
if (this.connection)
|
|
22
|
-
return;
|
|
23
|
-
if (this.initializing)
|
|
24
|
-
return this.initializing;
|
|
25
|
-
this.initializing = this.initialize();
|
|
26
|
-
try {
|
|
27
|
-
await this.initializing;
|
|
28
|
-
}
|
|
29
|
-
finally {
|
|
30
|
-
this.initializing = undefined;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
async initialize() {
|
|
34
|
-
const { cloudUrl, token } = this.options.credential;
|
|
35
|
-
const gatewayUrl = await gatewayConfig(cloudUrl, token);
|
|
36
|
-
if (this.stopped)
|
|
37
|
-
throw new Error("connector_stopped");
|
|
38
|
-
this.connection = (this.options.connect ?? connectGateway)({
|
|
39
|
-
gatewayUrl,
|
|
40
|
-
deviceToken: token,
|
|
41
|
-
createSocket: createNodeSocket,
|
|
42
|
-
beforeConnect: async () => {
|
|
43
|
-
try {
|
|
44
|
-
return await gatewayConfig(cloudUrl, token);
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
if (error instanceof CloudError && error.code === "invalid_device_session")
|
|
48
|
-
return null;
|
|
49
|
-
throw error;
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
onMessage: (message) => this.receive(message),
|
|
53
|
-
onStatus: (status) => {
|
|
54
|
-
this.status = status;
|
|
55
|
-
if (status !== "online") {
|
|
56
|
-
for (const pending of this.pending.values())
|
|
57
|
-
pending.finish(actionError("unknown_outcome", "The connection was lost. Execution may have occurred. Do not automatically retry."));
|
|
58
|
-
}
|
|
59
|
-
for (const wake of this.ready)
|
|
60
|
-
wake();
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
async list() {
|
|
65
|
-
if (this.stopped)
|
|
66
|
-
throw new Error("connector_stopped");
|
|
67
|
-
const { cloudUrl, token } = this.options.credential;
|
|
68
|
-
const body = await cloudRequest(cloudUrl, "/api/account/devices", token);
|
|
69
|
-
if (!isRecord(body) || !Array.isArray(body.items))
|
|
70
|
-
throw new CloudError("invalid_response");
|
|
71
|
-
return { items: body.items };
|
|
72
|
-
}
|
|
73
|
-
receive(message) {
|
|
74
|
-
const pending = this.pending.get(message.id);
|
|
75
|
-
if (!pending)
|
|
76
|
-
return;
|
|
77
|
-
if ("type" in message) {
|
|
78
|
-
pending.finish(actionError(message.code, "The Gateway could not forward the request."));
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
if (message.from !== pending.target)
|
|
82
|
-
return;
|
|
83
|
-
const response = parseResponse(message.payload);
|
|
84
|
-
if (response)
|
|
85
|
-
pending.finish(response);
|
|
86
|
-
}
|
|
87
|
-
async run(target, action, args) {
|
|
88
|
-
if (!validId(target) || !action || action.length > 128)
|
|
89
|
-
return actionError("invalid_request", "A device ID and action are required.");
|
|
90
|
-
try {
|
|
91
|
-
await this.ensure();
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
return actionError("gateway_unavailable", "The device connection is unavailable.");
|
|
95
|
-
}
|
|
96
|
-
if (this.status !== "online")
|
|
97
|
-
await new Promise((resolve) => {
|
|
98
|
-
const finish = () => {
|
|
99
|
-
clearTimeout(timer);
|
|
100
|
-
this.ready.delete(wake);
|
|
101
|
-
resolve();
|
|
102
|
-
};
|
|
103
|
-
const wake = () => {
|
|
104
|
-
if (["online", "revoked", "superseded", "stopped"].includes(this.status))
|
|
105
|
-
finish();
|
|
106
|
-
};
|
|
107
|
-
const timer = setTimeout(finish, 10_000);
|
|
108
|
-
this.ready.add(wake);
|
|
109
|
-
wake();
|
|
110
|
-
});
|
|
111
|
-
if (this.status !== "online")
|
|
112
|
-
return actionError("gateway_unavailable", "The device connection is offline. No operation was sent.");
|
|
113
|
-
if (this.pending.size >= 128)
|
|
114
|
-
return actionError("busy", "Too many device requests are pending.");
|
|
115
|
-
const id = randomUUID();
|
|
116
|
-
const envelope = {
|
|
117
|
-
id,
|
|
118
|
-
to: target,
|
|
119
|
-
payload: { type: "request", action, args },
|
|
120
|
-
};
|
|
121
|
-
try {
|
|
122
|
-
if (byteLength(JSON.stringify(envelope)) > MAX_MESSAGE_BYTES)
|
|
123
|
-
return actionError("message_too_large", "The request exceeds 128 KiB.");
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
return actionError("invalid_request", "Action arguments must be JSON.");
|
|
127
|
-
}
|
|
128
|
-
return new Promise((resolve) => {
|
|
129
|
-
const finish = (response) => {
|
|
130
|
-
clearTimeout(timer);
|
|
131
|
-
this.pending.delete(id);
|
|
132
|
-
resolve(response);
|
|
133
|
-
};
|
|
134
|
-
const timer = setTimeout(() => finish(actionError("unknown_outcome", "The local wait limit was reached. The remote program was not canceled. Do not automatically retry.")), this.options.waitMs ?? 60_000);
|
|
135
|
-
this.pending.set(id, { target, finish });
|
|
136
|
-
if (!this.connection?.send(envelope))
|
|
137
|
-
finish(actionError("unknown_outcome", "The request could not be submitted reliably. Do not automatically retry."));
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
stop() {
|
|
141
|
-
this.stopped = true;
|
|
142
|
-
this.connection?.stop();
|
|
143
|
-
for (const pending of this.pending.values())
|
|
144
|
-
pending.finish(actionError("unknown_outcome", "The CLI daemon is shutting down. Execution may have occurred."));
|
|
145
|
-
for (const wake of this.ready)
|
|
146
|
-
wake();
|
|
147
|
-
}
|
|
148
|
-
}
|
package/dist/daemon.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
interface DaemonState {
|
|
2
|
-
token: string;
|
|
3
|
-
port: number;
|
|
4
|
-
pid: number;
|
|
5
|
-
}
|
|
6
|
-
export declare function daemonStatus(): Promise<DaemonState | null>;
|
|
7
|
-
export declare function startDaemon(): Promise<DaemonState>;
|
|
8
|
-
export declare function stopDaemon(): Promise<void>;
|
|
9
|
-
export declare function callDaemon(path: "/devices" | "/run", body?: unknown): Promise<Response>;
|
|
10
|
-
export declare function serveDaemon(): Promise<void>;
|
|
11
|
-
export {};
|