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.
Files changed (37) hide show
  1. package/README.md +23 -5
  2. package/dist/{bootstrap-ink-BIOLTP7F.js → bootstrap-ink-HL2BYNW3.js} +37 -292
  3. package/dist/{chunk-RM3ZE44Z.js → chunk-3TJVFKXV.js} +1 -1
  4. package/dist/chunk-42H7R376.js +298 -0
  5. package/dist/chunk-45ZP755H.js +275 -0
  6. package/dist/{chunk-DJPUYMZM.js → chunk-5CWOJMAH.js} +2 -0
  7. package/dist/{chunk-2AFQKH5O.js → chunk-O27I2XFN.js} +278 -539
  8. package/dist/{chunk-2K4L4QQK.js → chunk-REO4O5VE.js} +68 -79
  9. package/dist/{chunk-Q2DSDWO2.js → chunk-SMPY52EV.js} +1 -30
  10. package/dist/{daemon-handle-6F2IB6CU.js → chunk-W2WGBXZG.js} +8 -9
  11. package/dist/cli-command-TLGIGF63.js +526 -0
  12. package/dist/cli.js +30 -5
  13. package/dist/{config-5CNJN3P7.js → config-QC5QSP3G.js} +1 -1
  14. package/dist/{daemon-PP3QAT6E.js → daemon-F4EGY77J.js} +43 -17
  15. package/dist/daemon-handle-NLCLYWFU.js +10 -0
  16. package/dist/server-X63FGHIZ.js +11 -0
  17. package/dist/web/assets/{Area-BALPhJJp.js → Area-DCES17aZ.js} +1 -1
  18. package/dist/web/assets/{analytics-uHVeOhLh.js → analytics-DDW6zcnj.js} +2 -2
  19. package/dist/web/assets/{breakdown-CR5dNNSG.js → breakdown-D5LPPtqh.js} +1 -1
  20. package/dist/web/assets/{chart-DRkqw0fm.js → chart-bHY7MQV0.js} +1 -1
  21. package/dist/web/assets/{explore-C-bHaX-I.js → explore-DdWB-Pu7.js} +1 -1
  22. package/dist/web/assets/index-C9jxb2o3.js +83 -0
  23. package/dist/web/assets/index-m0KfWnN2.css +1 -0
  24. package/dist/web/assets/{models-Qc2daHCo.js → models-o1wJnG6w.js} +2 -2
  25. package/dist/web/assets/{overview-D4VG2awg.js → overview-wq2uAEk4.js} +2 -2
  26. package/dist/web/assets/{panel-C1CsBs1H.js → panel-DBnK1oy0.js} +1 -1
  27. package/dist/web/assets/{primitives-S4UDgnZq.js → primitives-CBmteetD.js} +1 -1
  28. package/dist/web/assets/settings-sheet-CZ6GEPl3.js +1 -0
  29. package/dist/web/assets/{share-sheet-BpZBju-k.js → share-sheet-DUee3FlK.js} +1 -1
  30. package/dist/web/assets/{timeline-Cp-fyeuK.js → timeline-BxHwOJA_.js} +1 -1
  31. package/dist/web/assets/{use-dialog-trap-DaKbCG1Z.js → use-dialog-trap-30EqxYF-.js} +1 -1
  32. package/dist/web/index.html +2 -2
  33. package/package.json +1 -1
  34. package/dist/server-UHQEOVQH.js +0 -10
  35. package/dist/web/assets/index-C1jQdagn.js +0 -83
  36. package/dist/web/assets/index-DSj-Bxov.css +0 -1
  37. 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-Q2DSDWO2.js";
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-2AFQKH5O.js";
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-DJPUYMZM.js";
33
+ } from "./chunk-5CWOJMAH.js";
33
34
 
34
35
  // src/web/server.ts
35
36
  import { createServer } from "http";
36
- import { randomBytes, timingSafeEqual as timingSafeEqual2 } from "crypto";
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/ws.ts
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 isLoopbackHost(value) {
752
- if (!value) return false;
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
- return new URL(req.url ?? "/", "http://127.0.0.1").pathname === TOKMON_WS_PATH;
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 false;
759
+ return null;
762
760
  }
763
761
  }
764
- function wsToken(req) {
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
- return new URL(req.url ?? "/", "http://127.0.0.1").searchParams.get("wsToken");
778
+ const url = new URL(origin);
779
+ return (url.protocol === "http:" || url.protocol === "https:") && url.host.toLowerCase() === host;
767
780
  } catch {
768
- return null;
781
+ return false;
769
782
  }
770
783
  }
771
- function tokenMatches(actual, expected) {
772
- if (!actual) return false;
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
- function isAuthorized(req, token) {
778
- if (!isLoopbackHost(header(req, "host"))) return false;
779
- return tokenMatches(wsToken(req), token);
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 (!isLoopbackHost(header(req, "host"))) {
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 HOST = "127.0.0.1";
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 tokenMatches2(given, expected) {
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 && timingSafeEqual2(actual, token);
926
+ return actual.length === token.length && timingSafeEqual(actual, token);
929
927
  }
930
- function guardHost(req, res) {
931
- if (isLoopbackHostHeader(header2(req, "host"))) return true;
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 guardPrivileged(req, res, wsToken2) {
936
- const header3 = (n) => {
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, wsToken2, version) {
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: tokenMatches2(header2(req, "x-tokmon-token"), wsToken2)
957
+ owner: tokenMatches(header2(req, "x-tokmon-token"), wsToken)
968
958
  });
969
959
  return;
970
960
  }
971
961
  if (path === "/api/config") {
972
- if (!guardPrivileged(req, res, wsToken2)) return;
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 wsToken2 = opts.wsToken ?? randomBytes(32).toString("base64url");
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, wsToken2, version));
1014
- closeWsRpc = await mountWsRpc(server, { engine, state, wsToken: wsToken2 });
1015
- const port = await listenWithFallback(server, opts.port ?? DEFAULT_PORT);
1016
- const serverUrl = `http://${HOST}:${port}`;
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, HOST, () => {
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, HOST, () => resolve(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-RM3ZE44Z.js";
8
+ } from "./chunk-3TJVFKXV.js";
9
9
  import {
10
- appVersion,
11
- browserUrl
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, wsToken) {
48
- return { kind: "spawned", baseUrl: browserUrl(url, wsToken), wsToken, stop: () => {
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, lock.wsToken) : null;
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, wsToken: null, stop: () => {
156
+ return { kind: "degraded", baseUrl: null, stop: () => {
159
157
  } };
160
158
  }
159
+
161
160
  export {
162
161
  attachOrSpawn
163
162
  };