【问题标题】:How to get the current shop in shopify when using NodeJS (Public App)?使用NodeJS(公共应用程序)时如何在shopify中获取当前商店?
【发布时间】:2021-09-13 20:16:48
【问题描述】:

我是 Shopify 应用开发新手,尤其是 Shopify API。

我使用 Shopify CLI 创建了一个工作应用,现在想要与 API 进行通信。

我尝试访问以下端点:https://{my_shop]/admin/api/2021-07/shop.json

我了解到我需要一些访问令牌和商店名称才能访问此端点。

我在我的私人应用部分下创建了一个访问令牌。

但我不知道如何获取当前登录的商店。

例如,当单击前端中的按钮时,我想调用我的端点,该端点又调用 Shopify API 端点并检索信息。我该如何以正确的方式做到这一点?以及如何获取当前登录的商店?

这是我目前的代码:

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

请看看我的尝试 - 端点 /test 和端点 /test2。 test2 不工作。 ctx.session 为空。 ctx 本身为空。为什么?

test1 在我将商店名称硬编码到 url 时工作,然后我得到所需的数据。但是我如何在里面放一个 shop 变量呢?这就是我的奋斗目标。

【问题讨论】:

  • 考虑到您正在访问Admin API,您应该使用shopify-admin-api npmjs.com/package/shopify-admin-api 在授权之后,您将使用const shop = await shops.get();
  • @Ovi 你能在答案中举个例子吗?那会很有帮助!
  • 您是从应用内部还是外部调用 URL?
  • @AntoineAndrieu 目前,仅在后端
  • 据我了解,只有来自 Shopify 的请求将商店作为查询参数。我通常做的是在正文或标题中手动将商店作为查询参数传递。

标签: javascript node.js api shopify shopify-app


【解决方案1】:

我遇到了这个问题,并通过将 shop 作为查询参数传递来解决它。

我调用端点:

axios.get('/test', {
  params: {
    shop: 'fo.myshopify.com'
  }
});

并通过以下方式获得商店:

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

当然,你必须知道你调用端点的商店。

【讨论】:

  • 但是你怎么知道这家店呢?几个不同的商店会调用这个端点。找出哪个商店调用端点的常用方法是什么?
  • 是的,你怎么知道这家店的?让我知道。
  • 例如,您可以将其存储在本地存储中。
【解决方案2】:

首先,MemorySessionStorage由于其局限性,在生产环境中使用并不是一个好习惯,你可以找到一个很好的解释here

MemorySessionStorage 作为一个选项存在,可帮助您入门 尽快开发您的应用程序...

因此,实现 CustomSessionStorage(请参阅上面的文档),您将可以访问存储数据的 session,例如 shop , accessToken, scope 等等。只要发出经过身份验证的请求,并在标头中提供 JWT,您就可以使上下文正常工作。

例如(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 (<></>);
}

然后……

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

更多详情here

使用 axios,您可以将其定义为钩子(使用 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);
// ...

希望这对仍在寻求帮助的人有所帮助。

【讨论】:

  • 我确认这个答案有效!谢谢!
【解决方案3】:

koa-shopify-auth documentation 中没有任何 ctx.session 的引用。这个呢:

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

其他解决方案

认证后可以存储一个Cookie

afterAuth(ctx) {
    const { shop, accessToken } = ctx.session;
    ctx.cookies.set("shop", shop, { httpOnly: false, secure: true, sameSite: "none" });
    ctx.redirect("/");
},

然后在以后的请求中读取它:

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

【讨论】:

  • 来自问题:“ctx 本身为空”。所以这可能不是进入商店的正确方式。
  • 是的,遗憾的是 ctx 本身是 null 所以没有
  • 我已经尽可能接近地复制了您的相同示例,并使上下文正常工作。在koa 中,没有没有上下文的请求,因此上下文丢失的可能性很小。此外,中间件 (github.com/Shopify/koa-shopify-auth/blob/…) 没有任何东西可以弄乱上下文。您能否为我们提供一个完整的示例,包括package.json 以及重现错误所需的任何其他内容?
  • @AntoineAndrieu 来自原始问题:“ctx.session 为空。ctx 本身为空”。如果 ctx 为 null,则 ctx.session 不能为 null(因为它是无效的访问器)。这就是为什么我不认为这句话完全正确。
猜你喜欢
  • 1970-01-01
  • 2018-05-06
  • 2019-07-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-05
  • 2014-02-04
  • 1970-01-01
相关资源
最近更新 更多