Files
log/oauth/src/api/atproto.js
2025-06-19 19:12:29 +09:00

186 lines
5.6 KiB
JavaScript

// ATProto API client
import { ATProtoError, logError } from '../utils/errorHandler.js'
const ENDPOINTS = {
describeRepo: 'com.atproto.repo.describeRepo',
getProfile: 'app.bsky.actor.getProfile',
listRecords: 'com.atproto.repo.listRecords',
putRecord: 'com.atproto.repo.putRecord'
}
async function request(url, options = {}) {
try {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort(), 15000) // 15秒タイムアウト
const response = await fetch(url, {
...options,
signal: controller.signal
})
clearTimeout(timeoutId)
if (!response.ok) {
throw new ATProtoError(
`Request failed: ${response.statusText}`,
response.status,
{ url, method: options.method || 'GET' }
)
}
return await response.json()
} catch (error) {
if (error.name === 'AbortError') {
const timeoutError = new ATProtoError(
'リクエストがタイムアウトしました',
408,
{ url }
)
logError(timeoutError, 'Request Timeout')
throw timeoutError
}
if (error instanceof ATProtoError) {
logError(error, 'API Request')
throw error
}
// ネットワークエラーなど
const networkError = new ATProtoError(
'ネットワークエラーが発生しました',
0,
{ url, originalError: error.message }
)
logError(networkError, 'Network Error')
throw networkError
}
}
export const atproto = {
async getDid(pds, handle) {
const res = await request(`https://${pds}/xrpc/${ENDPOINTS.describeRepo}?repo=${handle}`)
return res.did
},
async getProfile(bsky, actor) {
// Skip test DIDs
if (actor && actor.includes('test-')) {
console.log('Skipping profile fetch for test DID:', actor)
return {
did: actor,
handle: 'test.user',
displayName: 'Test User',
avatar: null
}
}
// Check if endpoint supports getProfile
let apiEndpoint = bsky
// Allow public.api.bsky.app and bsky.syu.is, redirect other PDS endpoints
if (!bsky.includes('public.api.bsky.app') && !bsky.includes('bsky.syu.is')) {
// If it's a PDS endpoint that doesn't support getProfile, redirect to public API
console.warn(`getProfile called with PDS endpoint ${bsky}, redirecting to public API`)
apiEndpoint = 'https://public.api.bsky.app'
}
return await request(`${apiEndpoint}/xrpc/${ENDPOINTS.getProfile}?actor=${actor}`)
},
async getRecords(pds, repo, collection, limit = 10) {
const res = await request(`${pds}/xrpc/${ENDPOINTS.listRecords}?repo=${repo}&collection=${collection}&limit=${limit}`)
return res.records || []
},
async searchPlc(plc, did) {
try {
const data = await request(`${plc}/${did}`)
return {
success: true,
endpoint: data?.service?.[0]?.serviceEndpoint || null,
handle: data?.alsoKnownAs?.[0]?.replace('at://', '') || null
}
} catch {
return { success: false, endpoint: null, handle: null }
}
},
async putRecord(pds, record, agent) {
if (!agent) {
throw new Error('Agent required for putRecord')
}
// Use Agent's putRecord method instead of direct fetch
return await agent.com.atproto.repo.putRecord(record)
}
}
import { dataCache } from '../utils/cache.js'
// Collection specific methods
export const collections = {
async getBase(pds, repo, collection, limit = 10) {
const cacheKey = dataCache.generateKey('base', pds, repo, collection, limit)
const cached = dataCache.get(cacheKey)
if (cached) return cached
const data = await atproto.getRecords(pds, repo, collection, limit)
dataCache.set(cacheKey, data)
return data
},
async getLang(pds, repo, collection, limit = 10) {
const cacheKey = dataCache.generateKey('lang', pds, repo, collection, limit)
const cached = dataCache.get(cacheKey)
if (cached) return cached
const data = await atproto.getRecords(pds, repo, `${collection}.chat.lang`, limit)
dataCache.set(cacheKey, data)
return data
},
async getComment(pds, repo, collection, limit = 10) {
const cacheKey = dataCache.generateKey('comment', pds, repo, collection, limit)
const cached = dataCache.get(cacheKey)
if (cached) return cached
const data = await atproto.getRecords(pds, repo, `${collection}.chat.comment`, limit)
dataCache.set(cacheKey, data)
return data
},
async getChat(pds, repo, collection, limit = 10) {
const cacheKey = dataCache.generateKey('chat', pds, repo, collection, limit)
const cached = dataCache.get(cacheKey)
if (cached) return cached
const data = await atproto.getRecords(pds, repo, `${collection}.chat`, limit)
dataCache.set(cacheKey, data)
return data
},
async getUserList(pds, repo, collection, limit = 100) {
const cacheKey = dataCache.generateKey('userlist', pds, repo, collection, limit)
const cached = dataCache.get(cacheKey)
if (cached) return cached
const data = await atproto.getRecords(pds, repo, `${collection}.user`, limit)
dataCache.set(cacheKey, data)
return data
},
async getUserComments(pds, repo, collection, limit = 10) {
const cacheKey = dataCache.generateKey('usercomments', pds, repo, collection, limit)
const cached = dataCache.get(cacheKey)
if (cached) return cached
const data = await atproto.getRecords(pds, repo, collection, limit)
dataCache.set(cacheKey, data)
return data
},
// 投稿後にキャッシュを無効化
invalidateCache(collection) {
dataCache.invalidatePattern(collection)
}
}