Replies: 2 comments 4 replies
-
Were you able to find a work around to this? I need to access res.write and res.end for streaming. But NextResponse in App Router doesn't expose that. |
Beta Was this translation helpful? Give feedback.
4 replies
-
i'm using util : /**
* Enhanced adapter for Shopify API to work with Next.js App Router
* This correctly captures the OAuth redirect URL
*/
export function createShopifyRequestContext(req: NextRequest) {
// Track the redirect URL that Shopify tries to set
let capturedRedirectUrl: string | null = null;
// Create a partial implementation of IncomingMessage
const nodeReq = {
method: req.method,
url: req.url,
headers: Object.fromEntries(req.headers.entries()),
socket: {
remoteAddress: req.ip || "127.0.0.1",
},
} as any;
// Create a mock ServerResponse with redirect capturing
const nodeRes = {
headersSent: false,
statusCode: 200,
headers: {} as Record<string, string | string[]>,
// Method used by Shopify to get headers
getHeader: (name: string) => {
return nodeRes.headers[name.toLowerCase()];
},
// Method used by Shopify to set headers
setHeader: (name: string, value: string | string[]) => {
nodeRes.headers[name.toLowerCase()] = value;
return nodeRes;
},
// Method used by Shopify to get all headers
getHeaders: () => {
return nodeRes.headers;
},
// Critical method used by Shopify for redirects
writeHead: (
statusCode: number,
statusMessage?: string | Record<string, string | string[]>,
headers?: Record<string, string | string[]>,
) => {
nodeRes.statusCode = statusCode;
// Handle different overloads of writeHead
let headersObj: Record<string, string | string[]> | undefined;
if (typeof statusMessage === "object") {
headersObj = statusMessage;
} else if (headers) {
headersObj = headers;
}
if (headersObj) {
Object.entries(headersObj).forEach(([key, value]) => {
nodeRes.headers[key.toLowerCase()] = value;
});
}
// Capture the Location header for redirects
if (statusCode >= 300 && statusCode < 400) {
capturedRedirectUrl = Array.isArray(nodeRes.headers.location)
? nodeRes.headers.location[0]
: (nodeRes.headers.location as string);
console.log(
"Captured redirect URL from writeHead:",
capturedRedirectUrl,
);
}
return nodeRes;
},
// Method used by Shopify to end the response
end: (chunk?: any) => {
// Check if we have a redirect in headers but didn't catch it in writeHead
if (!capturedRedirectUrl && nodeRes.headers.location) {
capturedRedirectUrl = Array.isArray(nodeRes.headers.location)
? nodeRes.headers.location[0]
: (nodeRes.headers.location as string);
console.log(
"Captured redirect URL from end:",
capturedRedirectUrl,
);
}
return nodeRes;
},
// Method to get the captured redirect URL
getCapturedRedirectUrl: () => {
// Try to extract the redirect URL from logs if we didn't capture it
if (!capturedRedirectUrl) {
// Extract from the Shopify debug log that we can see in console
const debug =
process.env.NODE_ENV === "development"
? console.debug
: null;
if (debug) {
console.log("Trying to extract URL from OAuth debug logs");
// We know the URL is logged by Shopify's API
const logMessages =
(console as any).__proto__.log.mock?.calls || [];
for (const args of logMessages) {
const message = args.join(" ");
if (message.includes("redirecting to")) {
const match = message.match(
/redirecting to (https:\/\/[^\s|]+)/i,
);
if (match && match[1]) {
capturedRedirectUrl = match[1];
console.log(
"Extracted URL from logs:",
capturedRedirectUrl,
);
break;
}
}
}
}
}
// If all else fails, construct the URL manually
if (!capturedRedirectUrl) {
console.log("Manual fallback for redirect URL");
const host = req.headers.get("host") || "localhost:3000";
const shop = req.nextUrl.searchParams.get("domain");
if (shop) {
// Construct Shopify OAuth URL manually as a fallback
const sanitizedShop = shop.includes(".myshopify.com")
? shop
: `${shop}.myshopify.com`;
const state = Math.floor(
Math.random() * 1000000000000000,
).toString();
capturedRedirectUrl = `https://${sanitizedShop}/admin/oauth/authorize?client_id=${
process.env.SHOPIFY_CLIENT_ID
}&scope=${
Array.isArray(shopify.config.scopes)
? shopify.config.scopes.join(",")
: shopify.config.scopes.toString()
}&redirect_uri=${encodeURIComponent(
`https://${host}/api/integrations/shopify/callback`,
)}&state=${state}&grant_options%5B%5D=`;
console.log(
"Constructed fallback URL:",
capturedRedirectUrl,
);
}
}
return capturedRedirectUrl;
},
} as any;
return {
rawRequest: nodeReq,
rawResponse: nodeRes,
};
} example : import {
createShopifyRequestContext,
shopify,
SHOPIFY_REDIRECT_PATH,
} from "@/dashboard/integrations/_lib/shopify";
import { NextResponse,NextRequest } from "next/server";
import { NextApiResponse,NextApiRequest } from "next";
export const dynamic = "force-dynamic";
export async function GET(req: NextRequest, res: NextResponse) {
try {
const params = req.nextUrl.searchParams;
const domain = params.get("domain");
if (!domain) {
return NextResponse.json(
{ error: "Missing shop domain parameter" },
{ status: 400 },
);
}
// Validate the shop domain using Shopify's utils
const validShop = shopify.utils.sanitizeShop(domain, true);
if (!validShop) {
return NextResponse.json(
{ error: "Invalid Shopify domain" },
{ status: 400 },
);
}
// Use our adapter to create Node.js style request objects
const { rawRequest, rawResponse } = createShopifyRequestContext(req);
// Begin the OAuth process
await shopify.auth.begin({
shop: validShop,
callbackPath: SHOPIFY_REDIRECT_PATH,
isOnline: false,
rawRequest,
rawResponse,
});
// Get the captured redirect URL from our adapter
const redirectUrl = rawResponse.getCapturedRedirectUrl();
if (!redirectUrl) {
console.error(
"No redirect URL was captured from Shopify OAuth flow",
);
return NextResponse.json(
{ error: "Failed to generate authorization URL" },
{ status: 500 },
);
}
console.log("Redirecting to Shopify auth URL:", redirectUrl);
// Use the standard Response.redirect() method with the captured URL
return Response.redirect(redirectUrl);
} catch (error) {
console.error("Error starting Shopify OAuth:", error);
return NextResponse.json(
{ error: "Failed to start OAuth" },
{ status: 500 },
);
}
} |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Goals
Non-Goals
No response
Background
I am converting a nextjs13 Pages directory project to nextjs13 App Router. It uses an external library for oauth which requires that I send it a request and response object that conforms to the 'http' nodejs library interface. This worked fine with getServerSideProps() because the res/req in the context object was compatible with this. I understand and agree with your decision to make NextResponse extend the Response object, but it does sort of leave people with external dependencies on the older format a little out-of-luck here, unless I have missed something somewhere. I looked through the documentation to see if there was any way to get at the older format of req/res but haven't found any solutions here yet.
Proposal
I looked through the code and it does seem http.createServer() is still being used (though maybe only in a limited capacity?), so if that's true then maybe you can expose the original req/res somewhere for people that need it. Thanks!
Beta Was this translation helpful? Give feedback.
All reactions