import type { KitAuthInput, KitAuthMode } from './types.ts'
import { optionalString, requireString } from './types.ts'
export const KIT_API_BASE_URL = 'https://api.kit.com/v4'
export const KIT_AUTHORIZE_URL = 'https://api.kit.com/v4/oauth/authorize'
export const KIT_TOKEN_URL = 'https://api.kit.com/v4/oauth/token'
export const KIT_REQUIRED_HOSTS = ['api.kit.com'] as const
export const KIT_REQUIRED_HOST = 'api.kit.com'
export const KIT_CALLBACK_URL = 'https://kody.codes/connect/oauth'
export const KIT_OAUTH_APP_URL =
'https://developers.kit.com/kit-app-store/building-apps#creating-your-app'
export const KIT_API_KEY_DASHBOARD_URL = 'https://app.kit.com/account_settings/developer_settings'
export const KIT_AUTH_DOCS_URL = 'https://developers.kit.com/api-reference/authentication'
export const DEFAULT_INTEGRATION_NAME = 'kit'
export const DEFAULT_API_KEY_SECRET = 'kitApiKey'
export const DEFAULT_OAUTH_SCOPES = ['public'] as const
export const KIT_SCOPES = {
public: 'Default Kit App Store scope. Fine-grained scopes are not generally available yet.',
read: 'Read access to Kit API v4 when the app requests it.',
write: 'Write access to Kit API v4 when the app requests it.',
} as const
export function resolveIntegrationName(input: KitAuthInput = {}): string {
const explicit =
optionalString(input.integrationName, 'integrationName') ??
optionalString(input.integration, 'integration')
if (explicit) return explicit
const account = optionalString(input.account, 'account')
if (!account || account === 'default' || account === 'kit') {
return DEFAULT_INTEGRATION_NAME
}
if (account.startsWith('kit-')) return account
return `kit-${account}`
}
export function resolveSecretName(input: KitAuthInput = {}): string {
const explicit = optionalString(input.secretName, 'secretName')
if (explicit) return explicit
const integrationName = resolveIntegrationName(input)
if (integrationName === DEFAULT_INTEGRATION_NAME) return DEFAULT_API_KEY_SECRET
const suffix = integrationName.startsWith('kit-')
? integrationName.slice('kit-'.length)
: integrationName
return `${DEFAULT_API_KEY_SECRET}-${suffix}`
}
export function oauthConnectUrl(provider: string = DEFAULT_INTEGRATION_NAME): string {
const name = requireString(provider, 'provider')
const params = new URLSearchParams({
provider: name,
authorizeUrl: KIT_AUTHORIZE_URL,
tokenUrl: KIT_TOKEN_URL,
apiBaseUrl: KIT_API_BASE_URL,
scopes: DEFAULT_OAUTH_SCOPES.join(' '),
flow: 'confidential',
pkce: 'false',
allowedHosts: KIT_REQUIRED_HOSTS.join(','),
dashboardUrl: KIT_OAUTH_APP_URL,
})
return `https://kody.codes/connect/oauth?${params.toString()}`
}
export function apiKeySetupUrl(secretName: string = DEFAULT_API_KEY_SECRET): string {
const name = requireString(secretName, 'secretName')
const params = new URLSearchParams({
name,
description: 'Kit v4 API key for subscribers, forms, tags, broadcasts, and sequences',
allowedHosts: KIT_REQUIRED_HOSTS.join(','),
scope: 'user',
})
return `https://kody.codes/account/secrets/new?${params.toString()}`
}
export function reconnectUrl(provider: string = DEFAULT_INTEGRATION_NAME): string {
return `https://kody.codes/connect/oauth?provider=${encodeURIComponent(provider)}`
}
export function missingCredentialsMessage(options: {
integrationName: string
secretName: string
}): string {
return [
'Kit credentials are missing.',
'API key (recommended for personal automation): create a v4 key at',
KIT_API_KEY_DASHBOARD_URL,
'then save it (do not paste the value in chat):',
apiKeySetupUrl(options.secretName),
'OAuth (Kit App Store apps): create an app at',
KIT_OAUTH_APP_URL,
`with redirect URI ${KIT_CALLBACK_URL}, then connect:`,
oauthConnectUrl(options.integrationName),
`Required API host: ${KIT_REQUIRED_HOST}. Auth docs: ${KIT_AUTH_DOCS_URL}.`,
].join(' ')
}
export function nextStepForAuth(options: {
authMode: KitAuthMode
integrationName: string
secretName: string
}): string {
switch (options.authMode) {
case 'oauth':
return [
`Reconnect the "${options.integrationName}" OAuth integration.`,
reconnectUrl(options.integrationName),
`Full BYO connect URL: ${oauthConnectUrl(options.integrationName)}`,
].join(' ')
case 'apiKey':
return [
`Save a Kit v4 API key as ${options.secretName} and approve host ${KIT_REQUIRED_HOST}.`,
KIT_API_KEY_DASHBOARD_URL,
apiKeySetupUrl(options.secretName),
].join(' ')
default: {
const exhaustive: never = options.authMode
throw new Error(`Unsupported Kit auth mode: ${String(exhaustive)}`)
}
}
}
export function isMutatingMethod(method: string): boolean {
const normalized = method.toUpperCase()
return (
normalized === 'POST' ||
normalized === 'PUT' ||
normalized === 'PATCH' ||
normalized === 'DELETE'
)
}
export const OAUTH_CONNECT_URL = oauthConnectUrl()
export const API_KEY_SETUP_URL = apiKeySetupUrl()