Skip to content

Commit 4ccd6f6

Browse files
committed
fix session retry
1 parent 489e70f commit 4ccd6f6

3 files changed

Lines changed: 32 additions & 3 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "at-astro",
3-
"version": "1.1.0",
3+
"version": "1.1.1",
44
"private": false,
55
"description": "An Astro integration for the AT Protocol, implementing OAuth flow and publishing an authenticated client to fetch and mutate records.",
66
"keywords": [

src/lib/atproto-client.test.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { beforeEach, expect, mock, spyOn, test } from "bun:test"
2-
import { OAuthResolverError } from "@atproto/oauth-client"
2+
import { OAuthResolverError, TokenRefreshError } from "@atproto/oauth-client"
33
import type { AtAstroSession } from "../types/session"
44

55
const restore = mock()
@@ -12,6 +12,7 @@ beforeEach(() => {
1212
void mock.module("at-astro:config", () => ({
1313
config: {
1414
didSessionKey: "at-astro:did",
15+
oauthSessionPrefix: "at-astro:oauth:",
1516
publicEndpoint: "https://public.api.bsky.app",
1617
},
1718
}))
@@ -72,3 +73,26 @@ test("does not report a handle lookup failure as a signed-out session", async ()
7273
resolve.mockRejectedValueOnce(error)
7374
expect(getClient(session)).rejects.toBe(error)
7475
})
76+
77+
test("clears a deleted OAuth session and returns a public client", async () => {
78+
const values = new Map<string, string>([
79+
["at-astro:did", "did:plc:test"],
80+
["at-astro:oauth:did:plc:test", "stale credentials"],
81+
["preference", "keep"],
82+
])
83+
const storedSession = {
84+
get: async (key: string) => values.get(key),
85+
delete: (key: string) => {
86+
values.delete(key)
87+
},
88+
} as unknown as AtAstroSession
89+
restore.mockRejectedValueOnce(
90+
new TokenRefreshError("did:plc:test", "The session was deleted by another process"),
91+
)
92+
93+
expect(await getClient(storedSession)).toMatchObject({ did: null, handle: null })
94+
expect([...values]).toEqual([["preference", "keep"]])
95+
expect(resolve).not.toHaveBeenCalled()
96+
expect(await getClient(storedSession)).toMatchObject({ did: null, handle: null })
97+
expect(restore).toHaveBeenCalledTimes(1)
98+
})

src/lib/atproto-client.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Client } from "@atproto/lex"
2-
import { OAuthResolverError } from "@atproto/oauth-client"
2+
import { OAuthResolverError, TokenRefreshError } from "@atproto/oauth-client"
33
import { getOAuthClient } from "./atproto-oauth"
44
import { config } from "at-astro:config"
55
import type { AtAstroSession } from "../types/session"
@@ -34,6 +34,11 @@ export async function getClient(session: AtAstroSession | undefined): Promise<{
3434
const { handle } = await oauthClient.identityResolver.resolve(did)
3535
return { client, did, handle: handle == "handle.invalid" ? null : handle }
3636
} catch (error) {
37+
if (error instanceof TokenRefreshError) {
38+
session.delete(config.didSessionKey)
39+
session.delete(`${config.oauthSessionPrefix}${did}`)
40+
return getPublicClient()
41+
}
3742
if (!(error instanceof OAuthResolverError)) throw error
3843
console.error("Failed to restore AT Protocol session", error)
3944
return getPublicClient()

0 commit comments

Comments
 (0)