Skip to content

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

Package listing

@kody/morning-briefing

src/google-setup.ts

59 lines · 3.0 KB · TypeScript
const CALENDAR_READONLY_SCOPE = 'https://www.googleapis.com/auth/calendar.readonly'
const GMAIL_READONLY_SCOPE = 'https://www.googleapis.com/auth/gmail.readonly'

export const GOOGLE_CALENDAR_CONNECT_URL = 'https://kody.codes/connect/oauth?provider=google'

export const GOOGLE_INBOX_CONNECT_URL =
	'https://kody.codes/connect/oauth?provider=google&authorizeUrl=https%3A%2F%2Faccounts.google.com%2Fo%2Foauth2%2Fv2%2Fauth&tokenUrl=https%3A%2F%2Foauth2.googleapis.com%2Ftoken&flow=confidential&scopes=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar.readonly%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fgmail.readonly&allowedHosts=www.googleapis.com%2Cgmail.googleapis.com&extraAuthorizeParams=%7B%22access_type%22%3A%22offline%22%2C%22prompt%22%3A%22consent%22%7D'

function errorMessage(error: unknown): string {
	if (error instanceof Error && error.message) return error.message
	if (error && typeof error === 'object' && 'message' in error) {
		const message = (error as { message?: unknown }).message
		if (typeof message === 'string' && message.trim()) return message
	}
	return String(error || 'Unknown error')
}

function httpStatus(error: unknown): number | undefined {
	if (!error || typeof error !== 'object') return undefined
	const status = (error as { status?: unknown }).status
	return typeof status === 'number' ? status : undefined
}

function looksLikeInsufficientScope(error: unknown): boolean {
	const status = httpStatus(error)
	const message = errorMessage(error)
	return (
		status === 403 ||
		/insufficient[\s._-]?scope|ACCESS_TOKEN_SCOPE|accessNotConfigured|has not been used in project/i.test(
			message,
		)
	)
}

function looksLikeMissingAuth(error: unknown): boolean {
	return /Could not authenticate|not found|no refresh|Missing required Google account/i.test(
		errorMessage(error),
	)
}

export function googleCalendarSetupError(error: unknown, account: string): Error {
	const original = errorMessage(error)
	if (!looksLikeInsufficientScope(error) && !looksLikeMissingAuth(error)) {
		return error instanceof Error ? error : new Error(original)
	}
	return new Error(
		`Google Calendar could not run for account "${account}". It needs the ${CALENDAR_READONLY_SCOPE} scope (or calendar.events.readonly). Connect or reconnect the built-in integration at ${GOOGLE_CALENDAR_CONNECT_URL} and grant Calendar, then retry. Original error: ${original}`,
	)
}

export function googleInboxSetupError(error: unknown, account: string): Error {
	const original = errorMessage(error)
	if (!looksLikeInsufficientScope(error) && !looksLikeMissingAuth(error)) {
		return error instanceof Error ? error : new Error(original)
	}
	return new Error(
		`Gmail inbox reading could not run for account "${account}". Inbox access needs the ${GMAIL_READONLY_SCOPE} scope, which is restricted and is not on Kody's built-in Google OAuth menu. Bring your own Google OAuth client, enable the Gmail API, publish the app to Production (Testing refresh tokens expire after 7 days), and connect at ${GOOGLE_INBOX_CONNECT_URL}. Original error: ${original}`,
	)
}