Skip to content
← Public packages

@kody/google

Call Gmail, Calendar, Tasks, Drive, Docs, Sheets, People, YouTube, and Analytics through saved Google OAuth.

src/accounts.ts

130 lines · 4.8 KB · TypeScript
import { kody } from 'kody:runtime'

export const DEFAULT_GOOGLE_INTEGRATION = 'google'

export type GoogleAuthParams = {
	/** Saved Kody OAuth integration name. Wins over `account`. Defaults to `google`. */
	integration?: string
	/** Alias for `integration`. */
	integrationName?: string
	/**
	 * Account selector. Omitted / `default` / `google` → `google`.
	 * Values that already start with `google` are used as-is.
	 * Any other value is treated as a purpose suffix (`work` → `google-work`).
	 */
	account?: string
}

export type GoogleAccountInfo = {
	account: string
	integrationName: string
	label: string
	purpose: string | null
	useWhen: string
}

function normalizeIntegrationName(value: string): string {
	return value.trim().toLowerCase().replace(/[\s_]+/g, '-')
}

function purposeFromIntegration(integrationName: string): string | null {
	if (integrationName === DEFAULT_GOOGLE_INTEGRATION) return null
	if (integrationName.startsWith('google-')) return integrationName.slice('google-'.length)
	return integrationName
}

/**
 * Resolve and describe saved Google OAuth integrations (`google` or `google-<purpose>`).
 * @param integrationName - Saved OAuth integration name to describe.
 * @returns Account info with label, purpose, and when-to-use guidance.
 * @example
 * import accounts from 'kody:@kody/google/accounts'
 * const { defaultIntegration, accounts: list } = await accounts()
 * // => { defaultIntegration: 'google', accounts: [...], ... }
 */
export function describeGoogleAccount(integrationName: string): GoogleAccountInfo {
	const name = normalizeIntegrationName(integrationName)
	const purpose = purposeFromIntegration(name)
	return {
		account: purpose ?? 'default',
		integrationName: name,
		label: purpose ? `Google (${purpose})` : 'Google (default)',
		purpose,
		useWhen: purpose
			? `Use the saved OAuth integration named "${name}" for the "${purpose}" Google account.`
			: 'Use the default saved OAuth integration named "google".',
	}
}

/**
 * Resolve which saved Google OAuth integration to use.
 *
 * Multi-account is the Kody `<provider>-<purpose>` convention, not a closed
 * alias enum. Connect extra accounts as `google-work`, `google-youtube`, etc.
 * and pass `integration: 'google-work'` (or `account: 'work'`).
 */
export function resolveGoogleAccount(
	input: GoogleAuthParams | string = {},
): GoogleAccountInfo {
	const raw: GoogleAuthParams = typeof input === 'string' ? { account: input } : input || {}
	const explicit = raw.integration ?? raw.integrationName
	if (explicit && explicit.trim()) return describeGoogleAccount(explicit)

	const account = raw.account?.trim()
	if (!account || account === 'default' || account === DEFAULT_GOOGLE_INTEGRATION) {
		return describeGoogleAccount(DEFAULT_GOOGLE_INTEGRATION)
	}
	const normalized = normalizeIntegrationName(account)
	if (normalized === DEFAULT_GOOGLE_INTEGRATION || normalized.startsWith('google-')) {
		return describeGoogleAccount(normalized)
	}
	return describeGoogleAccount(`${DEFAULT_GOOGLE_INTEGRATION}-${normalized}`)
}

export function isGoogleIntegrationName(name: string): boolean {
	return name === DEFAULT_GOOGLE_INTEGRATION || name.startsWith('google-')
}

export async function listGoogleIntegrationNames(): Promise<string[]> {
	const listed = await kody.integrationList({})
	const rows = Array.isArray(listed)
		? listed
		: Array.isArray((listed as { integrations?: unknown }).integrations)
			? (listed as { integrations: unknown[] }).integrations
			: []
	const names = new Set<string>()
	for (const row of rows) {
		if (!row || typeof row !== 'object') continue
		const name = (row as { name?: unknown }).name
		if (typeof name === 'string' && isGoogleIntegrationName(name)) names.add(name)
	}
	if (names.size === 0) names.add(DEFAULT_GOOGLE_INTEGRATION)
	return [...names].sort()
}

export async function listGoogleAccounts(): Promise<GoogleAccountInfo[]> {
	const names = await listGoogleIntegrationNames()
	return names.map((name) => describeGoogleAccount(name))
}

/**
 * Describe how this package selects Google OAuth integrations.
 * @returns Default integration name, connected accounts, and connect URL.
 * @example
 * import accounts from 'kody:@kody/google/accounts'
 * const { defaultIntegration } = await accounts()
 * // => 'google'
 */
export default async function accounts() {
	return {
		defaultIntegration: DEFAULT_GOOGLE_INTEGRATION,
		naming: 'google or google-<purpose>',
		accounts: await listGoogleAccounts(),
		connectDefault: 'https://kody.codes/connect/oauth?provider=google',
		notes: [
			'Pass integrationName / integration for each saved connection. Default is "google".',
			'Name extra connections google-<purpose> (google-work, google-youtube). There is no reserved brand-channel alias table.',
			'Inbox reading and Drive-wide access need gmail.readonly / drive.readonly on the Google OAuth client you register.',
		],
	}
}