Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/zendesk

src/accounts.ts

125 lines · 3.8 KB · TypeScript
import { parseAuth, listUserSecretNames, listZendeskIntegrationNames } from './auth.ts'
import { readStoredConfig, resolveEmail } from './config.ts'
import type { ZendeskAuthInput, ZendeskAuthMode } from './types.ts'
import { requireRecord } from './types.ts'
import {
	API_TOKEN_SETUP_URL,
	API_USERNAME_SETUP_URL,
	DEFAULT_INTEGRATION_NAME,
	DEFAULT_TOKEN_SECRET,
	DEFAULT_USERNAME_SECRET,
	OAUTH_CONNECT_URL,
	SUBDOMAIN_PLACEHOLDER,
	ZENDESK_CALLBACK_URL,
	ZENDESK_SCOPES,
	apiTokenSetupUrl,
	apiUsernameSetupUrl,
	oauthConnectUrl,
	optionalSubdomain,
	resolveIntegrationName,
	resolveSecretName,
	resolveUsernameSecretName,
	zendeskHost,
} from './setup.ts'

export type ZendeskAccountResolution = {
	integrationName: string
	secretName: string
	usernameSecretName: string
	oauthConnected: boolean
	apiTokenSaved: boolean
	apiUsernameSaved: boolean
	preferredAuth: ZendeskAuthMode | null
	subdomain: string | null
	hasEmail: boolean
	apiHost: string | null
	connectUrl: string
	apiTokenUrl: string
	apiUsernameUrl: string
	callbackUrl: string
	requiredHosts: Array<string>
	scopes: typeof ZENDESK_SCOPES
}

/**
 * Resolve which Zendesk OAuth integration or API token to use.
 *
 * - `integration` / `integrationName` wins when set (exact name, e.g. `zendesk-work`)
 * - `account: 'work'` → `zendesk-work`
 * - `account: 'default'` / `'zendesk'` / omitted → `zendesk`
 * - Subdomain comes from the call or packageStorage `config.subdomain`
 *
 * @example
 * import listZendeskAccounts from 'kody:@kody/zendesk/accounts'
 * const accounts = await listZendeskAccounts({ subdomain: 'acme' })
 */
export async function accounts(
	params: ZendeskAuthInput = {},
): Promise<ZendeskAccountResolution> {
	const integrationName = resolveIntegrationName(params)
	const secretName = resolveSecretName(params)
	const usernameSecretName = resolveUsernameSecretName(params)
	const [secrets, integrations, stored, email] = await Promise.all([
		listUserSecretNames(),
		listZendeskIntegrationNames(),
		readStoredConfig(),
		resolveEmail(params),
	])
	const oauthConnected = integrations.includes(integrationName)
	const apiTokenSaved = secrets.has(secretName)
	const apiUsernameSaved = secrets.has(usernameSecretName)
	const forced = params.auth
	let preferredAuth: ZendeskAuthMode | null = null
	if (forced === 'oauth' && oauthConnected) preferredAuth = 'oauth'
	else if (forced === 'apiToken' && apiTokenSaved && apiUsernameSaved) preferredAuth = 'apiToken'
	else if (oauthConnected) preferredAuth = 'oauth'
	else if (apiTokenSaved && apiUsernameSaved) preferredAuth = 'apiToken'

	const subdomain =
		optionalSubdomain(params.subdomain) ?? stored.subdomain ?? null
	const host = subdomain ? zendeskHost(subdomain) : null

	return {
		integrationName,
		secretName,
		usernameSecretName,
		oauthConnected,
		apiTokenSaved,
		apiUsernameSaved,
		preferredAuth,
		subdomain,
		hasEmail: Boolean(email),
		apiHost: host,
		connectUrl: oauthConnectUrl(integrationName, subdomain ?? SUBDOMAIN_PLACEHOLDER),
		apiTokenUrl: apiTokenSetupUrl(secretName, subdomain ?? SUBDOMAIN_PLACEHOLDER),
		apiUsernameUrl: apiUsernameSetupUrl(
			usernameSecretName,
			subdomain ?? SUBDOMAIN_PLACEHOLDER,
			email,
		),
		callbackUrl: ZENDESK_CALLBACK_URL,
		requiredHosts: host ? [host] : [`${SUBDOMAIN_PLACEHOLDER}.zendesk.com`],
		scopes: ZENDESK_SCOPES,
	}
}

export {
	API_TOKEN_SETUP_URL,
	API_USERNAME_SETUP_URL,
	DEFAULT_INTEGRATION_NAME,
	DEFAULT_TOKEN_SECRET,
	DEFAULT_USERNAME_SECRET,
	OAUTH_CONNECT_URL,
}

/**
 * Resolve which Zendesk OAuth integration or API token to use.
 * @example
 * import listZendeskAccounts from 'kody:@kody/zendesk/accounts'
 * const info = await listZendeskAccounts({ account: 'work', subdomain: 'acme' })
 */
export default async function accountsEntrypoint(
	params: ZendeskAuthInput & Record<string, unknown> = {},
) {
	return accounts(parseAuth(requireRecord(params, 'accounts')))
}