@linxin666/dsh-remote-web-ui 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +9 -5
- package/README.zh.md +1 -1
- package/lib/client.js +84 -23
- package/lib/client.js.map +1 -1
- package/lib/index.js +173 -18
- package/lib/mobile.js +44 -25
- package/lib/mobile.js.map +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts +26 -11
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +30 -8
- package/lib/types/client/UpdatePanel.js +2 -0
- package/lib/types/client/locales.d.ts +4 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +4 -0
- package/lib/types/client/settings-form.d.ts +76 -5
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +97 -20
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +36 -10
- package/lib/types/mobile/mux.d.ts +13 -4
- package/lib/types/mobile/mux.d.ts.map +1 -1
- package/lib/types/mobile/mux.js +50 -30
- package/lib/types/mobile/mux.test.js +75 -6
- package/lib/types/mobile-api.d.ts +2 -0
- package/lib/types/mobile-api.d.ts.map +1 -1
- package/lib/types/mobile-api.js +4 -1
- package/lib/types/routes.d.ts +17 -0
- package/lib/types/routes.d.ts.map +1 -1
- package/lib/types/routes.js +49 -10
- package/lib/types/update.d.ts +44 -4
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +118 -8
- package/package.json +1 -1
- package/src/client/PluginSettingsCard.tsx +90 -18
- package/src/client/UpdatePanel.tsx +2 -0
- package/src/client/locales.ts +4 -0
- package/src/client/settings-card.module.css +9 -0
- package/src/client/settings-form.ts +153 -19
- package/src/index.ts +34 -10
- package/src/mobile/mux.test.ts +84 -6
- package/src/mobile/mux.ts +46 -25
- package/src/mobile-api.ts +6 -1
- package/src/routes.ts +51 -10
- package/src/update.ts +128 -9
package/lib/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { setInterval as setInterval$1 } from "node:timers";
|
|
|
3
3
|
import { Service } from "@deepseek-ai/cordis";
|
|
4
4
|
import z from "schemastery";
|
|
5
5
|
import { randomBytes } from "node:crypto";
|
|
6
|
+
import { z as z$1 } from "zod";
|
|
6
7
|
import { readFile } from "node:fs/promises";
|
|
7
8
|
import { existsSync, readFileSync } from "node:fs";
|
|
8
9
|
import { fileURLToPath } from "node:url";
|
|
@@ -638,6 +639,30 @@ const PAIR_PATHS = {
|
|
|
638
639
|
status: "/api/pair/status",
|
|
639
640
|
events: "/api/pair/events"
|
|
640
641
|
};
|
|
642
|
+
/**
|
|
643
|
+
* /api/pair request payload contracts. Each POST endpoint validates its body
|
|
644
|
+
* against one of these instead of reaching into a hand-parsed object: the
|
|
645
|
+
* control-plane endpoints that carry no meaningful payload use the permissive
|
|
646
|
+
* pairActionPayloadSchema so their smoke calls keep working unchanged, while
|
|
647
|
+
* issue/accept enforce their optional/required fields. Unknown (extra) keys
|
|
648
|
+
* are tolerated exactly as the previous manual reads ignored them.
|
|
649
|
+
*/
|
|
650
|
+
const issuePayloadSchema = z$1.object({
|
|
651
|
+
workspaceId: z$1.string().min(1).optional(),
|
|
652
|
+
address: z$1.string().min(1).optional()
|
|
653
|
+
});
|
|
654
|
+
const acceptPayloadSchema = z$1.object({ token: z$1.string().default("") });
|
|
655
|
+
const pairActionPayloadSchema = z$1.object({}).passthrough();
|
|
656
|
+
/**
|
|
657
|
+
* Parse a pair request body through schema. A missing/empty or non-object
|
|
658
|
+
* body is treated as an empty object (the same way the previous manual reads
|
|
659
|
+
* yielded absent fields), and a value that fails the schema returns
|
|
660
|
+
* `undefined` so the caller can answer with the existing error shape.
|
|
661
|
+
*/
|
|
662
|
+
function parsePairPayload(schema, body) {
|
|
663
|
+
const result = schema.safeParse(body ?? {});
|
|
664
|
+
return result.success ? result.data : void 0;
|
|
665
|
+
}
|
|
641
666
|
/** One JSON response. */
|
|
642
667
|
function writeJson$1(res, status, body) {
|
|
643
668
|
const payload = JSON.stringify(body);
|
|
@@ -762,8 +787,15 @@ function makeRoutes(deps) {
|
|
|
762
787
|
return;
|
|
763
788
|
}
|
|
764
789
|
const body = await readJsonBody$1(req);
|
|
765
|
-
const
|
|
766
|
-
|
|
790
|
+
const payload = parsePairPayload(issuePayloadSchema, body);
|
|
791
|
+
if (payload === void 0) {
|
|
792
|
+
writeJson$1(res, 400, {
|
|
793
|
+
ok: false,
|
|
794
|
+
code: "bad-payload"
|
|
795
|
+
});
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
const { workspaceId, address } = payload;
|
|
767
799
|
try {
|
|
768
800
|
const { token, expiresAt } = service.issue(workspaceId, address);
|
|
769
801
|
const base = address === void 0 ? service.publicBaseUrl ?? service.lanBaseUrl : service.lanBaseUrlFor(address);
|
|
@@ -801,8 +833,15 @@ function makeRoutes(deps) {
|
|
|
801
833
|
return;
|
|
802
834
|
}
|
|
803
835
|
const body = await readJsonBody$1(req);
|
|
804
|
-
const
|
|
805
|
-
|
|
836
|
+
const payload = parsePairPayload(acceptPayloadSchema, body);
|
|
837
|
+
if (payload === void 0) {
|
|
838
|
+
writeJson$1(res, 400, {
|
|
839
|
+
ok: false,
|
|
840
|
+
code: "bad-payload"
|
|
841
|
+
});
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
const result = service.accept(payload.token);
|
|
806
845
|
if (!result.ok) {
|
|
807
846
|
writeJson$1(res, result.code === "used" ? 409 : 404, {
|
|
808
847
|
ok: false,
|
|
@@ -828,7 +867,14 @@ function makeRoutes(deps) {
|
|
|
828
867
|
});
|
|
829
868
|
return;
|
|
830
869
|
}
|
|
831
|
-
await readJsonBody$1(req);
|
|
870
|
+
const body = await readJsonBody$1(req);
|
|
871
|
+
if (parsePairPayload(pairActionPayloadSchema, body) === void 0) {
|
|
872
|
+
writeJson$1(res, 400, {
|
|
873
|
+
ok: false,
|
|
874
|
+
code: "bad-payload"
|
|
875
|
+
});
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
832
878
|
service.stop();
|
|
833
879
|
writeJson$1(res, 200, { ok: true });
|
|
834
880
|
};
|
|
@@ -841,7 +887,14 @@ function makeRoutes(deps) {
|
|
|
841
887
|
});
|
|
842
888
|
return;
|
|
843
889
|
}
|
|
844
|
-
await readJsonBody$1(req);
|
|
890
|
+
const body = await readJsonBody$1(req);
|
|
891
|
+
if (parsePairPayload(pairActionPayloadSchema, body) === void 0) {
|
|
892
|
+
writeJson$1(res, 400, {
|
|
893
|
+
ok: false,
|
|
894
|
+
code: "bad-payload"
|
|
895
|
+
});
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
845
898
|
const deviceId = readCookie(req.headers.cookie, service.config.cookieName);
|
|
846
899
|
if (deviceId === void 0 || !service.heartbeat(deviceId)) {
|
|
847
900
|
writeJson$1(res, 401, {
|
|
@@ -1008,6 +1061,8 @@ const MOBILE_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
|
1008
1061
|
const MOBILE_PREFERENCES_METHOD = "mobile.preferences";
|
|
1009
1062
|
/** One session.list page (thin phones load incrementally). */
|
|
1010
1063
|
const SESSION_PAGE_SIZE = 20;
|
|
1064
|
+
/** SSE keep-alive ping cadence for the live mux stream (single connection). */
|
|
1065
|
+
const DEFAULT_EVENTS_HEARTBEAT_MS = 15e3;
|
|
1011
1066
|
/** Encode one list position as an opaque continuation cursor. */
|
|
1012
1067
|
function sessionListCursor(updatedAt, sessionId) {
|
|
1013
1068
|
return `${updatedAt}:${sessionId}`;
|
|
@@ -1040,6 +1095,7 @@ const MOBILE_API_METHOD_PREFIX = `${MOBILE_API_PREFIX}/`;
|
|
|
1040
1095
|
*/
|
|
1041
1096
|
function makeMobileApiRoutes(deps) {
|
|
1042
1097
|
const { service, apiProxy, mobileEnterToSend } = deps;
|
|
1098
|
+
const eventsHeartbeatMs = deps.eventsHeartbeatMs ?? DEFAULT_EVENTS_HEARTBEAT_MS;
|
|
1043
1099
|
/** The phone gate: a live paired-device cookie, or nothing else proceeds. */
|
|
1044
1100
|
const gateOk = (req) => {
|
|
1045
1101
|
const deviceId = readCookie(req.headers.cookie, service.config.cookieName);
|
|
@@ -1166,7 +1222,7 @@ function makeMobileApiRoutes(deps) {
|
|
|
1166
1222
|
try {
|
|
1167
1223
|
res.write(": ping\n\n");
|
|
1168
1224
|
} catch {}
|
|
1169
|
-
},
|
|
1225
|
+
}, eventsHeartbeatMs);
|
|
1170
1226
|
const onClose = () => {
|
|
1171
1227
|
if (closed) return;
|
|
1172
1228
|
closed = true;
|
|
@@ -1577,13 +1633,16 @@ function isLinkedSpec(spec) {
|
|
|
1577
1633
|
}
|
|
1578
1634
|
/**
|
|
1579
1635
|
* Resolve the anchor package's manifest path. The aggregate package is the
|
|
1580
|
-
* canonical entry point; this plugin's own package is the fallback.
|
|
1636
|
+
* canonical entry point; this plugin's own package is the fallback. Both a
|
|
1637
|
+
* throwing resolve and an undefined return mean "not installed" and move on
|
|
1638
|
+
* to the next candidate.
|
|
1581
1639
|
* @param resolve - a Node resolve implementation scoped to the host process.
|
|
1582
1640
|
* @returns the absolute manifest path, or undefined when neither is installed.
|
|
1583
1641
|
*/
|
|
1584
1642
|
function resolveAnchorManifest(resolve) {
|
|
1585
1643
|
for (const name of [AGGREGATE_PACKAGE, SELF_PACKAGE]) try {
|
|
1586
|
-
|
|
1644
|
+
const path = resolve(name + "/package.json");
|
|
1645
|
+
if (path !== void 0) return path;
|
|
1587
1646
|
} catch {}
|
|
1588
1647
|
}
|
|
1589
1648
|
/**
|
|
@@ -1645,14 +1704,21 @@ async function fetchLatestVersion(name, fetchImpl, timeoutMs = 1e4) {
|
|
|
1645
1704
|
return;
|
|
1646
1705
|
}
|
|
1647
1706
|
}
|
|
1707
|
+
/**
|
|
1708
|
+
* Sentinel for an installed version that could not be read (missing manifest
|
|
1709
|
+
* or resolve failure). It is a real-looking version so `checkUpdates` can
|
|
1710
|
+
* render the affected row, but the verified-update comparison must never
|
|
1711
|
+
* treat it as evidence — a read failure is not a version that "moved".
|
|
1712
|
+
*/
|
|
1713
|
+
const VERSION_UNKNOWN = "0.0.0";
|
|
1648
1714
|
/** The resolved current version of one family package (probe failure tolerated). */
|
|
1649
1715
|
function readInstalledVersion(resolve, name) {
|
|
1650
1716
|
try {
|
|
1651
1717
|
const path = resolve(name + "/package.json");
|
|
1652
1718
|
const version = path === void 0 ? void 0 : readManifest(path)?.version;
|
|
1653
|
-
return typeof version === "string" ? version :
|
|
1719
|
+
return typeof version === "string" ? version : VERSION_UNKNOWN;
|
|
1654
1720
|
} catch {
|
|
1655
|
-
return
|
|
1721
|
+
return VERSION_UNKNOWN;
|
|
1656
1722
|
}
|
|
1657
1723
|
}
|
|
1658
1724
|
/**
|
|
@@ -1744,13 +1810,18 @@ function runUpdate(deps) {
|
|
|
1744
1810
|
const candidates = [
|
|
1745
1811
|
{
|
|
1746
1812
|
command: "pnpm",
|
|
1747
|
-
args: [
|
|
1813
|
+
args: [
|
|
1814
|
+
"update",
|
|
1815
|
+
"--latest",
|
|
1816
|
+
...packages
|
|
1817
|
+
]
|
|
1748
1818
|
},
|
|
1749
1819
|
{
|
|
1750
1820
|
command: "corepack",
|
|
1751
1821
|
args: [
|
|
1752
1822
|
"pnpm",
|
|
1753
1823
|
"update",
|
|
1824
|
+
"--latest",
|
|
1754
1825
|
...packages
|
|
1755
1826
|
]
|
|
1756
1827
|
},
|
|
@@ -1760,6 +1831,7 @@ function runUpdate(deps) {
|
|
|
1760
1831
|
"--yes",
|
|
1761
1832
|
"pnpm",
|
|
1762
1833
|
"update",
|
|
1834
|
+
"--latest",
|
|
1763
1835
|
...packages
|
|
1764
1836
|
]
|
|
1765
1837
|
}
|
|
@@ -1856,6 +1928,70 @@ function runUpdate(deps) {
|
|
|
1856
1928
|
runCandidate(0);
|
|
1857
1929
|
});
|
|
1858
1930
|
}
|
|
1931
|
+
/**
|
|
1932
|
+
* Run the update, then verify the installed versions actually moved. pnpm
|
|
1933
|
+
* exits 0 even when it silently kept the installed versions — the pnpm 11
|
|
1934
|
+
* `minimumReleaseAge` gate refuses same-day releases by default — so a green
|
|
1935
|
+
* exit alone would report a misleading "update complete". Re-read the
|
|
1936
|
+
* installed versions afterwards and surface a `stale` failure (with the
|
|
1937
|
+
* captured pnpm output) when nothing moved, so the panel can tell the user
|
|
1938
|
+
* how to unblock the gate instead of claiming success.
|
|
1939
|
+
*
|
|
1940
|
+
* The stale decision anchors on the pre-run installed versions, not on the
|
|
1941
|
+
* registry latest: under a lenient gate pnpm may move 0.1.12 -> 0.1.13 while
|
|
1942
|
+
* latest stays 0.1.15 — that is a real update, not stale. The post-run anchor
|
|
1943
|
+
* path is re-resolved rather than reused from boot time: pnpm removes the old
|
|
1944
|
+
* version's .pnpm directory on update, so a boot-time captured path would
|
|
1945
|
+
* fail to read and collapse a successful update into a bogus 'missing'.
|
|
1946
|
+
*
|
|
1947
|
+
* A green exit whose verification has nothing to compare (registry probe
|
|
1948
|
+
* outage, missing anchor, non-npm mode) is reported as `verify-failed` — it
|
|
1949
|
+
* must never read as success. A green exit where no package moved but the
|
|
1950
|
+
* post-run check could not prove the install is fully current (a partial
|
|
1951
|
+
* probe failure hides whether a gate kept something back) is also
|
|
1952
|
+
* `verify-failed`: it must not collapse into a success either.
|
|
1953
|
+
* @param deps - the run and check seams.
|
|
1954
|
+
* @returns the run result; `stale` when exit 0 left every version in place,
|
|
1955
|
+
* `verify-failed` when the post-run check could not verify anything.
|
|
1956
|
+
*/
|
|
1957
|
+
async function runUpdateVerified(deps) {
|
|
1958
|
+
const before = /* @__PURE__ */ new Map();
|
|
1959
|
+
for (const name of deps.run.packages) {
|
|
1960
|
+
const version = readInstalledVersion(deps.check.resolve, name);
|
|
1961
|
+
if (version !== VERSION_UNKNOWN) before.set(name, version);
|
|
1962
|
+
}
|
|
1963
|
+
const result = await runUpdate(deps.run);
|
|
1964
|
+
if (!result.ok) return result;
|
|
1965
|
+
const anchorManifestPath = resolveAnchorManifest(deps.check.resolve) ?? deps.check.anchorManifestPath;
|
|
1966
|
+
const status = await checkUpdates({
|
|
1967
|
+
...deps.check,
|
|
1968
|
+
anchorManifestPath
|
|
1969
|
+
});
|
|
1970
|
+
if (status.error !== void 0 || status.mode !== "npm") return {
|
|
1971
|
+
...result,
|
|
1972
|
+
ok: false,
|
|
1973
|
+
errorCode: "verify-failed",
|
|
1974
|
+
error: "pnpm exited 0 but the post-run version check could not verify the install"
|
|
1975
|
+
};
|
|
1976
|
+
if (status.packages.some((packageStatus) => {
|
|
1977
|
+
const beforeVersion = before.get(packageStatus.name);
|
|
1978
|
+
if (beforeVersion === void 0 || packageStatus.current === VERSION_UNKNOWN) return false;
|
|
1979
|
+
return packageStatus.current !== beforeVersion;
|
|
1980
|
+
})) return result;
|
|
1981
|
+
if (status.outdated) return {
|
|
1982
|
+
...result,
|
|
1983
|
+
ok: false,
|
|
1984
|
+
errorCode: "stale",
|
|
1985
|
+
error: "pnpm exited 0 but the installed versions did not change"
|
|
1986
|
+
};
|
|
1987
|
+
if (status.packages.some((packageStatus) => packageStatus.latest === void 0)) return {
|
|
1988
|
+
...result,
|
|
1989
|
+
ok: false,
|
|
1990
|
+
errorCode: "verify-failed",
|
|
1991
|
+
error: "pnpm exited 0 but the post-run version check could not verify the install"
|
|
1992
|
+
};
|
|
1993
|
+
return result;
|
|
1994
|
+
}
|
|
1859
1995
|
//#endregion
|
|
1860
1996
|
//#region src/update-routes.ts
|
|
1861
1997
|
/** Route paths (exact matches under /api). */
|
|
@@ -2037,11 +2173,17 @@ function apply(ctx, config) {
|
|
|
2037
2173
|
const apiProxy = ctx.get("apiProxy");
|
|
2038
2174
|
if (apiProxy === void 0) console.warn("remote-web-ui: apiProxy service unavailable — the mobile data channel is disabled");
|
|
2039
2175
|
const requireFromHost = createRequire(import.meta.url);
|
|
2040
|
-
const
|
|
2176
|
+
const resolveAnchorPath = () => resolveAnchorManifest((specifier) => {
|
|
2177
|
+
try {
|
|
2178
|
+
return requireFromHost.resolve(specifier);
|
|
2179
|
+
} catch {
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
});
|
|
2041
2183
|
const updateRoutes = makeUpdateRoutes({
|
|
2042
2184
|
fence: (request) => isTrustedApiRequest(request, []),
|
|
2043
2185
|
check: () => checkUpdates({
|
|
2044
|
-
anchorManifestPath,
|
|
2186
|
+
anchorManifestPath: resolveAnchorPath(),
|
|
2045
2187
|
resolve: (specifier) => {
|
|
2046
2188
|
try {
|
|
2047
2189
|
return requireFromHost.resolve(specifier);
|
|
@@ -2052,7 +2194,7 @@ function apply(ctx, config) {
|
|
|
2052
2194
|
fetchLatest: (name) => fetchLatestVersion(name, fetch)
|
|
2053
2195
|
}),
|
|
2054
2196
|
run: async () => {
|
|
2055
|
-
const target = resolveUpdateTarget({ anchorManifestPath });
|
|
2197
|
+
const target = resolveUpdateTarget({ anchorManifestPath: resolveAnchorPath() });
|
|
2056
2198
|
if ("error" in target) {
|
|
2057
2199
|
const code = target.error;
|
|
2058
2200
|
return {
|
|
@@ -2063,9 +2205,22 @@ function apply(ctx, config) {
|
|
|
2063
2205
|
errorCode: code
|
|
2064
2206
|
};
|
|
2065
2207
|
}
|
|
2066
|
-
return
|
|
2067
|
-
|
|
2068
|
-
|
|
2208
|
+
return runUpdateVerified({
|
|
2209
|
+
run: {
|
|
2210
|
+
profileDir: target.profileDir,
|
|
2211
|
+
packages: target.packages
|
|
2212
|
+
},
|
|
2213
|
+
check: {
|
|
2214
|
+
anchorManifestPath: resolveAnchorPath(),
|
|
2215
|
+
resolve: (specifier) => {
|
|
2216
|
+
try {
|
|
2217
|
+
return requireFromHost.resolve(specifier);
|
|
2218
|
+
} catch {
|
|
2219
|
+
return;
|
|
2220
|
+
}
|
|
2221
|
+
},
|
|
2222
|
+
fetchLatest: (name) => fetchLatestVersion(name, fetch)
|
|
2223
|
+
}
|
|
2069
2224
|
});
|
|
2070
2225
|
}
|
|
2071
2226
|
});
|
package/lib/mobile.js
CHANGED
|
@@ -12770,6 +12770,12 @@ function browserSource(url) {
|
|
|
12770
12770
|
}
|
|
12771
12771
|
const DEFAULT_POLL_INTERVAL_MS = 3e3;
|
|
12772
12772
|
const DEFAULT_STALL_THRESHOLD_MS = 12e3;
|
|
12773
|
+
/**
|
|
12774
|
+
* Stall-check granularity: the single scheduler tick runs at least this
|
|
12775
|
+
* often so fallback arms within a second of the stall threshold passing,
|
|
12776
|
+
* while the poll cadence itself stays {@link MuxClientOptions.pollIntervalMs}.
|
|
12777
|
+
*/
|
|
12778
|
+
const STALL_CHECK_MS = 1e3;
|
|
12773
12779
|
/** Poll window: enough recent events to cover a few seconds of agent output. */
|
|
12774
12780
|
const DEFAULT_POLL_PAGE_SIZE = 50;
|
|
12775
12781
|
/**
|
|
@@ -12800,9 +12806,11 @@ var MuxClient = class {
|
|
|
12800
12806
|
sseAlive = false;
|
|
12801
12807
|
/** Per-session highest event seq already emitted, for poll dedup. */
|
|
12802
12808
|
pollWatermark = /* @__PURE__ */ new Map();
|
|
12803
|
-
|
|
12804
|
-
|
|
12809
|
+
/** Single scheduler tick: both the stall check and the poll cadence ride this one interval. */
|
|
12810
|
+
tickTimer;
|
|
12805
12811
|
polling = false;
|
|
12812
|
+
/** Epoch ms of the next due poll while polling (kept on the same tick timer). */
|
|
12813
|
+
nextPollAt = 0;
|
|
12806
12814
|
/**
|
|
12807
12815
|
* @param url - the mobile events endpoint (browser-relative).
|
|
12808
12816
|
* @param options - seams.
|
|
@@ -12820,15 +12828,16 @@ var MuxClient = class {
|
|
|
12820
12828
|
this.stopped = false;
|
|
12821
12829
|
this.lastDataAt = this.now();
|
|
12822
12830
|
if (this.source === void 0) this.connect();
|
|
12823
|
-
this.
|
|
12831
|
+
this.startTick();
|
|
12824
12832
|
}
|
|
12825
12833
|
/** Close for good. */
|
|
12826
12834
|
stop() {
|
|
12827
12835
|
this.stopped = true;
|
|
12828
|
-
this.
|
|
12836
|
+
this.stopTick();
|
|
12829
12837
|
this.stopPolling();
|
|
12830
12838
|
this.closeSource();
|
|
12831
12839
|
this.observeSessionId = void 0;
|
|
12840
|
+
this.nextPollAt = 0;
|
|
12832
12841
|
}
|
|
12833
12842
|
/** Subscribe to validated frames; returns an unsubscribe function. */
|
|
12834
12843
|
onFrame(listener) {
|
|
@@ -12866,36 +12875,46 @@ var MuxClient = class {
|
|
|
12866
12875
|
if (this.observeSessionId !== void 0) this.startPolling();
|
|
12867
12876
|
};
|
|
12868
12877
|
}
|
|
12869
|
-
|
|
12870
|
-
|
|
12871
|
-
|
|
12872
|
-
|
|
12873
|
-
|
|
12874
|
-
|
|
12875
|
-
|
|
12876
|
-
|
|
12877
|
-
|
|
12878
|
-
|
|
12879
|
-
|
|
12880
|
-
|
|
12881
|
-
|
|
12882
|
-
|
|
12878
|
+
/**
|
|
12879
|
+
* The single scheduler tick is the only interval this client owns. One
|
|
12880
|
+
* timer (at the finer of the stall-check and poll cadences) both arms the
|
|
12881
|
+
* polling fallback once the stall threshold passes and drives each poll at
|
|
12882
|
+
* {@link MuxClientOptions.pollIntervalMs} — one timer instead of two.
|
|
12883
|
+
*/
|
|
12884
|
+
startTick() {
|
|
12885
|
+
if (this.tickTimer !== void 0) return;
|
|
12886
|
+
const cadence = Math.min(this.pollIntervalMs, STALL_CHECK_MS);
|
|
12887
|
+
this.tickTimer = setInterval(() => {
|
|
12888
|
+
this.tick();
|
|
12889
|
+
}, cadence);
|
|
12890
|
+
}
|
|
12891
|
+
stopTick() {
|
|
12892
|
+
if (this.tickTimer !== void 0) {
|
|
12893
|
+
clearInterval(this.tickTimer);
|
|
12894
|
+
this.tickTimer = void 0;
|
|
12895
|
+
}
|
|
12896
|
+
}
|
|
12897
|
+
tick() {
|
|
12898
|
+
if (this.stopped) return;
|
|
12899
|
+
if (this.observeSessionId === void 0) return;
|
|
12900
|
+
if (this.polling) {
|
|
12901
|
+
if (this.now() >= this.nextPollAt) {
|
|
12902
|
+
this.nextPollAt = this.now() + this.pollIntervalMs;
|
|
12903
|
+
this.pollTick();
|
|
12904
|
+
}
|
|
12905
|
+
return;
|
|
12883
12906
|
}
|
|
12907
|
+
if (this.sseAlive) return;
|
|
12908
|
+
if (this.now() - this.lastDataAt > this.stallThresholdMs) this.startPolling();
|
|
12884
12909
|
}
|
|
12885
12910
|
startPolling() {
|
|
12886
12911
|
if (this.polling || this.stopped) return;
|
|
12887
12912
|
this.polling = true;
|
|
12913
|
+
this.nextPollAt = this.now() + this.pollIntervalMs;
|
|
12888
12914
|
this.pollTick();
|
|
12889
|
-
this.pollTimer = setInterval(() => {
|
|
12890
|
-
this.pollTick();
|
|
12891
|
-
}, this.pollIntervalMs);
|
|
12892
12915
|
}
|
|
12893
12916
|
stopPolling() {
|
|
12894
12917
|
this.polling = false;
|
|
12895
|
-
if (this.pollTimer !== void 0) {
|
|
12896
|
-
clearInterval(this.pollTimer);
|
|
12897
|
-
this.pollTimer = void 0;
|
|
12898
|
-
}
|
|
12899
12918
|
}
|
|
12900
12919
|
/**
|
|
12901
12920
|
* Fetch the latest history page for the observed session and re-emit any
|