A space to discuss GraphQL queries, mutations, troubleshooting, throttling, and best practices.
Hi,
I am trying to create my first embedded admin app and everything seems to be working fine with API version
However, when i switch to API version "2021-10", I get an INTERNAL SERVER ERROR on this request as my app tries to load:
https://cf93-47-155-234-31.ngrok.io/auth/callback?code=69a908234e220f32a5bddfe32c6b11f1&hmac=cdc672a2cb88c7213e191734fda314c6ffa919aa3a5a51255959ccc80e44514b&host=c29jaWFsLWNvbW1lcmNlLWRlbW8ubXlzaG9waWZ5LmNvbS9hZG1pbg&shop=g3-demo.myshopify.com&state=185784368861671×tamp=1641087145
The Node error on my terminal is this:
InternalServerError: Failed to make Shopify HTTP request: FetchError: invalid json response body at https://g3-demo.myshopify.com/admin/api/undefined/graphql.json reason: Unexpected end of JSON input
┃ at Object.throw (/Users/gmisa/Dev/g3-demo/node_modules/koa/lib/context.js:97:11)
┃ at /Users/gmisa/Dev/g3-demo/node_modules/@shopify/koa-shopify-auth/dist/src/auth/index.js:102:42
┃ at step (/Users/gmisa/Dev/g3-demo/node_modules/tslib/tslib.js:133:27)
┃ at Object.throw (/Users/gmisa/Dev/g3-demo/node_modules/tslib/tslib.js:114:57)
┃ at rejected (/Users/gmisa/Dev/g3-demo/node_modules/tslib/tslib.js:105:69)
┃ at runMicrotasks (<anonymous>)
┃ at processTicksAndRejections (internal/process/task_queues.js:97:5)
Seems like there is "undefined" in the URL:
https://g3-demo.myshopify.com/admin/api/undefined/graphql.json
This only happens on API version "2021-10" and "2022-01". Previous API versions work just fine. Hope someone can help. Thank you!
Here is my _app.js:
import { ApolloClient, HttpLink, ApolloProvider, InMemoryCache } from "@apollo/client";
import App from "next/app";
import { AppProvider } from "@shopify/polaris";
import { Provider, useAppBridge } from "@shopify/app-bridge-react";
import { authenticatedFetch } from "@shopify/app-bridge-utils";
import { Redirect } from "@shopify/app-bridge/actions";
import { store, persistor } from '../modules/store';
import { Provider as ReduxProvider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { compose } from 'redux';
import "@shopify/polaris/dist/styles.css";
import translations from "@shopify/polaris/locales/en.json";
function userLoggedInFetch(app) {
const fetchFunction = authenticatedFetch(app);
return async (uri, options) => {
const response = await fetchFunction(uri, options);
if (
response.headers.get("X-Shopify-API-Request-Failure-Reauthorize") === "1"
) {
const authUrlHeader = response.headers.get(
"X-Shopify-API-Request-Failure-Reauthorize-Url"
);
const redirect = Redirect.create(app);
redirect.dispatch(Redirect.Action.APP, authUrlHeader || `/auth`);
return null;
}
return response;
};
}
function MyProvider(props) {
const app = useAppBridge();
const fetchOptions = {
credentials: "include"
};
const link = new HttpLink({ fetch: userLoggedInFetch(app), fetchOptions });
const client = new ApolloClient({
cache: new InMemoryCache(),
link
});
const Component = props.Component;
return (
<ApolloProvider client={client}>
<Component {...props} />
</ApolloProvider>
);
}
class MyApp extends App {
render() {
const { Component, pageProps, host } = this.props;
return (
<AppProvider i18n={translations}>
<Provider
config={{
apiKey: API_KEY,
host: host,
forceRedirect: true,
}}
>
<ReduxProvider store={store}>
<PersistGate persistor={persistor}>
<MyProvider Component={Component} {...pageProps} />
</PersistGate>
</ReduxProvider>
</Provider>
</AppProvider>
);
}
}
MyApp.getInitialProps = async ({ ctx }) => {
return {
host: ctx.query.host,
};
};
export default MyApp;
Here's my server.js:
import "@babel/polyfill";
import dotenv from "dotenv";
import "isomorphic-fetch";
import createShopifyAuth, { verifyRequest } from "@shopify/koa-shopify-auth";
import Shopify, { ApiVersion } from "@shopify/shopify-api";
import Koa from "koa";
import next from "next";
import Router from "koa-router";
dotenv.config();
const port = parseInt(process.env.PORT, 10) || 8081;
const dev = process.env.NODE_ENV !== "production";
const app = next({
dev,
});
const handle = app.getRequestHandler();
Shopify.Context.initialize({
API_KEY: process.env.SHOPIFY_API_KEY,
API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
SCOPES: process.env.SCOPES.split(","),
HOST_NAME: process.env.HOST.replace(/https:\/\/|\/$/g, ""),
API_VERSION: ApiVersion.October21,
IS_EMBEDDED_APP: true,
// This should be replaced with your preferred storage strategy
SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});
// Storing the currently active shops in memory will force them to re-login when your server restarts. You should
// persist this object in your app.
const ACTIVE_SHOPIFY_SHOPS = {};
app.prepare().then(async () => {
const server = new Koa();
const router = new Router();
server.keys = [Shopify.Context.API_SECRET_KEY];
server.use(
createShopifyAuth({
async afterAuth(ctx) {
// Access token and shop available in ctx.state.shopify
const { shop, accessToken, scope } = ctx.state.shopify;
const host = ctx.query.host;
ACTIVE_SHOPIFY_SHOPS[shop] = scope;
const response = await Shopify.Webhooks.Registry.register({
shop,
accessToken,
path: "/webhooks",
topic: "APP_UNINSTALLED",
webhookHandler: async (topic, shop, body) =>
delete ACTIVE_SHOPIFY_SHOPS[shop],
});
if (!response.success) {
console.log(
`Failed to register APP_UNINSTALLED webhook: ${response.result}`
);
}
// Redirect to app with shop parameter upon auth
ctx.redirect(`/?shop=${shop}&host=${host}`);
},
})
);
const handleRequest = async (ctx) => {
await handle(ctx.req, ctx.res);
ctx.respond = false;
ctx.res.statusCode = 200;
};
router.post("/webhooks", async (ctx) => {
try {
await Shopify.Webhooks.Registry.process(ctx.req, ctx.res);
console.log(`Webhook processed, returned status code 200`);
} catch (error) {
console.log(`Failed to process webhook: ${error}`);
}
});
router.post(
"/graphql",
verifyRequest({ returnHeader: true }),
async (ctx, next) => {
await Shopify.Utils.graphqlProxy(ctx.req, ctx.res);
}
);
router.get("(/_next/static/.*)", handleRequest); // Static content is clear
router.get("/_next/webpack-hmr", handleRequest); // Webpack content is clear
router.get("(.*)", async (ctx) => {
const shop = ctx.query.shop;
// This shop hasn't been seen yet, go through OAuth to create a session
if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
ctx.redirect(`/auth?shop=${shop}`);
} else {
await handleRequest(ctx);
}
});
server.use(router.allowedMethods());
server.use(router.routes());
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`);
});
});
Solved! Go to the solution
This is an accepted solution.
Ok seems like I solved the issue. In server.js, instead of using the API_VERSION enum, i used the string value '2021-10' and it worked.
Shopify.Context.initialize({
API_KEY: process.env.SHOPIFY_API_KEY,
API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
SCOPES: process.env.SCOPES.split(","),
HOST_NAME: process.env.HOST.replace(/https:\/\/|\/$/g, ""),
API_VERSION: '2021-10', //instead of using the API_VERSION enum, use the string value
IS_EMBEDDED_APP: true,
SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});
This is an accepted solution.
Ok seems like I solved the issue. In server.js, instead of using the API_VERSION enum, i used the string value '2021-10' and it worked.
Shopify.Context.initialize({
API_KEY: process.env.SHOPIFY_API_KEY,
API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
SCOPES: process.env.SCOPES.split(","),
HOST_NAME: process.env.HOST.replace(/https:\/\/|\/$/g, ""),
API_VERSION: '2021-10', //instead of using the API_VERSION enum, use the string value
IS_EMBEDDED_APP: true,
SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});
This solved my issue, i just upgraded my shopify api version, and this issue suddenly occured, thanks you for the solution