Something went wrong. Try again.
Monorepo for Aesthetic.Computer aesthetic.computer
Something went wrong. Try again.
JavaScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191// FF1 Proxy (Netlify Function)// Relays FF1 displayPlaylist requests to Feral File cloud relay.// // NOTE: The Feral File relay (artwork-info.feral-file.workers.dev) may be // temporarily unavailable. This function provides two modes:// 1. Cloud relay mode (requires topicID + apiKey from FF1 app)// 2. Direct mode (requires deviceIp - FF1 must be accessible from server)
const RELAY_BASE = "https://artwork-info.feral-file.workers.dev/api/cast";
function corsHeaders() { return { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type", };}
export async function handler(event) { if (event.httpMethod === "OPTIONS") { return { statusCode: 204, headers: corsHeaders(), body: "", }; }
if (event.httpMethod !== "POST") { return { statusCode: 405, headers: corsHeaders(), body: JSON.stringify({ success: false, error: "Method not allowed" }), }; }
let body; try { body = JSON.parse(event.body || "{}"); } catch (err) { return { statusCode: 400, headers: corsHeaders(), body: JSON.stringify({ success: false, error: "Invalid JSON body" }), }; }
const { topicID, apiKey, command, request, deviceIp } = body;
// Mode 1: Direct device connection (if deviceIp provided) if (deviceIp) { const directUrl = `http://${deviceIp}:1111/api/cast`; const payload = { command: command || "displayPlaylist", request, };
try { const response = await fetch(directUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), });
const responseText = await response.text(); let responseData; try { responseData = JSON.parse(responseText); } catch { responseData = { raw: responseText }; }
if (!response.ok) { return { statusCode: response.status, headers: corsHeaders(), body: JSON.stringify({ success: false, error: `FF1 direct error: ${response.status}`, details: responseData, }), }; }
return { statusCode: 200, headers: corsHeaders(), body: JSON.stringify({ success: true, response: responseData, mode: "direct" }), }; } catch (err) { return { statusCode: 500, headers: corsHeaders(), body: JSON.stringify({ success: false, error: `Cannot reach FF1 at ${deviceIp}: ${err.message}`, hint: "FF1 must be accessible from the server (same network or tunneled)" }), }; } }
// Mode 2: Cloud relay (requires topicID) if (!topicID) { return { statusCode: 400, headers: corsHeaders(), body: JSON.stringify({ success: false, error: "Missing topicID - get this from your FF1 app settings", hint: "Go to FF1 app > Settings > Developer to find your Topic ID", }), }; }
if (!apiKey) { return { statusCode: 400, headers: corsHeaders(), body: JSON.stringify({ success: false, error: "Missing apiKey - the cloud relay requires an API key", hint: "Get your API key from the FF1 app or contact Feral File support", }), }; }
const relayUrl = `${RELAY_BASE}?topicID=${encodeURIComponent(topicID)}`;
const payload = { command: command || "displayPlaylist", request, };
try { const response = await fetch(relayUrl, { method: "POST", headers: { "Content-Type": "application/json", "API-KEY": apiKey, }, body: JSON.stringify(payload), });
const responseText = await response.text(); let responseData; try { responseData = JSON.parse(responseText); } catch { responseData = { raw: responseText }; }
if (!response.ok) { // Check for Cloudflare worker errors (relay may be down) if (response.status === 404 || responseText.includes("error code: 1042")) { return { statusCode: 503, headers: corsHeaders(), body: JSON.stringify({ success: false, error: "FF1 cloud relay appears to be unavailable", hint: "The Feral File relay service may be down. Try using direct mode with deviceIp, or use the local tunnel (ac-ff1 tunnel).", details: responseData, }), }; }
return { statusCode: response.status, headers: corsHeaders(), body: JSON.stringify({ success: false, error: `FF1 relay error: ${response.status}`, details: responseData, }), }; }
return { statusCode: 200, headers: corsHeaders(), body: JSON.stringify({ success: true, response: responseData, mode: "relay" }), }; } catch (err) { return { statusCode: 500, headers: corsHeaders(), body: JSON.stringify({ success: false, error: err.message }), }; }}