diggylab 0.1.43 → 0.1.44
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 +16 -7
- package/cli.js +50 -17
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -111,8 +111,12 @@ The equivalents: `diggylab login --cookies 'name=value; ...'` for a quoted one-l
|
|
|
111
111
|
`diggylab login --cookies cookies.json` for a saved file.
|
|
112
112
|
|
|
113
113
|
Only `name` and `value` are required; the agent fills in the rest (`.facebook.com`, `/`, session
|
|
114
|
-
expiry). Every `facebook.com` cookie is kept
|
|
115
|
-
|
|
114
|
+
expiry). Every `facebook.com` cookie is kept. The export must contain `c_user` (the agent rejects it
|
|
115
|
+
otherwise) and `xs`, which carries the session — without `xs` Facebook refuses the session during the
|
|
116
|
+
check, and `datr` is worth keeping too.
|
|
117
|
+
|
|
118
|
+
If you paste cookies, prefer the file form or `--cookies -` over `--cookies '...'`: a value on the
|
|
119
|
+
command line stays in your shell history, and pasted text stays in the terminal scrollback.
|
|
116
120
|
|
|
117
121
|
Do not use `document.cookie` from the Console: Facebook's `c_user` and `xs` are HttpOnly, so they
|
|
118
122
|
never appear there. The DevTools cookie table and the extension do show them.
|
|
@@ -126,12 +130,17 @@ happens the command says so — approve the login from your phone and import aga
|
|
|
126
130
|
|
|
127
131
|
## Troubleshooting
|
|
128
132
|
|
|
133
|
+
- `socket closed: 1005` followed by `session: ONLINE` about every 14 minutes is **normal**: the access
|
|
134
|
+
token lives 15 minutes, so the worker refreshes it one minute early and reconnects with the new one
|
|
135
|
+
(the disconnect lasts a second or two, and a running scan keeps going).
|
|
129
136
|
- `socket closed: 4009` right after `session: ONLINE` means **two workers use the same device** — for
|
|
130
137
|
example the systemd service plus a manual `diggylab run`. The worker that connected last keeps the
|
|
131
138
|
device and the older one stops with a message. Check `pgrep -af "diggylab run"` and
|
|
132
139
|
`systemctl status diggylab --no-pager`, then keep only one.
|
|
133
|
-
- `socket closed: 1006`
|
|
134
|
-
backoff. If it never settles, check
|
|
140
|
+
- `socket error:` plus `socket closed: 1006` and `RECONNECTING (NETWORK_ERROR)` is a dropped
|
|
141
|
+
connection; the worker retries with backoff. If it never settles, check `curl -sS -o /dev/null -w
|
|
142
|
+
'%{http_code}\n' https://api.diggylab.com/health` and that the machine clock is right (`date -u`),
|
|
143
|
+
because a clock off by more than five minutes makes the server's messages look stale.
|
|
135
144
|
- `Facebook: missing` after a login means the session did not survive; run `diggylab login` again.
|
|
136
145
|
|
|
137
146
|
## Security notes
|
|
@@ -167,9 +176,9 @@ Wants=network-online.target
|
|
|
167
176
|
|
|
168
177
|
[Service]
|
|
169
178
|
ExecStart=/usr/local/bin/diggylab run
|
|
170
|
-
Environment=DIGGYLAB_AGENT_DIR=/root/.diggylab
|
|
171
|
-
Environment=DIGGYLAB_API_URL=https://api.diggylab.com
|
|
172
|
-
Restart=
|
|
179
|
+
Environment="DIGGYLAB_AGENT_DIR=/root/.diggylab"
|
|
180
|
+
Environment="DIGGYLAB_API_URL=https://api.diggylab.com"
|
|
181
|
+
Restart=on-failure
|
|
173
182
|
RestartSec=10
|
|
174
183
|
|
|
175
184
|
[Install]
|
package/cli.js
CHANGED
|
@@ -2434,11 +2434,10 @@ function parseCookieTable(input) {
|
|
|
2434
2434
|
const cookies = [];
|
|
2435
2435
|
for (const line of input.split("\n")) {
|
|
2436
2436
|
const columns = line.split(" ");
|
|
2437
|
-
|
|
2438
|
-
const name = columns[0].trim();
|
|
2437
|
+
const name = columns[0]?.trim() ?? "";
|
|
2439
2438
|
const value = columns[1];
|
|
2440
|
-
if (!name || name === "Name"
|
|
2441
|
-
cookies.push({ name, value });
|
|
2439
|
+
if (!value || !name || name === "Name") continue;
|
|
2440
|
+
cookies.push({ domain: columns[2]?.trim(), name, value });
|
|
2442
2441
|
}
|
|
2443
2442
|
return cookies;
|
|
2444
2443
|
}
|
|
@@ -2528,6 +2527,9 @@ function createFileCrypto(keyPath) {
|
|
|
2528
2527
|
// src/cli/systemd.ts
|
|
2529
2528
|
var SERVICE_NAME = "diggylab";
|
|
2530
2529
|
var SERVICE_PATH = `/etc/systemd/system/${SERVICE_NAME}.service`;
|
|
2530
|
+
function quote(value) {
|
|
2531
|
+
return /\s/.test(value) ? `"${value.replace(/"/g, '\\"')}"` : value;
|
|
2532
|
+
}
|
|
2531
2533
|
function buildSystemdUnit(options) {
|
|
2532
2534
|
return `[Unit]
|
|
2533
2535
|
Description=DiggyLab Desktop Agent
|
|
@@ -2535,9 +2537,9 @@ After=network-online.target
|
|
|
2535
2537
|
Wants=network-online.target
|
|
2536
2538
|
|
|
2537
2539
|
[Service]
|
|
2538
|
-
ExecStart=${options.execStart} run
|
|
2539
|
-
Environment=DIGGYLAB_AGENT_DIR=${options.configDirectory}
|
|
2540
|
-
Environment=DIGGYLAB_API_URL=${options.apiOrigin}
|
|
2540
|
+
ExecStart=${options.execStart.map(quote).join(" ")} run
|
|
2541
|
+
Environment="DIGGYLAB_AGENT_DIR=${options.configDirectory}"
|
|
2542
|
+
Environment="DIGGYLAB_API_URL=${options.apiOrigin}"
|
|
2541
2543
|
Restart=on-failure
|
|
2542
2544
|
RestartSec=10
|
|
2543
2545
|
|
|
@@ -2612,6 +2614,14 @@ function createRuntime(options) {
|
|
|
2612
2614
|
function stamp() {
|
|
2613
2615
|
return `[${(/* @__PURE__ */ new Date()).toTimeString().slice(0, 8)}]`;
|
|
2614
2616
|
}
|
|
2617
|
+
function describeSocketError(error) {
|
|
2618
|
+
if (error instanceof Error) {
|
|
2619
|
+
const code = error.code;
|
|
2620
|
+
return `${error.message || error.name}${typeof code === "string" ? ` (${code})` : ""}`;
|
|
2621
|
+
}
|
|
2622
|
+
if (error && typeof error === "object" && "type" in error) return `event ${String(error.type)}`;
|
|
2623
|
+
return String(error);
|
|
2624
|
+
}
|
|
2615
2625
|
function waitForFacebookLogin(login, timeoutMs) {
|
|
2616
2626
|
return new Promise((resolve, reject) => {
|
|
2617
2627
|
const timer = setTimeout(() => {
|
|
@@ -2711,17 +2721,22 @@ async function commandRename(runtime, options, io) {
|
|
|
2711
2721
|
return 1;
|
|
2712
2722
|
}
|
|
2713
2723
|
const requested = options.name?.trim() || await promptForName(io);
|
|
2714
|
-
|
|
2715
|
-
if (!name) {
|
|
2724
|
+
if (!requested) {
|
|
2716
2725
|
io.error('A device name is required, for example `diggylab rename --name "VPS H\xE0 N\u1ED9i"`.');
|
|
2717
2726
|
return 2;
|
|
2718
2727
|
}
|
|
2728
|
+
const name = sanitizeDeviceName(requested);
|
|
2719
2729
|
try {
|
|
2720
2730
|
const device = await runtime.manager.rename(name);
|
|
2721
2731
|
io.log(`Renamed to "${device.name}".`);
|
|
2722
2732
|
await printReadiness(runtime, io);
|
|
2723
2733
|
return 0;
|
|
2724
2734
|
} catch (error) {
|
|
2735
|
+
if (error instanceof DesktopAgentClientError && error.code === "DESKTOP_RESPONSE_INVALID") {
|
|
2736
|
+
io.error("The API answered the rename with something unexpected \u2014 usually an API that does not have this endpoint yet.");
|
|
2737
|
+
io.error(`Deploy the API, then check it with: curl -s -o /dev/null -w '%{http_code}\\n' -X PATCH https://api.diggylab.com/desktop-agent-auth/me -H 'content-type: application/json' -d '{"name":"x"}' (401 = the endpoint exists)`);
|
|
2738
|
+
return 1;
|
|
2739
|
+
}
|
|
2725
2740
|
io.error(`Could not rename this device: ${error instanceof Error ? error.message : String(error)}`);
|
|
2726
2741
|
return 1;
|
|
2727
2742
|
}
|
|
@@ -2798,8 +2813,10 @@ async function importFacebookCookies(runtime, source, io) {
|
|
|
2798
2813
|
io.error("Open facebook.com in your browser, make sure you are logged in, then copy the `cookie:` request header again.");
|
|
2799
2814
|
return 1;
|
|
2800
2815
|
}
|
|
2816
|
+
let saved = false;
|
|
2801
2817
|
try {
|
|
2802
2818
|
const accountId = await runtime.facebook.save(state);
|
|
2819
|
+
saved = true;
|
|
2803
2820
|
io.log(`Facebook session saved for account ${accountId}. Asking Facebook to confirm it...`);
|
|
2804
2821
|
const condition = await validateImportedSession(runtime, state);
|
|
2805
2822
|
if (condition !== "READY") {
|
|
@@ -2809,6 +2826,7 @@ async function importFacebookCookies(runtime, source, io) {
|
|
|
2809
2826
|
}
|
|
2810
2827
|
} catch (error) {
|
|
2811
2828
|
const message = error instanceof Error ? error.message : String(error);
|
|
2829
|
+
if (saved) await runtime.facebook.clear();
|
|
2812
2830
|
if (/Executable doesn't exist|playwright install/i.test(message)) {
|
|
2813
2831
|
io.error(MISSING_BROWSER_HINT);
|
|
2814
2832
|
} else if (message === "FACEBOOK_SESSION_INVALID") {
|
|
@@ -2867,7 +2885,9 @@ function readAnswer(options = {}) {
|
|
|
2867
2885
|
return;
|
|
2868
2886
|
}
|
|
2869
2887
|
if (idle) clearTimeout(idle);
|
|
2870
|
-
idle = setTimeout(
|
|
2888
|
+
idle = setTimeout(() => {
|
|
2889
|
+
if (!options.until || options.until(value)) finish();
|
|
2890
|
+
}, 350);
|
|
2871
2891
|
};
|
|
2872
2892
|
process.stdin.setEncoding("utf8");
|
|
2873
2893
|
process.stdin.on("data", handleData);
|
|
@@ -2895,10 +2915,14 @@ async function commandInstallBrowser(io) {
|
|
|
2895
2915
|
child.once("exit", (value) => resolve(value ?? 1));
|
|
2896
2916
|
});
|
|
2897
2917
|
let code = 1;
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2918
|
+
try {
|
|
2919
|
+
for (const [index, args] of browserInstallCommands(process.platform).entries()) {
|
|
2920
|
+
if (index > 0) io.log("Installing the system libraries failed; downloading the browser only...");
|
|
2921
|
+
code = await run(args);
|
|
2922
|
+
if (code === 0) break;
|
|
2923
|
+
}
|
|
2924
|
+
} catch {
|
|
2925
|
+
code = 1;
|
|
2902
2926
|
}
|
|
2903
2927
|
if (code !== 0) {
|
|
2904
2928
|
io.error(soft ? "Chromium download failed. Run `diggylab install-browser` later." : "Chromium download failed. Check the network and try again.");
|
|
@@ -2925,7 +2949,7 @@ async function commandRun(runtime, io) {
|
|
|
2925
2949
|
headers: { authorization: `Bearer ${accessToken}` },
|
|
2926
2950
|
maxPayload: 16 * 1024
|
|
2927
2951
|
});
|
|
2928
|
-
socket.on("error", (error) => io.log(`${stamp()} socket error: ${error
|
|
2952
|
+
socket.on("error", (error) => io.log(`${stamp()} socket error: ${describeSocketError(error)}`));
|
|
2929
2953
|
socket.on("close", (code, reason) => {
|
|
2930
2954
|
io.log(`${stamp()} socket closed: ${code}${reason?.length ? ` (${reason.toString()})` : ""}`);
|
|
2931
2955
|
});
|
|
@@ -3036,7 +3060,7 @@ function runCommand(command, args) {
|
|
|
3036
3060
|
function resolveServiceExecStart() {
|
|
3037
3061
|
const candidates = [join3(dirname3(process.execPath), "diggylab"), "/usr/local/bin/diggylab", "/usr/bin/diggylab"];
|
|
3038
3062
|
const link = candidates.find((candidate) => existsSync2(candidate));
|
|
3039
|
-
return link
|
|
3063
|
+
return link ? [link] : [process.execPath, fileURLToPath(import.meta.url)];
|
|
3040
3064
|
}
|
|
3041
3065
|
async function commandInstallService(runtime, io) {
|
|
3042
3066
|
if (process.platform !== "linux") {
|
|
@@ -3059,6 +3083,11 @@ async function commandInstallService(runtime, io) {
|
|
|
3059
3083
|
return 1;
|
|
3060
3084
|
}
|
|
3061
3085
|
io.log(`Wrote ${SERVICE_PATH}.`);
|
|
3086
|
+
io.log(`State directory: ${runtime.directory}`);
|
|
3087
|
+
if (process.env.SUDO_USER && process.env.SUDO_USER !== "root" && runtime.directory === join3(homedir(), CONFIG_DIRECTORY)) {
|
|
3088
|
+
io.error(`Heads up: this ran as root, so the service will use ${runtime.directory} (root's home).`);
|
|
3089
|
+
io.error(`If you paired as "${process.env.SUDO_USER}", pass --config to point at that user's ~/${CONFIG_DIRECTORY} instead.`);
|
|
3090
|
+
}
|
|
3062
3091
|
await runCommand("systemctl", ["daemon-reload"]).catch(() => 1);
|
|
3063
3092
|
const code = await runCommand("systemctl", ["enable", "--now", SERVICE_NAME]);
|
|
3064
3093
|
if (code !== 0) {
|
|
@@ -3126,8 +3155,12 @@ async function main(argv, io = console) {
|
|
|
3126
3155
|
return commandRun(runtime, io);
|
|
3127
3156
|
case "status":
|
|
3128
3157
|
return commandStatus(runtime, io);
|
|
3129
|
-
|
|
3158
|
+
case "unpair":
|
|
3130
3159
|
return commandUnpair(runtime, io);
|
|
3160
|
+
default:
|
|
3161
|
+
io.error(`Unknown command "${command}".`);
|
|
3162
|
+
io.error(USAGE);
|
|
3163
|
+
return 2;
|
|
3131
3164
|
}
|
|
3132
3165
|
}
|
|
3133
3166
|
function isEntryPoint() {
|