import { createAuthenticatedFetch, kody } from 'kody:runtime' import type { KitAuthInput, KitAuthMode } from './types.ts' import { optionalString, requireString } from './types.ts' import { DEFAULT_API_KEY_SECRET, missingCredentialsMessage, resolveIntegrationName, resolveSecretName, } from './setup.ts' const authenticatedFetches = new Map() export type ResolvedKitAuth = { mode: KitAuthMode integrationName: string secretName: string } function secretEntries(result: unknown): Array<{ name?: string; scope?: string }> { if ( result && typeof result === 'object' && Array.isArray((result as { secrets?: unknown }).secrets) ) { return (result as { secrets: Array<{ name?: string; scope?: string }> }).secrets } return Array.isArray(result) ? result : [] } export async function listUserSecretNames(): Promise> { try { const listed = await kody.secretList({ scope: 'user' }) const names = new Set() for (const entry of secretEntries(listed)) { if (entry?.name && (entry.scope === 'user' || !entry.scope)) { names.add(entry.name) } } return names } catch { return new Set() } } export async function listKitIntegrationNames(): Promise> { try { const listed = await kody.integrationList({}) const integrations = listed && typeof listed === 'object' && Array.isArray((listed as { integrations?: unknown }).integrations) ? (listed as { integrations: Array<{ name?: string }> }).integrations : Array.isArray(listed) ? listed : [] return integrations .map((item) => (typeof item?.name === 'string' ? item.name : '')) .filter((name) => name === 'kit' || name.startsWith('kit-')) } catch { return [] } } export { resolveIntegrationName, resolveSecretName } export async function resolveKitAuth(input: KitAuthInput = {}): Promise { const integrationName = resolveIntegrationName(input) const secretName = resolveSecretName(input) const forced = input.auth if (forced !== undefined && forced !== 'oauth' && forced !== 'apiKey') { throw new Error('auth must be "oauth" or "apiKey".') } const [secrets, integrations] = await Promise.all([ listUserSecretNames(), listKitIntegrationNames(), ]) const hasOauth = integrations.includes(integrationName) const hasApiKey = secrets.has(secretName) let mode: KitAuthMode if (forced === 'oauth') { if (!hasOauth) { throw new Error(missingCredentialsMessage({ integrationName, secretName })) } mode = 'oauth' } else if (forced === 'apiKey') { if (!hasApiKey) { throw new Error(missingCredentialsMessage({ integrationName, secretName })) } mode = 'apiKey' } else if (hasApiKey) { mode = 'apiKey' } else if (hasOauth) { mode = 'oauth' } else { throw new Error(missingCredentialsMessage({ integrationName, secretName })) } switch (mode) { case 'oauth': case 'apiKey': return { mode, integrationName, secretName } default: { const exhaustive: never = mode throw new Error(`Unsupported Kit auth mode: ${String(exhaustive)}`) } } } async function getOauthFetch(integrationName: string): Promise { let authedFetch = authenticatedFetches.get(integrationName) if (!authedFetch) { try { authedFetch = await createAuthenticatedFetch(integrationName) } catch (error) { const message = error instanceof Error ? error.message : String(error) throw new Error( [ `Kit OAuth integration "${integrationName}" is not usable: ${message}`, missingCredentialsMessage({ integrationName, secretName: DEFAULT_API_KEY_SECRET, }), ].join(' '), ) } authenticatedFetches.set(integrationName, authedFetch) } return authedFetch } export async function kitFetch( auth: ResolvedKitAuth, url: string, init: RequestInit, ): Promise { switch (auth.mode) { case 'oauth': { const authedFetch = await getOauthFetch(auth.integrationName) return authedFetch(url, init) } case 'apiKey': { const headers = new Headers(init.headers) headers.set('X-Kit-Api-Key', `{{secret:${auth.secretName}}}`) return fetch(url, { ...init, headers }) } default: { const exhaustive: never = auth.mode throw new Error(`Unsupported Kit auth mode: ${String(exhaustive)}`) } } } export function requireAuthMode(value: unknown): KitAuthMode | undefined { if (value === undefined) return undefined const mode = requireString(value, 'auth') if (mode !== 'oauth' && mode !== 'apiKey') { throw new Error('auth must be "oauth" or "apiKey".') } return mode } export function parseAuthInput(input: KitAuthInput & Record = {}): KitAuthInput { return { integrationName: optionalString(input.integrationName, 'integrationName'), integration: optionalString(input.integration, 'integration'), account: optionalString(input.account, 'account'), secretName: optionalString(input.secretName, 'secretName'), auth: requireAuthMode(input.auth), } }