console fill: never copy Google session cookies by default (device-bound); own profile in a Chrome started without automation switches
Google binds its session cookies to the device (Device Bound Session Credentials) and signs out sessions whose cookies look stolen, which can sign the owner out of their real Chrome. The default is now storebound's own profile per store; storebound starts Chrome itself (--user-data-dir, --remote-debugging-port=0, no --enable-automation, navigator.webdriver false) and attaches over CDP. --use-chrome-session google|apple is an explicit opt-in that warns. /signin/rejected is said plainly.
This commit is contained in:
parent
e0bef82b06
commit
0035931734
22 changed files with 456 additions and 156 deletions
|
|
@ -1,7 +1,7 @@
|
|||
// AI-assisted autofill of console forms that have no API, in a visible Chromium the owner signs in to:
|
||||
// storebound console fill lists the forms
|
||||
// storebound console fill <form> --dry [--json] the answers and the step-by-step guidance; opens no browser
|
||||
// storebound console fill <form> [--store apple|play] [--json] [--profile <dir>]
|
||||
// storebound console fill <form> [--store apple|play] [--json] [--profile <dir>] [--use-chrome-session google|apple]
|
||||
// Forms: apple-app-record, apple-age-rating (guide only: `storebound apple appinfo` sets it), apple-app-privacy,
|
||||
// play-app-record (Play Console's Create app form: the Play Developer API cannot create apps), play-content-rating,
|
||||
// play-target-audience. Answers come from the config only (cli/lib/console-answers.ts); anything
|
||||
|
|
@ -19,9 +19,11 @@
|
|||
// final button; the command ends when the window is closed. Locators are accessible names only
|
||||
// (cli/lib/console-forms.ts); a missing or ambiguous field means the UI changed: filling stops and the guidance is
|
||||
// printed instead. Not exposed over MCP.
|
||||
// Browser (cli/console/session.ts): the owner's running Chrome (--cdp / remote debugging), else a temporary copy of only
|
||||
// their Chrome sign-in cookies (macOS; deleted at the end), else storebound's own profile
|
||||
// (~/.storebound/console-profile/<store>, or --profile / STOREBOUND_CONSOLE_PROFILE / --fresh-profile).
|
||||
// Browser (cli/console/session.ts): storebound's own profile (~/.storebound/console-profile/<store>, or --profile /
|
||||
// STOREBOUND_CONSOLE_PROFILE) in a Chrome storebound starts without automation switches; the owner's running Chrome
|
||||
// when it has remote debugging (--cdp); a copy of the owner's Chrome sign-in only with --use-chrome-session google|apple
|
||||
// (opt-in: Google binds its cookies to the device and can sign the owner out of the real Chrome).
|
||||
// Google's /signin/rejected page is said plainly (GOOGLE_REJECTED).
|
||||
// Sources (checked October 2026): see cli/lib/console-answers.ts; Playwright launchPersistentContext, getByRole,
|
||||
// getByLabel: https://playwright.dev/docs/api/class-browsertype#browser-type-launch-persistent-context ,
|
||||
// https://playwright.dev/docs/locators
|
||||
|
|
@ -36,7 +38,7 @@ import { log, printJson, warn } from '../lib/log';
|
|||
import { openAcross, withAccountIds } from './accounts';
|
||||
import { appleAppId, withAppleAppId } from './app-id';
|
||||
import { bareUrl } from './debug';
|
||||
import { type ConsoleBrowser, openConsoleBrowser } from './session';
|
||||
import { type ConsoleBrowser, GOOGLE_REJECTED, googleRejected, openConsoleBrowser, parseUseChromeSession } from './session';
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
export type Page = any;
|
||||
|
|
@ -741,7 +743,7 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
if (!form) {
|
||||
const forms = FORM_IDS.map(id => { const m = formMeta(id, config); return { id, store: m.store, title: m.title, fillable: m.fillable }; });
|
||||
if (json) return printJson({ forms });
|
||||
console.log('storebound console fill <form> [--store apple|play] [--dry] [--json] [--profile <dir>] [--step-timeout <seconds>] [--debug-dump]\n');
|
||||
console.log('storebound console fill <form> [--store apple|play] [--dry] [--json] [--profile <dir>] [--use-chrome-session google|apple] [--step-timeout <seconds>] [--debug-dump]\n');
|
||||
for (const f of forms) console.log(` ${f.id.padEnd(22)} ${f.store.padEnd(6)} ${f.title}${f.fillable ? '' : ' (guide only)'}`);
|
||||
return;
|
||||
}
|
||||
|
|
@ -765,6 +767,7 @@ export async function run(ctx: Ctx): Promise<void> {
|
|||
const b = await openConsoleBrowser(root, {
|
||||
store: meta.store, profileDir: dir, say, fresh: args.has('fresh-profile') || ownProfile,
|
||||
...(args.get('cdp') ? { cdp: args.get('cdp') } : {}), ...(args.get('chrome-profile') ? { chromeProfile: args.get('chrome-profile') } : {}),
|
||||
useChromeSession: parseUseChromeSession(args.get('use-chrome-session') ?? (args.has('use-chrome-session') ? true : undefined)),
|
||||
});
|
||||
// Ctrl+C: close the browser gracefully (Chromium writes its cookies on a clean exit, so the sign-in is kept for the
|
||||
// next run), then remove the temporary profile of copy mode; that also happens on every other exit path
|
||||
|
|
@ -887,7 +890,7 @@ export async function fillSession(b: ConsoleBrowser, o: FillSessionOpts): Promis
|
|||
async function signedIn(): Promise<boolean> {
|
||||
const start = Date.now(), end = start + signInMs, first = firstWait;
|
||||
firstWait = false;
|
||||
let told = false, ok = 0, beat = start, watched = false, entered = false;
|
||||
let told = false, ok = 0, beat = start, watched = false, entered = false, rejected = false;
|
||||
const signInDump = async (p: Page | undefined, label: string) => {
|
||||
if (!o.debugEveryStep || !p || signInDumps >= 3) return;
|
||||
signInDumps++;
|
||||
|
|
@ -896,6 +899,7 @@ export async function fillSession(b: ConsoleBrowser, o: FillSessionOpts): Promis
|
|||
while (!b.isClosed() && Date.now() < end) {
|
||||
const p = active();
|
||||
const w = p ? await signInWhy(p) : null;
|
||||
if (p && !rejected && googleRejected(p.url())) { rejected = true; say(GOOGLE_REJECTED); }
|
||||
const state = (): string => !p ? 'no tab open' : `${bareUrl(p.url()) || p.url()}: ${w ? `a sign-in page (${w.rule})` : reached(p) ? 'the console' : 'neither a sign-in page nor the console yet'}`;
|
||||
// the wait really starts: a sign-in page, or a page that is still not the console after a few rounds
|
||||
if (p && !entered && (w || (!reached(p) && Date.now() - start >= 3 * tick))) { entered = true; await signInDump(p, 'sign-in wait'); }
|
||||
|
|
@ -914,7 +918,7 @@ export async function fillSession(b: ConsoleBrowser, o: FillSessionOpts): Promis
|
|||
sayWhy(w);
|
||||
if (!told) {
|
||||
told = true;
|
||||
if (first && b.mode === 'copy' && isSignInUrl(p.url())) say(`your Chrome session for ${consoleName} did not carry over (expired or tied to this device); sign in once in this window, or run with --fresh-profile`);
|
||||
if (first && b.mode === 'copy' && isSignInUrl(p.url())) say(`your copied Chrome session for ${consoleName} did not carry over (expired or tied to your own Chrome); sign in once in this window, or run without --use-chrome-session`);
|
||||
signInHint();
|
||||
}
|
||||
} else if (p && reached(p)) { if (++ok >= 2) { if (told) say('signed in'); lastWhy = ''; return true; } }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue