fix: allow cookie timeout config
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
- Query ID updater now tracks Following/Followers GraphQL operations.
|
||||
- Query ID updater now tracks BookmarkFolderTimeline and keeps bookmark query IDs seeded.
|
||||
- `following`/`followers` JSON user fields are now camelCase (`followersCount`, `followingCount`, `isBlueVerified`, `profileImageUrl`, `createdAt`).
|
||||
- Cookie extraction timeout is now configurable (default 20s on macOS) via `--cookie-timeout` / `BIRD_COOKIE_TIMEOUT_MS`.
|
||||
## 0.4.1 — 2025-12-31
|
||||
|
||||
### Added
|
||||
|
||||
@@ -88,6 +88,7 @@ bird query-ids --fresh
|
||||
|
||||
Global options:
|
||||
- `--timeout <ms>`: abort requests after the given timeout (milliseconds).
|
||||
- `--cookie-timeout <ms>`: cookie extraction timeout for keychain/OS helpers (milliseconds).
|
||||
- `--quote-depth <n>`: max quoted tweet depth in JSON output (default: 1; 0 disables).
|
||||
- `--plain`: stable output (no emoji, no color).
|
||||
- `--no-emoji`: disable emoji output.
|
||||
@@ -128,6 +129,7 @@ Example `~/.config/bird/config.json5`:
|
||||
// Cookie source order for browser extraction (string or array)
|
||||
cookieSource: ["firefox", "safari"],
|
||||
firefoxProfile: "default-release",
|
||||
cookieTimeoutMs: 20000,
|
||||
timeoutMs: 20000,
|
||||
quoteDepth: 1
|
||||
}
|
||||
@@ -135,6 +137,7 @@ Example `~/.config/bird/config.json5`:
|
||||
|
||||
Environment shortcuts:
|
||||
- `BIRD_TIMEOUT_MS`
|
||||
- `BIRD_COOKIE_TIMEOUT_MS`
|
||||
- `BIRD_QUOTE_DEPTH`
|
||||
|
||||
## Output
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
diff --git a/dist/providers/chromeSqliteMac.js b/dist/providers/chromeSqliteMac.js
|
||||
index 71c815f77378d03e07061976ac372b3a55a61ac4..3a85a2fce102128d7e7b69145d97bc353f663008 100644
|
||||
--- a/dist/providers/chromeSqliteMac.js
|
||||
+++ b/dist/providers/chromeSqliteMac.js
|
||||
@@ -15,7 +15,7 @@ export async function getCookiesFromChromeSqliteMac(options, origins, allowlistN
|
||||
const passwordResult = await readKeychainGenericPasswordFirst({
|
||||
account: 'Chrome',
|
||||
services: ['Chrome Safe Storage'],
|
||||
- timeoutMs: 3_000,
|
||||
+ timeoutMs: options.timeoutMs ?? 3_000,
|
||||
label: 'Chrome Safe Storage',
|
||||
});
|
||||
if (!passwordResult.ok) {
|
||||
Generated
+7
-2
@@ -4,13 +4,18 @@ settings:
|
||||
autoInstallPeers: false
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
patchedDependencies:
|
||||
'@steipete/sweet-cookie':
|
||||
hash: 05180a0fa74ba5074775f965db7d4561e59ba51978bd5e955c0e4f058a4ed340
|
||||
path: patches/@steipete__sweet-cookie.patch
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@steipete/sweet-cookie':
|
||||
specifier: 0.1.0
|
||||
version: 0.1.0
|
||||
version: 0.1.0(patch_hash=05180a0fa74ba5074775f965db7d4561e59ba51978bd5e955c0e4f058a4ed340)
|
||||
commander:
|
||||
specifier: ^14.0.2
|
||||
version: 14.0.2
|
||||
@@ -1051,7 +1056,7 @@ snapshots:
|
||||
|
||||
'@standard-schema/[email protected]': {}
|
||||
|
||||
'@steipete/[email protected]': {}
|
||||
'@steipete/[email protected](patch_hash=05180a0fa74ba5074775f965db7d4561e59ba51978bd5e955c0e4f058a4ed340)': {}
|
||||
|
||||
'@types/[email protected]':
|
||||
dependencies:
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
onlyBuiltDependencies:
|
||||
- esbuild
|
||||
|
||||
patchedDependencies:
|
||||
'@steipete/sweet-cookie': patches/@steipete__sweet-cookie.patch
|
||||
|
||||
@@ -108,6 +108,7 @@ type BirdConfig = {
|
||||
chromeProfile?: string;
|
||||
firefoxProfile?: string;
|
||||
cookieSource?: CookieSource | CookieSource[];
|
||||
cookieTimeoutMs?: number;
|
||||
timeoutMs?: number;
|
||||
quoteDepth?: number;
|
||||
};
|
||||
@@ -187,6 +188,7 @@ program
|
||||
.option('--ct0 <token>', 'Twitter ct0 cookie')
|
||||
.option('--chrome-profile <name>', 'Chrome profile name for cookie extraction', config.chromeProfile)
|
||||
.option('--firefox-profile <name>', 'Firefox profile name for cookie extraction', config.firefoxProfile)
|
||||
.option('--cookie-timeout <ms>', 'Cookie extraction timeout in milliseconds (keychain/OS helpers)')
|
||||
.option(
|
||||
'--cookie-source <source>',
|
||||
'Cookie source for browser cookie extraction (repeatable)',
|
||||
@@ -207,6 +209,7 @@ type CredentialsOptions = {
|
||||
chromeProfile?: string;
|
||||
firefoxProfile?: string;
|
||||
cookieSource?: CookieSource[];
|
||||
cookieTimeout?: string | number;
|
||||
};
|
||||
|
||||
function resolveCredentialsFromOptions(opts: CredentialsOptions): ReturnType<typeof resolveCredentials> {
|
||||
@@ -219,6 +222,7 @@ function resolveCredentialsFromOptions(opts: CredentialsOptions): ReturnType<typ
|
||||
cookieSource,
|
||||
chromeProfile: opts.chromeProfile || config.chromeProfile,
|
||||
firefoxProfile: opts.firefoxProfile || config.firefoxProfile,
|
||||
cookieTimeoutMs: resolveCookieTimeoutFromOptions(opts),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -245,6 +249,10 @@ function resolveTimeoutFromOptions(options: { timeout?: string | number }): numb
|
||||
return resolveTimeoutMs(options.timeout, config.timeoutMs, process.env.BIRD_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
function resolveCookieTimeoutFromOptions(options: { cookieTimeout?: string | number }): number | undefined {
|
||||
return resolveTimeoutMs(options.cookieTimeout, config.cookieTimeoutMs, process.env.BIRD_COOKIE_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
function resolveQuoteDepth(...values: Array<string | number | undefined | null>): number | undefined {
|
||||
for (const value of values) {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
|
||||
@@ -22,6 +22,7 @@ export type CookieSource = 'safari' | 'chrome' | 'firefox';
|
||||
const TWITTER_COOKIE_NAMES = ['auth_token', 'ct0'] as const;
|
||||
const TWITTER_URL = 'https://x.com/';
|
||||
const TWITTER_ORIGINS: string[] = ['https://x.com/', 'https://twitter.com/'];
|
||||
const DEFAULT_COOKIE_TIMEOUT_MS = 20_000;
|
||||
|
||||
function normalizeValue(value: unknown): string | null {
|
||||
if (typeof value !== 'string') {
|
||||
@@ -102,6 +103,7 @@ async function readTwitterCookiesFromBrowser(options: {
|
||||
source: CookieSource;
|
||||
chromeProfile?: string;
|
||||
firefoxProfile?: string;
|
||||
cookieTimeoutMs?: number;
|
||||
}): Promise<CookieExtractionResult> {
|
||||
const warnings: string[] = [];
|
||||
const out = buildEmpty();
|
||||
@@ -114,6 +116,7 @@ async function readTwitterCookiesFromBrowser(options: {
|
||||
mode: 'merge',
|
||||
chromeProfile: options.chromeProfile,
|
||||
firefoxProfile: options.firefoxProfile,
|
||||
timeoutMs: options.cookieTimeoutMs,
|
||||
});
|
||||
warnings.push(...providerWarnings);
|
||||
|
||||
@@ -170,9 +173,18 @@ export async function resolveCredentials(options: {
|
||||
cookieSource?: CookieSource | CookieSource[];
|
||||
chromeProfile?: string;
|
||||
firefoxProfile?: string;
|
||||
cookieTimeoutMs?: number;
|
||||
}): Promise<CookieExtractionResult> {
|
||||
const warnings: string[] = [];
|
||||
const cookies = buildEmpty();
|
||||
const cookieTimeoutMs =
|
||||
typeof options.cookieTimeoutMs === 'number' &&
|
||||
Number.isFinite(options.cookieTimeoutMs) &&
|
||||
options.cookieTimeoutMs > 0
|
||||
? options.cookieTimeoutMs
|
||||
: process.platform === 'darwin'
|
||||
? DEFAULT_COOKIE_TIMEOUT_MS
|
||||
: undefined;
|
||||
|
||||
if (options.authToken) {
|
||||
cookies.authToken = options.authToken;
|
||||
@@ -199,6 +211,7 @@ export async function resolveCredentials(options: {
|
||||
source,
|
||||
chromeProfile: options.chromeProfile,
|
||||
firefoxProfile: options.firefoxProfile,
|
||||
cookieTimeoutMs,
|
||||
});
|
||||
warnings.push(...res.warnings);
|
||||
if (res.cookies.authToken && res.cookies.ct0) {
|
||||
|
||||
+10
-2
@@ -4,11 +4,13 @@ type SweetCookieResult = { cookies: Array<{ name: string; value: string; domain?
|
||||
|
||||
const sweet = vi.hoisted(() => ({
|
||||
results: new Map<string, SweetCookieResult>(),
|
||||
options: new Map<string, { timeoutMs?: number }>(),
|
||||
}));
|
||||
|
||||
vi.mock('@steipete/sweet-cookie', () => ({
|
||||
getCookies: vi.fn(async (options: { browsers?: string[] }) => {
|
||||
getCookies: vi.fn(async (options: { browsers?: string[]; timeoutMs?: number }) => {
|
||||
const browser = options.browsers?.[0] ?? 'unknown';
|
||||
sweet.options.set(browser, options);
|
||||
return (
|
||||
sweet.results.get(browser) ?? {
|
||||
cookies: [],
|
||||
@@ -24,6 +26,7 @@ describe('cookies', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetModules();
|
||||
sweet.results.clear();
|
||||
sweet.options.clear();
|
||||
process.env = { ...originalEnv };
|
||||
process.env.AUTH_TOKEN = undefined;
|
||||
process.env.TWITTER_AUTH_TOKEN = undefined;
|
||||
@@ -186,11 +189,16 @@ describe('cookies', () => {
|
||||
});
|
||||
|
||||
const { resolveCredentials } = await import('../src/lib/cookies.js');
|
||||
const result = await resolveCredentials({ cookieSource: 'chrome', chromeProfile: 'Default' });
|
||||
const result = await resolveCredentials({
|
||||
cookieSource: 'chrome',
|
||||
chromeProfile: 'Default',
|
||||
cookieTimeoutMs: 15000,
|
||||
});
|
||||
|
||||
expect(result.cookies.authToken).toBe('test_auth');
|
||||
expect(result.cookies.ct0).toBe('test_ct0');
|
||||
expect(result.cookies.source).toContain('Chrome');
|
||||
expect(sweet.options.get('chrome')?.timeoutMs).toBe(15000);
|
||||
});
|
||||
|
||||
it('uses default browser order when cookieSource is omitted', async () => {
|
||||
|
||||
Reference in New Issue
Block a user