189 lines
4.8 KiB
Markdown
189 lines
4.8 KiB
Markdown
# Nano Banana Browser POC
|
|
|
|
This repo includes a small browser-driven POC for inspecting Nano Banana's free-tool flow without trying to bypass Turnstile, login, or rate limits.
|
|
|
|
## What it does
|
|
|
|
- Opens the Nano Banana page in a real browser
|
|
- Optionally prefills a public image URL
|
|
- Optionally clicks the primary action button
|
|
- Captures requests and responses for the relevant private endpoints
|
|
- Saves screenshots, console logs, downloads, and browser storage state
|
|
|
|
## What it does not do
|
|
|
|
- It does not bypass Cloudflare Turnstile
|
|
- It does not bypass login
|
|
- It does not try to evade rate limits
|
|
- It is not a production API adapter
|
|
|
|
## Commands
|
|
|
|
Remove background:
|
|
|
|
```bash
|
|
pnpm poc:nanobanana --mode remove-background --auto-submit
|
|
```
|
|
|
|
Upscale:
|
|
|
|
```bash
|
|
pnpm poc:nanobanana --mode upscale --auto-submit
|
|
```
|
|
|
|
Use a custom image URL:
|
|
|
|
```bash
|
|
pnpm poc:nanobanana \
|
|
--mode upscale \
|
|
--image-url "https://example.com/demo.jpg" \
|
|
--auto-submit
|
|
```
|
|
|
|
## Useful flags
|
|
|
|
- `--locale en`
|
|
- `--headless`
|
|
- `--no-auto-fill`
|
|
- `--auto-submit`
|
|
- `--close-on-success`
|
|
- `--timeout-ms 300000`
|
|
- `--out-dir /absolute/path/to/output`
|
|
|
|
## Output
|
|
|
|
By default, artifacts are written to:
|
|
|
|
```text
|
|
playwright/nanobanana-poc/<mode>-<timestamp>/
|
|
```
|
|
|
|
The folder includes:
|
|
|
|
- `summary.json`
|
|
- `session-plan.json`
|
|
- `storage-state-initial.json`
|
|
- `storage-state-final.json`
|
|
- `requests/*.json`
|
|
- `responses/*.json`
|
|
- `logs/*.json`
|
|
- screenshots such as `01-loaded.png` and `99-final.png`
|
|
|
|
## Current endpoints observed
|
|
|
|
- `/api/img/rmbg`
|
|
- `/api/img/upscale`
|
|
- `/api/img/result/{taskId}?type=img`
|
|
- `/api/img/image-safety`
|
|
- `/api/presigned-urls`
|
|
|
|
## Current findings (2026-05-21)
|
|
|
|
These findings were confirmed from the live frontend bundle and manual browser validation.
|
|
|
|
### Remove background
|
|
|
|
Frontend schema:
|
|
|
|
- `imageSource`: `"upload"` or `"url"`
|
|
- `imageUrl`: optional string
|
|
- `turnstileToken`: optional string
|
|
|
|
Observed request shape:
|
|
|
|
```json
|
|
{
|
|
"imageSource": "url",
|
|
"imageUrl": "https://...",
|
|
"turnstileToken": "..."
|
|
}
|
|
```
|
|
|
|
Observed endpoint behavior:
|
|
|
|
- `POST /api/img/rmbg`
|
|
- `Content-Type: application/json`
|
|
- success path reads `response.data` as the final output image URL
|
|
- after success the page clears `turnstileToken`
|
|
- `403` with `message === "nsfw"` is handled as an NSFW block
|
|
|
|
Manual validation in the in-app browser produced a successful result and exposed a final image URL hosted on `replicate.delivery`, which suggests the private backend may fan out to third-party storage or processing before returning the final asset URL.
|
|
|
|
### Image upscaler
|
|
|
|
Frontend schema:
|
|
|
|
- `imageSource`: `"upload"` or `"url"`
|
|
- `imageUrl`: optional string
|
|
- `scale`: number, min `1`, max `8`, default `4`
|
|
- `turnstileToken`: optional string
|
|
|
|
Observed submit request shape:
|
|
|
|
```json
|
|
{
|
|
"imageUrl": "https://...",
|
|
"scale": "4",
|
|
"turnstileToken": "..."
|
|
}
|
|
```
|
|
|
|
Upload mode can also send an `image` field populated from the uploaded asset URL.
|
|
|
|
Observed endpoint behavior:
|
|
|
|
- `POST /api/img/upscale`
|
|
- `Content-Type: application/json`
|
|
- success path reads `taskId` from `response.data.taskId` or `response.taskId`
|
|
- the frontend then polls `GET /api/img/result/{taskId}?type=img`
|
|
- polling treats `code === 1` or `status === 1` as success
|
|
- final output is read from `data.imgAfterSrc` or `imgAfterSrc`
|
|
- polling treats `code === 2` or `status === 2` as failure
|
|
- the polling loop waits 2 seconds and retries up to 60 times
|
|
- `402` is handled as an upgrade/paywall condition
|
|
- `403` with `message === "nsfw"` is handled as an NSFW block
|
|
|
|
### Turnstile
|
|
|
|
Observed Cloudflare site key:
|
|
|
|
- `0x4AAAAAAAh8CLI0IWTYOWMY`
|
|
|
|
Observed frontend behavior:
|
|
|
|
- when `turnstileToken` is missing, the frontend renders or executes Turnstile before submitting
|
|
- after a successful request, the frontend clears `turnstileToken`
|
|
- this means the free-tool flow is not designed as a stable unattended API
|
|
|
|
### Upload helper
|
|
|
|
Observed upload helper behavior:
|
|
|
|
- `POST /api/presigned-urls` with JSON like `{ "count": 1, "fileType": "<mime>" }`
|
|
- response returns `urls`
|
|
- the browser `PUT`s the file to the returned presigned URL
|
|
- the frontend then derives a public asset URL in the form:
|
|
|
|
```text
|
|
https://temp.videostudioai.com/<key>
|
|
```
|
|
|
|
### Practical takeaway
|
|
|
|
This is enough to build an internal unofficial wrapper, but not enough to remove the core operational risk:
|
|
|
|
- the flow still depends on Turnstile
|
|
- Turnstile tokens are intentionally short-lived and cleared after use
|
|
- the frontend contract may change at any time
|
|
- upscaling still appears more likely to require session state than remove-background
|
|
|
|
## Suggested use
|
|
|
|
Use this only as an internal validation tool to answer questions like:
|
|
|
|
- Does the browser session create anonymous or guest state?
|
|
- When does Turnstile appear?
|
|
- Which cookies or local storage values appear before the request?
|
|
- Does upscaling require an authenticated session?
|
|
- What request and polling shape does the frontend use today?
|