// A Google 401 / 403 that says what is missing (SERVICE_DISABLED, PERMISSION_DENIED, a refused key) becomes one // GoogleAccessError with the exact API or grant and the link to fix it. It is never retried: the same call fails the // same way until the owner (or the credential broker) changes something. With the broker as the token source the text // also names the Play permissions the broker lists for the package and the broker-side fix. import { brokerConfig, brokerPlayInfo, grantsOf, redact } from './broker'; import { findingText, type GoogleFinding, parseGoogleError } from './google-errors'; export class GoogleAccessError extends Error { /** Never sent again: retry loops and the upsert layer check this. */ readonly noRetry = true; constructor(msg: string, readonly finding: GoogleFinding) { super(msg); } } /** The GoogleAccessError for a failed call's text, or null when the text is another error. */ export async function accessError(text: string, o: { app?: string | null; root?: string; source?: 'broker' | 'file' } = {}): Promise { const f = parseGoogleError(text, { app: o.app ?? null }); if (!f || f.kind === 'quota') return null; const lines = [redact(text), findingText(f, { withUrl: true })]; if (o.source === 'broker') { if (f.kind === 'auth-revoked') lines.push('The token came from the credential broker: its key for this service account was revoked or deleted at Google; rotate the key on the broker.'); if (f.kind === 'api-disabled') lines.push(`Or enable it on the credential broker (google_api_enable ${f.projectId ?? ''} ${f.service}).`); if (f.kind === 'no-app-access' && o.root && o.app) { try { const cfg = brokerConfig(o.root); const r = cfg ? await brokerPlayInfo(cfg, o.app) : null; if (r?.kind === 'ok') { const g = grantsOf(r.info); lines.push(`The credential broker lists ${g ? (g.length ? g.join(', ') : 'no Play permissions') : 'no permission data'} for ${r.info.account ?? 'the service account'} on ${o.app}: grant the missing one there (google_play_grant).`); } else if (r) lines.push(r.message); } catch { /* the Google error stands on its own */ } } } return new GoogleAccessError(lines.join('\n'), f); }