chore: cleaned up types
332c80d8
7 file(s) · +49 −43
| 287 | 287 | defaultValue: "7", |
|
| 288 | 288 | placeholder: "7", |
|
| 289 | 289 | validate: (value) => { |
|
| 290 | - | const num = parseInt(value, 10); |
|
| 290 | + | if (!value) { |
|
| 291 | + | return "Please enter a number"; |
|
| 292 | + | } |
|
| 293 | + | const num = Number.parseInt(value, 10); |
|
| 291 | 294 | if (Number.isNaN(num) || num < 1) { |
|
| 292 | 295 | return "Please enter a positive number"; |
|
| 293 | 296 | } |
| 4 | 4 | import { resolveHandleToDid } from "../lib/atproto"; |
|
| 5 | 5 | import { |
|
| 6 | 6 | getCallbackPort, |
|
| 7 | - | getCallbackUrl, |
|
| 8 | 7 | getOAuthClient, |
|
| 9 | 8 | getOAuthScope, |
|
| 10 | 9 | } from "../lib/oauth-client"; |
| 209 | 209 | let agent: Awaited<ReturnType<typeof createAgent>> | undefined; |
|
| 210 | 210 | try { |
|
| 211 | 211 | agent = await createAgent(credentials); |
|
| 212 | - | s.stop(`Logged in as ${agent.session?.handle}`); |
|
| 212 | + | s.stop(`Logged in as ${agent.did}`); |
|
| 213 | 213 | } catch (error) { |
|
| 214 | 214 | s.stop("Failed to login"); |
|
| 215 | 215 | log.error(`Failed to login: ${error}`); |
| 76 | 76 | let agent: Awaited<ReturnType<typeof createAgent>> | undefined; |
|
| 77 | 77 | try { |
|
| 78 | 78 | agent = await createAgent(credentials); |
|
| 79 | - | s.stop(`Logged in as ${agent.session?.handle}`); |
|
| 79 | + | s.stop(`Logged in as ${agent.did}`); |
|
| 80 | 80 | } catch (error) { |
|
| 81 | 81 | s.stop("Failed to login"); |
|
| 82 | 82 | log.error(`Failed to login: ${error}`); |
| 13 | 13 | } from "./types"; |
|
| 14 | 14 | import { isAppPasswordCredentials, isOAuthCredentials } from "./types"; |
|
| 15 | 15 | ||
| 16 | + | /** |
|
| 17 | + | * Type guard to check if a record value is a DocumentRecord |
|
| 18 | + | */ |
|
| 19 | + | function isDocumentRecord(value: unknown): value is DocumentRecord { |
|
| 20 | + | if (!value || typeof value !== "object") return false; |
|
| 21 | + | const v = value as Record<string, unknown>; |
|
| 22 | + | return ( |
|
| 23 | + | v.$type === "site.standard.document" && |
|
| 24 | + | typeof v.title === "string" && |
|
| 25 | + | typeof v.site === "string" && |
|
| 26 | + | typeof v.path === "string" && |
|
| 27 | + | typeof v.textContent === "string" && |
|
| 28 | + | typeof v.publishedAt === "string" |
|
| 29 | + | ); |
|
| 30 | + | } |
|
| 31 | + | ||
| 16 | 32 | async function fileExists(filePath: string): Promise<boolean> { |
|
| 17 | 33 | try { |
|
| 18 | 34 | await fs.access(filePath); |
|
| 96 | 112 | showInDiscover?: boolean; |
|
| 97 | 113 | } |
|
| 98 | 114 | ||
| 99 | - | export async function createAgent(credentials: Credentials): Promise<AtpAgent> { |
|
| 115 | + | export async function createAgent(credentials: Credentials): Promise<Agent> { |
|
| 100 | 116 | if (isOAuthCredentials(credentials)) { |
|
| 101 | 117 | // OAuth flow - restore session from stored tokens |
|
| 102 | 118 | const client = await getOAuthClient(); |
|
| 103 | 119 | try { |
|
| 104 | 120 | const oauthSession = await client.restore(credentials.did); |
|
| 105 | 121 | // Wrap the OAuth session in an Agent which provides the atproto API |
|
| 106 | - | const agent = new Agent(oauthSession) as unknown as AtpAgent; |
|
| 107 | - | ||
| 108 | - | // The Agent class doesn't have session.did like AtpAgent does |
|
| 109 | - | // We need to set up a compatible session object for the rest of our code |
|
| 110 | - | agent.session = { |
|
| 111 | - | did: oauthSession.did, |
|
| 112 | - | handle: credentials.handle, |
|
| 113 | - | accessJwt: "", |
|
| 114 | - | refreshJwt: "", |
|
| 115 | - | active: true, |
|
| 116 | - | }; |
|
| 117 | - | ||
| 118 | - | return agent; |
|
| 122 | + | return new Agent(oauthSession); |
|
| 119 | 123 | } catch (error) { |
|
| 120 | 124 | if (error instanceof Error) { |
|
| 121 | 125 | // Check for common OAuth errors |
|
| 147 | 151 | } |
|
| 148 | 152 | ||
| 149 | 153 | export async function uploadImage( |
|
| 150 | - | agent: AtpAgent, |
|
| 154 | + | agent: Agent, |
|
| 151 | 155 | imagePath: string, |
|
| 152 | 156 | ): Promise<BlobObject | undefined> { |
|
| 153 | 157 | if (!(await fileExists(imagePath))) { |
|
| 216 | 220 | } |
|
| 217 | 221 | ||
| 218 | 222 | export async function createDocument( |
|
| 219 | - | agent: AtpAgent, |
|
| 223 | + | agent: Agent, |
|
| 220 | 224 | post: BlogPost, |
|
| 221 | 225 | config: PublisherConfig, |
|
| 222 | 226 | coverImage?: BlobObject, |
|
| 259 | 263 | } |
|
| 260 | 264 | ||
| 261 | 265 | const response = await agent.com.atproto.repo.createRecord({ |
|
| 262 | - | repo: agent.session!.did, |
|
| 266 | + | repo: agent.did!, |
|
| 263 | 267 | collection: "site.standard.document", |
|
| 264 | 268 | record, |
|
| 265 | 269 | }); |
|
| 268 | 272 | } |
|
| 269 | 273 | ||
| 270 | 274 | export async function updateDocument( |
|
| 271 | - | agent: AtpAgent, |
|
| 275 | + | agent: Agent, |
|
| 272 | 276 | post: BlogPost, |
|
| 273 | 277 | atUri: string, |
|
| 274 | 278 | config: PublisherConfig, |
|
| 321 | 325 | } |
|
| 322 | 326 | ||
| 323 | 327 | await agent.com.atproto.repo.putRecord({ |
|
| 324 | - | repo: agent.session!.did, |
|
| 328 | + | repo: agent.did!, |
|
| 325 | 329 | collection: collection!, |
|
| 326 | 330 | rkey: rkey!, |
|
| 327 | 331 | record, |
|
| 361 | 365 | } |
|
| 362 | 366 | ||
| 363 | 367 | export async function listDocuments( |
|
| 364 | - | agent: AtpAgent, |
|
| 368 | + | agent: Agent, |
|
| 365 | 369 | publicationUri?: string, |
|
| 366 | 370 | ): Promise<ListDocumentsResult[]> { |
|
| 367 | 371 | const documents: ListDocumentsResult[] = []; |
|
| 369 | 373 | ||
| 370 | 374 | do { |
|
| 371 | 375 | const response = await agent.com.atproto.repo.listRecords({ |
|
| 372 | - | repo: agent.session!.did, |
|
| 376 | + | repo: agent.did!, |
|
| 373 | 377 | collection: "site.standard.document", |
|
| 374 | 378 | limit: 100, |
|
| 375 | 379 | cursor, |
|
| 376 | 380 | }); |
|
| 377 | 381 | ||
| 378 | 382 | for (const record of response.data.records) { |
|
| 379 | - | const value = record.value as unknown as DocumentRecord; |
|
| 383 | + | if (!isDocumentRecord(record.value)) { |
|
| 384 | + | continue; |
|
| 385 | + | } |
|
| 380 | 386 | ||
| 381 | 387 | // If publicationUri is specified, only include documents from that publication |
|
| 382 | - | if (publicationUri && value.site !== publicationUri) { |
|
| 388 | + | if (publicationUri && record.value.site !== publicationUri) { |
|
| 383 | 389 | continue; |
|
| 384 | 390 | } |
|
| 385 | 391 | ||
| 386 | 392 | documents.push({ |
|
| 387 | 393 | uri: record.uri, |
|
| 388 | 394 | cid: record.cid, |
|
| 389 | - | value, |
|
| 395 | + | value: record.value, |
|
| 390 | 396 | }); |
|
| 391 | 397 | } |
|
| 392 | 398 | ||
| 397 | 403 | } |
|
| 398 | 404 | ||
| 399 | 405 | export async function createPublication( |
|
| 400 | - | agent: AtpAgent, |
|
| 406 | + | agent: Agent, |
|
| 401 | 407 | options: CreatePublicationOptions, |
|
| 402 | 408 | ): Promise<string> { |
|
| 403 | 409 | let icon: BlobObject | undefined; |
|
| 428 | 434 | } |
|
| 429 | 435 | ||
| 430 | 436 | const response = await agent.com.atproto.repo.createRecord({ |
|
| 431 | - | repo: agent.session!.did, |
|
| 437 | + | repo: agent.did!, |
|
| 432 | 438 | collection: "site.standard.publication", |
|
| 433 | 439 | record, |
|
| 434 | 440 | }); |
|
| 481 | 487 | * Create a Bluesky post with external link embed |
|
| 482 | 488 | */ |
|
| 483 | 489 | export async function createBlueskyPost( |
|
| 484 | - | agent: AtpAgent, |
|
| 490 | + | agent: Agent, |
|
| 485 | 491 | options: CreateBlueskyPostOptions, |
|
| 486 | 492 | ): Promise<StrongRef> { |
|
| 487 | 493 | const { title, description, canonicalUrl, coverImage, publishedAt } = options; |
|
| 576 | 582 | }; |
|
| 577 | 583 | ||
| 578 | 584 | const response = await agent.com.atproto.repo.createRecord({ |
|
| 579 | - | repo: agent.session!.did, |
|
| 585 | + | repo: agent.did!, |
|
| 580 | 586 | collection: "app.bsky.feed.post", |
|
| 581 | 587 | record, |
|
| 582 | 588 | }); |
|
| 591 | 597 | * Add bskyPostRef to an existing document record |
|
| 592 | 598 | */ |
|
| 593 | 599 | export async function addBskyPostRefToDocument( |
|
| 594 | - | agent: AtpAgent, |
|
| 600 | + | agent: Agent, |
|
| 595 | 601 | documentAtUri: string, |
|
| 596 | 602 | bskyPostRef: StrongRef, |
|
| 597 | 603 | ): Promise<void> { |
|
| 95 | 95 | } |
|
| 96 | 96 | } |
|
| 97 | 97 | ||
| 98 | - | // Otherwise, check all OAuth sessions to find a matching handle |
|
| 99 | - | // (This is a fallback - handle matching isn't perfect without storing handles) |
|
| 100 | - | const sessions = await listOAuthSessions(); |
|
| 101 | - | for (const did of sessions) { |
|
| 102 | - | // Could enhance this by storing handle with session, but for now |
|
| 103 | - | // just return null if profile isn't a DID |
|
| 104 | - | } |
|
| 105 | - | ||
| 98 | + | // Otherwise, we would need to check all OAuth sessions to find a matching handle, |
|
| 99 | + | // but handle matching isn't perfect without storing handles alongside sessions. |
|
| 100 | + | // For now, just return null if profile isn't a DID. |
|
| 106 | 101 | return null; |
|
| 107 | 102 | } |
|
| 108 | 103 |
| 18 | 18 | // This prevents the "No lock mechanism provided" warning |
|
| 19 | 19 | const locks = new Map<string, Promise<void>>(); |
|
| 20 | 20 | ||
| 21 | - | async function requestLock(key: string, fn: () => Promise<void>): Promise<void> { |
|
| 21 | + | async function requestLock<T>( |
|
| 22 | + | key: string, |
|
| 23 | + | fn: () => T | PromiseLike<T>, |
|
| 24 | + | ): Promise<T> { |
|
| 22 | 25 | // Wait for any existing lock on this key |
|
| 23 | 26 | while (locks.has(key)) { |
|
| 24 | 27 | await locks.get(key); |
|
| 32 | 35 | locks.set(key, lockPromise); |
|
| 33 | 36 | ||
| 34 | 37 | try { |
|
| 35 | - | await fn(); |
|
| 38 | + | return await fn(); |
|
| 36 | 39 | } finally { |
|
| 37 | 40 | locks.delete(key); |
|
| 38 | 41 | resolve!(); |
|