105 lines
3.5 KiB
TypeScript
105 lines
3.5 KiB
TypeScript
|
|
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 CORS_HEADERS = {
|
||
|
|
"Access-Control-Allow-Origin": "*",
|
||
|
|
"Access-Control-Allow-Headers": "Authorization, Content-Type",
|
||
|
|
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
||
|
|
};
|
||
|
|
|
||
|
|
type ActivityRequestBody = {
|
||
|
|
orderId?: string;
|
||
|
|
orderNumber?: string;
|
||
|
|
locale?: string;
|
||
|
|
};
|
||
|
|
|
||
|
|
export async function loader({ request }: LoaderFunctionArgs) {
|
||
|
|
if (request.method === "OPTIONS") {
|
||
|
|
return new Response(null, { status: 204, headers: CORS_HEADERS });
|
||
|
|
}
|
||
|
|
|
||
|
|
return json({ success: false }, { status: 405, headers: CORS_HEADERS });
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function action({ request }: ActionFunctionArgs) {
|
||
|
|
if (request.method === "OPTIONS") {
|
||
|
|
return new Response(null, { status: 204, headers: CORS_HEADERS });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (request.method !== "POST") {
|
||
|
|
return json({ success: false }, { status: 405, headers: CORS_HEADERS });
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
const checkoutAuth = (authenticate as unknown as { public?: { checkout?: (request: Request) => Promise<unknown> } })
|
||
|
|
.public?.checkout;
|
||
|
|
if (!checkoutAuth) {
|
||
|
|
throw new Error("Shopify checkout authentication is unavailable");
|
||
|
|
}
|
||
|
|
|
||
|
|
const authResult = (await checkoutAuth(request)) as {
|
||
|
|
shop?: string;
|
||
|
|
admin?: unknown;
|
||
|
|
sessionToken?: { dest?: string; iss?: string };
|
||
|
|
session?: { shop?: string };
|
||
|
|
};
|
||
|
|
const shop = authResult.shop || authResult.session?.shop || shopFromSessionToken(authResult.sessionToken);
|
||
|
|
const adminResult = authResult.admin ? { admin: authResult.admin } : shop ? await unauthenticated.admin(shop) : null;
|
||
|
|
const admin = adminResult?.admin as {
|
||
|
|
graphql: (query: string, options?: { variables?: Record<string, unknown> }) => Promise<Response>;
|
||
|
|
};
|
||
|
|
if (!shop || !admin) {
|
||
|
|
throw new Error("Invalid Shopify checkout session");
|
||
|
|
}
|
||
|
|
|
||
|
|
const body = (await request.json()) as ActivityRequestBody;
|
||
|
|
if (!body.orderId) {
|
||
|
|
return json({ success: false }, { headers: CORS_HEADERS });
|
||
|
|
}
|
||
|
|
|
||
|
|
const result = await runActivityCheck(
|
||
|
|
{
|
||
|
|
shop,
|
||
|
|
orderId: body.orderId,
|
||
|
|
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 json(result, { headers: CORS_HEADERS });
|
||
|
|
} catch {
|
||
|
|
return json({ success: false }, { headers: CORS_HEADERS });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function shopFromSessionToken(sessionToken: { dest?: string; iss?: string } | undefined): string | undefined {
|
||
|
|
const source = sessionToken?.dest || sessionToken?.iss;
|
||
|
|
if (!source) {
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
return new URL(source).hostname;
|
||
|
|
} catch {
|
||
|
|
return source.replace(/^https?:\/\//, "").split("/")[0];
|
||
|
|
}
|
||
|
|
}
|