tokentracker-cli 1.2.2 → 1.3.4
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 +2 -0
- package/README.zh-CN.md +2 -0
- package/THIRD_PARTY_NOTICES.md +92 -0
- package/dashboard/dist/THIRD_PARTY_NOTICES.txt +92 -0
- package/dashboard/dist/assets/AchievementBadge-CKjtQp-U.js +1 -0
- package/dashboard/dist/assets/AchievementsPage-DpOXrabE.js +2 -0
- package/dashboard/dist/assets/{AchievementsSection-BDqKRGyL.js → AchievementsSection-DiXyIoa0.js} +1 -1
- package/dashboard/dist/assets/BadgeDetailModal-CILTTkPF.js +1 -0
- package/dashboard/dist/assets/{BotAnimated-OQbsVw1O.js → BotAnimated-CrBHWiRi.js} +1 -1
- package/dashboard/dist/assets/Card-DUItW3xI.js +1 -0
- package/dashboard/dist/assets/CloudCheckoutPage-C_t_PDlG.js +1 -0
- package/dashboard/dist/assets/CloudPage-pvJUrhya.js +1 -0
- package/dashboard/dist/assets/CloudUsageExport-DQbDwiyk.js +4 -0
- package/dashboard/dist/assets/{CommandPalette-8B38qUs0.js → CommandPalette-BvHilrbV.js} +1 -1
- package/dashboard/dist/assets/CommunityStatsModal-CizxEYv-.js +1 -0
- package/dashboard/dist/assets/{ConfirmModal-Dhu_qmiM.js → ConfirmModal-h9nISVy7.js} +1 -1
- package/dashboard/dist/assets/Controls-BjdIonTX.js +1 -0
- package/dashboard/dist/assets/DashboardPage-Bxryr4qp.js +1 -0
- package/dashboard/dist/assets/DateRangePopover-DpKh-0l8.js +1 -0
- package/dashboard/dist/assets/{DevicePage-Bvz7JJwt.js → DevicePage-DUJR9hJC.js} +1 -1
- package/dashboard/dist/assets/DialogClose-BmVAmN-o.js +1 -0
- package/dashboard/dist/assets/DialogDescription-C0Cgq67g.js +1 -0
- package/dashboard/dist/assets/DialogTitle-BsaIbN8k.js +1 -0
- package/dashboard/dist/assets/{FadeIn-DDF5PqTX.js → FadeIn-BpSt2P_W.js} +1 -1
- package/dashboard/dist/assets/{HeaderGithubStar-CNzQFPol.js → HeaderGithubStar-Cc_VHB0R.js} +1 -1
- package/dashboard/dist/assets/{HoverTooltip-BKg9Z6gg.js → HoverTooltip-WFd21iP9.js} +1 -1
- package/dashboard/dist/assets/{IpCheckPage-BTdrLt08.js → IpCheckPage-C_mUPSRB.js} +1 -1
- package/dashboard/dist/assets/LandingPage-DVR8ClZO.js +2 -0
- package/dashboard/dist/assets/LeaderboardPage-D8ydzVpn.js +2 -0
- package/dashboard/dist/assets/LeaderboardProBadge-BjX4EFl2.js +1 -0
- package/dashboard/dist/assets/LeaderboardProfileModal-vbPMNn7W.js +73 -0
- package/dashboard/dist/assets/{LeaderboardProfilePage-CoqcDSTw.js → LeaderboardProfilePage-CqClmuA-.js} +2 -2
- package/dashboard/dist/assets/{LimitsPage-3RuSFnn-.js → LimitsPage-DaRggHge.js} +2 -2
- package/dashboard/dist/assets/{LocalOnlyNotice-D-OrIXty.js → LocalOnlyNotice-B6nDke_u.js} +1 -1
- package/dashboard/dist/assets/LoginCard-C28QVjd7.js +1 -0
- package/dashboard/dist/assets/LoginModal-Bx1btNhH.js +1 -0
- package/dashboard/dist/assets/LoginPage-DVOGNGYI.js +1 -0
- package/dashboard/dist/assets/PetPage-nOKryTde.js +1 -0
- package/dashboard/dist/assets/PopoverPopup-Ch6NLnu6.js +1 -0
- package/dashboard/dist/assets/{ProviderIcon-BTYVbUti.js → ProviderIcon-BRY2br-g.js} +1 -1
- package/dashboard/dist/assets/ResetPasswordPage-ko1OQecQ.js +1 -0
- package/dashboard/dist/assets/Select-Q8mS3_od.js +1 -0
- package/dashboard/dist/assets/SelectItemText-C1id8GDj.js +1 -0
- package/dashboard/dist/assets/SelfHostPage-Dzb2bDiM.js +1 -0
- package/dashboard/dist/assets/{ServiceStatusPage-Cb3k_C4g.js → ServiceStatusPage-ewFwz29J.js} +1 -1
- package/dashboard/dist/assets/SessionsPage-C6Y8kbnM.js +3 -0
- package/dashboard/dist/assets/SessionsPage-GV2fuNmd.css +1 -0
- package/dashboard/dist/assets/SettingsPage-nztCXEPs.js +2 -0
- package/dashboard/dist/assets/SkillsPage-CcG77tqU.js +1 -0
- package/dashboard/dist/assets/{TokenGalaxy-BGRF4I_8.js → TokenGalaxy-D2HDrbQR.js} +2 -2
- package/dashboard/dist/assets/TooltipPopup-C_bF2ZwO.js +1 -0
- package/dashboard/dist/assets/TrendMonitor-BcyoN5ns.js +19 -0
- package/dashboard/dist/assets/WidgetsPage-BirK0U_N.js +1 -0
- package/dashboard/dist/assets/{WrappedPage-BqyB7ezg.js → WrappedPage-thsXBuw6.js} +1 -1
- package/dashboard/dist/assets/__vite-browser-external-Bq8nj-JV.js +45 -0
- package/dashboard/dist/assets/__vite-browser-external-BwmeEfv7.css +1 -0
- package/dashboard/dist/assets/{agent-logos-CEcgxIBU.js → agent-logos-CK6SzhI7.js} +1 -1
- package/dashboard/dist/assets/arrow-left-c_NOSfoq.js +1 -0
- package/dashboard/dist/assets/arrow-right-CP7drKCT.js +1 -0
- package/dashboard/dist/assets/{arrow-up-right-CdzAw406.js → arrow-up-right-DQ6QfCpo.js} +1 -1
- package/dashboard/dist/assets/calendar-days-D_pxFQvT.js +1 -0
- package/dashboard/dist/assets/{check-DhEiHlwq.js → check-HAS31kTT.js} +1 -1
- package/dashboard/dist/assets/chevron-down-BIMuk6oN.js +1 -0
- package/dashboard/dist/assets/cloud-checkout-COmDwrUT.js +1 -0
- package/dashboard/dist/assets/composite-kLwqpqEk.js +1 -0
- package/dashboard/dist/assets/download-9RgsZIiH.js +1 -0
- package/dashboard/dist/assets/{external-link-SOFbVRrH.js → external-link-C_gYjHGF.js} +1 -1
- package/dashboard/dist/assets/{flame-BRdc7HDz.js → flame-BSsCvgT8.js} +1 -1
- package/dashboard/dist/assets/{format-tokens-DhIeweGK.js → format-tokens-N22Wqv1X.js} +1 -1
- package/dashboard/dist/assets/{host-mode-APftzzvz.js → host-mode-JzrhHX7F.js} +1 -1
- package/dashboard/dist/assets/{icons-D-wu8eUg.js → icons-pK99mIMv.js} +1 -1
- package/dashboard/dist/assets/{index-CZ3D1Q7c.js → index-BhviBroj.js} +1 -1
- package/dashboard/dist/assets/{index-D2g1tPGl.js → index-CmD84EJi.js} +1 -1
- package/dashboard/dist/assets/{index-CiWf_0mc.js → index-Dk43KNa6.js} +1 -1
- package/dashboard/dist/assets/info-D-RLFnSK.js +1 -0
- package/dashboard/dist/assets/{link-2-Cg6bEvjQ.js → link-2-BiKXW9mV.js} +1 -1
- package/dashboard/dist/assets/{loader-circle-Cd7h4LcE.js → loader-circle-KVUaEKV2.js} +1 -1
- package/dashboard/dist/assets/{modifiers.esm-B7hOvflM.js → modifiers.esm-5NK8j3mT.js} +1 -1
- package/dashboard/dist/assets/monitor-smartphone-u9yr-_Es.js +1 -0
- package/dashboard/dist/assets/os-DMmhX9tk.js +1 -0
- package/dashboard/dist/assets/play-D0MaDCOx.js +1 -0
- package/dashboard/dist/assets/plus-DTJVtoAJ.js +1 -0
- package/dashboard/dist/assets/provider-display-YhtG0I9Y.js +42 -0
- package/dashboard/dist/assets/{react-CfB5zBqR.js → react-gIVxucOB.js} +1 -1
- package/dashboard/dist/assets/safePolygon-C_sPnJ1L.js +1 -0
- package/dashboard/dist/assets/{search-DMtc6bBy.js → search-DdQRwFYV.js} +1 -1
- package/dashboard/dist/assets/server-M0rmbGWy.js +1 -0
- package/dashboard/dist/assets/shield-check-9-sqQKdD.js +1 -0
- package/dashboard/dist/assets/skills-api-B0qBWnuK.js +1 -0
- package/dashboard/dist/assets/{sortable.esm-O7ntEYCi.js → sortable.esm-iqbm7x5D.js} +1 -1
- package/dashboard/dist/assets/{store-D-FZbBaC.js → store-DstuWd4a.js} +1 -1
- package/dashboard/dist/assets/{terminal-Y3qDL50U.js → terminal-Dz0MxHNG.js} +1 -1
- package/dashboard/dist/assets/{trash-D_PaPrXq.js → trash-DBMFOFVz.js} +1 -1
- package/dashboard/dist/assets/{use-community-stats-DGTBo_4v.js → use-community-stats-CacbAcUR.js} +1 -1
- package/dashboard/dist/assets/{use-latest-request-guard-BpfCferm.js → use-latest-request-guard-nBuQVZOR.js} +1 -1
- package/dashboard/dist/assets/use-limits-display-prefs-fqg8hs6b.js +1 -0
- package/dashboard/dist/assets/use-native-settings-EAqAFx2x.js +1 -0
- package/dashboard/dist/assets/{use-ordered-list-CClTBWi0.js → use-ordered-list-DGLjjU6Q.js} +1 -1
- package/dashboard/dist/assets/use-pet-settings-C5MzWHE7.js +2 -0
- package/dashboard/dist/assets/use-reduced-motion-DK2oZ2uC.js +1 -0
- package/dashboard/dist/assets/{use-transform-CA5mW4u8.js → use-transform-C25eCuz_.js} +1 -1
- package/dashboard/dist/assets/use-usage-limits-BhqnEai3.js +1 -0
- package/dashboard/dist/assets/{useCurrency-CaOhUMcm.js → useCurrency-t3PYQi1l.js} +1 -1
- package/dashboard/dist/assets/usePositioner-C59mj6kG.js +1 -0
- package/dashboard/dist/assets/useTokenFormat-C0xQsBGf.js +1 -0
- package/dashboard/dist/assets/zap-CyH0tvKM.js +1 -0
- package/dashboard/dist/index.html +2 -2
- package/dashboard/dist/ip-check.html +2 -2
- package/dashboard/dist/leaderboard.html +2 -2
- package/dashboard/dist/pricing.html +6 -5
- package/dashboard/dist/privacy.html +3 -3
- package/dashboard/dist/share.html +2 -2
- package/dashboard/dist/terms.html +6 -6
- package/package.json +7 -2
- package/src/commands/device-login.js +60 -33
- package/src/commands/init.js +26 -7
- package/src/commands/serve.js +3 -3
- package/src/commands/sync.js +43 -22
- package/src/lib/atomic-json.js +27 -0
- package/src/lib/bearer-token.js +11 -0
- package/src/lib/cloud-account.js +4 -1
- package/src/lib/local-api.js +282 -69
- package/src/lib/openclaw-cli.js +104 -0
- package/src/lib/openclaw-hook.js +1 -46
- package/src/lib/openclaw-session-plugin.js +1 -59
- package/src/lib/pricing/seed-snapshot.json +1 -1
- package/src/lib/proxy-env.js +8 -3
- package/src/lib/rollout.js +15 -10
- package/src/lib/runtime-config.js +84 -14
- package/src/lib/skills-manager.js +8 -1
- package/src/lib/static-server.js +19 -4
- package/src/lib/upload-throttle.js +18 -4
- package/dashboard/dist/assets/AchievementBadge-B67_Cp1T.js +0 -1
- package/dashboard/dist/assets/AchievementsPage-DNJ5n0Bn.js +0 -2
- package/dashboard/dist/assets/BadgeDetailModal-DFL1MPcg.js +0 -1
- package/dashboard/dist/assets/Card-dqzakOoX.js +0 -1
- package/dashboard/dist/assets/CommunityStatsModal-DnbCiGms.js +0 -1
- package/dashboard/dist/assets/Controls-BgQflA-3.js +0 -1
- package/dashboard/dist/assets/DashboardPage-DeLRhWU_.js +0 -1
- package/dashboard/dist/assets/DialogClose-DUxjaRqf.js +0 -1
- package/dashboard/dist/assets/DialogDescription-h6XMtzIU.js +0 -1
- package/dashboard/dist/assets/DialogTitle-C8vpXqg8.js +0 -1
- package/dashboard/dist/assets/LandingPage-cjoagPAb.js +0 -2
- package/dashboard/dist/assets/LeaderboardAvatar-CJdG51hR.js +0 -1
- package/dashboard/dist/assets/LeaderboardPage-D-yf2rOQ.js +0 -2
- package/dashboard/dist/assets/LeaderboardProfileModal--hrfpUPF.js +0 -73
- package/dashboard/dist/assets/LoginCard-BiMyV2Uf.js +0 -1
- package/dashboard/dist/assets/LoginModal-D79CMZmf.js +0 -1
- package/dashboard/dist/assets/LoginPage-BLyGuPlG.js +0 -1
- package/dashboard/dist/assets/PetPage-Bv0K4k2t.js +0 -1
- package/dashboard/dist/assets/PopoverPopup-3eucO8QU.js +0 -1
- package/dashboard/dist/assets/ResetPasswordPage-VYMl84_-.js +0 -1
- package/dashboard/dist/assets/Select-D8N2TJz1.js +0 -1
- package/dashboard/dist/assets/SelectItemText-CoTR0Uwm.js +0 -1
- package/dashboard/dist/assets/SessionsPage-BngTWN7Q.css +0 -1
- package/dashboard/dist/assets/SessionsPage-BtH5iLbi.js +0 -3
- package/dashboard/dist/assets/SettingsPage-huGkex5G.js +0 -2
- package/dashboard/dist/assets/SkillsPage-B9usVOTU.js +0 -1
- package/dashboard/dist/assets/TrendMonitor-B8SKBsWO.js +0 -19
- package/dashboard/dist/assets/WidgetsPage-o_91L3Zi.js +0 -1
- package/dashboard/dist/assets/__vite-browser-external-Cj9D7QBh.js +0 -45
- package/dashboard/dist/assets/__vite-browser-external-gKkiQad5.css +0 -1
- package/dashboard/dist/assets/composite-DO7_ilHE.js +0 -1
- package/dashboard/dist/assets/download-kGPw24XQ.js +0 -1
- package/dashboard/dist/assets/info-Qgc5VctK.js +0 -1
- package/dashboard/dist/assets/monitor-smartphone-wFv5VQQf.js +0 -1
- package/dashboard/dist/assets/plus-0xHQei42.js +0 -1
- package/dashboard/dist/assets/provider-display-DNqVkqfp.js +0 -42
- package/dashboard/dist/assets/skills-api-BeN184fz.js +0 -1
- package/dashboard/dist/assets/use-limits-display-prefs-Drs82lSi.js +0 -1
- package/dashboard/dist/assets/use-pet-settings-DvaQ7XrL.js +0 -2
- package/dashboard/dist/assets/use-reduced-motion-Dg10dVuu.js +0 -1
- package/dashboard/dist/assets/use-session-efficiency-pref-BiYRXVlN.js +0 -1
- package/dashboard/dist/assets/use-usage-limits-B0uafB4O.js +0 -1
- package/dashboard/dist/assets/useTokenFormat-JjQCKdIr.js +0 -1
- package/dashboard/dist/assets/zap-Cqur2X5s.js +0 -1
|
@@ -6,30 +6,17 @@ const fs = require("node:fs/promises");
|
|
|
6
6
|
|
|
7
7
|
const { readJson, updateJsonLocked } = require("../lib/fs");
|
|
8
8
|
const { resolveTrackerPaths } = require("../lib/tracker-paths");
|
|
9
|
-
const { resolveRuntimeConfig } = require("../lib/runtime-config");
|
|
9
|
+
const { resolveRuntimeConfig, assertInsforgeRuntime, normalizeInstanceBaseUrl, clearDeviceIdentity, resetInstanceState } = require("../lib/runtime-config");
|
|
10
10
|
const { functionUrlFor, fetchFunctionResponse } = require("../lib/function-url");
|
|
11
11
|
|
|
12
12
|
const POLL_INTERVAL_MS = 5_000;
|
|
13
13
|
const ABSOLUTE_TIMEOUT_MS = 16 * 60 * 1000; // matches the 15-min server window with a small buffer
|
|
14
14
|
|
|
15
|
-
function
|
|
16
|
-
return resolveRuntimeConfig({
|
|
17
|
-
// Preserve device-login's explicit legacy override names and precedence,
|
|
18
|
-
// while routing persisted config through the shared retired-host filter.
|
|
19
|
-
cli: {
|
|
20
|
-
baseUrl:
|
|
21
|
-
process.env.TOKENTRACKER_BASE_URL ||
|
|
22
|
-
process.env.TOKENTRACKER_API_URL,
|
|
23
|
-
},
|
|
24
|
-
config: config || {},
|
|
25
|
-
env: {},
|
|
26
|
-
}).baseUrl;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function authorize({ baseUrl, clientInfo, machineId }) {
|
|
15
|
+
async function authorize({ baseUrl, clientInfo, machineId, anonKey, timeoutMs = 20_000 }) {
|
|
30
16
|
const res = await fetchFunctionResponse(functionUrlFor(baseUrl, "tokentracker-device-flow-authorize"), {
|
|
31
17
|
method: "POST",
|
|
32
|
-
headers: { "Content-Type": "application/json" },
|
|
18
|
+
headers: { "Content-Type": "application/json", ...(anonKey ? { apikey: anonKey } : {}) },
|
|
19
|
+
...(timeoutMs > 0 ? { signal: AbortSignal.timeout(timeoutMs) } : {}),
|
|
33
20
|
body: JSON.stringify({
|
|
34
21
|
client_info: clientInfo,
|
|
35
22
|
// Machine-stable identity: the server anchors the issued device to this
|
|
@@ -46,10 +33,11 @@ async function authorize({ baseUrl, clientInfo, machineId }) {
|
|
|
46
33
|
return res.json();
|
|
47
34
|
}
|
|
48
35
|
|
|
49
|
-
async function pollOnce({ baseUrl, deviceCode }) {
|
|
36
|
+
async function pollOnce({ baseUrl, deviceCode, anonKey, timeoutMs = 20_000 }) {
|
|
50
37
|
const res = await fetchFunctionResponse(functionUrlFor(baseUrl, "tokentracker-device-flow-poll"), {
|
|
51
38
|
method: "POST",
|
|
52
|
-
headers: { "Content-Type": "application/json" },
|
|
39
|
+
headers: { "Content-Type": "application/json", ...(anonKey ? { apikey: anonKey } : {}) },
|
|
40
|
+
...(timeoutMs > 0 ? { signal: AbortSignal.timeout(timeoutMs) } : {}),
|
|
53
41
|
body: JSON.stringify({ device_code: deviceCode }),
|
|
54
42
|
});
|
|
55
43
|
const data = await res.json().catch(() => ({}));
|
|
@@ -80,7 +68,12 @@ async function cmdDeviceLogin(argv = [], options = {}) {
|
|
|
80
68
|
const { trackerDir } = await resolveTrackerPaths({ home });
|
|
81
69
|
const configPath = path.join(trackerDir, "config.json");
|
|
82
70
|
const config = (await readJson(configPath)) || {};
|
|
83
|
-
|
|
71
|
+
if (opts.anonKeyFile) opts.anonKey = (await fs.readFile(opts.anonKeyFile, "utf8")).trim();
|
|
72
|
+
const runtime = assertInsforgeRuntime(resolveRuntimeConfig({
|
|
73
|
+
cli: { baseUrl: opts.baseUrl || process.env.TOKENTRACKER_BASE_URL || process.env.TOKENTRACKER_API_URL,
|
|
74
|
+
anonKey: opts.anonKey, dashboardUrl: opts.dashboardUrl }, config, env: process.env,
|
|
75
|
+
}));
|
|
76
|
+
const baseUrl = runtime.baseUrl;
|
|
84
77
|
|
|
85
78
|
const clientInfo = `${os.platform()}-${os.arch()} ${os.hostname()}`;
|
|
86
79
|
// Same machineId the local API serves to the dashboard — both login paths
|
|
@@ -88,7 +81,30 @@ async function cmdDeviceLogin(argv = [], options = {}) {
|
|
|
88
81
|
const { getOrCreateMachineId } = require("../lib/local-api");
|
|
89
82
|
const machineId = getOrCreateMachineId(path.join(trackerDir, "queue.jsonl"));
|
|
90
83
|
process.stdout.write(`Requesting device code from ${baseUrl}...\n`);
|
|
91
|
-
const authResp = await authorize({ baseUrl, clientInfo, machineId });
|
|
84
|
+
const authResp = await authorize({ baseUrl, clientInfo, machineId, anonKey: runtime.anonKey, timeoutMs: runtime.httpTimeoutMs });
|
|
85
|
+
if (typeof authResp.verification_uri !== "string" || !authResp.verification_uri || !authResp.device_code) {
|
|
86
|
+
throw new Error("Device authorization returned an incomplete response");
|
|
87
|
+
}
|
|
88
|
+
const dashboard = new URL(runtime.dashboardUrl);
|
|
89
|
+
const expectedPath = dashboard.pathname.replace(/\/$/, "") + "/device";
|
|
90
|
+
for (const value of [authResp.verification_uri, authResp.verification_uri_complete].filter(Boolean)) {
|
|
91
|
+
const returned = new URL(value);
|
|
92
|
+
if (returned.origin !== dashboard.origin || returned.pathname !== expectedPath || returned.username || returned.password) {
|
|
93
|
+
throw new Error("Device authorization returned a different dashboard. Configure --dashboard-url for this instance.");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
await updateJsonLocked(configPath, async current => {
|
|
97
|
+
const previous = resolveRuntimeConfig({ config, env: {} });
|
|
98
|
+
const latest = resolveRuntimeConfig({ config: current, env: {} });
|
|
99
|
+
if ((latest.baseUrl !== previous.baseUrl || latest.anonKey !== previous.anonKey) &&
|
|
100
|
+
(latest.baseUrl !== baseUrl || latest.anonKey !== runtime.anonKey)) {
|
|
101
|
+
throw new Error("Backend instance changed during device login");
|
|
102
|
+
}
|
|
103
|
+
const changedInstance = previous.baseUrl !== baseUrl || previous.anonKey !== runtime.anonKey;
|
|
104
|
+
if (changedInstance) resetInstanceState(trackerDir);
|
|
105
|
+
return { ...(changedInstance ? clearDeviceIdentity(current) : current), baseUrl,
|
|
106
|
+
anonKey: runtime.anonKey, dashboardUrl: runtime.dashboardUrl };
|
|
107
|
+
});
|
|
92
108
|
|
|
93
109
|
if (opts.json) {
|
|
94
110
|
process.stdout.write(JSON.stringify(authResp, null, 2) + "\n");
|
|
@@ -129,7 +145,7 @@ async function cmdDeviceLogin(argv = [], options = {}) {
|
|
|
129
145
|
await sleepFn(wait);
|
|
130
146
|
let result;
|
|
131
147
|
try {
|
|
132
|
-
result = await pollOnce({ baseUrl, deviceCode: authResp.device_code });
|
|
148
|
+
result = await pollOnce({ baseUrl, deviceCode: authResp.device_code, anonKey: runtime.anonKey, timeoutMs: runtime.httpTimeoutMs });
|
|
133
149
|
consecutiveErrors = 0;
|
|
134
150
|
} catch (e) {
|
|
135
151
|
consecutiveErrors++;
|
|
@@ -140,16 +156,21 @@ async function cmdDeviceLogin(argv = [], options = {}) {
|
|
|
140
156
|
if (!result.deviceToken) {
|
|
141
157
|
throw new Error("device login approved but server did not return a device token");
|
|
142
158
|
}
|
|
143
|
-
await updateJsonLocked(configPath, async (current) =>
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
159
|
+
await updateJsonLocked(configPath, async (current) => {
|
|
160
|
+
if (normalizeInstanceBaseUrl(current.baseUrl) !== baseUrl || current.anonKey !== runtime.anonKey) {
|
|
161
|
+
throw new Error("Backend instance changed during device login");
|
|
162
|
+
}
|
|
163
|
+
return { ...current,
|
|
164
|
+
...(machineId ? { machineId } : {}),
|
|
165
|
+
baseUrl,
|
|
166
|
+
user_id: result.user_id,
|
|
167
|
+
deviceToken: result.deviceToken,
|
|
168
|
+
deviceTokenBaseUrl: baseUrl,
|
|
169
|
+
deviceId: result.deviceId || current.deviceId || config.deviceId,
|
|
170
|
+
device_login_at: new Date().toISOString(),
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
process.stdout.write(`\nApproved. device token written to ${configPath}\n`);
|
|
153
174
|
return;
|
|
154
175
|
}
|
|
155
176
|
if (result.status === "expired") {
|
|
@@ -164,12 +185,18 @@ async function cmdDeviceLogin(argv = [], options = {}) {
|
|
|
164
185
|
}
|
|
165
186
|
|
|
166
187
|
function parseArgs(argv) {
|
|
167
|
-
const out = { json: false, baseUrl: null };
|
|
188
|
+
const out = { json: false, baseUrl: null, anonKey: null, anonKeyFile: null, dashboardUrl: null };
|
|
168
189
|
for (let i = 0; i < argv.length; i++) {
|
|
169
190
|
const a = argv[i];
|
|
170
191
|
if (a === "--json") out.json = true;
|
|
171
192
|
else if (a === "--base-url") {
|
|
172
193
|
out.baseUrl = argv[++i] || null;
|
|
194
|
+
} else if (a === "--anon-key") {
|
|
195
|
+
out.anonKey = argv[++i] || null;
|
|
196
|
+
} else if (a === "--anon-key-file") {
|
|
197
|
+
out.anonKeyFile = argv[++i] || null;
|
|
198
|
+
} else if (a === "--dashboard-url") {
|
|
199
|
+
out.dashboardUrl = argv[++i] || null;
|
|
173
200
|
} else throw new Error(`Unknown option: ${a}`);
|
|
174
201
|
}
|
|
175
202
|
return out;
|
package/src/commands/init.js
CHANGED
|
@@ -79,7 +79,8 @@ const {
|
|
|
79
79
|
resolveReasonixHome,
|
|
80
80
|
resolveTraeStoragePath,
|
|
81
81
|
} = require("../lib/rollout");
|
|
82
|
-
const { resolveRuntimeConfig, DEFAULT_BASE_URL
|
|
82
|
+
const { resolveRuntimeConfig, DEFAULT_BASE_URL, normalizeInstanceBaseUrl, assertInsforgeRuntime,
|
|
83
|
+
clearDeviceIdentity, resetInstanceState } = require("../lib/runtime-config");
|
|
83
84
|
const {
|
|
84
85
|
BOLD,
|
|
85
86
|
DIM,
|
|
@@ -159,9 +160,10 @@ const SUPPORTED_PROVIDERS = [
|
|
|
159
160
|
"Command Code",
|
|
160
161
|
];
|
|
161
162
|
|
|
162
|
-
async function cmdInit(argv) {
|
|
163
|
+
async function cmdInit(argv, options = {}) {
|
|
163
164
|
const opts = parseArgs(argv);
|
|
164
|
-
const home = os.homedir();
|
|
165
|
+
const home = options.home || os.homedir();
|
|
166
|
+
if (opts.anonKeyFile) opts.anonKey = (await fs.readFile(opts.anonKeyFile, "utf8")).trim();
|
|
165
167
|
|
|
166
168
|
const { rootDir, trackerDir, binDir } = await resolveTrackerPaths({ home });
|
|
167
169
|
|
|
@@ -171,10 +173,11 @@ async function cmdInit(argv) {
|
|
|
171
173
|
|
|
172
174
|
const existingConfig = await readJson(configPath);
|
|
173
175
|
const runtime = resolveRuntimeConfig({
|
|
174
|
-
cli: { baseUrl: opts.baseUrl, dashboardUrl: opts.dashboardUrl },
|
|
176
|
+
cli: { baseUrl: opts.baseUrl, dashboardUrl: opts.dashboardUrl, anonKey: opts.anonKey },
|
|
175
177
|
config: existingConfig || {},
|
|
176
178
|
env: process.env,
|
|
177
179
|
});
|
|
180
|
+
assertInsforgeRuntime(runtime);
|
|
178
181
|
const notifyPath = path.join(binDir, "notify.cjs");
|
|
179
182
|
const appDir = path.join(trackerDir, "app");
|
|
180
183
|
const trackerBinPath = path.join(appDir, "bin", "tracker.js");
|
|
@@ -369,16 +372,28 @@ async function runSetup({
|
|
|
369
372
|
existingConfig && typeof existingConfig === "object" && !Array.isArray(existingConfig)
|
|
370
373
|
? existingConfig
|
|
371
374
|
: {};
|
|
375
|
+
const previousRuntime = resolveRuntimeConfig({ config: existingPlainConfig, env: {} });
|
|
376
|
+
const changedInstance = normalizeInstanceBaseUrl(previousRuntime.baseUrl) !== runtime.baseUrl ||
|
|
377
|
+
previousRuntime.anonKey !== runtime.anonKey;
|
|
378
|
+
if (changedInstance) {
|
|
379
|
+
resetInstanceState(trackerDir);
|
|
380
|
+
deviceToken = null; deviceId = null;
|
|
381
|
+
}
|
|
372
382
|
const config = {
|
|
373
|
-
...existingPlainConfig,
|
|
383
|
+
...(changedInstance ? clearDeviceIdentity(existingPlainConfig) : existingPlainConfig),
|
|
374
384
|
installedAt,
|
|
375
385
|
// Keep a persisted legacy URL until the first sync. sync owns the migration
|
|
376
386
|
// lock and must reset the upload offset/backoff before removing this marker;
|
|
377
387
|
// rewriting it here would skip the historical replay permanently.
|
|
378
388
|
baseUrl: opts.baseUrl || existingPlainConfig.baseUrl || DEFAULT_BASE_URL,
|
|
379
389
|
};
|
|
380
|
-
if (opts.dashboardUrl) {
|
|
381
|
-
config.dashboardUrl =
|
|
390
|
+
if (changedInstance || opts.dashboardUrl) {
|
|
391
|
+
config.dashboardUrl = runtime.dashboardUrl;
|
|
392
|
+
}
|
|
393
|
+
if (changedInstance || opts.anonKey || runtime.sources.anonKey === "env") config.anonKey = runtime.anonKey;
|
|
394
|
+
if (runtime.baseUrl !== DEFAULT_BASE_URL) {
|
|
395
|
+
config.baseUrl = runtime.baseUrl;
|
|
396
|
+
config.dashboardUrl = runtime.dashboardUrl;
|
|
382
397
|
}
|
|
383
398
|
|
|
384
399
|
await writeJson(configPath, config);
|
|
@@ -1333,6 +1348,8 @@ function parseArgs(argv) {
|
|
|
1333
1348
|
const out = {
|
|
1334
1349
|
baseUrl: null,
|
|
1335
1350
|
dashboardUrl: null,
|
|
1351
|
+
anonKey: null,
|
|
1352
|
+
anonKeyFile: null,
|
|
1336
1353
|
email: null,
|
|
1337
1354
|
password: null,
|
|
1338
1355
|
deviceName: null,
|
|
@@ -1347,6 +1364,8 @@ function parseArgs(argv) {
|
|
|
1347
1364
|
const a = argv[i];
|
|
1348
1365
|
if (a === "--base-url") out.baseUrl = argv[++i] || null;
|
|
1349
1366
|
else if (a === "--dashboard-url") out.dashboardUrl = argv[++i] || null;
|
|
1367
|
+
else if (a === "--anon-key") out.anonKey = argv[++i] || null;
|
|
1368
|
+
else if (a === "--anon-key-file") out.anonKeyFile = argv[++i] || null;
|
|
1350
1369
|
else if (a === "--email") out.email = argv[++i] || null;
|
|
1351
1370
|
else if (a === "--password") out.password = argv[++i] || null;
|
|
1352
1371
|
else if (a === "--device-name") out.deviceName = argv[++i] || null;
|
package/src/commands/serve.js
CHANGED
|
@@ -159,7 +159,7 @@ async function cmdServe(argv) {
|
|
|
159
159
|
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
160
160
|
|
|
161
161
|
// CORS preflight
|
|
162
|
-
if (req.method === "OPTIONS") {
|
|
162
|
+
if (req.method === "OPTIONS" && !url.pathname.startsWith("/api/auth/")) {
|
|
163
163
|
res.writeHead(204, {
|
|
164
164
|
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
165
165
|
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
@@ -179,12 +179,12 @@ async function cmdServe(argv) {
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
// Static files
|
|
182
|
-
const served = await serveStaticFile(dashboardDir, url.pathname, res);
|
|
182
|
+
const served = await serveStaticFile(dashboardDir, url.pathname, res, { localRuntimeConfig: true });
|
|
183
183
|
if (served) return;
|
|
184
184
|
|
|
185
185
|
// SPA fallback
|
|
186
186
|
if (shouldServeSpaFallback(req, url)) {
|
|
187
|
-
await serveStaticFile(dashboardDir, "/index.html", res);
|
|
187
|
+
await serveStaticFile(dashboardDir, "/index.html", res, { localRuntimeConfig: true });
|
|
188
188
|
return;
|
|
189
189
|
}
|
|
190
190
|
|
package/src/commands/sync.js
CHANGED
|
@@ -156,7 +156,7 @@ const {
|
|
|
156
156
|
parseRetryAfterMs,
|
|
157
157
|
} = require("../lib/upload-throttle");
|
|
158
158
|
const AUTO_UPLOAD_CONFIG = {
|
|
159
|
-
intervalMs:
|
|
159
|
+
intervalMs: 15 * 60_000,
|
|
160
160
|
batchSize: 200,
|
|
161
161
|
maxBatchesSmall: 5,
|
|
162
162
|
maxBatchesLarge: 5,
|
|
@@ -507,6 +507,7 @@ async function clearSyncSkip(trackerDir) {
|
|
|
507
507
|
}
|
|
508
508
|
|
|
509
509
|
async function cmdSync(argv, context = {}) {
|
|
510
|
+
context = context && typeof context === "object" ? context : {};
|
|
510
511
|
const opts = parseArgs(argv);
|
|
511
512
|
const diagnostics = context && typeof context === "object" ? context.diagnostics : null;
|
|
512
513
|
const cursorStoreOptions = context && typeof context === "object"
|
|
@@ -528,8 +529,8 @@ async function cmdSync(argv, context = {}) {
|
|
|
528
529
|
? context.cursorSyncDeps
|
|
529
530
|
: {};
|
|
530
531
|
const syncDiagnostics = diagnostics && typeof diagnostics === "object" ? diagnostics : null;
|
|
531
|
-
const home = os.homedir();
|
|
532
|
-
const
|
|
532
|
+
const home = context.home || os.homedir();
|
|
533
|
+
const trackerDir = context.trackerDataDir || (await resolveTrackerPaths({ home })).trackerDir;
|
|
533
534
|
// Manual CLI sync is a one-time upload request, without changing the toggle.
|
|
534
535
|
// Hooks, native publication and detached retries must honor the saved opt-in.
|
|
535
536
|
const requiresCloudSyncPref = opts.auto || opts.background || opts.fromRetry || opts.fromNotify || opts.fromOpenclaw ||
|
|
@@ -589,7 +590,8 @@ async function cmdSync(argv, context = {}) {
|
|
|
589
590
|
// token unless the local API has already minted a replacement from the
|
|
590
591
|
// user's current login session and passed it through the environment.
|
|
591
592
|
// Ingest is a whole-row upsert per bucket key, making replay idempotent.
|
|
592
|
-
if (config && isLegacyInsforgeBaseUrl(config.baseUrl)
|
|
593
|
+
if (config && isLegacyInsforgeBaseUrl(config.baseUrl) &&
|
|
594
|
+
!Object.hasOwn(process.env, "TOKENTRACKER_LOCAL_SYNC_DEVICE_TOKEN")) {
|
|
593
595
|
const priorQueueState = (await readJson(queueStatePath)) || {};
|
|
594
596
|
// Prepare the replay exactly once. The note survives successful batches,
|
|
595
597
|
// so retryable failures retain both their committed offset and backoff
|
|
@@ -708,6 +710,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
708
710
|
const isBackgroundAllLocalSync = isBackgroundLightweightSync && opts.allLocalSources;
|
|
709
711
|
const isFullSourceScan = !autoSourceScope && !isBackgroundLightweightSync;
|
|
710
712
|
const sourceAllowed = (...sources) => {
|
|
713
|
+
if (Array.isArray(context.scanSources) && !sources.some(source => context.scanSources.includes(source))) return false;
|
|
711
714
|
if (isBackgroundLightweightSync) {
|
|
712
715
|
if (autoSourceScope) {
|
|
713
716
|
return (
|
|
@@ -3373,7 +3376,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
3373
3376
|
}
|
|
3374
3377
|
// An authenticated local API supplies this capability for this upload.
|
|
3375
3378
|
// Keep a separately configured CLI account from overriding its owner.
|
|
3376
|
-
const
|
|
3379
|
+
const resolvedRuntime = resolveRuntimeConfig({
|
|
3377
3380
|
cli: {
|
|
3378
3381
|
deviceToken: process.env.TOKENTRACKER_LOCAL_SYNC_DEVICE_TOKEN,
|
|
3379
3382
|
...(process.env.TOKENTRACKER_LOCAL_SYNC_DEVICE_TOKEN ? {
|
|
@@ -3385,6 +3388,12 @@ async function cmdSync(argv, context = {}) {
|
|
|
3385
3388
|
env: process.env,
|
|
3386
3389
|
});
|
|
3387
3390
|
|
|
3391
|
+
// Empty is an explicit local-server denial, so it cannot fall back to a
|
|
3392
|
+
// separately configured CLI account when the browser is signed out.
|
|
3393
|
+
const runtime = Object.hasOwn(process.env, "TOKENTRACKER_LOCAL_SYNC_DEVICE_TOKEN")
|
|
3394
|
+
? { ...resolvedRuntime, deviceToken: resolvedRuntime.configurationError ? null : process.env.TOKENTRACKER_LOCAL_SYNC_DEVICE_TOKEN }
|
|
3395
|
+
: resolvedRuntime;
|
|
3396
|
+
|
|
3388
3397
|
let uploadResult = { inserted: 0, skipped: 0 };
|
|
3389
3398
|
let uploadAttempted = false;
|
|
3390
3399
|
let autoUploadDecision = null;
|
|
@@ -3397,22 +3406,25 @@ async function cmdSync(argv, context = {}) {
|
|
|
3397
3406
|
0,
|
|
3398
3407
|
queueSizeBefore - Number(uploadStateBefore.offset || 0),
|
|
3399
3408
|
);
|
|
3400
|
-
//
|
|
3401
|
-
//
|
|
3402
|
-
|
|
3403
|
-
const bypassSuccessInterval = opts.publishAccount || opts.drain || legacyBaseUrlMigration;
|
|
3409
|
+
// Automatic producers honor success and failure deadlines. A verified
|
|
3410
|
+
// manual recheck may probe changed membership; the server decides access.
|
|
3411
|
+
const bypassSuccessInterval = (!opts.auto && opts.drain) || legacyBaseUrlMigration;
|
|
3404
3412
|
const lastSuccessMs = Number(uploadThrottleState.lastSuccessMs || 0);
|
|
3405
3413
|
const successDeadline = lastSuccessMs > 0
|
|
3406
|
-
? Math.min(Number(uploadThrottleState.nextAllowedAtMs || 0),
|
|
3407
|
-
lastSuccessMs + AUTO_UPLOAD_CONFIG.intervalMs + AUTO_UPLOAD_DEFAULTS.jitterMsMax)
|
|
3414
|
+
? Math.max(Number(uploadThrottleState.serverNextAllowedAtMs || 0), Math.min(Number(uploadThrottleState.nextAllowedAtMs || 0),
|
|
3415
|
+
lastSuccessMs + AUTO_UPLOAD_CONFIG.intervalMs + AUTO_UPLOAD_DEFAULTS.jitterMsMax))
|
|
3408
3416
|
: Number(uploadThrottleState.nextAllowedAtMs || 0);
|
|
3417
|
+
const recheckCloudAccess = opts.drain && process.env.TOKENTRACKER_CLOUD_RECHECK === "1" &&
|
|
3418
|
+
Boolean(process.env.TOKENTRACKER_LOCAL_SYNC_DEVICE_TOKEN?.trim());
|
|
3419
|
+
const storedBackoff = recheckCloudAccess ? 0 : Number(uploadThrottleState.backoffUntilMs || 0);
|
|
3409
3420
|
autoUploadDecision = decideAutoUpload({
|
|
3410
3421
|
nowMs: Date.now(),
|
|
3411
3422
|
pendingBytes: pendingBytesBefore,
|
|
3412
3423
|
state: {
|
|
3413
3424
|
...uploadThrottleState,
|
|
3414
|
-
|
|
3415
|
-
|
|
3425
|
+
backoffUntilMs: storedBackoff,
|
|
3426
|
+
nextAllowedAtMs: recheckCloudAccess ? 0 : bypassSuccessInterval
|
|
3427
|
+
? Math.max(storedBackoff, Number(uploadThrottleState.serverNextAllowedAtMs || 0))
|
|
3416
3428
|
: successDeadline,
|
|
3417
3429
|
},
|
|
3418
3430
|
config: AUTO_UPLOAD_CONFIG,
|
|
@@ -3458,6 +3470,7 @@ async function cmdSync(argv, context = {}) {
|
|
|
3458
3470
|
const replacementDeviceToken = legacyBaseUrlMigration?.replacementDeviceToken;
|
|
3459
3471
|
const stateAfterFailure = (await readJson(queueStatePath)) || { offset: 0 };
|
|
3460
3472
|
const canFallbackWithoutSplittingHistory =
|
|
3473
|
+
!Object.hasOwn(process.env, "TOKENTRACKER_LOCAL_SYNC_DEVICE_TOKEN") &&
|
|
3461
3474
|
(error?.status === 401 || error?.status === 403) &&
|
|
3462
3475
|
replacementDeviceToken &&
|
|
3463
3476
|
fallbackDeviceToken &&
|
|
@@ -3496,12 +3509,16 @@ async function cmdSync(argv, context = {}) {
|
|
|
3496
3509
|
}
|
|
3497
3510
|
// Record success so the exponential backoff step resets — otherwise
|
|
3498
3511
|
// a single past failure keeps us pessimistically throttled forever.
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3512
|
+
if (uploadResult.batches > 0) {
|
|
3513
|
+
uploadThrottleState = recordUploadSuccess({
|
|
3514
|
+
nowMs: Date.now(),
|
|
3515
|
+
state: uploadThrottleState,
|
|
3516
|
+
config: AUTO_UPLOAD_CONFIG,
|
|
3517
|
+
cloudAccess: uploadResult.cloudAccess,
|
|
3518
|
+
attemptId: process.env.TOKENTRACKER_LOCAL_SYNC_ATTEMPT_ID,
|
|
3519
|
+
});
|
|
3520
|
+
await writeJson(uploadThrottlePath, uploadThrottleState);
|
|
3521
|
+
}
|
|
3505
3522
|
} catch (e) {
|
|
3506
3523
|
// Persist a backoff on 429 / 5xx so the next auto-sync waits instead
|
|
3507
3524
|
// of retrying immediately and making the rate-limit worse. The
|
|
@@ -4191,6 +4208,8 @@ async function drainQueueToCloud({ baseUrl, anonKey, deviceToken, queuePath, que
|
|
|
4191
4208
|
let inserted = 0;
|
|
4192
4209
|
let skipped = 0;
|
|
4193
4210
|
let batches = 0;
|
|
4211
|
+
let cloudAccess = null;
|
|
4212
|
+
const uploadId = crypto.randomUUID();
|
|
4194
4213
|
|
|
4195
4214
|
const queueSize = await safeStatSize(queuePath);
|
|
4196
4215
|
const limit = Math.min(Math.max(1, Math.floor(Number(batchSize || 200))), MAX_INGEST_BUCKETS);
|
|
@@ -4216,6 +4235,7 @@ async function drainQueueToCloud({ baseUrl, anonKey, deviceToken, queuePath, que
|
|
|
4216
4235
|
method: "POST",
|
|
4217
4236
|
headers,
|
|
4218
4237
|
body: JSON.stringify({
|
|
4238
|
+
upload_id: uploadId,
|
|
4219
4239
|
hourly: result.buckets,
|
|
4220
4240
|
...(result.sessionStates.length > 0
|
|
4221
4241
|
? { account_session_states: result.sessionStates }
|
|
@@ -4230,14 +4250,15 @@ async function drainQueueToCloud({ baseUrl, anonKey, deviceToken, queuePath, que
|
|
|
4230
4250
|
const err = new Error(`HTTP ${res.status}: ${rawText.substring(0, 500)}`);
|
|
4231
4251
|
err.status = res.status;
|
|
4232
4252
|
err.code = res.status === 401 ? "CLOUD_DEVICE_TOKEN_REJECTED"
|
|
4233
|
-
:
|
|
4234
|
-
: "CLOUD_UPLOAD_FAILED";
|
|
4253
|
+
: typeof data?.code === "string" ? data.code
|
|
4254
|
+
: res.status === 403 ? "CLOUD_UPLOAD_FORBIDDEN" : "CLOUD_UPLOAD_FAILED";
|
|
4235
4255
|
const retryAfter = res.headers?.get?.("Retry-After") ?? null;
|
|
4236
4256
|
const retryAfterMs = parseRetryAfterMs(retryAfter);
|
|
4237
4257
|
if (retryAfterMs !== null) err.retryAfterMs = retryAfterMs;
|
|
4238
4258
|
throw err;
|
|
4239
4259
|
}
|
|
4240
4260
|
|
|
4261
|
+
cloudAccess = data;
|
|
4241
4262
|
inserted += Number(data?.inserted || 0);
|
|
4242
4263
|
skipped += Number(data?.skipped || 0);
|
|
4243
4264
|
batches += 1;
|
|
@@ -4248,7 +4269,7 @@ async function drainQueueToCloud({ baseUrl, anonKey, deviceToken, queuePath, que
|
|
|
4248
4269
|
await writeJson(queueStatePath, state);
|
|
4249
4270
|
}
|
|
4250
4271
|
|
|
4251
|
-
return { inserted, skipped, batches };
|
|
4272
|
+
return { inserted, skipped, batches, ...(cloudAccess?.next_allowed_at ? { cloudAccess } : {}) };
|
|
4252
4273
|
}
|
|
4253
4274
|
|
|
4254
4275
|
async function readQueueBatch(queuePath, startOffset, maxBuckets) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
const crypto = require("node:crypto");
|
|
4
|
+
|
|
5
|
+
function writeJsonAtomicSync(filePath, value) {
|
|
6
|
+
const contents = JSON.stringify(value, null, 2) + "\n";
|
|
7
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
8
|
+
const tmp = `${filePath}.tmp.${crypto.randomUUID()}`;
|
|
9
|
+
let fd;
|
|
10
|
+
let created = false;
|
|
11
|
+
try {
|
|
12
|
+
fd = fs.openSync(tmp, "wx", 0o600);
|
|
13
|
+
created = true;
|
|
14
|
+
fs.writeFileSync(fd, contents, "utf8");
|
|
15
|
+
fs.closeSync(fd);
|
|
16
|
+
fd = undefined;
|
|
17
|
+
// Replace the final directory entry rather than following a link that
|
|
18
|
+
// appeared after the caller read or checked the previous configuration.
|
|
19
|
+
fs.renameSync(tmp, filePath);
|
|
20
|
+
created = false;
|
|
21
|
+
} finally {
|
|
22
|
+
if (fd !== undefined) fs.closeSync(fd);
|
|
23
|
+
if (created) fs.unlinkSync(tmp);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
module.exports = { writeJsonAtomicSync };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
function parseBearerToken(authorization) {
|
|
2
|
+
// Match only the prefix so an all-whitespace header cannot make two
|
|
3
|
+
// overlapping quantifiers repeatedly backtrack over the same characters.
|
|
4
|
+
const value = String(authorization || "");
|
|
5
|
+
const prefix = /^Bearer[ \t]+/i.exec(value);
|
|
6
|
+
if (!prefix) return null;
|
|
7
|
+
const token = value.slice(prefix[0].length);
|
|
8
|
+
return token && !/[\r\n]/.test(token) ? token : null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = { parseBearerToken };
|
package/src/lib/cloud-account.js
CHANGED
|
@@ -383,6 +383,9 @@ async function fetchAccountFunction({
|
|
|
383
383
|
if (!res || !res.ok) {
|
|
384
384
|
const err = new Error(`Account fetch failed with HTTP ${res ? res.status : "?"}`);
|
|
385
385
|
err.status = res ? res.status : 0;
|
|
386
|
+
const failure = res?.json ? await res.json().catch(() => ({})) : {};
|
|
387
|
+
if (typeof failure?.code === "string") err.code = failure.code;
|
|
388
|
+
if (typeof failure?.recovery_url === "string") err.recoveryUrl = failure.recovery_url;
|
|
386
389
|
throw err;
|
|
387
390
|
}
|
|
388
391
|
const data = await res.json();
|
|
@@ -495,7 +498,7 @@ async function fetchAccountUsage({
|
|
|
495
498
|
}
|
|
496
499
|
return JSON.stringify(data);
|
|
497
500
|
} catch (error) {
|
|
498
|
-
if (
|
|
501
|
+
if (error.status === 401 && sessionAtStart === sessionGeneration) {
|
|
499
502
|
invalidateCloudAccountPayloadCache({ sessionChanged: true });
|
|
500
503
|
error.invalidatedSessionGeneration = sessionGeneration;
|
|
501
504
|
}
|