// Standalone dev-only mock of the Bigscreen API. Runs as a plain Node HTTP // server (no Next dependency) so the site itself has ZERO server routes and // exports cleanly to a static bundle. `npm run local` starts this alongside // next dev, and next.config proxies /api/mock/* here (see the rewrite). Never // ships to production — prod points config.json at the real API. // // Demo credentials: demo@bigscreenvr.com / beyond2 // Behavior mirrors the old React app: tokens ride response HEADERS // (x-access-token / x-refresh-token), renewal via 401 {code:4} + nonce, orders // carry shopifyOrderName / ipd / lineItems[{type,...}]. import http from "node:http"; const PORT = Number(process.env.MOCK_PORT || 4100); const DEMO = { email: "demo@bigscreenvr.com", password: "beyond2", username: "demo_pilot" }; const ACCESS = "mock-access-token"; const REFRESH = "mock-refresh-token"; const NONCE = "mock-nonce"; const state = { isVerified: true, ipdByOrder: {}, loggedOut: false }; const account = () => ({ id: "acc_demo_1", username: DEMO.username, email: DEMO.email, isVerified: state.isVerified }); const orders = () => [ { id: "ord_beyond2_001", shopifyOrderName: "#48213", ipd: state.ipdByOrder["ord_beyond2_001"] ?? 63, createdAt: 1739000000000, scanRequestId: "scan_001", lineItems: [ { type: "BigscreenBeyondV1", ipd: state.ipdByOrder["ord_beyond2_001"] ?? 63 }, { type: "BeyondCushionV1" }, { type: "AudioStrapV1" }, ], }, { id: "ord_beyond2_002", shopifyOrderName: "#51877", ipd: state.ipdByOrder["ord_beyond2_002"] ?? 60, createdAt: 1747000000000, scanRequestId: "scan_002", lineItems: [ { type: "BigscreenBeyondV1", ipd: state.ipdByOrder["ord_beyond2_002"] ?? 60 }, { type: "PrescriptionLenses", ODRight: "-1.25", OSLeft: "-1.00" }, ], }, ]; const entitlements = () => [ { id: "ent_001", status: "ACTIVE", media: { id: "med_001", title: "Demo Feature Film", posterUrl: "" }, receipt: { stripePaymentResult: { currency: "usd", amount: 499 } }, expiresAt: Date.now() + 1000 * 60 * 60 * 40 }, ]; const product = () => ({ bigMediaId: "med_001", sku: { id: "sku_001", price: 499, currency: "usd" }, marketingData: { "en-US": { title: "Demo Feature Film", description: "A mock movie product for testing the ticket purchase flow end to end. Buy it here, watch it inside Bigscreen on your headset.", webImages: { backgroundImageUrl: "", posterImageUrl: "" }, } }, }); const authed = (req) => !state.loggedOut && req.headers["x-access-token"] === ACCESS; // Returns { status, body, headers }. function handle(req, path, body) { const p = "/" + path.join("/"); const method = req.method; const T = { "x-access-token": ACCESS, "x-refresh-token": REFRESH }; const ok = (b, status = 200, headers = {}) => ({ status, body: b, headers }); if (p === "/auth/login" && method === "POST") { if (body?.email === DEMO.email && body?.password === DEMO.password) { state.loggedOut = false; return ok(account(), 200, T); } return ok({ code: 7, message: "Invalid email or password." }, 401); } if (p === "/auth/account" && method === "POST") { state.isVerified = false; state.loggedOut = false; return ok({ ...account(), username: body?.username, email: body?.email }, 200, T); } if (p === "/auth/account" && method === "GET") { if (!authed(req)) return ok({ message: "Unauthorized" }, 401); return ok(account()); } if (p === "/auth/verify") { if (authed(req)) return ok({ ok: true }); return ok({ code: 4, message: "Access token needs renewal." }, 401, { "x-bigscreen-nonce": NONCE }); } if (p === "/auth/renew") { if (req.headers["x-bigscreen-nonce"] === NONCE && req.headers["x-refresh-token"] === REFRESH && !state.loggedOut) return ok({ ok: true }, 200, T); return ok({ message: "Renewal failed" }, 401); } if (p === "/auth/logout") { state.loggedOut = true; return ok({ ok: true }); } if (p === "/auth/forgot" && method === "POST") return ok({ ok: true }); if (p === "/auth/reset" && method === "POST") { if (body?.token === "expired") return ok({ message: "Reset link expired." }, 422); return ok({ ok: true }); } if (p === "/auth/resend") { setTimeout(() => { state.isVerified = true; }, 8000); return ok({ ok: true }); } if (path[0] === "auth" && path[1] === "reset" && method === "GET") { if (path[2] === "expired") return ok({ message: "Expired" }, 422); return ok({ ok: true }); } if (path[0] === "auth" && path[1] === "token2" && path[2] === "login") { state.loggedOut = false; return ok({ redirectUrl: "/account/orders" }, 200, T); } if (path[0] === "auth" && path[1] === "email" && (path[2] === "verify" || path[2] === "update")) { if (path[3] === "expired") return ok({ message: "Expired" }, 422); state.isVerified = true; return ok({ ok: true }); } if (p === "/auth/whoami") return ok({ ip: "127.0.0.1" }); if (p === "/beyond/orders") { if (!authed(req)) return ok({ message: "Unauthorized" }, 401); return ok(orders()); } if (p === "/beyond/scans") { if (!authed(req)) return ok({ message: "Unauthorized" }, 401); return ok([{ id: "scan_001", appClipUrl: "https://appclip.apple.com/id?p=com.bigscreen.scan&token=mock" }]); } if (path[0] === "beyond" && path[1] === "scan" && path[3] === "process") { if (!authed(req)) return ok({ message: "Unauthorized" }, 401); if (path[2] === "fail") return ok({ code: 3, message: "Scan quality check failed.", scan: { appClipUrl: "https://appclip.apple.com/id?p=com.bigscreen.scan&token=retry" } }, 422); if (path[2] === "fatal") return ok({ code: 99, message: "Internal error." }, 500); return ok({ ok: true }); } if (path[0] === "beyond" && path[1] === "order" && path[2]) { const order = orders().find((o) => o.id === path[2]); if (!order) return ok({ message: "Order not found" }, 404); if (path[3] === "ipd" && method === "PUT") { state.ipdByOrder[order.id] = Number(body?.ipd); return ok({ ...order, ipd: Number(body?.ipd) }); } return ok(order); } if (p === "/beyond/multipass") return ok({ url: "https://store.bigscreenvr.com/account" }); if (p === "/media/entitlements") { if (!authed(req)) return ok({ message: "Unauthorized" }, 401); return ok(entitlements()); } if (path[0] === "media" && path[1] === "product" && path[2]) return ok(product()); if (p === "/media/order" && method === "POST") return ok({ ok: true, receipt: { stripePaymentResult: { currency: "usd", amount: 499 } } }); if (p === "/rdc/config") return ok({ url: "https://rdc.bigscreencloud.com/Bigscreen-RDC-0.927.49-4b1194/BigscreenRemoteDesktopSetup.exe" }); if (path[0] === "apps" && path[1] === "browser" && path[2]) return ok({ embedUrl: "about:blank", options: { adminToken: null }, mockNote: "Hyperbeam embed URLs require the real cloud API." }); return ok({ message: `Mock: no handler for ${method} ${p}` }, 404); } const server = http.createServer((req, res) => { let raw = ""; req.on("data", (c) => { raw += c; }); req.on("end", () => { // Strip the /api/mock prefix that next.config proxies through. let pathname = new URL(req.url, "http://x").pathname.replace(/^\/api\/mock/, ""); const segs = pathname.split("/").filter(Boolean); let body = null; if (raw) { try { body = JSON.parse(raw); } catch { body = null; } } const { status, body: out, headers } = handle(req, segs, body); res.writeHead(status, { "content-type": "application/json", "cache-control": "no-store", ...headers }); res.end(JSON.stringify(out)); }); }); server.listen(PORT, "127.0.0.1", () => { console.log(`[mock] Bigscreen mock API on http://127.0.0.1:${PORT} (demo@bigscreenvr.com / beyond2)`); });