@amd-gaia/agent-ui 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/browser-p1BgTzJo.js +8 -0
- package/dist/assets/index-DRnhDSoO.css +1 -0
- package/dist/assets/index-DmVbNLtH.js +72 -0
- package/dist/index.html +2 -2
- package/main.cjs +63 -65
- package/package.json +13 -13
- package/services/backend-installer.cjs +86 -4
- package/services/backend-orphan.cjs +83 -0
- package/services/tray-manager.cjs +24 -10
- package/dist/assets/browser-CTB2jwNe.js +0 -8
- package/dist/assets/index-B6CVnI2W.js +0 -565
- package/dist/assets/index-Dhg6L9-N.css +0 -1
package/dist/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<link rel="icon" type="image/png" href="./favicon.png" />
|
|
7
7
|
<title>GAIA</title>
|
|
8
|
-
<script type="module" crossorigin src="./assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
8
|
+
<script type="module" crossorigin src="./assets/index-DmVbNLtH.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="./assets/index-DRnhDSoO.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
package/main.cjs
CHANGED
|
@@ -17,7 +17,7 @@ const { app, BrowserWindow, dialog, shell } = require("electron");
|
|
|
17
17
|
const path = require("path");
|
|
18
18
|
const fs = require("fs");
|
|
19
19
|
const os = require("os");
|
|
20
|
-
const { spawn
|
|
20
|
+
const { spawn } = require("child_process");
|
|
21
21
|
const { pathToFileURL } = require("url");
|
|
22
22
|
|
|
23
23
|
// ── Shared log path ───────────────────────────────────────────────────────────
|
|
@@ -57,6 +57,7 @@ const TrayManager = require("./services/tray-manager.cjs");
|
|
|
57
57
|
const AgentProcessManager = require("./services/agent-process-manager.cjs");
|
|
58
58
|
const NotificationService = require("./services/notification-service.cjs");
|
|
59
59
|
const PortManager = require("./services/port-manager.cjs");
|
|
60
|
+
const { isGaiaBackendProcess } = require("./services/backend-orphan.cjs");
|
|
60
61
|
const { buildIndexQuery } = require("./services/index-query.cjs");
|
|
61
62
|
const backendInstaller = require("./services/backend-installer.cjs");
|
|
62
63
|
const installerProgressDialog = require("./services/backend-installer-progress-dialog.cjs");
|
|
@@ -224,6 +225,57 @@ let isQuitting = false;
|
|
|
224
225
|
|
|
225
226
|
// ── Backend Process ────────────────────────────────────────────────────────
|
|
226
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Terminate a backend left running from a previous launch.
|
|
230
|
+
*
|
|
231
|
+
* The desktop app may be double-clicked multiple times or crash, leaving an
|
|
232
|
+
* orphaned `gaia chat --ui` process. On Windows that process keeps an open
|
|
233
|
+
* handle on `gaia.exe`, so the next upgrade's `uv pip install --refresh` fails
|
|
234
|
+
* with `os error 32` (file in use) — issue #1388. We MUST run this BEFORE the
|
|
235
|
+
* installer (`bootstrapBackend()`), not just before spawning the new backend,
|
|
236
|
+
* so the executable can be replaced.
|
|
237
|
+
*
|
|
238
|
+
* The PID is read from ~/.gaia/backend.pid and verified to be a GAIA backend
|
|
239
|
+
* (conservative match) before signalling, so we never kill unrelated user
|
|
240
|
+
* processes (issue #782 TOCTOU mitigation). Idempotent: the pidfile is removed
|
|
241
|
+
* after the first call, so later calls are no-ops.
|
|
242
|
+
*/
|
|
243
|
+
async function cleanupOrphanedBackend() {
|
|
244
|
+
try {
|
|
245
|
+
const pidFile = path.join(_GAIA_DIR, "backend.pid");
|
|
246
|
+
if (!fs.existsSync(pidFile)) return;
|
|
247
|
+
try {
|
|
248
|
+
const existingPid = parseInt(fs.readFileSync(pidFile, "utf8").trim(), 10);
|
|
249
|
+
if (!Number.isNaN(existingPid)) {
|
|
250
|
+
console.log(`[main] Found existing backend pidfile (${existingPid}) — verifying process identity`);
|
|
251
|
+
|
|
252
|
+
let isBackend = false;
|
|
253
|
+
try {
|
|
254
|
+
isBackend = isGaiaBackendProcess(existingPid);
|
|
255
|
+
} catch (err) {
|
|
256
|
+
console.warn(`[main] Could not verify pid ${existingPid}: ${err.message}`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (!isBackend) {
|
|
260
|
+
console.log(`[main] PID ${existingPid} does not appear to be a GAIA backend; skipping kill`);
|
|
261
|
+
} else {
|
|
262
|
+
try {
|
|
263
|
+
await portManager.killBackend(existingPid);
|
|
264
|
+
console.log(`[main] Cleaned up previous backend pid ${existingPid}`);
|
|
265
|
+
} catch (err) {
|
|
266
|
+
console.warn(`[main] Could not clean previous backend pid ${existingPid}: ${err.message}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
} catch (err) {
|
|
271
|
+
console.warn(`[main] Failed reading backend pidfile: ${err.message}`);
|
|
272
|
+
}
|
|
273
|
+
try { fs.unlinkSync(pidFile); } catch { /* ignore */ }
|
|
274
|
+
} catch (err) {
|
|
275
|
+
console.warn(`[main] PID cleanup check failed: ${err.message}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
227
279
|
/**
|
|
228
280
|
* Start the GAIA Python backend. Expects the backend installer to have
|
|
229
281
|
* already ensured the venv is populated — callers should await
|
|
@@ -253,70 +305,10 @@ async function startBackend() {
|
|
|
253
305
|
backendPort = DEFAULT_BACKEND_PORT;
|
|
254
306
|
}
|
|
255
307
|
healthCheckUrl = `http://localhost:${backendPort}/api/health`;
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
try {
|
|
261
|
-
const pidFile = path.join(_GAIA_DIR, "backend.pid");
|
|
262
|
-
if (fs.existsSync(pidFile)) {
|
|
263
|
-
try {
|
|
264
|
-
const existingPid = parseInt(fs.readFileSync(pidFile, "utf8").trim(), 10);
|
|
265
|
-
if (!Number.isNaN(existingPid)) {
|
|
266
|
-
console.log(`[main] Found existing backend pidfile (${existingPid}) — verifying process identity`);
|
|
267
|
-
|
|
268
|
-
// Verify the PID belongs to a GAIA backend before signalling it.
|
|
269
|
-
// Prefer a conservative match on the process command to avoid
|
|
270
|
-
// accidentally killing unrelated user processes (TOCTOU mitigation).
|
|
271
|
-
let isBackend = false;
|
|
272
|
-
try {
|
|
273
|
-
if (process.platform === "linux") {
|
|
274
|
-
const procCmd = `/proc/${existingPid}/cmdline`;
|
|
275
|
-
if (fs.existsSync(procCmd)) {
|
|
276
|
-
const raw = fs.readFileSync(procCmd, "utf8");
|
|
277
|
-
// /proc/<pid>/cmdline is NUL-separated; use a stricter match to
|
|
278
|
-
// avoid killing unrelated Python processes (Jupyter, LSP, etc.).
|
|
279
|
-
// Legitimate backend uses: `gaia chat --ui --ui-port <port>`
|
|
280
|
-
const looksLikeGaiaBackend = raw.includes("gaia") && (raw.includes("chat") || raw.includes("--ui-port"));
|
|
281
|
-
if (looksLikeGaiaBackend) {
|
|
282
|
-
isBackend = true;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
} else if (process.platform === "darwin") {
|
|
286
|
-
try {
|
|
287
|
-
const out = spawnSync("ps", ["-p", String(existingPid), "-o", "command="], { encoding: "utf8" });
|
|
288
|
-
const cmd = (out && out.stdout) ? out.stdout : "";
|
|
289
|
-
const looksLikeGaiaBackend = cmd.includes("gaia") && (cmd.includes("chat") || cmd.includes("--ui-port"));
|
|
290
|
-
if (looksLikeGaiaBackend) {
|
|
291
|
-
isBackend = true;
|
|
292
|
-
}
|
|
293
|
-
} catch {
|
|
294
|
-
// fallthrough
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
} catch (err) {
|
|
298
|
-
console.warn(`[main] Could not verify pid ${existingPid}: ${err.message}`);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (!isBackend) {
|
|
302
|
-
console.log(`[main] PID ${existingPid} does not appear to be a GAIA backend; skipping kill`);
|
|
303
|
-
} else {
|
|
304
|
-
try {
|
|
305
|
-
await portManager.killBackend(existingPid);
|
|
306
|
-
console.log(`[main] Cleaned up previous backend pid ${existingPid}`);
|
|
307
|
-
} catch (err) {
|
|
308
|
-
console.warn(`[main] Could not clean previous backend pid ${existingPid}: ${err.message}`);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
} catch (err) {
|
|
313
|
-
console.warn(`[main] Failed reading backend pidfile: ${err.message}`);
|
|
314
|
-
}
|
|
315
|
-
try { fs.unlinkSync(pidFile); } catch { /* ignore */ }
|
|
316
|
-
}
|
|
317
|
-
} catch (err) {
|
|
318
|
-
console.warn(`[main] PID cleanup check failed: ${err.message}`);
|
|
319
|
-
}
|
|
308
|
+
// Defensively clean up any orphaned backend in case bootstrap was skipped
|
|
309
|
+
// on this path. cleanupOrphanedBackend() is idempotent and a no-op once the
|
|
310
|
+
// pidfile has already been consumed earlier in app startup.
|
|
311
|
+
await cleanupOrphanedBackend();
|
|
320
312
|
|
|
321
313
|
console.log(`Starting backend: ${gaiaCmd} chat --ui --ui-port ${backendPort}`);
|
|
322
314
|
|
|
@@ -801,6 +793,12 @@ app.whenReady().then(async () => {
|
|
|
801
793
|
console.warn("[main] Agent seeding failed (non-fatal):", err);
|
|
802
794
|
}
|
|
803
795
|
|
|
796
|
+
// Phase A0: kill any orphaned backend from a previous launch BEFORE the
|
|
797
|
+
// installer runs. On Windows a live `gaia chat --ui` holds an open handle on
|
|
798
|
+
// gaia.exe, so an upgrade's `uv pip install --refresh` fails with os error 32
|
|
799
|
+
// unless that process is gone first (issue #1388).
|
|
800
|
+
await cleanupOrphanedBackend();
|
|
801
|
+
|
|
804
802
|
// Phase A: ensure the Python backend is installed BEFORE creating the
|
|
805
803
|
// main window. The progress dialog owns the UI during this phase.
|
|
806
804
|
const bootstrapOk = await bootstrapBackend();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amd-gaia/agent-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"productName": "GAIA Agent UI",
|
|
6
6
|
"description": "Privacy-first agentic AI interface with document Q&A - runs 100% locally on AMD Ryzen AI",
|
|
@@ -67,23 +67,23 @@
|
|
|
67
67
|
"@testing-library/jest-dom": "^6.6.3",
|
|
68
68
|
"@testing-library/react": "^16.1.0",
|
|
69
69
|
"@testing-library/user-event": "^14.5.2",
|
|
70
|
-
"@types/react": "^
|
|
71
|
-
"@types/react-dom": "^
|
|
72
|
-
"@vitejs/plugin-react": "^
|
|
70
|
+
"@types/react": "^19.2.17",
|
|
71
|
+
"@types/react-dom": "^19.2.3",
|
|
72
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
73
73
|
"electron": "^42.2.0",
|
|
74
74
|
"electron-builder": "^26.8.1",
|
|
75
|
-
"jsdom": "^
|
|
76
|
-
"lucide-react": "^
|
|
75
|
+
"jsdom": "^29.1.1",
|
|
76
|
+
"lucide-react": "^1.17.0",
|
|
77
77
|
"msw": "^2.7.0",
|
|
78
78
|
"qrcode": "^1.5.4",
|
|
79
|
-
"react": "^
|
|
80
|
-
"react-dom": "^
|
|
81
|
-
"react-markdown": "^
|
|
79
|
+
"react": "^19.2.7",
|
|
80
|
+
"react-dom": "^19.2.7",
|
|
81
|
+
"react-markdown": "^10.1.0",
|
|
82
82
|
"rehype-sanitize": "^6.0.0",
|
|
83
83
|
"remark-gfm": "^4.0.1",
|
|
84
|
-
"typescript": "^
|
|
85
|
-
"vite": "^
|
|
86
|
-
"vitest": "^
|
|
87
|
-
"zustand": "^
|
|
84
|
+
"typescript": "^6.0.3",
|
|
85
|
+
"vite": "^8.0.16",
|
|
86
|
+
"vitest": "^4.1.8",
|
|
87
|
+
"zustand": "^5.0.14"
|
|
88
88
|
}
|
|
89
89
|
}
|
|
@@ -62,6 +62,11 @@ const NETWORK_CHECK_HOSTS = Object.freeze([
|
|
|
62
62
|
]);
|
|
63
63
|
const NETWORK_CHECK_TIMEOUT_MS = 5000;
|
|
64
64
|
|
|
65
|
+
// Pip-install resilience: the install stage fetches heavy transitive deps from
|
|
66
|
+
// PyPI, so retry a transient network failure a few times before giving up.
|
|
67
|
+
const INSTALL_MAX_ATTEMPTS = 3;
|
|
68
|
+
const INSTALL_RETRY_BACKOFF_MS = 3000;
|
|
69
|
+
|
|
65
70
|
// ── Bundled `uv` binary ──────────────────────────────────────────────────────
|
|
66
71
|
//
|
|
67
72
|
// Issue #782 / T3: the AppImage now ships a pinned `uv` under
|
|
@@ -468,6 +473,47 @@ function runCommand(cmd, args, { env, stageLabel } = {}) {
|
|
|
468
473
|
});
|
|
469
474
|
}
|
|
470
475
|
|
|
476
|
+
/**
|
|
477
|
+
* Detect the Windows "file in use" signature in command output. An upgrade's
|
|
478
|
+
* `uv pip install --refresh` can't replace gaia.exe while a previous GAIA
|
|
479
|
+
* process still holds it open — pip reports os error 32 (issue #1388).
|
|
480
|
+
*/
|
|
481
|
+
function isFileLockedError(output) {
|
|
482
|
+
if (!output) return false;
|
|
483
|
+
return /os error 32/i.test(output) || /being used by another process/i.test(output);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Detect a transient network failure in `uv pip install` output so the
|
|
488
|
+
* install stage can retry instead of failing the whole bootstrap. The
|
|
489
|
+
* install stage downloads heavy transitive deps (scipy, numpy, torch) from
|
|
490
|
+
* PyPI; a single mid-stream hiccup ("stream closed because of a broken pipe")
|
|
491
|
+
* otherwise fails the entire backend install — and, in the release pipeline,
|
|
492
|
+
* the whole AppImage smoke test that gates publishing.
|
|
493
|
+
*
|
|
494
|
+
* Matches only network-shaped failures — NOT dependency-resolution errors
|
|
495
|
+
* ("No solution found") or disk-full, which retrying cannot fix.
|
|
496
|
+
*/
|
|
497
|
+
function isTransientNetworkError(output) {
|
|
498
|
+
if (!output) return false;
|
|
499
|
+
return (
|
|
500
|
+
/broken pipe/i.test(output) ||
|
|
501
|
+
/stream closed/i.test(output) ||
|
|
502
|
+
/failed to fetch/i.test(output) ||
|
|
503
|
+
/error sending request/i.test(output) ||
|
|
504
|
+
/connection (?:error|reset|closed|refused|aborted)/i.test(output) ||
|
|
505
|
+
/could not connect/i.test(output) ||
|
|
506
|
+
/(?:request|operation|connection)?\s*tim(?:ed\s*out|eout)/i.test(output) ||
|
|
507
|
+
/temporary failure in name resolution/i.test(output) ||
|
|
508
|
+
/(?:could not resolve|failed to lookup|dns error)/i.test(output) ||
|
|
509
|
+
/network is unreachable/i.test(output)
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function sleep(ms) {
|
|
514
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
515
|
+
}
|
|
516
|
+
|
|
471
517
|
// ── Pre-checks ───────────────────────────────────────────────────────────────
|
|
472
518
|
|
|
473
519
|
/**
|
|
@@ -1146,16 +1192,50 @@ async function installBackend(opts = {}) {
|
|
|
1146
1192
|
pipArgs.push("--extra-index-url", "https://download.pytorch.org/whl/cpu");
|
|
1147
1193
|
}
|
|
1148
1194
|
|
|
1149
|
-
|
|
1195
|
+
// Retry the install on transient PyPI/network failures. The heavy
|
|
1196
|
+
// transitive deps (scipy, numpy, torch) are fetched live from PyPI even
|
|
1197
|
+
// when the gaia wheel itself is local, so a single broken-pipe mid-download
|
|
1198
|
+
// would otherwise fail the whole bootstrap (and block a release). File-lock
|
|
1199
|
+
// failures (Windows os-error-32) are NOT retried — they need user action.
|
|
1200
|
+
let installResult;
|
|
1201
|
+
for (let attempt = 1; attempt <= INSTALL_MAX_ATTEMPTS; attempt++) {
|
|
1202
|
+
installResult = await runCommand("uv", pipArgs, { stageLabel: "pip" });
|
|
1203
|
+
if (installResult.code === 0) break;
|
|
1204
|
+
const attemptOutput = `${installResult.stdout || ""}\n${installResult.stderr || ""}`;
|
|
1205
|
+
const canRetry =
|
|
1206
|
+
attempt < INSTALL_MAX_ATTEMPTS &&
|
|
1207
|
+
isTransientNetworkError(attemptOutput) &&
|
|
1208
|
+
!isFileLockedError(attemptOutput);
|
|
1209
|
+
if (!canRetry) break;
|
|
1210
|
+
const backoffMs = INSTALL_RETRY_BACKOFF_MS * attempt;
|
|
1211
|
+
log(
|
|
1212
|
+
`Transient network error during install (attempt ${attempt}/${INSTALL_MAX_ATTEMPTS}). ` +
|
|
1213
|
+
`Retrying in ${Math.round(backoffMs / 1000)}s…`
|
|
1214
|
+
);
|
|
1215
|
+
report(
|
|
1216
|
+
STAGES.INSTALL_PACKAGE,
|
|
1217
|
+
0,
|
|
1218
|
+
`Network hiccup — retrying install (attempt ${attempt + 1}/${INSTALL_MAX_ATTEMPTS})`
|
|
1219
|
+
);
|
|
1220
|
+
await sleep(backoffMs);
|
|
1221
|
+
}
|
|
1150
1222
|
if (installResult.code !== 0) {
|
|
1223
|
+
// Windows: an upgrade can't replace gaia.exe while a previous GAIA
|
|
1224
|
+
// process still holds it open (issue #1388). The orphan cleanup in
|
|
1225
|
+
// main.cjs should have killed it first; if the lock persists, point the
|
|
1226
|
+
// user at the live process rather than the generic manual-install hint.
|
|
1227
|
+
const output = `${installResult.stdout || ""}\n${installResult.stderr || ""}`;
|
|
1228
|
+
const suggestion = isFileLockedError(output)
|
|
1229
|
+
? "A running GAIA process is locking the install. Close GAIA completely (including any leftover gaia.exe in Task Manager), then relaunch."
|
|
1230
|
+
: `Try installing manually:\n uv pip install ${pipPackage} --python ${
|
|
1231
|
+
IS_WINDOWS ? `${GAIA_VENV_DISPLAY}/Scripts/python.exe` : `${GAIA_VENV_DISPLAY}/bin/python`
|
|
1232
|
+
}\nThen restart GAIA. See https://amd-gaia.ai/quickstart#cli-install`;
|
|
1151
1233
|
throw new InstallError(
|
|
1152
1234
|
`Failed to install ${pipPackage} (pip exit ${installResult.code}).`,
|
|
1153
1235
|
{
|
|
1154
1236
|
stage: STAGES.INSTALL_PACKAGE,
|
|
1155
1237
|
code: installResult.code,
|
|
1156
|
-
suggestion
|
|
1157
|
-
IS_WINDOWS ? `${GAIA_VENV_DISPLAY}/Scripts/python.exe` : `${GAIA_VENV_DISPLAY}/bin/python`
|
|
1158
|
-
}\nThen restart GAIA. See https://amd-gaia.ai/quickstart#cli-install`,
|
|
1238
|
+
suggestion,
|
|
1159
1239
|
}
|
|
1160
1240
|
);
|
|
1161
1241
|
}
|
|
@@ -1439,6 +1519,8 @@ module.exports = {
|
|
|
1439
1519
|
runPreChecks,
|
|
1440
1520
|
checkDiskSpace,
|
|
1441
1521
|
checkNetwork,
|
|
1522
|
+
isFileLockedError,
|
|
1523
|
+
isTransientNetworkError,
|
|
1442
1524
|
|
|
1443
1525
|
// State machine
|
|
1444
1526
|
getState,
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Identify orphaned GAIA backend processes left over from a previous launch.
|
|
2
|
+
//
|
|
3
|
+
// On Windows an orphaned `gaia chat --ui` process keeps an open handle on
|
|
4
|
+
// `gaia.exe`, which makes the next `uv pip install --refresh` upgrade fail with
|
|
5
|
+
// `os error 32` (file in use) — issue #1388. We must positively identify and
|
|
6
|
+
// kill that process BEFORE the installer runs, so the executable can be
|
|
7
|
+
// replaced.
|
|
8
|
+
//
|
|
9
|
+
// The check is deliberately conservative: it returns false whenever it cannot
|
|
10
|
+
// positively confirm the process is a GAIA backend, so we never signal an
|
|
11
|
+
// unrelated user process (issue #782 TOCTOU mitigation).
|
|
12
|
+
|
|
13
|
+
const realFs = require("fs");
|
|
14
|
+
const { spawnSync: realSpawnSync } = require("child_process");
|
|
15
|
+
|
|
16
|
+
// Legitimate backend invocation is `gaia chat --ui --ui-port <port>`. Match on
|
|
17
|
+
// "gaia" plus one of the backend-specific flags so unrelated Python/Node
|
|
18
|
+
// processes (Jupyter, LSP, etc.) are never matched.
|
|
19
|
+
function commandLooksLikeBackend(cmd) {
|
|
20
|
+
if (!cmd) return false;
|
|
21
|
+
return cmd.includes("gaia") && (cmd.includes("chat") || cmd.includes("--ui-port"));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Best-effort check that `pid` belongs to a GAIA backend process.
|
|
26
|
+
*
|
|
27
|
+
* @param {number} pid
|
|
28
|
+
* @param {object} [deps] injectable dependencies for testing
|
|
29
|
+
* @param {string} [deps.platform] process.platform override
|
|
30
|
+
* @param {object} [deps.fs] fs module override
|
|
31
|
+
* @param {Function} [deps.spawnSync] child_process.spawnSync override
|
|
32
|
+
* @returns {boolean}
|
|
33
|
+
*/
|
|
34
|
+
function isGaiaBackendProcess(pid, deps = {}) {
|
|
35
|
+
const fs = deps.fs || realFs;
|
|
36
|
+
const spawnSync = deps.spawnSync || realSpawnSync;
|
|
37
|
+
const platform = deps.platform || process.platform;
|
|
38
|
+
|
|
39
|
+
// Defense-in-depth: pid is interpolated into shell/CIM-filter args below.
|
|
40
|
+
// Production callers parseInt it, but reject anything non-integer here so a
|
|
41
|
+
// future caller can't inject through the PowerShell -Filter / ps -p args.
|
|
42
|
+
if (!Number.isInteger(pid)) return false;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
if (platform === "linux") {
|
|
46
|
+
const procCmd = `/proc/${pid}/cmdline`;
|
|
47
|
+
if (!fs.existsSync(procCmd)) return false;
|
|
48
|
+
// /proc/<pid>/cmdline is NUL-separated.
|
|
49
|
+
const raw = fs.readFileSync(procCmd, "utf8");
|
|
50
|
+
return commandLooksLikeBackend(raw);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (platform === "darwin") {
|
|
54
|
+
const out = spawnSync("ps", ["-p", String(pid), "-o", "command="], {
|
|
55
|
+
encoding: "utf8",
|
|
56
|
+
timeout: 5000,
|
|
57
|
+
});
|
|
58
|
+
return commandLooksLikeBackend(out && out.stdout ? out.stdout : "");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (platform === "win32") {
|
|
62
|
+
const out = spawnSync(
|
|
63
|
+
"powershell.exe",
|
|
64
|
+
[
|
|
65
|
+
"-NoProfile",
|
|
66
|
+
"-NonInteractive",
|
|
67
|
+
"-Command",
|
|
68
|
+
`(Get-CimInstance Win32_Process -Filter "ProcessId=${pid}").CommandLine`,
|
|
69
|
+
],
|
|
70
|
+
// timeout: a hung PowerShell on the recovery path must not block app startup.
|
|
71
|
+
{ encoding: "utf8", timeout: 5000 }
|
|
72
|
+
);
|
|
73
|
+
return commandLooksLikeBackend(out && out.stdout ? out.stdout : "");
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
// Any probe failure → treat as "not a backend" so we don't kill blindly.
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { isGaiaBackendProcess, commandLooksLikeBackend };
|
|
@@ -57,8 +57,7 @@ class TrayManager {
|
|
|
57
57
|
this.config = this._loadConfig();
|
|
58
58
|
|
|
59
59
|
/** @type {Electron.NativeImage} */
|
|
60
|
-
|
|
61
|
-
this._icon = this._loadIcon(trayIconFile);
|
|
60
|
+
this._icon = this._loadTrayIcon();
|
|
62
61
|
|
|
63
62
|
this._registerIpcHandlers();
|
|
64
63
|
}
|
|
@@ -153,18 +152,33 @@ class TrayManager {
|
|
|
153
152
|
|
|
154
153
|
// ── Private: Icon loading ────────────────────────────────────────────
|
|
155
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Pick the platform-appropriate tray icon. The menu bar is ~22pt tall, so
|
|
157
|
+
* each platform gets a purpose-built small asset — never the 4K app icon.
|
|
158
|
+
* @returns {Electron.NativeImage}
|
|
159
|
+
*/
|
|
160
|
+
_loadTrayIcon() {
|
|
161
|
+
if (process.platform === "win32") {
|
|
162
|
+
return this._loadIcon("tray-icon.ico");
|
|
163
|
+
}
|
|
164
|
+
if (process.platform === "darwin") {
|
|
165
|
+
// macOS menu-bar extras must be template images: the OS tints the
|
|
166
|
+
// alpha mask for light/dark mode. Electron auto-discovers the @2x
|
|
167
|
+
// retina variant by name.
|
|
168
|
+
const img = this._loadIcon("tray-iconTemplate.png");
|
|
169
|
+
img.setTemplateImage(true);
|
|
170
|
+
return img;
|
|
171
|
+
}
|
|
172
|
+
return this._loadIcon("tray-icon.png"); // Linux: full-colour PNG
|
|
173
|
+
}
|
|
174
|
+
|
|
156
175
|
_loadIcon(filename) {
|
|
157
176
|
// __dirname is services/, assets/ is one level up alongside main.cjs
|
|
158
177
|
const iconPath = path.join(__dirname, "..", "assets", filename);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return nativeImage.createFromPath(iconPath);
|
|
162
|
-
}
|
|
163
|
-
} catch (err) {
|
|
164
|
-
console.warn(`[tray] Could not load icon ${filename}:`, err.message);
|
|
178
|
+
if (!fs.existsSync(iconPath)) {
|
|
179
|
+
throw new Error(`[tray] icon asset missing: ${iconPath}`);
|
|
165
180
|
}
|
|
166
|
-
|
|
167
|
-
return nativeImage.createEmpty();
|
|
181
|
+
return nativeImage.createFromPath(iconPath);
|
|
168
182
|
}
|
|
169
183
|
|
|
170
184
|
// ── Private: Config persistence ──────────────────────────────────────
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
function St(e,t){for(var i=0;i<t.length;i++){const r=t[i];if(typeof r!="string"&&!Array.isArray(r)){for(const n in r)if(n!=="default"&&!(n in e)){const o=Object.getOwnPropertyDescriptor(r,n);o&&Object.defineProperty(e,n,o.get?o:{enumerable:!0,get:()=>r[n]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}var _={},Rt=function(){return typeof Promise=="function"&&Promise.prototype&&Promise.prototype.then},dt={},I={};let it;const Lt=[0,26,44,70,100,134,172,196,242,292,346,404,466,532,581,655,733,815,901,991,1085,1156,1258,1364,1474,1588,1706,1828,1921,2051,2185,2323,2465,2611,2761,2876,3034,3196,3362,3532,3706];I.getSymbolSize=function(t){if(!t)throw new Error('"version" cannot be null or undefined');if(t<1||t>40)throw new Error('"version" should be in range from 1 to 40');return t*4+17};I.getSymbolTotalCodewords=function(t){return Lt[t]};I.getBCHDigit=function(e){let t=0;for(;e!==0;)t++,e>>>=1;return t};I.setToSJISFunction=function(t){if(typeof t!="function")throw new Error('"toSJISFunc" is not a valid function.');it=t};I.isKanjiModeEnabled=function(){return typeof it<"u"};I.toSJIS=function(t){return it(t)};var j={};(function(e){e.L={bit:1},e.M={bit:0},e.Q={bit:3},e.H={bit:2};function t(i){if(typeof i!="string")throw new Error("Param is not a string");switch(i.toLowerCase()){case"l":case"low":return e.L;case"m":case"medium":return e.M;case"q":case"quartile":return e.Q;case"h":case"high":return e.H;default:throw new Error("Unknown EC Level: "+i)}}e.isValid=function(r){return r&&typeof r.bit<"u"&&r.bit>=0&&r.bit<4},e.from=function(r,n){if(e.isValid(r))return r;try{return t(r)}catch{return n}}})(j);function ht(){this.buffer=[],this.length=0}ht.prototype={get:function(e){const t=Math.floor(e/8);return(this.buffer[t]>>>7-e%8&1)===1},put:function(e,t){for(let i=0;i<t;i++)this.putBit((e>>>t-i-1&1)===1)},getLengthInBits:function(){return this.length},putBit:function(e){const t=Math.floor(this.length/8);this.buffer.length<=t&&this.buffer.push(0),e&&(this.buffer[t]|=128>>>this.length%8),this.length++}};var _t=ht;function H(e){if(!e||e<1)throw new Error("BitMatrix size must be defined and greater than 0");this.size=e,this.data=new Uint8Array(e*e),this.reservedBit=new Uint8Array(e*e)}H.prototype.set=function(e,t,i,r){const n=e*this.size+t;this.data[n]=i,r&&(this.reservedBit[n]=!0)};H.prototype.get=function(e,t){return this.data[e*this.size+t]};H.prototype.xor=function(e,t,i){this.data[e*this.size+t]^=i};H.prototype.isReserved=function(e,t){return this.reservedBit[e*this.size+t]};var Dt=H,wt={};(function(e){const t=I.getSymbolSize;e.getRowColCoords=function(r){if(r===1)return[];const n=Math.floor(r/7)+2,o=t(r),s=o===145?26:Math.ceil((o-13)/(2*n-2))*2,c=[o-7];for(let u=1;u<n-1;u++)c[u]=c[u-1]-s;return c.push(6),c.reverse()},e.getPositions=function(r){const n=[],o=e.getRowColCoords(r),s=o.length;for(let c=0;c<s;c++)for(let u=0;u<s;u++)c===0&&u===0||c===0&&u===s-1||c===s-1&&u===0||n.push([o[c],o[u]]);return n}})(wt);var mt={};const Ut=I.getSymbolSize,lt=7;mt.getPositions=function(t){const i=Ut(t);return[[0,0],[i-lt,0],[0,i-lt]]};var yt={};(function(e){e.Patterns={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};const t={N1:3,N2:3,N3:40,N4:10};e.isValid=function(n){return n!=null&&n!==""&&!isNaN(n)&&n>=0&&n<=7},e.from=function(n){return e.isValid(n)?parseInt(n,10):void 0},e.getPenaltyN1=function(n){const o=n.size;let s=0,c=0,u=0,a=null,l=null;for(let p=0;p<o;p++){c=u=0,a=l=null;for(let w=0;w<o;w++){let f=n.get(p,w);f===a?c++:(c>=5&&(s+=t.N1+(c-5)),a=f,c=1),f=n.get(w,p),f===l?u++:(u>=5&&(s+=t.N1+(u-5)),l=f,u=1)}c>=5&&(s+=t.N1+(c-5)),u>=5&&(s+=t.N1+(u-5))}return s},e.getPenaltyN2=function(n){const o=n.size;let s=0;for(let c=0;c<o-1;c++)for(let u=0;u<o-1;u++){const a=n.get(c,u)+n.get(c,u+1)+n.get(c+1,u)+n.get(c+1,u+1);(a===4||a===0)&&s++}return s*t.N2},e.getPenaltyN3=function(n){const o=n.size;let s=0,c=0,u=0;for(let a=0;a<o;a++){c=u=0;for(let l=0;l<o;l++)c=c<<1&2047|n.get(a,l),l>=10&&(c===1488||c===93)&&s++,u=u<<1&2047|n.get(l,a),l>=10&&(u===1488||u===93)&&s++}return s*t.N3},e.getPenaltyN4=function(n){let o=0;const s=n.data.length;for(let u=0;u<s;u++)o+=n.data[u];return Math.abs(Math.ceil(o*100/s/5)-10)*t.N4};function i(r,n,o){switch(r){case e.Patterns.PATTERN000:return(n+o)%2===0;case e.Patterns.PATTERN001:return n%2===0;case e.Patterns.PATTERN010:return o%3===0;case e.Patterns.PATTERN011:return(n+o)%3===0;case e.Patterns.PATTERN100:return(Math.floor(n/2)+Math.floor(o/3))%2===0;case e.Patterns.PATTERN101:return n*o%2+n*o%3===0;case e.Patterns.PATTERN110:return(n*o%2+n*o%3)%2===0;case e.Patterns.PATTERN111:return(n*o%3+(n+o)%2)%2===0;default:throw new Error("bad maskPattern:"+r)}}e.applyMask=function(n,o){const s=o.size;for(let c=0;c<s;c++)for(let u=0;u<s;u++)o.isReserved(u,c)||o.xor(u,c,i(n,u,c))},e.getBestMask=function(n,o){const s=Object.keys(e.Patterns).length;let c=0,u=1/0;for(let a=0;a<s;a++){o(a),e.applyMask(a,n);const l=e.getPenaltyN1(n)+e.getPenaltyN2(n)+e.getPenaltyN3(n)+e.getPenaltyN4(n);e.applyMask(a,n),l<u&&(u=l,c=a)}return c}})(yt);var G={};const R=j,K=[1,1,1,1,1,1,1,1,1,1,2,2,1,2,2,4,1,2,4,4,2,4,4,4,2,4,6,5,2,4,6,6,2,5,8,8,4,5,8,8,4,5,8,11,4,8,10,11,4,9,12,16,4,9,16,16,6,10,12,18,6,10,17,16,6,11,16,19,6,13,18,21,7,14,21,25,8,16,20,25,8,17,23,25,9,17,23,34,9,18,25,30,10,20,27,32,12,21,29,35,12,23,34,37,12,25,34,40,13,26,35,42,14,28,38,45,15,29,40,48,16,31,43,51,17,33,45,54,18,35,48,57,19,37,51,60,19,38,53,63,20,40,56,66,21,43,59,70,22,45,62,74,24,47,65,77,25,49,68,81],J=[7,10,13,17,10,16,22,28,15,26,36,44,20,36,52,64,26,48,72,88,36,64,96,112,40,72,108,130,48,88,132,156,60,110,160,192,72,130,192,224,80,150,224,264,96,176,260,308,104,198,288,352,120,216,320,384,132,240,360,432,144,280,408,480,168,308,448,532,180,338,504,588,196,364,546,650,224,416,600,700,224,442,644,750,252,476,690,816,270,504,750,900,300,560,810,960,312,588,870,1050,336,644,952,1110,360,700,1020,1200,390,728,1050,1260,420,784,1140,1350,450,812,1200,1440,480,868,1290,1530,510,924,1350,1620,540,980,1440,1710,570,1036,1530,1800,570,1064,1590,1890,600,1120,1680,1980,630,1204,1770,2100,660,1260,1860,2220,720,1316,1950,2310,750,1372,2040,2430];G.getBlocksCount=function(t,i){switch(i){case R.L:return K[(t-1)*4+0];case R.M:return K[(t-1)*4+1];case R.Q:return K[(t-1)*4+2];case R.H:return K[(t-1)*4+3];default:return}};G.getTotalCodewordsCount=function(t,i){switch(i){case R.L:return J[(t-1)*4+0];case R.M:return J[(t-1)*4+1];case R.Q:return J[(t-1)*4+2];case R.H:return J[(t-1)*4+3];default:return}};var Et={},Q={};const z=new Uint8Array(512),O=new Uint8Array(256);(function(){let t=1;for(let i=0;i<255;i++)z[i]=t,O[t]=i,t<<=1,t&256&&(t^=285);for(let i=255;i<512;i++)z[i]=z[i-255]})();Q.log=function(t){if(t<1)throw new Error("log("+t+")");return O[t]};Q.exp=function(t){return z[t]};Q.mul=function(t,i){return t===0||i===0?0:z[O[t]+O[i]]};(function(e){const t=Q;e.mul=function(r,n){const o=new Uint8Array(r.length+n.length-1);for(let s=0;s<r.length;s++)for(let c=0;c<n.length;c++)o[s+c]^=t.mul(r[s],n[c]);return o},e.mod=function(r,n){let o=new Uint8Array(r);for(;o.length-n.length>=0;){const s=o[0];for(let u=0;u<n.length;u++)o[u]^=t.mul(n[u],s);let c=0;for(;c<o.length&&o[c]===0;)c++;o=o.slice(c)}return o},e.generateECPolynomial=function(r){let n=new Uint8Array([1]);for(let o=0;o<r;o++)n=e.mul(n,new Uint8Array([1,t.exp(o)]));return n}})(Et);const Ct=Et;function st(e){this.genPoly=void 0,this.degree=e,this.degree&&this.initialize(this.degree)}st.prototype.initialize=function(t){this.degree=t,this.genPoly=Ct.generateECPolynomial(this.degree)};st.prototype.encode=function(t){if(!this.genPoly)throw new Error("Encoder not initialized");const i=new Uint8Array(t.length+this.degree);i.set(t);const r=Ct.mod(i,this.genPoly),n=this.degree-r.length;if(n>0){const o=new Uint8Array(this.degree);return o.set(r,n),o}return r};var Ft=st,pt={},L={},ut={};ut.isValid=function(t){return!isNaN(t)&&t>=1&&t<=40};var M={};const Bt="[0-9]+",kt="[A-Z $%*+\\-./:]+";let V="(?:[u3000-u303F]|[u3040-u309F]|[u30A0-u30FF]|[uFF00-uFFEF]|[u4E00-u9FAF]|[u2605-u2606]|[u2190-u2195]|u203B|[u2010u2015u2018u2019u2025u2026u201Cu201Du2225u2260]|[u0391-u0451]|[u00A7u00A8u00B1u00B4u00D7u00F7])+";V=V.replace(/u/g,"\\u");const vt="(?:(?![A-Z0-9 $%*+\\-./:]|"+V+`)(?:.|[\r
|
|
2
|
-
]))+`;M.KANJI=new RegExp(V,"g");M.BYTE_KANJI=new RegExp("[^A-Z0-9 $%*+\\-./:]+","g");M.BYTE=new RegExp(vt,"g");M.NUMERIC=new RegExp(Bt,"g");M.ALPHANUMERIC=new RegExp(kt,"g");const zt=new RegExp("^"+V+"$"),Vt=new RegExp("^"+Bt+"$"),Ht=new RegExp("^[A-Z0-9 $%*+\\-./:]+$");M.testKanji=function(t){return zt.test(t)};M.testNumeric=function(t){return Vt.test(t)};M.testAlphanumeric=function(t){return Ht.test(t)};(function(e){const t=ut,i=M;e.NUMERIC={id:"Numeric",bit:1,ccBits:[10,12,14]},e.ALPHANUMERIC={id:"Alphanumeric",bit:2,ccBits:[9,11,13]},e.BYTE={id:"Byte",bit:4,ccBits:[8,16,16]},e.KANJI={id:"Kanji",bit:8,ccBits:[8,10,12]},e.MIXED={bit:-1},e.getCharCountIndicator=function(o,s){if(!o.ccBits)throw new Error("Invalid mode: "+o);if(!t.isValid(s))throw new Error("Invalid version: "+s);return s>=1&&s<10?o.ccBits[0]:s<27?o.ccBits[1]:o.ccBits[2]},e.getBestModeForData=function(o){return i.testNumeric(o)?e.NUMERIC:i.testAlphanumeric(o)?e.ALPHANUMERIC:i.testKanji(o)?e.KANJI:e.BYTE},e.toString=function(o){if(o&&o.id)return o.id;throw new Error("Invalid mode")},e.isValid=function(o){return o&&o.bit&&o.ccBits};function r(n){if(typeof n!="string")throw new Error("Param is not a string");switch(n.toLowerCase()){case"numeric":return e.NUMERIC;case"alphanumeric":return e.ALPHANUMERIC;case"kanji":return e.KANJI;case"byte":return e.BYTE;default:throw new Error("Unknown mode: "+n)}}e.from=function(o,s){if(e.isValid(o))return o;try{return r(o)}catch{return s}}})(L);(function(e){const t=I,i=G,r=j,n=L,o=ut,s=7973,c=t.getBCHDigit(s);function u(w,f,m){for(let y=1;y<=40;y++)if(f<=e.getCapacity(y,m,w))return y}function a(w,f){return n.getCharCountIndicator(w,f)+4}function l(w,f){let m=0;return w.forEach(function(y){const T=a(y.mode,f);m+=T+y.getBitsLength()}),m}function p(w,f){for(let m=1;m<=40;m++)if(l(w,m)<=e.getCapacity(m,f,n.MIXED))return m}e.from=function(f,m){return o.isValid(f)?parseInt(f,10):m},e.getCapacity=function(f,m,y){if(!o.isValid(f))throw new Error("Invalid QR Code version");typeof y>"u"&&(y=n.BYTE);const T=t.getSymbolTotalCodewords(f),h=i.getTotalCodewordsCount(f,m),E=(T-h)*8;if(y===n.MIXED)return E;const d=E-a(y,f);switch(y){case n.NUMERIC:return Math.floor(d/10*3);case n.ALPHANUMERIC:return Math.floor(d/11*2);case n.KANJI:return Math.floor(d/13);case n.BYTE:default:return Math.floor(d/8)}},e.getBestVersionForData=function(f,m){let y;const T=r.from(m,r.M);if(Array.isArray(f)){if(f.length>1)return p(f,T);if(f.length===0)return 1;y=f[0]}else y=f;return u(y.mode,y.getLength(),T)},e.getEncodedBits=function(f){if(!o.isValid(f)||f<7)throw new Error("Invalid QR Code version");let m=f<<12;for(;t.getBCHDigit(m)-c>=0;)m^=s<<t.getBCHDigit(m)-c;return f<<12|m}})(pt);var At={};const et=I,Tt=1335,Kt=21522,ft=et.getBCHDigit(Tt);At.getEncodedBits=function(t,i){const r=t.bit<<3|i;let n=r<<10;for(;et.getBCHDigit(n)-ft>=0;)n^=Tt<<et.getBCHDigit(n)-ft;return(r<<10|n)^Kt};var It={};const Jt=L;function D(e){this.mode=Jt.NUMERIC,this.data=e.toString()}D.getBitsLength=function(t){return 10*Math.floor(t/3)+(t%3?t%3*3+1:0)};D.prototype.getLength=function(){return this.data.length};D.prototype.getBitsLength=function(){return D.getBitsLength(this.data.length)};D.prototype.write=function(t){let i,r,n;for(i=0;i+3<=this.data.length;i+=3)r=this.data.substr(i,3),n=parseInt(r,10),t.put(n,10);const o=this.data.length-i;o>0&&(r=this.data.substr(i),n=parseInt(r,10),t.put(n,o*3+1))};var Ot=D;const Yt=L,Z=["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"," ","$","%","*","+","-",".","/",":"];function U(e){this.mode=Yt.ALPHANUMERIC,this.data=e}U.getBitsLength=function(t){return 11*Math.floor(t/2)+6*(t%2)};U.prototype.getLength=function(){return this.data.length};U.prototype.getBitsLength=function(){return U.getBitsLength(this.data.length)};U.prototype.write=function(t){let i;for(i=0;i+2<=this.data.length;i+=2){let r=Z.indexOf(this.data[i])*45;r+=Z.indexOf(this.data[i+1]),t.put(r,11)}this.data.length%2&&t.put(Z.indexOf(this.data[i]),6)};var jt=U;const Gt=L;function F(e){this.mode=Gt.BYTE,typeof e=="string"?this.data=new TextEncoder().encode(e):this.data=new Uint8Array(e)}F.getBitsLength=function(t){return t*8};F.prototype.getLength=function(){return this.data.length};F.prototype.getBitsLength=function(){return F.getBitsLength(this.data.length)};F.prototype.write=function(e){for(let t=0,i=this.data.length;t<i;t++)e.put(this.data[t],8)};var Qt=F;const qt=L,Wt=I;function k(e){this.mode=qt.KANJI,this.data=e}k.getBitsLength=function(t){return t*13};k.prototype.getLength=function(){return this.data.length};k.prototype.getBitsLength=function(){return k.getBitsLength(this.data.length)};k.prototype.write=function(e){let t;for(t=0;t<this.data.length;t++){let i=Wt.toSJIS(this.data[t]);if(i>=33088&&i<=40956)i-=33088;else if(i>=57408&&i<=60351)i-=49472;else throw new Error("Invalid SJIS character: "+this.data[t]+`
|
|
3
|
-
Make sure your charset is UTF-8`);i=(i>>>8&255)*192+(i&255),e.put(i,13)}};var Zt=k,Nt={exports:{}};(function(e){var t={single_source_shortest_paths:function(i,r,n){var o={},s={};s[r]=0;var c=t.PriorityQueue.make();c.push(r,0);for(var u,a,l,p,w,f,m,y,T;!c.empty();){u=c.pop(),a=u.value,p=u.cost,w=i[a]||{};for(l in w)w.hasOwnProperty(l)&&(f=w[l],m=p+f,y=s[l],T=typeof s[l]>"u",(T||y>m)&&(s[l]=m,c.push(l,m),o[l]=a))}if(typeof n<"u"&&typeof s[n]>"u"){var h=["Could not find a path from ",r," to ",n,"."].join("");throw new Error(h)}return o},extract_shortest_path_from_predecessor_list:function(i,r){for(var n=[],o=r;o;)n.push(o),i[o],o=i[o];return n.reverse(),n},find_path:function(i,r,n){var o=t.single_source_shortest_paths(i,r,n);return t.extract_shortest_path_from_predecessor_list(o,n)},PriorityQueue:{make:function(i){var r=t.PriorityQueue,n={},o;i=i||{};for(o in r)r.hasOwnProperty(o)&&(n[o]=r[o]);return n.queue=[],n.sorter=i.sorter||r.default_sorter,n},default_sorter:function(i,r){return i.cost-r.cost},push:function(i,r){var n={value:i,cost:r};this.queue.push(n),this.queue.sort(this.sorter)},pop:function(){return this.queue.shift()},empty:function(){return this.queue.length===0}}};e.exports=t})(Nt);var Xt=Nt.exports;(function(e){const t=L,i=Ot,r=jt,n=Qt,o=Zt,s=M,c=I,u=Xt;function a(h){return unescape(encodeURIComponent(h)).length}function l(h,E,d){const g=[];let C;for(;(C=h.exec(d))!==null;)g.push({data:C[0],index:C.index,mode:E,length:C[0].length});return g}function p(h){const E=l(s.NUMERIC,t.NUMERIC,h),d=l(s.ALPHANUMERIC,t.ALPHANUMERIC,h);let g,C;return c.isKanjiModeEnabled()?(g=l(s.BYTE,t.BYTE,h),C=l(s.KANJI,t.KANJI,h)):(g=l(s.BYTE_KANJI,t.BYTE,h),C=[]),E.concat(d,g,C).sort(function(A,N){return A.index-N.index}).map(function(A){return{data:A.data,mode:A.mode,length:A.length}})}function w(h,E){switch(E){case t.NUMERIC:return i.getBitsLength(h);case t.ALPHANUMERIC:return r.getBitsLength(h);case t.KANJI:return o.getBitsLength(h);case t.BYTE:return n.getBitsLength(h)}}function f(h){return h.reduce(function(E,d){const g=E.length-1>=0?E[E.length-1]:null;return g&&g.mode===d.mode?(E[E.length-1].data+=d.data,E):(E.push(d),E)},[])}function m(h){const E=[];for(let d=0;d<h.length;d++){const g=h[d];switch(g.mode){case t.NUMERIC:E.push([g,{data:g.data,mode:t.ALPHANUMERIC,length:g.length},{data:g.data,mode:t.BYTE,length:g.length}]);break;case t.ALPHANUMERIC:E.push([g,{data:g.data,mode:t.BYTE,length:g.length}]);break;case t.KANJI:E.push([g,{data:g.data,mode:t.BYTE,length:a(g.data)}]);break;case t.BYTE:E.push([{data:g.data,mode:t.BYTE,length:a(g.data)}])}}return E}function y(h,E){const d={},g={start:{}};let C=["start"];for(let B=0;B<h.length;B++){const A=h[B],N=[];for(let S=0;S<A.length;S++){const b=A[S],v=""+B+S;N.push(v),d[v]={node:b,lastCount:0},g[v]={};for(let W=0;W<C.length;W++){const P=C[W];d[P]&&d[P].node.mode===b.mode?(g[P][v]=w(d[P].lastCount+b.length,b.mode)-w(d[P].lastCount,b.mode),d[P].lastCount+=b.length):(d[P]&&(d[P].lastCount=b.length),g[P][v]=w(b.length,b.mode)+4+t.getCharCountIndicator(b.mode,E))}}C=N}for(let B=0;B<C.length;B++)g[C[B]].end=0;return{map:g,table:d}}function T(h,E){let d;const g=t.getBestModeForData(h);if(d=t.from(E,g),d!==t.BYTE&&d.bit<g.bit)throw new Error('"'+h+'" cannot be encoded with mode '+t.toString(d)+`.
|
|
4
|
-
Suggested mode is: `+t.toString(g));switch(d===t.KANJI&&!c.isKanjiModeEnabled()&&(d=t.BYTE),d){case t.NUMERIC:return new i(h);case t.ALPHANUMERIC:return new r(h);case t.KANJI:return new o(h);case t.BYTE:return new n(h)}}e.fromArray=function(E){return E.reduce(function(d,g){return typeof g=="string"?d.push(T(g,null)):g.data&&d.push(T(g.data,g.mode)),d},[])},e.fromString=function(E,d){const g=p(E,c.isKanjiModeEnabled()),C=m(g),B=y(C,d),A=u.find_path(B.map,"start","end"),N=[];for(let S=1;S<A.length-1;S++)N.push(B.table[A[S]].node);return e.fromArray(f(N))},e.rawSplit=function(E){return e.fromArray(p(E,c.isKanjiModeEnabled()))}})(It);const q=I,X=j,xt=_t,$t=Dt,te=wt,ee=mt,nt=yt,rt=G,ne=Ft,Y=pt,re=At,oe=L,x=It;function ie(e,t){const i=e.size,r=ee.getPositions(t);for(let n=0;n<r.length;n++){const o=r[n][0],s=r[n][1];for(let c=-1;c<=7;c++)if(!(o+c<=-1||i<=o+c))for(let u=-1;u<=7;u++)s+u<=-1||i<=s+u||(c>=0&&c<=6&&(u===0||u===6)||u>=0&&u<=6&&(c===0||c===6)||c>=2&&c<=4&&u>=2&&u<=4?e.set(o+c,s+u,!0,!0):e.set(o+c,s+u,!1,!0))}}function se(e){const t=e.size;for(let i=8;i<t-8;i++){const r=i%2===0;e.set(i,6,r,!0),e.set(6,i,r,!0)}}function ue(e,t){const i=te.getPositions(t);for(let r=0;r<i.length;r++){const n=i[r][0],o=i[r][1];for(let s=-2;s<=2;s++)for(let c=-2;c<=2;c++)s===-2||s===2||c===-2||c===2||s===0&&c===0?e.set(n+s,o+c,!0,!0):e.set(n+s,o+c,!1,!0)}}function ce(e,t){const i=e.size,r=Y.getEncodedBits(t);let n,o,s;for(let c=0;c<18;c++)n=Math.floor(c/3),o=c%3+i-8-3,s=(r>>c&1)===1,e.set(n,o,s,!0),e.set(o,n,s,!0)}function $(e,t,i){const r=e.size,n=re.getEncodedBits(t,i);let o,s;for(o=0;o<15;o++)s=(n>>o&1)===1,o<6?e.set(o,8,s,!0):o<8?e.set(o+1,8,s,!0):e.set(r-15+o,8,s,!0),o<8?e.set(8,r-o-1,s,!0):o<9?e.set(8,15-o-1+1,s,!0):e.set(8,15-o-1,s,!0);e.set(r-8,8,1,!0)}function ae(e,t){const i=e.size;let r=-1,n=i-1,o=7,s=0;for(let c=i-1;c>0;c-=2)for(c===6&&c--;;){for(let u=0;u<2;u++)if(!e.isReserved(n,c-u)){let a=!1;s<t.length&&(a=(t[s]>>>o&1)===1),e.set(n,c-u,a),o--,o===-1&&(s++,o=7)}if(n+=r,n<0||i<=n){n-=r,r=-r;break}}}function le(e,t,i){const r=new xt;i.forEach(function(u){r.put(u.mode.bit,4),r.put(u.getLength(),oe.getCharCountIndicator(u.mode,e)),u.write(r)});const n=q.getSymbolTotalCodewords(e),o=rt.getTotalCodewordsCount(e,t),s=(n-o)*8;for(r.getLengthInBits()+4<=s&&r.put(0,4);r.getLengthInBits()%8!==0;)r.putBit(0);const c=(s-r.getLengthInBits())/8;for(let u=0;u<c;u++)r.put(u%2?17:236,8);return fe(r,e,t)}function fe(e,t,i){const r=q.getSymbolTotalCodewords(t),n=rt.getTotalCodewordsCount(t,i),o=r-n,s=rt.getBlocksCount(t,i),c=r%s,u=s-c,a=Math.floor(r/s),l=Math.floor(o/s),p=l+1,w=a-l,f=new ne(w);let m=0;const y=new Array(s),T=new Array(s);let h=0;const E=new Uint8Array(e.buffer);for(let A=0;A<s;A++){const N=A<u?l:p;y[A]=E.slice(m,m+N),T[A]=f.encode(y[A]),m+=N,h=Math.max(h,N)}const d=new Uint8Array(r);let g=0,C,B;for(C=0;C<h;C++)for(B=0;B<s;B++)C<y[B].length&&(d[g++]=y[B][C]);for(C=0;C<w;C++)for(B=0;B<s;B++)d[g++]=T[B][C];return d}function ge(e,t,i,r){let n;if(Array.isArray(e))n=x.fromArray(e);else if(typeof e=="string"){let a=t;if(!a){const l=x.rawSplit(e);a=Y.getBestVersionForData(l,i)}n=x.fromString(e,a||40)}else throw new Error("Invalid data");const o=Y.getBestVersionForData(n,i);if(!o)throw new Error("The amount of data is too big to be stored in a QR Code");if(!t)t=o;else if(t<o)throw new Error(`
|
|
5
|
-
The chosen QR Code version cannot contain this amount of data.
|
|
6
|
-
Minimum version required to store current data is: `+o+`.
|
|
7
|
-
`);const s=le(t,i,n),c=q.getSymbolSize(t),u=new $t(c);return ie(u,t),se(u),ue(u,t),$(u,i,0),t>=7&&ce(u,t),ae(u,s),isNaN(r)&&(r=nt.getBestMask(u,$.bind(null,u,i))),nt.applyMask(r,u),$(u,i,r),{modules:u,version:t,errorCorrectionLevel:i,maskPattern:r,segments:n}}dt.create=function(t,i){if(typeof t>"u"||t==="")throw new Error("No input text");let r=X.M,n,o;return typeof i<"u"&&(r=X.from(i.errorCorrectionLevel,X.M),n=Y.from(i.version),o=nt.from(i.maskPattern),i.toSJISFunc&&q.setToSJISFunction(i.toSJISFunc)),ge(t,n,r,o)};var bt={},ct={};(function(e){function t(i){if(typeof i=="number"&&(i=i.toString()),typeof i!="string")throw new Error("Color should be defined as hex string");let r=i.slice().replace("#","").split("");if(r.length<3||r.length===5||r.length>8)throw new Error("Invalid hex color: "+i);(r.length===3||r.length===4)&&(r=Array.prototype.concat.apply([],r.map(function(o){return[o,o]}))),r.length===6&&r.push("F","F");const n=parseInt(r.join(""),16);return{r:n>>24&255,g:n>>16&255,b:n>>8&255,a:n&255,hex:"#"+r.slice(0,6).join("")}}e.getOptions=function(r){r||(r={}),r.color||(r.color={});const n=typeof r.margin>"u"||r.margin===null||r.margin<0?4:r.margin,o=r.width&&r.width>=21?r.width:void 0,s=r.scale||4;return{width:o,scale:o?4:s,margin:n,color:{dark:t(r.color.dark||"#000000ff"),light:t(r.color.light||"#ffffffff")},type:r.type,rendererOpts:r.rendererOpts||{}}},e.getScale=function(r,n){return n.width&&n.width>=r+n.margin*2?n.width/(r+n.margin*2):n.scale},e.getImageWidth=function(r,n){const o=e.getScale(r,n);return Math.floor((r+n.margin*2)*o)},e.qrToImageData=function(r,n,o){const s=n.modules.size,c=n.modules.data,u=e.getScale(s,o),a=Math.floor((s+o.margin*2)*u),l=o.margin*u,p=[o.color.light,o.color.dark];for(let w=0;w<a;w++)for(let f=0;f<a;f++){let m=(w*a+f)*4,y=o.color.light;if(w>=l&&f>=l&&w<a-l&&f<a-l){const T=Math.floor((w-l)/u),h=Math.floor((f-l)/u);y=p[c[T*s+h]?1:0]}r[m++]=y.r,r[m++]=y.g,r[m++]=y.b,r[m]=y.a}}})(ct);(function(e){const t=ct;function i(n,o,s){n.clearRect(0,0,o.width,o.height),o.style||(o.style={}),o.height=s,o.width=s,o.style.height=s+"px",o.style.width=s+"px"}function r(){try{return document.createElement("canvas")}catch{throw new Error("You need to specify a canvas element")}}e.render=function(o,s,c){let u=c,a=s;typeof u>"u"&&(!s||!s.getContext)&&(u=s,s=void 0),s||(a=r()),u=t.getOptions(u);const l=t.getImageWidth(o.modules.size,u),p=a.getContext("2d"),w=p.createImageData(l,l);return t.qrToImageData(w.data,o,u),i(p,a,l),p.putImageData(w,0,0),a},e.renderToDataURL=function(o,s,c){let u=c;typeof u>"u"&&(!s||!s.getContext)&&(u=s,s=void 0),u||(u={});const a=e.render(o,s,u),l=u.type||"image/png",p=u.rendererOpts||{};return a.toDataURL(l,p.quality)}})(bt);var Mt={};const de=ct;function gt(e,t){const i=e.a/255,r=t+'="'+e.hex+'"';return i<1?r+" "+t+'-opacity="'+i.toFixed(2).slice(1)+'"':r}function tt(e,t,i){let r=e+t;return typeof i<"u"&&(r+=" "+i),r}function he(e,t,i){let r="",n=0,o=!1,s=0;for(let c=0;c<e.length;c++){const u=Math.floor(c%t),a=Math.floor(c/t);!u&&!o&&(o=!0),e[c]?(s++,c>0&&u>0&&e[c-1]||(r+=o?tt("M",u+i,.5+a+i):tt("m",n,0),n=0,o=!1),u+1<t&&e[c+1]||(r+=tt("h",s),s=0)):n++}return r}Mt.render=function(t,i,r){const n=de.getOptions(i),o=t.modules.size,s=t.modules.data,c=o+n.margin*2,u=n.color.light.a?"<path "+gt(n.color.light,"fill")+' d="M0 0h'+c+"v"+c+'H0z"/>':"",a="<path "+gt(n.color.dark,"stroke")+' d="'+he(s,o,n.margin)+'"/>',l='viewBox="0 0 '+c+" "+c+'"',w='<svg xmlns="http://www.w3.org/2000/svg" '+(n.width?'width="'+n.width+'" height="'+n.width+'" ':"")+l+' shape-rendering="crispEdges">'+u+a+`</svg>
|
|
8
|
-
`;return typeof r=="function"&&r(null,w),w};const we=Rt,ot=dt,Pt=bt,me=Mt;function at(e,t,i,r,n){const o=[].slice.call(arguments,1),s=o.length,c=typeof o[s-1]=="function";if(!c&&!we())throw new Error("Callback required as last argument");if(c){if(s<2)throw new Error("Too few arguments provided");s===2?(n=i,i=t,t=r=void 0):s===3&&(t.getContext&&typeof n>"u"?(n=r,r=void 0):(n=r,r=i,i=t,t=void 0))}else{if(s<1)throw new Error("Too few arguments provided");return s===1?(i=t,t=r=void 0):s===2&&!t.getContext&&(r=i,i=t,t=void 0),new Promise(function(u,a){try{const l=ot.create(i,r);u(e(l,t,r))}catch(l){a(l)}})}try{const u=ot.create(i,r);n(null,e(u,t,r))}catch(u){n(u)}}var ye=_.create=ot.create,Ee=_.toCanvas=at.bind(null,Pt.render),Ce=_.toDataURL=at.bind(null,Pt.renderToDataURL),pe=_.toString=at.bind(null,function(e,t,i){return me.render(e,i)});const Be=St({__proto__:null,create:ye,default:_,toCanvas:Ee,toDataURL:Ce,toString:pe},[_]);export{Be as b};
|