tokentracker-cli 0.23.0 → 0.23.2
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/dashboard/dist/assets/{Card-BgXt23Ak.js → Card-WqeqJCTJ.js} +1 -1
- package/dashboard/dist/assets/DashboardPage-DfXV-T4E.js +42 -0
- package/dashboard/dist/assets/{DevicePage-CWTFzGYj.js → DevicePage-CKQwUIFV.js} +1 -1
- package/dashboard/dist/assets/{FadeIn-r11-zwBU.js → FadeIn-BvXHu5jo.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-ZDYMF__6.js → HeaderGithubStar-BYgV73_y.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-cCSBXnv2.js → IpCheckPage-BjuJDGa8.js} +1 -1
- package/dashboard/dist/assets/{LandingPage-DDUROVcN.js → LandingPage-Dymt6fsi.js} +1 -1
- package/dashboard/dist/assets/LeaderboardPage-BWJX1oGI.js +5 -0
- package/dashboard/dist/assets/{LeaderboardProfilePage-BthYFghU.js → LeaderboardProfilePage-DmNl8aoL.js} +1 -1
- package/dashboard/dist/assets/{LimitsPage-BQpint0k.js → LimitsPage-D_q9Z5nB.js} +1 -1
- package/dashboard/dist/assets/{LoginPage-EUjSrjOv.js → LoginPage-BvwX7KJg.js} +1 -1
- package/dashboard/dist/assets/PopoverPopup-CrBQ_8vY.js +12 -0
- package/dashboard/dist/assets/{ProviderIcon-D7GhGvvD.js → ProviderIcon-Cb-YMfcK.js} +1 -1
- package/dashboard/dist/assets/SettingsPage-u30V0Mqy.js +1 -0
- package/dashboard/dist/assets/SkillsPage-ssktZGFE.js +1 -0
- package/dashboard/dist/assets/{WidgetsPage-QfPgS0dO.js → WidgetsPage-_geExCIR.js} +1 -1
- package/dashboard/dist/assets/{WrappedPage-0iiBrhBo.js → WrappedPage-C5Wcs8L4.js} +1 -1
- package/dashboard/dist/assets/check-Cu-lm-wz.js +1 -0
- package/dashboard/dist/assets/{chevron-down-Br4ZSPLP.js → chevron-down-BbPjwLP4.js} +1 -1
- package/dashboard/dist/assets/{download-CnQNKpma.js → download-iIollDJi.js} +1 -1
- package/dashboard/dist/assets/info-DjS23Duh.js +1 -0
- package/dashboard/dist/assets/{leaderboard-columns-Bdgb7vS7.js → leaderboard-columns-DTnRH3EZ.js} +1 -1
- package/dashboard/dist/assets/main-Cb6vMAZa.js +688 -0
- package/dashboard/dist/assets/{use-limits-display-prefs-B5TQdyrf.js → use-limits-display-prefs-DZ9hgvef.js} +1 -1
- package/dashboard/dist/assets/{use-native-settings-CSxKG_Na.js → use-native-settings-DS1TfSil.js} +1 -1
- package/dashboard/dist/assets/{use-reduced-motion-C9rLBEnX.js → use-reduced-motion-D82galUK.js} +1 -1
- package/dashboard/dist/assets/{use-usage-limits-Bmok4oKO.js → use-usage-limits-B-svr8YY.js} +1 -1
- package/dashboard/dist/assets/useCurrency-uA9QPua4.js +1 -0
- package/dashboard/dist/index.html +1 -1
- package/dashboard/dist/share.html +1 -1
- package/package.json +1 -1
- package/src/cli.js +2 -1
- package/src/commands/serve.js +57 -2
- package/src/lib/local-api.js +48 -9
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/dashboard/dist/assets/DashboardPage-NVAsY99I.js +0 -64
- package/dashboard/dist/assets/LeaderboardPage-BaETRtBF.js +0 -5
- package/dashboard/dist/assets/PopoverPopup-DyjmUoRY.js +0 -12
- package/dashboard/dist/assets/SettingsPage-9lfQ1c28.js +0 -1
- package/dashboard/dist/assets/SkillsPage-Bt7d1kd0.js +0 -1
- package/dashboard/dist/assets/check-CtXuxG1H.js +0 -1
- package/dashboard/dist/assets/main-D7f_DoMp.js +0 -676
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r as a}from"./main-
|
|
1
|
+
import{r as a}from"./main-Cb6vMAZa.js";const d=["claude","codex","cursor","gemini","kimi","kiro","copilot","antigravity"],S={claude:"Claude",codex:"Codex",cursor:"Cursor",gemini:"Gemini",kimi:"Kimi",kiro:"Kiro",copilot:"GitHub Copilot",antigravity:"Antigravity"},v={claude:"/brand-logos/claude-code.svg",codex:"/brand-logos/codex.svg",cursor:"/brand-logos/cursor.svg",gemini:"/brand-logos/gemini.svg",kimi:"/brand-logos/kimi.svg",kiro:"/brand-logos/kiro.svg",copilot:"/brand-logos/copilot.svg",antigravity:"/brand-logos/antigravity.svg"},l="tt.limits.providerOrder",g="tt.limits.providerVisibility";function w(){if(typeof window>"u")return[...d];try{const i=window.localStorage.getItem(l);if(!i)return[...d];const s=JSON.parse(i);if(!Array.isArray(s))return[...d];const r=s.filter(c=>d.includes(c));for(const c of d)r.includes(c)||r.push(c);return r}catch{return[...d]}}function y(){const i=Object.fromEntries(d.map(s=>[s,!0]));if(typeof window>"u")return i;try{const s=window.localStorage.getItem(g);if(!s)return i;const r=JSON.parse(s);if(!r||typeof r!="object")return i;const c={...i};for(const u of d)typeof r[u]=="boolean"&&(c[u]=r[u]);return c}catch{return i}}function C(){const[i,s]=a.useState(w),[r,c]=a.useState(y);a.useEffect(()=>{if(!(typeof window>"u"))try{window.localStorage.setItem(l,JSON.stringify(i))}catch{}},[i]),a.useEffect(()=>{if(!(typeof window>"u"))try{window.localStorage.setItem(g,JSON.stringify(r))}catch{}},[r]),a.useEffect(()=>{if(typeof window>"u")return;const o=t=>{t.key===l&&s(w()),t.key===g&&c(y())};return window.addEventListener("storage",o),()=>window.removeEventListener("storage",o)},[]);const u=a.useCallback(o=>{c(t=>({...t,[o]:!t[o]}))},[]),b=a.useCallback(o=>{s(t=>{const e=t.indexOf(o);if(e<=0)return t;const n=[...t];return[n[e-1],n[e]]=[n[e],n[e-1]],n})},[]),p=a.useCallback(o=>{s(t=>{const e=t.indexOf(o);if(e<0||e>=t.length-1)return t;const n=[...t];return[n[e],n[e+1]]=[n[e+1],n[e]],n})},[]),O=a.useCallback((o,t)=>{o!==t&&s(e=>{const n=e.indexOf(o),m=e.indexOf(t);if(n<0||m<0)return e;const f=[...e],[I]=f.splice(n,1);return f.splice(m,0,I),f})},[]),k=a.useCallback(()=>{s([...d]),c(Object.fromEntries(d.map(o=>[o,!0])))},[]),x=a.useMemo(()=>i.filter(o=>r[o]!==!1),[i,r]);return{order:i,visibility:r,visibleOrdered:x,toggle:u,moveUp:b,moveDown:p,moveToward:O,reset:k}}export{v as L,S as a,C as u};
|
package/dashboard/dist/assets/{use-native-settings-CSxKG_Na.js → use-native-settings-DS1TfSil.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{C as a,
|
|
1
|
+
import{C as a,a7 as x,r as n,aH as g,aI as m,aJ as b,aK as u,aL as f,aF as h}from"./main-Cb6vMAZa.js";import{C as y}from"./Card-WqeqJCTJ.js";function p({checked:s,onChange:t,disabled:e,ariaLabel:i}){return a.jsx("button",{type:"button",role:"switch","aria-checked":s,"aria-label":i,onClick:t,disabled:e,className:x("relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-oai-brand-500 disabled:opacity-50 disabled:cursor-not-allowed",s?"bg-oai-brand-500":"bg-oai-gray-300 dark:bg-oai-gray-700"),children:a.jsx("span",{className:x("inline-block h-3.5 w-3.5 rounded-full bg-white transition-transform",s?"translate-x-[18px]":"translate-x-[3px]")})})}function j({label:s,hint:t,control:e}){return a.jsxs("div",{className:"flex items-center justify-between gap-4 py-3",children:[a.jsxs("div",{className:"min-w-0 flex-1",children:[a.jsx("div",{className:"text-sm text-oai-gray-900 dark:text-oai-gray-200",children:s}),t?a.jsx("div",{className:"mt-0.5 text-xs text-oai-gray-500 dark:text-oai-gray-400",children:t}):null]}),a.jsx("div",{className:"shrink-0",children:e})]})}function N({title:s,subtitle:t,action:e,children:i}){return a.jsxs(y,{children:[a.jsxs("div",{className:"mb-3 flex items-start justify-between gap-4",children:[a.jsxs("div",{className:"min-w-0 flex-1",children:[a.jsx("h2",{className:"text-sm font-medium text-oai-gray-500 dark:text-oai-gray-300 uppercase tracking-wide",children:s}),t?a.jsx("p",{className:"mt-1 truncate text-xs text-oai-gray-500 dark:text-oai-gray-400",children:t}):null]}),e?a.jsx("div",{className:"shrink-0",children:e}):null]}),a.jsx("div",{className:"-mb-3 divide-y divide-oai-gray-200/60 dark:divide-oai-gray-800/60",children:i})]})}function w({options:s,value:t,onChange:e}){return a.jsx("div",{className:"inline-flex items-center rounded-lg border border-oai-gray-200 bg-oai-gray-50 p-0.5 dark:border-oai-gray-800 dark:bg-oai-gray-900",children:s.map(({value:i,label:d,Icon:l})=>{const r=t===i;return a.jsxs("button",{type:"button",onClick:()=>e(i),"aria-pressed":r,className:x("inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors",r?"bg-white text-oai-black shadow-sm dark:bg-oai-gray-800 dark:text-white":"text-oai-gray-500 hover:text-oai-black dark:text-oai-gray-400 dark:hover:text-white"),children:[l?a.jsx(l,{className:"h-3.5 w-3.5","aria-hidden":!0}):null,a.jsx("span",{children:d})]},i)})})}function S(){const[s,t]=n.useState(null),e=g()&&m();n.useEffect(()=>{if(!e)return;const r=b(o=>t(o));return u(),r},[e]);const i=n.useCallback((r,o)=>{e&&(t(c=>c&&{...c,[r]:o}),f(r,o))},[e]),d=n.useCallback(r=>{e&&h(r)},[e]),l=n.useCallback(()=>{e&&u()},[e]);return{available:e,settings:s,setSetting:i,runAction:d,refresh:l}}export{N as S,p as T,j as a,w as b,S as u};
|
package/dashboard/dist/assets/{use-reduced-motion-C9rLBEnX.js → use-reduced-motion-D82galUK.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{aM as t,aN as o,r,aO as s}from"./main-Cb6vMAZa.js";function u(){!t.current&&o();const[e]=r.useState(s.current);return e}export{u};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{r,
|
|
1
|
+
import{r,al as l}from"./main-Cb6vMAZa.js";function y(i){const[o,a]=r.useState(null),[u,s]=r.useState(null),[c,f]=r.useState(!0),n=!!i?.initialRefresh,g=r.useCallback(async()=>{try{const e=await l({refresh:!0});a(e&&typeof e=="object"?e:null),s(null)}catch(e){s(e?.message||String(e))}},[]);return r.useEffect(()=>{let e=!1;return(async()=>{try{const t=await l(n?{refresh:!0}:{});if(e)return;a(t&&typeof t=="object"?t:null),s(null)}catch(t){if(e)return;s(t?.message||String(t))}finally{e||f(!1)}})(),()=>{e=!0}},[n]),{data:o,error:u,isLoading:c,refresh:g}}export{y as u};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{r as e,aA as t,aB as r,I as c}from"./main-Cb6vMAZa.js";const a=Object.freeze({currency:c,rate:1,symbol:"$",rates:{...r},rateSource:"default",rateFetchedAt:null,setCurrency:()=>{}});function u(){return e.useContext(t)??a}export{u};
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
]
|
|
211
211
|
}
|
|
212
212
|
</script>
|
|
213
|
-
<script type="module" crossorigin src="/assets/main-
|
|
213
|
+
<script type="module" crossorigin src="/assets/main-Cb6vMAZa.js"></script>
|
|
214
214
|
<link rel="stylesheet" crossorigin href="/assets/main-0bSXJNLn.css">
|
|
215
215
|
</head>
|
|
216
216
|
<body>
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"description": "Shareable Token Tracker dashboard snapshot."
|
|
52
52
|
}
|
|
53
53
|
</script>
|
|
54
|
-
<script type="module" crossorigin src="/assets/main-
|
|
54
|
+
<script type="module" crossorigin src="/assets/main-Cb6vMAZa.js"></script>
|
|
55
55
|
<link rel="stylesheet" crossorigin href="/assets/main-0bSXJNLn.css">
|
|
56
56
|
</head>
|
|
57
57
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokentracker-cli",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.2",
|
|
4
4
|
"description": "Token usage tracker for AI agent CLIs (Claude Code, Codex, Cursor, Gemini, Kiro, OpenCode, OpenClaw, Every Code, Hermes, GitHub Copilot, Kimi Code, CodeBuddy, Grok Build, oh-my-pi, pi, Craft Agents, Kilo CLI, Kilo Code, Roo Code, Zed Agent, Goose)",
|
|
5
5
|
"main": "src/cli.js",
|
|
6
6
|
"bin": {
|
package/src/cli.js
CHANGED
|
@@ -63,7 +63,7 @@ function printHelp() {
|
|
|
63
63
|
"",
|
|
64
64
|
"Usage:",
|
|
65
65
|
" npx tokentracker Open local dashboard",
|
|
66
|
-
" npx tokentracker [--debug] serve [--port 7680] [--no-open] [--no-sync]",
|
|
66
|
+
" npx tokentracker [--debug] serve [--port 7680] [--allowed-hosts <host[,host...]>] [--no-open] [--no-sync]",
|
|
67
67
|
" npx tokentracker [--debug] init [--yes] [--dry-run] [--no-open] [--link-code <code>]",
|
|
68
68
|
" npx tokentracker [--debug] sync [--auto] [--drain] [--from-openclaw]",
|
|
69
69
|
" npx tokentracker [--debug] status [--probe-keychain] [--probe-keychain-details]",
|
|
@@ -82,6 +82,7 @@ function printHelp() {
|
|
|
82
82
|
" - OpenClaw hook auto-links when OpenClaw is installed (requires gateway restart).",
|
|
83
83
|
" - auto sync waits for a device token.",
|
|
84
84
|
" - optional: --dashboard-url for hosted landing.",
|
|
85
|
+
" - serve: --allowed-hosts marks extra hostnames as local-dashboard auth proxy origins (for tunnels/previews).",
|
|
85
86
|
" - sync parses ~/.codex/sessions/**/rollout-*.jsonl and ~/.code/sessions/**/rollout-*.jsonl, then uploads token deltas.",
|
|
86
87
|
" - --from-openclaw marks sync runs triggered by OpenClaw hooks.",
|
|
87
88
|
" - --debug shows original backend errors.",
|
package/src/commands/serve.js
CHANGED
|
@@ -87,7 +87,7 @@ async function cmdServe(argv) {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
// 3. Create handler
|
|
90
|
-
const handleApi = createLocalApiHandler({ queuePath });
|
|
90
|
+
const handleApi = createLocalApiHandler({ queuePath, allowedHosts: opts.allowedHosts });
|
|
91
91
|
|
|
92
92
|
const server = http.createServer(async (req, res) => {
|
|
93
93
|
try {
|
|
@@ -104,6 +104,11 @@ async function cmdServe(argv) {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
// API routes
|
|
107
|
+
if (url.pathname === "/api/dashboard-config") {
|
|
108
|
+
serveDashboardConfig(res, { allowedHosts: opts.allowedHosts });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
107
112
|
if (
|
|
108
113
|
url.pathname.startsWith("/functions/")
|
|
109
114
|
|| url.pathname.startsWith("/api/")
|
|
@@ -224,19 +229,66 @@ function resolveDashboardDir() {
|
|
|
224
229
|
return null;
|
|
225
230
|
}
|
|
226
231
|
|
|
232
|
+
function splitHostList(value) {
|
|
233
|
+
if (Array.isArray(value)) return value.flatMap(splitHostList);
|
|
234
|
+
if (typeof value !== "string") return [];
|
|
235
|
+
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function normalizeAllowedHost(value) {
|
|
239
|
+
if (typeof value !== "string") return null;
|
|
240
|
+
const raw = value.trim();
|
|
241
|
+
if (!raw || raw.includes("*") || /\s/.test(raw)) return null;
|
|
242
|
+
try {
|
|
243
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : `http://${raw}`;
|
|
244
|
+
const url = new URL(withScheme);
|
|
245
|
+
if (!url.hostname || url.username || url.password) return null;
|
|
246
|
+
return url.hostname.toLowerCase();
|
|
247
|
+
} catch (_e) {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function normalizeAllowedHosts(values) {
|
|
253
|
+
const out = [];
|
|
254
|
+
const seen = new Set();
|
|
255
|
+
for (const item of splitHostList(values)) {
|
|
256
|
+
const host = normalizeAllowedHost(item);
|
|
257
|
+
if (!host || seen.has(host)) continue;
|
|
258
|
+
seen.add(host);
|
|
259
|
+
out.push(host);
|
|
260
|
+
}
|
|
261
|
+
return out;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function serveDashboardConfig(res, { allowedHosts } = {}) {
|
|
265
|
+
const body = Buffer.from(JSON.stringify({ allowedHosts: normalizeAllowedHosts(allowedHosts) }), "utf8");
|
|
266
|
+
res.writeHead(200, {
|
|
267
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
268
|
+
"Content-Length": body.length,
|
|
269
|
+
"Cache-Control": "no-store",
|
|
270
|
+
});
|
|
271
|
+
res.end(body);
|
|
272
|
+
}
|
|
273
|
+
|
|
227
274
|
function parseArgs(argv) {
|
|
228
|
-
const opts = { port: DEFAULT_PORT, open: true, sync: true };
|
|
275
|
+
const opts = { port: DEFAULT_PORT, open: true, sync: true, allowedHosts: [] };
|
|
229
276
|
for (let i = 0; i < argv.length; i++) {
|
|
230
277
|
const arg = argv[i];
|
|
231
278
|
if (arg === "--port" && i + 1 < argv.length) {
|
|
232
279
|
const n = parseInt(argv[++i], 10);
|
|
233
280
|
if (Number.isFinite(n) && n > 0 && n < 65536) opts.port = n;
|
|
281
|
+
} else if (arg === "--allowed-hosts" && i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
|
|
282
|
+
opts.allowedHosts.push(...normalizeAllowedHosts(argv[++i]));
|
|
283
|
+
} else if (arg.startsWith("--allowed-hosts=")) {
|
|
284
|
+
opts.allowedHosts.push(...normalizeAllowedHosts(arg.slice("--allowed-hosts=".length)));
|
|
234
285
|
} else if (arg === "--no-open") {
|
|
235
286
|
opts.open = false;
|
|
236
287
|
} else if (arg === "--no-sync") {
|
|
237
288
|
opts.sync = false;
|
|
238
289
|
}
|
|
239
290
|
}
|
|
291
|
+
opts.allowedHosts = normalizeAllowedHosts(opts.allowedHosts);
|
|
240
292
|
return opts;
|
|
241
293
|
}
|
|
242
294
|
|
|
@@ -246,4 +298,7 @@ module.exports = {
|
|
|
246
298
|
NPM_PACKAGE_NAME,
|
|
247
299
|
LOCAL_BIND_HOST,
|
|
248
300
|
getLocalServerUrl,
|
|
301
|
+
parseArgs,
|
|
302
|
+
normalizeAllowedHosts,
|
|
303
|
+
serveDashboardConfig,
|
|
249
304
|
};
|
package/src/lib/local-api.js
CHANGED
|
@@ -457,13 +457,45 @@ function isLoopbackHostname(hostname) {
|
|
|
457
457
|
return hostname === "127.0.0.1" || hostname === "localhost" || hostname === "::1" || hostname === "[::1]";
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
-
function
|
|
460
|
+
function normalizeAllowedHost(value) {
|
|
461
|
+
if (typeof value !== "string") return null;
|
|
462
|
+
const raw = value.trim();
|
|
463
|
+
if (!raw || raw.includes("*") || /\s/.test(raw)) return null;
|
|
464
|
+
try {
|
|
465
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : `http://${raw}`;
|
|
466
|
+
const url = new URL(withScheme);
|
|
467
|
+
if (!url.hostname || url.username || url.password) return null;
|
|
468
|
+
return url.hostname.toLowerCase();
|
|
469
|
+
} catch (_e) {
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function normalizeAllowedHosts(values) {
|
|
475
|
+
const raw = Array.isArray(values) ? values : [values];
|
|
476
|
+
const out = [];
|
|
477
|
+
const seen = new Set();
|
|
478
|
+
for (const item of raw) {
|
|
479
|
+
const host = normalizeAllowedHost(item);
|
|
480
|
+
if (!host || seen.has(host)) continue;
|
|
481
|
+
seen.add(host);
|
|
482
|
+
out.push(host);
|
|
483
|
+
}
|
|
484
|
+
return out;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function hasAllowedLoopbackOrigin(headers = {}, allowedHosts = []) {
|
|
488
|
+
const allowed = new Set(normalizeAllowedHosts(allowedHosts));
|
|
461
489
|
const candidates = [headers.origin, headers.referer];
|
|
462
490
|
for (const raw of candidates) {
|
|
463
491
|
if (raw == null || raw === "") continue;
|
|
464
492
|
try {
|
|
465
493
|
const url = new URL(String(raw));
|
|
466
|
-
if (
|
|
494
|
+
if (!["http:", "https:"].includes(url.protocol)) return false;
|
|
495
|
+
const host = url.hostname.toLowerCase();
|
|
496
|
+
if (url.protocol === "http:" && isLoopbackHostname(host)) continue;
|
|
497
|
+
if (allowed.has(host)) continue;
|
|
498
|
+
return false;
|
|
467
499
|
} catch (_e) {
|
|
468
500
|
return false;
|
|
469
501
|
}
|
|
@@ -668,8 +700,9 @@ function rewriteIpCheckHtml(html) {
|
|
|
668
700
|
// Main handler factory
|
|
669
701
|
// ---------------------------------------------------------------------------
|
|
670
702
|
|
|
671
|
-
function createLocalApiHandler({ queuePath }) {
|
|
703
|
+
function createLocalApiHandler({ queuePath, allowedHosts = [] } = {}) {
|
|
672
704
|
const qp = queuePath || resolveQueuePath();
|
|
705
|
+
const allowedLocalOriginHosts = normalizeAllowedHosts(allowedHosts);
|
|
673
706
|
|
|
674
707
|
// Server-side cookie relay: captures auth cookies from InsForge cloud responses
|
|
675
708
|
// so that both browser and WKWebView share the same login session via the proxy.
|
|
@@ -804,9 +837,10 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
804
837
|
return typeof value === "string" ? value : "";
|
|
805
838
|
}
|
|
806
839
|
|
|
807
|
-
function buildRelayCookieHeader(clientCookieHeader) {
|
|
840
|
+
function buildRelayCookieHeader(clientCookieHeader, { relayPrecedenceNames = [] } = {}) {
|
|
808
841
|
const normalizedClientCookieHeader = normalizeCookieHeader(clientCookieHeader);
|
|
809
842
|
if (relayCookies.size === 0) return normalizedClientCookieHeader;
|
|
843
|
+
const relayPrecedence = new Set(relayPrecedenceNames);
|
|
810
844
|
const clientPairs = new Map();
|
|
811
845
|
if (normalizedClientCookieHeader) {
|
|
812
846
|
for (const part of normalizedClientCookieHeader.split(";")) {
|
|
@@ -816,9 +850,10 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
816
850
|
if (n) clientPairs.set(n, part.trim());
|
|
817
851
|
}
|
|
818
852
|
}
|
|
819
|
-
// Merge relay cookies
|
|
853
|
+
// Merge relay cookies. Normal requests keep client precedence; refresh
|
|
854
|
+
// recovery can opt relay cookies into precedence over stale WebView cookies.
|
|
820
855
|
for (const [name, raw] of relayCookies) {
|
|
821
|
-
if (clientPairs.has(name)) continue;
|
|
856
|
+
if (clientPairs.has(name) && !relayPrecedence.has(name)) continue;
|
|
822
857
|
const scIdx = raw.indexOf(";");
|
|
823
858
|
const pair = scIdx > 0 ? raw.substring(0, scIdx).trim() : raw;
|
|
824
859
|
clientPairs.set(name, pair);
|
|
@@ -839,7 +874,7 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
839
874
|
? headerToken.trim()
|
|
840
875
|
: cookieToken || "";
|
|
841
876
|
if (!token || token !== localAuthToken) return false;
|
|
842
|
-
return hasAllowedLoopbackOrigin(req?.headers || {});
|
|
877
|
+
return hasAllowedLoopbackOrigin(req?.headers || {}, allowedLocalOriginHosts);
|
|
843
878
|
}
|
|
844
879
|
|
|
845
880
|
return async function handleLocalApi(req, res, url) {
|
|
@@ -897,7 +932,7 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
897
932
|
const hasClientCookie = normalizeCookieHeader(proxyHeaders["cookie"]).trim().length > 0;
|
|
898
933
|
const hasCsrfHeader = typeof proxyHeaders["x-csrf-token"] === "string" && proxyHeaders["x-csrf-token"].trim().length > 0;
|
|
899
934
|
const relayCsrfToken = getRelayCookieValue(csrfRelayCookieName);
|
|
900
|
-
if (p === "/api/auth/refresh" &&
|
|
935
|
+
if (p === "/api/auth/refresh" && relayCsrfToken) {
|
|
901
936
|
proxyHeaders["x-csrf-token"] = relayCsrfToken;
|
|
902
937
|
}
|
|
903
938
|
const hasEffectiveCsrfHeader =
|
|
@@ -918,7 +953,11 @@ function createLocalApiHandler({ queuePath }) {
|
|
|
918
953
|
// Invalid CSRF errors on startup.
|
|
919
954
|
const originalCookieHeader = normalizeCookieHeader(proxyHeaders["cookie"]);
|
|
920
955
|
const mergedCookie = shouldInjectRelayCookies
|
|
921
|
-
? buildRelayCookieHeader(originalCookieHeader
|
|
956
|
+
? buildRelayCookieHeader(originalCookieHeader, {
|
|
957
|
+
relayPrecedenceNames: p === "/api/auth/refresh"
|
|
958
|
+
? [csrfRelayCookieName, "insforge_refresh_token"]
|
|
959
|
+
: [],
|
|
960
|
+
})
|
|
922
961
|
: originalCookieHeader;
|
|
923
962
|
const injectedRelayCookies =
|
|
924
963
|
shouldInjectRelayCookies && relayCookies.size > 0 && mergedCookie !== originalCookieHeader;
|