Skip to content

Instantly share code, notes, and snippets.

@Salakar
Created November 20, 2024 01:22
Show Gist options
  • Save Salakar/00cb071813eb8dfe300e0053ab02e1f6 to your computer and use it in GitHub Desktop.
Save Salakar/00cb071813eb8dfe300e0053ab02e1f6 to your computer and use it in GitHub Desktop.
Bluesky Sessions with Cloudflare KV
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