Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get the current shop in shopify when using NodeJS (Public App)?

I am new to Shopify App Devlopment, especially the Shopify API.

I create a working app with the Shopify CLI and now want to communicate with the API.

I try to access following endpoint: https://{my_shop]/admin/api/2021-07/shop.json

I learned that I need some access token and the shop name to access this endpoint.

I created an access token under my private apps section.

But I dont know how to get the currently logged in store.

For example, when clicking a button in my frontend, I would like to call my endpoint, which in turn calls the Shopify API endpoint and retrieves the information. How do I do this the right way? And how do I get the currently logged in shop?

This is my code so far:

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";
import axios from 'axios';

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:\/\//, ""),
  API_VERSION: ApiVersion.October20,
  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}`);
      },
    })
  );

  router.get("/test2", verifyRequest(), async(ctx, res) => {
    const {shop, accessToken } = ctx.session;
    console.log(shop);
    console.log(accessToken);
  })

  router.get("/test", async (ctx) => {

    const config = {
      headers: {
        'Content-Type': 'application/json',
        'X-Shopify-Access-Token': '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'
      }
    }

    await axios.get('${the_store_name_belongs_here}/admin/api/2021-07/shop.json', config).then(res => {
      ctx.body = res.data;
    });
  });

  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}`);
  });
});

Please have a look at my attempts - endpoint /test and endpoint /test2. test2 is not working. ctx.session is null. ctx itself is null. Why?

test1 is working when I hard code my shops name into the url, then I get the desired data. But how do I put a shop variable inside? That's my struggle.

like image 728
IonicMan Avatar asked Oct 26 '22 11:10

IonicMan


People also ask

What is app bridge in Shopify?

Shopify App Bridge is a JavaScript library that helps you embed your app directly inside Shopify. Shopify App Bridge works with apps embedded in Shopify Admin, and the Shopify Mobile and Shopify POS apps to help you create a seamless experience for merchants. Read the documentation to learn more.


1 Answers

First of all, it is not good practice to use MemorySessionStorage in production environment due to its limitations, you can find a good explanation here

MemorySessionStorage exists as an option to help you get started developing your apps as quickly as possible...

So, implement a CustomSessionStorage (refer to the doc above), you will have access to the session that stores data such as shop, accessToken, scope among others. As long as authenticated requests are made, providing the JWT in the header, you can get the context properly working.

e.g. (react-koa):

//client.js
import { useAppBridge } from "@shopify/app-bridge-react";
import { getSessionToken } from "@shopify/app-bridge-utils";

function Index() {
   const app = useAppBridge();

   async function getProducts() {
       const token = await getSessionToken(app);

       const response = await fetch("/api/products", {
           headers: { "Authorization": `Bearer ${token}` }
       });

       const result = await response.json();
       console.log(result);
   }

   return (<></>);
}

and then...

// server.js

router.get("/api/products", verifyRequest({ returnHeader: true }), async (ctx) => {
    // Load the current session to get the `accessToken`.
    // pass a third parameter clarifying the accessMode (isOnline = true by default)
    const session = await Shopify.Utils.loadCurrentSession(ctx.req, ctx.res);

    // Create a new client for the specified shop.
    const client = new Shopify.Clients.Rest(session.shop, session.accessToken);

    // Use `client.get` to request the specified Shopify REST API endpoint, in this case `products`.
    const products = await client.get({
      path: 'products',
    });

    ctx.body = results.body;
    ctx.res.status = 200;
  });

more details here.

Using axios, you can define it as a hook (working example using TypeScript):

import axios from 'axios';
import { useAppBridge } from '@shopify/app-bridge-react';
import { getSessionToken } from '@shopify/app-bridge-utils';

function useAxios() {
  const app = useAppBridge();
  const instance = axios.create();
  instance.interceptors.request.use(async function (config) {
    const token = await getSessionToken(app);
    config.headers['Authorization'] = `Bearer ${token}`;
    return config;
  });
  return [instance];
}

export default useAxios;

// index.js

// ...
const [axios] = useAxios();

// ...
const result = await axios.get('/api/products');
console.log(result.data);
// ...

Hope this helps anyone who is still looking for help.

like image 56
hnakao11 Avatar answered Oct 29 '22 23:10

hnakao11