Created
November 20, 2024 01:22
-
-
Save Salakar/00cb071813eb8dfe300e0053ab02e1f6 to your computer and use it in GitHub Desktop.
Bluesky Sessions with Cloudflare KV
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { AtpAgent, AtpSessionData, RichText } from "@atproto/api"; | |
import { AppContext } from "types"; | |
class WorkerSessionManager { | |
constructor(private readonly ctx: AppContext) {} | |
async getSession(identifier: string): Promise<AtpSessionData | null> { | |
const sessionStr = await this.ctx.env.KVCACHE.get(`session:${identifier}`); | |
return sessionStr ? JSON.parse(sessionStr) : null; | |
} | |
async saveSession( | |
identifier: string, | |
session: AtpSessionData | |
): Promise<void> { | |
await this.ctx.env.KVCACHE.put( | |
`session:${identifier}`, | |
JSON.stringify(session), | |
{ expirationTtl: 43200 } | |
); | |
} | |
async deleteSession(identifier: string): Promise<void> { | |
await this.ctx.env.KVCACHE.delete(`session:${identifier}`); | |
} | |
} | |
export class BlueSkyClient { | |
private agent: AtpAgent; | |
private sessionManager: WorkerSessionManager; | |
constructor( | |
private readonly ctx: AppContext, | |
private readonly appHandle: string, | |
private readonly appPassword: string | |
) { | |
this.sessionManager = new WorkerSessionManager(ctx); | |
this.agent = new AtpAgent({ service: "https://bsky.social" }); | |
} | |
private async ensureSession(): Promise<void> { | |
let session = await this.sessionManager.getSession(this.appHandle); | |
if (!session) { | |
await this.agent.login({ | |
identifier: this.appHandle, | |
password: this.appPassword, | |
}); | |
// Make sure we can serialize the session. | |
session = { | |
accessJwt: this.agent.session!.accessJwt, | |
active: this.agent.session!.active, | |
did: this.agent.session!.did, | |
email: this.agent.session!.email, | |
emailAuthFactor: this.agent.session!.emailAuthFactor, | |
emailConfirmed: this.agent.session!.emailConfirmed, | |
handle: this.agent.session!.handle, | |
refreshJwt: this.agent.session!.refreshJwt, | |
status: this.agent.session!.status, | |
}; | |
await this.sessionManager.saveSession(this.appHandle, session); | |
} else { | |
await this.agent.resumeSession(session); | |
} | |
} | |
async createPost(text: string, replyTo?: { uri: string; cid: string }) { | |
await this.ensureSession(); | |
const rt = new RichText({ text }); | |
await rt.detectFacets(this.agent); | |
const postRecord = { | |
text: rt.text, | |
facets: rt.facets, | |
langs: ["en"], | |
reply: replyTo | |
? { | |
root: { uri: replyTo.uri, cid: replyTo.cid }, | |
parent: { uri: replyTo.uri, cid: replyTo.cid }, | |
} | |
: undefined, | |
createdAt: new Date().toISOString(), | |
}; | |
return this.agent.post(postRecord); | |
} | |
async followUser(userDid: string) { | |
await this.ensureSession(); | |
return this.agent.follow(userDid); | |
} | |
async unfollowUser(followUri: string) { | |
await this.ensureSession(); | |
return this.agent.deleteFollow(followUri); | |
} | |
async getProfile(handle: string) { | |
await this.ensureSession(); | |
return this.agent.getProfile({ actor: handle }); | |
} | |
async resolveHandle(handle: string) { | |
await this.ensureSession(); | |
return this.agent.resolveHandle({ handle }); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment