proto(woo): per-browser Woo creds via X-Woo-* request headers

Prototype routing so 2–3 owners can share one store URL with different WP
application passwords. Device creds live in localStorage; server uses
request override → Settings fallback and never writes overrides into the
Settings row. Inference stays on shared Settings.

See PROTOTYPE.md for UAT with two browsers and known LAN forge risk.
This commit is contained in:
grokbot_ty
2026-09-26 12:26:21 -07:00
parent fc40c66c44
commit 2f6335ec26
8 changed files with 419 additions and 31 deletions
+78
View File
@@ -0,0 +1,78 @@
/**
* Browser-local Woo credentials (prototype per-user routing).
*
* Stored in localStorage on this device only — never written to server Settings
* via the device-creds path. Attached as X-Woo-Username / X-Woo-App-Password on
* Woo-touching API calls. See PROTOTYPE.md.
*/
const LS_USER = 'slabuploader.woo.device.username';
const LS_PASS = 'slabuploader.woo.device.appPassword';
export const HEADER_WOO_USERNAME = 'X-Woo-Username';
export const HEADER_WOO_APP_PASSWORD = 'X-Woo-App-Password';
export interface DeviceWooCreds {
username: string;
appPassword: string;
}
function safeGet(key: string): string {
try {
return (localStorage.getItem(key) || '').trim();
} catch {
return '';
}
}
function safeSet(key: string, value: string): void {
try {
if (value) localStorage.setItem(key, value);
else localStorage.removeItem(key);
} catch {
/* private mode / quota — ignore */
}
}
export function getDeviceWooCreds(): DeviceWooCreds {
return {
username: safeGet(LS_USER),
appPassword: safeGet(LS_PASS),
};
}
/** Persist this browser's Woo login. Empty strings clear the slot. */
export function setDeviceWooCreds(creds: DeviceWooCreds): void {
safeSet(LS_USER, (creds.username || '').trim());
safeSet(LS_PASS, (creds.appPassword || '').trim());
}
export function clearDeviceWooCreds(): void {
safeSet(LS_USER, '');
safeSet(LS_PASS, '');
}
/** Headers to merge into Woo-backed fetch calls. Empty object when unset. */
export function wooOverrideHeaders(): Record<string, string> {
const { username, appPassword } = getDeviceWooCreds();
if (!username || !appPassword) return {};
return {
[HEADER_WOO_USERNAME]: username,
[HEADER_WOO_APP_PASSWORD]: appPassword,
};
}
/** Merge Content-Type / caller headers with device Woo override when present. */
export function withWooHeaders(extra?: HeadersInit): HeadersInit {
const woo = wooOverrideHeaders();
if (!extra) return woo;
if (extra instanceof Headers) {
const h = new Headers(extra);
for (const [k, v] of Object.entries(woo)) h.set(k, v);
return h;
}
if (Array.isArray(extra)) {
return [...extra, ...Object.entries(woo)];
}
return { ...extra, ...woo };
}