jh-web-gateway 2.0.1 → 2.1.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/dist/{chunk-J7PNSI42.js → chunk-TNKXXCTQ.js} +208 -141
- package/dist/chunk-TNKXXCTQ.js.map +1 -0
- package/dist/cli.js +28 -21
- package/dist/cli.js.map +1 -1
- package/dist/{tui-2J5JN3FF.js → tui-YGSNFLO7.js} +4 -5
- package/dist/tui-YGSNFLO7.js.map +1 -0
- package/package.json +2 -1
- package/dist/chunk-J7PNSI42.js.map +0 -1
- package/dist/tui-2J5JN3FF.js.map +0 -1
|
@@ -64,12 +64,15 @@ async function findOrOpenJhPage(browser) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
// src/infra/config.ts
|
|
67
|
-
import { readFile, writeFile, mkdir } from "fs/promises";
|
|
67
|
+
import { readFile, writeFile, mkdir, chmod } from "fs/promises";
|
|
68
68
|
import { homedir } from "os";
|
|
69
69
|
import { join } from "path";
|
|
70
70
|
function getConfigPath() {
|
|
71
71
|
return join(homedir(), ".jh-gateway", "config.json");
|
|
72
72
|
}
|
|
73
|
+
function getConfigDir() {
|
|
74
|
+
return join(homedir(), ".jh-gateway");
|
|
75
|
+
}
|
|
73
76
|
function getDefaultConfig() {
|
|
74
77
|
return {
|
|
75
78
|
cdpUrl: "http://127.0.0.1:9222",
|
|
@@ -162,15 +165,13 @@ function validateConfig(raw) {
|
|
|
162
165
|
}
|
|
163
166
|
async function loadConfig() {
|
|
164
167
|
const configPath = getConfigPath();
|
|
165
|
-
const configDir = join(homedir(), ".jh-gateway");
|
|
166
168
|
let raw;
|
|
167
169
|
try {
|
|
168
170
|
raw = await readFile(configPath, "utf8");
|
|
169
171
|
} catch (err) {
|
|
170
172
|
if (isNodeError(err) && (err.code === "ENOENT" || err.code === "ENOTDIR")) {
|
|
171
173
|
const defaults = getDefaultConfig();
|
|
172
|
-
await
|
|
173
|
-
await writeFile(configPath, JSON.stringify(defaults, null, 2), "utf8");
|
|
174
|
+
await saveConfig(defaults);
|
|
174
175
|
return defaults;
|
|
175
176
|
}
|
|
176
177
|
throw err;
|
|
@@ -183,13 +184,19 @@ async function loadConfig() {
|
|
|
183
184
|
`Config validation error: config file at ${configPath} contains malformed JSON`
|
|
184
185
|
);
|
|
185
186
|
}
|
|
186
|
-
|
|
187
|
+
const validated = validateConfig(parsed);
|
|
188
|
+
await enforceConfigPermissions(configPath, getConfigDir());
|
|
189
|
+
return validated;
|
|
187
190
|
}
|
|
188
191
|
async function saveConfig(config) {
|
|
189
192
|
const configPath = getConfigPath();
|
|
190
|
-
const configDir =
|
|
191
|
-
await mkdir(configDir, { recursive: true });
|
|
192
|
-
await writeFile(configPath, JSON.stringify(config, null, 2),
|
|
193
|
+
const configDir = getConfigDir();
|
|
194
|
+
await mkdir(configDir, { recursive: true, mode: 448 });
|
|
195
|
+
await writeFile(configPath, JSON.stringify(config, null, 2), {
|
|
196
|
+
encoding: "utf8",
|
|
197
|
+
mode: 384
|
|
198
|
+
});
|
|
199
|
+
await enforceConfigPermissions(configPath, configDir);
|
|
193
200
|
}
|
|
194
201
|
async function updateConfig(partial) {
|
|
195
202
|
const current = await loadConfig();
|
|
@@ -202,9 +209,26 @@ async function updateConfig(partial) {
|
|
|
202
209
|
function isNodeError(err) {
|
|
203
210
|
return err instanceof Error && "code" in err;
|
|
204
211
|
}
|
|
212
|
+
async function enforceConfigPermissions(configPath, configDir) {
|
|
213
|
+
await chmod(configDir, 448).catch((err) => {
|
|
214
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
215
|
+
console.warn(`[config] Could not enforce secure directory permissions: ${message}`);
|
|
216
|
+
});
|
|
217
|
+
await chmod(configPath, 384).catch((err) => {
|
|
218
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
219
|
+
console.warn(`[config] Could not enforce secure file permissions: ${message}`);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
205
222
|
|
|
206
223
|
// src/core/auth-capture.ts
|
|
207
224
|
var JH_HOST = "chat.ai.jh.edu";
|
|
225
|
+
function isJhUrl(url) {
|
|
226
|
+
try {
|
|
227
|
+
return new URL(url).hostname === JH_HOST;
|
|
228
|
+
} catch {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
208
232
|
function getTokenExpiry(token) {
|
|
209
233
|
try {
|
|
210
234
|
const parts = token.split(".");
|
|
@@ -238,24 +262,15 @@ async function captureCredentials(cdpUrl, timeoutMs = 12e4) {
|
|
|
238
262
|
page = await context.newPage();
|
|
239
263
|
}
|
|
240
264
|
const targetPage = page;
|
|
265
|
+
const routePattern = "**/*";
|
|
241
266
|
return new Promise((resolve, reject) => {
|
|
242
267
|
let settled = false;
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
settled = true;
|
|
246
|
-
reject(
|
|
247
|
-
new Error(
|
|
248
|
-
`Credential capture timed out after ${timeoutMs / 1e3}s. Please log in to chat.ai.jh.edu and send a message to trigger authentication.`
|
|
249
|
-
)
|
|
250
|
-
);
|
|
251
|
-
}, timeoutMs);
|
|
252
|
-
targetPage.route("**/*", async (route) => {
|
|
268
|
+
let cleanedUp = false;
|
|
269
|
+
const routeHandler = async (route) => {
|
|
253
270
|
const request = route.request();
|
|
254
271
|
const headers = await request.headers();
|
|
255
272
|
const authHeader = headers["authorization"] ?? headers["Authorization"] ?? "";
|
|
256
|
-
if (!settled && authHeader.startsWith("Bearer ") && request.url()
|
|
257
|
-
settled = true;
|
|
258
|
-
clearTimeout(timer);
|
|
273
|
+
if (!settled && authHeader.startsWith("Bearer ") && isJhUrl(request.url())) {
|
|
259
274
|
try {
|
|
260
275
|
const bearerToken = authHeader.slice("Bearer ".length).trim();
|
|
261
276
|
const rawCookies = await targetPage.context().cookies();
|
|
@@ -271,14 +286,45 @@ async function captureCredentials(cdpUrl, timeoutMs = 12e4) {
|
|
|
271
286
|
expiresAt
|
|
272
287
|
};
|
|
273
288
|
await updateConfig({ credentials: captured });
|
|
274
|
-
|
|
289
|
+
settleSuccess(captured);
|
|
275
290
|
} catch (err) {
|
|
276
|
-
|
|
291
|
+
settleFailure(err);
|
|
277
292
|
}
|
|
278
293
|
}
|
|
279
|
-
await route.continue()
|
|
280
|
-
|
|
281
|
-
|
|
294
|
+
await route.continue().catch((err) => {
|
|
295
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
296
|
+
console.warn(`[auth-capture] Route continuation failed: ${message}`);
|
|
297
|
+
});
|
|
298
|
+
};
|
|
299
|
+
const cleanup = async () => {
|
|
300
|
+
if (cleanedUp) return;
|
|
301
|
+
cleanedUp = true;
|
|
302
|
+
clearTimeout(timer);
|
|
303
|
+
await targetPage.unroute(routePattern, routeHandler).catch((err) => {
|
|
304
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
305
|
+
console.warn(`[auth-capture] Route cleanup failed: ${message}`);
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
const settleSuccess = (captured) => {
|
|
309
|
+
if (settled) return;
|
|
310
|
+
settled = true;
|
|
311
|
+
void cleanup().finally(() => resolve(captured));
|
|
312
|
+
};
|
|
313
|
+
const settleFailure = (err) => {
|
|
314
|
+
if (settled) return;
|
|
315
|
+
settled = true;
|
|
316
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
317
|
+
void cleanup().finally(() => reject(error));
|
|
318
|
+
};
|
|
319
|
+
const timer = setTimeout(() => {
|
|
320
|
+
settleFailure(
|
|
321
|
+
new Error(
|
|
322
|
+
`Credential capture timed out after ${timeoutMs / 1e3}s. Please log in to chat.ai.jh.edu and send a message to trigger authentication.`
|
|
323
|
+
)
|
|
324
|
+
);
|
|
325
|
+
}, timeoutMs);
|
|
326
|
+
targetPage.route(routePattern, routeHandler).then(() => {
|
|
327
|
+
if (!isJhUrl(targetPage.url())) {
|
|
282
328
|
targetPage.goto(`https://${JH_HOST}`, { waitUntil: "commit" }).catch(() => {
|
|
283
329
|
});
|
|
284
330
|
} else {
|
|
@@ -286,11 +332,7 @@ async function captureCredentials(cdpUrl, timeoutMs = 12e4) {
|
|
|
286
332
|
});
|
|
287
333
|
}
|
|
288
334
|
}).catch((err) => {
|
|
289
|
-
|
|
290
|
-
settled = true;
|
|
291
|
-
clearTimeout(timer);
|
|
292
|
-
reject(err);
|
|
293
|
-
}
|
|
335
|
+
settleFailure(err);
|
|
294
336
|
});
|
|
295
337
|
});
|
|
296
338
|
}
|
|
@@ -316,7 +358,7 @@ async function captureCredentialsActive(cdpUrl, timeoutMs = 3e4) {
|
|
|
316
358
|
const request = route.request();
|
|
317
359
|
const headers = await request.headers();
|
|
318
360
|
const authHeader = headers["authorization"] ?? headers["Authorization"] ?? "";
|
|
319
|
-
if (!settled && authHeader.startsWith("Bearer ") && request.url()
|
|
361
|
+
if (!settled && authHeader.startsWith("Bearer ") && isJhUrl(request.url())) {
|
|
320
362
|
settled = true;
|
|
321
363
|
clearTimeout(timer);
|
|
322
364
|
try {
|
|
@@ -360,6 +402,7 @@ async function captureCredentialsActive(cdpUrl, timeoutMs = 3e4) {
|
|
|
360
402
|
|
|
361
403
|
// src/infra/gateway-auth.ts
|
|
362
404
|
import { randomBytes } from "crypto";
|
|
405
|
+
import { timingSafeEqual } from "crypto";
|
|
363
406
|
function generateApiKey() {
|
|
364
407
|
return `jh-local-${randomBytes(16).toString("hex")}`;
|
|
365
408
|
}
|
|
@@ -385,7 +428,7 @@ function authMiddleware(config) {
|
|
|
385
428
|
}
|
|
386
429
|
if (mode === "bearer") {
|
|
387
430
|
const expected = `Bearer ${token}`;
|
|
388
|
-
if (authHeader
|
|
431
|
+
if (!safeEquals(authHeader, expected)) {
|
|
389
432
|
return c.json(
|
|
390
433
|
{
|
|
391
434
|
error: {
|
|
@@ -400,7 +443,7 @@ function authMiddleware(config) {
|
|
|
400
443
|
}
|
|
401
444
|
} else if (mode === "basic") {
|
|
402
445
|
const expected = `Basic ${Buffer.from(`gateway:${token}`).toString("base64")}`;
|
|
403
|
-
if (authHeader
|
|
446
|
+
if (!safeEquals(authHeader, expected)) {
|
|
404
447
|
return c.json(
|
|
405
448
|
{
|
|
406
449
|
error: {
|
|
@@ -417,6 +460,12 @@ function authMiddleware(config) {
|
|
|
417
460
|
return next();
|
|
418
461
|
};
|
|
419
462
|
}
|
|
463
|
+
function safeEquals(actual, expected) {
|
|
464
|
+
const actualBuffer = Buffer.from(actual);
|
|
465
|
+
const expectedBuffer = Buffer.from(expected);
|
|
466
|
+
if (actualBuffer.length !== expectedBuffer.length) return false;
|
|
467
|
+
return timingSafeEqual(actualBuffer, expectedBuffer);
|
|
468
|
+
}
|
|
420
469
|
|
|
421
470
|
// src/infra/types.ts
|
|
422
471
|
var MODEL_ENDPOINT_MAP = {
|
|
@@ -1667,6 +1716,7 @@ var PagePool = class {
|
|
|
1667
1716
|
maxPages;
|
|
1668
1717
|
maxWaitMs;
|
|
1669
1718
|
initPromise = null;
|
|
1719
|
+
pagesCreating = 0;
|
|
1670
1720
|
constructor(options = {}) {
|
|
1671
1721
|
this.targetUrl = options.targetUrl ?? "https://chat.ai.jh.edu";
|
|
1672
1722
|
this.maxPages = options.maxPages ?? 3;
|
|
@@ -1703,8 +1753,13 @@ var PagePool = class {
|
|
|
1703
1753
|
*/
|
|
1704
1754
|
async acquire() {
|
|
1705
1755
|
let pooled = this.pages.find((p2) => !p2.inUse);
|
|
1706
|
-
if (!pooled && this.pages.length < this.maxPages && this.browser) {
|
|
1707
|
-
|
|
1756
|
+
if (!pooled && this.pages.length + this.pagesCreating < this.maxPages && this.browser) {
|
|
1757
|
+
this.pagesCreating++;
|
|
1758
|
+
try {
|
|
1759
|
+
pooled = await this.createPage();
|
|
1760
|
+
} finally {
|
|
1761
|
+
this.pagesCreating--;
|
|
1762
|
+
}
|
|
1708
1763
|
}
|
|
1709
1764
|
if (!pooled) {
|
|
1710
1765
|
pooled = this.pages.reduce(
|
|
@@ -1731,15 +1786,27 @@ var PagePool = class {
|
|
|
1731
1786
|
throw new Error("No browser context available");
|
|
1732
1787
|
}
|
|
1733
1788
|
const page = await context.newPage();
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1789
|
+
try {
|
|
1790
|
+
await page.goto(this.targetUrl, { waitUntil: "networkidle", timeout: 3e4 });
|
|
1791
|
+
const finalUrl = page.url();
|
|
1792
|
+
if (!finalUrl.includes("chat.ai.jh.edu")) {
|
|
1793
|
+
throw new Error(
|
|
1794
|
+
`New page redirected away from target: ${finalUrl} \u2014 browser session may have expired, restart without --headless to re-login.`
|
|
1795
|
+
);
|
|
1796
|
+
}
|
|
1797
|
+
console.log(`[PagePool] New page ready: ${finalUrl}`);
|
|
1798
|
+
const pooled = {
|
|
1799
|
+
page,
|
|
1800
|
+
queue: new RequestQueue(this.maxWaitMs),
|
|
1801
|
+
inUse: false
|
|
1802
|
+
};
|
|
1803
|
+
this.pages.push(pooled);
|
|
1804
|
+
return pooled;
|
|
1805
|
+
} catch (err) {
|
|
1806
|
+
await page.close().catch(() => {
|
|
1807
|
+
});
|
|
1808
|
+
throw err;
|
|
1809
|
+
}
|
|
1743
1810
|
}
|
|
1744
1811
|
/** Close all pages except the seed page */
|
|
1745
1812
|
async drain() {
|
|
@@ -1755,6 +1822,99 @@ var PagePool = class {
|
|
|
1755
1822
|
}
|
|
1756
1823
|
};
|
|
1757
1824
|
|
|
1825
|
+
// src/core/token-refresher.ts
|
|
1826
|
+
var CredentialHolder = class {
|
|
1827
|
+
creds = null;
|
|
1828
|
+
/** Returns the current credentials, or `null` if none have been set yet. */
|
|
1829
|
+
get() {
|
|
1830
|
+
return this.creds;
|
|
1831
|
+
}
|
|
1832
|
+
/** Atomically replaces the stored credentials. */
|
|
1833
|
+
set(creds) {
|
|
1834
|
+
this.creds = creds;
|
|
1835
|
+
}
|
|
1836
|
+
};
|
|
1837
|
+
function shouldRefresh(nowMs, expiresAt, thresholdMs) {
|
|
1838
|
+
return expiresAt * 1e3 - nowMs < thresholdMs;
|
|
1839
|
+
}
|
|
1840
|
+
var BACKOFF_DELAYS = [5e3, 15e3, 3e4];
|
|
1841
|
+
var TokenRefresher = class {
|
|
1842
|
+
credentialHolder;
|
|
1843
|
+
cdpUrl;
|
|
1844
|
+
checkIntervalMs;
|
|
1845
|
+
refreshBeforeExpiryMs;
|
|
1846
|
+
maxRetries;
|
|
1847
|
+
intervalId = null;
|
|
1848
|
+
constructor(credentialHolder, cdpUrl, options) {
|
|
1849
|
+
this.credentialHolder = credentialHolder;
|
|
1850
|
+
this.cdpUrl = cdpUrl;
|
|
1851
|
+
this.checkIntervalMs = options?.checkIntervalMs ?? 6e4;
|
|
1852
|
+
this.refreshBeforeExpiryMs = options?.refreshBeforeExpiryMs ?? 3e5;
|
|
1853
|
+
this.maxRetries = options?.maxRetries ?? 3;
|
|
1854
|
+
}
|
|
1855
|
+
/** Start the background check interval. */
|
|
1856
|
+
start() {
|
|
1857
|
+
if (this.intervalId !== null) return;
|
|
1858
|
+
this.intervalId = setInterval(() => {
|
|
1859
|
+
void this.checkAndRefresh();
|
|
1860
|
+
}, this.checkIntervalMs);
|
|
1861
|
+
}
|
|
1862
|
+
/** Stop the background check interval. */
|
|
1863
|
+
stop() {
|
|
1864
|
+
if (this.intervalId !== null) {
|
|
1865
|
+
clearInterval(this.intervalId);
|
|
1866
|
+
this.intervalId = null;
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
/** Check if a refresh is needed and perform it. Returns true if refreshed. */
|
|
1870
|
+
async checkAndRefresh() {
|
|
1871
|
+
const creds = this.credentialHolder.get();
|
|
1872
|
+
if (!creds || !creds.expiresAt) {
|
|
1873
|
+
return false;
|
|
1874
|
+
}
|
|
1875
|
+
if (!shouldRefresh(Date.now(), creds.expiresAt, this.refreshBeforeExpiryMs)) {
|
|
1876
|
+
return false;
|
|
1877
|
+
}
|
|
1878
|
+
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
|
|
1879
|
+
try {
|
|
1880
|
+
const newCreds = await captureCredentialsActive(this.cdpUrl);
|
|
1881
|
+
const gatewayCreds = {
|
|
1882
|
+
bearerToken: newCreds.bearerToken,
|
|
1883
|
+
cookie: newCreds.cookie,
|
|
1884
|
+
userAgent: newCreds.userAgent,
|
|
1885
|
+
expiresAt: newCreds.expiresAt
|
|
1886
|
+
};
|
|
1887
|
+
this.credentialHolder.set(gatewayCreds);
|
|
1888
|
+
await updateConfig({ credentials: gatewayCreds });
|
|
1889
|
+
const expiryDate = new Date(newCreds.expiresAt * 1e3).toISOString();
|
|
1890
|
+
console.log(`[TokenRefresher] Credentials refreshed successfully. New expiry: ${expiryDate}`);
|
|
1891
|
+
return true;
|
|
1892
|
+
} catch (err) {
|
|
1893
|
+
const delay = BACKOFF_DELAYS[attempt] ?? BACKOFF_DELAYS[BACKOFF_DELAYS.length - 1];
|
|
1894
|
+
if (attempt < this.maxRetries - 1) {
|
|
1895
|
+
console.warn(
|
|
1896
|
+
`[TokenRefresher] Refresh attempt ${attempt + 1}/${this.maxRetries} failed, retrying in ${delay / 1e3}s...`
|
|
1897
|
+
);
|
|
1898
|
+
await this.sleep(delay);
|
|
1899
|
+
} else {
|
|
1900
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1901
|
+
console.warn(
|
|
1902
|
+
`
|
|
1903
|
+
\u26A0\uFE0F [TokenRefresher] All ${this.maxRetries} refresh attempts failed. ${msg}
|
|
1904
|
+
Continuing with current credentials. If requests start failing with 401,
|
|
1905
|
+
restart with \`jh-gateway start\` (without --headless) to re-login.
|
|
1906
|
+
`
|
|
1907
|
+
);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
return false;
|
|
1912
|
+
}
|
|
1913
|
+
sleep(ms) {
|
|
1914
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1915
|
+
}
|
|
1916
|
+
};
|
|
1917
|
+
|
|
1758
1918
|
// src/infra/chrome-manager.ts
|
|
1759
1919
|
import { existsSync } from "fs";
|
|
1760
1920
|
import { execSync, spawn } from "child_process";
|
|
@@ -1996,99 +2156,6 @@ Expected locations for ${process.platform}:
|
|
|
1996
2156
|
}
|
|
1997
2157
|
};
|
|
1998
2158
|
|
|
1999
|
-
// src/core/token-refresher.ts
|
|
2000
|
-
var CredentialHolder = class {
|
|
2001
|
-
creds = null;
|
|
2002
|
-
/** Returns the current credentials, or `null` if none have been set yet. */
|
|
2003
|
-
get() {
|
|
2004
|
-
return this.creds;
|
|
2005
|
-
}
|
|
2006
|
-
/** Atomically replaces the stored credentials. */
|
|
2007
|
-
set(creds) {
|
|
2008
|
-
this.creds = creds;
|
|
2009
|
-
}
|
|
2010
|
-
};
|
|
2011
|
-
function shouldRefresh(nowMs, expiresAt, thresholdMs) {
|
|
2012
|
-
return expiresAt * 1e3 - nowMs < thresholdMs;
|
|
2013
|
-
}
|
|
2014
|
-
var BACKOFF_DELAYS = [5e3, 15e3, 3e4];
|
|
2015
|
-
var TokenRefresher = class {
|
|
2016
|
-
credentialHolder;
|
|
2017
|
-
cdpUrl;
|
|
2018
|
-
checkIntervalMs;
|
|
2019
|
-
refreshBeforeExpiryMs;
|
|
2020
|
-
maxRetries;
|
|
2021
|
-
intervalId = null;
|
|
2022
|
-
constructor(credentialHolder, cdpUrl, options) {
|
|
2023
|
-
this.credentialHolder = credentialHolder;
|
|
2024
|
-
this.cdpUrl = cdpUrl;
|
|
2025
|
-
this.checkIntervalMs = options?.checkIntervalMs ?? 6e4;
|
|
2026
|
-
this.refreshBeforeExpiryMs = options?.refreshBeforeExpiryMs ?? 3e5;
|
|
2027
|
-
this.maxRetries = options?.maxRetries ?? 3;
|
|
2028
|
-
}
|
|
2029
|
-
/** Start the background check interval. */
|
|
2030
|
-
start() {
|
|
2031
|
-
if (this.intervalId !== null) return;
|
|
2032
|
-
this.intervalId = setInterval(() => {
|
|
2033
|
-
void this.checkAndRefresh();
|
|
2034
|
-
}, this.checkIntervalMs);
|
|
2035
|
-
}
|
|
2036
|
-
/** Stop the background check interval. */
|
|
2037
|
-
stop() {
|
|
2038
|
-
if (this.intervalId !== null) {
|
|
2039
|
-
clearInterval(this.intervalId);
|
|
2040
|
-
this.intervalId = null;
|
|
2041
|
-
}
|
|
2042
|
-
}
|
|
2043
|
-
/** Check if a refresh is needed and perform it. Returns true if refreshed. */
|
|
2044
|
-
async checkAndRefresh() {
|
|
2045
|
-
const creds = this.credentialHolder.get();
|
|
2046
|
-
if (!creds || !creds.expiresAt) {
|
|
2047
|
-
return false;
|
|
2048
|
-
}
|
|
2049
|
-
if (!shouldRefresh(Date.now(), creds.expiresAt, this.refreshBeforeExpiryMs)) {
|
|
2050
|
-
return false;
|
|
2051
|
-
}
|
|
2052
|
-
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
|
|
2053
|
-
try {
|
|
2054
|
-
const newCreds = await captureCredentialsActive(this.cdpUrl);
|
|
2055
|
-
const gatewayCreds = {
|
|
2056
|
-
bearerToken: newCreds.bearerToken,
|
|
2057
|
-
cookie: newCreds.cookie,
|
|
2058
|
-
userAgent: newCreds.userAgent,
|
|
2059
|
-
expiresAt: newCreds.expiresAt
|
|
2060
|
-
};
|
|
2061
|
-
this.credentialHolder.set(gatewayCreds);
|
|
2062
|
-
await updateConfig({ credentials: gatewayCreds });
|
|
2063
|
-
const expiryDate = new Date(newCreds.expiresAt * 1e3).toISOString();
|
|
2064
|
-
console.log(`[TokenRefresher] Credentials refreshed successfully. New expiry: ${expiryDate}`);
|
|
2065
|
-
return true;
|
|
2066
|
-
} catch (err) {
|
|
2067
|
-
const delay = BACKOFF_DELAYS[attempt] ?? BACKOFF_DELAYS[BACKOFF_DELAYS.length - 1];
|
|
2068
|
-
if (attempt < this.maxRetries - 1) {
|
|
2069
|
-
console.warn(
|
|
2070
|
-
`[TokenRefresher] Refresh attempt ${attempt + 1}/${this.maxRetries} failed, retrying in ${delay / 1e3}s...`
|
|
2071
|
-
);
|
|
2072
|
-
await this.sleep(delay);
|
|
2073
|
-
} else {
|
|
2074
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2075
|
-
console.warn(
|
|
2076
|
-
`
|
|
2077
|
-
\u26A0\uFE0F [TokenRefresher] All ${this.maxRetries} refresh attempts failed. ${msg}
|
|
2078
|
-
Continuing with current credentials. If requests start failing with 401,
|
|
2079
|
-
restart with \`jh-gateway start\` (without --headless) to re-login.
|
|
2080
|
-
`
|
|
2081
|
-
);
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
return false;
|
|
2086
|
-
}
|
|
2087
|
-
sleep(ms) {
|
|
2088
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
2089
|
-
}
|
|
2090
|
-
};
|
|
2091
|
-
|
|
2092
2159
|
export {
|
|
2093
2160
|
getChromeWebSocketUrl,
|
|
2094
2161
|
connectToChrome,
|
|
@@ -2104,9 +2171,9 @@ export {
|
|
|
2104
2171
|
isTokenExpired,
|
|
2105
2172
|
startServer,
|
|
2106
2173
|
PagePool,
|
|
2107
|
-
ChromeManager,
|
|
2108
2174
|
CredentialHolder,
|
|
2109
2175
|
shouldRefresh,
|
|
2110
|
-
TokenRefresher
|
|
2176
|
+
TokenRefresher,
|
|
2177
|
+
ChromeManager
|
|
2111
2178
|
};
|
|
2112
|
-
//# sourceMappingURL=chunk-
|
|
2179
|
+
//# sourceMappingURL=chunk-TNKXXCTQ.js.map
|