import { json, type ActionFunctionArgs, type LoaderFunctionArgs } from "@remix-run/node"; import { v4 as uuidv4 } from "uuid"; import { authenticate, unauthenticated } from "../shopify.server"; import { runActivityCheck } from "../services/activity.server"; import { getActivityConfig } from "../services/config.server"; import { callPush, callStatus } from "../services/dreame-client.server"; import { findSuccessfulPush, recordSuccessfulPush } from "../services/idempotency.server"; import { logActivityPush } from "../services/logger.server"; import { getOrderDetails } from "../services/order.server"; const CACHE_CONTROL = "no-store, max-age=0"; const CHECKOUT_CORS_HEADERS = ["Authorization", "Content-Type"]; const FALLBACK_CORS_HEADERS = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "Authorization, Content-Type", "Access-Control-Allow-Methods": "POST, OPTIONS", "Cache-Control": CACHE_CONTROL, }; type CheckoutAuthResult = Awaited> & { admin?: unknown; session?: { shop?: string }; shop?: string; }; type CheckoutCors = CheckoutAuthResult["cors"]; type ActivityRequestBody = { orderId?: string; orderNumber?: string; locale?: string; }; export async function loader({ request }: LoaderFunctionArgs) { if (request.method === "OPTIONS") { return handleOptions(request); } return fallbackCors( errorResponse("METHOD_NOT_ALLOWED", "Only POST is supported.", 405, { Allow: "POST", }), ); } export async function action({ request }: ActionFunctionArgs) { if (request.method === "OPTIONS") { return handleOptions(request); } if (request.method !== "POST") { return fallbackCors( errorResponse("METHOD_NOT_ALLOWED", "Only POST is supported.", 405, { Allow: "POST", }), ); } let authResult: CheckoutAuthResult; try { authResult = (await authenticate.public.checkout(request, { corsHeaders: CHECKOUT_CORS_HEADERS, })) as CheckoutAuthResult; } catch (error) { return authErrorResponse(error); } const { cors, sessionToken } = authResult; const shop = normalizeShopDomain(authResult.shop || authResult.session?.shop || sessionToken.dest); if (!shop) { return checkoutCors( cors, errorResponse("INVALID_SHOP_CONTEXT", "A valid Shopify shop context is required.", 401), ); } const body = await parseJsonBody(request); if (!body) { return checkoutCors(cors, errorResponse("INVALID_JSON", "Request body must be valid JSON.", 400)); } if (typeof body.orderId !== "string" || body.orderId.trim() === "") { return checkoutCors(cors, errorResponse("ORDER_ID_REQUIRED", "orderId is required.", 400)); } try { const adminResult = authResult.admin ? { admin: authResult.admin } : shop ? await unauthenticated.admin(shop) : null; const admin = adminResult?.admin as { graphql: (query: string, options?: { variables?: Record }) => Promise; }; if (!shop || !admin) { throw new Error("Invalid Shopify checkout session"); } const result = await runActivityCheck( { shop, orderId: body.orderId.trim(), orderNumber: body.orderNumber, locale: body.locale, }, { nowMs: () => Date.now(), uuid: uuidv4, getOrder: (_shop, orderId) => getOrderDetails(admin, orderId), getConfig: () => getActivityConfig(admin), findSuccess: findSuccessfulPush, recordSuccess: recordSuccessfulPush, log: logActivityPush, callStatus, callPush, }, ); return checkoutCors(cors, json(result, { headers: noStoreHeaders() })); } catch { return checkoutCors(cors, errorResponse("ACTIVITY_CHECK_FAILED", "Activity check failed.", 500)); } } async function handleOptions(request: Request): Promise { try { const { cors } = await authenticate.public.checkout(request, { corsHeaders: CHECKOUT_CORS_HEADERS, }); return checkoutCors(cors, new Response(null, { status: 204, headers: noStoreHeaders() })); } catch (error) { return authErrorResponse(error); } } async function parseJsonBody(request: Request): Promise { try { return (await request.json()) as ActivityRequestBody; } catch { return null; } } function normalizeShopDomain(value: unknown): string | null { if (typeof value !== "string" || value.trim() === "") { return null; } try { const url = value.includes("://") ? value : `https://${value}`; const hostname = new URL(url).hostname.toLowerCase(); return hostname.endsWith(".myshopify.com") ? hostname : null; } catch { return null; } } function errorResponse(code: string, message: string, status: number, headers: HeadersInit = {}): Response { return json( { success: false, error: { code, message, }, }, { status, headers: { ...headers, ...noStoreHeaders(), }, }, ); } function authErrorResponse(error: unknown): Response { if (error instanceof Response) { return fallbackCors(withNoStore(error)); } return fallbackCors(errorResponse("CHECKOUT_AUTHENTICATION_FAILED", "Checkout authentication failed.", 401)); } function checkoutCors(cors: CheckoutCors, response: Response): Response { return cors(withNoStore(response)); } function fallbackCors(response: Response): Response { for (const [key, value] of Object.entries(FALLBACK_CORS_HEADERS)) { response.headers.set(key, value); } return response; } function withNoStore(response: Response): Response { response.headers.set("Cache-Control", CACHE_CONTROL); return response; } function noStoreHeaders(): HeadersInit { return { "Cache-Control": CACHE_CONTROL, }; }