返回市场
模拟桥接

模拟桥接

作者:ctrlaltdylan26 星标更新:2025-10-15

项目介绍

<div align="center"> <img src="assets/img/mock-bridge-logo.png" alt="Mock Bridge Logo" width="200" />

Shopify Mock Bridge

一个全面的浏览器测试解决方案,适用于Shopify嵌入式应用。本地模拟Shopify Admin环境和App Bridge API,无需真实的Shopify凭据、验证码或双因素认证。

npm version License: MIT

</div>

🎥 观看演示视频

<div align="center"> <a href="https://www.youtube.com/watch?v=swflaGObK4A" target="_blank"> <img src="assets/img/yt-thumbnail.png" alt="Mock Bridge Demo Video" width="600" /> </a>

▶️ 观看演示视频 - 查看Mock Bridge如何简化Shopify应用测试

</div>

🎯 为什么使用这个包?

测试Shopify嵌入式应用很困难:

  • 🚫 Shopify Admin需要双因素认证和验证码
  • 🤖 Playwright/自动化工具无法绕过安全措施
  • 🔒 Chrome DevTools MCP无法与你的嵌入式Shopify应用交互
  • 🌐 CI/CD流水线需要互联网连接和凭据
  • 🐛 在真实管理后台进行手动测试缓慢且不可靠

这个包解决了所有这些问题:

  • ✅ 不需要验证码、双因素认证或真实的Shopify账户
  • ✅ 完整支持Playwright和自动化工具
  • ✅ 兼容Chrome MCP和DevTools
  • ✅ 支持离线运行和CI/CD流水线
  • ✅ 集成真实数据库以进行全面测试

🗺️ 发展路线图

核心功能

  • ✅ iFrame嵌入
  • ✅ 会话令牌发放

App Bridge API

  • 直接覆盖管理员API获取
  • Toast实现
  • 资源选择器
  • 动态权限

📦 安装

npm install @getverdict/mock-bridge --save-dev
# 或
yarn add @getverdict/mock-bridge --dev
# 或
pnpm add @getverdict/mock-bridge --save-dev

⚡ 快速开始(两个命令)

# 1. 安装包
npm install @getverdict/mock-bridge --save-dev

# 2. 启动模拟(只需提供你的应用URL)
npx @getverdict/mock-bridge http://localhost:3000

就这样! 你的应用现在在模拟的Shopify Admin中运行于 http://localhost:3080

🚀 快速入门指南

第一步:启动模拟服务器

方案A:一键启动(推荐)

# 最简单的方法 - 只需提供你的应用URL
npx @getverdict/mock-bridge http://localhost:3000

# 自动检测来自SHOPIFY_API_KEY环境变量的客户端ID
# 自动检测常见的应用路径和配置

方案B:配置文件

# 生成配置文件
npx @getverdict/mock-bridge init

# 编辑生成的mock.config.js,然后运行:
npx @getverdict/mock-bridge
# 添加到package.json脚本以便轻松访问
{
  "scripts": {
    "dev": "next dev",
    "mock:admin": "mock-bridge http://localhost:3000",
    "dev:mock": "concurrently \"npm run dev\" \"npm run mock:admin\""
  }
}

方案C:编程API

如果你需要更多控制,仍然可以使用编程API:

// scripts/start-mock-admin.js
const { MockShopifyAdminServer } = require("@verdict/mock-bridge");

async function startMockAdmin() {
  const server = new MockShopifyAdminServer({
    appUrl: "http://localhost:3000",
    clientId: process.env.SHOPIFY_API_KEY,
    clientSecret: "mock-secret-12345",
    port: 3080,
    debug: true,
  });

  await server.start();
  console.log("🎉 模拟的Shopify Admin已准备好 http://localhost:3080");
}

startMockAdmin().catch(console.error);

第二步:前端集成

使你的应用能够检测并使用模拟环境:

方案A:自动检测(推荐)

替换你的App Bridge脚本加载:

<!-- 前:直接从CDN加载 -->
<script src="https://cdn.shopify.com/shopifycloud/app-bridge.js"></script>

<!-- 后:智能加载,支持模拟 -->
<script>
  // 检查是否处于模拟环境中
  const urlParams = new URLSearchParams(window.location.search);
  const isEmbedded = urlParams.get("embedded") === "1";
  const host = urlParams.get("host");

  let isMockEnvironment = false;

  // 从URL参数检测模拟环境
  if (isEmbedded && host) {
    try {
      const decodedHost = atob(host);
      if (
        decodedHost.includes("localhost") ||
        decodedHost.includes("mock") ||
        window.location.hostname === "localhost"
      ) {
        isMockEnvironment = true;
      }
    } catch (e) {
      // 忽略解码错误
    }
  }

  // 加载适当的App Bridge
  if (isMockEnvironment) {
    console.log("正在加载模拟App Bridge");
    const script = document.createElement("script");
    script.src = "http://localhost:3080/app-bridge.js";
    script.onerror = () => {
      // 如果模拟失败,回退到真实的CDN
      const fallback = document.createElement("script");
      fallback.src = "https://cdn.shopify.com/shopifycloud/app-bridge.js";
      document.head.appendChild(fallback);
    };
    document.head.appendChild(script);
  } else {
    console.log("正在加载真实的Shopify App Bridge");
    const script = document.createElement("script");
    script.src = "https://cdn.shopify.com/shopifycloud/app-bridge.js";
    document.head.appendChild(script);
  }
</script>

方案B:包实用工具(TypeScript)

// app.tsx 或 _app.tsx
import { setupAppBridge } from "@getverdict/mock-bridge/client";

useEffect(() => {
  setupAppBridge({
    debug: true,
    onMockDetected: (mockServerUrl) => {
      console.log("检测到模拟环境:", mockServerUrl);
    },
    onShopifyDetected: () => {
      console.log("检测到真实的Shopify环境");
    },
  })
    .then(() => {
      console.log("App Bridge加载成功");
    })
    .catch(console.error);
}, []);

第三步:后端集成

使你的后端支持模拟会话令牌以及真实的令牌:

快速集成(替换现有的JWT验证)

// 前:只有真实的Shopify令牌
import { verifyShopifyJWT } from "./your-auth";

export async function authenticate(token: string) {
  const authData = await verifyShopifyJWT(token);
  // ...其余身份验证逻辑
}
// 后:支持模拟和真实的令牌
import {
  validateSessionToken,
  createMockUser,
} from "@getverdict/mock-bridge/auth";

export async function authenticate(token: string) {
  const authData = await validateSessionToken(token, {
    shopifySecret: process.env.SHOPIFY_API_PRIVATE_KEY!,
  });

  if (!authData) {
    throw new Error("无效的令牌");
  }

  // 从你的数据库中获取商店(对模拟和真实环境都相同)
  const shop = await getShopByName(authData.shopName);
  if (!shop) {
    throw new Error("未找到商店");
  }

  if (authData.isMock) {
    // 模拟环境 - 跳过Shopify API调用
    return {
      shop,
      currentUser: createMockUser({
        shopName: authData.shopName,
        permissions: shop.settings?.defaultStaffPermissions,
      }),
      isMock: true,
    };
  } else {
    // 真实环境 - 继续正常的Shopify流程
    const currentUser = await exchangeTokenForUser(token, shop);
    return {
      shop,
      currentUser,
      isMock: false,
    };
  }
}

特定框架示例

Next.js API路由:

// pages/api/products.ts
import { validateSessionToken } from "@getverdict/mock-bridge/auth";

export default async function handler(req, res) {
  const token = req.headers.authorization?.replace("Bearer ", "");

  const authData = await validateSessionToken(token, {
    shopifySecret: process.env.SHOPIFY_API_PRIVATE_KEY!,
  });

  if (!authData) {
    return res.status(401).json({ error: "未经授权" });
  }

  if (authData.isMock) {
    // 返回用于测试的模拟数据
    return res.json({
      products: [
        { id: "1", title: "模拟产品1", price: "19.99" },
        { id: "2", title: "模拟产品2", price: "29.99" },
      ],
    });
  } else {
    // 从Shopify获取真实的产品
    const products = await fetchShopifyProducts(authData.shopName);
    return res.json({ products });
  }
}

Express.js中间件:

import {
  validateSessionToken,
  createMockUser,
} from "@getverdict/mock-bridge/auth";

function createAuthMiddleware() {
  return async (req, res, next) => {
    const token = req.headers.authorization?.replace("Bearer ", "");

    const authData = await validateSessionToken(token, {
      shopifySecret: process.env.SHOPIFY_API_PRIVATE_KEY!,
    });

    if (!authData) {
      return res.status(401).json({ error: "未经授权" });
    }

    const shop = await getShopByName(authData.shopName);
    req.shop = shop;
    req.isMockAuth = authData.isMock;

    if (authData.isMock) {
      req.currentUser = createMockUser({ shopName: authData.shopName });
    } else {
      req.currentUser = await exchangeTokenForUser(token, shop);
    }

    next();
  };
}

app.use("/api/*", createAuthMiddleware());

第四步:数据库设置

确保你的模拟商店存在于数据库中:

// 添加到你的数据库种子或设置脚本
async function setupMockShop() {
  const mockShop = {
    name: "test-shop.myshopify.com",
    displayName: "模拟测试商店",
    accessToken: "mock-access-token",
    active: true,
    settings: {
      defaultStaffPermissions: [
        "read_products",
        "write_products",
        "read_orders",
        "write_orders",
      ],
    },
  };

  await createOrUpdateShop(mockShop);
  console.log("创建了用于测试的模拟商店");
}

// 在开发设置期间运行
if (process.env.NODE_ENV === "development") {
  setupMockShop();
}

第五步:开始开发

# 方案1:CLI命令(最简单)
npx @getverdict/mock-bridge http://localhost:3000

# 方案2:Package.json脚本
npm run dev:mock

# 方案3:分离终端
npm run dev                    # 终端1:你的应用
npm run mock:admin            # 终端2:模拟管理后台

然后导航到:

🖥️ CLI参考

快速命令

# 基本用法,自动检测
npx @getverdict/mock-bridge http://localhost:3000

# 如果本地安装,你可以使用更短的命令:
# npm install @getverdict/mock-bridge --save-dev
# npx mock-bridge http://localhost:3000

# 完整配置
npx @getverdict/mock-bridge http://localhost:3000/shopify \
  --client-id your-client-id \
  --port 3080 \
  --debug

# 使用配置文件
npx @getverdict/mock-bridge init           # 创建配置文件
npx @getverdict/mock-bridge                # 使用配置文件

# 帮助和版本
npx @getverdict/mock-bridge --help
n