From a084c6a77a01c4e6880aa63039cb04041ac849ba Mon Sep 17 00:00:00 2001 From: Skywatch Date: Wed, 22 Oct 2025 12:58:08 -0400 Subject: [PATCH] feat: Add retry logic to authentication The authentication flow was updated to include retry logic. This ensures that the application attempts to log in multiple times if the initial attempts fail. This also addresses an issue where the app was exiting before the Jetstream client was started. --- .claude/settings.local.json | 3 ++- .gitignore | 1 + .session | 10 ------- src/agent.ts | 54 ++++++++++++++++++++++++++++++++++--- src/main.ts | 5 ++++ src/tests/agent.test.ts | 13 ++++++++- 6 files changed, 70 insertions(+), 16 deletions(-) delete mode 100644 .session diff --git a/.claude/settings.local.json b/.claude/settings.local.json index e25a6f6..c5cfe5d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -12,7 +12,8 @@ "mcp__git-mcp-server__git_set_working_dir", "Bash(npm run test:run:*)", "Bash(bunx eslint:*)", - "Bash(bun test:run:*)" + "Bash(bun test:run:*)", + "Bash(bun run type-check:*)" ], "deny": [], "ask": [] diff --git a/.gitignore b/.gitignore index 5bba42c..e730425 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ cursor.txt labels.db* .DS_Store coverage/ +.session diff --git a/.session b/.session deleted file mode 100644 index 9883374..0000000 --- a/.session +++ /dev/null @@ -1,10 +0,0 @@ -{ - "accessJwt": "eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NksifQ.eyJzY29wZSI6ImNvbS5hdHByb3RvLmFwcFBhc3MiLCJzdWIiOiJkaWQ6cGxjOmpzamxoajM1NzRvZHRmcWF6cXhuNG9uZCIsImlhdCI6MTc2MTA3OTUzMSwiZXhwIjoxNzYxMDg2NzMxLCJhdWQiOiJkaWQ6d2ViOm95c3RlcmxpbmcudXMtd2VzdC5ob3N0LmJza3kubmV0d29yayJ9.2EPsA8yDLvngSPzOu-DHy-2SQCjgzk4wFxgsOL7BXq1gwmRkoJy_Poykjb8m9JeYt9_s08-VCM_h1C43FOVosg", - "refreshJwt": "eyJ0eXAiOiJyZWZyZXNoK2p3dCIsImFsZyI6IkVTMjU2SyJ9.eyJzY29wZSI6ImNvbS5hdHByb3RvLnJlZnJlc2giLCJzdWIiOiJkaWQ6cGxjOmpzamxoajM1NzRvZHRmcWF6cXhuNG9uZCIsImF1ZCI6ImRpZDp3ZWI6YnNreS5zb2NpYWwiLCJqdGkiOiJpOWVKcTVHa0VQeS9ISVV0YWtUb0dqMW55Mllzb25PK0VGMHUySGRoNFNFIiwiaWF0IjoxNzYxMDc5NTMxLCJleHAiOjE3Njg4NTU1MzF9.5YawggT0amOGgZryO5h2kJ11ePimtc0YMqs8W-ZzxPkU8aymD0m29w4_wZXeyoK4vclU-YvlUc9iDr5SgrqJ2w", - "handle": "automod.skywatch.blue", - "did": "did:plc:jsjlhj3574odtfqazqxn4ond", - "email": "bsky.duration409@passmail.net", - "emailConfirmed": true, - "emailAuthFactor": true, - "active": true -} \ No newline at end of file diff --git a/src/agent.ts b/src/agent.ts index 767d4e8..5b6096e 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -99,6 +99,15 @@ async function performLogin(): Promise { } } +const MAX_LOGIN_RETRIES = 3; +const RETRY_DELAY_MS = 2000; + +let loginPromise: Promise | null = null; + +async function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + async function authenticate(): Promise { const savedSession = loadSession(); @@ -121,7 +130,44 @@ async function authenticate(): Promise { return performLogin(); } -export const login = authenticate; -export const isLoggedIn = authenticate() - .then((success) => success) - .catch(() => false); +async function authenticateWithRetry(): Promise { + // Reuse existing login attempt if one is in progress + if (loginPromise) { + return loginPromise; + } + + loginPromise = (async () => { + for (let attempt = 1; attempt <= MAX_LOGIN_RETRIES; attempt++) { + logger.info( + { attempt, maxRetries: MAX_LOGIN_RETRIES }, + "Attempting login", + ); + + const success = await authenticate(); + + if (success) { + logger.info("Authentication successful"); + return; + } + + if (attempt < MAX_LOGIN_RETRIES) { + logger.warn( + { attempt, maxRetries: MAX_LOGIN_RETRIES, retryInMs: RETRY_DELAY_MS }, + "Login failed, retrying", + ); + await sleep(RETRY_DELAY_MS); + } + } + + logger.error( + { maxRetries: MAX_LOGIN_RETRIES }, + "All login attempts failed, aborting", + ); + process.exit(1); + })(); + + return loginPromise; +} + +export const login = authenticateWithRetry; +export const isLoggedIn = authenticateWithRetry().then(() => true); diff --git a/src/main.ts b/src/main.ts index 63bee3c..b7f7044 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,6 +5,7 @@ import type { IdentityEvent, } from "@skyware/jetstream"; import { Jetstream } from "@skyware/jetstream"; +import { login } from "./agent.js"; import { CURSOR_UPDATE_INTERVAL, FIREHOSE_URL, @@ -344,6 +345,10 @@ const metricsServer = startMetricsServer(METRICS_PORT); logger.info({ process: "MAIN" }, "Connecting to Redis"); await connectRedis(); +logger.info({ process: "MAIN" }, "Authenticating with Bluesky"); +await login(); +logger.info({ process: "MAIN" }, "Authentication complete, starting Jetstream"); + jetstream.start(); async function shutdown() { diff --git a/src/tests/agent.test.ts b/src/tests/agent.test.ts index 7bd377e..f9c8899 100644 --- a/src/tests/agent.test.ts +++ b/src/tests/agent.test.ts @@ -13,13 +13,24 @@ describe("Agent", () => { OZONE_PDS: "pds.test.com", })); + // Mock session + const mockSession = { + did: "did:plc:test123", + handle: "test.bsky.social", + accessJwt: "test-access-jwt", + refreshJwt: "test-refresh-jwt", + }; + // Mock the AtpAgent - const mockLogin = vi.fn(() => Promise.resolve()); + const mockLogin = vi.fn(() => + Promise.resolve({ success: true, data: mockSession }), + ); const mockConstructor = vi.fn(); vi.doMock("@atproto/api", () => ({ AtpAgent: class { login = mockLogin; service: URL; + session = mockSession; constructor(options: { service: string }) { mockConstructor(options); this.service = new URL(options.service); -- 2.43.0 From 584bc7bceaabe31c553d4942d76a1e9f1066175c Mon Sep 17 00:00:00 2001 From: Skywatch Date: Wed, 22 Oct 2025 13:06:33 -0400 Subject: [PATCH] Bumped version number --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 68e207b..5322c2a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "skywatch-tools", - "version": "1.3.0", + "version": "2.0.1", "type": "module", "scripts": { "start": "npx tsx src/main.ts", -- 2.43.0