credential broker: Play tokens from POST /api/google/token (cached, single flight, local key fallback), grants and APIs per package in validate, Play write preflight, apps check and the desktop Accounts view; Google 403 names the API or grant, no retry
This commit is contained in:
parent
304407e523
commit
2c5e30090f
27 changed files with 1145 additions and 43 deletions
|
|
@ -1,6 +1,6 @@
|
|||
// Google Play Developer API (androidpublisher v3) client: JSON + media calls with retry, and the edit lifecycle.
|
||||
import { playServiceAccount } from '../lib/credentials';
|
||||
import { tokenSource } from '../lib/google-auth';
|
||||
import { PLAY_SCOPE, playToken } from '../lib/google-auth';
|
||||
import { accessError } from '../lib/google-access';
|
||||
import { log, warn } from '../lib/log';
|
||||
import { errorBody } from '../lib/http';
|
||||
import { noteGoogle } from '../lib/quota';
|
||||
|
|
@ -15,7 +15,8 @@ export const EDIT_GONE = /Edit has been deleted|editAlreadyCommitted|already bee
|
|||
export type PlayClient = Awaited<ReturnType<typeof playClient>>;
|
||||
|
||||
export async function playClient(root: string, packageName: string) {
|
||||
const token = tokenSource(playServiceAccount(root));
|
||||
// the credential broker's token when one is configured, else the local service account key (cli/lib/google-auth.ts)
|
||||
const token = playToken(root, PLAY_SCOPE, { packageName });
|
||||
const API = `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${packageName}`;
|
||||
const UPLOAD = `https://androidpublisher.googleapis.com/upload/androidpublisher/v3/applications/${packageName}`;
|
||||
|
||||
|
|
@ -49,6 +50,8 @@ export async function playClient(root: string, packageName: string) {
|
|||
const msg = /^\s*</.test(text) ? `(HTML ${r.status} page: wrong path or casing?)` : errorBody(text);
|
||||
if (r.status >= 500 && !safe) throw new AmbiguousWriteError(`${method} ${short()} -> ${r.status} ${msg}`, r.status);
|
||||
if (r.status === 404 && method === 'DELETE' && attempt > 0) return {} as T;
|
||||
// a disabled API or a missing Play permission: the exact API / grant and its link, never retried (pitfall 122)
|
||||
if (r.status === 403 || r.status === 401) { const g = await accessError(`${method} ${short()} -> ${r.status} ${msg}`, { app: packageName, root, source: token.source() }); if (g) throw g; }
|
||||
throw new Error(`${method} ${short()} -> ${r.status} ${msg}`);
|
||||
}
|
||||
}
|
||||
|
|
@ -141,6 +144,8 @@ export async function playClient(root: string, packageName: string) {
|
|||
|
||||
return {
|
||||
API, UPLOAD, packageName, call,
|
||||
/** Where this client's tokens come from: "broker" or "file" (no values). */
|
||||
credentialSource: () => token.source(),
|
||||
/**
|
||||
* Read-only edit: opens one, runs fn (GETs only), then deletes it, also on error. Never validated or committed.
|
||||
* A failed delete is reported (Play drops an unused edit on its own after a while, but it should not linger).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue