From 03fc5ce691aaa703c83e0403c4067b28a4836466 Mon Sep 17 00:00:00 2001 From: Boris Mann Date: Sat, 4 Jul 2026 00:28:02 -0700 Subject: [PATCH] Logout of OAuth or password session before exiting --- src/atproto.ts | 9 ++ src/index.ts | 246 +++++++++++++++++++++++++------------------------ 2 files changed, 137 insertions(+), 118 deletions(-) diff --git a/src/atproto.ts b/src/atproto.ts index 2bd4fff..42e2e34 100644 --- a/src/atproto.ts +++ b/src/atproto.ts @@ -26,6 +26,7 @@ export interface AtprotoSession { service: string; client: Client; uploadBlob(buffer: Buffer, mimeType: string, log: Logger): Promise; + logout(): Promise; } const LOCAL_REDIRECT_PATH = '/callback'; @@ -99,6 +100,10 @@ class PasswordSessionAdapter implements AtprotoSession { } return json.blob; } + + async logout(): Promise { + await this.passwordSession.logout(); + } } class OAuthSessionAdapter implements AtprotoSession { @@ -137,6 +142,10 @@ class OAuthSessionAdapter implements AtprotoSession { } return json.blob; } + + async logout(): Promise { + await this.oauthSession.signOut(); + } } async function getPasswordSession(config: Config, log: Logger): Promise { diff --git a/src/index.ts b/src/index.ts index 85910de..7706540 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,144 +26,154 @@ async function main() { state.publicationAtUri = config.publicationAtUri; const session = config.dryRun ? undefined : await getSession(config, log); - const posts = await fetchAllPosts({ ghostUrl: config.ghostUrl, ghostApiKey: config.ghostApiKey }); - log.info(`fetched ${posts.length} posts`); - let processed = 0; let failed = 0; let skipped = 0; - for (const post of posts) { - const existing = state.posts[post.uuid]; - try { - if (post.visibility !== 'public') { - log.info(`skipping non-public post: ${post.title}`); - skipped++; - continue; - } + try { + const posts = await fetchAllPosts({ ghostUrl: config.ghostUrl, ghostApiKey: config.ghostApiKey }); + log.info(`fetched ${posts.length} posts`); + + for (const post of posts) { + const existing = state.posts[post.uuid]; + try { + if (post.visibility !== 'public') { + log.info(`skipping non-public post: ${post.title}`); + skipped++; + continue; + } - log.info(`processing: ${post.title}`); - const assetMap = new Map(); + log.info(`processing: ${post.title}`); + const assetMap = new Map(); - const inlineUrls = collectImageUrls(post.html, config.ghostUrl); - if (post.feature_image) { - inlineUrls.push(post.feature_image); - } + const inlineUrls = collectImageUrls(post.html, config.ghostUrl); + if (post.feature_image) { + inlineUrls.push(post.feature_image); + } - if (!config.dryRun && session) { - state.assets ??= {}; - for (const url of new Set(inlineUrls)) { - const info = await getOrUploadImage( - url, - config.ghostUrl, - config.exportDir, - state.assets as Record, - session, - log - ); - if (info) { - assetMap.set(info.url, info.blob); + if (!config.dryRun && session) { + state.assets ??= {}; + for (const url of new Set(inlineUrls)) { + const info = await getOrUploadImage( + url, + config.ghostUrl, + config.exportDir, + state.assets as Record, + session, + log + ); + if (info) { + assetMap.set(info.url, info.blob); + } + } + await saveState(config.stateFile, state); + } else if (config.dryRun) { + const DUMMY_CID = 'bafyreiaouvhh2kl5s3jo2uaqnn23hlpl2kut2n2qsgfvme54umlro3iari'; + for (const url of new Set(inlineUrls)) { + assetMap.set(url, { $type: 'blob', ref: { $link: DUMMY_CID }, mimeType: 'image/jpeg', size: 1000 }); } } - await saveState(config.stateFile, state); - } else if (config.dryRun) { - const DUMMY_CID = 'bafyreiaouvhh2kl5s3jo2uaqnn23hlpl2kut2n2qsgfvme54umlro3iari'; - for (const url of new Set(inlineUrls)) { - assetMap.set(url, { $type: 'blob', ref: { $link: DUMMY_CID }, mimeType: 'image/jpeg', size: 1000 }); + + const contentWrapper = await convertHtmlToOffprint(post.html, config.ghostUrl, assetMap, log); + + const tags = (post.tags ?? []).map((t) => t.name); + const description = post.custom_excerpt ?? post.excerpt ?? undefined; + + const documentRecord: StandardDocumentRecord = { + $type: 'site.standard.document', + site: config.publicationAtUri, + path: `/${post.slug}`, + title: post.title, + description, + publishedAt: post.published_at, + updatedAt: post.updated_at, + textContent: post.plaintext ?? '', + tags, + content: { $type: 'app.offprint.content', items: contentWrapper.items }, + }; + + if (post.feature_image) { + const featureBlob = assetMap.get(resolveUrl(config.ghostUrl, post.feature_image)); + if (featureBlob) { + documentRecord.coverImage = featureBlob; + } } - } - const contentWrapper = await convertHtmlToOffprint(post.html, config.ghostUrl, assetMap, log); - - const tags = (post.tags ?? []).map((t) => t.name); - const description = post.custom_excerpt ?? post.excerpt ?? undefined; - - const documentRecord: StandardDocumentRecord = { - $type: 'site.standard.document', - site: config.publicationAtUri, - path: `/${post.slug}`, - title: post.title, - description, - publishedAt: post.published_at, - updatedAt: post.updated_at, - textContent: post.plaintext ?? '', - tags, - content: { $type: 'app.offprint.content', items: contentWrapper.items }, - }; - - if (post.feature_image) { - const featureBlob = assetMap.get(resolveUrl(config.ghostUrl, post.feature_image)); - if (featureBlob) { - documentRecord.coverImage = featureBlob; + validateStandardDocument(documentRecord, log); + + let documentRef; + if (config.dryRun || !session) { + log.info(`[dry-run] would create/update site.standard.document for "${post.title}"`); + documentRef = { uri: 'at://dry-run/site.standard.document/dry', cid: 'dry-run' }; + } else if (existing?.documentUri?.startsWith('at://did:') && existing.documentCid && existing.documentCid !== 'dry-run') { + documentRef = await putRecord( + session, + 'site.standard.document', + rkeyFromUri(existing.documentUri), + documentRecord, + existing.documentCid, + log + ); + } else { + documentRef = await createRecord(session, 'site.standard.document', documentRecord, log); } - } - validateStandardDocument(documentRecord, log); - - let documentRef; - if (config.dryRun || !session) { - log.info(`[dry-run] would create/update site.standard.document for "${post.title}"`); - documentRef = { uri: 'at://dry-run/site.standard.document/dry', cid: 'dry-run' }; - } else if (existing?.documentUri?.startsWith('at://did:') && existing.documentCid && existing.documentCid !== 'dry-run') { - documentRef = await putRecord( - session, - 'site.standard.document', - rkeyFromUri(existing.documentUri), - documentRecord, - existing.documentCid, - log - ); - } else { - documentRef = await createRecord(session, 'site.standard.document', documentRecord, log); - } + const articleRecord = { + $type: 'app.offprint.document.article', + document: { + $type: 'com.atproto.repo.strongRef', + uri: documentRef.uri, + cid: documentRef.cid, + }, + }; + + let articleRef; + if (config.dryRun || !session) { + log.info(`[dry-run] would create/update app.offprint.document.article for "${post.title}"`); + articleRef = { uri: 'at://dry-run/app.offprint.document.article/dry', cid: 'dry-run' }; + } else if (existing?.articleUri?.startsWith('at://did:') && existing.articleCid && existing.articleCid !== 'dry-run') { + articleRef = await putRecord( + session, + 'app.offprint.document.article', + rkeyFromUri(existing.articleUri), + articleRecord, + existing.articleCid, + log + ); + } else { + articleRef = await createRecord(session, 'app.offprint.document.article', articleRecord, log); + } - const articleRecord = { - $type: 'app.offprint.document.article', - document: { - $type: 'com.atproto.repo.strongRef', - uri: documentRef.uri, - cid: documentRef.cid, - }, - }; - - let articleRef; - if (config.dryRun || !session) { - log.info(`[dry-run] would create/update app.offprint.document.article for "${post.title}"`); - articleRef = { uri: 'at://dry-run/app.offprint.document.article/dry', cid: 'dry-run' }; - } else if (existing?.articleUri?.startsWith('at://did:') && existing.articleCid && existing.articleCid !== 'dry-run') { - articleRef = await putRecord( - session, - 'app.offprint.document.article', - rkeyFromUri(existing.articleUri), - articleRecord, - existing.articleCid, - log - ); - } else { - articleRef = await createRecord(session, 'app.offprint.document.article', articleRecord, log); - } + state.posts[post.uuid] = { + ghostSlug: post.slug, + ghostTitle: post.title, + documentUri: documentRef.uri, + documentCid: documentRef.cid, + articleUri: articleRef.uri, + articleCid: articleRef.cid, + }; + if (!config.dryRun) { + await saveState(config.stateFile, state); + } - state.posts[post.uuid] = { - ghostSlug: post.slug, - ghostTitle: post.title, - documentUri: documentRef.uri, - documentCid: documentRef.cid, - articleUri: articleRef.uri, - articleCid: articleRef.cid, - }; - if (!config.dryRun) { - await saveState(config.stateFile, state); + processed++; + } catch (err: any) { + log.error(`failed to process "${post.title ?? post.slug}": ${err?.message ?? err}`); + log.error(err?.stack ?? ''); + failed++; } + } - processed++; - } catch (err: any) { - log.error(`failed to process "${post.title ?? post.slug}": ${err?.message ?? err}`); - log.error(err?.stack ?? ''); - failed++; + log.info(`done: ${processed} processed, ${failed} failed, ${skipped} skipped`); + } finally { + if (!config.dryRun && session) { + log.info('logging out'); + await session.logout().catch((err: any) => { + log.error(`logout failed: ${err?.message ?? err}`); + }); } } - log.info(`done: ${processed} processed, ${failed} failed, ${skipped} skipped`); process.exit(failed > 0 ? 1 : 0); } -- 2.51.2