Hi,
I had created an application using shopify CLI and I am trying to test an application I have been working on a development store. The application worked fine before and I was able to install it on development stores and test it. I have been working on the application for two months now and it is almost at the end of finishing. However, I decided to go ahead and test the application’s functionality after uninstalling, and reinstalling it. This is where problem began. Once i uninstalled the application and went ahead to reinstall it, I am never shown the app installation prompt and i get redirected to the application’s UI which then gives me
Unhandled Runtime Error
AppBridgeError: APP::ERROR::INVALID_CONFIG: host must be provided
I believe the error happens because of the installation prompt not appearing. The host value is undefined from MyApp.getInitialProps() function inside _app.js file.
My server.js code is below.
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 mongo from 'koa-mongo';
import cors from '@koa/cors';
import next from 'next';
import Router from 'koa-router';
import serve from 'koa-static';
import conditional from 'koa-conditional-get';
import etag from 'koa-etag';
import cacheControl from 'koa-cache-control';
import staticCache from 'koa-static-cache';
import MongoClientConnection from './mongoClientConnection';
import { v4 as uuidv4 } from 'uuid';
import RedisStore from './redisStore';
import AmazonProductRouter from './routes/products';
import { dev, port } from './config';
import { getUser, insertUser, updateUser } from './queries';
import { CronJob } from './cron';
dotenv.config();
const app = next({ dev });
console.log(dev, port, 'dev and port');
const handle = app.getRequestHandler();
const sessionStorage = new RedisStore();
const CustomSessionStorage = new Shopify.Session.CustomSessionStorage(
sessionStorage.storeCallback,
sessionStorage.loadCallback,
sessionStorage.deleteCallback
);
CustomSessionStorage.client = sessionStorage.client;
CustomSessionStorage.getAsync = sessionStorage.getAsync;
CustomSessionStorage.setAsync = sessionStorage.setAsync;
CustomSessionStorage.delAsync = sessionStorage.delAsync;
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.October20,
IS_EMBEDDED_APP: true,
SESSION_STORAGE: CustomSessionStorage,
});
app.prepare().then(async () => {
const server = new Koa();
const router = new Router();
server.keys = [Shopify.Context.API_SECRET_KEY];
server.use(cors());
server.on('error', (err, ctx) => {
const errorMessage = err.toString();
if (
errorMessage !==
'BadRequestError: Expected a valid shop query parameter' &&
errorMessage !== 'ClientError [BadRequestError]: Malicious Path'
) {
if (errorMessage.startsWith('TimeoutError')) {
console.log('Timeout restarting');
process.exit(1);
}
}
});
if (dev) {
server.use(mongo({ host: 'localhost', port: 27017, db: process.env.DB }));
} else {
server.use(
mongo({
host: 'localhost',
port: 27017,
user: '*****',
pass: '******',
authSource: process.env.DB,
db: process.env.DB,
})
);
}
const responseUninstallWebhookHandler = async (topic, shop, body) => {
const mongoDb = await MongoClientConnection.db();
await mongoDb
.collection('access_tokens')
.updateOne({ shop }, { $set: { installStatus: 'uninstalled' } });
};
Shopify.Webhooks.Registry.webhookRegistry.push({
path: '/webhooks',
topic: 'APP_UNINSTALLED',
webhookHandler: responseUninstallWebhookHandler,
});
server.use(
createShopifyAuth({
accessMode: 'offline',
async afterAuth(ctx) {
try {
const { shop, accessToken, scope } = ctx.state.shopify;
const host = ctx.query.host;
const user = await getUser(ctx.db, shop);
if (!user) {
const result = await insertUser(ctx.db, shop, accessToken);
} else {
const result = await updateUser(ctx.db, shop, 'installed');
}
const responseUninstall = await Shopify.Webhooks.Registry.register({
shop,
accessToken,
path: '/webhooks',
topic: 'APP_UNINSTALLED',
webhookHandler: responseUninstallWebhookHandler,
});
if (!responseUninstall.success) {
console.log(
`Failed to register APP_UNINSTALLED webhook: ${responseUninstall.result}`
);
}
// Redirect to app with shop parameter upon auth
ctx.redirect(`/?shop=${shop}&host=${host}`);
} catch (e) {
console.log(e, 'auth error');
}
},
})
);
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) => {
console.log(ctx.query.host); // host is undefined here
const shop = ctx.query.shop;
const user = await getUser(ctx.db, shop);
if (!user || user.installStatus !== 'installed') {
ctx.redirect(`/auth?shop=${shop}`);
} else {
await handleRequest(ctx);
}
});
server
.use(AmazonProductRouter.routes())
.use(AmazonProductRouter.allowedMethods());
server.use(router.allowedMethods());
server.use(router.routes());
server.listen(port, () => {
console.log(`> Ready on http://localhost:${port}`);
});
//CronJob();
});
Below is my _app.js code.
import ApolloClient from 'apollo-boost';
import { ApolloProvider } from 'react-apollo';
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 '@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 client = new ApolloClient({
fetch: userLoggedInFetch(app),
fetchOptions: {
credentials: 'include',
},
});
const Component = props.Component;
return (
);
}
class MyApp extends App {
render() {
const { Component, pageProps, host } = this.props;
// host is undeifend here
return (
);
}
}
MyApp.getInitialProps = async ({ ctx }) => {
console.log(ctx);
return {
host: ctx.query.host, // there is not host available in ctx.query
};
};
export default MyApp;
export default MyApp;
I am using app-bridge-react version 2.0.2
I also have attached the screenshot of the error that appears while trying to install the app.
Any help would be great