tokmon 0.23.4 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -5
- package/dist/{bootstrap-ink-BIOLTP7F.js → bootstrap-ink-HL2BYNW3.js} +37 -292
- package/dist/{chunk-RM3ZE44Z.js → chunk-3TJVFKXV.js} +1 -1
- package/dist/chunk-42H7R376.js +298 -0
- package/dist/chunk-45ZP755H.js +275 -0
- package/dist/{chunk-DJPUYMZM.js → chunk-5CWOJMAH.js} +2 -0
- package/dist/{chunk-2AFQKH5O.js → chunk-O27I2XFN.js} +278 -539
- package/dist/{chunk-2K4L4QQK.js → chunk-REO4O5VE.js} +68 -79
- package/dist/{chunk-Q2DSDWO2.js → chunk-SMPY52EV.js} +1 -30
- package/dist/{daemon-handle-6F2IB6CU.js → chunk-W2WGBXZG.js} +8 -9
- package/dist/cli-command-TLGIGF63.js +526 -0
- package/dist/cli.js +30 -5
- package/dist/{config-5CNJN3P7.js → config-QC5QSP3G.js} +1 -1
- package/dist/{daemon-PP3QAT6E.js → daemon-F4EGY77J.js} +43 -17
- package/dist/daemon-handle-NLCLYWFU.js +10 -0
- package/dist/server-X63FGHIZ.js +11 -0
- package/dist/web/assets/{Area-BALPhJJp.js → Area-DCES17aZ.js} +1 -1
- package/dist/web/assets/{analytics-uHVeOhLh.js → analytics-DDW6zcnj.js} +2 -2
- package/dist/web/assets/{breakdown-CR5dNNSG.js → breakdown-D5LPPtqh.js} +1 -1
- package/dist/web/assets/{chart-DRkqw0fm.js → chart-bHY7MQV0.js} +1 -1
- package/dist/web/assets/{explore-C-bHaX-I.js → explore-DdWB-Pu7.js} +1 -1
- package/dist/web/assets/index-C9jxb2o3.js +83 -0
- package/dist/web/assets/index-m0KfWnN2.css +1 -0
- package/dist/web/assets/{models-Qc2daHCo.js → models-o1wJnG6w.js} +2 -2
- package/dist/web/assets/{overview-D4VG2awg.js → overview-wq2uAEk4.js} +2 -2
- package/dist/web/assets/{panel-C1CsBs1H.js → panel-DBnK1oy0.js} +1 -1
- package/dist/web/assets/{primitives-S4UDgnZq.js → primitives-CBmteetD.js} +1 -1
- package/dist/web/assets/settings-sheet-CZ6GEPl3.js +1 -0
- package/dist/web/assets/{share-sheet-BpZBju-k.js → share-sheet-DUee3FlK.js} +1 -1
- package/dist/web/assets/{timeline-Cp-fyeuK.js → timeline-BxHwOJA_.js} +1 -1
- package/dist/web/assets/{use-dialog-trap-DaKbCG1Z.js → use-dialog-trap-30EqxYF-.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/server-UHQEOVQH.js +0 -10
- package/dist/web/assets/index-C1jQdagn.js +0 -83
- package/dist/web/assets/index-DSj-Bxov.css +0 -1
- package/dist/web/assets/settings-sheet-DHi4Dt2z.js +0 -1
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
appVersion,
|
|
4
|
-
browserUrl,
|
|
5
4
|
findWebRoot,
|
|
6
5
|
send,
|
|
7
6
|
sendJson,
|
|
8
7
|
serveStatic
|
|
9
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-SMPY52EV.js";
|
|
9
|
+
import {
|
|
10
|
+
buildAccounts,
|
|
11
|
+
colorHex,
|
|
12
|
+
fetchPeak,
|
|
13
|
+
namedHex
|
|
14
|
+
} from "./chunk-42H7R376.js";
|
|
10
15
|
import {
|
|
11
16
|
PROVIDERS,
|
|
12
17
|
TOKMON_CAPABILITIES,
|
|
@@ -14,14 +19,10 @@ import {
|
|
|
14
19
|
TOKMON_WS_METHODS,
|
|
15
20
|
TOKMON_WS_PATH,
|
|
16
21
|
TokmonRpcGroup,
|
|
17
|
-
buildAccounts,
|
|
18
|
-
colorHex,
|
|
19
22
|
detectProviders,
|
|
20
|
-
fetchPeak,
|
|
21
|
-
namedHex,
|
|
22
23
|
resolveTimezone,
|
|
23
24
|
withTimeout
|
|
24
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-O27I2XFN.js";
|
|
25
26
|
import {
|
|
26
27
|
cacheDir,
|
|
27
28
|
expandHome,
|
|
@@ -29,11 +30,11 @@ import {
|
|
|
29
30
|
normalizeConfig,
|
|
30
31
|
saveConfig,
|
|
31
32
|
snapshotCacheFile
|
|
32
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-5CWOJMAH.js";
|
|
33
34
|
|
|
34
35
|
// src/web/server.ts
|
|
35
36
|
import { createServer } from "http";
|
|
36
|
-
import { randomBytes, timingSafeEqual
|
|
37
|
+
import { randomBytes, timingSafeEqual } from "crypto";
|
|
37
38
|
|
|
38
39
|
// src/web/data.ts
|
|
39
40
|
async function resolveAccounts(config) {
|
|
@@ -676,7 +677,6 @@ async function applyConfigUpdate(engine, state, input) {
|
|
|
676
677
|
}
|
|
677
678
|
|
|
678
679
|
// src/web/ws.ts
|
|
679
|
-
import { timingSafeEqual } from "crypto";
|
|
680
680
|
import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
|
|
681
681
|
import { NodeWS } from "@effect/platform-node/NodeSocket";
|
|
682
682
|
import { Effect, Exit, Layer, Queue, Scope, Stream } from "effect";
|
|
@@ -743,40 +743,55 @@ async function listHomeDirectory(rawPath) {
|
|
|
743
743
|
return { path: abs, parent: parentFor(root, abs), entries };
|
|
744
744
|
}
|
|
745
745
|
|
|
746
|
-
// src/web/
|
|
746
|
+
// src/web/request-guard.ts
|
|
747
|
+
import { isIP } from "net";
|
|
747
748
|
function header(req, name) {
|
|
748
749
|
const value = req.headers[name.toLowerCase()];
|
|
749
750
|
return Array.isArray(value) ? value[0] : value;
|
|
750
751
|
}
|
|
751
|
-
function
|
|
752
|
-
if (!value) return
|
|
753
|
-
const host = value.trim().toLowerCase();
|
|
754
|
-
if (!host) return false;
|
|
755
|
-
return /^(?:127\.0\.0\.1|localhost)(?::\d{1,5})?$/.test(host) || /^\[::1\](?::\d{1,5})?$/.test(host);
|
|
756
|
-
}
|
|
757
|
-
function isWsPath(req) {
|
|
752
|
+
function hostnameFromHost(value) {
|
|
753
|
+
if (!value) return null;
|
|
758
754
|
try {
|
|
759
|
-
|
|
755
|
+
const url = new URL(`http://${value.trim()}`);
|
|
756
|
+
if (url.username || url.password || url.pathname !== "/" || url.search || url.hash) return null;
|
|
757
|
+
return url.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
760
758
|
} catch {
|
|
761
|
-
return
|
|
759
|
+
return null;
|
|
762
760
|
}
|
|
763
761
|
}
|
|
764
|
-
function
|
|
762
|
+
function isLoopbackHostHeader(value) {
|
|
763
|
+
const hostname = hostnameFromHost(value);
|
|
764
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
|
|
765
|
+
}
|
|
766
|
+
function isAllowedHostHeader(value, allowNetworkAccess) {
|
|
767
|
+
if (isLoopbackHostHeader(value)) return true;
|
|
768
|
+
if (!allowNetworkAccess) return false;
|
|
769
|
+
const hostname = hostnameFromHost(value);
|
|
770
|
+
return hostname !== null && isIP(hostname) !== 0;
|
|
771
|
+
}
|
|
772
|
+
function isSameOriginRequest(req) {
|
|
773
|
+
const origin = header(req, "origin");
|
|
774
|
+
if (!origin || origin === "null") return true;
|
|
775
|
+
const host = header(req, "host")?.trim().toLowerCase();
|
|
776
|
+
if (!host) return false;
|
|
765
777
|
try {
|
|
766
|
-
|
|
778
|
+
const url = new URL(origin);
|
|
779
|
+
return (url.protocol === "http:" || url.protocol === "https:") && url.host.toLowerCase() === host;
|
|
767
780
|
} catch {
|
|
768
|
-
return
|
|
781
|
+
return false;
|
|
769
782
|
}
|
|
770
783
|
}
|
|
771
|
-
function
|
|
772
|
-
|
|
773
|
-
const left = Buffer.from(actual);
|
|
774
|
-
const right = Buffer.from(expected);
|
|
775
|
-
return left.length === right.length && timingSafeEqual(left, right);
|
|
784
|
+
function isAllowedLocalRequest(req, allowNetworkAccess) {
|
|
785
|
+
return isAllowedHostHeader(header(req, "host"), allowNetworkAccess) && isSameOriginRequest(req);
|
|
776
786
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
787
|
+
|
|
788
|
+
// src/web/ws.ts
|
|
789
|
+
function isWsPath(req) {
|
|
790
|
+
try {
|
|
791
|
+
return new URL(req.url ?? "/", "http://127.0.0.1").pathname === TOKMON_WS_PATH;
|
|
792
|
+
} catch {
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
780
795
|
}
|
|
781
796
|
function rejectUpgrade(socket, status = 403, message = "Forbidden") {
|
|
782
797
|
try {
|
|
@@ -855,15 +870,11 @@ async function mountWsRpc(server, deps) {
|
|
|
855
870
|
)
|
|
856
871
|
);
|
|
857
872
|
const onUpgrade = (req, socket, head) => {
|
|
858
|
-
if (!
|
|
873
|
+
if (!isAllowedLocalRequest(req, deps.state.config.allowNetworkAccess)) {
|
|
859
874
|
rejectUpgrade(socket);
|
|
860
875
|
return;
|
|
861
876
|
}
|
|
862
877
|
if (!isWsPath(req)) return;
|
|
863
|
-
if (!isAuthorized(req, deps.wsToken)) {
|
|
864
|
-
rejectUpgrade(socket);
|
|
865
|
-
return;
|
|
866
|
-
}
|
|
867
878
|
upgradeHandler(req, socket, head);
|
|
868
879
|
};
|
|
869
880
|
server.prependListener("upgrade", onUpgrade);
|
|
@@ -900,59 +911,38 @@ async function mountWsRpc(server, deps) {
|
|
|
900
911
|
}
|
|
901
912
|
|
|
902
913
|
// src/web/server.ts
|
|
903
|
-
var
|
|
914
|
+
var LOOPBACK_HOST = "127.0.0.1";
|
|
915
|
+
var NETWORK_HOST = "0.0.0.0";
|
|
904
916
|
var DEFAULT_PORT = 4317;
|
|
905
917
|
var MAX_PORT_TRIES = 20;
|
|
906
|
-
function isLoopbackHostHeader(value) {
|
|
907
|
-
if (!value) return false;
|
|
908
|
-
const host = value.trim().toLowerCase();
|
|
909
|
-
return /^(?:127\.0\.0\.1|localhost)(?::\d{1,5})?$/.test(host) || /^\[::1\](?::\d{1,5})?$/.test(host);
|
|
910
|
-
}
|
|
911
|
-
function isLoopbackOrigin(origin) {
|
|
912
|
-
if (!origin || origin === "null") return true;
|
|
913
|
-
try {
|
|
914
|
-
const u = new URL(origin);
|
|
915
|
-
return isLoopbackHostHeader(u.host);
|
|
916
|
-
} catch {
|
|
917
|
-
return false;
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
918
|
function header2(req, name) {
|
|
921
919
|
const value = req.headers[name];
|
|
922
920
|
return Array.isArray(value) ? value[0] : value;
|
|
923
921
|
}
|
|
924
|
-
function
|
|
922
|
+
function tokenMatches(given, expected) {
|
|
925
923
|
if (!given) return false;
|
|
926
924
|
const actual = Buffer.from(given);
|
|
927
925
|
const token = Buffer.from(expected);
|
|
928
|
-
return actual.length === token.length &&
|
|
926
|
+
return actual.length === token.length && timingSafeEqual(actual, token);
|
|
929
927
|
}
|
|
930
|
-
function guardHost(req, res) {
|
|
931
|
-
if (
|
|
928
|
+
function guardHost(req, res, allowNetworkAccess) {
|
|
929
|
+
if (isAllowedHostHeader(header2(req, "host"), allowNetworkAccess)) return true;
|
|
932
930
|
sendJson(res, 403, { error: "forbidden" });
|
|
933
931
|
return false;
|
|
934
932
|
}
|
|
935
|
-
function
|
|
936
|
-
|
|
937
|
-
const v = req.headers[n];
|
|
938
|
-
return Array.isArray(v) ? v[0] : v;
|
|
939
|
-
};
|
|
940
|
-
if (!tokenMatches2(header3("x-tokmon-token"), wsToken2)) {
|
|
941
|
-
sendJson(res, 403, { error: "forbidden" });
|
|
942
|
-
return false;
|
|
943
|
-
}
|
|
944
|
-
if (!isLoopbackOrigin(header3("origin"))) {
|
|
933
|
+
function guardSameOrigin(req, res) {
|
|
934
|
+
if (!isSameOriginRequest(req)) {
|
|
945
935
|
sendJson(res, 403, { error: "forbidden" });
|
|
946
936
|
return false;
|
|
947
937
|
}
|
|
948
938
|
return true;
|
|
949
939
|
}
|
|
950
|
-
function createRouter(engine, state, vite, webRoot,
|
|
940
|
+
function createRouter(engine, state, vite, webRoot, wsToken, version) {
|
|
951
941
|
return (req, res) => {
|
|
952
942
|
const url = req.url || "/";
|
|
953
943
|
const path = url.split("?")[0];
|
|
954
944
|
const method = req.method || "GET";
|
|
955
|
-
if (!guardHost(req, res)) return;
|
|
945
|
+
if (!guardHost(req, res, state.config.allowNetworkAccess)) return;
|
|
956
946
|
if (path === "/api/data") {
|
|
957
947
|
engine.touch();
|
|
958
948
|
sendJson(res, 200, engine.snapshot() ?? { pending: true });
|
|
@@ -964,12 +954,12 @@ function createRouter(engine, state, vite, webRoot, wsToken2, version) {
|
|
|
964
954
|
ready: engine.snapshot() !== null,
|
|
965
955
|
version,
|
|
966
956
|
// Discovery requires this proof; public health checks retain their useful 200 response.
|
|
967
|
-
owner:
|
|
957
|
+
owner: tokenMatches(header2(req, "x-tokmon-token"), wsToken)
|
|
968
958
|
});
|
|
969
959
|
return;
|
|
970
960
|
}
|
|
971
961
|
if (path === "/api/config") {
|
|
972
|
-
if (!
|
|
962
|
+
if (!guardSameOrigin(req, res)) return;
|
|
973
963
|
if (method === "GET") {
|
|
974
964
|
sendJson(res, 200, state.config);
|
|
975
965
|
return;
|
|
@@ -996,7 +986,7 @@ async function startWebServer(opts) {
|
|
|
996
986
|
const version = appVersion();
|
|
997
987
|
const summaryIntervalMs = summaryIntervalFor(state.config);
|
|
998
988
|
const billingIntervalMs = billingIntervalFor(state.config);
|
|
999
|
-
const
|
|
989
|
+
const wsToken = opts.wsToken ?? randomBytes(32).toString("base64url");
|
|
1000
990
|
const log = (msg) => {
|
|
1001
991
|
if (opts.log) process.stdout.write(msg + "\n");
|
|
1002
992
|
};
|
|
@@ -1010,10 +1000,11 @@ async function startWebServer(opts) {
|
|
|
1010
1000
|
const webRoot = vite ? null : findWebRoot();
|
|
1011
1001
|
if (!vite && !webRoot) log(" \u26A0 no dashboard available \u2014 see the page for build/dev instructions");
|
|
1012
1002
|
engine = createDataEngine({ version, config: state.config, tz, summaryIntervalMs, billingIntervalMs, resolved });
|
|
1013
|
-
server.addListener("request", createRouter(engine, state, vite, webRoot,
|
|
1014
|
-
closeWsRpc = await mountWsRpc(server, { engine, state
|
|
1015
|
-
const
|
|
1016
|
-
const
|
|
1003
|
+
server.addListener("request", createRouter(engine, state, vite, webRoot, wsToken, version));
|
|
1004
|
+
closeWsRpc = await mountWsRpc(server, { engine, state });
|
|
1005
|
+
const bindHost = state.config.allowNetworkAccess ? NETWORK_HOST : LOOPBACK_HOST;
|
|
1006
|
+
const port = await listenWithFallback(server, opts.port ?? DEFAULT_PORT, bindHost);
|
|
1007
|
+
const serverUrl = `http://${LOOPBACK_HOST}:${port}`;
|
|
1017
1008
|
if (vite?.warmupRequest) {
|
|
1018
1009
|
try {
|
|
1019
1010
|
await Promise.race([vite.warmupRequest("/src/main.tsx"), delay(5e3)]);
|
|
@@ -1024,9 +1015,7 @@ async function startWebServer(opts) {
|
|
|
1024
1015
|
let stopped = false;
|
|
1025
1016
|
return {
|
|
1026
1017
|
url: serverUrl,
|
|
1027
|
-
browserUrl: browserUrl(serverUrl, wsToken2),
|
|
1028
1018
|
port,
|
|
1029
|
-
wsToken: wsToken2,
|
|
1030
1019
|
snapshot: engine.snapshot,
|
|
1031
1020
|
config: () => state.config,
|
|
1032
1021
|
stop: async () => {
|
|
@@ -1078,11 +1067,11 @@ function closeServer(server, timeoutMs = 1e3) {
|
|
|
1078
1067
|
}
|
|
1079
1068
|
});
|
|
1080
1069
|
}
|
|
1081
|
-
function listenWithFallback(server, startPort) {
|
|
1070
|
+
function listenWithFallback(server, startPort, host) {
|
|
1082
1071
|
return new Promise((resolve, reject) => {
|
|
1083
1072
|
if (startPort === 0) {
|
|
1084
1073
|
server.once("error", reject);
|
|
1085
|
-
server.listen(0,
|
|
1074
|
+
server.listen(0, host, () => {
|
|
1086
1075
|
const addr = server.address();
|
|
1087
1076
|
resolve(typeof addr === "object" && addr ? addr.port : 0);
|
|
1088
1077
|
});
|
|
@@ -1100,7 +1089,7 @@ function listenWithFallback(server, startPort) {
|
|
|
1100
1089
|
reject(err);
|
|
1101
1090
|
}
|
|
1102
1091
|
});
|
|
1103
|
-
server.listen(port,
|
|
1092
|
+
server.listen(port, host, () => resolve(port));
|
|
1104
1093
|
};
|
|
1105
1094
|
attempt();
|
|
1106
1095
|
});
|
|
@@ -116,39 +116,10 @@ function appVersion() {
|
|
|
116
116
|
return "";
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
// src/web/open.ts
|
|
120
|
-
import { spawn } from "child_process";
|
|
121
|
-
import { appendFileSync } from "fs";
|
|
122
|
-
function browserUrl(url, wsToken) {
|
|
123
|
-
return wsToken ? `${url}#/#tokmonToken=${encodeURIComponent(wsToken)}` : url;
|
|
124
|
-
}
|
|
125
|
-
function openBrowser(url, wsToken) {
|
|
126
|
-
const target = browserUrl(url, wsToken);
|
|
127
|
-
if (process.env.TOKMON_OPENLOG) {
|
|
128
|
-
try {
|
|
129
|
-
appendFileSync(process.env.TOKMON_OPENLOG, target + "\n");
|
|
130
|
-
} catch {
|
|
131
|
-
}
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
try {
|
|
135
|
-
if (process.platform === "darwin") {
|
|
136
|
-
spawn("open", [target], { stdio: "ignore", detached: true }).unref();
|
|
137
|
-
} else if (process.platform === "win32") {
|
|
138
|
-
spawn("cmd", ["/c", "start", "", target], { stdio: "ignore", detached: true }).unref();
|
|
139
|
-
} else {
|
|
140
|
-
spawn("xdg-open", [target], { stdio: "ignore", detached: true }).unref();
|
|
141
|
-
}
|
|
142
|
-
} catch {
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
119
|
export {
|
|
147
120
|
findWebRoot,
|
|
148
121
|
send,
|
|
149
122
|
sendJson,
|
|
150
123
|
serveStatic,
|
|
151
|
-
appVersion
|
|
152
|
-
browserUrl,
|
|
153
|
-
openBrowser
|
|
124
|
+
appVersion
|
|
154
125
|
};
|
|
@@ -5,12 +5,10 @@ import {
|
|
|
5
5
|
readLock,
|
|
6
6
|
reclaimDeadLock,
|
|
7
7
|
verifyLock
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-3TJVFKXV.js";
|
|
9
9
|
import {
|
|
10
|
-
appVersion
|
|
11
|
-
|
|
12
|
-
} from "./chunk-Q2DSDWO2.js";
|
|
13
|
-
import "./chunk-DJPUYMZM.js";
|
|
10
|
+
appVersion
|
|
11
|
+
} from "./chunk-SMPY52EV.js";
|
|
14
12
|
|
|
15
13
|
// src/client/daemon-handle.ts
|
|
16
14
|
import { spawn } from "child_process";
|
|
@@ -44,13 +42,13 @@ function parseHandshake(line) {
|
|
|
44
42
|
return null;
|
|
45
43
|
}
|
|
46
44
|
}
|
|
47
|
-
function connected(url
|
|
48
|
-
return { kind: "spawned", baseUrl:
|
|
45
|
+
function connected(url) {
|
|
46
|
+
return { kind: "spawned", baseUrl: url, stop: () => {
|
|
49
47
|
} };
|
|
50
48
|
}
|
|
51
49
|
async function attach(opts, version) {
|
|
52
50
|
const lock = await verifyLock(readLock(opts), version);
|
|
53
|
-
return lock ? connected(lock.url
|
|
51
|
+
return lock ? connected(lock.url) : null;
|
|
54
52
|
}
|
|
55
53
|
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
56
54
|
async function retireIncompatibleDaemon(opts, version, timeoutMs) {
|
|
@@ -155,9 +153,10 @@ async function attachOrSpawn(opts = {}) {
|
|
|
155
153
|
});
|
|
156
154
|
}
|
|
157
155
|
function degraded() {
|
|
158
|
-
return { kind: "degraded", baseUrl: null,
|
|
156
|
+
return { kind: "degraded", baseUrl: null, stop: () => {
|
|
159
157
|
} };
|
|
160
158
|
}
|
|
159
|
+
|
|
161
160
|
export {
|
|
162
161
|
attachOrSpawn
|
|
163
162
|
};
|