sweetlink 0.1.0 → 0.2.1
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/CHANGELOG.md +20 -0
- package/LICENSE +1 -1
- package/README.md +22 -11
- package/dist/daemon/src/codename.d.ts.map +1 -1
- package/dist/daemon/src/codename.js +11 -11
- package/dist/daemon/src/codename.js.map +1 -1
- package/dist/daemon/src/index.d.ts +1 -1
- package/dist/daemon/src/index.js +131 -122
- package/dist/daemon/src/index.js.map +1 -1
- package/dist/shared/src/env.d.ts.map +1 -1
- package/dist/shared/src/env.js +12 -12
- package/dist/shared/src/env.js.map +1 -1
- package/dist/shared/src/index.d.ts +30 -30
- package/dist/shared/src/index.d.ts.map +1 -1
- package/dist/shared/src/index.js +17 -17
- package/dist/shared/src/index.js.map +1 -1
- package/dist/shared/src/node.d.ts +1 -1
- package/dist/shared/src/node.d.ts.map +1 -1
- package/dist/shared/src/node.js +18 -18
- package/dist/shared/src/node.js.map +1 -1
- package/dist/src/codex.d.ts +1 -1
- package/dist/src/codex.js +19 -19
- package/dist/src/codex.js.map +1 -1
- package/dist/src/commands/click.d.ts +1 -1
- package/dist/src/commands/click.d.ts.map +1 -1
- package/dist/src/commands/click.js +32 -24
- package/dist/src/commands/click.js.map +1 -1
- package/dist/src/commands/run-js.d.ts +1 -1
- package/dist/src/commands/run-js.d.ts.map +1 -1
- package/dist/src/commands/run-js.js +10 -10
- package/dist/src/commands/run-js.js.map +1 -1
- package/dist/src/commands/trust-ca.d.ts +1 -1
- package/dist/src/commands/trust-ca.js +15 -15
- package/dist/src/core/config-file.d.ts +6 -0
- package/dist/src/core/config-file.d.ts.map +1 -1
- package/dist/src/core/config-file.js +73 -38
- package/dist/src/core/config-file.js.map +1 -1
- package/dist/src/core/config.d.ts +3 -2
- package/dist/src/core/config.d.ts.map +1 -1
- package/dist/src/core/config.js +31 -18
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/core/dev-bootstrap.d.ts +10 -0
- package/dist/src/core/dev-bootstrap.d.ts.map +1 -0
- package/dist/src/core/dev-bootstrap.js +112 -0
- package/dist/src/core/dev-bootstrap.js.map +1 -0
- package/dist/src/core/env.d.ts +1 -1
- package/dist/src/core/env.d.ts.map +1 -1
- package/dist/src/core/env.js +3 -3
- package/dist/src/core/env.js.map +1 -1
- package/dist/src/devtools-registry.d.ts.map +1 -1
- package/dist/src/devtools-registry.js +48 -46
- package/dist/src/devtools-registry.js.map +1 -1
- package/dist/src/env.d.ts +1 -1
- package/dist/src/env.js +1 -1
- package/dist/src/http.d.ts.map +1 -1
- package/dist/src/http.js +7 -6
- package/dist/src/http.js.map +1 -1
- package/dist/src/index.d.ts +7 -7
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +450 -363
- package/dist/src/index.js.map +1 -1
- package/dist/src/runtime/browser/client.d.ts +3 -3
- package/dist/src/runtime/browser/client.d.ts.map +1 -1
- package/dist/src/runtime/browser/client.js +79 -72
- package/dist/src/runtime/browser/client.js.map +1 -1
- package/dist/src/runtime/browser/commands/index.d.ts +2 -2
- package/dist/src/runtime/browser/commands/index.js +17 -17
- package/dist/src/runtime/browser/commands/index.js.map +1 -1
- package/dist/src/runtime/browser/dom-to-image-loader.d.ts.map +1 -1
- package/dist/src/runtime/browser/dom-to-image-loader.js +7 -7
- package/dist/src/runtime/browser/dom-to-image-loader.js.map +1 -1
- package/dist/src/runtime/browser/index.d.ts +3 -3
- package/dist/src/runtime/browser/index.js +2 -2
- package/dist/src/runtime/browser/module-loader.js +2 -2
- package/dist/src/runtime/browser/screenshot/hooks.d.ts +25 -53
- package/dist/src/runtime/browser/screenshot/hooks.d.ts.map +1 -1
- package/dist/src/runtime/browser/screenshot/hooks.js +32 -29
- package/dist/src/runtime/browser/screenshot/hooks.js.map +1 -1
- package/dist/src/runtime/browser/screenshot/index.d.ts +3 -3
- package/dist/src/runtime/browser/screenshot/index.d.ts.map +1 -1
- package/dist/src/runtime/browser/screenshot/index.js +17 -15
- package/dist/src/runtime/browser/screenshot/index.js.map +1 -1
- package/dist/src/runtime/browser/screenshot/renderers/dom-to-image.d.ts +1 -1
- package/dist/src/runtime/browser/screenshot/renderers/dom-to-image.js +13 -13
- package/dist/src/runtime/browser/screenshot/renderers/dom-to-image.js.map +1 -1
- package/dist/src/runtime/browser/screenshot/renderers/html2canvas.d.ts +1 -1
- package/dist/src/runtime/browser/screenshot/renderers/html2canvas.d.ts.map +1 -1
- package/dist/src/runtime/browser/screenshot/renderers/html2canvas.js +24 -24
- package/dist/src/runtime/browser/screenshot/renderers/html2canvas.js.map +1 -1
- package/dist/src/runtime/browser/screenshot/targets.d.ts +2 -2
- package/dist/src/runtime/browser/screenshot/targets.js +9 -9
- package/dist/src/runtime/browser/screenshot/utils.d.ts +1 -1
- package/dist/src/runtime/browser/screenshot/utils.d.ts.map +1 -1
- package/dist/src/runtime/browser/screenshot/utils.js +73 -71
- package/dist/src/runtime/browser/screenshot/utils.js.map +1 -1
- package/dist/src/runtime/browser/selector-discovery.d.ts +1 -1
- package/dist/src/runtime/browser/selector-discovery.d.ts.map +1 -1
- package/dist/src/runtime/browser/selector-discovery.js +24 -22
- package/dist/src/runtime/browser/selector-discovery.js.map +1 -1
- package/dist/src/runtime/browser/storage/session-storage.d.ts +1 -1
- package/dist/src/runtime/browser/storage/session-storage.d.ts.map +1 -1
- package/dist/src/runtime/browser/storage/session-storage.js +12 -10
- package/dist/src/runtime/browser/storage/session-storage.js.map +1 -1
- package/dist/src/runtime/browser/types.d.ts +7 -7
- package/dist/src/runtime/browser/types.js +1 -1
- package/dist/src/runtime/browser/utils/console.d.ts +1 -1
- package/dist/src/runtime/browser/utils/console.js +16 -16
- package/dist/src/runtime/browser/utils/console.js.map +1 -1
- package/dist/src/runtime/browser/utils/environment.js +2 -2
- package/dist/src/runtime/browser/utils/errors.d.ts.map +1 -1
- package/dist/src/runtime/browser/utils/errors.js +11 -11
- package/dist/src/runtime/browser/utils/number.d.ts.map +1 -1
- package/dist/src/runtime/browser/utils/number.js.map +1 -1
- package/dist/src/runtime/browser/utils/object.d.ts.map +1 -1
- package/dist/src/runtime/browser/utils/object.js +2 -2
- package/dist/src/runtime/browser/utils/object.js.map +1 -1
- package/dist/src/runtime/browser/utils/sanitize.js +2 -2
- package/dist/src/runtime/browser/utils/time.d.ts.map +1 -1
- package/dist/src/runtime/browser/utils/time.js +1 -1
- package/dist/src/runtime/chrome/cookies.d.ts +4 -4
- package/dist/src/runtime/chrome/cookies.d.ts.map +1 -1
- package/dist/src/runtime/chrome/cookies.js +31 -31
- package/dist/src/runtime/chrome/cookies.js.map +1 -1
- package/dist/src/runtime/chrome/deep-link.d.ts +13 -0
- package/dist/src/runtime/chrome/deep-link.d.ts.map +1 -0
- package/dist/src/runtime/chrome/deep-link.js +292 -0
- package/dist/src/runtime/chrome/deep-link.js.map +1 -0
- package/dist/src/runtime/chrome/diagnostics.d.ts.map +1 -1
- package/dist/src/runtime/chrome/diagnostics.js +13 -11
- package/dist/src/runtime/chrome/diagnostics.js.map +1 -1
- package/dist/src/runtime/chrome/focus.d.ts.map +1 -1
- package/dist/src/runtime/chrome/focus.js +5 -5
- package/dist/src/runtime/chrome/focus.js.map +1 -1
- package/dist/src/runtime/chrome/launch.d.ts.map +1 -1
- package/dist/src/runtime/chrome/launch.js +30 -30
- package/dist/src/runtime/chrome/launch.js.map +1 -1
- package/dist/src/runtime/chrome/puppeteer.d.ts +2 -2
- package/dist/src/runtime/chrome/puppeteer.d.ts.map +1 -1
- package/dist/src/runtime/chrome/puppeteer.js +16 -11
- package/dist/src/runtime/chrome/puppeteer.js.map +1 -1
- package/dist/src/runtime/chrome/reuse.d.ts.map +1 -1
- package/dist/src/runtime/chrome/reuse.js +38 -35
- package/dist/src/runtime/chrome/reuse.js.map +1 -1
- package/dist/src/runtime/chrome/session.d.ts +1 -1
- package/dist/src/runtime/chrome/session.d.ts.map +1 -1
- package/dist/src/runtime/chrome/session.js +12 -10
- package/dist/src/runtime/chrome/session.js.map +1 -1
- package/dist/src/runtime/chrome.d.ts +8 -8
- package/dist/src/runtime/chrome.js +8 -8
- package/dist/src/runtime/cookies.d.ts +1 -16
- package/dist/src/runtime/cookies.d.ts.map +1 -1
- package/dist/src/runtime/cookies.js +177 -281
- package/dist/src/runtime/cookies.js.map +1 -1
- package/dist/src/runtime/devstack.d.ts +1 -1
- package/dist/src/runtime/devstack.d.ts.map +1 -1
- package/dist/src/runtime/devstack.js +31 -28
- package/dist/src/runtime/devstack.js.map +1 -1
- package/dist/src/runtime/devtools/background.js +22 -22
- package/dist/src/runtime/devtools/cdp.d.ts +3 -2
- package/dist/src/runtime/devtools/cdp.d.ts.map +1 -1
- package/dist/src/runtime/devtools/cdp.js +61 -56
- package/dist/src/runtime/devtools/cdp.js.map +1 -1
- package/dist/src/runtime/devtools/config.d.ts +44 -1
- package/dist/src/runtime/devtools/config.d.ts.map +1 -1
- package/dist/src/runtime/devtools/config.js +15 -15
- package/dist/src/runtime/devtools/config.js.map +1 -1
- package/dist/src/runtime/devtools/constants.d.ts.map +1 -1
- package/dist/src/runtime/devtools/constants.js +18 -10
- package/dist/src/runtime/devtools/constants.js.map +1 -1
- package/dist/src/runtime/devtools/diagnostics.d.ts +6 -3
- package/dist/src/runtime/devtools/diagnostics.d.ts.map +1 -1
- package/dist/src/runtime/devtools/diagnostics.js +40 -38
- package/dist/src/runtime/devtools/diagnostics.js.map +1 -1
- package/dist/src/runtime/devtools/oauth.d.ts +1 -1
- package/dist/src/runtime/devtools/oauth.d.ts.map +1 -1
- package/dist/src/runtime/devtools/oauth.js +35 -28
- package/dist/src/runtime/devtools/oauth.js.map +1 -1
- package/dist/src/runtime/devtools/types.d.ts +3 -48
- package/dist/src/runtime/devtools/types.d.ts.map +1 -1
- package/dist/src/runtime/devtools.d.ts +7 -7
- package/dist/src/runtime/devtools.js +7 -7
- package/dist/src/runtime/next-devtools.d.ts.map +1 -1
- package/dist/src/runtime/next-devtools.js +12 -12
- package/dist/src/runtime/next-devtools.js.map +1 -1
- package/dist/src/runtime/screenshot.d.ts +9 -9
- package/dist/src/runtime/screenshot.d.ts.map +1 -1
- package/dist/src/runtime/screenshot.js +65 -46
- package/dist/src/runtime/screenshot.js.map +1 -1
- package/dist/src/runtime/scripts.d.ts +1 -1
- package/dist/src/runtime/scripts.d.ts.map +1 -1
- package/dist/src/runtime/scripts.js +28 -28
- package/dist/src/runtime/scripts.js.map +1 -1
- package/dist/src/runtime/session.d.ts +6 -6
- package/dist/src/runtime/session.d.ts.map +1 -1
- package/dist/src/runtime/session.js +35 -35
- package/dist/src/runtime/session.js.map +1 -1
- package/dist/src/runtime/smoke.d.ts +3 -3
- package/dist/src/runtime/smoke.d.ts.map +1 -1
- package/dist/src/runtime/smoke.js +85 -79
- package/dist/src/runtime/smoke.js.map +1 -1
- package/dist/src/runtime/url.d.ts +2 -2
- package/dist/src/runtime/url.d.ts.map +1 -1
- package/dist/src/runtime/url.js +20 -20
- package/dist/src/runtime/url.js.map +1 -1
- package/dist/src/screenshot-hooks.d.ts +1 -1
- package/dist/src/screenshot-hooks.d.ts.map +1 -1
- package/dist/src/screenshot-hooks.js +12 -12
- package/dist/src/screenshot-hooks.js.map +1 -1
- package/dist/src/shared/env.d.ts.map +1 -1
- package/dist/src/shared/env.js +11 -11
- package/dist/src/shared/env.js.map +1 -1
- package/dist/src/shared/index.d.ts +30 -30
- package/dist/src/shared/index.d.ts.map +1 -1
- package/dist/src/shared/index.js +17 -17
- package/dist/src/shared/index.js.map +1 -1
- package/dist/src/shared/node.d.ts +1 -1
- package/dist/src/shared/node.d.ts.map +1 -1
- package/dist/src/shared/node.js +18 -18
- package/dist/src/shared/node.js.map +1 -1
- package/dist/src/token.d.ts +1 -1
- package/dist/src/token.d.ts.map +1 -1
- package/dist/src/token.js +21 -18
- package/dist/src/token.js.map +1 -1
- package/dist/src/types.d.ts +7 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/util/app-label.js +4 -4
- package/dist/src/util/errors.d.ts.map +1 -1
- package/dist/src/util/errors.js +12 -10
- package/dist/src/util/errors.js.map +1 -1
- package/dist/src/util/path.js +2 -2
- package/dist/src/util/regex.js +4 -4
- package/package.json +48 -40
package/dist/daemon/src/index.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import { spawnSync } from
|
|
4
|
-
import { existsSync, mkdirSync, readFileSync } from
|
|
5
|
-
import { createServer as createHttpsServer, request as httpsRequest } from
|
|
6
|
-
import os from
|
|
7
|
-
import path from
|
|
8
|
-
import { URL } from
|
|
9
|
-
import { createSweetLinkCommandId, SWEETLINK_DEFAULT_PORT, SWEETLINK_HEARTBEAT_INTERVAL_MS, SWEETLINK_HEARTBEAT_TOLERANCE_MS, SWEETLINK_WS_PATH, verifySweetLinkToken, } from
|
|
10
|
-
import { readSweetLinkEnv } from
|
|
11
|
-
import { getDefaultSweetLinkSecretPath, resolveSweetLinkSecret, } from
|
|
12
|
-
import WebSocket, { WebSocketServer } from
|
|
13
|
-
import { z } from
|
|
14
|
-
import { generateSessionCodename } from
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
5
|
+
import { createServer as createHttpsServer, request as httpsRequest } from "node:https";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { URL } from "node:url";
|
|
9
|
+
import { createSweetLinkCommandId, SWEETLINK_DEFAULT_PORT, SWEETLINK_HEARTBEAT_INTERVAL_MS, SWEETLINK_HEARTBEAT_TOLERANCE_MS, SWEETLINK_WS_PATH, verifySweetLinkToken, } from "../../shared/src/index.js";
|
|
10
|
+
import { readSweetLinkEnv } from "../../shared/src/env.js";
|
|
11
|
+
import { getDefaultSweetLinkSecretPath, resolveSweetLinkSecret, } from "../../shared/src/node.js";
|
|
12
|
+
import WebSocket, { WebSocketServer } from "ws";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import { generateSessionCodename } from "./codename.js";
|
|
15
15
|
const SHUTDOWN_GRACE_MS = 1000;
|
|
16
16
|
const unrefTimer = (handle) => {
|
|
17
17
|
const candidate = handle;
|
|
18
|
-
if (typeof candidate ===
|
|
18
|
+
if (typeof candidate === "object" && candidate !== null && "unref" in candidate) {
|
|
19
19
|
const unref = candidate.unref;
|
|
20
|
-
if (typeof unref ===
|
|
20
|
+
if (typeof unref === "function") {
|
|
21
21
|
unref.call(candidate);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
@@ -26,31 +26,31 @@ const toError = (value) => {
|
|
|
26
26
|
if (value instanceof Error) {
|
|
27
27
|
return value;
|
|
28
28
|
}
|
|
29
|
-
if (typeof value ===
|
|
29
|
+
if (typeof value === "string") {
|
|
30
30
|
return Object.assign(new Error(value), { cause: value });
|
|
31
31
|
}
|
|
32
|
-
if (value && typeof value ===
|
|
32
|
+
if (value && typeof value === "object" && "message" in value) {
|
|
33
33
|
const candidate = value.message;
|
|
34
|
-
if (typeof candidate ===
|
|
34
|
+
if (typeof candidate === "string" && candidate.trim().length > 0) {
|
|
35
35
|
return Object.assign(new Error(candidate.trim()), { cause: value });
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
return Object.assign(new Error(
|
|
38
|
+
return Object.assign(new Error("Unknown error"), { cause: value });
|
|
39
39
|
};
|
|
40
40
|
const getErrorMessage = (value) => {
|
|
41
41
|
const error = toError(value);
|
|
42
|
-
return error.message ||
|
|
42
|
+
return error.message || "Unknown error";
|
|
43
43
|
};
|
|
44
|
-
const CERT_DIR = path.join(os.homedir(),
|
|
45
|
-
const CERT_PATH = path.join(CERT_DIR,
|
|
46
|
-
const KEY_PATH = path.join(CERT_DIR,
|
|
44
|
+
const CERT_DIR = path.join(os.homedir(), ".sweetlink", "certs");
|
|
45
|
+
const CERT_PATH = path.join(CERT_DIR, "localhost-cert.pem");
|
|
46
|
+
const KEY_PATH = path.join(CERT_DIR, "localhost-key.pem");
|
|
47
47
|
const SOCKET_STATE_LABEL = {
|
|
48
|
-
0:
|
|
49
|
-
1:
|
|
50
|
-
2:
|
|
51
|
-
3:
|
|
48
|
+
0: "connecting",
|
|
49
|
+
1: "open",
|
|
50
|
+
2: "closing",
|
|
51
|
+
3: "closed",
|
|
52
52
|
};
|
|
53
|
-
const SweetLinkConsoleLevelSchema = z.enum([
|
|
53
|
+
const SweetLinkConsoleLevelSchema = z.enum(["log", "info", "warn", "error", "debug"]);
|
|
54
54
|
const consoleEventSchema = z
|
|
55
55
|
.object({
|
|
56
56
|
id: z.string(),
|
|
@@ -59,7 +59,7 @@ const consoleEventSchema = z
|
|
|
59
59
|
args: z.array(z.unknown()),
|
|
60
60
|
})
|
|
61
61
|
.passthrough();
|
|
62
|
-
const commandResultSchema = z.discriminatedUnion(
|
|
62
|
+
const commandResultSchema = z.discriminatedUnion("ok", [
|
|
63
63
|
z
|
|
64
64
|
.object({
|
|
65
65
|
ok: z.literal(true),
|
|
@@ -82,7 +82,7 @@ const commandResultSchema = z.discriminatedUnion('ok', [
|
|
|
82
82
|
]);
|
|
83
83
|
const registerMessageSchema = z
|
|
84
84
|
.object({
|
|
85
|
-
kind: z.literal(
|
|
85
|
+
kind: z.literal("register"),
|
|
86
86
|
token: z.string(),
|
|
87
87
|
sessionId: z.string(),
|
|
88
88
|
url: z.string(),
|
|
@@ -93,25 +93,25 @@ const registerMessageSchema = z
|
|
|
93
93
|
.passthrough();
|
|
94
94
|
const heartbeatMessageSchema = z
|
|
95
95
|
.object({
|
|
96
|
-
kind: z.literal(
|
|
96
|
+
kind: z.literal("heartbeat"),
|
|
97
97
|
sessionId: z.string(),
|
|
98
98
|
})
|
|
99
99
|
.passthrough();
|
|
100
100
|
const commandResultMessageSchema = z
|
|
101
101
|
.object({
|
|
102
|
-
kind: z.literal(
|
|
102
|
+
kind: z.literal("commandResult"),
|
|
103
103
|
sessionId: z.string(),
|
|
104
104
|
result: commandResultSchema,
|
|
105
105
|
})
|
|
106
106
|
.passthrough();
|
|
107
107
|
const consoleMessageSchema = z
|
|
108
108
|
.object({
|
|
109
|
-
kind: z.literal(
|
|
109
|
+
kind: z.literal("console"),
|
|
110
110
|
sessionId: z.string(),
|
|
111
111
|
events: z.array(consoleEventSchema),
|
|
112
112
|
})
|
|
113
113
|
.passthrough();
|
|
114
|
-
const clientMessageSchema = z.discriminatedUnion(
|
|
114
|
+
const clientMessageSchema = z.discriminatedUnion("kind", [
|
|
115
115
|
registerMessageSchema,
|
|
116
116
|
heartbeatMessageSchema,
|
|
117
117
|
commandResultMessageSchema,
|
|
@@ -119,7 +119,7 @@ const clientMessageSchema = z.discriminatedUnion('kind', [
|
|
|
119
119
|
]);
|
|
120
120
|
const runScriptCommandSchema = z
|
|
121
121
|
.object({
|
|
122
|
-
type: z.literal(
|
|
122
|
+
type: z.literal("runScript"),
|
|
123
123
|
code: z.string(),
|
|
124
124
|
timeoutMs: z.number().finite().positive().optional(),
|
|
125
125
|
captureConsole: z.boolean().optional(),
|
|
@@ -127,44 +127,49 @@ const runScriptCommandSchema = z
|
|
|
127
127
|
.passthrough();
|
|
128
128
|
const getDomCommandSchema = z
|
|
129
129
|
.object({
|
|
130
|
-
type: z.literal(
|
|
130
|
+
type: z.literal("getDom"),
|
|
131
131
|
selector: z.string().optional(),
|
|
132
132
|
includeShadowDom: z.boolean().optional(),
|
|
133
133
|
})
|
|
134
134
|
.passthrough();
|
|
135
135
|
const navigateCommandSchema = z
|
|
136
136
|
.object({
|
|
137
|
-
type: z.literal(
|
|
137
|
+
type: z.literal("navigate"),
|
|
138
138
|
url: z.string(),
|
|
139
139
|
})
|
|
140
140
|
.passthrough();
|
|
141
141
|
const pingCommandSchema = z
|
|
142
142
|
.object({
|
|
143
|
-
type: z.literal(
|
|
143
|
+
type: z.literal("ping"),
|
|
144
144
|
})
|
|
145
145
|
.passthrough();
|
|
146
146
|
const screenshotCommandSchema = z
|
|
147
147
|
.object({
|
|
148
|
-
type: z.literal(
|
|
149
|
-
mode: z.union([z.literal(
|
|
148
|
+
type: z.literal("screenshot"),
|
|
149
|
+
mode: z.union([z.literal("full"), z.literal("element")]).default("full"),
|
|
150
150
|
selector: z.union([z.string(), z.null()]).optional(),
|
|
151
151
|
quality: z.number().finite().min(0).max(100).optional(),
|
|
152
152
|
timeoutMs: z.number().finite().positive().optional(),
|
|
153
153
|
renderer: z
|
|
154
|
-
.union([
|
|
154
|
+
.union([
|
|
155
|
+
z.literal("auto"),
|
|
156
|
+
z.literal("puppeteer"),
|
|
157
|
+
z.literal("html2canvas"),
|
|
158
|
+
z.literal("html-to-image"),
|
|
159
|
+
])
|
|
155
160
|
.optional(),
|
|
156
161
|
hooks: z.array(z.unknown()).optional(),
|
|
157
162
|
})
|
|
158
163
|
.passthrough();
|
|
159
164
|
const discoverSelectorsCommandSchema = z
|
|
160
165
|
.object({
|
|
161
|
-
type: z.literal(
|
|
166
|
+
type: z.literal("discoverSelectors"),
|
|
162
167
|
scopeSelector: z.union([z.string(), z.null()]).optional(),
|
|
163
168
|
limit: z.number().int().positive().optional(),
|
|
164
169
|
includeHidden: z.boolean().optional(),
|
|
165
170
|
})
|
|
166
171
|
.passthrough();
|
|
167
|
-
const commandSchema = z.discriminatedUnion(
|
|
172
|
+
const commandSchema = z.discriminatedUnion("type", [
|
|
168
173
|
runScriptCommandSchema,
|
|
169
174
|
getDomCommandSchema,
|
|
170
175
|
navigateCommandSchema,
|
|
@@ -174,33 +179,35 @@ const commandSchema = z.discriminatedUnion('type', [
|
|
|
174
179
|
]);
|
|
175
180
|
const timeoutOverrideSchema = z.number().finite().positive().optional();
|
|
176
181
|
const resolveDaemonPort = (value) => {
|
|
177
|
-
if (value && typeof value ===
|
|
182
|
+
if (value && typeof value === "object" && value !== null) {
|
|
178
183
|
const portCandidate = value.port;
|
|
179
|
-
if (typeof portCandidate ===
|
|
184
|
+
if (typeof portCandidate === "number" && Number.isFinite(portCandidate) && portCandidate > 0) {
|
|
180
185
|
return portCandidate;
|
|
181
186
|
}
|
|
182
187
|
}
|
|
183
188
|
return SWEETLINK_DEFAULT_PORT;
|
|
184
189
|
};
|
|
185
190
|
const isSecretResolution = (value) => {
|
|
186
|
-
if (!value || typeof value !==
|
|
191
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
187
192
|
return false;
|
|
188
193
|
}
|
|
189
194
|
const candidate = value;
|
|
190
|
-
if (typeof candidate.secret !==
|
|
195
|
+
if (typeof candidate.secret !== "string" || candidate.secret.length === 0) {
|
|
191
196
|
return false;
|
|
192
197
|
}
|
|
193
|
-
if (candidate.source !==
|
|
198
|
+
if (candidate.source !== "env" &&
|
|
199
|
+
candidate.source !== "file" &&
|
|
200
|
+
candidate.source !== "generated") {
|
|
194
201
|
return false;
|
|
195
202
|
}
|
|
196
|
-
if (candidate.path !== undefined && typeof candidate.path !==
|
|
203
|
+
if (candidate.path !== undefined && typeof candidate.path !== "string") {
|
|
197
204
|
return false;
|
|
198
205
|
}
|
|
199
206
|
return true;
|
|
200
207
|
};
|
|
201
208
|
const assertSecretResolution = (value) => {
|
|
202
209
|
if (!isSecretResolution(value)) {
|
|
203
|
-
throw new TypeError(
|
|
210
|
+
throw new TypeError("SweetLink secret resolution payload is invalid");
|
|
204
211
|
}
|
|
205
212
|
return value;
|
|
206
213
|
};
|
|
@@ -213,24 +220,24 @@ async function main() {
|
|
|
213
220
|
}
|
|
214
221
|
const rawSecretResolution = await resolveSweetLinkSecret({ autoCreate: true });
|
|
215
222
|
const { secret, source, path: secretPath } = assertSecretResolution(rawSecretResolution);
|
|
216
|
-
log(`SweetLink secret source: ${source}${secretPath ? ` (${secretPath})` :
|
|
223
|
+
log(`SweetLink secret source: ${source}${secretPath ? ` (${secretPath})` : ""}`);
|
|
217
224
|
ensureCertificates();
|
|
218
225
|
const { cert, key } = loadCertificates();
|
|
219
226
|
const state = new SweetLinkState(secret);
|
|
220
227
|
const server = createHttpsServer({ key, cert }, (req, res) => {
|
|
221
228
|
handleHttpRequest(state, req, res).catch((error) => {
|
|
222
|
-
console.warn(
|
|
229
|
+
console.warn("SweetLink daemon request handler failed:", getErrorMessage(error));
|
|
223
230
|
});
|
|
224
231
|
});
|
|
225
232
|
const wsServer = new WebSocketServer({ server, path: SWEETLINK_WS_PATH });
|
|
226
|
-
wsServer.on(
|
|
227
|
-
server.listen(daemonPort,
|
|
233
|
+
wsServer.on("connection", (socket) => state.handleSocket(socket));
|
|
234
|
+
server.listen(daemonPort, "127.0.0.1", () => {
|
|
228
235
|
log(`SweetLink daemon listening on https://localhost:${daemonPort}`);
|
|
229
236
|
log(`WebSocket endpoint ready at wss://localhost:${daemonPort}${SWEETLINK_WS_PATH}`);
|
|
230
|
-
log(
|
|
237
|
+
log("Press Ctrl+C to stop.");
|
|
231
238
|
});
|
|
232
|
-
process.on(
|
|
233
|
-
process.on(
|
|
239
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
240
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
234
241
|
function shutdown(signal) {
|
|
235
242
|
log(`Received ${signal}, shutting down SweetLink daemon...`);
|
|
236
243
|
wsServer.close();
|
|
@@ -248,18 +255,18 @@ async function main() {
|
|
|
248
255
|
async function isDaemonAlreadyRunning(port) {
|
|
249
256
|
return await new Promise((resolve) => {
|
|
250
257
|
const request = httpsRequest({
|
|
251
|
-
hostname:
|
|
258
|
+
hostname: "127.0.0.1",
|
|
252
259
|
port,
|
|
253
|
-
path:
|
|
254
|
-
method:
|
|
260
|
+
path: "/healthz",
|
|
261
|
+
method: "GET",
|
|
255
262
|
rejectUnauthorized: false,
|
|
256
263
|
timeout: 750,
|
|
257
264
|
}, (response) => {
|
|
258
265
|
response.resume();
|
|
259
266
|
resolve(response.statusCode === 200);
|
|
260
267
|
});
|
|
261
|
-
request.on(
|
|
262
|
-
request.on(
|
|
268
|
+
request.on("error", () => resolve(false));
|
|
269
|
+
request.on("timeout", () => {
|
|
263
270
|
request.destroy();
|
|
264
271
|
resolve(false);
|
|
265
272
|
});
|
|
@@ -275,29 +282,29 @@ class SweetLinkState {
|
|
|
275
282
|
unrefTimer(expiryInterval);
|
|
276
283
|
}
|
|
277
284
|
verifyCliToken(token) {
|
|
278
|
-
return verifySweetLinkToken({ secret: this.#secret, token, expectedScope:
|
|
285
|
+
return verifySweetLinkToken({ secret: this.#secret, token, expectedScope: "cli" });
|
|
279
286
|
}
|
|
280
287
|
handleSocket(socket) {
|
|
281
288
|
let sessionId = null;
|
|
282
|
-
socket.on(
|
|
289
|
+
socket.on("message", (data) => {
|
|
283
290
|
try {
|
|
284
291
|
const raw = decodeSocketPayload(data);
|
|
285
292
|
const parsedMessage = parseClientMessage(JSON.parse(raw));
|
|
286
293
|
const message = parsedMessage;
|
|
287
294
|
switch (message.kind) {
|
|
288
|
-
case
|
|
295
|
+
case "register": {
|
|
289
296
|
sessionId = this.#handleRegister(socket, message);
|
|
290
297
|
break;
|
|
291
298
|
}
|
|
292
|
-
case
|
|
299
|
+
case "heartbeat": {
|
|
293
300
|
this.#touchSession(message.sessionId);
|
|
294
301
|
break;
|
|
295
302
|
}
|
|
296
|
-
case
|
|
303
|
+
case "commandResult": {
|
|
297
304
|
this.#handleCommandResult(message.sessionId, message.result);
|
|
298
305
|
break;
|
|
299
306
|
}
|
|
300
|
-
case
|
|
307
|
+
case "console": {
|
|
301
308
|
this.#handleConsoleEvents(message.sessionId, message.events);
|
|
302
309
|
break;
|
|
303
310
|
}
|
|
@@ -314,10 +321,12 @@ class SweetLinkState {
|
|
|
314
321
|
console.warn(`SweetLink socket error: ${errorMessage}`);
|
|
315
322
|
}
|
|
316
323
|
});
|
|
317
|
-
socket.once(
|
|
324
|
+
socket.once("close", (code, reasonBuffer) => {
|
|
318
325
|
if (sessionId) {
|
|
319
|
-
const reasonText = reasonBuffer?.toString?.(
|
|
320
|
-
const closeDetail = reasonText && reasonText.length > 0
|
|
326
|
+
const reasonText = reasonBuffer?.toString?.("utf8") ?? "";
|
|
327
|
+
const closeDetail = reasonText && reasonText.length > 0
|
|
328
|
+
? `socket closed (${code}: ${reasonText})`
|
|
329
|
+
: `socket closed (${code})`;
|
|
321
330
|
this.#removeSession(sessionId, closeDetail);
|
|
322
331
|
}
|
|
323
332
|
});
|
|
@@ -328,7 +337,7 @@ class SweetLinkState {
|
|
|
328
337
|
const consoleEventsBuffered = entry.consoleBuffer.length;
|
|
329
338
|
let consoleErrorsBuffered = 0;
|
|
330
339
|
for (const event of entry.consoleBuffer) {
|
|
331
|
-
if (event.level ===
|
|
340
|
+
if (event.level === "error") {
|
|
332
341
|
consoleErrorsBuffered += 1;
|
|
333
342
|
}
|
|
334
343
|
}
|
|
@@ -354,15 +363,15 @@ class SweetLinkState {
|
|
|
354
363
|
sendCommand(sessionId, rawCommand, timeoutMs = 15_000) {
|
|
355
364
|
const session = this.#sessions.get(sessionId);
|
|
356
365
|
if (!session) {
|
|
357
|
-
throw new Error(
|
|
366
|
+
throw new Error("Session not found or offline");
|
|
358
367
|
}
|
|
359
368
|
if (session.socket.readyState !== WebSocket.OPEN) {
|
|
360
|
-
throw new Error(
|
|
369
|
+
throw new Error("Session socket is not open");
|
|
361
370
|
}
|
|
362
371
|
const commandId = createSweetLinkCommandId();
|
|
363
372
|
const command = { ...rawCommand, id: commandId };
|
|
364
373
|
const payload = {
|
|
365
|
-
kind:
|
|
374
|
+
kind: "command",
|
|
366
375
|
sessionId: session.metadata.sessionId,
|
|
367
376
|
command,
|
|
368
377
|
};
|
|
@@ -370,7 +379,7 @@ class SweetLinkState {
|
|
|
370
379
|
return new Promise((resolve, reject) => {
|
|
371
380
|
const timeout = setTimeout(() => {
|
|
372
381
|
session.pending.delete(commandId);
|
|
373
|
-
reject(new Error(
|
|
382
|
+
reject(new Error("Command timed out"));
|
|
374
383
|
}, timeoutMs);
|
|
375
384
|
session.pending.set(commandId, {
|
|
376
385
|
commandId,
|
|
@@ -400,9 +409,9 @@ class SweetLinkState {
|
|
|
400
409
|
#handleRegister(socket, message) {
|
|
401
410
|
const token = message.token;
|
|
402
411
|
const sessionId = message.sessionId;
|
|
403
|
-
const payload = verifySweetLinkToken({ secret: this.#secret, token, expectedScope:
|
|
412
|
+
const payload = verifySweetLinkToken({ secret: this.#secret, token, expectedScope: "session" });
|
|
404
413
|
if (!payload.sessionId || payload.sessionId !== sessionId) {
|
|
405
|
-
throw new Error(
|
|
414
|
+
throw new Error("Session token mismatch");
|
|
406
415
|
}
|
|
407
416
|
const metadata = {
|
|
408
417
|
sessionId,
|
|
@@ -429,7 +438,7 @@ class SweetLinkState {
|
|
|
429
438
|
this.#sessions.set(sessionId, entry);
|
|
430
439
|
try {
|
|
431
440
|
const metadataMessage = {
|
|
432
|
-
kind:
|
|
441
|
+
kind: "metadata",
|
|
433
442
|
sessionId,
|
|
434
443
|
codename: metadata.codename,
|
|
435
444
|
};
|
|
@@ -476,7 +485,7 @@ class SweetLinkState {
|
|
|
476
485
|
}
|
|
477
486
|
}
|
|
478
487
|
#socketStateToString(readyState) {
|
|
479
|
-
return SOCKET_STATE_LABEL[readyState] ??
|
|
488
|
+
return SOCKET_STATE_LABEL[readyState] ?? "unknown";
|
|
480
489
|
}
|
|
481
490
|
#removeSession(sessionId, reason) {
|
|
482
491
|
const session = this.#sessions.get(sessionId);
|
|
@@ -505,24 +514,24 @@ async function handleHttpRequest(state, req, res) {
|
|
|
505
514
|
const basePort = daemonPort;
|
|
506
515
|
const requestUrl = req.url ? new URL(req.url, `https://localhost:${basePort}`) : null;
|
|
507
516
|
if (!requestUrl) {
|
|
508
|
-
respondJson(res, 400, { error:
|
|
517
|
+
respondJson(res, 400, { error: "Invalid request URL" });
|
|
509
518
|
return;
|
|
510
519
|
}
|
|
511
|
-
if (req.method ===
|
|
520
|
+
if (req.method === "OPTIONS") {
|
|
512
521
|
respondCors(res);
|
|
513
522
|
return;
|
|
514
523
|
}
|
|
515
|
-
if (requestUrl.pathname ===
|
|
516
|
-
respondJson(res, 200, { status:
|
|
524
|
+
if (requestUrl.pathname === "/healthz") {
|
|
525
|
+
respondJson(res, 200, { status: "ok" });
|
|
517
526
|
return;
|
|
518
527
|
}
|
|
519
528
|
const authorization = req.headers.authorization;
|
|
520
|
-
if (!authorization?.startsWith(
|
|
521
|
-
respondJson(res, 401, { error:
|
|
529
|
+
if (!authorization?.startsWith("Bearer ")) {
|
|
530
|
+
respondJson(res, 401, { error: "Missing SweetLink token" });
|
|
522
531
|
return;
|
|
523
532
|
}
|
|
524
533
|
try {
|
|
525
|
-
const token = authorization.slice(
|
|
534
|
+
const token = authorization.slice("Bearer ".length).trim();
|
|
526
535
|
state.verifyCliToken(token);
|
|
527
536
|
}
|
|
528
537
|
catch (error) {
|
|
@@ -530,29 +539,29 @@ async function handleHttpRequest(state, req, res) {
|
|
|
530
539
|
respondJson(res, 401, { error: message });
|
|
531
540
|
return;
|
|
532
541
|
}
|
|
533
|
-
if (req.method ===
|
|
542
|
+
if (req.method === "GET" && requestUrl.pathname === "/sessions") {
|
|
534
543
|
const sessions = state.listSessions();
|
|
535
544
|
respondJson(res, 200, { sessions });
|
|
536
545
|
return;
|
|
537
546
|
}
|
|
538
|
-
if (req.method ===
|
|
539
|
-
requestUrl.pathname.startsWith(
|
|
540
|
-
requestUrl.pathname.endsWith(
|
|
541
|
-
const sessionId = requestUrl.pathname.split(
|
|
547
|
+
if (req.method === "GET" &&
|
|
548
|
+
requestUrl.pathname.startsWith("/sessions/") &&
|
|
549
|
+
requestUrl.pathname.endsWith("/console")) {
|
|
550
|
+
const sessionId = requestUrl.pathname.split("/")[2];
|
|
542
551
|
if (sessionId == null) {
|
|
543
|
-
respondJson(res, 400, { error:
|
|
552
|
+
respondJson(res, 400, { error: "Session id missing" });
|
|
544
553
|
return;
|
|
545
554
|
}
|
|
546
555
|
const events = state.getSessionConsole(sessionId);
|
|
547
556
|
respondJson(res, 200, { sessionId, events });
|
|
548
557
|
return;
|
|
549
558
|
}
|
|
550
|
-
if (req.method ===
|
|
551
|
-
requestUrl.pathname.startsWith(
|
|
552
|
-
requestUrl.pathname.endsWith(
|
|
553
|
-
const sessionId = requestUrl.pathname.split(
|
|
559
|
+
if (req.method === "POST" &&
|
|
560
|
+
requestUrl.pathname.startsWith("/sessions/") &&
|
|
561
|
+
requestUrl.pathname.endsWith("/command")) {
|
|
562
|
+
const sessionId = requestUrl.pathname.split("/")[2];
|
|
554
563
|
if (sessionId == null) {
|
|
555
|
-
respondJson(res, 400, { error:
|
|
564
|
+
respondJson(res, 400, { error: "Session id missing" });
|
|
556
565
|
return;
|
|
557
566
|
}
|
|
558
567
|
try {
|
|
@@ -567,18 +576,18 @@ async function handleHttpRequest(state, req, res) {
|
|
|
567
576
|
}
|
|
568
577
|
return;
|
|
569
578
|
}
|
|
570
|
-
respondJson(res, 404, { error:
|
|
579
|
+
respondJson(res, 404, { error: "Not Found" });
|
|
571
580
|
}
|
|
572
581
|
async function readJson(req) {
|
|
573
582
|
const chunks = [];
|
|
574
583
|
for await (const chunk of req) {
|
|
575
|
-
const bufferChunk = typeof chunk ===
|
|
584
|
+
const bufferChunk = typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk;
|
|
576
585
|
chunks.push(bufferChunk);
|
|
577
586
|
}
|
|
578
587
|
if (chunks.length === 0) {
|
|
579
588
|
return {};
|
|
580
589
|
}
|
|
581
|
-
const text = Buffer.concat(chunks).toString(
|
|
590
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
582
591
|
if (!text) {
|
|
583
592
|
return {};
|
|
584
593
|
}
|
|
@@ -587,38 +596,38 @@ async function readJson(req) {
|
|
|
587
596
|
function respondJson(res, status, body) {
|
|
588
597
|
const payload = JSON.stringify(body ?? {}, null, 2);
|
|
589
598
|
res.statusCode = status;
|
|
590
|
-
res.setHeader(
|
|
591
|
-
res.setHeader(
|
|
592
|
-
res.setHeader(
|
|
593
|
-
res.setHeader(
|
|
599
|
+
res.setHeader("Content-Type", "application/json");
|
|
600
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
601
|
+
res.setHeader("Access-Control-Allow-Headers", "authorization, content-type");
|
|
602
|
+
res.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
|
|
594
603
|
res.end(payload);
|
|
595
604
|
}
|
|
596
605
|
function respondCors(res) {
|
|
597
606
|
res.statusCode = 204;
|
|
598
|
-
res.setHeader(
|
|
599
|
-
res.setHeader(
|
|
600
|
-
res.setHeader(
|
|
607
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
608
|
+
res.setHeader("Access-Control-Allow-Headers", "authorization, content-type");
|
|
609
|
+
res.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
|
|
601
610
|
res.end();
|
|
602
611
|
}
|
|
603
612
|
function decodeSocketPayload(data) {
|
|
604
|
-
if (typeof data ===
|
|
613
|
+
if (typeof data === "string") {
|
|
605
614
|
return data;
|
|
606
615
|
}
|
|
607
616
|
if (Buffer.isBuffer(data)) {
|
|
608
|
-
return data.toString(
|
|
617
|
+
return data.toString("utf8");
|
|
609
618
|
}
|
|
610
619
|
if (Array.isArray(data)) {
|
|
611
620
|
const buffers = data.map((chunk) => (Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
|
|
612
|
-
return Buffer.concat(buffers).toString(
|
|
621
|
+
return Buffer.concat(buffers).toString("utf8");
|
|
613
622
|
}
|
|
614
623
|
if (ArrayBuffer.isView(data)) {
|
|
615
624
|
const view = data;
|
|
616
|
-
return Buffer.from(view.buffer, view.byteOffset, view.byteLength).toString(
|
|
625
|
+
return Buffer.from(view.buffer, view.byteOffset, view.byteLength).toString("utf8");
|
|
617
626
|
}
|
|
618
627
|
if (data instanceof ArrayBuffer) {
|
|
619
|
-
return Buffer.from(data).toString(
|
|
628
|
+
return Buffer.from(data).toString("utf8");
|
|
620
629
|
}
|
|
621
|
-
return Buffer.from([]).toString(
|
|
630
|
+
return Buffer.from([]).toString("utf8");
|
|
622
631
|
}
|
|
623
632
|
function parseClientMessage(raw) {
|
|
624
633
|
return clientMessageSchema.parse(raw);
|
|
@@ -630,8 +639,8 @@ function parseCommandRequest(raw) {
|
|
|
630
639
|
return { command, timeoutMs };
|
|
631
640
|
}
|
|
632
641
|
function ensureObject(value) {
|
|
633
|
-
if (!value || typeof value !==
|
|
634
|
-
throw new Error(
|
|
642
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
643
|
+
throw new Error("Request body must be a JSON object");
|
|
635
644
|
}
|
|
636
645
|
return value;
|
|
637
646
|
}
|
|
@@ -639,21 +648,21 @@ function ensureCertificates() {
|
|
|
639
648
|
if (existsSync(CERT_PATH) && existsSync(KEY_PATH)) {
|
|
640
649
|
return;
|
|
641
650
|
}
|
|
642
|
-
log(
|
|
651
|
+
log("Generating SweetLink TLS certificates via mkcert...");
|
|
643
652
|
mkdirSync(CERT_DIR, { recursive: true });
|
|
644
|
-
const mkcertLookup = spawnSync(
|
|
653
|
+
const mkcertLookup = spawnSync("which", ["mkcert"], { stdio: "pipe" });
|
|
645
654
|
if (mkcertLookup.status !== 0) {
|
|
646
655
|
const secretPath = getDefaultSweetLinkSecretPath();
|
|
647
656
|
throw new Error('mkcert is required but not found. Install via "brew install mkcert nss" and rerun pnpm sweetlink. ' +
|
|
648
657
|
`Generated SweetLink secret saved at ${secretPath}.`);
|
|
649
658
|
}
|
|
650
|
-
const install = spawnSync(
|
|
659
|
+
const install = spawnSync("mkcert", ["-install"], { stdio: "inherit" });
|
|
651
660
|
if (install.status !== 0) {
|
|
652
|
-
throw new Error(
|
|
661
|
+
throw new Error("Failed to run mkcert -install");
|
|
653
662
|
}
|
|
654
|
-
const create = spawnSync(
|
|
663
|
+
const create = spawnSync("mkcert", ["-cert-file", CERT_PATH, "-key-file", KEY_PATH, "localhost", "127.0.0.1", "::1"], { stdio: "inherit" });
|
|
655
664
|
if (create.status !== 0) {
|
|
656
|
-
throw new Error(
|
|
665
|
+
throw new Error("Failed to generate mkcert certificates");
|
|
657
666
|
}
|
|
658
667
|
}
|
|
659
668
|
function loadCertificates() {
|