Files
gods-eye-view/scripts/qa-photo-stub.mjs
T
Sameh Khamis 1a8defbfc8 feat(flights): show the tracked aircraft's Planespotters photo in Contacts
Tracking a civil flight asks Planespotters for that aircraft's photo, once
per aircraft, straight from the browser. The CONTEXT panel's CONTACTS view
shows it under the flight's name with the photographer's credit, as a plain
link to the photo's page. Only images on plnspttrs.net and pages on
www.planespotters.net are ever shown or linked. Military aircraft and a
converted TR-3B are never looked up.

Flight lookups (type, route, photo) whose source failed temporarily are
asked again after a 60-second cool-down; an answer, found or not, and a
permanent refusal stay final for the session. A route belongs to the
callsign it was looked up for, so a callsign change drops the old route.

Browser QA scripts that track a flight answer the photo lookup themselves,
so they never call the third party.
2026-11-09 20:36:03 -08:00

79 lines
2.5 KiB
JavaScript

/**
* Keep browser QA off third-party aircraft photo services.
*
* Tracking a civil flight asks Planespotters for the aircraft's photo. QA
* runs track synthetic hexes, so a real request only adds a third-party
* dependency and, when the service refuses a headless browser, a console
* error that fails the "no console errors" checks. Every photo lookup is
* answered with no photos instead, without leaving the browser.
*/
/** Origin of the aircraft photo lookup. */
export const PLANESPOTTERS_ORIGIN = 'https://api.planespotters.net';
/** The answer every stubbed lookup gets: no photos for this aircraft. */
export const PLANESPOTTERS_EMPTY_BODY = JSON.stringify({ photos: [] });
/** Whether a URL is a photo lookup the stub answers. */
export function isPlanespottersRequest(url) {
try {
return new URL(String(url)).origin === PLANESPOTTERS_ORIGIN;
} catch {
return false;
}
}
/**
* Answer an intercepted photo lookup from a page that already uses request
* interception. Returns false for any other request, which the caller
* handles as before.
* @param {import('puppeteer').HTTPRequest} request
* @returns {boolean} Whether the request was answered.
*/
export function respondToPlanespotters(request) {
if (!isPlanespottersRequest(request.url())) return false;
request.respond({
status: 200,
contentType: 'application/json',
headers: { 'Access-Control-Allow-Origin': '*' },
body: PLANESPOTTERS_EMPTY_BODY,
});
return true;
}
/**
* Stub photo lookups in a page that does not intercept requests: wraps
* `fetch` before any app code runs, so a lookup never reaches the network.
* Composes with other `fetch` shims in either order.
* @param {import('puppeteer').Page} page
* @returns {Promise<void>}
*/
export async function stubPlanespotters(page) {
await page.evaluateOnNewDocument(
(origin, body) => {
const original = window.fetch.bind(window);
window.fetch = (input, init) => {
const href =
typeof input === 'string' ? input : input?.url || String(input);
let target = '';
try {
target = new URL(href, location.href).origin;
} catch {
/* not a URL */
}
if (target === origin) {
return Promise.resolve(
new Response(body, {
status: 200,
headers: { 'Content-Type': 'application/json' },
}),
);
}
return original(input, init);
};
},
PLANESPOTTERS_ORIGIN,
PLANESPOTTERS_EMPTY_BODY,
);
}