@tapflowio/relay 0.27.1 → 0.28.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/dist/RelayServer.d.ts +54 -0
- package/dist/RelayServer.d.ts.map +1 -1
- package/dist/RelayServer.js +384 -37
- package/dist/RelayServer.js.map +1 -1
- package/dist/SessionManager.d.ts +63 -2
- package/dist/SessionManager.d.ts.map +1 -1
- package/dist/SessionManager.js +123 -1
- package/dist/SessionManager.js.map +1 -1
- package/dist/api/apps.d.ts +2 -1
- package/dist/api/apps.d.ts.map +1 -1
- package/dist/api/apps.js +20 -7
- package/dist/api/apps.js.map +1 -1
- package/dist/api/builds.d.ts +2 -1
- package/dist/api/builds.d.ts.map +1 -1
- package/dist/api/builds.js +13 -39
- package/dist/api/builds.js.map +1 -1
- package/dist/api/profile.d.ts.map +1 -1
- package/dist/api/profile.js +3 -1
- package/dist/api/profile.js.map +1 -1
- package/dist/api/runVideos.d.ts +85 -0
- package/dist/api/runVideos.d.ts.map +1 -0
- package/dist/api/runVideos.js +494 -0
- package/dist/api/runVideos.js.map +1 -0
- package/dist/api/runs.d.ts +77 -0
- package/dist/api/runs.d.ts.map +1 -0
- package/dist/api/runs.js +579 -0
- package/dist/api/runs.js.map +1 -0
- package/dist/api/settings.d.ts +1 -1
- package/dist/api/settings.d.ts.map +1 -1
- package/dist/api/settings.js +43 -3
- package/dist/api/settings.js.map +1 -1
- package/dist/db.d.ts +2 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +4 -0
- package/dist/db.js.map +1 -1
- package/dist/lib/buildDeletion.d.ts +21 -0
- package/dist/lib/buildDeletion.d.ts.map +1 -0
- package/dist/lib/buildDeletion.js +58 -0
- package/dist/lib/buildDeletion.js.map +1 -0
- package/dist/lib/watchActions.d.ts +10 -0
- package/dist/lib/watchActions.d.ts.map +1 -0
- package/dist/lib/watchActions.js +27 -0
- package/dist/lib/watchActions.js.map +1 -0
- package/dist/middleware/auth.d.ts +1 -0
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +1 -1
- package/dist/middleware/auth.js.map +1 -1
- package/dist/migrations/015_flow_runs.sql +59 -0
- package/dist/migrations/016_flow_run_videos.sql +51 -0
- package/dist/router.d.ts +5 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +24 -0
- package/dist/router.js.map +1 -1
- package/package.json +9 -9
- package/public/assets/AISessionWatch-C2h4NZ_I.js +31 -0
- package/public/assets/AISessionWatch-C2h4NZ_I.js.br +0 -0
- package/public/assets/AISessionWatch-C2h4NZ_I.js.gz +0 -0
- package/public/assets/AndroidDeviceScreen-C55msKfh.js +35 -0
- package/public/assets/AndroidDeviceScreen-C55msKfh.js.br +0 -0
- package/public/assets/AndroidDeviceScreen-C55msKfh.js.gz +0 -0
- package/public/assets/AppCenter-S1y-8_7l.js +26 -0
- package/public/assets/AppCenter-S1y-8_7l.js.br +0 -0
- package/public/assets/AppCenter-S1y-8_7l.js.gz +0 -0
- package/public/assets/Default-C-l1O-g9.js +6 -0
- package/public/assets/Default-C-l1O-g9.js.br +0 -0
- package/public/assets/Default-C-l1O-g9.js.gz +0 -0
- package/public/assets/Invite-BCndVxaO.js +1 -0
- package/public/assets/Invite-BCndVxaO.js.br +0 -0
- package/public/assets/Invite-BCndVxaO.js.gz +0 -0
- package/public/assets/MacResources-41cFDgE1.js +1 -0
- package/public/assets/MacResources-41cFDgE1.js.br +0 -0
- package/public/assets/MacResources-41cFDgE1.js.gz +0 -0
- package/public/assets/{NotFound-wHOnT2Fl.js → NotFound-DVq_rCKc.js} +1 -1
- package/public/assets/NotFound-DVq_rCKc.js.br +0 -0
- package/public/assets/NotFound-DVq_rCKc.js.gz +0 -0
- package/public/assets/QASession-B67RsIPm.js +81 -0
- package/public/assets/QASession-B67RsIPm.js.br +0 -0
- package/public/assets/QASession-B67RsIPm.js.gz +0 -0
- package/public/assets/ResetPassword-8rj7ASt1.js +1 -0
- package/public/assets/ResetPassword-8rj7ASt1.js.br +0 -0
- package/public/assets/ResetPassword-8rj7ASt1.js.gz +0 -0
- package/public/assets/RunDetail-BIgq9gJ0.js +16 -0
- package/public/assets/RunDetail-BIgq9gJ0.js.br +0 -0
- package/public/assets/RunDetail-BIgq9gJ0.js.gz +0 -0
- package/public/assets/Runs-DpK5aoBr.js +6 -0
- package/public/assets/Runs-DpK5aoBr.js.br +0 -0
- package/public/assets/Runs-DpK5aoBr.js.gz +0 -0
- package/public/assets/Setup-DZrhA_7b.js +1 -0
- package/public/assets/Setup-DZrhA_7b.js.br +0 -0
- package/public/assets/Setup-DZrhA_7b.js.gz +0 -0
- package/public/assets/Team-CJThCjd3.js +6 -0
- package/public/assets/Team-CJThCjd3.js.br +0 -0
- package/public/assets/Team-CJThCjd3.js.gz +0 -0
- package/public/assets/Tokens-DGURtHWi.js +1 -0
- package/public/assets/Tokens-DGURtHWi.js.br +0 -0
- package/public/assets/Tokens-DGURtHWi.js.gz +0 -0
- package/public/assets/WatchedDevice-CpaKHa1x.js +1 -0
- package/public/assets/WatchedDevice-CpaKHa1x.js.br +0 -0
- package/public/assets/WatchedDevice-CpaKHa1x.js.gz +0 -0
- package/public/assets/aiSession-BW09Jhqz.js +26 -0
- package/public/assets/aiSession-BW09Jhqz.js.br +0 -0
- package/public/assets/aiSession-BW09Jhqz.js.gz +0 -0
- package/public/assets/alert-dialog-t5vlOabq.js +1 -0
- package/public/assets/alert-dialog-t5vlOabq.js.br +0 -0
- package/public/assets/alert-dialog-t5vlOabq.js.gz +0 -0
- package/public/assets/{badge-DNaS0Mu3.js → badge-DrDCTAQQ.js} +1 -1
- package/public/assets/badge-DrDCTAQQ.js.br +0 -0
- package/public/assets/badge-DrDCTAQQ.js.gz +0 -0
- package/public/assets/build-format-ofd2iIzf.js +1 -0
- package/public/assets/build-format-ofd2iIzf.js.br +0 -0
- package/public/assets/build-format-ofd2iIzf.js.gz +0 -0
- package/public/assets/dialog-BOFZkCES.js +1 -0
- package/public/assets/dialog-BOFZkCES.js.br +0 -0
- package/public/assets/dialog-BOFZkCES.js.gz +0 -0
- package/public/assets/field-hint-B9fPUgi7.js +1 -0
- package/public/assets/field-hint-B9fPUgi7.js.br +0 -0
- package/public/assets/field-hint-B9fPUgi7.js.gz +0 -0
- package/public/assets/index-Bixhy5MB.js +248 -0
- package/public/assets/index-Bixhy5MB.js.br +0 -0
- package/public/assets/index-Bixhy5MB.js.gz +0 -0
- package/public/assets/index-D17hSi50.js +1 -0
- package/public/assets/index-D17hSi50.js.br +0 -0
- package/public/assets/index-D17hSi50.js.gz +0 -0
- package/public/assets/index-h2hiZ2OM.css +1 -0
- package/public/assets/index-h2hiZ2OM.css.br +0 -0
- package/public/assets/index-h2hiZ2OM.css.gz +0 -0
- package/public/assets/list-view-BpOY1e-d.js +1 -0
- package/public/assets/list-view-BpOY1e-d.js.br +0 -0
- package/public/assets/list-view-BpOY1e-d.js.gz +0 -0
- package/public/assets/{pencil-BN18R4oa.js → pencil-ZlrAsWuU.js} +2 -2
- package/public/assets/pencil-ZlrAsWuU.js.br +0 -0
- package/public/assets/pencil-ZlrAsWuU.js.gz +0 -0
- package/public/assets/play-4CsGZdQU.js +6 -0
- package/public/assets/play-4CsGZdQU.js.br +0 -0
- package/public/assets/play-4CsGZdQU.js.gz +0 -0
- package/public/assets/resource-health-oWJ8hy9X.js +1 -0
- package/public/assets/resource-health-oWJ8hy9X.js.br +0 -0
- package/public/assets/resource-health-oWJ8hy9X.js.gz +0 -0
- package/public/assets/runs-CInQb6v1.js +1 -0
- package/public/assets/runs-CInQb6v1.js.br +0 -0
- package/public/assets/runs-CInQb6v1.js.gz +0 -0
- package/public/assets/search-input-G_QpBvPM.js +6 -0
- package/public/assets/search-input-G_QpBvPM.js.br +0 -0
- package/public/assets/search-input-G_QpBvPM.js.gz +0 -0
- package/public/assets/select-DDzv2YfY.js +11 -0
- package/public/assets/select-DDzv2YfY.js.br +0 -0
- package/public/assets/select-DDzv2YfY.js.gz +0 -0
- package/public/assets/slider-DE7lQdK4.js +1 -0
- package/public/assets/slider-DE7lQdK4.js.br +0 -0
- package/public/assets/slider-DE7lQdK4.js.gz +0 -0
- package/public/assets/tabs-Co1N3x_X.js +1 -0
- package/public/assets/tabs-Co1N3x_X.js.br +0 -0
- package/public/assets/tabs-Co1N3x_X.js.gz +0 -0
- package/public/assets/tech-label-BMvuJmUf.js +1 -0
- package/public/assets/tech-label-BMvuJmUf.js.br +0 -0
- package/public/assets/tech-label-BMvuJmUf.js.gz +0 -0
- package/public/assets/{trash-D1l1Fm-Z.js → trash-Cwj2dbgB.js} +3 -3
- package/public/assets/trash-Cwj2dbgB.js.br +0 -0
- package/public/assets/trash-Cwj2dbgB.js.gz +0 -0
- package/public/assets/{useFocusAfterSwap-BY3N9f9v.js → useFocusAfterSwap-BdItkQCu.js} +1 -1
- package/public/assets/useFocusAfterSwap-BdItkQCu.js.br +0 -0
- package/public/assets/useFocusAfterSwap-BdItkQCu.js.gz +0 -0
- package/public/assets/useRelay-CES3I1Mt.js +1 -0
- package/public/assets/useRelay-CES3I1Mt.js.br +0 -0
- package/public/assets/useRelay-CES3I1Mt.js.gz +0 -0
- package/public/assets/useShownAfter-By1Yxysz.js +1 -0
- package/public/assets/useShownAfter-By1Yxysz.js.br +0 -0
- package/public/assets/useShownAfter-By1Yxysz.js.gz +0 -0
- package/public/assets/{vendor-charts-BykzUV2D.js → vendor-charts-BE7R_8yA.js} +2 -2
- package/public/assets/vendor-charts-BE7R_8yA.js.br +0 -0
- package/public/assets/vendor-charts-BE7R_8yA.js.gz +0 -0
- package/public/assets/{vendor-react-BlPSzrEe.js → vendor-react-DTSdA8m-.js} +4 -4
- package/public/assets/vendor-react-DTSdA8m-.js.br +0 -0
- package/public/assets/{vendor-react-BlPSzrEe.js.gz → vendor-react-DTSdA8m-.js.gz} +0 -0
- package/public/assets/{vendor-router-B_lVHrKl.js → vendor-router-B1pQim9Z.js} +3 -3
- package/public/assets/vendor-router-B1pQim9Z.js.br +0 -0
- package/public/assets/vendor-router-B1pQim9Z.js.gz +0 -0
- package/public/assets/video-swsaHrfi.js +6 -0
- package/public/assets/video-swsaHrfi.js.br +0 -0
- package/public/assets/video-swsaHrfi.js.gz +0 -0
- package/public/index.html +30 -4
- package/public/index.html.br +0 -0
- package/public/index.html.gz +0 -0
- package/public/og-image.jpg +0 -0
- package/public/assets/AppCenter-CeLjUbnR.js +0 -26
- package/public/assets/AppCenter-CeLjUbnR.js.br +0 -0
- package/public/assets/AppCenter-CeLjUbnR.js.gz +0 -0
- package/public/assets/Default-UB0Pa3lN.js +0 -1
- package/public/assets/Default-UB0Pa3lN.js.br +0 -0
- package/public/assets/Default-UB0Pa3lN.js.gz +0 -0
- package/public/assets/Invite-Dv3omu3x.js +0 -1
- package/public/assets/Invite-Dv3omu3x.js.br +0 -0
- package/public/assets/Invite-Dv3omu3x.js.gz +0 -0
- package/public/assets/MacResources-CY4-OdGt.js +0 -1
- package/public/assets/MacResources-CY4-OdGt.js.br +0 -0
- package/public/assets/MacResources-CY4-OdGt.js.gz +0 -0
- package/public/assets/NotFound-wHOnT2Fl.js.br +0 -0
- package/public/assets/NotFound-wHOnT2Fl.js.gz +0 -0
- package/public/assets/QASession-H5eRSwZv.js +0 -150
- package/public/assets/QASession-H5eRSwZv.js.br +0 -0
- package/public/assets/QASession-H5eRSwZv.js.gz +0 -0
- package/public/assets/ResetPassword-nFTigYt2.js +0 -1
- package/public/assets/ResetPassword-nFTigYt2.js.br +0 -0
- package/public/assets/ResetPassword-nFTigYt2.js.gz +0 -0
- package/public/assets/Setup-UmLtF2PF.js +0 -1
- package/public/assets/Setup-UmLtF2PF.js.br +0 -0
- package/public/assets/Setup-UmLtF2PF.js.gz +0 -0
- package/public/assets/Team-DW40kt3H.js +0 -6
- package/public/assets/Team-DW40kt3H.js.br +0 -0
- package/public/assets/Team-DW40kt3H.js.gz +0 -0
- package/public/assets/Tokens-B1a4ChJK.js +0 -1
- package/public/assets/Tokens-B1a4ChJK.js.br +0 -0
- package/public/assets/Tokens-B1a4ChJK.js.gz +0 -0
- package/public/assets/alert-dialog-aoqYdAwx.js +0 -1
- package/public/assets/alert-dialog-aoqYdAwx.js.br +0 -0
- package/public/assets/alert-dialog-aoqYdAwx.js.gz +0 -0
- package/public/assets/badge-DNaS0Mu3.js.br +0 -0
- package/public/assets/badge-DNaS0Mu3.js.gz +0 -0
- package/public/assets/build-format-DF5aFcag.js +0 -11
- package/public/assets/build-format-DF5aFcag.js.br +0 -0
- package/public/assets/build-format-DF5aFcag.js.gz +0 -0
- package/public/assets/dialog-DE2WUMuX.js +0 -11
- package/public/assets/dialog-DE2WUMuX.js.br +0 -0
- package/public/assets/dialog-DE2WUMuX.js.gz +0 -0
- package/public/assets/field-hint-Csb40Gzp.js +0 -1
- package/public/assets/field-hint-Csb40Gzp.js.br +0 -2
- package/public/assets/field-hint-Csb40Gzp.js.gz +0 -0
- package/public/assets/index-BbCkTtjO.css +0 -1
- package/public/assets/index-BbCkTtjO.css.br +0 -0
- package/public/assets/index-BbCkTtjO.css.gz +0 -0
- package/public/assets/index-DSW9h8XX.js +0 -218
- package/public/assets/index-DSW9h8XX.js.br +0 -0
- package/public/assets/index-DSW9h8XX.js.gz +0 -0
- package/public/assets/list-view-C0w97ltJ.js +0 -1
- package/public/assets/list-view-C0w97ltJ.js.br +0 -0
- package/public/assets/list-view-C0w97ltJ.js.gz +0 -0
- package/public/assets/pencil-BN18R4oa.js.br +0 -0
- package/public/assets/pencil-BN18R4oa.js.gz +0 -0
- package/public/assets/resource-health-Bi20EUDs.js +0 -1
- package/public/assets/resource-health-Bi20EUDs.js.br +0 -0
- package/public/assets/resource-health-Bi20EUDs.js.gz +0 -0
- package/public/assets/trash-D1l1Fm-Z.js.br +0 -0
- package/public/assets/trash-D1l1Fm-Z.js.gz +0 -0
- package/public/assets/useFocusAfterSwap-BY3N9f9v.js.br +0 -0
- package/public/assets/useFocusAfterSwap-BY3N9f9v.js.gz +0 -0
- package/public/assets/vendor-charts-BykzUV2D.js.br +0 -0
- package/public/assets/vendor-charts-BykzUV2D.js.gz +0 -0
- package/public/assets/vendor-react-BlPSzrEe.js.br +0 -0
- package/public/assets/vendor-router-B_lVHrKl.js.br +0 -0
- package/public/assets/vendor-router-B_lVHrKl.js.gz +0 -0
package/dist/api/runs.js
ADDED
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { randomUUID } from 'crypto';
|
|
4
|
+
import { getDb, isDbOpen } from '../db.js';
|
|
5
|
+
import { assertCanWrite, requireBuildAuth, requireViewAuth } from '../middleware/auth.js';
|
|
6
|
+
import { json, readBodyLimited } from '../router.js';
|
|
7
|
+
import { unlinkSafe } from '../lib/uploads.js';
|
|
8
|
+
// Flow run records: what `tapflow flow run` did, for the runs page. The CLI writes them over REST with the
|
|
9
|
+
// token it already runs with, and nothing here may ever make that run slower or fail it — the caps below
|
|
10
|
+
// refuse rather than wait, and every refusal is an answer the CLI can drop.
|
|
11
|
+
const MAX_JSON_BYTES = 1024 * 1024;
|
|
12
|
+
const MAX_SCREENSHOT_BYTES = 5 * 1024 * 1024;
|
|
13
|
+
const MAX_FLOWS = 500;
|
|
14
|
+
const MAX_STEPS = 1000;
|
|
15
|
+
const MAX_NAME = 500;
|
|
16
|
+
const MAX_MESSAGE = 4000;
|
|
17
|
+
const MAX_STEP_MESSAGE = 2000;
|
|
18
|
+
const MAX_CLIENT_ID = 200;
|
|
19
|
+
const MAX_SUGGESTIONS = 3;
|
|
20
|
+
const MAX_SUGGESTION_YAML = 600;
|
|
21
|
+
const MAX_SUGGESTION_FIELD = 200;
|
|
22
|
+
const PAGE_DEFAULT = 50;
|
|
23
|
+
const PAGE_MAX = 100;
|
|
24
|
+
/** A run with no build has nothing else to expire with. */
|
|
25
|
+
const BUILDLESS_TTL_DAYS = 7;
|
|
26
|
+
/**
|
|
27
|
+
* How long a running run whose holder has no socket stays running when the relay did not see the socket
|
|
28
|
+
* close — a relay restart, which drops every socket without a close handler for the ones before it. The
|
|
29
|
+
* close handler is the normal path (`abandonRunsOf`); this only catches what it could not.
|
|
30
|
+
*/
|
|
31
|
+
const ORPHAN_GRACE_SECONDS = 60;
|
|
32
|
+
/**
|
|
33
|
+
* The owner keys the runner's socket can have (`ownerKeyFor` in `RelayServer`). **Two, because the socket
|
|
34
|
+
* and REST disagree on loopback**: the handshake does not read a PAT from a local connection, so a runner on
|
|
35
|
+
* the relay's own Mac — a common CI setup — holds its sessions as `anon:<client>`, while the same token on
|
|
36
|
+
* REST names its user. Matching the user form only left every such run unconnected and aborted it a minute
|
|
37
|
+
* in. The anonymous form is reachable only from loopback, and only with the client id, which nothing serves.
|
|
38
|
+
*/
|
|
39
|
+
export function holderKeys(createdBy, client) {
|
|
40
|
+
return createdBy === null ? [`anon:${client}`] : [`${createdBy}:${client}`, `anon:${client}`];
|
|
41
|
+
}
|
|
42
|
+
/** Where run screenshots live, alongside `uploads/` and `recordings/`. */
|
|
43
|
+
export function runScreenshotsDirFor(uploadsDir) {
|
|
44
|
+
return path.join(uploadsDir, '../run-screenshots');
|
|
45
|
+
}
|
|
46
|
+
function isObj(v) {
|
|
47
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
48
|
+
}
|
|
49
|
+
/** A string cut to `max`, or null for anything that is not a non-empty string. */
|
|
50
|
+
function str(v, max) {
|
|
51
|
+
if (typeof v !== 'string' || v.length === 0)
|
|
52
|
+
return null;
|
|
53
|
+
return v.length > max ? v.slice(0, max) : v;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A failed `tapOn`'s suggested lines (B2), kept whole or not at all: unlike a message, a cut YAML line is a different
|
|
57
|
+
* selector — `"Pay now la` matches by part — so one over its cap is dropped, never shortened.
|
|
58
|
+
*/
|
|
59
|
+
function suggestions(v) {
|
|
60
|
+
if (!Array.isArray(v))
|
|
61
|
+
return [];
|
|
62
|
+
const whole = (x, max) => (typeof x === 'string' && x.length <= max ? x : null);
|
|
63
|
+
const out = [];
|
|
64
|
+
for (const s of v) {
|
|
65
|
+
if (out.length === MAX_SUGGESTIONS)
|
|
66
|
+
break;
|
|
67
|
+
if (!isObj(s) || !isObj(s.element))
|
|
68
|
+
continue;
|
|
69
|
+
const yaml = whole(s.yaml, MAX_SUGGESTION_YAML);
|
|
70
|
+
const role = whole(s.element.role, 50);
|
|
71
|
+
const label = whole(s.element.label, MAX_SUGGESTION_FIELD);
|
|
72
|
+
const identifier = s.element.identifier === undefined ? undefined : whole(s.element.identifier, MAX_SUGGESTION_FIELD);
|
|
73
|
+
if (!yaml || !yaml.startsWith('tapOn: ') || role === null || label === null || identifier === null)
|
|
74
|
+
continue;
|
|
75
|
+
out.push({ yaml, element: { role, label, ...(identifier ? { identifier } : {}) } });
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
function nonNegInt(v) {
|
|
80
|
+
return typeof v === 'number' && Number.isInteger(v) && v >= 0 ? v : null;
|
|
81
|
+
}
|
|
82
|
+
/** Only `http:` and `https:`: the dashboard renders this as a link, and `javascript:` is a URL too. */
|
|
83
|
+
function httpUrl(v) {
|
|
84
|
+
const s = str(v, 2000);
|
|
85
|
+
if (s === null)
|
|
86
|
+
return null;
|
|
87
|
+
try {
|
|
88
|
+
const u = new URL(s);
|
|
89
|
+
return u.protocol === 'http:' || u.protocol === 'https:' ? u.toString() : null;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function readJsonBody(req, res) {
|
|
96
|
+
const buf = await readBodyLimited(req, MAX_JSON_BYTES);
|
|
97
|
+
if (buf === null) {
|
|
98
|
+
json(res, 413, { error: 'Body too large' });
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const parsed = JSON.parse(buf.toString('utf-8'));
|
|
103
|
+
if (isObj(parsed))
|
|
104
|
+
return parsed;
|
|
105
|
+
}
|
|
106
|
+
catch { /* answered below */ }
|
|
107
|
+
json(res, 400, { error: 'Body must be a JSON object' });
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
export function findRun(id) {
|
|
111
|
+
return getDb().prepare('SELECT * FROM flow_runs WHERE id = ?').get(id);
|
|
112
|
+
}
|
|
113
|
+
// ── liveness ────────────────────────────────────────────────────────────────
|
|
114
|
+
const ABANDON = `
|
|
115
|
+
UPDATE flow_runs
|
|
116
|
+
SET status = 'aborted', finished_by = 'relay', failure_kind = 'holder-lost',
|
|
117
|
+
error_message = 'The flow runner disconnected before the run finished',
|
|
118
|
+
finished_at = datetime('now')
|
|
119
|
+
WHERE status = 'running'`;
|
|
120
|
+
/**
|
|
121
|
+
* A socket with `socketKey` closed: the runs it held are over unless another socket of the same runner is
|
|
122
|
+
* still open. Called from the socket close handler, so it tolerates a relay started without a database
|
|
123
|
+
* (tests that never touch REST).
|
|
124
|
+
*/
|
|
125
|
+
export function abandonRunsOf(socketKey, holders) {
|
|
126
|
+
if (!isDbOpen())
|
|
127
|
+
return;
|
|
128
|
+
const colon = socketKey.indexOf(':');
|
|
129
|
+
const user = socketKey.slice(0, colon);
|
|
130
|
+
const client = socketKey.slice(colon + 1);
|
|
131
|
+
const db = getDb();
|
|
132
|
+
const runs = (user === 'anon'
|
|
133
|
+
? db.prepare(`SELECT seq, created_by, holder_client FROM flow_runs WHERE status = 'running' AND holder_client = ?`).all(client)
|
|
134
|
+
: db.prepare(`SELECT seq, created_by, holder_client FROM flow_runs WHERE status = 'running' AND holder_client = ? AND created_by = ?`).all(client, Number(user)));
|
|
135
|
+
if (runs.length === 0)
|
|
136
|
+
return;
|
|
137
|
+
const open = holders.connectedKeys();
|
|
138
|
+
const stmt = db.prepare(`${ABANDON} AND seq = ?`);
|
|
139
|
+
for (const r of runs) {
|
|
140
|
+
if (!holderKeys(r.created_by, r.holder_client).some((k) => open.has(k)))
|
|
141
|
+
stmt.run(r.seq);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Close every running run whose holder is gone and has been quiet past the grace. Read paths call this
|
|
146
|
+
* before answering, and the daily purge persists it, so no timer exists for it.
|
|
147
|
+
*/
|
|
148
|
+
export function settleOrphanedRuns(holders) {
|
|
149
|
+
const running = getDb().prepare(`
|
|
150
|
+
SELECT seq, created_by, holder_client FROM flow_runs
|
|
151
|
+
WHERE status = 'running' AND last_activity_at < datetime('now', ?)
|
|
152
|
+
`).all(`-${ORPHAN_GRACE_SECONDS} seconds`);
|
|
153
|
+
if (running.length === 0)
|
|
154
|
+
return;
|
|
155
|
+
const open = holders.connectedKeys();
|
|
156
|
+
const stmt = getDb().prepare(`${ABANDON} AND seq = ?`);
|
|
157
|
+
for (const r of running) {
|
|
158
|
+
if (!holderKeys(r.created_by, r.holder_client).some((k) => open.has(k)))
|
|
159
|
+
stmt.run(r.seq);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// ── writes (the run's creator only) ─────────────────────────────────────────
|
|
163
|
+
/**
|
|
164
|
+
* The run, when this request may write to it: a `builds:write` credential whose owner may write, belonging
|
|
165
|
+
* to the user who created the run — and, when it was created with a PAT, that same PAT. A run id leaking
|
|
166
|
+
* into a CI log must not let another job, or another person's token, rewrite its result.
|
|
167
|
+
*/
|
|
168
|
+
export function ownRun(req, res, id,
|
|
169
|
+
/** Seconds after the runner finished the run during which it may still write. Only video uses it: the last
|
|
170
|
+
* flow's tail is sent after `finish`, which goes first so a slow video never costs the run its own ending. */
|
|
171
|
+
graceAfterFinishSeconds = 0) {
|
|
172
|
+
const auth = requireBuildAuth(req, res);
|
|
173
|
+
if (!auth)
|
|
174
|
+
return null;
|
|
175
|
+
if (!assertCanWrite(res, auth.userId))
|
|
176
|
+
return null;
|
|
177
|
+
const run = findRun(id);
|
|
178
|
+
if (!run) {
|
|
179
|
+
json(res, 404, { error: 'Run not found' });
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
const samePat = run.pat_id === null || run.pat_id === auth.patId;
|
|
183
|
+
if (run.created_by !== auth.userId || !samePat) {
|
|
184
|
+
json(res, 403, { error: 'Only the run that created this record may write to it' });
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
if (run.finished_by === 'client' && !finishedWithin(run, graceAfterFinishSeconds)) {
|
|
188
|
+
json(res, 409, { error: 'Run already finished' });
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
return run;
|
|
192
|
+
}
|
|
193
|
+
/** Whether the run was finished no more than `seconds` ago. */
|
|
194
|
+
export function finishedWithin(run, seconds) {
|
|
195
|
+
if (seconds <= 0 || run.finished_at === null)
|
|
196
|
+
return false;
|
|
197
|
+
const row = getDb().prepare(`SELECT ? > datetime('now', ?) AS recent`).get(run.finished_at, `-${seconds} seconds`);
|
|
198
|
+
return row.recent === 1;
|
|
199
|
+
}
|
|
200
|
+
export async function handleCreateRun(req, res) {
|
|
201
|
+
const auth = requireBuildAuth(req, res);
|
|
202
|
+
if (!auth)
|
|
203
|
+
return;
|
|
204
|
+
if (!assertCanWrite(res, auth.userId))
|
|
205
|
+
return;
|
|
206
|
+
const body = await readJsonBody(req, res);
|
|
207
|
+
if (!body)
|
|
208
|
+
return;
|
|
209
|
+
const client = body.client;
|
|
210
|
+
if (typeof client !== 'string' || client.length === 0 || client.length > MAX_CLIENT_ID) {
|
|
211
|
+
return json(res, 400, { error: 'client must be the id the runner connected with' });
|
|
212
|
+
}
|
|
213
|
+
if (!Array.isArray(body.flows) || body.flows.length > MAX_FLOWS) {
|
|
214
|
+
return json(res, 400, { error: `flows must be an array of at most ${MAX_FLOWS}` });
|
|
215
|
+
}
|
|
216
|
+
const flows = [];
|
|
217
|
+
for (const f of body.flows) {
|
|
218
|
+
const name = isObj(f) ? str(f.name, MAX_NAME) : null;
|
|
219
|
+
if (name === null)
|
|
220
|
+
return json(res, 400, { error: 'every flow needs a name' });
|
|
221
|
+
flows.push({ name, file: str(f.file, MAX_NAME) });
|
|
222
|
+
}
|
|
223
|
+
let buildId = null;
|
|
224
|
+
if (body.buildId !== undefined && body.buildId !== null) {
|
|
225
|
+
buildId = nonNegInt(body.buildId);
|
|
226
|
+
if (buildId === null || !getDb().prepare('SELECT 1 FROM builds WHERE id = ?').get(buildId)) {
|
|
227
|
+
return json(res, 400, { error: 'buildId does not name a build' });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const ci = isObj(body.ci) ? body.ci : null;
|
|
231
|
+
const id = randomUUID();
|
|
232
|
+
getDb().prepare(`
|
|
233
|
+
INSERT INTO flow_runs (id, build_id, holder_client, created_by, pat_id, is_ci, ci_provider, ci_branch, ci_commit,
|
|
234
|
+
ci_job_url, flows_json, flows_total, delete_after)
|
|
235
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CASE WHEN ? IS NULL THEN datetime('now', ?) END)
|
|
236
|
+
`).run(id, buildId, client, auth.userId, auth.patId ?? null, ci ? 1 : 0, ci ? str(ci.provider, 50) : null, ci ? str(ci.branch, MAX_NAME) : null, ci ? str(ci.commit, 100) : null, ci ? httpUrl(ci.jobUrl) : null, JSON.stringify(flows), flows.length, buildId, `+${BUILDLESS_TTL_DAYS} days`);
|
|
237
|
+
json(res, 201, { id });
|
|
238
|
+
}
|
|
239
|
+
const FLOW_STATUSES = new Set(['passed', 'failed']);
|
|
240
|
+
const STEP_STATUSES = new Set(['passed', 'failed', 'skipped']);
|
|
241
|
+
export async function handleReportFlow(req, res, params) {
|
|
242
|
+
const run = ownRun(req, res, params.id);
|
|
243
|
+
if (!run)
|
|
244
|
+
return;
|
|
245
|
+
const idx = Number(params.idx);
|
|
246
|
+
if (!Number.isInteger(idx) || idx < 0 || idx >= run.flows_total)
|
|
247
|
+
return json(res, 404, { error: 'No such flow in this run' });
|
|
248
|
+
const body = await readJsonBody(req, res);
|
|
249
|
+
if (!body)
|
|
250
|
+
return;
|
|
251
|
+
if (typeof body.status !== 'string' || !FLOW_STATUSES.has(body.status)) {
|
|
252
|
+
return json(res, 400, { error: 'status must be passed or failed' });
|
|
253
|
+
}
|
|
254
|
+
const durationMs = nonNegInt(body.durationMs);
|
|
255
|
+
if (durationMs === null)
|
|
256
|
+
return json(res, 400, { error: 'durationMs must be a non-negative integer' });
|
|
257
|
+
if (!Array.isArray(body.steps) || body.steps.length > MAX_STEPS) {
|
|
258
|
+
return json(res, 400, { error: `steps must be an array of at most ${MAX_STEPS}` });
|
|
259
|
+
}
|
|
260
|
+
// Rebuilt field by field, so what is stored is only what the page renders.
|
|
261
|
+
const steps = [];
|
|
262
|
+
for (const s of body.steps) {
|
|
263
|
+
if (!isObj(s) || typeof s.status !== 'string' || !STEP_STATUSES.has(s.status)) {
|
|
264
|
+
return json(res, 400, { error: 'every step needs a status of passed, failed or skipped' });
|
|
265
|
+
}
|
|
266
|
+
const message = str(s.message, MAX_STEP_MESSAGE);
|
|
267
|
+
const suggested = s.status === 'failed' ? suggestions(s.suggestions) : [];
|
|
268
|
+
steps.push({
|
|
269
|
+
index: nonNegInt(s.index) ?? steps.length,
|
|
270
|
+
name: str(s.name, MAX_NAME) ?? '',
|
|
271
|
+
status: s.status,
|
|
272
|
+
durationMs: nonNegInt(s.durationMs) ?? 0,
|
|
273
|
+
...(message !== null ? { message } : {}),
|
|
274
|
+
...(suggested.length > 0 ? { suggestions: suggested } : {}),
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
const planned = JSON.parse(run.flows_json)[idx];
|
|
278
|
+
const db = getDb();
|
|
279
|
+
// The run can go while the body arrives — its build purged, its app deleted — and the insert would then
|
|
280
|
+
// fail its foreign key as a 500 with a stack in the log, for an answer that is just "not found".
|
|
281
|
+
// `ownRun` judged the run before the body arrived; it is judged again here, where nothing can interleave.
|
|
282
|
+
const refusal = db.transaction(() => {
|
|
283
|
+
const now = db.prepare('SELECT finished_by FROM flow_runs WHERE seq = ?').get(run.seq);
|
|
284
|
+
if (!now)
|
|
285
|
+
return 'gone';
|
|
286
|
+
if (now.finished_by === 'client')
|
|
287
|
+
return 'finished';
|
|
288
|
+
// An upsert that keeps the screenshot: the CLI may retry a report, and the screenshot arrives separately.
|
|
289
|
+
db.prepare(`
|
|
290
|
+
INSERT INTO flow_run_flows (run_seq, idx, name, file, device_id, device_name, platform, status, failure_kind,
|
|
291
|
+
failure_message, duration_ms, steps_json)
|
|
292
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
293
|
+
ON CONFLICT (run_seq, idx) DO UPDATE SET
|
|
294
|
+
device_id = excluded.device_id, device_name = excluded.device_name, platform = excluded.platform,
|
|
295
|
+
status = excluded.status, failure_kind = excluded.failure_kind, failure_message = excluded.failure_message,
|
|
296
|
+
duration_ms = excluded.duration_ms, steps_json = excluded.steps_json
|
|
297
|
+
`).run(run.seq, idx, planned.name, planned.file, str(body.deviceId, MAX_NAME), str(body.deviceName, MAX_NAME), str(body.platform, 50), body.status, str(body.failureKind, 50), str(body.failureMessage, MAX_MESSAGE), durationMs, JSON.stringify(steps));
|
|
298
|
+
db.prepare(`UPDATE flow_runs SET last_activity_at = datetime('now') WHERE seq = ?`).run(run.seq);
|
|
299
|
+
return null;
|
|
300
|
+
})();
|
|
301
|
+
if (refusal === 'gone')
|
|
302
|
+
return json(res, 404, { error: 'Run not found' });
|
|
303
|
+
if (refusal === 'finished')
|
|
304
|
+
return json(res, 409, { error: 'Run already finished' });
|
|
305
|
+
json(res, 200, { ok: true });
|
|
306
|
+
}
|
|
307
|
+
const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
308
|
+
const JPEG_MAGIC = Buffer.from([0xff, 0xd8, 0xff]);
|
|
309
|
+
/** The type the bytes are, not the type the request says — the file is served back to browsers. */
|
|
310
|
+
function imageKind(buf) {
|
|
311
|
+
if (buf.subarray(0, PNG_MAGIC.length).equals(PNG_MAGIC))
|
|
312
|
+
return { mime: 'image/png', ext: '.png' };
|
|
313
|
+
if (buf.subarray(0, JPEG_MAGIC.length).equals(JPEG_MAGIC))
|
|
314
|
+
return { mime: 'image/jpeg', ext: '.jpg' };
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
export async function handleUploadFlowScreenshot(req, res, params, screenshotsDir) {
|
|
318
|
+
const run = ownRun(req, res, params.id);
|
|
319
|
+
if (!run)
|
|
320
|
+
return;
|
|
321
|
+
const current = getDb().prepare('SELECT screenshot_file FROM flow_run_flows WHERE run_seq = ? AND idx = ?');
|
|
322
|
+
if (!current.get(run.seq, Number(params.idx)))
|
|
323
|
+
return json(res, 404, { error: 'Report the flow before its screenshot' });
|
|
324
|
+
const buf = await readBodyLimited(req, MAX_SCREENSHOT_BYTES);
|
|
325
|
+
if (buf === null)
|
|
326
|
+
return json(res, 413, { error: `Screenshot larger than ${MAX_SCREENSHOT_BYTES} bytes` });
|
|
327
|
+
const kind = imageKind(buf);
|
|
328
|
+
if (!kind)
|
|
329
|
+
return json(res, 415, { error: 'Screenshot must be PNG or JPEG' });
|
|
330
|
+
fs.mkdirSync(screenshotsDir, { recursive: true });
|
|
331
|
+
const filename = `${randomUUID()}${kind.ext}`;
|
|
332
|
+
await fs.promises.writeFile(path.join(screenshotsDir, filename), buf);
|
|
333
|
+
// **Re-read after the awaits, with nothing awaited between the read and the update.** Two uploads for one
|
|
334
|
+
// flow (a retry) both read the same old name before their bodies arrived, and a run deleted with its build
|
|
335
|
+
// meanwhile updates no row. Either way a file no row names would stay forever: nothing sweeps this
|
|
336
|
+
// directory, so a file kept here outlives the run it was "deleted with".
|
|
337
|
+
const before = current.get(run.seq, Number(params.idx));
|
|
338
|
+
const updated = getDb().prepare('UPDATE flow_run_flows SET screenshot_file = ?, screenshot_mime = ? WHERE run_seq = ? AND idx = ?')
|
|
339
|
+
.run(filename, kind.mime, run.seq, Number(params.idx));
|
|
340
|
+
if (updated.changes === 0) {
|
|
341
|
+
unlinkSafe(path.join(screenshotsDir, filename), 'run screenshot');
|
|
342
|
+
return json(res, 404, { error: 'Run not found' });
|
|
343
|
+
}
|
|
344
|
+
if (before?.screenshot_file)
|
|
345
|
+
unlinkSafe(path.join(screenshotsDir, before.screenshot_file), 'run screenshot');
|
|
346
|
+
json(res, 200, { ok: true });
|
|
347
|
+
}
|
|
348
|
+
const FINAL_STATUSES = new Set(['passed', 'failed', 'aborted']);
|
|
349
|
+
export async function handleFinishRun(req, res, params) {
|
|
350
|
+
const run = ownRun(req, res, params.id);
|
|
351
|
+
if (!run)
|
|
352
|
+
return;
|
|
353
|
+
const body = await readJsonBody(req, res);
|
|
354
|
+
if (!body)
|
|
355
|
+
return;
|
|
356
|
+
if (typeof body.status !== 'string' || !FINAL_STATUSES.has(body.status)) {
|
|
357
|
+
return json(res, 400, { error: 'status must be passed, failed or aborted' });
|
|
358
|
+
}
|
|
359
|
+
const exitCode = nonNegInt(body.exitCode);
|
|
360
|
+
if (exitCode === null)
|
|
361
|
+
return json(res, 400, { error: 'exitCode must be a non-negative integer' });
|
|
362
|
+
// A run the relay closed as `holder-lost` is still finished here: after a relay restart the CLI is alive,
|
|
363
|
+
// reports its own outcome, and knows it better than the relay's guess.
|
|
364
|
+
// The state is checked in the update itself, not only in `ownRun`: two finishes (a retry after a timeout) can
|
|
365
|
+
// both pass that check while their bodies arrive, and the second would overwrite the first.
|
|
366
|
+
const db = getDb();
|
|
367
|
+
const updated = db.prepare(`
|
|
368
|
+
UPDATE flow_runs
|
|
369
|
+
SET status = ?, finished_by = 'client', exit_code = ?, failure_kind = ?, error_message = ?,
|
|
370
|
+
finished_at = datetime('now'), last_activity_at = datetime('now')
|
|
371
|
+
WHERE seq = ? AND (finished_by IS NULL OR finished_by = 'relay')
|
|
372
|
+
`).run(body.status, exitCode, str(body.failureKind, 50), str(body.errorMessage, MAX_MESSAGE), run.seq);
|
|
373
|
+
if (updated.changes === 0) {
|
|
374
|
+
return db.prepare('SELECT 1 FROM flow_runs WHERE seq = ?').get(run.seq)
|
|
375
|
+
? json(res, 409, { error: 'Run already finished' })
|
|
376
|
+
: json(res, 404, { error: 'Run not found' });
|
|
377
|
+
}
|
|
378
|
+
json(res, 200, { ok: true });
|
|
379
|
+
}
|
|
380
|
+
function buildSummary(buildId) {
|
|
381
|
+
if (buildId === null)
|
|
382
|
+
return null;
|
|
383
|
+
const b = getDb().prepare(`
|
|
384
|
+
SELECT b.id, ap.name AS app_name, b.version_name, b.build_number, ap.platform
|
|
385
|
+
FROM builds b LEFT JOIN apps ap ON ap.id = b.app_id WHERE b.id = ?
|
|
386
|
+
`).get(buildId);
|
|
387
|
+
if (!b)
|
|
388
|
+
return null;
|
|
389
|
+
return { id: b.id, appName: b.app_name, versionName: b.version_name, buildNumber: b.build_number, platform: b.platform };
|
|
390
|
+
}
|
|
391
|
+
function creatorName(userId) {
|
|
392
|
+
if (userId === null)
|
|
393
|
+
return null;
|
|
394
|
+
const u = getDb().prepare('SELECT email, display_name FROM users WHERE id = ?').get(userId);
|
|
395
|
+
if (!u)
|
|
396
|
+
return null;
|
|
397
|
+
return u.display_name ?? u.email.split('@')[0];
|
|
398
|
+
}
|
|
399
|
+
function runSummary(run) {
|
|
400
|
+
return {
|
|
401
|
+
id: run.id,
|
|
402
|
+
status: run.status,
|
|
403
|
+
finishedBy: run.finished_by,
|
|
404
|
+
failureKind: run.failure_kind,
|
|
405
|
+
errorMessage: run.error_message,
|
|
406
|
+
exitCode: run.exit_code,
|
|
407
|
+
isCi: run.is_ci === 1,
|
|
408
|
+
ci: run.is_ci === 1
|
|
409
|
+
? { provider: run.ci_provider, branch: run.ci_branch, commit: run.ci_commit, jobUrl: run.ci_job_url }
|
|
410
|
+
: null,
|
|
411
|
+
build: buildSummary(run.build_id),
|
|
412
|
+
createdBy: creatorName(run.created_by),
|
|
413
|
+
flowsTotal: run.flows_total,
|
|
414
|
+
createdAt: run.created_at,
|
|
415
|
+
finishedAt: run.finished_at,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
export function handleListRuns(req, res, holders) {
|
|
419
|
+
if (!requireViewAuth(req, res))
|
|
420
|
+
return;
|
|
421
|
+
settleOrphanedRuns(holders);
|
|
422
|
+
const q = new URL(req.url ?? '/', 'http://x').searchParams;
|
|
423
|
+
const where = [];
|
|
424
|
+
const args = [];
|
|
425
|
+
// A number that is not an id is refused rather than read: `build=abc` would match nothing, and a malformed cursor
|
|
426
|
+
// would quietly answer the newest page — a valid-looking reply holding the wrong runs. An empty value is "none".
|
|
427
|
+
// **The text is checked, not `Number(text)`**, which reads " " as 0, "0x10" as 16 and "1e2" as 100 — and then the
|
|
428
|
+
// number too, since a long enough run of digits becomes Infinity or loses its last digits.
|
|
429
|
+
const ids = {};
|
|
430
|
+
for (const key of ['build', 'before', 'after']) {
|
|
431
|
+
const v = q.get(key);
|
|
432
|
+
if (!v)
|
|
433
|
+
continue;
|
|
434
|
+
const id = Number(v);
|
|
435
|
+
if (!/^\d+$/.test(v) || !Number.isSafeInteger(id))
|
|
436
|
+
return json(res, 400, { error: `${key} must be a non-negative integer` });
|
|
437
|
+
ids[key] = id;
|
|
438
|
+
}
|
|
439
|
+
if (ids.build !== undefined) {
|
|
440
|
+
where.push('r.build_id = ?');
|
|
441
|
+
args.push(ids.build);
|
|
442
|
+
}
|
|
443
|
+
const status = q.get('status');
|
|
444
|
+
if (status !== null) {
|
|
445
|
+
where.push('r.status = ?');
|
|
446
|
+
args.push(status);
|
|
447
|
+
}
|
|
448
|
+
const ci = q.get('ci');
|
|
449
|
+
if (ci === '1' || ci === '0') {
|
|
450
|
+
where.push('r.is_ci = ?');
|
|
451
|
+
args.push(Number(ci));
|
|
452
|
+
}
|
|
453
|
+
const flow = q.get('flow');
|
|
454
|
+
if (flow !== null) {
|
|
455
|
+
// The planned list, not the reported rows: a run that never reached the flow still ran with it. A part of the
|
|
456
|
+
// name, as the runs page searches as you type. SQLite's `lower` folds ASCII only, so "Über" is still
|
|
457
|
+
// case-sensitive; Hangul has no case.
|
|
458
|
+
where.push(`EXISTS (SELECT 1 FROM json_each(r.flows_json) j WHERE instr(lower(json_extract(j.value, '$.name')), lower(?)) > 0)`);
|
|
459
|
+
args.push(flow);
|
|
460
|
+
}
|
|
461
|
+
const limitParam = Number(q.get('limit'));
|
|
462
|
+
const limit = Number.isInteger(limitParam) && limitParam > 0 ? Math.min(limitParam, PAGE_MAX) : PAGE_DEFAULT;
|
|
463
|
+
// Pages by cursor in both directions: `before` for the page of older runs, `after` for newer ones. Not by number,
|
|
464
|
+
// because new runs keep arriving on top and a page number would slide. `after` reads upward from the cursor and
|
|
465
|
+
// is turned back to newest first, so it is the page directly above the one it came from.
|
|
466
|
+
const filtered = (extra) => [...where, ...(extra ? [extra] : [])];
|
|
467
|
+
const sql = (conds, order) => `
|
|
468
|
+
SELECT r.*,
|
|
469
|
+
(SELECT COUNT(*) FROM flow_run_flows f WHERE f.run_seq = r.seq AND f.status = 'passed') AS flows_passed,
|
|
470
|
+
(SELECT COUNT(*) FROM flow_run_flows f WHERE f.run_seq = r.seq AND f.status = 'failed') AS flows_failed
|
|
471
|
+
FROM flow_runs r
|
|
472
|
+
${conds.length ? `WHERE ${conds.join(' AND ')}` : ''}
|
|
473
|
+
ORDER BY r.seq ${order}
|
|
474
|
+
LIMIT ?`;
|
|
475
|
+
const db = getDb();
|
|
476
|
+
const { before, after } = ids;
|
|
477
|
+
const page = after !== undefined
|
|
478
|
+
? db.prepare(sql(filtered('r.seq > ?'), 'ASC')).all(...args, after, limit).reverse()
|
|
479
|
+
: before !== undefined
|
|
480
|
+
? db.prepare(sql(filtered('r.seq < ?'), 'DESC')).all(...args, before, limit)
|
|
481
|
+
: db.prepare(sql(filtered(null), 'DESC')).all(...args, limit);
|
|
482
|
+
// A cursor only where a page exists past it, so the page's Newer and Older are offered only when they lead somewhere.
|
|
483
|
+
const exists = (cond, seq) => Boolean(db.prepare(`SELECT 1 FROM flow_runs r WHERE ${filtered(cond).join(' AND ')} LIMIT 1`).get(...args, seq));
|
|
484
|
+
const first = page[0];
|
|
485
|
+
const last = page[page.length - 1];
|
|
486
|
+
json(res, 200, {
|
|
487
|
+
items: page.map((r) => ({ ...runSummary(r), flowsPassed: r.flows_passed, flowsFailed: r.flows_failed })),
|
|
488
|
+
nextCursor: last && exists('r.seq < ?', last.seq) ? String(last.seq) : null,
|
|
489
|
+
prevCursor: first && exists('r.seq > ?', first.seq) ? String(first.seq) : null,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
export function handleGetRun(req, res, params, holders, videosOf = () => new Map()) {
|
|
493
|
+
if (!requireViewAuth(req, res))
|
|
494
|
+
return;
|
|
495
|
+
settleOrphanedRuns(holders);
|
|
496
|
+
const run = findRun(params.id);
|
|
497
|
+
if (!run)
|
|
498
|
+
return json(res, 404, { error: 'Run not found' });
|
|
499
|
+
const videos = videosOf(run);
|
|
500
|
+
const reported = new Map(getDb().prepare('SELECT * FROM flow_run_flows WHERE run_seq = ? ORDER BY idx').all(run.seq)
|
|
501
|
+
.map((f) => [f.idx, f]));
|
|
502
|
+
const planned = JSON.parse(run.flows_json);
|
|
503
|
+
const firstUnreported = planned.findIndex((_, i) => !reported.has(i));
|
|
504
|
+
const flows = planned.map((p, idx) => {
|
|
505
|
+
const f = reported.get(idx);
|
|
506
|
+
if (!f) {
|
|
507
|
+
// Not reported yet: the first is the one running now, the rest are waiting — unless the run is over.
|
|
508
|
+
const status = run.status !== 'running' ? 'not-run' : idx === firstUnreported ? 'running' : 'pending';
|
|
509
|
+
return { idx, name: p.name, file: p.file, status, video: videos.get(idx) ?? null };
|
|
510
|
+
}
|
|
511
|
+
return {
|
|
512
|
+
idx, name: f.name, file: f.file, status: f.status,
|
|
513
|
+
device: f.device_id ? { id: f.device_id, name: f.device_name, platform: f.platform } : null,
|
|
514
|
+
failureKind: f.failure_kind, failureMessage: f.failure_message, durationMs: f.duration_ms,
|
|
515
|
+
steps: JSON.parse(f.steps_json),
|
|
516
|
+
hasScreenshot: f.screenshot_file !== null,
|
|
517
|
+
video: videos.get(idx) ?? null,
|
|
518
|
+
};
|
|
519
|
+
});
|
|
520
|
+
// The session to watch is the one the holder holds **now**, never a stored one: session ids outlive runs,
|
|
521
|
+
// so a stored id would open whoever took the device next.
|
|
522
|
+
const watchSessionId = run.status === 'running' ? holders.heldSession(holderKeys(run.created_by, run.holder_client)) : null;
|
|
523
|
+
json(res, 200, { ...runSummary(run), flows, watchSessionId });
|
|
524
|
+
}
|
|
525
|
+
export function handleGetFlowScreenshot(req, res, params, screenshotsDir) {
|
|
526
|
+
if (!requireViewAuth(req, res))
|
|
527
|
+
return;
|
|
528
|
+
const row = getDb().prepare(`
|
|
529
|
+
SELECT f.screenshot_file, f.screenshot_mime FROM flow_run_flows f JOIN flow_runs r ON r.seq = f.run_seq
|
|
530
|
+
WHERE r.id = ? AND f.idx = ?
|
|
531
|
+
`).get(params.id, Number(params.idx));
|
|
532
|
+
if (!row?.screenshot_file)
|
|
533
|
+
return json(res, 404, { error: 'No screenshot' });
|
|
534
|
+
let data;
|
|
535
|
+
try {
|
|
536
|
+
data = fs.readFileSync(path.join(screenshotsDir, row.screenshot_file));
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
return json(res, 404, { error: 'No screenshot' });
|
|
540
|
+
}
|
|
541
|
+
res.writeHead(200, {
|
|
542
|
+
'Content-Type': row.screenshot_mime ?? 'image/png',
|
|
543
|
+
'Content-Length': data.length,
|
|
544
|
+
'X-Content-Type-Options': 'nosniff',
|
|
545
|
+
'Cache-Control': 'private, max-age=3600',
|
|
546
|
+
});
|
|
547
|
+
res.end(data);
|
|
548
|
+
}
|
|
549
|
+
// ── retention ───────────────────────────────────────────────────────────────
|
|
550
|
+
/** Runs with no build, past their own expiry. A build's runs go with the build (`deleteBuildsWithDependents`). */
|
|
551
|
+
export function purgeExpiredRuns(screenshotsDir, videosDir, holders) {
|
|
552
|
+
settleOrphanedRuns(holders);
|
|
553
|
+
const db = getDb();
|
|
554
|
+
const expired = db.prepare(`
|
|
555
|
+
SELECT seq FROM flow_runs WHERE build_id IS NULL AND delete_after IS NOT NULL AND delete_after < datetime('now')
|
|
556
|
+
`).all();
|
|
557
|
+
if (expired.length === 0)
|
|
558
|
+
return;
|
|
559
|
+
const files = [];
|
|
560
|
+
const videos = [];
|
|
561
|
+
db.transaction(() => {
|
|
562
|
+
const shots = db.prepare('SELECT screenshot_file FROM flow_run_flows WHERE run_seq = ? AND screenshot_file IS NOT NULL');
|
|
563
|
+
// Evicted videos have no file left; their rows go with the run (ON DELETE CASCADE).
|
|
564
|
+
const vids = db.prepare('SELECT file FROM flow_run_videos WHERE run_seq = ? AND evicted_at IS NULL');
|
|
565
|
+
const del = db.prepare('DELETE FROM flow_runs WHERE seq = ?');
|
|
566
|
+
for (const { seq } of expired) {
|
|
567
|
+
for (const s of shots.all(seq))
|
|
568
|
+
files.push(s.screenshot_file);
|
|
569
|
+
for (const v of vids.all(seq))
|
|
570
|
+
videos.push(v.file);
|
|
571
|
+
del.run(seq);
|
|
572
|
+
}
|
|
573
|
+
})();
|
|
574
|
+
for (const f of files)
|
|
575
|
+
unlinkSafe(path.join(screenshotsDir, f), 'run screenshot');
|
|
576
|
+
for (const f of videos)
|
|
577
|
+
unlinkSafe(path.join(videosDir, f), 'run video');
|
|
578
|
+
}
|
|
579
|
+
//# sourceMappingURL=runs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runs.js","sourceRoot":"","sources":["../../src/api/runs.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,IAAI,CAAA;AACnB,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACnC,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACzF,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAE9C,2GAA2G;AAC3G,yGAAyG;AACzG,4EAA4E;AAE5E,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,CAAA;AAClC,MAAM,oBAAoB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAA;AAC5C,MAAM,SAAS,GAAG,GAAG,CAAA;AACrB,MAAM,SAAS,GAAG,IAAI,CAAA;AACtB,MAAM,QAAQ,GAAG,GAAG,CAAA;AACpB,MAAM,WAAW,GAAG,IAAI,CAAA;AACxB,MAAM,gBAAgB,GAAG,IAAI,CAAA;AAC7B,MAAM,aAAa,GAAG,GAAG,CAAA;AACzB,MAAM,eAAe,GAAG,CAAC,CAAA;AACzB,MAAM,mBAAmB,GAAG,GAAG,CAAA;AAC/B,MAAM,oBAAoB,GAAG,GAAG,CAAA;AAChC,MAAM,YAAY,GAAG,EAAE,CAAA;AACvB,MAAM,QAAQ,GAAG,GAAG,CAAA;AACpB,2DAA2D;AAC3D,MAAM,kBAAkB,GAAG,CAAC,CAAA;AAC5B;;;;GAIG;AACH,MAAM,oBAAoB,GAAG,EAAE,CAAA;AAa/B;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,SAAwB,EAAE,MAAc;IACjE,OAAO,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,IAAI,MAAM,EAAE,EAAE,QAAQ,MAAM,EAAE,CAAC,CAAA;AAC/F,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,oBAAoB,CAAC,UAAkB;IACrD,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,oBAAoB,CAAC,CAAA;AACpD,CAAC;AAMD,SAAS,KAAK,CAAC,CAAU;IACvB,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;AACjE,CAAC;AAED,kFAAkF;AAClF,SAAS,GAAG,CAAC,CAAU,EAAE,GAAW;IAClC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACxD,OAAO,CAAC,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAC7C,CAAC;AAED;;;GAGG;AACH,SAAS,WAAW,CAAC,CAAU;IAC7B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,EAAE,CAAA;IAChC,MAAM,KAAK,GAAG,CAAC,CAAU,EAAE,GAAW,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;IAChG,MAAM,GAAG,GAAsF,EAAE,CAAA;IACjG,KAAK,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QAClB,IAAI,GAAG,CAAC,MAAM,KAAK,eAAe;YAAE,MAAK;QACzC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC;YAAE,SAAQ;QAC5C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAA;QAC/C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;QACtC,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,oBAAoB,CAAC,CAAA;QAC1D,MAAM,UAAU,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,oBAAoB,CAAC,CAAA;QACrH,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,IAAI,UAAU,KAAK,IAAI;YAAE,SAAQ;QAC5G,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;IACrF,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,SAAS,CAAC,CAAU;IAC3B,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AAC1E,CAAC;AAED,uGAAuG;AACvG,SAAS,OAAO,CAAC,CAAU;IACzB,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;IACtB,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAC3B,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAA;QACpB,OAAO,CAAC,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;IAChF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,GAAyB,EAAE,GAAwB;IAC7E,MAAM,GAAG,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE,cAAc,CAAC,CAAA;IACtD,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;QAAC,OAAO,IAAI,CAAA;IAAC,CAAC;IAC9E,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAA;QACzD,IAAI,KAAK,CAAC,MAAM,CAAC;YAAE,OAAO,MAAM,CAAA;IAClC,CAAC;IAAC,MAAM,CAAC,CAAC,oBAAoB,CAAC,CAAC;IAChC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,4BAA4B,EAAE,CAAC,CAAA;IACvD,OAAO,IAAI,CAAA;AACb,CAAC;AA4CD,MAAM,UAAU,OAAO,CAAC,EAAU;IAChC,OAAO,KAAK,EAAE,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAuB,CAAA;AAC9F,CAAC;AAED,+EAA+E;AAE/E,MAAM,OAAO,GAAG;;;;;4BAKY,CAAA;AAE5B;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE,OAAmB;IAClE,IAAI,CAAC,QAAQ,EAAE;QAAE,OAAM;IACvB,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IACpC,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;IACtC,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAA;IACzC,MAAM,EAAE,GAAG,KAAK,EAAE,CAAA;IAClB,MAAM,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM;QAC3B,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,qGAAqG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC;QAC/H,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,wHAAwH,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAC1F,CAAA;IACxE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAM;IAC7B,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,EAAE,CAAA;IACpC,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,OAAO,cAAc,CAAC,CAAA;IACjD,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IAC1F,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAmB;IACpD,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC,OAAO,CAAC;;;GAG/B,CAAC,CAAC,GAAG,CAAC,IAAI,oBAAoB,UAAU,CAAwE,CAAA;IACjH,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAM;IAChC,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,EAAE,CAAA;IACpC,MAAM,IAAI,GAAG,KAAK,EAAE,CAAC,OAAO,CAAC,GAAG,OAAO,cAAc,CAAC,CAAA;IACtD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;IAC1F,CAAC;AACH,CAAC;AAED,+EAA+E;AAE/E;;;;GAIG;AACH,MAAM,UAAU,MAAM,CACpB,GAAyB,EACzB,GAAwB,EACxB,EAAU;AACV;+GAC+G;AAC/G,uBAAuB,GAAG,CAAC;IAE3B,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACvC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IACtB,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAA;IAClD,MAAM,GAAG,GAAG,OAAO,CAAC,EAAE,CAAC,CAAA;IACvB,IAAI,CAAC,GAAG,EAAE,CAAC;QAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;QAAC,OAAO,IAAI,CAAA;IAAC,CAAC;IACrE,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,KAAK,IAAI,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,KAAK,CAAA;IAChE,IAAI,GAAG,CAAC,UAAU,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAC/C,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,uDAAuD,EAAE,CAAC,CAAA;QAClF,OAAO,IAAI,CAAA;IACb,CAAC;IACD,IAAI,GAAG,CAAC,WAAW,KAAK,QAAQ,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,uBAAuB,CAAC,EAAE,CAAC;QAClF,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC,CAAA;QACjD,OAAO,IAAI,CAAA;IACb,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,cAAc,CAAC,GAAgC,EAAE,OAAe;IAC9E,IAAI,OAAO,IAAI,CAAC,IAAI,GAAG,CAAC,WAAW,KAAK,IAAI;QAAE,OAAO,KAAK,CAAA;IAC1D,MAAM,GAAG,GAAG,KAAK,EAAE,CAAC,OAAO,CAAC,yCAAyC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,OAAO,UAAU,CAAuB,CAAA;IACxI,OAAO,GAAG,CAAC,MAAM,KAAK,CAAC,CAAA;AACzB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAyB,EAAE,GAAwB;IACvF,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACvC,IAAI,CAAC,IAAI;QAAE,OAAM;IACjB,IAAI,CAAC,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC;QAAE,OAAM;IAC7C,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACzC,IAAI,CAAC,IAAI;QAAE,OAAM;IAEjB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAA;IAC1B,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,GAAG,aAAa,EAAE,CAAC;QACvF,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,iDAAiD,EAAE,CAAC,CAAA;IACrF,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;QAChE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,qCAAqC,SAAS,EAAE,EAAE,CAAC,CAAA;IACpF,CAAC;IACD,MAAM,KAAK,GAA4C,EAAE,CAAA;IACzD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAkB,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QACpD,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAA;QAC9E,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,CAAE,CAAS,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAA;IAC5D,CAAC;IAED,IAAI,OAAO,GAAkB,IAAI,CAAA;IACjC,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QACxD,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;QACjC,IAAI,OAAO,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,mCAAmC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YAC3F,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAC,CAAA;QACnE,CAAC;IACH,CAAC;IAED,MAAM,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;IAC1C,MAAM,EAAE,GAAG,UAAU,EAAE,CAAA;IACvB,KAAK,EAAE,CAAC,OAAO,CAAC;;;;GAIf,CAAC,CAAC,GAAG,CACJ,EAAE,EAAE,OAAO,EAAE,MAAM,EACnB,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,IAAI,IAAI,EAC/B,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EACV,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAChC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,EACpC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAC/B,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAC9B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,EACnC,OAAO,EAAE,IAAI,kBAAkB,OAAO,CACvC,CAAA;IACD,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC,CAAA;AACxB,CAAC;AAED,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAA;AACnD,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAA;AAE9D,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,GAAyB,EACzB,GAAwB,EACxB,MAA8B;IAE9B,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC,CAAA;IACvC,IAAI,CAAC,GAAG;QAAE,OAAM;IAChB,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IAC9B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,IAAI,GAAG,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAA;IAC7H,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACzC,IAAI,CAAC,IAAI;QAAE,OAAM;IAEjB,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACvE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAC,CAAA;IACrE,CAAC;IACD,MAAM,UAAU,GAAG,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IAC7C,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,2CAA2C,EAAE,CAAC,CAAA;IACtG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;QAChE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,qCAAqC,SAAS,EAAE,EAAE,CAAC,CAAA;IACpF,CAAC;IACD,2EAA2E;IAC3E,MAAM,KAAK,GAA0I,EAAE,CAAA;IACvJ,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAkB,EAAE,CAAC;QACxC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;YAC9E,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,wDAAwD,EAAE,CAAC,CAAA;QAC5F,CAAC;QACD,MAAM,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAA;QAChD,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QACzE,KAAK,CAAC,IAAI,CAAC;YACT,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM;YACzC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE;YACjC,MAAM,EAAE,CAAC,CAAC,MAAM;YAChB,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;YACxC,GAAG,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACxC,GAAG,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5D,CAAC,CAAA;IACJ,CAAC;IACD,MAAM,OAAO,GAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAA6C,CAAC,GAAG,CAAC,CAAA;IAE5F,MAAM,EAAE,GAAG,KAAK,EAAE,CAAA;IAClB,wGAAwG;IACxG,iGAAiG;IACjG,0GAA0G;IAC1G,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAA+B,EAAE;QAC9D,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,iDAAiD,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAA+C,CAAA;QACpI,IAAI,CAAC,GAAG;YAAE,OAAO,MAAM,CAAA;QACvB,IAAI,GAAG,CAAC,WAAW,KAAK,QAAQ;YAAE,OAAO,UAAU,CAAA;QACnD,0GAA0G;QAC1G,EAAE,CAAC,OAAO,CAAC;;;;;;;;KAQV,CAAC,CAAC,GAAG,CACJ,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,EACxC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC,EACpF,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,cAAc,EAAE,WAAW,CAAC,EAC7E,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAClC,CAAA;QACD,EAAE,CAAC,OAAO,CAAC,uEAAuE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAChG,OAAO,IAAI,CAAA;IACb,CAAC,CAAC,EAAE,CAAA;IACJ,IAAI,OAAO,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAA;IACzE,IAAI,OAAO,KAAK,UAAU;QAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC,CAAA;IACpF,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;AAC9B,CAAC;AAED,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;AAC/E,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAA;AAElD,mGAAmG;AACnG,SAAS,SAAS,CAAC,GAAW;IAC5B,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,EAAE,CAAA;IAClG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,EAAE,CAAA;IACrG,OAAO,IAAI,CAAA;AACb,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,GAAyB,EACzB,GAAwB,EACxB,MAA8B,EAC9B,cAAsB;IAEtB,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC,CAAA;IACvC,IAAI,CAAC,GAAG;QAAE,OAAM;IAChB,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC,OAAO,CAAC,0EAA0E,CAAC,CAAA;IAC3G,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,uCAAuC,EAAE,CAAC,CAAA;IAExH,MAAM,GAAG,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAA;IAC5D,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,0BAA0B,oBAAoB,QAAQ,EAAE,CAAC,CAAA;IAC1G,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAA;IAC3B,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAC,CAAA;IAE7E,EAAE,CAAC,SAAS,CAAC,cAAc,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACjD,MAAM,QAAQ,GAAG,GAAG,UAAU,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IAC7C,MAAM,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAA;IACrE,0GAA0G;IAC1G,2GAA2G;IAC3G,mGAAmG;IACnG,yEAAyE;IACzE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAmD,CAAA;IACzG,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC,OAAO,CAAC,kGAAkG,CAAC;SAChI,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;IACxD,IAAI,OAAO,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;QAC1B,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,EAAE,gBAAgB,CAAC,CAAA;QACjE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAA;IACnD,CAAC;IACD,IAAI,MAAM,EAAE,eAAe;QAAE,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,eAAe,CAAC,EAAE,gBAAgB,CAAC,CAAA;IAC5G,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;AAC9B,CAAC;AAED,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAA;AAE/D,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAyB,EACzB,GAAwB,EACxB,MAA8B;IAE9B,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC,CAAA;IACvC,IAAI,CAAC,GAAG;QAAE,OAAM;IAChB,MAAM,IAAI,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACzC,IAAI,CAAC,IAAI;QAAE,OAAM;IACjB,IAAI,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QACxE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,0CAA0C,EAAE,CAAC,CAAA;IAC9E,CAAC;IACD,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACzC,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,yCAAyC,EAAE,CAAC,CAAA;IAClG,0GAA0G;IAC1G,uEAAuE;IACvE,8GAA8G;IAC9G,4FAA4F;IAC5F,MAAM,EAAE,GAAG,KAAK,EAAE,CAAA;IAClB,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC;;;;;GAK1B,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,WAAW,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAA;IACtG,IAAI,OAAO,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,CAAC,OAAO,CAAC,uCAAuC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC;YACrE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,sBAAsB,EAAE,CAAC;YACnD,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAA;IAChD,CAAC;IACD,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;AAC9B,CAAC;AAMD,SAAS,YAAY,CAAC,OAAsB;IAC1C,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IACjC,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC,OAAO,CAAC;;;GAGzB,CAAC,CAAC,GAAG,CAAC,OAAO,CAA2I,CAAA;IACzJ,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAA;IACnB,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC,YAAY,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAA;AAC1H,CAAC;AAED,SAAS,WAAW,CAAC,MAAqB;IACxC,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAChC,MAAM,CAAC,GAAG,KAAK,EAAE,CAAC,OAAO,CAAC,oDAAoD,CAAC,CAAC,GAAG,CAAC,MAAM,CAC9B,CAAA;IAC5D,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAA;IACnB,OAAO,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;AAChD,CAAC;AAED,SAAS,UAAU,CAAC,GAAW;IAC7B,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,UAAU,EAAE,GAAG,CAAC,WAAW;QAC3B,WAAW,EAAE,GAAG,CAAC,YAAY;QAC7B,YAAY,EAAE,GAAG,CAAC,aAAa;QAC/B,QAAQ,EAAE,GAAG,CAAC,SAAS;QACvB,IAAI,EAAE,GAAG,CAAC,KAAK,KAAK,CAAC;QACrB,EAAE,EAAE,GAAG,CAAC,KAAK,KAAK,CAAC;YACjB,CAAC,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,UAAU,EAAE;YACrG,CAAC,CAAC,IAAI;QACR,KAAK,EAAE,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC;QACjC,SAAS,EAAE,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC;QACtC,UAAU,EAAE,GAAG,CAAC,WAAW;QAC3B,SAAS,EAAE,GAAG,CAAC,UAAU;QACzB,UAAU,EAAE,GAAG,CAAC,WAAW;KAC5B,CAAA;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,GAAyB,EAAE,GAAwB,EAAE,OAAmB;IACrG,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC;QAAE,OAAM;IACtC,kBAAkB,CAAC,OAAO,CAAC,CAAA;IAE3B,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,UAAU,CAAC,CAAC,YAAY,CAAA;IAC1D,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,MAAM,IAAI,GAAwB,EAAE,CAAA;IACpC,kHAAkH;IAClH,iHAAiH;IACjH,kHAAkH;IAClH,2FAA2F;IAC3F,MAAM,GAAG,GAA0D,EAAE,CAAA;IACrE,KAAK,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,CAAU,EAAE,CAAC;QACxD,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACpB,IAAI,CAAC,CAAC;YAAE,SAAQ;QAChB,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;QACpB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC;YAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,GAAG,GAAG,iCAAiC,EAAE,CAAC,CAAA;QAC5H,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAA;IACf,CAAC;IACD,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IAAC,CAAC;IACnF,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAC9B,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QAAC,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAAC,CAAC;IACtE,MAAM,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IACtB,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;QAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;IAAC,CAAC;IAClF,MAAM,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAC1B,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,8GAA8G;QAC9G,qGAAqG;QACrG,sCAAsC;QACtC,KAAK,CAAC,IAAI,CAAC,oHAAoH,CAAC,CAAA;QAChI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACjB,CAAC;IACD,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAA;IACzC,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,YAAY,CAAA;IAE5G,kHAAkH;IAClH,gHAAgH;IAChH,yFAAyF;IACzF,MAAM,QAAQ,GAAG,CAAC,KAAoB,EAAE,EAAE,CAAC,CAAC,GAAG,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAChF,MAAM,GAAG,GAAG,CAAC,KAAe,EAAE,KAAqB,EAAE,EAAE,CAAC;;;;;OAKnD,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE;sBACnC,KAAK;aACd,CAAA;IAEX,MAAM,EAAE,GAAG,KAAK,EAAE,CAAA;IAClB,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,CAAA;IAC7B,MAAM,IAAI,GAAG,KAAK,KAAK,SAAS;QAC9B,CAAC,CAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,CAAW,CAAC,OAAO,EAAE;QAC/F,CAAC,CAAC,MAAM,KAAK,SAAS;YACpB,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,CAAU;YACrF,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,KAAK,CAAU,CAAA;IAE1E,sHAAsH;IACtH,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,GAAW,EAAE,EAAE,CAAC,OAAO,CACnD,EAAE,CAAC,OAAO,CAAC,mCAAmC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,GAAG,CAAC,CACxG,CAAA;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;IACrB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;IAClC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE;QACb,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC;QACxG,UAAU,EAAE,IAAI,IAAI,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI;QAC3E,UAAU,EAAE,KAAK,IAAI,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI;KAC/E,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,GAAyB,EACzB,GAAwB,EACxB,MAA8B,EAC9B,OAAmB,EACnB,WAAkD,GAAG,EAAE,CAAC,IAAI,GAAG,EAAE;IAEjE,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC;QAAE,OAAM;IACtC,kBAAkB,CAAC,OAAO,CAAC,CAAA;IAC3B,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IAC9B,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAA;IAC3D,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAA;IAE5B,MAAM,QAAQ,GAAG,IAAI,GAAG,CACrB,KAAK,EAAE,CAAC,OAAO,CAAC,6DAA6D,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAe;SACvG,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAC1B,CAAA;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAA4C,CAAA;IACrF,MAAM,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;IACrE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE;QACnC,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC3B,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,qGAAqG;YACrG,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,KAAK,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAA;YACrG,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAA;QACpF,CAAC;QACD,OAAO;YACL,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM;YACjD,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI;YAC3F,WAAW,EAAE,CAAC,CAAC,YAAY,EAAE,cAAc,EAAE,CAAC,CAAC,eAAe,EAAE,UAAU,EAAE,CAAC,CAAC,WAAW;YACzF,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAc;YAC5C,aAAa,EAAE,CAAC,CAAC,eAAe,KAAK,IAAI;YACzC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI;SAC/B,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,0GAA0G;IAC1G,0DAA0D;IAC1D,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC3H,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAA;AAC/D,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,GAAyB,EACzB,GAAwB,EACxB,MAA8B,EAC9B,cAAsB;IAEtB,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC;QAAE,OAAM;IACtC,MAAM,GAAG,GAAG,KAAK,EAAE,CAAC,OAAO,CAAC;;;GAG3B,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAmF,CAAA;IACvH,IAAI,CAAC,GAAG,EAAE,eAAe;QAAE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAA;IAC5E,IAAI,IAAY,CAAA;IAChB,IAAI,CAAC;QACH,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC,CAAA;IACxE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAA;IACnD,CAAC;IACD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;QACjB,cAAc,EAAE,GAAG,CAAC,eAAe,IAAI,WAAW;QAClD,gBAAgB,EAAE,IAAI,CAAC,MAAM;QAC7B,wBAAwB,EAAE,SAAS;QACnC,eAAe,EAAE,uBAAuB;KACzC,CAAC,CAAA;IACF,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AACf,CAAC;AAED,+EAA+E;AAE/E,kHAAkH;AAClH,MAAM,UAAU,gBAAgB,CAAC,cAAsB,EAAE,SAAiB,EAAE,OAAmB;IAC7F,kBAAkB,CAAC,OAAO,CAAC,CAAA;IAC3B,MAAM,EAAE,GAAG,KAAK,EAAE,CAAA;IAClB,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC;;GAE1B,CAAC,CAAC,GAAG,EAAuB,CAAA;IAC7B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAM;IAChC,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,MAAM,MAAM,GAAa,EAAE,CAAA;IAC3B,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE;QAClB,MAAM,KAAK,GAAG,EAAE,CAAC,OAAO,CAAC,8FAA8F,CAAC,CAAA;QACxH,oFAAoF;QACpF,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,CAAC,2EAA2E,CAAC,CAAA;QACpG,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,qCAAqC,CAAC,CAAA;QAC7D,KAAK,MAAM,EAAE,GAAG,EAAE,IAAI,OAAO,EAAE,CAAC;YAC9B,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,GAAG,CAAkC;gBAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,CAAA;YAC9F,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAuB;gBAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;YACxE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACd,CAAC;IACH,CAAC,CAAC,EAAE,CAAA;IACJ,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAA;IACjF,KAAK,MAAM,CAAC,IAAI,MAAM;QAAE,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,WAAW,CAAC,CAAA;AAC1E,CAAC"}
|
package/dist/api/settings.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import http from 'http';
|
|
2
|
-
export declare function handleGetSettings(req: http.IncomingMessage, res: http.ServerResponse): void;
|
|
2
|
+
export declare function handleGetSettings(req: http.IncomingMessage, res: http.ServerResponse, videosDir?: string): void;
|
|
3
3
|
export declare function handleUpdateSettings(req: http.IncomingMessage, res: http.ServerResponse, uploadsDir: string): void;
|
|
4
4
|
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/api/settings.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../../src/api/settings.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AAgBvB,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAsB/G;AAED,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,UAAU,EAAE,MAAM,GACjB,IAAI,CA+EN"}
|