Skip to content

Instantly share code, notes, and snippets.

@robksawyer
Last active June 2, 2024 06:07
Show Gist options
  • Select an option

  • Save robksawyer/c082fc6e73cc20431431f57e49f28741 to your computer and use it in GitHub Desktop.

Select an option

Save robksawyer/c082fc6e73cc20431431f57e49f28741 to your computer and use it in GitHub Desktop.

Revisions

  1. robksawyer revised this gist Jun 2, 2024. 2 changed files with 175 additions and 19 deletions.
    75 changes: 56 additions & 19 deletions convertRequest.ts
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,7 @@
    import { IncomingMessage } from "http";
    import { NextRequest } from "next/server";
    import { Socket } from "net";
    import { cookies } from "next/headers";

    export async function convertNextRequestToIncomingMessage(
    request: NextRequest
    @@ -12,38 +13,74 @@ export async function convertNextRequestToIncomingMessage(
    const incomingMessage = new IncomingMessage(new Socket());

    try {
    // Convert headers
    incomingMessage.headers = Object.fromEntries(request.headers.entries());
    incomingMessage.method = request.method;
    incomingMessage.url = request.url;

    const bodyChunks: Uint8Array[] = [];
    console.log("Converted headers:", incomingMessage.headers);

    // Ensure all cookies are set in headers
    const cookieStore = cookies();
    const allCookies = cookieStore.getAll();
    const cookieHeader = allCookies
    .map((cookie) => `${cookie.name}=${cookie.value}`)
    .join("; ");

    if (cookieHeader) {
    incomingMessage.headers["cookie"] = cookieHeader;
    } else {
    console.error("No cookies found");
    }

    let body: Buffer | null = null;

    // Convert body if it exists
    if (request.body) {
    const reader = request.body.getReader();
    let result = await reader.read();
    while (!result.done) {
    bodyChunks.push(result.value);
    result = await reader.read();
    const stream = new ReadableStream({
    start(controller) {
    function push() {
    reader.read().then(({ done, value }) => {
    if (done) {
    controller.close();
    return;
    }
    controller.enqueue(value);
    push();
    });
    }
    push();
    },
    });

    const response = new Response(stream);
    const bodyBuffer = await response.arrayBuffer();
    body = Buffer.from(bodyBuffer);

    if (body.length > 0) {
    incomingMessage.push(body);
    }
    }
    incomingMessage.push(null);

    const bodyBuffer = Buffer.concat(bodyChunks);
    if (bodyBuffer.length > 0) {
    incomingMessage.push(bodyBuffer);
    // Log the converted body
    console.log("Converted body buffer:", body);
    }
    incomingMessage.push(null);

    // Mimic NextApiRequest properties
    (incomingMessage as any).query = Object.fromEntries(
    new URL(request.url).searchParams.entries()
    );
    const url = new URL(request.url);
    const query = Object.fromEntries(url.searchParams.entries());
    console.log("Converted query parameters:", query);

    const parsedBody =
    body && body.length > 0 ? JSON.parse(body.toString()) : null;
    console.log("Parsed body:", parsedBody);

    (incomingMessage as any).query = query;
    (incomingMessage as any).cookies = Object.fromEntries(
    request.headers
    .get("cookie")
    ?.split("; ")
    .map((cookie) => cookie.split("=")) || []
    allCookies.map((cookie) => [cookie.name, cookie.value])
    );
    (incomingMessage as any).body =
    bodyBuffer.length > 0 ? JSON.parse(bodyBuffer.toString()) : null;
    (incomingMessage as any).body = parsedBody;
    } catch (error) {
    console.error("Error converting NextRequest to IncomingMessage:", error);
    throw error;
    119 changes: 119 additions & 0 deletions supabaseSessionStorage.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,119 @@
    // lib/supabaseSessionStorage.ts
    import { SessionInterface } from "@shopify/shopify-api/dist/auth/session/types";
    import { CustomSessionStorage } from "@shopify/shopify-api/dist/auth/session";
    import { supabase } from "@/utils/supabaseClient";

    const DATABASE = "shopify_sessions";

    // Store session callback
    const storeSession = async (session: SessionInterface): Promise<boolean> => {
    try {
    console.log("Storing session:", session);

    // Ensure isOnline is a boolean
    const isOnline =
    typeof session.isOnline === "boolean"
    ? session.isOnline
    : session.isOnline === "true";

    // Ensure isActive is a boolean and defaults to true if not a function
    const isActive =
    typeof session.isActive === "function" ? session.isActive() : true;

    const sessionData = {
    id: session.id, // Unique identifier provided by Shopify
    shop_domain: session.shop,
    state: session.state,
    is_online: isOnline,
    scope: session.scope || "", // Providing default empty string if scope is undefined
    expires: session.expires ? session.expires.toISOString() : null, // Providing null if expires is undefined and converting to ISO string
    online_access_info: session.onlineAccessInfo || {}, // Providing empty object if undefined
    access_token: session.accessToken,
    is_active: isActive,
    };

    // Log the session data to debug issues
    console.log("Session data to be saved:", sessionData);

    // Save the session to Supabase
    const { data, error } = await supabase.from(DATABASE).upsert([sessionData]); // Using upsert instead of insert

    if (error) {
    console.error("Error storing session:", error.message);
    return false;
    }

    console.log("Session stored successfully:", session.id);
    return true;
    } catch (error) {
    console.error("Error storing session:", error);
    return false;
    }
    };

    // Load session callback
    const loadSession = async (
    id: string
    ): Promise<SessionInterface | undefined> => {
    try {
    const { data, error } = await supabase
    .from(DATABASE)
    .select(
    "shop_domain, access_token, is_online, state, scope, expires, online_access_info, is_active"
    )
    .eq("id", id)
    .single();

    if (error) {
    console.error("Error loading session:", error.message);
    return undefined;
    }

    if (data) {
    const session: SessionInterface = {
    id: id,
    shop: data.shop_domain,
    state: data.state,
    isOnline: data.is_online,
    scope: data.scope,
    expires: data.expires ? new Date(data.expires) : undefined,
    onlineAccessInfo: data.online_access_info,
    accessToken: data.access_token,
    isActive: () => data.is_active,
    };
    console.log("Session loaded successfully:", session.id);
    return session;
    }

    return undefined;
    } catch (error) {
    console.error("Error loading session:", error);
    return undefined;
    }
    };

    // Delete session callback
    const deleteSession = async (id: string): Promise<boolean> => {
    try {
    const { error } = await supabase.from(DATABASE).delete().eq("id", id);

    if (error) {
    console.error("Error deleting session:", error.message);
    return false;
    }

    console.log("Session deleted successfully:", id);
    return true;
    } catch (error) {
    console.error("Error deleting session:", error);
    return false;
    }
    };

    class SupabaseSessionStorage extends CustomSessionStorage {
    constructor() {
    super(storeSession, loadSession, deleteSession);
    }
    }

    export const sessionStorage = new SupabaseSessionStorage();
  2. robksawyer revised this gist May 31, 2024. No changes.
  3. robksawyer revised this gist May 31, 2024. No changes.
  4. robksawyer created this gist May 31, 2024.
    53 changes: 53 additions & 0 deletions convertRequest.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,53 @@
    import { IncomingMessage } from "http";
    import { NextRequest } from "next/server";
    import { Socket } from "net";

    export async function convertNextRequestToIncomingMessage(
    request: NextRequest
    ): Promise<IncomingMessage> {
    if (!request || typeof request !== "object") {
    throw new Error("Invalid request object");
    }

    const incomingMessage = new IncomingMessage(new Socket());

    try {
    incomingMessage.headers = Object.fromEntries(request.headers.entries());
    incomingMessage.method = request.method;
    incomingMessage.url = request.url;

    const bodyChunks: Uint8Array[] = [];
    if (request.body) {
    const reader = request.body.getReader();
    let result = await reader.read();
    while (!result.done) {
    bodyChunks.push(result.value);
    result = await reader.read();
    }
    }

    const bodyBuffer = Buffer.concat(bodyChunks);
    if (bodyBuffer.length > 0) {
    incomingMessage.push(bodyBuffer);
    }
    incomingMessage.push(null);

    // Mimic NextApiRequest properties
    (incomingMessage as any).query = Object.fromEntries(
    new URL(request.url).searchParams.entries()
    );
    (incomingMessage as any).cookies = Object.fromEntries(
    request.headers
    .get("cookie")
    ?.split("; ")
    .map((cookie) => cookie.split("=")) || []
    );
    (incomingMessage as any).body =
    bodyBuffer.length > 0 ? JSON.parse(bodyBuffer.toString()) : null;
    } catch (error) {
    console.error("Error converting NextRequest to IncomingMessage:", error);
    throw error;
    }

    return incomingMessage;
    }
    53 changes: 53 additions & 0 deletions convertResponse.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,53 @@
    import { ServerResponse } from "http";
    import { NextResponse } from "next/server";

    export function convertNextResponseToServerResponse(
    nextResponse: NextResponse
    ): ServerResponse {
    if (!nextResponse || typeof nextResponse !== "object") {
    throw new Error("Invalid nextResponse object");
    }

    const serverResponse = new ServerResponse({} as any);

    try {
    serverResponse.statusCode = nextResponse.status;
    serverResponse.statusMessage = nextResponse.statusText;
    for (const [key, value] of nextResponse.headers.entries()) {
    serverResponse.setHeader(key, value);
    }

    // Implement write method
    serverResponse.write = (
    chunk: any,
    encodingOrCallback?: any,
    callback?: any
    ): boolean => {
    if (typeof encodingOrCallback === "function") {
    encodingOrCallback(null);
    } else if (typeof callback === "function") {
    callback(null);
    }
    return true; // Ensure boolean is returned
    };

    // Implement end method
    serverResponse.end = (
    chunk: any,
    encodingOrCallback?: any,
    callback?: any
    ): ServerResponse => {
    if (typeof encodingOrCallback === "function") {
    encodingOrCallback(null);
    } else if (typeof callback === "function") {
    callback(null);
    }
    return serverResponse; // Ensure ServerResponse is returned
    };
    } catch (error) {
    console.error("Error converting NextResponse to ServerResponse:", error);
    throw error;
    }

    return serverResponse;
    }