Let the extension update itself, and host the dashboard
Chrome has no update channel for an unpacked extension, so every pull left one manual step: click reload on its card. The dashboard now publishes the version its own checkout ships at /api/autobuyer/status, and the extension reloads itself when it is behind — chrome.runtime.reload() re-reads the folder from disk, which the update check has already pulled into. It only ever runs code that is on disk, so a reload that changes nothing is reported and waited out rather than repeated; a dashboard that is behind is ignored. Also adds the deploy stack for running the dashboard on the droplet, next to the autotrader one: it joins that nginx's network and renders a server block into its conf.d rather than standing up a second ingress. AUTOFIRMER_DB moves the SQLite file onto a volume, since the working directory in a container is replaced on every deploy. And waitFor gained `hidden`: an absent gate matched on DOM presence only, so a modal that closes by going display:none could never satisfy it. The extension grew a visibility probe for this — locate cannot answer it, because absent and hidden are both errors there and a wait gate has to tell them apart. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1f4459985a
commit
d1e8668440
@@ -2,8 +2,11 @@ import { NextResponse } from 'next/server';
|
||||
|
||||
/** The Chromium extension calls these routes from a background service worker,
|
||||
* whose Origin is `chrome-extension://<id>`. The id changes every time the
|
||||
* unpacked extension is reloaded, so we allow any origin — these routes are
|
||||
* only ever reachable on the LAN behind the port-3000 firewall rule. */
|
||||
* unpacked extension is reloaded, so we allow any origin.
|
||||
*
|
||||
* That was harmless while this only ever answered on the LAN behind the
|
||||
* port-3000 firewall rule. The hosted dashboard has no such fence — see
|
||||
* deploy/README.md, "No login". */
|
||||
export const CORS_HEADERS = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PATCH, DELETE, OPTIONS',
|
||||
|
||||
@@ -1,11 +1,41 @@
|
||||
import { readFileSync, statSync } from 'fs';
|
||||
import path from 'path';
|
||||
import { countPendingLocate } from '@/lib/db';
|
||||
import { corsJson, corsPreflight } from '../cors';
|
||||
|
||||
/** The extension version this checkout ships.
|
||||
*
|
||||
* Read out of the manifest rather than kept as a constant here: the manifest is
|
||||
* the file Chrome actually loads, so the two cannot drift apart. Cached on
|
||||
* mtime because every browser polls this every few seconds and the file only
|
||||
* changes when an update lands. Null when there is no extension folder beside
|
||||
* the server — the extension then leaves itself alone, which is the right
|
||||
* answer for a dashboard that cannot see what it is supposed to be running. */
|
||||
let manifestCache: { mtimeMs: number; version: string | null } | null = null;
|
||||
|
||||
function shippedExtensionVersion(): string | null {
|
||||
const file = path.join(process.cwd(), 'extension', 'manifest.json');
|
||||
try {
|
||||
const { mtimeMs } = statSync(file);
|
||||
if (!manifestCache || manifestCache.mtimeMs !== mtimeMs) {
|
||||
const parsed = JSON.parse(readFileSync(file, 'utf8')) as { version?: string };
|
||||
manifestCache = { mtimeMs, version: parsed.version ?? null };
|
||||
}
|
||||
return manifestCache.version;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Polled by the Chromium extension every few seconds, and by the AutoBuyer page.
|
||||
* The AutoBuyer has no on/off switch — it is ready whenever the runner is — so
|
||||
* the only thing worth reporting here is whether clicks are waiting to be served. */
|
||||
* the only things worth reporting here are whether clicks are waiting to be
|
||||
* served, and which extension build this checkout expects to be talking to. */
|
||||
export async function GET() {
|
||||
return corsJson({ pendingLocate: countPendingLocate() });
|
||||
return corsJson({
|
||||
pendingLocate: countPendingLocate(),
|
||||
extensionVersion: shippedExtensionVersion(),
|
||||
});
|
||||
}
|
||||
|
||||
export async function OPTIONS() {
|
||||
|
||||
Reference in New Issue
Block a user