shopify-app-template-node icon indicating copy to clipboard operation
shopify-app-template-node copied to clipboard

AppBridgeError INVALID_CONFIG: host must be provided

Open templatetuners opened this issue 3 years ago • 14 comments

When I open the app I get this error:

Unhandled Runtime Error
AppBridgeError: APP::ERROR::INVALID_CONFIG: host must be provided

Sometimes I can't even install it because of this.

I have both .env and process.env files completed. Sometimes I see for a second a message popup to enable cookies but I can't press Enable, the redirect is too fast.

templatetuners avatar May 15 '21 07:05 templatetuners

https://github.com/Shopify/shopify-app-bridge/issues/48

templatetuners avatar May 15 '21 09:05 templatetuners

The same thing happened to me, I added it by adding the host to the configuration.

A new query parameter named host has been introduced with the release of App Bridge 2.0. The base64-encoded host parameter represents the domain that is currently hosting your embedded app.

We start with the env file, it is important that you have the SHOPIFY_APP_URL defined, since it is the value that I use as host.

SHOPIFY_API_KEY=123
SHOPIFY_API_SECRET=shpss_123
SHOPIFY_API_SCOPES=read_products,write_products,read_script_tags,write_script_tags
SHOPIFY_APP_URL=https://123.ngrok.io

Now, in your next.config.js file you must take that value and send it, as well as the apiKey.

// next.config.js

require("dotenv").config();
const webpack = require("webpack");

const apiKey = JSON.stringify(process.env.SHOPIFY_API_KEY);
const host = JSON.stringify(process.env.SHOPIFY_APP_URL);

module.exports = {
    webpack: (config) => {
        const env = { API_KEY: apiKey, HOST_URL: host };
        config.plugins.push(new webpack.DefinePlugin(env));
        return config;
    },
};

Finally you have to pass that host to the configuration. This must be modified with base64 and that's it. You can see your app from the store you are testing in.

import React from "react";
import App from "next/app";
import Head from "next/head";
import { AppProvider } from "@shopify/polaris";
import { Provider } from "@shopify/app-bridge-react";
import "@shopify/polaris/dist/styles.css";
import translations from "@shopify/polaris/locales/es.json";

class MyApp extends App {
    render() {
        const { Component, pageProps, shopOrigin } = this.props;

        const config = {
            apiKey: API_KEY,
            shopOrigin,
            host: Buffer.from(HOST_URL).toString("base64"),
            forceRedirect: true,
        };

        return (
            <React.Fragment>
                <Head>
                    <title>Sample App</title>
                    <meta charSet="utf-8" />
                </Head>
                <Provider config={config}>
                    <AppProvider i18n={translations}>
                        <Component {...pageProps} />
                    </AppProvider>
                </Provider>
            </React.Fragment>
        );
    }
}

MyApp.getInitialProps = async ({ ctx }) => {
    return {
        shopOrigin: ctx.query.shop,
    };
};

export default MyApp;

FiliSantillan avatar May 15 '21 22:05 FiliSantillan

@FiliSantillan wait why are we pulling from env when it's available in the query?

Update your pages/_app.js to include host in getInitialProps and then call it in your config from this.props along side shopOrigin

const { Component, pageProps, shopOrigin, host } = this.props;
const config = { apiKey: API_KEY, shopOrigin, host, forceRedirect: true };
...
...
...

MyApp.getInitialProps = async ({ ctx }) => {
  return { shopOrigin: ctx.query.shop, host: ctx.query.host };
};

And if you're using something like recurring subscriptions, you'll need to get host from the URL and pass it in your return URL

const getHost = (ctx) => {
 const baseUrl = new URL(`https://${ctx.request.header.host}${ctx.request.url}`);
  return baseUrl.searchParams.get("host");  
 };
server.use(
 createShopifyAuth({ async afterAuth(ctx) {
  const { shop, scope, accessToken } = ctx.state.shopify;
  const host = getHost(ctx);
  ACTIVE_SHOPIFY_SHOPS[shop] = scope;
  const returnUrl = `https://${Shopify.Context.HOST_NAME}?host=${host}&shop=${shop}`;
  const subscriptionUrl = await getSubscriptionUrl(accessToken, shop, returnUrl );
  ctx.redirect(subscriptionUrl);
  },
 })
);

kinngh avatar May 18 '21 06:05 kinngh

Did this help anyone? I have the same problem, still. server.js : from the node tutorial.


require("isomorphic-fetch");
const dotenv = require("dotenv");
const Koa = require("koa");
const next = require("next");
const { default: createShopifyAuth } = require("@shopify/koa-shopify-auth");
const { verifyRequest } = require("@shopify/koa-shopify-auth");
const { default: Shopify, ApiVersion } = require("@shopify/shopify-api");
const Router = require("koa-router");

dotenv.config();

Shopify.Context.initialize({
    API_KEY: process.env.SHOPIFY_API_KEY,
    API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
    SCOPES: process.env.SHOPIFY_API_SCOPES.split(","),
    HOST_NAME: process.env.SHOPIFY_APP_URL.replace(/https:\/\//, ""),
    API_VERSION: ApiVersion.April21,
    IS_EMBEDDED_APP: true,
    SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});

const port = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();

const ACTIVE_SHOPIFY_SHOPS = {};

app.prepare().then(() => {
    const server = new Koa();
    const router = new Router();
    server.keys = [Shopify.Context.API_SECRET_KEY];

    server.use(
        createShopifyAuth({
            afterAuth(ctx) {
                const { shop, scope } = ctx.state.shopify;
               
                ACTIVE_SHOPIFY_SHOPS[shop] = scope;

                ctx.redirect(`/`);
                ctx.redirect(`/?shop=${shop}`);
            },
        })
    );


    const handleRequest = async (ctx) => {
        await handle(ctx.req, ctx.res);
        ctx.respond = false;
        ctx.res.statusCode = 200;
    };

    router.get("/", async (ctx) => {
        const shop = ctx.query.shop;

        if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
            ctx.redirect(`/auth?shop=${shop}`);
        } else {
            await handleRequest(ctx);
        }
    });

    router.get("(/_next/static/.*)", handleRequest);
    router.get("/_next/webpack-hmr", handleRequest);
    router.get("(.*)", verifyRequest(), handleRequest);

    server.use(router.allowedMethods());
    server.use(router.routes());

    server.listen(port, () => {
        console.log(`> Ready on http://localhost:${port}`);
    });
});

.env

SHOPIFY_API_KEY=0000
SHOPIFY_API_SECRET=0000
SHOPIFY_API_SCOPES=read_products
SHOPIFY_APP_URL=https://daee3b7889af.ngrok.io

next.config.js


require("dotenv").config();
const webpack = require("webpack");

const apiKey = JSON.stringify(process.env.SHOPIFY_API_KEY);
const host = JSON.stringify(process.env.SHOPIFY_APP_URL);

module.exports = {
    webpack: (config) => {
        const env = { API_KEY: apiKey, HOST_URL: host };
        config.plugins.push(new webpack.DefinePlugin(env));
        return config;
    },
};


app.js

import App from "next/app";
import Head from "next/head";
import { AppProvider } from "@shopify/polaris";
import { Provider } from "@shopify/app-bridge-react";
import "@shopify/polaris/dist/styles.css";
import translations from "@shopify/polaris/locales/en.json";
import ClientRouter from "../components/ClientRouter";

class MyApp extends App {
    render() {
        const { Component, pageProps, shopOrigin, host } = this.props;
        const config = {
            apiKey: API_KEY,
            shopOrigin,
            host,
            forceRedirect: true,
        };
        return (
            <React.Fragment>
                <Head>
                    <title>Sample App</title>
                    <meta charSet="utf-8" />
                </Head>
                <Provider config={config}>
                    <ClientRouter />
                    <AppProvider i18n={translations}>
                        <Component {...pageProps} />
                    </AppProvider>
                </Provider>
            </React.Fragment>
        );
    }
}
MyApp.getInitialProps = async ({ ctx }) => {
    return {
        shopOrigin: ctx.query.shop, host: ctx.query.host 
    };
};

export default MyApp;

Theantipioneer avatar May 23 '21 03:05 Theantipioneer

looks like this was fixed by #629

sergey-v9 avatar Jun 04 '21 21:06 sergey-v9

but the tutorial is not updated yet. Still had to add what @kinngh posted ( thanks!).

lubojanski avatar Jun 19 '21 14:06 lubojanski

@lubojanski thanks for reminding that now you don't have to use a separate function to call in on host and instead it's directly available. I've updated the code on my template repository available here or you can directly look at the individual files at pages/_app.js and server.js. Though I hope they kinda update the repo soon.

kinngh avatar Jun 19 '21 18:06 kinngh

Anyone figure out a fix for this I'm trying to deploy the app to AWS Cloudfront and this issue keeps popping up. Man deploying shopify apps to AWS is a F***** nightmare this really needs worked out better. In 13 years of development I've never seen any company use Heroku everyone is on AWS/Azure.

joshbedo avatar Aug 10 '21 20:08 joshbedo

Im still facing this issue. Seems my code is pretty correct but after moving to vercel it starts giving me this error......

when running locally and using local-tunnel/ngrok all works but after deploying to vercel and changing my URLs it stops working.......

rodrigograca31 avatar Nov 11 '21 00:11 rodrigograca31

@joshbedo I'd appriciate if you could share any insights you found? seems you were deploying to AWS.... did it work? did you change anything?

rodrigograca31 avatar Nov 11 '21 00:11 rodrigograca31

Any updates on this?

MatiSap avatar Mar 29 '22 14:03 MatiSap

+1

CostierLucas avatar Apr 08 '22 23:04 CostierLucas

Please note there is a bug in some versions of @shopify/app-bridge-react that will cause the "host must be provided error".

This bug randomly occurred after an "npm update" with no code changes.

app-bridge-react 2.0.24 seems to be the only stable version.

Also see https://community.shopify.com/c/shopify-apis-and-sdks/shopify-app-bridge-react-getting-error-host-not-provided-after/m-p/1579622

ghost avatar May 29 '22 04:05 ghost

This also happens when the session expires after 24 hours. Had to manually redirect the app to the auth url to reauthenticate the user to fix this issue.

towfiqi avatar Jul 24 '22 04:07 towfiqi

This issue is stale because it has been open for 60 days with no activity. It will be closed if no further action occurs in 14 days.

github-actions[bot] avatar Oct 07 '22 02:10 github-actions[bot]

We are closing this issue because it has been inactive for a few months. This probably means that it is not reproducible or it has been fixed in a newer version. If it’s an enhancement and hasn’t been taken on since it was submitted, then it seems other issues have taken priority.

If you still encounter this issue with the latest stable version, please reopen using the issue template. You can also contribute directly by submitting a pull request– see the CONTRIBUTING.md file for guidelines

Thank you!

github-actions[bot] avatar Oct 22 '22 02:10 github-actions[bot]

For Remix developers,

To resolve this issue, include the following code in the app.tsx file. This addition should fix the issue.

export const loader = async ({ request }: LoaderFunctionArgs) => {
  const url = new URL(request.url);
  const params = new URLSearchParams(url.search);
  const host = params.get('host');
  
  await authenticate.admin(request);
  return json({ apiKey: process.env.SHOPIFY_API_KEY || "", host: host });
};

zaaack93 avatar Feb 14 '24 10:02 zaaack93