Fixes #1
+2
-1
.claude/settings.local.json
+2
-1
.claude/settings.local.json
-10
.session
-10
.session
···
1
-
{
2
-
"accessJwt": "eyJ0eXAiOiJhdCtqd3QiLCJhbGciOiJFUzI1NksifQ.eyJzY29wZSI6ImNvbS5hdHByb3RvLmFwcFBhc3MiLCJzdWIiOiJkaWQ6cGxjOmpzamxoajM1NzRvZHRmcWF6cXhuNG9uZCIsImlhdCI6MTc2MTA3OTUzMSwiZXhwIjoxNzYxMDg2NzMxLCJhdWQiOiJkaWQ6d2ViOm95c3RlcmxpbmcudXMtd2VzdC5ob3N0LmJza3kubmV0d29yayJ9.2EPsA8yDLvngSPzOu-DHy-2SQCjgzk4wFxgsOL7BXq1gwmRkoJy_Poykjb8m9JeYt9_s08-VCM_h1C43FOVosg",
3
-
"refreshJwt": "eyJ0eXAiOiJyZWZyZXNoK2p3dCIsImFsZyI6IkVTMjU2SyJ9.eyJzY29wZSI6ImNvbS5hdHByb3RvLnJlZnJlc2giLCJzdWIiOiJkaWQ6cGxjOmpzamxoajM1NzRvZHRmcWF6cXhuNG9uZCIsImF1ZCI6ImRpZDp3ZWI6YnNreS5zb2NpYWwiLCJqdGkiOiJpOWVKcTVHa0VQeS9ISVV0YWtUb0dqMW55Mllzb25PK0VGMHUySGRoNFNFIiwiaWF0IjoxNzYxMDc5NTMxLCJleHAiOjE3Njg4NTU1MzF9.5YawggT0amOGgZryO5h2kJ11ePimtc0YMqs8W-ZzxPkU8aymD0m29w4_wZXeyoK4vclU-YvlUc9iDr5SgrqJ2w",
4
-
"handle": "automod.skywatch.blue",
5
-
"did": "did:plc:jsjlhj3574odtfqazqxn4ond",
6
-
"email": "bsky.duration409@passmail.net",
7
-
"emailConfirmed": true,
8
-
"emailAuthFactor": true,
9
-
"active": true
10
-
}
+1
-1
package.json
+1
-1
package.json
+50
-4
src/agent.ts
+50
-4
src/agent.ts
···
99
99
}
100
100
}
101
101
102
+
const MAX_LOGIN_RETRIES = 3;
103
+
const RETRY_DELAY_MS = 2000;
104
+
105
+
let loginPromise: Promise<void> | null = null;
106
+
107
+
async function sleep(ms: number): Promise<void> {
108
+
return new Promise((resolve) => setTimeout(resolve, ms));
109
+
}
110
+
102
111
async function authenticate(): Promise<boolean> {
103
112
const savedSession = loadSession();
104
113
···
121
130
return performLogin();
122
131
}
123
132
124
-
export const login = authenticate;
125
-
export const isLoggedIn = authenticate()
126
-
.then((success) => success)
127
-
.catch(() => false);
133
+
async function authenticateWithRetry(): Promise<void> {
134
+
// Reuse existing login attempt if one is in progress
135
+
if (loginPromise) {
136
+
return loginPromise;
137
+
}
138
+
139
+
loginPromise = (async () => {
140
+
for (let attempt = 1; attempt <= MAX_LOGIN_RETRIES; attempt++) {
141
+
logger.info(
142
+
{ attempt, maxRetries: MAX_LOGIN_RETRIES },
143
+
"Attempting login",
144
+
);
145
+
146
+
const success = await authenticate();
147
+
148
+
if (success) {
149
+
logger.info("Authentication successful");
150
+
return;
151
+
}
152
+
153
+
if (attempt < MAX_LOGIN_RETRIES) {
154
+
logger.warn(
155
+
{ attempt, maxRetries: MAX_LOGIN_RETRIES, retryInMs: RETRY_DELAY_MS },
156
+
"Login failed, retrying",
157
+
);
158
+
await sleep(RETRY_DELAY_MS);
159
+
}
160
+
}
161
+
162
+
logger.error(
163
+
{ maxRetries: MAX_LOGIN_RETRIES },
164
+
"All login attempts failed, aborting",
165
+
);
166
+
process.exit(1);
167
+
})();
168
+
169
+
return loginPromise;
170
+
}
171
+
172
+
export const login = authenticateWithRetry;
173
+
export const isLoggedIn = authenticateWithRetry().then(() => true);
+5
src/main.ts
+5
src/main.ts
···
5
5
IdentityEvent,
6
6
} from "@skyware/jetstream";
7
7
import { Jetstream } from "@skyware/jetstream";
8
+
import { login } from "./agent.js";
8
9
import {
9
10
CURSOR_UPDATE_INTERVAL,
10
11
FIREHOSE_URL,
···
343
344
344
345
logger.info({ process: "MAIN" }, "Connecting to Redis");
345
346
await connectRedis();
347
+
348
+
logger.info({ process: "MAIN" }, "Authenticating with Bluesky");
349
+
await login();
350
+
logger.info({ process: "MAIN" }, "Authentication complete, starting Jetstream");
346
351
347
352
jetstream.start();
348
353
+12
-1
src/tests/agent.test.ts
+12
-1
src/tests/agent.test.ts
···
13
13
OZONE_PDS: "pds.test.com",
14
14
}));
15
15
16
+
// Mock session
17
+
const mockSession = {
18
+
did: "did:plc:test123",
19
+
handle: "test.bsky.social",
20
+
accessJwt: "test-access-jwt",
21
+
refreshJwt: "test-refresh-jwt",
22
+
};
23
+
16
24
// Mock the AtpAgent
17
-
const mockLogin = vi.fn(() => Promise.resolve());
25
+
const mockLogin = vi.fn(() =>
26
+
Promise.resolve({ success: true, data: mockSession }),
27
+
);
18
28
const mockConstructor = vi.fn();
19
29
vi.doMock("@atproto/api", () => ({
20
30
AtpAgent: class {
21
31
login = mockLogin;
22
32
service: URL;
33
+
session = mockSession;
23
34
constructor(options: { service: string }) {
24
35
mockConstructor(options);
25
36
this.service = new URL(options.service);
History
2 rounds
0 comments
skywatch.blue
submitted
#1
2 commits
expand
collapse
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.
Bumped version number
expand 0 comments
pull request successfully merged
skywatch.blue
submitted
#0
1 commit
expand
collapse
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.