From 56fced9e1e0030d607260ee707241e25feda5f03 Mon Sep 17 00:00:00 2001 From: fazlulkabir34 Date: Wed, 25 Mar 2026 02:52:56 +0600 Subject: [PATCH 01/21] feat: React Router admin auth helper, package exports, peers - Add withMockBridgeAdminAuthForReactRouter under @getverdict/mock-bridge/react-router - Wire package.json exports for ., ./auth, ./react-router, ./client - Optional peerDependencies for @shopify/shopify-app-react-router and @shopify/shopify-api - Document SHOPIFY_APP_REACT_ROUTER, BACKEND_INTEGRATION caveat, README link - tsconfig paths for Shopify package subpath typings under classic moduleResolution --- README.md | 2 + docs/BACKEND_INTEGRATION.md | 6 +- docs/SHOPIFY_APP_REACT_ROUTER.md | 79 ++++++++++++ package-lock.json | 116 ++++++++++++++++++ package.json | 37 +++++- src/react-router/index.ts | 6 + .../withMockBridgeAdminAuthForReactRouter.ts | 107 ++++++++++++++++ tsconfig.json | 11 +- 8 files changed, 360 insertions(+), 4 deletions(-) create mode 100644 docs/SHOPIFY_APP_REACT_ROUTER.md create mode 100644 src/react-router/index.ts create mode 100644 src/react-router/withMockBridgeAdminAuthForReactRouter.ts diff --git a/README.md b/README.md index c17a7ed..00dff60 100644 --- a/README.md +++ b/README.md @@ -307,6 +307,8 @@ export async function authenticate(token: string) { #### Framework-Specific Examples +**Shopify App React Router** (`authenticate.admin(request)`): see the dedicated guide **[docs/SHOPIFY_APP_REACT_ROUTER.md](./docs/SHOPIFY_APP_REACT_ROUTER.md)** and `@getverdict/mock-bridge/react-router`. + **Next.js API Routes:** ```typescript diff --git a/docs/BACKEND_INTEGRATION.md b/docs/BACKEND_INTEGRATION.md index bebd08f..8a59dee 100644 --- a/docs/BACKEND_INTEGRATION.md +++ b/docs/BACKEND_INTEGRATION.md @@ -2,6 +2,10 @@ This guide explains how to integrate mock session token support into any Shopify app backend. The authentication utilities provided by this package allow your backend to seamlessly handle both real Shopify tokens and mock tokens for testing, with minimal code changes. +## Shopify App React Router + +If you use `@shopify/shopify-app-react-router`, authentication is exposed as **`shopify.authenticate.admin(request)`** (and other `authenticate.*` methods), not as a `(token: string) => …` function. The **`withMockTokenSupport`** helper in this package only wraps **token-string** APIs; it does not replace or wrap `authenticate.admin`. For Mock Bridge in React Router apps, use **`withMockBridgeAdminAuthForReactRouter`** from `@getverdict/mock-bridge/react-router` and follow **[Shopify App React Router](./SHOPIFY_APP_REACT_ROUTER.md)**. + ## Table of Contents - [Quick Start](#quick-start) @@ -455,7 +459,7 @@ Generate mock user objects for testing. ### withMockTokenSupport(authFunction, shopifySecret, options?) -Wrapper that adds mock token support to existing auth functions. +Wrapper that adds mock token support to existing auth functions. This is only applicable when your API is a **`(token: string) => …`** handler. It does **not** apply to `@shopify/shopify-app-react-router`’s **`authenticate.admin(request)`**; use **`withMockBridgeAdminAuthForReactRouter`** ([Shopify App React Router](./SHOPIFY_APP_REACT_ROUTER.md)) instead. **Parameters:** - `authFunction: (token: string) => T | Promise` - Existing auth function diff --git a/docs/SHOPIFY_APP_REACT_ROUTER.md b/docs/SHOPIFY_APP_REACT_ROUTER.md new file mode 100644 index 0000000..a44b318 --- /dev/null +++ b/docs/SHOPIFY_APP_REACT_ROUTER.md @@ -0,0 +1,79 @@ +# Shopify App React Router + +Use this guide if your app is built with [`@shopify/shopify-app-react-router`](https://shopify.dev/docs/api/shopify-app-react-router) (Shopify CLI React Router template). + +## Problem + +`shopify.authenticate.admin(request)` decodes the session JWT, loads a session from your configured storage, and may call Shopify’s token exchange when no session exists. Mock Bridge issues embedded session JWTs for local testing; when those tokens are signed with your real app secret, decoding succeeds, but Shopify will not exchange a mock-issued token. Without a row in session storage, admin authentication fails (redirect or error). + +## Solution + +When `SHOPIFY_MOCK_BRIDGE_AUTH=1`, wrap **only** `authenticate.admin` with `withMockBridgeAdminAuthForReactRouter` from `@getverdict/mock-bridge/react-router`. It decodes the session token using `@shopify/shopify-api` (same audience checks as the framework), seeds a minimal offline or online `Session` if needed, then calls the real `shopify.authenticate.admin(request)`. Webhooks and other `authenticate.*` methods are unchanged. + +## Environment variables + +| Variable | When | Purpose | +| -------- | ---- | ------- | +| `SHOPIFY_MOCK_BRIDGE_AUTH` | E2E / CI only | Must be `"1"` for the wrapper to seed a session before delegating to `authenticate.admin`. | +| `SHOPIFY_MOCK_BRIDGE_ACCESS_TOKEN` | Optional | If set, stored on the seeded session as `accessToken` so tests that call the real Admin API can use a valid token. | + +**Never set `SHOPIFY_MOCK_BRIDGE_AUTH` in production.** It bypasses normal session acquisition for admin requests when enabled. + +## Align JWT verification with the mock server + +`MockShopifyAdminServer` must sign session tokens with the **same secret** your app uses for JWT verification: set `clientSecret` to your app’s `SHOPIFY_API_SECRET` / `apiSecretKey` when you rely on that signing path. Otherwise `decodeSessionToken` will reject mock tokens. + +## `shopify.server.ts` example + +After `const shopify = shopifyApp({ ... })`, export `authenticate` like this (mirror the same `apiKey`, `apiSecretKey`, `apiVersion`, `scopes`, `appUrl`, and `customShopDomains` as `shopifyApp`): + +```typescript +import { shopifyApp, ApiVersion } from "@shopify/shopify-app-react-router/server"; +import { withMockBridgeAdminAuthForReactRouter } from "@getverdict/mock-bridge/react-router"; + +const shopify = shopifyApp({ + apiKey: process.env.SHOPIFY_API_KEY!, + apiSecretKey: process.env.SHOPIFY_API_SECRET!, + apiVersion: ApiVersion.October25, + scopes: process.env.SCOPES?.split(",") ?? [], + appUrl: process.env.SHOPIFY_APP_URL!, + // sessionStorage, authPathPrefix, etc. +}); + +export default shopify; + +export const authenticate = + process.env.SHOPIFY_MOCK_BRIDGE_AUTH === "1" + ? withMockBridgeAdminAuthForReactRouter(shopify, { + apiKey: process.env.SHOPIFY_API_KEY, + apiSecretKey: process.env.SHOPIFY_API_SECRET!, + apiVersion: ApiVersion.October25, + scopes: process.env.SCOPES?.split(",") ?? [], + appUrl: process.env.SHOPIFY_APP_URL!, + }) + : shopify.authenticate; +``` + +Set `useOnlineTokens: true` in the reflect config if your app uses online tokens and you need the same session id strategy as production. + +## App Bridge and CSP + +Point embedded App Bridge at your mock server and relax CSP for the mock admin origin in development, similar to the Remix guide: + +- See [Shopify app remix](./SHOPIFY_APP_REMIX.md) for the `__APP_BRIDGE_URL` and `frame-ancestors` patterns (adjust host/port if yours differ). + +## Runtime dependency + +The `@getverdict/mock-bridge/react-router` implementation loads **`@shopify/shopify-api` only** at runtime (for `shopifyApi`, `Session`, and `decodeSessionToken`). Your app must still install **`@shopify/shopify-app-react-router`** and peers (`react`, `react-dom`, `react-router`) as required by Shopify’s template. + +## Install from npm or Git + +```bash +npm install @getverdict/mock-bridge --save-dev +# or a branch: +# "@getverdict/mock-bridge": "github:bevycommerce/mock-bridge#feature/react-router-admin-auth" +``` + +## Related + +- [Backend integration](./BACKEND_INTEGRATION.md) — token-string validators and `withMockTokenSupport` (not a substitute for `authenticate.admin(request)`). diff --git a/package-lock.json b/package-lock.json index 0def13d..08adf3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,8 @@ "mock-bridge": "dist/cli/index.js" }, "devDependencies": { + "@shopify/shopify-api": "^13.0.0", + "@shopify/shopify-app-react-router": "^1.2.0", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jsonwebtoken": "^9.0.5", @@ -77,6 +79,86 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@shopify/admin-api-client": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@shopify/admin-api-client/-/admin-api-client-1.1.2.tgz", + "integrity": "sha512-sV5gS8x4kj0KLULlOJkoBzm+tu6tn+1PCK9a6inySv5qtzmf/JxDWtLgaW9vRXeXHo/Sl/p3mBWhY3//fKA8iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shopify/graphql-client": "^1.4.2" + } + }, + "node_modules/@shopify/graphql-client": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@shopify/graphql-client/-/graphql-client-1.4.2.tgz", + "integrity": "sha512-C/fEyx6BZmTGeJv0HSsduhfn5ZbkXk0nePkyo0+xXxorP1I+uvx9pobdFxTfiPnOrh+P6QiKzBgIviksnSo4YA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@shopify/shopify-api": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@shopify/shopify-api/-/shopify-api-13.0.0.tgz", + "integrity": "sha512-tf55PjnIoDY6wjv9gJB1FMYoRmalUCdB8ayPLlPwmDUzCRCKfbQdKrBEX1jnKRmSm7EvtjkUSqbI60v0/jAk9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shopify/admin-api-client": "^1.1.2", + "@shopify/graphql-client": "^1.4.2", + "@shopify/storefront-api-client": "^1.0.10", + "compare-versions": "^6.1.1", + "isbot": "^5.1.34", + "jose": "^5.9.6", + "lossless-json": "^4.3.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@shopify/shopify-app-react-router": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@shopify/shopify-app-react-router/-/shopify-app-react-router-1.2.0.tgz", + "integrity": "sha512-8yZzVw5a1LwIwN2HZQzUXKAs3c+39aSWDLT10gxTu+FJY3r5d0ndL7AhbRpwnS+TB8c0tp5JPVlVFXlVM4e+Bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shopify/admin-api-client": "^1.1.2", + "@shopify/shopify-api": "^13.0.0", + "@shopify/shopify-app-session-storage": "^5.0.0", + "@shopify/storefront-api-client": "^1.0.10", + "compare-versions": "^6.1.1", + "isbot": "^5.1.34" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18", + "react-router": "^7.6.2" + } + }, + "node_modules/@shopify/shopify-app-session-storage": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@shopify/shopify-app-session-storage/-/shopify-app-session-storage-5.0.0.tgz", + "integrity": "sha512-hFUeDGmA4NJmnsq0peYxYIhCzPtXzP9dKjoz04VwWq0XTFzmjy+6yLWY9LFNI4gSiK3Mws3FSLKaatd8TqIj2g==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@shopify/shopify-api": "^13.0.0" + } + }, + "node_modules/@shopify/storefront-api-client": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@shopify/storefront-api-client/-/storefront-api-client-1.0.10.tgz", + "integrity": "sha512-A0ItFW58L5+y/jnnJDEsmt2YcQSt2BG+pNoQOYRpPBJaOLpTj7A13q1QVrE5XYVGZCA6ZQ6FqAtgosHHrkut0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shopify/graphql-client": "^1.4.2" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -510,6 +592,13 @@ "node": ">=16" } }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "dev": true, + "license": "MIT" + }, "node_modules/concurrently": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", @@ -1125,6 +1214,26 @@ "node": ">=0.10.0" } }, + "node_modules/isbot": { + "version": "5.1.36", + "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.36.tgz", + "integrity": "sha512-C/ZtXyJqDPZ7G7JPr06ApWyYoHjYexQbS6hPYD4WYCzpv2Qes6Z+CCEfTX4Owzf+1EJ933PoI2p+B9v7wpGZBQ==", + "dev": true, + "license": "Unlicense", + "engines": { + "node": ">=18" + } + }, + "node_modules/jose": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", + "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -1216,6 +1325,13 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, + "node_modules/lossless-json": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lossless-json/-/lossless-json-4.3.0.tgz", + "integrity": "sha512-ToxOC+SsduRmdSuoLZLYAr5zy1Qu7l5XhmPWM3zefCZ5IcrzW/h108qbJUKfOlDlhvhjUK84+8PSVX0kxnit0g==", + "dev": true, + "license": "MIT" + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", diff --git a/package.json b/package.json index 4ba2a62..fbeccaf 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,24 @@ "description": "Mock Shopify App Bridge for testing and development", "main": "dist/index.js", "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "./auth": { + "types": "./dist/auth/index.d.ts", + "default": "./dist/auth/index.js" + }, + "./react-router": { + "types": "./dist/react-router/index.d.ts", + "default": "./dist/react-router/index.js" + }, + "./client": { + "types": "./dist/client/mock-detector.d.ts", + "default": "./dist/client/mock-detector.js" + } + }, "bin": { "mock-bridge": "dist/cli/index.js" }, @@ -17,7 +35,8 @@ "start": "node dist/server/index.js", "watch": "tsc --watch", "clean": "rm -rf dist", - "prepare": "cd admin-frame && npm install --legacy-peer-deps && cd ../app-bridge && npm install && cd .. && npm run build" + "prepare": "cd admin-frame && npm install --legacy-peer-deps && cd ../app-bridge && npm install && cd .. && npm run build", + "smoke:subpaths": "node -e \"require('./dist/auth'); require('./dist/react-router'); console.log('subpath require ok');\"" }, "keywords": [ "shopify", @@ -32,17 +51,31 @@ "type": "git", "url": "https://github.com/ctrlaltdylan/mock-bridge.git" }, + "peerDependencies": { + "@shopify/shopify-api": "^11.0.0 || ^12.0.0 || ^13.0.0", + "@shopify/shopify-app-react-router": "^1.0.0" + }, + "peerDependenciesMeta": { + "@shopify/shopify-api": { + "optional": true + }, + "@shopify/shopify-app-react-router": { + "optional": true + } + }, "dependencies": { "body-parser": "^1.20.2", "chalk": "^4.1.2", "commander": "^11.1.0", "cors": "^2.8.5", "express": "^4.18.2", - "jsonwebtoken": "^9.0.2", "http-proxy-middleware": "^3.0.3", + "jsonwebtoken": "^9.0.2", "uuid": "^9.0.1" }, "devDependencies": { + "@shopify/shopify-api": "^13.0.0", + "@shopify/shopify-app-react-router": "^1.2.0", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jsonwebtoken": "^9.0.5", diff --git a/src/react-router/index.ts b/src/react-router/index.ts new file mode 100644 index 0000000..6c57118 --- /dev/null +++ b/src/react-router/index.ts @@ -0,0 +1,6 @@ +export { + withMockBridgeAdminAuthForReactRouter, + type MockBridgeAuthReflectConfig, +} from './withMockBridgeAdminAuthForReactRouter'; + +export type { MockBridgeAuthReflectConfig as MockBridgeReactRouterAuthConfig } from './withMockBridgeAdminAuthForReactRouter'; diff --git a/src/react-router/withMockBridgeAdminAuthForReactRouter.ts b/src/react-router/withMockBridgeAdminAuthForReactRouter.ts new file mode 100644 index 0000000..7779e19 --- /dev/null +++ b/src/react-router/withMockBridgeAdminAuthForReactRouter.ts @@ -0,0 +1,107 @@ +import { shopifyApi, Session, type ApiVersion } from '@shopify/shopify-api'; +import type { shopifyApp } from '@shopify/shopify-app-react-router/server'; + +type ShopifyAppInstance = ReturnType; + +export type MockBridgeAuthReflectConfig = { + apiKey: string | undefined; + apiSecretKey: string; + apiVersion: ApiVersion; + scopes: string[] | undefined; + appUrl: string; + useOnlineTokens?: boolean; + customShopDomains?: string[]; +}; + +function sessionTokenFromRequest(request: Request): string | undefined { + const auth = request.headers.get('authorization'); + if (auth?.startsWith('Bearer ')) return auth.slice(7).trim(); + const url = new URL(request.url); + return url.searchParams.get('id_token')?.trim() || undefined; +} + +function buildDecodeApi(config: MockBridgeAuthReflectConfig) { + const appUrl = new URL(config.appUrl); + const rawScheme = appUrl.protocol.replace(':', '') || 'https'; + const hostScheme = rawScheme === 'http' ? 'http' : 'https'; + return shopifyApi({ + apiKey: config.apiKey || '', + apiSecretKey: config.apiSecretKey, + apiVersion: config.apiVersion, + scopes: config.scopes ?? [], + hostName: appUrl.host, + hostScheme, + isEmbeddedApp: true, + ...(config.customShopDomains?.length + ? { customShopDomains: config.customShopDomains } + : {}), + }); +} + +/** + * When SHOPIFY_MOCK_BRIDGE_AUTH=1, wraps authenticate.admin only; webhook and other methods unchanged. + */ +export function withMockBridgeAdminAuthForReactRouter( + shopify: ShopifyAppInstance, + reflectConfig: MockBridgeAuthReflectConfig, +): ShopifyAppInstance['authenticate'] { + const baseAdmin = shopify.authenticate.admin.bind(shopify.authenticate); + const storage = shopify.sessionStorage; + + if (!storage) { + return shopify.authenticate; + } + + const decodeApi = buildDecodeApi(reflectConfig); + const scopeString = (reflectConfig.scopes ?? []).filter(Boolean).join(','); + + return { + ...shopify.authenticate, + admin: async (request: Request) => { + if (process.env.SHOPIFY_MOCK_BRIDGE_AUTH !== '1') { + return baseAdmin(request); + } + + const token = sessionTokenFromRequest(request); + if (!token || !reflectConfig.apiSecretKey) { + return baseAdmin(request); + } + + let payload: Awaited< + ReturnType + >; + try { + payload = await decodeApi.session.decodeSessionToken(token); + } catch { + return baseAdmin(request); + } + + const dest = payload.dest; + if (typeof dest !== 'string' || !payload.sub) { + return baseAdmin(request); + } + + const shop = new URL(dest).hostname; + const sessionId = reflectConfig.useOnlineTokens + ? decodeApi.session.getJwtSessionId(shop, String(payload.sub)) + : decodeApi.session.getOfflineId(shop); + + const existing = await storage.loadSession(sessionId); + const withinMs = 5 * 60 * 1000; + if (!existing?.isActive(undefined, withinMs)) { + const mockSession = new Session({ + id: sessionId, + shop, + state: 'mock-bridge-e2e', + isOnline: Boolean(reflectConfig.useOnlineTokens), + scope: scopeString, + accessToken: + process.env.SHOPIFY_MOCK_BRIDGE_ACCESS_TOKEN ?? 'mock-access-token', + }); + await storage.storeSession(mockSession); + } + + return baseAdmin(request); + }, + } as ShopifyAppInstance['authenticate']; +} diff --git a/tsconfig.json b/tsconfig.json index f007184..00a5435 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,16 @@ "declarationMap": true, "sourceMap": true, "resolveJsonModule": true, - "moduleResolution": "node" + "moduleResolution": "node", + "baseUrl": ".", + "paths": { + "@shopify/shopify-app-react-router/server": [ + "./node_modules/@shopify/shopify-app-react-router/dist/ts/server/index.d.ts" + ], + "@shopify/shopify-api/adapters/web-api": [ + "./node_modules/@shopify/shopify-api/dist/ts/adapters/web-api/index.d.ts" + ] + } }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "**/*.test.ts"] From 07ea1bab0073cb01edede26bd1c67351709efff5 Mon Sep 17 00:00:00 2001 From: fazlulkabir34 Date: Wed, 25 Mar 2026 03:06:22 +0600 Subject: [PATCH 02/21] refactor: standardize string quotes and improve code consistency in withMockBridgeAdminAuthForReactRouter --- .../withMockBridgeAdminAuthForReactRouter.ts | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/react-router/withMockBridgeAdminAuthForReactRouter.ts b/src/react-router/withMockBridgeAdminAuthForReactRouter.ts index 7779e19..de94b20 100644 --- a/src/react-router/withMockBridgeAdminAuthForReactRouter.ts +++ b/src/react-router/withMockBridgeAdminAuthForReactRouter.ts @@ -1,5 +1,5 @@ -import { shopifyApi, Session, type ApiVersion } from '@shopify/shopify-api'; -import type { shopifyApp } from '@shopify/shopify-app-react-router/server'; +import { shopifyApi, Session, type ApiVersion } from "@shopify/shopify-api"; +import type { shopifyApp } from "@shopify/shopify-app-react-router/server"; type ShopifyAppInstance = ReturnType; @@ -14,18 +14,18 @@ export type MockBridgeAuthReflectConfig = { }; function sessionTokenFromRequest(request: Request): string | undefined { - const auth = request.headers.get('authorization'); - if (auth?.startsWith('Bearer ')) return auth.slice(7).trim(); + const auth = request.headers.get("authorization"); + if (auth?.startsWith("Bearer ")) return auth.slice(7).trim(); const url = new URL(request.url); - return url.searchParams.get('id_token')?.trim() || undefined; + return url.searchParams.get("id_token")?.trim() || undefined; } function buildDecodeApi(config: MockBridgeAuthReflectConfig) { const appUrl = new URL(config.appUrl); - const rawScheme = appUrl.protocol.replace(':', '') || 'https'; - const hostScheme = rawScheme === 'http' ? 'http' : 'https'; + const rawScheme = appUrl.protocol.replace(":", "") || "https"; + const hostScheme = rawScheme === "http" ? "http" : "https"; return shopifyApi({ - apiKey: config.apiKey || '', + apiKey: config.apiKey || "", apiSecretKey: config.apiSecretKey, apiVersion: config.apiVersion, scopes: config.scopes ?? [], @@ -44,7 +44,7 @@ function buildDecodeApi(config: MockBridgeAuthReflectConfig) { export function withMockBridgeAdminAuthForReactRouter( shopify: ShopifyAppInstance, reflectConfig: MockBridgeAuthReflectConfig, -): ShopifyAppInstance['authenticate'] { +): ShopifyAppInstance["authenticate"] { const baseAdmin = shopify.authenticate.admin.bind(shopify.authenticate); const storage = shopify.sessionStorage; @@ -53,12 +53,12 @@ export function withMockBridgeAdminAuthForReactRouter( } const decodeApi = buildDecodeApi(reflectConfig); - const scopeString = (reflectConfig.scopes ?? []).filter(Boolean).join(','); + const scopeString = (reflectConfig.scopes ?? []).filter(Boolean).join(","); return { ...shopify.authenticate, admin: async (request: Request) => { - if (process.env.SHOPIFY_MOCK_BRIDGE_AUTH !== '1') { + if (process.env.SHOPIFY_MOCK_BRIDGE_AUTH !== "1") { return baseAdmin(request); } @@ -77,7 +77,7 @@ export function withMockBridgeAdminAuthForReactRouter( } const dest = payload.dest; - if (typeof dest !== 'string' || !payload.sub) { + if (typeof dest !== "string" || !payload.sub) { return baseAdmin(request); } @@ -92,16 +92,16 @@ export function withMockBridgeAdminAuthForReactRouter( const mockSession = new Session({ id: sessionId, shop, - state: 'mock-bridge-e2e', + state: "mock-bridge-e2e", isOnline: Boolean(reflectConfig.useOnlineTokens), scope: scopeString, accessToken: - process.env.SHOPIFY_MOCK_BRIDGE_ACCESS_TOKEN ?? 'mock-access-token', + process.env.SHOPIFY_MOCK_BRIDGE_ACCESS_TOKEN ?? "mock-access-token", }); await storage.storeSession(mockSession); } return baseAdmin(request); }, - } as ShopifyAppInstance['authenticate']; + } as ShopifyAppInstance["authenticate"]; } From a0408fae21bf4e08dbf711952ee9d29941a369cf Mon Sep 17 00:00:00 2001 From: fazlulkabir34 Date: Wed, 25 Mar 2026 03:52:10 +0600 Subject: [PATCH 03/21] feat: add withMockBridgeAdminAuthForReactRouter helper and update package exports - Introduced withMockBridgeAdminAuthForReactRouter for enhanced admin authentication in React Router apps. - Updated package.json to remove deprecated react-router export and streamline exports. - Enhanced documentation to reflect changes in integration instructions for Shopify App React Router. - Adjusted type exports for better clarity and consistency. --- dist/auth/index.d.ts | 2 ++ dist/auth/index.d.ts.map | 2 +- dist/auth/index.js | 4 ++- dist/auth/index.js.map | 2 +- docs/BACKEND_INTEGRATION.md | 2 +- docs/SHOPIFY_APP_REACT_ROUTER.md | 29 ++++++++++--------- package.json | 7 +---- src/auth/index.ts | 9 +++++- .../withMockBridgeAdminAuthForReactRouter.ts | 19 +++++++++++- src/react-router/index.ts | 6 ---- 10 files changed, 51 insertions(+), 31 deletions(-) rename src/{react-router => auth}/withMockBridgeAdminAuthForReactRouter.ts (88%) delete mode 100644 src/react-router/index.ts diff --git a/dist/auth/index.d.ts b/dist/auth/index.d.ts index 9ebe2d5..117f950 100644 --- a/dist/auth/index.d.ts +++ b/dist/auth/index.d.ts @@ -4,4 +4,6 @@ export { STANDARD_MOCK_SECRET, STANDARD_MOCK_SHOP, STANDARD_MOCK_USER_ID } from export { createMockUser, createMockShopifyUser, type MockCurrentUser, type MockUserOptions } from './createMockUser'; export { withMockTokenSupport, withMockTokenMiddleware, type MockTokenHandlerOptions } from './withMockTokenSupport'; export { type SessionTokenPayload, type TokenGeneratorOptions } from '../auth/token-generator'; +export { withMockBridgeAdminAuthForReactRouter, type MockBridgeAuthReflectConfig } from './withMockBridgeAdminAuthForReactRouter'; +export type { MockBridgeAuthReflectConfig as MockBridgeReactRouterAuthConfig } from './withMockBridgeAdminAuthForReactRouter'; //# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/auth/index.d.ts.map b/dist/auth/index.d.ts.map index 3faa9b4..ddcc8d3 100644 --- a/dist/auth/index.d.ts.map +++ b/dist/auth/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,oBAAoB,EACpB,KAAK,UAAU,EACf,KAAK,oBAAoB,EAC1B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,WAAW,EACX,sBAAsB,EACvB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,KAAK,uBAAuB,EAC7B,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,oBAAoB,EACpB,KAAK,UAAU,EACf,KAAK,oBAAoB,EAC1B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,WAAW,EACX,sBAAsB,EACvB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,oBAAoB,EACpB,kBAAkB,EAClB,qBAAqB,EACtB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,cAAc,EACd,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,KAAK,uBAAuB,EAC7B,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC3B,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,qCAAqC,EACrC,KAAK,2BAA2B,EACjC,MAAM,yCAAyC,CAAC;AAEjD,YAAY,EAAE,2BAA2B,IAAI,+BAA+B,EAAE,MAAM,yCAAyC,CAAC"} \ No newline at end of file diff --git a/dist/auth/index.js b/dist/auth/index.js index db2b29a..098725e 100644 --- a/dist/auth/index.js +++ b/dist/auth/index.js @@ -1,6 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.withMockTokenMiddleware = exports.withMockTokenSupport = exports.createMockShopifyUser = exports.createMockUser = exports.STANDARD_MOCK_USER_ID = exports.STANDARD_MOCK_SHOP = exports.STANDARD_MOCK_SECRET = exports.shouldEnableMockTokens = exports.isMockToken = exports.validateSessionToken = void 0; +exports.withMockBridgeAdminAuthForReactRouter = exports.withMockTokenMiddleware = exports.withMockTokenSupport = exports.createMockShopifyUser = exports.createMockUser = exports.STANDARD_MOCK_USER_ID = exports.STANDARD_MOCK_SHOP = exports.STANDARD_MOCK_SECRET = exports.shouldEnableMockTokens = exports.isMockToken = exports.validateSessionToken = void 0; // Authentication utilities for backend integration var validateSessionToken_1 = require("./validateSessionToken"); Object.defineProperty(exports, "validateSessionToken", { enumerable: true, get: function () { return validateSessionToken_1.validateSessionToken; } }); @@ -17,4 +17,6 @@ Object.defineProperty(exports, "createMockShopifyUser", { enumerable: true, get: var withMockTokenSupport_1 = require("./withMockTokenSupport"); Object.defineProperty(exports, "withMockTokenSupport", { enumerable: true, get: function () { return withMockTokenSupport_1.withMockTokenSupport; } }); Object.defineProperty(exports, "withMockTokenMiddleware", { enumerable: true, get: function () { return withMockTokenSupport_1.withMockTokenMiddleware; } }); +var withMockBridgeAdminAuthForReactRouter_1 = require("./withMockBridgeAdminAuthForReactRouter"); +Object.defineProperty(exports, "withMockBridgeAdminAuthForReactRouter", { enumerable: true, get: function () { return withMockBridgeAdminAuthForReactRouter_1.withMockBridgeAdminAuthForReactRouter; } }); //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/auth/index.js.map b/dist/auth/index.js.map index 7cd881e..7bde9a0 100644 --- a/dist/auth/index.js.map +++ b/dist/auth/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":";;;AAAA,mDAAmD;AACnD,+DAIgC;AAH9B,4HAAA,oBAAoB,OAAA;AAKtB,6CAGuB;AAFrB,0GAAA,WAAW,OAAA;AACX,qHAAA,sBAAsB,OAAA;AAGxB,yCAIqB;AAHnB,iHAAA,oBAAoB,OAAA;AACpB,+GAAA,kBAAkB,OAAA;AAClB,kHAAA,qBAAqB,OAAA;AAGvB,mDAK0B;AAJxB,gHAAA,cAAc,OAAA;AACd,uHAAA,qBAAqB,OAAA;AAKvB,+DAIgC;AAH9B,4HAAA,oBAAoB,OAAA;AACpB,+HAAA,uBAAuB,OAAA"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":";;;AAAA,mDAAmD;AACnD,+DAIgC;AAH9B,4HAAA,oBAAoB,OAAA;AAKtB,6CAGuB;AAFrB,0GAAA,WAAW,OAAA;AACX,qHAAA,sBAAsB,OAAA;AAGxB,yCAIqB;AAHnB,iHAAA,oBAAoB,OAAA;AACpB,+GAAA,kBAAkB,OAAA;AAClB,kHAAA,qBAAqB,OAAA;AAGvB,mDAK0B;AAJxB,gHAAA,cAAc,OAAA;AACd,uHAAA,qBAAqB,OAAA;AAKvB,+DAIgC;AAH9B,4HAAA,oBAAoB,OAAA;AACpB,+HAAA,uBAAuB,OAAA;AAUzB,iGAGiD;AAF/C,8JAAA,qCAAqC,OAAA"} \ No newline at end of file diff --git a/docs/BACKEND_INTEGRATION.md b/docs/BACKEND_INTEGRATION.md index 8a59dee..7279bb1 100644 --- a/docs/BACKEND_INTEGRATION.md +++ b/docs/BACKEND_INTEGRATION.md @@ -4,7 +4,7 @@ This guide explains how to integrate mock session token support into any Shopify ## Shopify App React Router -If you use `@shopify/shopify-app-react-router`, authentication is exposed as **`shopify.authenticate.admin(request)`** (and other `authenticate.*` methods), not as a `(token: string) => …` function. The **`withMockTokenSupport`** helper in this package only wraps **token-string** APIs; it does not replace or wrap `authenticate.admin`. For Mock Bridge in React Router apps, use **`withMockBridgeAdminAuthForReactRouter`** from `@getverdict/mock-bridge/react-router` and follow **[Shopify App React Router](./SHOPIFY_APP_REACT_ROUTER.md)**. +If you use `@shopify/shopify-app-react-router`, authentication is exposed as **`shopify.authenticate.admin(request)`** (and other `authenticate.*` methods), not as a `(token: string) => …` function. The **`withMockTokenSupport`** helper in this package only wraps **token-string** APIs; it does not replace or wrap `authenticate.admin`. For Mock Bridge in React Router apps, use **`withMockBridgeAdminAuthForReactRouter`** from `@getverdict/mock-bridge/auth` and follow **[Shopify App React Router](./SHOPIFY_APP_REACT_ROUTER.md)**. ## Table of Contents diff --git a/docs/SHOPIFY_APP_REACT_ROUTER.md b/docs/SHOPIFY_APP_REACT_ROUTER.md index a44b318..d222323 100644 --- a/docs/SHOPIFY_APP_REACT_ROUTER.md +++ b/docs/SHOPIFY_APP_REACT_ROUTER.md @@ -8,14 +8,14 @@ Use this guide if your app is built with [`@shopify/shopify-app-react-router`](h ## Solution -When `SHOPIFY_MOCK_BRIDGE_AUTH=1`, wrap **only** `authenticate.admin` with `withMockBridgeAdminAuthForReactRouter` from `@getverdict/mock-bridge/react-router`. It decodes the session token using `@shopify/shopify-api` (same audience checks as the framework), seeds a minimal offline or online `Session` if needed, then calls the real `shopify.authenticate.admin(request)`. Webhooks and other `authenticate.*` methods are unchanged. +When `SHOPIFY_MOCK_BRIDGE_AUTH=1`, wrap **only** `authenticate.admin` with `withMockBridgeAdminAuthForReactRouter` from `@getverdict/mock-bridge/auth`. It decodes the session token using `@shopify/shopify-api` (same audience checks as the framework), seeds a minimal offline or online `Session` if needed, then calls the real `shopify.authenticate.admin(request)`. Webhooks and other `authenticate.*` methods are unchanged. ## Environment variables -| Variable | When | Purpose | -| -------- | ---- | ------- | -| `SHOPIFY_MOCK_BRIDGE_AUTH` | E2E / CI only | Must be `"1"` for the wrapper to seed a session before delegating to `authenticate.admin`. | -| `SHOPIFY_MOCK_BRIDGE_ACCESS_TOKEN` | Optional | If set, stored on the seeded session as `accessToken` so tests that call the real Admin API can use a valid token. | +| Variable | When | Purpose | +| ---------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------ | +| `SHOPIFY_MOCK_BRIDGE_AUTH` | E2E / CI only | Must be `"1"` for the wrapper to seed a session before delegating to `authenticate.admin`. | +| `SHOPIFY_MOCK_BRIDGE_ACCESS_TOKEN` | Optional | If set, stored on the seeded session as `accessToken` so tests that call the real Admin API can use a valid token. | **Never set `SHOPIFY_MOCK_BRIDGE_AUTH` in production.** It bypasses normal session acquisition for admin requests when enabled. @@ -28,28 +28,31 @@ When `SHOPIFY_MOCK_BRIDGE_AUTH=1`, wrap **only** `authenticate.admin` with `with After `const shopify = shopifyApp({ ... })`, export `authenticate` like this (mirror the same `apiKey`, `apiSecretKey`, `apiVersion`, `scopes`, `appUrl`, and `customShopDomains` as `shopifyApp`): ```typescript -import { shopifyApp, ApiVersion } from "@shopify/shopify-app-react-router/server"; -import { withMockBridgeAdminAuthForReactRouter } from "@getverdict/mock-bridge/react-router"; +import { + shopifyApp, + ApiVersion +} from '@shopify/shopify-app-react-router/server'; +import { withMockBridgeAdminAuthForReactRouter } from '@getverdict/mock-bridge/auth'; const shopify = shopifyApp({ apiKey: process.env.SHOPIFY_API_KEY!, apiSecretKey: process.env.SHOPIFY_API_SECRET!, apiVersion: ApiVersion.October25, - scopes: process.env.SCOPES?.split(",") ?? [], - appUrl: process.env.SHOPIFY_APP_URL!, + scopes: process.env.SCOPES?.split(',') ?? [], + appUrl: process.env.SHOPIFY_APP_URL! // sessionStorage, authPathPrefix, etc. }); export default shopify; export const authenticate = - process.env.SHOPIFY_MOCK_BRIDGE_AUTH === "1" + process.env.SHOPIFY_MOCK_BRIDGE_AUTH === '1' ? withMockBridgeAdminAuthForReactRouter(shopify, { apiKey: process.env.SHOPIFY_API_KEY, apiSecretKey: process.env.SHOPIFY_API_SECRET!, apiVersion: ApiVersion.October25, - scopes: process.env.SCOPES?.split(",") ?? [], - appUrl: process.env.SHOPIFY_APP_URL!, + scopes: process.env.SCOPES?.split(',') ?? [], + appUrl: process.env.SHOPIFY_APP_URL! }) : shopify.authenticate; ``` @@ -64,7 +67,7 @@ Point embedded App Bridge at your mock server and relax CSP for the mock admin o ## Runtime dependency -The `@getverdict/mock-bridge/react-router` implementation loads **`@shopify/shopify-api` only** at runtime (for `shopifyApi`, `Session`, and `decodeSessionToken`). Your app must still install **`@shopify/shopify-app-react-router`** and peers (`react`, `react-dom`, `react-router`) as required by Shopify’s template. +The `withMockBridgeAdminAuthForReactRouter` helper (exported from `@getverdict/mock-bridge/auth`) loads **`@shopify/shopify-api` only** at runtime (for `shopifyApi`, `Session`, and `decodeSessionToken`). Your app must still install **`@shopify/shopify-app-react-router`** and peers (`react`, `react-dom`, `react-router`) as required by Shopify’s template. ## Install from npm or Git diff --git a/package.json b/package.json index fbeccaf..4fef9fc 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,6 @@ "types": "./dist/auth/index.d.ts", "default": "./dist/auth/index.js" }, - "./react-router": { - "types": "./dist/react-router/index.d.ts", - "default": "./dist/react-router/index.js" - }, "./client": { "types": "./dist/client/mock-detector.d.ts", "default": "./dist/client/mock-detector.js" @@ -35,8 +31,7 @@ "start": "node dist/server/index.js", "watch": "tsc --watch", "clean": "rm -rf dist", - "prepare": "cd admin-frame && npm install --legacy-peer-deps && cd ../app-bridge && npm install && cd .. && npm run build", - "smoke:subpaths": "node -e \"require('./dist/auth'); require('./dist/react-router'); console.log('subpath require ok');\"" + "prepare": "cd admin-frame && npm install --legacy-peer-deps && cd ../app-bridge && npm install && cd .. && npm run build" }, "keywords": [ "shopify", diff --git a/src/auth/index.ts b/src/auth/index.ts index 3863d2f..d4abe33 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -33,4 +33,11 @@ export { export { type SessionTokenPayload, type TokenGeneratorOptions -} from '../auth/token-generator'; \ No newline at end of file +} from '../auth/token-generator'; + +export { + withMockBridgeAdminAuthForReactRouter, + type MockBridgeAuthReflectConfig +} from './withMockBridgeAdminAuthForReactRouter'; + +export type { MockBridgeAuthReflectConfig as MockBridgeReactRouterAuthConfig } from './withMockBridgeAdminAuthForReactRouter'; diff --git a/src/react-router/withMockBridgeAdminAuthForReactRouter.ts b/src/auth/withMockBridgeAdminAuthForReactRouter.ts similarity index 88% rename from src/react-router/withMockBridgeAdminAuthForReactRouter.ts rename to src/auth/withMockBridgeAdminAuthForReactRouter.ts index de94b20..10f4016 100644 --- a/src/react-router/withMockBridgeAdminAuthForReactRouter.ts +++ b/src/auth/withMockBridgeAdminAuthForReactRouter.ts @@ -20,6 +20,19 @@ function sessionTokenFromRequest(request: Request): string | undefined { return url.searchParams.get("id_token")?.trim() || undefined; } +/** Resolves shop hostname from JWT `dest` (full URL or bare hostname). */ +function shopHostnameFromDest(dest: string): string | undefined { + try { + return new URL(dest).hostname; + } catch { + try { + return new URL(`https://${dest}`).hostname; + } catch { + return undefined; + } + } +} + function buildDecodeApi(config: MockBridgeAuthReflectConfig) { const appUrl = new URL(config.appUrl); const rawScheme = appUrl.protocol.replace(":", "") || "https"; @@ -81,7 +94,11 @@ export function withMockBridgeAdminAuthForReactRouter( return baseAdmin(request); } - const shop = new URL(dest).hostname; + const shop = shopHostnameFromDest(dest.trim()); + if (!shop) { + return baseAdmin(request); + } + const sessionId = reflectConfig.useOnlineTokens ? decodeApi.session.getJwtSessionId(shop, String(payload.sub)) : decodeApi.session.getOfflineId(shop); diff --git a/src/react-router/index.ts b/src/react-router/index.ts deleted file mode 100644 index 6c57118..0000000 --- a/src/react-router/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { - withMockBridgeAdminAuthForReactRouter, - type MockBridgeAuthReflectConfig, -} from './withMockBridgeAdminAuthForReactRouter'; - -export type { MockBridgeAuthReflectConfig as MockBridgeReactRouterAuthConfig } from './withMockBridgeAdminAuthForReactRouter'; From ca4eab23efe9700ca6ef1b89f8cdfc78a74cfcdb Mon Sep 17 00:00:00 2001 From: fazlulkabir34 Date: Wed, 25 Mar 2026 04:03:31 +0600 Subject: [PATCH 04/21] Update README.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 00dff60..b613955 100644 --- a/README.md +++ b/README.md @@ -307,8 +307,7 @@ export async function authenticate(token: string) { #### Framework-Specific Examples -**Shopify App React Router** (`authenticate.admin(request)`): see the dedicated guide **[docs/SHOPIFY_APP_REACT_ROUTER.md](./docs/SHOPIFY_APP_REACT_ROUTER.md)** and `@getverdict/mock-bridge/react-router`. - +**Shopify App React Router** (`authenticate.admin(request)`): see the dedicated guide **[docs/SHOPIFY_APP_REACT_ROUTER.md](./docs/SHOPIFY_APP_REACT_ROUTER.md)** and `@getverdict/mock-bridge/auth`. **Next.js API Routes:** ```typescript From 03f381550fb454c4bef9d4645cd06e4bb658e352 Mon Sep 17 00:00:00 2001 From: fazlulkabir34 Date: Wed, 25 Mar 2026 04:05:24 +0600 Subject: [PATCH 05/21] Update Shopify App React Router documentation Removed redundant mention of `@getverdict/mock-bridge/auth` in the Shopify App React Router section. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b613955..5918548 100644 --- a/README.md +++ b/README.md @@ -307,7 +307,7 @@ export async function authenticate(token: string) { #### Framework-Specific Examples -**Shopify App React Router** (`authenticate.admin(request)`): see the dedicated guide **[docs/SHOPIFY_APP_REACT_ROUTER.md](./docs/SHOPIFY_APP_REACT_ROUTER.md)** and `@getverdict/mock-bridge/auth`. +**Shopify App React Router** (`authenticate.admin(request)`): see the dedicated guide **[docs/SHOPIFY_APP_REACT_ROUTER.md](./docs/SHOPIFY_APP_REACT_ROUTER.md)**. **Next.js API Routes:** ```typescript From ee12a942960c840d99cefc20d1e10ae2768b5a5f Mon Sep 17 00:00:00 2001 From: fazlulkabir34 Date: Wed, 25 Mar 2026 04:07:08 +0600 Subject: [PATCH 06/21] Update tsconfig.json Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- tsconfig.json | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/tsconfig.json b/tsconfig.json index 00a5435..8b2582e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,16 +13,7 @@ "declarationMap": true, "sourceMap": true, "resolveJsonModule": true, - "moduleResolution": "node", - "baseUrl": ".", - "paths": { - "@shopify/shopify-app-react-router/server": [ - "./node_modules/@shopify/shopify-app-react-router/dist/ts/server/index.d.ts" - ], - "@shopify/shopify-api/adapters/web-api": [ - "./node_modules/@shopify/shopify-api/dist/ts/adapters/web-api/index.d.ts" - ] - } + "moduleResolution": "node16" }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "**/*.test.ts"] From 1871614a9e1838650eb94274117e0e34a6d19e54 Mon Sep 17 00:00:00 2001 From: fazlulkabir34 Date: Wed, 25 Mar 2026 05:06:29 +0600 Subject: [PATCH 07/21] Updated dist files --- app-bridge/dist/index.js | 1076 +++++++++++++++++ ...withMockBridgeAdminAuthForReactRouter.d.ts | 18 + ...MockBridgeAdminAuthForReactRouter.d.ts.map | 1 + .../withMockBridgeAdminAuthForReactRouter.js | 99 ++ ...thMockBridgeAdminAuthForReactRouter.js.map | 1 + dist/server/dev.d.ts | 2 + dist/server/dev.d.ts.map | 1 + dist/server/dev.js | 35 + dist/server/dev.js.map | 1 + 9 files changed, 1234 insertions(+) create mode 100644 app-bridge/dist/index.js create mode 100644 dist/auth/withMockBridgeAdminAuthForReactRouter.d.ts create mode 100644 dist/auth/withMockBridgeAdminAuthForReactRouter.d.ts.map create mode 100644 dist/auth/withMockBridgeAdminAuthForReactRouter.js create mode 100644 dist/auth/withMockBridgeAdminAuthForReactRouter.js.map create mode 100644 dist/server/dev.d.ts create mode 100644 dist/server/dev.d.ts.map create mode 100644 dist/server/dev.js create mode 100644 dist/server/dev.js.map diff --git a/app-bridge/dist/index.js b/app-bridge/dist/index.js new file mode 100644 index 0000000..6030a9c --- /dev/null +++ b/app-bridge/dist/index.js @@ -0,0 +1,1076 @@ +"use strict"; +(() => { + // ../node_modules/uuid/dist/esm-browser/rng.js + var getRandomValues; + var rnds8 = new Uint8Array(16); + function rng() { + if (!getRandomValues) { + getRandomValues = typeof crypto !== "undefined" && crypto.getRandomValues && crypto.getRandomValues.bind(crypto); + if (!getRandomValues) { + throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported"); + } + } + return getRandomValues(rnds8); + } + + // ../node_modules/uuid/dist/esm-browser/stringify.js + var byteToHex = []; + for (let i = 0; i < 256; ++i) { + byteToHex.push((i + 256).toString(16).slice(1)); + } + function unsafeStringify(arr, offset = 0) { + return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]; + } + + // ../node_modules/uuid/dist/esm-browser/native.js + var randomUUID = typeof crypto !== "undefined" && crypto.randomUUID && crypto.randomUUID.bind(crypto); + var native_default = { + randomUUID + }; + + // ../node_modules/uuid/dist/esm-browser/v4.js + function v4(options, buf, offset) { + if (native_default.randomUUID && !buf && !options) { + return native_default.randomUUID(); + } + options = options || {}; + const rnds = options.random || (options.rng || rng)(); + rnds[6] = rnds[6] & 15 | 64; + rnds[8] = rnds[8] & 63 | 128; + if (buf) { + offset = offset || 0; + for (let i = 0; i < 16; ++i) { + buf[offset + i] = rnds[i]; + } + return buf; + } + return unsafeStringify(rnds); + } + var v4_default = v4; + + // src/invokeFeature.ts + function invokeFeature(feature, action, payload) { + return new Promise((resolve, reject) => { + const actionId = v4_default(); + const request = { + feature, + action, + payload + }; + window.parent.postMessage({ + type: "FEATURE_ACTION_REQUEST", + action_id: actionId, + ...request + }, "*"); + const rejectTimeout = setTimeout(() => { + reject(new Error("Feature action timed out after 1 second")); + }, 1e3); + const handler = (event) => { + if (event.data && event.data.type === "FEATURE_ACTION_RESPONSE") { + if (event.data.action_id !== actionId) return; + resolve(event.data.payload); + window.removeEventListener("message", handler); + clearTimeout(rejectTimeout); + } + }; + window.addEventListener("message", handler); + }); + } + + // src/features/modal.ts + function attachModalContentObserver(modalElement, propagate) { + const contentElement = document.createElement("div"); + modalElement.content = contentElement; + const observer = new MutationObserver((mutations) => { + propagate(contentElement.innerHTML); + }); + observer.observe(contentElement, { + childList: true, + subtree: true + }); + } + function observeModalElements() { + function extractModalData(modalElement) { + const id = modalElement.getAttribute("id"); + if (!id) return null; + const variant = modalElement.getAttribute("variant") || "base"; + const src = modalElement.getAttribute("src"); + const titleBar = modalElement.querySelector("ui-title-bar"); + const title = titleBar?.getAttribute("title") || ""; + const buttons = titleBar ? Array.from(titleBar.querySelectorAll("button")).map((btn) => ({ + id: btn.getAttribute("id") || "", + label: btn.textContent || "", + variant: btn.getAttribute("variant") || void 0, + tone: btn.getAttribute("tone") || void 0, + disabled: btn.disabled, + loading: btn.hasAttribute("loading") + })) : []; + return { + id, + title, + variant, + src, + buttons + }; + } + function extractModalHtml(modalElement) { + const clone = modalElement.cloneNode(true); + const titleBar = clone.querySelector("ui-title-bar"); + if (titleBar) { + titleBar.remove(); + } + return clone.innerHTML.trim(); + } + async function syncModalToParent(modalElement) { + const data = extractModalData(modalElement); + if (data) { + await invokeFeature("modal", "update", { + id: data.id, + heading: data.title, + content: data + }); + const html = extractModalHtml(modalElement); + if (html) { + await invokeFeature("modal", "updateHtml", { + id: data.id, + html + }); + } + } + } + function observeModals() { + const modals = document.querySelectorAll("ui-modal"); + modals.forEach((modal2) => { + modal2.style.display = "none"; + syncModalToParent(modal2); + attachModalContentObserver(modal2, (html) => { + const id2 = modal2.getAttribute("id"); + if (!id2) return; + invokeFeature("modal", "updateHtml", { + id: id2, + html + }); + }); + const id = modal2.getAttribute("id"); + if (!id) return; + modal2.show = () => { + invokeFeature("modal", "show", { id }); + }; + modal2.hide = () => { + invokeFeature("modal", "hide", { id }); + }; + modal2.toggle = () => { + invokeFeature("modal", "toggle", { id }); + }; + }); + } + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", observeModals); + } else { + observeModals(); + } + const documentObserver = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node instanceof HTMLElement && node.tagName.toLowerCase() === "ui-modal") { + syncModalToParent(node); + } + }); + }); + }); + documentObserver.observe(document.documentElement, { + childList: true, + subtree: true + }); + } + function modal() { + if (typeof document !== "undefined") { + observeModalElements(); + } + return { + toggle: async (id) => { + await invokeFeature("modal", "toggle", { id }); + }, + show: async (id) => { + await invokeFeature("modal", "show", { id }); + }, + hide: async (id) => { + await invokeFeature("modal", "hide", { id }); + } + }; + } + + // src/features/save-bar.ts + function observeSaveBarElements() { + function setupSaveBar(element) { + const id = element.getAttribute("id"); + if (!id) return; + element.style.display = "none"; + const discardConfirmation = element.hasAttribute("data-discard-confirmation"); + invokeFeature("saveBar", "update", { id, discardConfirmation }); + element.show = () => { + invokeFeature("saveBar", "show", { id }); + }; + element.hide = () => { + invokeFeature("saveBar", "hide", { id }); + }; + } + function setupFormSaveBar(form) { + const id = `form-save-bar-${Date.now()}`; + const discardConfirmation = form.hasAttribute("data-discard-confirmation"); + let isDirty = false; + const originalValues = /* @__PURE__ */ new Map(); + const inputs = form.querySelectorAll("input, textarea, select"); + inputs.forEach((input) => { + const el = input; + originalValues.set(el.name || el.id, el.value); + }); + form.addEventListener("input", () => { + let hasChanges = false; + inputs.forEach((input) => { + const el = input; + const key = el.name || el.id; + if (originalValues.get(key) !== el.value) { + hasChanges = true; + } + }); + if (hasChanges && !isDirty) { + isDirty = true; + invokeFeature("saveBar", "show", { id }); + } else if (!hasChanges && isDirty) { + isDirty = false; + invokeFeature("saveBar", "hide", { id }); + } + }); + window.addEventListener("message", (event) => { + if (event.data?.type === "SAVE_BAR_SAVE" && event.data.id === id) { + form.dispatchEvent(new Event("submit", { bubbles: true, cancelable: true })); + isDirty = false; + invokeFeature("saveBar", "hide", { id }); + } + if (event.data?.type === "SAVE_BAR_DISCARD" && event.data.id === id) { + form.reset(); + isDirty = false; + } + }); + invokeFeature("saveBar", "update", { id, discardConfirmation }); + } + function observeElements() { + document.querySelectorAll("ui-save-bar").forEach((el) => setupSaveBar(el)); + document.querySelectorAll("form[data-save-bar]").forEach((el) => setupFormSaveBar(el)); + } + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", observeElements); + } else { + observeElements(); + } + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node instanceof HTMLElement) { + if (node.tagName.toLowerCase() === "ui-save-bar") { + setupSaveBar(node); + } + if (node.tagName === "FORM" && node.hasAttribute("data-save-bar")) { + setupFormSaveBar(node); + } + } + }); + }); + }); + observer.observe(document.documentElement, { + childList: true, + subtree: true + }); + } + function saveBar() { + if (typeof document !== "undefined") { + observeSaveBarElements(); + } + return { + show: async (id) => { + await invokeFeature("saveBar", "show", { id }); + }, + hide: async (id) => { + await invokeFeature("saveBar", "hide", { id }); + }, + toggle: async (id) => { + await invokeFeature("saveBar", "toggle", { id }); + }, + leaveConfirmation: async () => { + } + }; + } + + // src/features/scopes.ts + function scopes() { + return { + query: async () => { + return { + granted: [], + required: [], + optional: [] + }; + }, + request: async () => { + return { + result: "granted-all", + detail: { + granted: [], + required: [], + optional: [] + } + }; + }, + revoke: async () => { + return { + result: "granted-all", + detail: { + granted: [], + required: [], + optional: [] + } + }; + } + }; + } + + // src/features/config.ts + function config() { + return { + apiKey: "", + shop: "", + locale: "en" + }; + } + + // src/features/environment.ts + function environment() { + return { + embedded: window.self !== window.top, + mobile: /mobile|android|iphone|ipad/i.test(navigator.userAgent), + pos: false + }; + } + + // src/features/user.ts + function user() { + return async () => { + return { + id: 0, + email: "", + name: "" + }; + }; + } + + // src/features/toast.ts + function toast() { + let toastId = 0; + return { + show: (message, opts) => { + const id = `toast-${++toastId}`; + console.log("[MockAppBridge] Toast:", message, opts); + return id; + }, + hide: (id) => { + console.log("[MockAppBridge] Hide toast:", id); + } + }; + } + + // src/features/resource-picker.ts + function resourcePicker() { + return async (options) => { + console.log("[MockAppBridge] Resource picker opened with options:", options); + return Promise.resolve([]); + }; + } + + // src/features/scanner.ts + function scanner() { + return { + capture: async () => { + return { + data: "" + }; + } + }; + } + + // src/features/pos.ts + function pos() { + return { + cart: { + fetch: async () => ({ + subTotal: "0.00", + taxTotal: "0.00", + grandTotal: "0.00", + lineItems: [], + properties: {} + }), + subscribe: (callback) => { + console.log("[MockAppBridge] POS cart subscribed:", callback); + return () => { + }; + }, + setCustomer: async (customer) => { + console.log("[MockAppBridge] POS setCustomer:", customer); + }, + removeCustomer: async () => { + console.log("[MockAppBridge] POS removeCustomer"); + }, + addAddress: async (address) => { + console.log("[MockAppBridge] POS addAddress:", address); + }, + updateAddress: async (index, address) => { + console.log("[MockAppBridge] POS updateAddress:", index, address); + }, + applyCartDiscount: async (type, discountDescription, amount) => { + console.log("[MockAppBridge] POS applyCartDiscount:", type, discountDescription, amount); + }, + applyCartCodeDiscount: async (code) => { + console.log("[MockAppBridge] POS applyCartCodeDiscount:", code); + }, + removeCartDiscount: async () => { + console.log("[MockAppBridge] POS removeCartDiscount"); + }, + removeAllDiscounts: async (disableAutomaticDiscounts) => { + console.log("[MockAppBridge] POS removeAllDiscounts:", disableAutomaticDiscounts); + }, + addCartProperties: async (properties) => { + console.log("[MockAppBridge] POS addCartProperties:", properties); + }, + removeCartProperties: async (keys) => { + console.log("[MockAppBridge] POS removeCartProperties:", keys); + }, + addCustomSale: async (customSale) => { + console.log("[MockAppBridge] POS addCustomSale:", customSale); + }, + clear: async () => { + console.log("[MockAppBridge] POS clear cart"); + }, + addLineItem: async (variantId, quantity) => { + console.log("[MockAppBridge] POS addLineItem:", variantId, quantity); + }, + updateLineItem: async (uuid, quantity) => { + console.log("[MockAppBridge] POS updateLineItem:", uuid, quantity); + }, + removeLineItem: async (uuid) => { + console.log("[MockAppBridge] POS removeLineItem:", uuid); + }, + setLineItemDiscount: async (uuid, type, discountDescription, amount) => { + console.log("[MockAppBridge] POS setLineItemDiscount:", uuid, type, discountDescription, amount); + }, + removeLineItemDiscount: async (uuid) => { + console.log("[MockAppBridge] POS removeLineItemDiscount:", uuid); + }, + addLineItemProperties: async (uuid, properties) => { + console.log("[MockAppBridge] POS addLineItemProperties:", uuid, properties); + }, + removeLineItemProperties: async (uuid, properties) => { + console.log("[MockAppBridge] POS removeLineItemProperties:", uuid, properties); + } + }, + close: async () => { + console.log("[MockAppBridge] POS close"); + }, + device: async () => ({ + name: "", + serialNumber: "" + }), + location: async () => ({ + id: 0, + name: "", + active: true + }) + }; + } + + // src/features/intents.ts + function intents() { + return { + invoke: async (query) => { + console.log("[MockAppBridge] Intent invoked:", query); + return { + complete: Promise.resolve({ code: "ok" }) + }; + }, + register: (callback) => { + console.log("[MockAppBridge] Intent handler registered:", callback); + return () => { + }; + } + }; + } + + // src/features/web-vitals.ts + function webVitals() { + return { + onReport: async (callback) => { + console.log("[MockAppBridge] Web Vitals callback registered:", callback); + } + }; + } + + // src/features/support.ts + function support() { + return { + registerHandler: async (callback) => { + console.log("[MockAppBridge] Support handler registered:", callback); + } + }; + } + + // src/features/reviews.ts + function reviews() { + return { + request: async () => { + console.log("[MockAppBridge] Review requested"); + return { + success: true, + code: "success", + message: "Review modal shown successfully" + }; + } + }; + } + + // src/features/picker.ts + function picker() { + return async (options) => { + console.log("[MockAppBridge] Picker opened with options:", options); + return { + selected: Promise.resolve([]) + }; + }; + } + + // src/features/app.ts + function app() { + return { + extensions: async () => { + console.log("[MockAppBridge] Extensions requested"); + return []; + } + }; + } + + // src/features/loading.ts + function loading() { + return (isLoading) => { + console.log("[MockAppBridge] Loading:", isLoading); + invokeFeature("loading", "setLoading", { isLoading }); + }; + } + + // src/features/nav-menu.ts + function observeNavMenuElements() { + function extractNavItems(navMenu2) { + const items = []; + const links = navMenu2.querySelectorAll("a, ui-link, s-link"); + links.forEach((link) => { + const href = link.getAttribute("href") || "/"; + const label = link.textContent?.trim() || ""; + const isHome = link.getAttribute("rel") === "home"; + if (label) { + items.push({ label, href, isHome }); + } + }); + return items; + } + function setupNavMenu(navMenu2) { + navMenu2.style.display = "none"; + const items = extractNavItems(navMenu2); + if (items.length > 0) { + invokeFeature("navMenu", "setItems", { items }); + } + const observer2 = new MutationObserver(() => { + const updatedItems = extractNavItems(navMenu2); + invokeFeature("navMenu", "setItems", { items: updatedItems }); + }); + observer2.observe(navMenu2, { + childList: true, + subtree: true, + characterData: true + }); + } + function observeElements() { + const selectors = ["ui-nav-menu", "nav-menu", "s-app-nav"]; + selectors.forEach((selector) => { + document.querySelectorAll(selector).forEach((el) => setupNavMenu(el)); + }); + } + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", observeElements); + } else { + observeElements(); + } + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node instanceof HTMLElement) { + const tagName = node.tagName.toLowerCase(); + if (["ui-nav-menu", "nav-menu", "s-app-nav"].includes(tagName)) { + setupNavMenu(node); + } + } + }); + }); + }); + observer.observe(document.documentElement, { + childList: true, + subtree: true + }); + window.addEventListener("message", (event) => { + if (event.data?.type === "NAV_MENU_CLICK") { + const href = event.data.href; + if (href) { + window.location.href = href; + } + } + }); + } + function navMenu() { + if (typeof document !== "undefined") { + observeNavMenuElements(); + } + return {}; + } + + // src/index.ts + (function(window2) { + "use strict"; + const appInstances = /* @__PURE__ */ new Map(); + let currentSessionToken = null; + let tokenRefreshInterval = null; + const Actions = { + Modal: { + Action: { + OPEN: "MODAL_OPEN", + CLOSE: "MODAL_CLOSE", + UPDATE: "MODAL_UPDATE", + DATA: "MODAL_DATA" + }, + Size: { + Small: "small", + Medium: "medium", + Large: "large", + Full: "full" + }, + create: function(app2, options) { + return { + id: "modal_" + Date.now(), + dispatch: function(action) { + app2.dispatch({ type: action, payload: options }); + }, + set: function(newOptions) { + Object.assign(options, newOptions); + this.dispatch(Actions.Modal.Action.UPDATE); + }, + unsubscribe: function() { + } + }; + } + }, + Toast: { + Action: { + SHOW: "TOAST_SHOW", + CLEAR: "TOAST_CLEAR" + }, + create: function(app2, options) { + return { + dispatch: function(action) { + if (action === Actions.Toast.Action.SHOW) { + console.log("[MockAppBridge] Toast:", options.message); + } + } + }; + } + }, + Loading: { + Action: { + START: "LOADING_START", + STOP: "LOADING_STOP" + }, + create: function(app2) { + return { + dispatch: function(action) { + app2.dispatch({ type: action }); + } + }; + } + }, + TitleBar: { + Action: { + UPDATE: "TITLEBAR_UPDATE" + }, + create: function(app2, options) { + return { + set: function(newOptions) { + Object.assign(options, newOptions); + app2.dispatch({ type: Actions.TitleBar.Action.UPDATE, payload: options }); + } + }; + } + }, + ResourcePicker: { + Action: { + OPEN: "RESOURCE_PICKER_OPEN", + SELECT: "RESOURCE_PICKER_SELECT", + CANCEL: "RESOURCE_PICKER_CANCEL" + }, + ResourceType: { + Product: "product", + ProductVariant: "variant", + Collection: "collection" + }, + create: function(app2, options) { + const subscribers = /* @__PURE__ */ new Map(); + return { + dispatch: function(action) { + if (action === Actions.ResourcePicker.Action.OPEN) { + setTimeout(() => { + const mockSelection = { + selection: [{ + id: "gid://shopify/Product/123456", + title: "Mock Product", + handle: "mock-product" + }] + }; + subscribers.forEach((callback) => { + if (callback.action === Actions.ResourcePicker.Action.SELECT) { + callback.handler(mockSelection); + } + }); + }, 100); + } + }, + subscribe: function(action, handler) { + const id = Date.now(); + subscribers.set(id, { action, handler }); + return () => subscribers.delete(id); + } + }; + } + }, + Redirect: { + Action: { + APP: "APP_REDIRECT", + REMOTE: "REMOTE_REDIRECT", + ADMIN_PATH: "ADMIN_PATH_REDIRECT" + }, + create: function(app2) { + return { + dispatch: function(action, payload) { + if (action === Actions.Redirect.Action.REMOTE) { + window2.open(payload.url, payload.newContext ? "_blank" : "_self"); + } + } + }; + }, + toRemote: function(payload) { + return { type: Actions.Redirect.Action.REMOTE, payload }; + }, + toApp: function(payload) { + return { type: Actions.Redirect.Action.APP, payload }; + } + }, + Error: { + Action: { + INVALID_ACTION: "INVALID_ACTION", + INVALID_PAYLOAD: "INVALID_PAYLOAD", + NETWORK: "NETWORK_ERROR", + UNAUTHORIZED: "UNAUTHORIZED" + } + } + }; + const utilities = { + getSessionToken: async function(_app) { + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error("Session token request timeout")); + }, 5e3); + const handler = function(event) { + if (event.data && event.data.type === "SESSION_TOKEN_RESPONSE") { + clearTimeout(timeout); + window2.removeEventListener("message", handler); + currentSessionToken = event.data.token; + resolve(event.data.token); + } + }; + window2.addEventListener("message", handler); + window2.parent.postMessage({ + type: "SESSION_TOKEN_REQUEST", + source: "app" + }, "*"); + }); + }, + authenticatedFetch: function(app2, fetch2) { + return async function(...args) { + const token = await utilities.getSessionToken(app2); + const url = args[0]; + const options = args[1] || {}; + if (typeof url === "string") { + options.headers = options.headers || {}; + if (options.headers instanceof Headers) { + options.headers.append("Authorization", `Bearer ${token}`); + } else { + options.headers["Authorization"] = `Bearer ${token}`; + } + } + return fetch2(url, options); + }; + } + }; + function createApp(config2) { + if (!config2.apiKey) { + throw new Error("API key is required"); + } + if (!config2.host) { + throw new Error("Host is required"); + } + const app2 = { + config: config2, + listeners: /* @__PURE__ */ new Set(), + errorListeners: /* @__PURE__ */ new Set(), + dispatch: function(action) { + console.log("[MockAppBridge] Dispatching:", action); + window2.parent.postMessage({ + type: "APP_BRIDGE_ACTION", + action, + source: "app" + }, "*"); + this.listeners.forEach((listener) => listener(action)); + }, + subscribe: function(eventNameOrCallback, callback) { + const handler = callback || eventNameOrCallback; + const wrappedHandler = (action) => { + if (typeof eventNameOrCallback === "string") { + if (action.type === eventNameOrCallback) { + handler(action); + } + } else { + handler(action); + } + }; + this.listeners.add(wrappedHandler); + return () => { + this.listeners.delete(wrappedHandler); + }; + }, + error: function(callback) { + this.errorListeners.add(callback); + return () => { + this.errorListeners.delete(callback); + }; + }, + getState: async function() { + return { + staffMember: { + id: "123456789", + name: "Test User", + email: "test@mockshop.com" + }, + shop: { + domain: new URL(atob(config2.host)).hostname, + name: "Mock Shop" + } + }; + }, + // Add idToken method for @shopify/app-bridge-react compatibility + idToken: async function() { + return utilities.getSessionToken(app2); + } + }; + appInstances.set(config2.apiKey, app2); + if (!tokenRefreshInterval) { + tokenRefreshInterval = window2.setInterval(async () => { + try { + await utilities.getSessionToken(app2); + } catch (error) { + console.error("[MockAppBridge] Token refresh failed:", error); + } + }, 5e4); + } + return app2; + } + const platform = { + isShopifyEmbedded: function() { + return window2.self !== window2.top; + }, + isMobile: function() { + return /mobile|android|iphone|ipad/i.test(navigator.userAgent); + }, + isShopifyMobile: function() { + return false; + }, + isShopifyPOS: function() { + return false; + } + }; + window2.createApp = createApp; + window2.shopifyAppBridge = { + createApp, + actions: Actions, + utilities, + platform + }; + window2.shopify = { + modal: modal(), + saveBar: saveBar(), + scopes: scopes(), + config: config(), + environment: environment(), + user: user(), + toast: toast(), + resourcePicker: resourcePicker(), + scanner: scanner(), + pos: pos(), + intents: intents(), + webVitals: webVitals(), + support: support(), + reviews: reviews(), + picker: picker(), + app: app(), + // Lifecycle methods + ready: Promise.resolve(), + loading: loading(), + // Origin should be empty string for mock + origin: "", + // Add idToken method directly to shopify global for useAppBridge() compatibility + idToken: async function() { + console.log("[MockAppBridge] idToken called on shopify global"); + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error("Session token request timeout")); + }, 5e3); + const handler = function(event) { + if (event.data && event.data.type === "SESSION_TOKEN_RESPONSE") { + clearTimeout(timeout); + window2.removeEventListener("message", handler); + currentSessionToken = event.data.token; + resolve(event.data.token); + } + }; + window2.addEventListener("message", handler); + window2.parent.postMessage({ + type: "SESSION_TOKEN_REQUEST", + source: "app" + }, "*"); + }); + } + }; + if (!window2.ShopifyAppBridge) { + window2.ShopifyAppBridge = { + createApp, + actions: Actions, + utilities, + platform + }; + } + navMenu(); + console.log("[MockAppBridge] Client library loaded"); + let adminApiConfig = "mock"; + let mockServerUrl = ""; + const detectMockServerUrl = () => { + const scripts = document.querySelectorAll('script[src*="app-bridge.js"]'); + for (const script of scripts) { + const src = script.src; + if (src) { + const url = new URL(src); + return url.origin; + } + } + try { + if (window2.parent !== window2) { + return window2.parent.location.origin; + } + } catch (e) { + } + return "http://localhost:3080"; + }; + mockServerUrl = detectMockServerUrl(); + const fetchAdminApiConfig = async () => { + try { + const response = await fetch(`${mockServerUrl}/api/config`); + const config2 = await response.json(); + if (config2.adminApi) { + adminApiConfig = config2.adminApi; + console.log("[MockAppBridge] Admin API config:", adminApiConfig); + } + } catch (error) { + console.warn("[MockAppBridge] Could not fetch admin API config, using default (mock)"); + } + }; + fetchAdminApiConfig(); + const isAdminApiRequest = (url) => { + if (typeof url !== "string") return false; + return url.includes("/admin/api/") || url.match(/^https:\/\/[^/]+\.myshopify\.com\/admin\/api\//) !== null; + }; + const _fetch = window2.fetch; + window2.fetch = async (...args) => { + const [input, init] = args; + const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url; + const options = init || {}; + const headers = {}; + if (options.headers) { + if (options.headers instanceof Headers) { + options.headers.forEach((value, key) => { + headers[key] = value; + }); + } else if (Array.isArray(options.headers)) { + options.headers.forEach(([key, value]) => { + headers[key] = value; + }); + } else { + Object.assign(headers, options.headers); + } + } + if (currentSessionToken) { + headers["Authorization"] = `Bearer ${currentSessionToken}`; + } + if (isAdminApiRequest(url)) { + console.log("[MockAppBridge] Intercepting Admin API request:", url); + if (adminApiConfig === "mock") { + return _fetch(`${mockServerUrl}/mock-admin-api`, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...headers + }, + body: JSON.stringify({ + url, + method: options.method || "GET", + body: options.body + }) + }); + } else if (typeof adminApiConfig === "object" && "proxy" in adminApiConfig) { + return _fetch(adminApiConfig.proxy, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...headers + }, + body: JSON.stringify({ + url, + method: options.method || "GET", + body: options.body + }) + }); + } else if (typeof adminApiConfig === "object" && "accessToken" in adminApiConfig) { + headers["X-Shopify-Access-Token"] = adminApiConfig.accessToken; + delete headers["Authorization"]; + return _fetch(url, { + ...options, + headers + }); + } + } + return _fetch(input, { + ...options, + headers + }); + }; + })(window); +})(); diff --git a/dist/auth/withMockBridgeAdminAuthForReactRouter.d.ts b/dist/auth/withMockBridgeAdminAuthForReactRouter.d.ts new file mode 100644 index 0000000..ae46ab1 --- /dev/null +++ b/dist/auth/withMockBridgeAdminAuthForReactRouter.d.ts @@ -0,0 +1,18 @@ +import { type ApiVersion } from "@shopify/shopify-api"; +import type { shopifyApp } from "@shopify/shopify-app-react-router/server"; +type ShopifyAppInstance = ReturnType; +export type MockBridgeAuthReflectConfig = { + apiKey: string | undefined; + apiSecretKey: string; + apiVersion: ApiVersion; + scopes: string[] | undefined; + appUrl: string; + useOnlineTokens?: boolean; + customShopDomains?: string[]; +}; +/** + * When SHOPIFY_MOCK_BRIDGE_AUTH=1, wraps authenticate.admin only; webhook and other methods unchanged. + */ +export declare function withMockBridgeAdminAuthForReactRouter(shopify: ShopifyAppInstance, reflectConfig: MockBridgeAuthReflectConfig): ShopifyAppInstance["authenticate"]; +export {}; +//# sourceMappingURL=withMockBridgeAdminAuthForReactRouter.d.ts.map \ No newline at end of file diff --git a/dist/auth/withMockBridgeAdminAuthForReactRouter.d.ts.map b/dist/auth/withMockBridgeAdminAuthForReactRouter.d.ts.map new file mode 100644 index 0000000..3364059 --- /dev/null +++ b/dist/auth/withMockBridgeAdminAuthForReactRouter.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"withMockBridgeAdminAuthForReactRouter.d.ts","sourceRoot":"","sources":["../../src/auth/withMockBridgeAdminAuthForReactRouter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC5E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAE3E,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAExD,MAAM,MAAM,2BAA2B,GAAG;IACxC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B,CAAC;AAwCF;;GAEG;AACH,wBAAgB,qCAAqC,CACnD,OAAO,EAAE,kBAAkB,EAC3B,aAAa,EAAE,2BAA2B,GACzC,kBAAkB,CAAC,cAAc,CAAC,CAgEpC"} \ No newline at end of file diff --git a/dist/auth/withMockBridgeAdminAuthForReactRouter.js b/dist/auth/withMockBridgeAdminAuthForReactRouter.js new file mode 100644 index 0000000..a7a044e --- /dev/null +++ b/dist/auth/withMockBridgeAdminAuthForReactRouter.js @@ -0,0 +1,99 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.withMockBridgeAdminAuthForReactRouter = withMockBridgeAdminAuthForReactRouter; +const shopify_api_1 = require("@shopify/shopify-api"); +function sessionTokenFromRequest(request) { + const auth = request.headers.get("authorization"); + if (auth?.startsWith("Bearer ")) + return auth.slice(7).trim(); + const url = new URL(request.url); + return url.searchParams.get("id_token")?.trim() || undefined; +} +/** Resolves shop hostname from JWT `dest` (full URL or bare hostname). */ +function shopHostnameFromDest(dest) { + try { + return new URL(dest).hostname; + } + catch { + try { + return new URL(`https://${dest}`).hostname; + } + catch { + return undefined; + } + } +} +function buildDecodeApi(config) { + const appUrl = new URL(config.appUrl); + const rawScheme = appUrl.protocol.replace(":", "") || "https"; + const hostScheme = rawScheme === "http" ? "http" : "https"; + return (0, shopify_api_1.shopifyApi)({ + apiKey: config.apiKey || "", + apiSecretKey: config.apiSecretKey, + apiVersion: config.apiVersion, + scopes: config.scopes ?? [], + hostName: appUrl.host, + hostScheme, + isEmbeddedApp: true, + ...(config.customShopDomains?.length + ? { customShopDomains: config.customShopDomains } + : {}), + }); +} +/** + * When SHOPIFY_MOCK_BRIDGE_AUTH=1, wraps authenticate.admin only; webhook and other methods unchanged. + */ +function withMockBridgeAdminAuthForReactRouter(shopify, reflectConfig) { + const baseAdmin = shopify.authenticate.admin.bind(shopify.authenticate); + const storage = shopify.sessionStorage; + if (!storage) { + return shopify.authenticate; + } + const decodeApi = buildDecodeApi(reflectConfig); + const scopeString = (reflectConfig.scopes ?? []).filter(Boolean).join(","); + return { + ...shopify.authenticate, + admin: async (request) => { + if (process.env.SHOPIFY_MOCK_BRIDGE_AUTH !== "1") { + return baseAdmin(request); + } + const token = sessionTokenFromRequest(request); + if (!token || !reflectConfig.apiSecretKey) { + return baseAdmin(request); + } + let payload; + try { + payload = await decodeApi.session.decodeSessionToken(token); + } + catch { + return baseAdmin(request); + } + const dest = payload.dest; + if (typeof dest !== "string" || !payload.sub) { + return baseAdmin(request); + } + const shop = shopHostnameFromDest(dest.trim()); + if (!shop) { + return baseAdmin(request); + } + const sessionId = reflectConfig.useOnlineTokens + ? decodeApi.session.getJwtSessionId(shop, String(payload.sub)) + : decodeApi.session.getOfflineId(shop); + const existing = await storage.loadSession(sessionId); + const withinMs = 5 * 60 * 1000; + if (!existing?.isActive(undefined, withinMs)) { + const mockSession = new shopify_api_1.Session({ + id: sessionId, + shop, + state: "mock-bridge-e2e", + isOnline: Boolean(reflectConfig.useOnlineTokens), + scope: scopeString, + accessToken: process.env.SHOPIFY_MOCK_BRIDGE_ACCESS_TOKEN ?? "mock-access-token", + }); + await storage.storeSession(mockSession); + } + return baseAdmin(request); + }, + }; +} +//# sourceMappingURL=withMockBridgeAdminAuthForReactRouter.js.map \ No newline at end of file diff --git a/dist/auth/withMockBridgeAdminAuthForReactRouter.js.map b/dist/auth/withMockBridgeAdminAuthForReactRouter.js.map new file mode 100644 index 0000000..3817bdf --- /dev/null +++ b/dist/auth/withMockBridgeAdminAuthForReactRouter.js.map @@ -0,0 +1 @@ +{"version":3,"file":"withMockBridgeAdminAuthForReactRouter.js","sourceRoot":"","sources":["../../src/auth/withMockBridgeAdminAuthForReactRouter.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/server/dev.d.ts b/dist/server/dev.d.ts new file mode 100644 index 0000000..9df53d6 --- /dev/null +++ b/dist/server/dev.d.ts @@ -0,0 +1,2 @@ +export {}; +//# sourceMappingURL=dev.d.ts.map \ No newline at end of file diff --git a/dist/server/dev.d.ts.map b/dist/server/dev.d.ts.map new file mode 100644 index 0000000..d94308a --- /dev/null +++ b/dist/server/dev.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/server/dev.ts"],"names":[],"mappings":""} \ No newline at end of file diff --git a/dist/server/dev.js b/dist/server/dev.js new file mode 100644 index 0000000..d6df7fa --- /dev/null +++ b/dist/server/dev.js @@ -0,0 +1,35 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const _1 = require("."); +const APP_URL = process.env.SHOPIFY_APP_URL; +if (!APP_URL) + throw new Error('SHOPIFY_APP_URL is not set'); +const CLIENT_ID = process.env.SHOPIFY_CLIENT_ID; +if (!CLIENT_ID) + throw new Error('SHOPIFY_CLIENT_ID is not set'); +const CLIENT_SECRET = process.env.SHOPIFY_CLIENT_SECRET; +if (!CLIENT_SECRET) + throw new Error('SHOPIFY_CLIENT_SECRET is not set'); +const SHOP = process.env.SHOPIFY_SHOP; +if (!SHOP) + throw new Error('SHOPIFY_SHOP is not set'); +const PORT = process.env.SHOPIFY_PORT; +if (!PORT) + throw new Error('SHOPIFY_PORT is not set'); +const DEBUG = process.env.SHOPIFY_DEBUG; +if (!DEBUG) + throw new Error('SHOPIFY_DEBUG is not set'); +const MOCK_USER_ID = process.env.MOCK_USER_ID; +if (!MOCK_USER_ID) + throw new Error('MOCK_USER_ID is not set'); +const server = new _1.MockShopifyAdminServer({ + appUrl: APP_URL, + clientId: CLIENT_ID, + clientSecret: CLIENT_SECRET, + userId: MOCK_USER_ID, + shop: SHOP, + port: Number(PORT), + debug: DEBUG === 'true', +}); +server.start(); +//# sourceMappingURL=dev.js.map \ No newline at end of file diff --git a/dist/server/dev.js.map b/dist/server/dev.js.map new file mode 100644 index 0000000..d95af33 --- /dev/null +++ b/dist/server/dev.js.map @@ -0,0 +1 @@ +{"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/server/dev.ts"],"names":[],"mappings":";;AAAA,wBAA2C;AAE3C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;AAC5C,IAAI,CAAC,OAAO;IAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;AAE5D,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;AAChD,IAAI,CAAC,SAAS;IAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC;AAEhE,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;AACxD,IAAI,CAAC,aAAa;IAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;AAExE,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;AACtC,IAAI,CAAC,IAAI;IAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;AAEtD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;AACtC,IAAI,CAAC,IAAI;IAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;AAEtD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;AACxC,IAAI,CAAC,KAAK;IAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;AAExD,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;AAC9C,IAAI,CAAC,YAAY;IAAE,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;AAE9D,MAAM,MAAM,GAAG,IAAI,yBAAsB,CAAC;IACxC,MAAM,EAAE,OAAO;IACf,QAAQ,EAAE,SAAS;IACnB,YAAY,EAAE,aAAa;IAC3B,MAAM,EAAE,YAAY;IACpB,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC;IAClB,KAAK,EAAE,KAAK,KAAK,MAAM;CACxB,CAAC,CAAC;AAEH,MAAM,CAAC,KAAK,EAAE,CAAC"} \ No newline at end of file From b1d7cf899f05bf03ea3e4644e3bbc763410240d3 Mon Sep 17 00:00:00 2001 From: fazlulkabir34 Date: Wed, 25 Mar 2026 05:21:38 +0600 Subject: [PATCH 08/21] chore: update package-lock.json and tsconfig.json --- package-lock.json | 92 +++++++++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 11 +++++- 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 08adf3e..481402d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,18 @@ }, "engines": { "node": ">=18.0.0" + }, + "peerDependencies": { + "@shopify/shopify-api": "^11.0.0 || ^12.0.0 || ^13.0.0", + "@shopify/shopify-app-react-router": "^1.0.0" + }, + "peerDependenciesMeta": { + "@shopify/shopify-api": { + "optional": true + }, + "@shopify/shopify-app-react-router": { + "optional": true + } } }, "node_modules/@cspotcode/source-map-support": { @@ -1577,6 +1589,70 @@ "node": ">= 0.8" } }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-router": { + "version": "7.13.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.2.tgz", + "integrity": "sha512-tX1Aee+ArlKQP+NIUd7SE6Li+CiGKwQtbS+FfRxPX6Pe4vHOo6nr9d++u5cwg+Z8K/x8tP+7qLmujDtfrAoUJA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -1629,6 +1705,14 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", @@ -1695,6 +1779,14 @@ "node": ">= 0.8.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", diff --git a/tsconfig.json b/tsconfig.json index 8b2582e..00a5435 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,16 @@ "declarationMap": true, "sourceMap": true, "resolveJsonModule": true, - "moduleResolution": "node16" + "moduleResolution": "node", + "baseUrl": ".", + "paths": { + "@shopify/shopify-app-react-router/server": [ + "./node_modules/@shopify/shopify-app-react-router/dist/ts/server/index.d.ts" + ], + "@shopify/shopify-api/adapters/web-api": [ + "./node_modules/@shopify/shopify-api/dist/ts/adapters/web-api/index.d.ts" + ] + } }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "**/*.test.ts"] From 8ca6f6e26353e2ec277371bec668d88359d36b10 Mon Sep 17 00:00:00 2001 From: fazlulkabir34 Date: Wed, 25 Mar 2026 06:27:58 +0600 Subject: [PATCH 09/21] chore: update package version to 1.3.1 and modify prepare script --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 4fef9fc..bc1186a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@getverdict/mock-bridge", - "version": "1.3.0", + "version": "1.3.1", "description": "Mock Shopify App Bridge for testing and development", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -31,7 +31,7 @@ "start": "node dist/server/index.js", "watch": "tsc --watch", "clean": "rm -rf dist", - "prepare": "cd admin-frame && npm install --legacy-peer-deps && cd ../app-bridge && npm install && cd .. && npm run build" + "prepare": "tsc" }, "keywords": [ "shopify", @@ -44,7 +44,7 @@ "license": "MIT", "repository": { "type": "git", - "url": "https://github.com/ctrlaltdylan/mock-bridge.git" + "url": "https://github.com/bevycommerce/mock-bridge.git" }, "peerDependencies": { "@shopify/shopify-api": "^11.0.0 || ^12.0.0 || ^13.0.0", @@ -92,4 +92,4 @@ "publishConfig": { "access": "public" } -} +} \ No newline at end of file From d7260238dd6568d6af2a67ba0c66362bbe32b1aa Mon Sep 17 00:00:00 2001 From: fazlulkabir34 Date: Wed, 25 Mar 2026 19:38:40 +0600 Subject: [PATCH 10/21] fix: update withMockBridgeAdminAuthForReactRouter to import web-api adapter --- docs/SHOPIFY_APP_REACT_ROUTER.md | 2 +- src/auth/withMockBridgeAdminAuthForReactRouter.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/SHOPIFY_APP_REACT_ROUTER.md b/docs/SHOPIFY_APP_REACT_ROUTER.md index d222323..24a41fa 100644 --- a/docs/SHOPIFY_APP_REACT_ROUTER.md +++ b/docs/SHOPIFY_APP_REACT_ROUTER.md @@ -67,7 +67,7 @@ Point embedded App Bridge at your mock server and relax CSP for the mock admin o ## Runtime dependency -The `withMockBridgeAdminAuthForReactRouter` helper (exported from `@getverdict/mock-bridge/auth`) loads **`@shopify/shopify-api` only** at runtime (for `shopifyApi`, `Session`, and `decodeSessionToken`). Your app must still install **`@shopify/shopify-app-react-router`** and peers (`react`, `react-dom`, `react-router`) as required by Shopify’s template. +The helper imports `@shopify/shopify-api/adapters/web-api` before calling `shopifyApi` (same adapter as `@shopify/shopify-app-react-router/server`), so `shopifyApi` works even when your bundler loads mock-bridge before the framework entry. Your app must still install **`@shopify/shopify-app-react-router`** and peers (`react`, `react-dom`, `react-router`) as required by Shopify’s template. ## Install from npm or Git diff --git a/src/auth/withMockBridgeAdminAuthForReactRouter.ts b/src/auth/withMockBridgeAdminAuthForReactRouter.ts index 10f4016..2df1d00 100644 --- a/src/auth/withMockBridgeAdminAuthForReactRouter.ts +++ b/src/auth/withMockBridgeAdminAuthForReactRouter.ts @@ -1,5 +1,6 @@ -import { shopifyApi, Session, type ApiVersion } from "@shopify/shopify-api"; -import type { shopifyApp } from "@shopify/shopify-app-react-router/server"; +import '@shopify/shopify-api/adapters/web-api'; +import { shopifyApi, Session, type ApiVersion } from '@shopify/shopify-api'; +import type { shopifyApp } from '@shopify/shopify-app-react-router/server'; type ShopifyAppInstance = ReturnType; From 3d20587fc12e31b6736ec846c3fe3127a7cd2f58 Mon Sep 17 00:00:00 2001 From: fazlulkabir34 Date: Wed, 25 Mar 2026 19:53:49 +0600 Subject: [PATCH 11/21] chore: add assets directory to package.json and update withMockBridgeAdminAuthForReactRouter imports --- dist/auth/withMockBridgeAdminAuthForReactRouter.d.ts | 5 +++-- dist/auth/withMockBridgeAdminAuthForReactRouter.d.ts.map | 2 +- dist/auth/withMockBridgeAdminAuthForReactRouter.js | 1 + dist/auth/withMockBridgeAdminAuthForReactRouter.js.map | 2 +- package.json | 1 + 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/dist/auth/withMockBridgeAdminAuthForReactRouter.d.ts b/dist/auth/withMockBridgeAdminAuthForReactRouter.d.ts index ae46ab1..9fa4995 100644 --- a/dist/auth/withMockBridgeAdminAuthForReactRouter.d.ts +++ b/dist/auth/withMockBridgeAdminAuthForReactRouter.d.ts @@ -1,5 +1,6 @@ -import { type ApiVersion } from "@shopify/shopify-api"; -import type { shopifyApp } from "@shopify/shopify-app-react-router/server"; +import '@shopify/shopify-api/adapters/web-api'; +import { type ApiVersion } from '@shopify/shopify-api'; +import type { shopifyApp } from '@shopify/shopify-app-react-router/server'; type ShopifyAppInstance = ReturnType; export type MockBridgeAuthReflectConfig = { apiKey: string | undefined; diff --git a/dist/auth/withMockBridgeAdminAuthForReactRouter.d.ts.map b/dist/auth/withMockBridgeAdminAuthForReactRouter.d.ts.map index 3364059..47538d3 100644 --- a/dist/auth/withMockBridgeAdminAuthForReactRouter.d.ts.map +++ b/dist/auth/withMockBridgeAdminAuthForReactRouter.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"withMockBridgeAdminAuthForReactRouter.d.ts","sourceRoot":"","sources":["../../src/auth/withMockBridgeAdminAuthForReactRouter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC5E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAE3E,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAExD,MAAM,MAAM,2BAA2B,GAAG;IACxC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B,CAAC;AAwCF;;GAEG;AACH,wBAAgB,qCAAqC,CACnD,OAAO,EAAE,kBAAkB,EAC3B,aAAa,EAAE,2BAA2B,GACzC,kBAAkB,CAAC,cAAc,CAAC,CAgEpC"} \ No newline at end of file +{"version":3,"file":"withMockBridgeAdminAuthForReactRouter.d.ts","sourceRoot":"","sources":["../../src/auth/withMockBridgeAdminAuthForReactRouter.ts"],"names":[],"mappings":"AAAA,OAAO,uCAAuC,CAAC;AAC/C,OAAO,EAAuB,KAAK,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC5E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0CAA0C,CAAC;AAE3E,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAExD,MAAM,MAAM,2BAA2B,GAAG;IACxC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC9B,CAAC;AAwCF;;GAEG;AACH,wBAAgB,qCAAqC,CACnD,OAAO,EAAE,kBAAkB,EAC3B,aAAa,EAAE,2BAA2B,GACzC,kBAAkB,CAAC,cAAc,CAAC,CAgEpC"} \ No newline at end of file diff --git a/dist/auth/withMockBridgeAdminAuthForReactRouter.js b/dist/auth/withMockBridgeAdminAuthForReactRouter.js index a7a044e..622572b 100644 --- a/dist/auth/withMockBridgeAdminAuthForReactRouter.js +++ b/dist/auth/withMockBridgeAdminAuthForReactRouter.js @@ -1,6 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.withMockBridgeAdminAuthForReactRouter = withMockBridgeAdminAuthForReactRouter; +require("@shopify/shopify-api/adapters/web-api"); const shopify_api_1 = require("@shopify/shopify-api"); function sessionTokenFromRequest(request) { const auth = request.headers.get("authorization"); diff --git a/dist/auth/withMockBridgeAdminAuthForReactRouter.js.map b/dist/auth/withMockBridgeAdminAuthForReactRouter.js.map index 3817bdf..8fd4a94 100644 --- a/dist/auth/withMockBridgeAdminAuthForReactRouter.js.map +++ b/dist/auth/withMockBridgeAdminAuthForReactRouter.js.map @@ -1 +1 @@ -{"version":3,"file":"withMockBridgeAdminAuthForReactRouter.js","sourceRoot":"","sources":["../../src/auth/withMockBridgeAdminAuthForReactRouter.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"withMockBridgeAdminAuthForReactRouter.js","sourceRoot":"","sources":["../../src/auth/withMockBridgeAdminAuthForReactRouter.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/package.json b/package.json index bc1186a..706d1d8 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ }, "files": [ "dist/", + "assets/", "admin-frame/dist/", "app-bridge/dist/" ], From bef38bab8123a3d6c96e7de3a00ceedd6370ae52 Mon Sep 17 00:00:00 2001 From: fazlulkabir34 Date: Wed, 25 Mar 2026 20:19:43 +0600 Subject: [PATCH 12/21] reverted to old prepare script --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 706d1d8..31c69e6 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "start": "node dist/server/index.js", "watch": "tsc --watch", "clean": "rm -rf dist", - "prepare": "tsc" + "prepare": "cd admin-frame && npm install --legacy-peer-deps && cd ../app-bridge && npm install && cd .. && npm run build" }, "keywords": [ "shopify", From 4410639cce112971315605f8de63d6ebb0fdae8c Mon Sep 17 00:00:00 2001 From: fazlulkabir34 Date: Tue, 31 Mar 2026 20:04:18 +0600 Subject: [PATCH 13/21] feat: implement ResourcePicker component and integrate with mock bridge - Added ResourcePicker component for selecting products, variants, and collections. - Updated App component to include ResourcePicker. - Enhanced mock bridge to handle resource picker actions. - Updated package.json and package-lock.json to include new dependencies for React and related types. - Modified README.md to reflect the new ResourcePicker functionality. - Updated tsconfig.json to include new type definitions for resource picker. --- .gitignore | 2 +- README.md | 2 +- admin-frame/src/App.tsx | 2 + .../components/features/ResourcePicker.tsx | 257 ++++++++++++++++++ admin-frame/src/hooks/useMockBridge.ts | 72 ++++- admin-frame/src/store/features.ts | 12 +- .../src/store/features/resource-picker.ts | 229 ++++++++++++++++ admin-frame/src/types/resource-picker.ts | 66 +++++ app-bridge/dist/index.js | 98 +++++-- app-bridge/src/features/loading.ts | 6 +- app-bridge/src/features/resource-picker.ts | 21 +- app-bridge/src/index.ts | 46 +++- app-bridge/src/invokeFeature.ts | 30 +- app-bridge/src/resource-picker-bridge.ts | 48 ++++ dist/client/mock-detector.d.ts | 2 +- dist/client/mock-detector.d.ts.map | 2 +- dist/client/mock-detector.js | 3 +- dist/client/mock-detector.js.map | 2 +- dist/mock-resource-picker-catalog.d.ts | 35 +++ dist/mock-resource-picker-catalog.d.ts.map | 1 + dist/mock-resource-picker-catalog.js | 93 +++++++ dist/mock-resource-picker-catalog.js.map | 1 + dist/react/AppProvider.d.ts | 22 ++ dist/react/AppProvider.d.ts.map | 1 + dist/react/AppProvider.js | 80 ++++++ dist/react/AppProvider.js.map | 1 + dist/react/EmbeddedShopifyAppProvider.d.ts | 15 + .../react/EmbeddedShopifyAppProvider.d.ts.map | 1 + dist/react/EmbeddedShopifyAppProvider.js | 26 ++ dist/react/EmbeddedShopifyAppProvider.js.map | 1 + dist/react/index.d.ts | 3 + dist/react/index.d.ts.map | 1 + dist/react/index.js | 9 + dist/react/index.js.map | 1 + dist/server/index.d.ts | 1 + dist/server/index.d.ts.map | 2 +- dist/server/index.js | 19 ++ dist/server/index.js.map | 2 +- dist/types/index.d.ts | 3 + dist/types/index.d.ts.map | 2 +- docs/RESOURCE_PICKER.md | 76 ++++++ docs/SHOPIFY_APP_REACT_ROUTER.md | 61 ++++- docs/SHOPIFY_APP_REMIX.md | 12 +- package-lock.json | 49 +++- package.json | 22 +- src/client/mock-detector.ts | 5 +- src/mock-resource-picker-catalog.ts | 125 +++++++++ src/react/AppProvider.ts | 72 +++++ src/react/EmbeddedShopifyAppProvider.ts | 36 +++ src/react/index.ts | 9 + src/server/index.ts | 39 +++ src/types/index.ts | 4 + tsconfig.json | 3 + 53 files changed, 1624 insertions(+), 109 deletions(-) create mode 100644 admin-frame/src/components/features/ResourcePicker.tsx create mode 100644 admin-frame/src/store/features/resource-picker.ts create mode 100644 admin-frame/src/types/resource-picker.ts create mode 100644 app-bridge/src/resource-picker-bridge.ts create mode 100644 dist/mock-resource-picker-catalog.d.ts create mode 100644 dist/mock-resource-picker-catalog.d.ts.map create mode 100644 dist/mock-resource-picker-catalog.js create mode 100644 dist/mock-resource-picker-catalog.js.map create mode 100644 dist/react/AppProvider.d.ts create mode 100644 dist/react/AppProvider.d.ts.map create mode 100644 dist/react/AppProvider.js create mode 100644 dist/react/AppProvider.js.map create mode 100644 dist/react/EmbeddedShopifyAppProvider.d.ts create mode 100644 dist/react/EmbeddedShopifyAppProvider.d.ts.map create mode 100644 dist/react/EmbeddedShopifyAppProvider.js create mode 100644 dist/react/EmbeddedShopifyAppProvider.js.map create mode 100644 dist/react/index.d.ts create mode 100644 dist/react/index.d.ts.map create mode 100644 dist/react/index.js create mode 100644 dist/react/index.js.map create mode 100644 docs/RESOURCE_PICKER.md create mode 100644 src/mock-resource-picker-catalog.ts create mode 100644 src/react/AppProvider.ts create mode 100644 src/react/EmbeddedShopifyAppProvider.ts create mode 100644 src/react/index.ts diff --git a/.gitignore b/.gitignore index 4740520..bbee5ad 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ yarn-error.log* .pnpm-debug.log* # Build outputs -dist/ +# dist/ build/ *.tsbuildinfo diff --git a/README.md b/README.md index 5918548..5d68e8c 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ | `shopify.environment` | ✅ Supported | `embedded`, `mobile`, `pos` | | `shopify.user` | ✅ Supported | Returns mock user object | | `shopify.scopes` | 🔶 Stub | Returns mock data | -| `shopify.resourcePicker` | 🔶 Stub | Returns empty array | +| `shopify.resourcePicker` | ✅ Mock UI | Opens a mock admin modal; catalog from [`/api/resource-picker-catalog`](docs/RESOURCE_PICKER.md) (customizable via `resourcePickerCatalog` on the server) | | `shopify.picker` | 🔶 Stub | Returns empty selection | | `shopify.scanner` | 🔶 Stub | Returns mock scan data | | `shopify.pos` | 🔶 Stub | Cart API with mock data | diff --git a/admin-frame/src/App.tsx b/admin-frame/src/App.tsx index 974c383..10240c9 100644 --- a/admin-frame/src/App.tsx +++ b/admin-frame/src/App.tsx @@ -2,6 +2,7 @@ import { EmbeddedApp } from "./components/EmbeddedApp" import { Modal } from "./components/features/Modal" import { Loading } from "./components/features/Loading" import { SaveBar } from "./components/features/SaveBar" +import { ResourcePicker } from "./components/features/ResourcePicker" import { Frame } from "./components/Frame" function App() { @@ -10,6 +11,7 @@ function App() { + diff --git a/admin-frame/src/components/features/ResourcePicker.tsx b/admin-frame/src/components/features/ResourcePicker.tsx new file mode 100644 index 0000000..f4914e8 --- /dev/null +++ b/admin-frame/src/components/features/ResourcePicker.tsx @@ -0,0 +1,257 @@ +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { useResourcePickerFeatureStore } from '../../store/features/resource-picker'; +import type { ResourcePickerType } from '../../types/resource-picker'; + +const typeLabels: Record = { + product: 'Products', + variant: 'Variants', + collection: 'Collections', +}; + +function ResourcePickerModal() { + const isOpen = useResourcePickerFeatureStore((s) => s.isOpen); + const options = useResourcePickerFeatureStore((s) => s.options); + const catalog = useResourcePickerFeatureStore((s) => s.catalog); + const loading = useResourcePickerFeatureStore((s) => s.loading); + const error = useResourcePickerFeatureStore((s) => s.error); + const query = useResourcePickerFeatureStore((s) => s.query); + const selectedIds = useResourcePickerFeatureStore((s) => s.selectedIds); + const setQuery = useResourcePickerFeatureStore((s) => s.setQuery); + const toggleId = useResourcePickerFeatureStore((s) => s.toggleId); + const cancel = useResourcePickerFeatureStore((s) => s.cancel); + const confirm = useResourcePickerFeatureStore((s) => s.confirm); + + const [localQuery, setLocalQuery] = useState(''); + + useEffect(() => { + if (isOpen) { + setLocalQuery(''); + } + }, [isOpen]); + + useEffect(() => { + if (!isOpen) return; + const t = window.setTimeout(() => { + if (localQuery !== query) { + setQuery(localQuery); + } + }, 250); + return () => window.clearTimeout(t); + }, [localQuery, isOpen, query, setQuery]); + + const pickerType: ResourcePickerType = options?.type ?? 'product'; + + const rows = useMemo(() => { + if (!catalog) return []; + if (pickerType === 'product') { + return catalog.products.map((p) => ({ + id: p.id, + primary: p.title, + secondary: p.handle, + })); + } + if (pickerType === 'variant') { + return catalog.variants.map((v) => ({ + id: v.id, + primary: v.displayName, + secondary: `${v.price} · ${v.sku ?? v.id}`, + })); + } + return catalog.collections.map((c) => ({ + id: c.id, + primary: c.title, + secondary: c.handle, + })); + }, [catalog, pickerType]); + + const onToggle = useCallback( + (id: string) => { + toggleId(id); + }, + [toggleId], + ); + + if (!isOpen) { + return null; + } + + const heading = typeLabels[pickerType]; + const multi = options?.multiple === true; + + return ( +
{ + if (e.target === e.currentTarget) { + cancel(); + } + }} + > +
+
+

+ Select {heading} +

+ + {multi ? 'Multi-select' : 'Single select'} + +
+ +
+ + setLocalQuery(e.target.value)} + placeholder={`Search ${heading.toLowerCase()}…`} + style={{ + width: '100%', + marginTop: 6, + padding: '10px 12px', + borderRadius: 8, + border: '1px solid #c9c9c9', + fontSize: '0.95rem', + boxSizing: 'border-box', + }} + /> +
+ +
+ {loading && ( +

Loading catalog…

+ )} + {error && !loading && ( +

{error}

+ )} + {!loading && !error && rows.length === 0 && ( +

No results.

+ )} + {!loading && + !error && + rows.map((row) => ( + + ))} +
+ +
+ + +
+
+
+ ); +} + +export function ResourcePicker() { + const [container] = useState(() => document.createElement('div')); + + useEffect(() => { + document.body.appendChild(container); + return () => { + document.body.removeChild(container); + }; + }, [container]); + + return createPortal(, container); +} diff --git a/admin-frame/src/hooks/useMockBridge.ts b/admin-frame/src/hooks/useMockBridge.ts index d66207f..3762c74 100644 --- a/admin-frame/src/hooks/useMockBridge.ts +++ b/admin-frame/src/hooks/useMockBridge.ts @@ -1,6 +1,8 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { useConfig } from "./useConfig"; import { getFeatureStore, type FeatureActionName, type FeatureActionPayload, type FeatureName } from "../store/features"; +import { useResourcePickerFeatureStore } from "../store/features/resource-picker"; +import type { ResourcePickerOpenOptions } from "../types/resource-picker"; export type FeatureActionRequest< F extends FeatureName = FeatureName, @@ -12,8 +14,17 @@ export type FeatureActionRequest< payload: P | FeatureActionPayload; } +/** Includes special-case features handled in useMockBridge (e.g. resourcePicker). */ +export type HostFeatureActionMessage = FeatureActionRequest | { + feature: 'resourcePicker'; + action: 'open'; + payload: ResourcePickerOpenOptions; +} + export function useMockBridge() { const iframeRef = useRef(null); + const mockSignalTimeoutsRef = useRef([]); + const mockSignalIntervalRef = useRef | null>(null); const config = useConfig(); const [sessionToken, setSessionToken] = useState(''); @@ -38,6 +49,35 @@ export function useMockBridge() { } }, [config]); + const clearMockSignalSchedule = useCallback(() => { + for (const id of mockSignalTimeoutsRef.current) { + window.clearTimeout(id); + } + mockSignalTimeoutsRef.current = []; + if (mockSignalIntervalRef.current != null) { + window.clearInterval(mockSignalIntervalRef.current); + mockSignalIntervalRef.current = null; + } + }, []); + + const scheduleMockEnvironmentSignals = useCallback(() => { + clearMockSignalSchedule(); + sendMockSignal(); + const delays = [10, 50, 100, 250, 500, 1000, 2000, 4000, 6000]; + for (const d of delays) { + mockSignalTimeoutsRef.current.push(window.setTimeout(sendMockSignal, d)); + } + mockSignalIntervalRef.current = window.setInterval(sendMockSignal, 400); + mockSignalTimeoutsRef.current.push( + window.setTimeout(() => { + if (mockSignalIntervalRef.current != null) { + window.clearInterval(mockSignalIntervalRef.current); + mockSignalIntervalRef.current = null; + } + }, 12_000), + ); + }, [clearMockSignalSchedule, sendMockSignal]); + const getSessionToken = useCallback(() => { if (!config) return Promise.resolve(''); @@ -84,9 +124,19 @@ export function useMockBridge() { // Embedded app called something, like shopify.modal.show('modal_id') // Proxy the calls to their corresponding feature store if (event.data.type === 'FEATURE_ACTION_REQUEST') { - const { feature, action, payload } = event.data as FeatureActionRequest; + const { feature, action, payload } = event.data as HostFeatureActionMessage; + const actionId = event.data.action_id as string; + + if (feature === 'resourcePicker' && action === 'open') { + useResourcePickerFeatureStore.getState().openFromBridge({ + actionId, + iframeWindow: iframeRef.current?.contentWindow ?? null, + options: (payload ?? {}) as ResourcePickerOpenOptions, + }); + return; + } - const featureStore = getFeatureStore(feature); + const featureStore = getFeatureStore(feature as FeatureName); const state = featureStore.getState() as Record; const actionFn = state[action as string]; @@ -105,25 +155,21 @@ export function useMockBridge() { } const handleIframeLoad = () => { - // Send signal immediately - sendMockSignal(); - - // Send signal a few more times to ensure delivery before timeout - setTimeout(sendMockSignal, 10); - setTimeout(sendMockSignal, 50); - setTimeout(sendMockSignal, 100); - } + scheduleMockEnvironmentSignals(); + }; window.addEventListener('message', handleMessage); - // Send signal immediately when iframe starts loading - iframeRef.current?.addEventListener('load', handleIframeLoad); + iframe.addEventListener('load', handleIframeLoad); + // Iframe may have already fired `load` before this effect ran; SPAs also mount listeners late. + scheduleMockEnvironmentSignals(); return () => { + clearMockSignalSchedule(); window.removeEventListener('message', handleMessage); iframe.removeEventListener('load', handleIframeLoad); }; - }, [config, sendMockSignal, sessionToken]); + }, [clearMockSignalSchedule, config, scheduleMockEnvironmentSignals, sessionToken]); const iframeSrc = (() => { if (!config || !sessionToken) return ''; diff --git a/admin-frame/src/store/features.ts b/admin-frame/src/store/features.ts index b2c8637..35db542 100644 --- a/admin-frame/src/store/features.ts +++ b/admin-frame/src/store/features.ts @@ -10,7 +10,11 @@ export const features = { navMenu: useNavMenuFeatureStore, } -export type FeatureName = keyof typeof features; +type FeaturesMap = typeof features; + +/** Registered iframe-invokable features (see useMockBridge). */ +export type FeatureName = keyof FeaturesMap; + export type FeatureStore = ReturnType; export type FeatureActions = { [K in keyof FeatureStore as FeatureStore[K] extends (...args: any[]) => any ? K : never]: FeatureStore[K]; @@ -19,9 +23,5 @@ export type FeatureActionName = keyof FeatureActions; export type FeatureActionPayload> = FeatureActions[A] extends (payload: infer P) => any ? P : never; export function getFeatureStore(name: F) { - if (!features[name]) { - throw new Error(`Feature ${name} not found/supported`); - } - return features[name]; -} \ No newline at end of file +} diff --git a/admin-frame/src/store/features/resource-picker.ts b/admin-frame/src/store/features/resource-picker.ts new file mode 100644 index 0000000..bb88c5b --- /dev/null +++ b/admin-frame/src/store/features/resource-picker.ts @@ -0,0 +1,229 @@ +import { create } from 'zustand'; +import { combine } from 'zustand/middleware'; +import type { + MockResourcePickerProduct, + MockResourcePickerVariant, + MockResourcePickerCollection, + ResourcePickerCatalogApiResponse, + ResourcePickerOpenOptions, + ResourcePickerResultPayload, + ResourcePickerSelectionRow, + ResourcePickerType, +} from '../../types/resource-picker'; + +type ResourcePickerState = { + isOpen: boolean; + actionId: string | null; + iframeWindow: Window | null; + options: ResourcePickerOpenOptions | null; + query: string; + loading: boolean; + error: string | null; + catalog: ResourcePickerCatalogApiResponse | null; + selectedIds: Set; +}; + +const emptyCatalog: ResourcePickerCatalogApiResponse = { + products: [], + variants: [], + collections: [], +}; + +function closedPickerState(): Pick< + ResourcePickerState, + | 'isOpen' + | 'actionId' + | 'iframeWindow' + | 'options' + | 'query' + | 'catalog' + | 'selectedIds' + | 'error' +> { + return { + isOpen: false, + actionId: null, + iframeWindow: null, + options: null, + query: '', + catalog: null, + selectedIds: new Set(), + error: null, + }; +} + +function selectionRowsForProduct(p: MockResourcePickerProduct): ResourcePickerSelectionRow { + return { + id: p.id, + title: p.title, + handle: p.handle, + variants: p.variants, + }; +} + +function selectionRowForVariant(v: MockResourcePickerVariant): ResourcePickerSelectionRow { + return { + id: v.id, + title: v.productTitle, + displayName: v.displayName, + price: v.price, + product: { + id: v.productId, + title: v.productTitle, + handle: v.productHandle, + }, + }; +} + +function selectionRowForCollection(c: MockResourcePickerCollection): ResourcePickerSelectionRow { + return { + id: c.id, + title: c.title, + handle: c.handle, + }; +} + +export const useResourcePickerFeatureStore = create( + combine( + { + isOpen: false, + actionId: null, + iframeWindow: null, + options: null, + query: '', + loading: false, + error: null, + catalog: null, + selectedIds: new Set(), + } as ResourcePickerState, + (set, get) => ({ + /** Called from mock admin host when iframe requests the picker (see useMockBridge). */ + openFromBridge: (payload: { + actionId: string; + iframeWindow: Window | null; + options: ResourcePickerOpenOptions; + }) => { + const preset = new Set(payload.options.selectionIds ?? []); + set({ + isOpen: true, + actionId: payload.actionId, + iframeWindow: payload.iframeWindow, + options: payload.options, + query: '', + loading: true, + error: null, + catalog: null, + selectedIds: preset, + }); + + const q = ''; + fetch(`/api/resource-picker-catalog?q=${encodeURIComponent(q)}`) + .then((r) => { + if (!r.ok) throw new Error(`Catalog ${r.status}`); + return r.json() as Promise; + }) + .then((catalog) => { + set({ catalog, loading: false, error: null }); + }) + .catch((e: Error) => { + set({ + loading: false, + error: e.message || 'Failed to load catalog', + catalog: emptyCatalog, + }); + }); + }, + + setQuery: (query: string) => { + set({ query, loading: true, error: null }); + fetch(`/api/resource-picker-catalog?q=${encodeURIComponent(query)}`) + .then((r) => { + if (!r.ok) throw new Error(`Catalog ${r.status}`); + return r.json() as Promise; + }) + .then((catalog) => { + set({ catalog, loading: false }); + }) + .catch((e: Error) => { + set({ loading: false, error: e.message }); + }); + }, + + toggleId: (id: string) => + set((state) => { + const next = new Set(state.selectedIds); + const allowMulti = state.options?.multiple === true; + if (next.has(id)) { + next.delete(id); + } else { + if (!allowMulti) { + next.clear(); + } + next.add(id); + } + return { selectedIds: next }; + }), + + close: () => set(closedPickerState()), + + cancel: () => { + const { actionId, iframeWindow } = get(); + const payload: ResourcePickerResultPayload = { cancelled: true, selection: [] }; + if (actionId && iframeWindow) { + iframeWindow.postMessage( + { + type: 'FEATURE_ACTION_RESPONSE', + action_id: actionId, + payload, + }, + '*', + ); + } + set(closedPickerState()); + }, + + confirm: () => { + const state = get(); + const { actionId, iframeWindow, options, catalog, selectedIds } = state; + if (!actionId || !iframeWindow || !options || !catalog) { + set(closedPickerState()); + return; + } + + const type: ResourcePickerType = options.type ?? 'product'; + const selection: ResourcePickerSelectionRow[] = []; + + if (type === 'product') { + for (const p of catalog.products) { + if (selectedIds.has(p.id)) { + selection.push(selectionRowsForProduct(p)); + } + } + } else if (type === 'variant') { + for (const v of catalog.variants) { + if (selectedIds.has(v.id)) { + selection.push(selectionRowForVariant(v)); + } + } + } else { + for (const c of catalog.collections) { + if (selectedIds.has(c.id)) { + selection.push(selectionRowForCollection(c)); + } + } + } + + const payload: ResourcePickerResultPayload = { cancelled: false, selection }; + iframeWindow.postMessage( + { + type: 'FEATURE_ACTION_RESPONSE', + action_id: actionId, + payload, + }, + '*', + ); + set(closedPickerState()); + }, + }), + ), +); diff --git a/admin-frame/src/types/resource-picker.ts b/admin-frame/src/types/resource-picker.ts new file mode 100644 index 0000000..c453fe7 --- /dev/null +++ b/admin-frame/src/types/resource-picker.ts @@ -0,0 +1,66 @@ +export type ResourcePickerType = 'product' | 'variant' | 'collection'; + +/** Payload sent from embedded app (App Bridge) when opening the picker */ +export type ResourcePickerOpenOptions = { + type?: ResourcePickerType; + multiple?: boolean; + selectionIds?: string[]; +}; + +export type MockResourcePickerCollection = { + id: string; + title: string; + handle: string; +}; + +export type MockResourcePickerVariant = { + id: string; + title: string; + displayName: string; + price: string; + sku?: string; + inventoryQuantity?: number; + productId: string; + productTitle: string; + productHandle: string; +}; + +export type MockResourcePickerProduct = { + id: string; + title: string; + handle: string; + status: string; + variants: MockResourcePickerVariant[]; +}; + +export type ResourcePickerCatalogApiResponse = { + products: MockResourcePickerProduct[]; + variants: MockResourcePickerVariant[]; + collections: MockResourcePickerCollection[]; +}; + +/** Selection rows returned to the iframe (product / variant / collection) */ +export type ResourcePickerSelectionRow = + | { + id: string; + title: string; + handle: string; + variants?: MockResourcePickerVariant[]; + } + | { + id: string; + title: string; + displayName: string; + price?: string; + product?: { id: string; title: string; handle?: string }; + } + | { + id: string; + title: string; + handle: string; + }; + +export type ResourcePickerResultPayload = { + cancelled: boolean; + selection: ResourcePickerSelectionRow[]; +}; diff --git a/app-bridge/dist/index.js b/app-bridge/dist/index.js index 6030a9c..3fbc02b 100644 --- a/app-bridge/dist/index.js +++ b/app-bridge/dist/index.js @@ -57,14 +57,18 @@ action, payload }; - window.parent.postMessage({ - type: "FEATURE_ACTION_REQUEST", - action_id: actionId, - ...request - }, "*"); + const timeoutMs = feature === "resourcePicker" && action === "open" ? 3e5 : 1e3; + window.parent.postMessage( + { + type: "FEATURE_ACTION_REQUEST", + action_id: actionId, + ...request + }, + "*" + ); const rejectTimeout = setTimeout(() => { - reject(new Error("Feature action timed out after 1 second")); - }, 1e3); + reject(new Error(`Feature action timed out after ${timeoutMs} ms`)); + }, timeoutMs); const handler = (event) => { if (event.data && event.data.type === "FEATURE_ACTION_RESPONSE") { if (event.data.action_id !== actionId) return; @@ -379,11 +383,48 @@ }; } + // src/resource-picker-bridge.ts + async function openMockResourcePickerFromBridge(options) { + const raw = await invokeFeature( + "resourcePicker", + "open", + options + ); + if (!raw || typeof raw !== "object") { + return { cancelled: true, selection: [] }; + } + return { + cancelled: Boolean(raw.cancelled), + selection: Array.isArray(raw.selection) ? raw.selection : [] + }; + } + function bridgePayloadFromLegacyResourcePickerOptions(options) { + let type = "product"; + if (options?.type === "variant" || options?.type === "product_variant") { + type = "variant"; + } else if (options?.type === "collection") { + type = "collection"; + } + return { + type, + multiple: options?.multiple, + selectionIds: options?.selectionIds + }; + } + // src/features/resource-picker.ts function resourcePicker() { return async (options) => { - console.log("[MockAppBridge] Resource picker opened with options:", options); - return Promise.resolve([]); + const payload = { + type: options?.type === "variant" ? "variant" : options?.type === "collection" ? "collection" : "product", + multiple: options?.multiple === true, + selectionIds: options?.selectionIds + }; + const result = await openMockResourcePickerFromBridge(payload); + if (result.cancelled) { + return []; + } + return result.selection; }; } @@ -560,7 +601,7 @@ function loading() { return (isLoading) => { console.log("[MockAppBridge] Loading:", isLoading); - invokeFeature("loading", "setLoading", { isLoading }); + invokeFeature("loading", "setLoading", { isLoading: Boolean(isLoading) }); }; } @@ -730,20 +771,33 @@ return { dispatch: function(action) { if (action === Actions.ResourcePicker.Action.OPEN) { - setTimeout(() => { - const mockSelection = { - selection: [{ - id: "gid://shopify/Product/123456", - title: "Mock Product", - handle: "mock-product" - }] - }; + void openMockResourcePickerFromBridge( + bridgePayloadFromLegacyResourcePickerOptions({ + type: options?.type, + multiple: options?.multiple, + selectionIds: options?.selectionIds + }) + ).then((result) => { + if (result.cancelled) { + subscribers.forEach((callback) => { + if (callback.action === Actions.ResourcePicker.Action.CANCEL) { + callback.handler({}); + } + }); + } else { + subscribers.forEach((callback) => { + if (callback.action === Actions.ResourcePicker.Action.SELECT) { + callback.handler({ selection: result.selection }); + } + }); + } + }).catch(() => { subscribers.forEach((callback) => { - if (callback.action === Actions.ResourcePicker.Action.SELECT) { - callback.handler(mockSelection); + if (callback.action === Actions.ResourcePicker.Action.CANCEL) { + callback.handler({}); } }); - }, 100); + }); } }, subscribe: function(action, handler) { @@ -975,7 +1029,7 @@ let mockServerUrl = ""; const detectMockServerUrl = () => { const scripts = document.querySelectorAll('script[src*="app-bridge.js"]'); - for (const script of scripts) { + for (const script of Array.from(scripts)) { const src = script.src; if (src) { const url = new URL(src); diff --git a/app-bridge/src/features/loading.ts b/app-bridge/src/features/loading.ts index b69c82a..c8aca25 100644 --- a/app-bridge/src/features/loading.ts +++ b/app-bridge/src/features/loading.ts @@ -1,8 +1,8 @@ import { invokeFeature } from "../invokeFeature"; -export function loading(): (isLoading: boolean) => void { - return (isLoading: boolean) => { +export function loading(): (isLoading?: boolean) => void { + return (isLoading?: boolean) => { console.log('[MockAppBridge] Loading:', isLoading); - invokeFeature('loading', 'setLoading', { isLoading }); + invokeFeature('loading', 'setLoading', { isLoading: Boolean(isLoading) }); }; } diff --git a/app-bridge/src/features/resource-picker.ts b/app-bridge/src/features/resource-picker.ts index 1dc7b3a..94e0535 100644 --- a/app-bridge/src/features/resource-picker.ts +++ b/app-bridge/src/features/resource-picker.ts @@ -1,10 +1,23 @@ +import type { ResourcePickerBridgeOpenPayload } from '../resource-picker-bridge'; +import { openMockResourcePickerFromBridge } from '../resource-picker-bridge'; export function resourcePicker(): NonNullable['resourcePicker'] { return async (options) => { - console.log('[MockAppBridge] Resource picker opened with options:', options); + const payload: ResourcePickerBridgeOpenPayload = { + type: + options?.type === 'variant' + ? 'variant' + : options?.type === 'collection' + ? 'collection' + : 'product', + multiple: options?.multiple === true, + selectionIds: options?.selectionIds as string[] | undefined, + }; - // TODO: Resource picker types are a bit complicated. - // But this should be enough for a placeholder - return Promise.resolve([] as any); + const result = await openMockResourcePickerFromBridge(payload); + if (result.cancelled) { + return [] as any; + } + return result.selection as any; }; } diff --git a/app-bridge/src/index.ts b/app-bridge/src/index.ts index 11a7b38..6f8740f 100644 --- a/app-bridge/src/index.ts +++ b/app-bridge/src/index.ts @@ -21,6 +21,10 @@ import { picker } from "./features/picker"; import { app } from "./features/app"; import { loading } from "./features/loading"; import { navMenu } from "./features/nav-menu"; +import { + bridgePayloadFromLegacyResourcePickerOptions, + openMockResourcePickerFromBridge, +} from "./resource-picker-bridge"; (function (window) { 'use strict'; @@ -119,21 +123,35 @@ import { navMenu } from "./features/nav-menu"; return { dispatch: function (action: any) { if (action === Actions.ResourcePicker.Action.OPEN) { - // Mock selection after a delay - setTimeout(() => { - const mockSelection = { - selection: [{ - id: 'gid://shopify/Product/123456', - title: 'Mock Product', - handle: 'mock-product', - }], - }; - subscribers.forEach(callback => { - if (callback.action === Actions.ResourcePicker.Action.SELECT) { - callback.handler(mockSelection); + void openMockResourcePickerFromBridge( + bridgePayloadFromLegacyResourcePickerOptions({ + type: options?.type, + multiple: options?.multiple, + selectionIds: options?.selectionIds, + }), + ) + .then((result) => { + if (result.cancelled) { + subscribers.forEach((callback: { action: string; handler: (p: unknown) => void }) => { + if (callback.action === Actions.ResourcePicker.Action.CANCEL) { + callback.handler({}); + } + }); + } else { + subscribers.forEach((callback: { action: string; handler: (p: unknown) => void }) => { + if (callback.action === Actions.ResourcePicker.Action.SELECT) { + callback.handler({ selection: result.selection }); + } + }); } + }) + .catch(() => { + subscribers.forEach((callback: { action: string; handler: (p: unknown) => void }) => { + if (callback.action === Actions.ResourcePicker.Action.CANCEL) { + callback.handler({}); + } + }); }); - }, 100); } }, subscribe: function (action: any, handler: any) { @@ -420,7 +438,7 @@ import { navMenu } from "./features/nav-menu"; const detectMockServerUrl = (): string => { // Try to get from current script src const scripts = document.querySelectorAll('script[src*="app-bridge.js"]'); - for (const script of scripts) { + for (const script of Array.from(scripts)) { const src = (script as HTMLScriptElement).src; if (src) { const url = new URL(src); diff --git a/app-bridge/src/invokeFeature.ts b/app-bridge/src/invokeFeature.ts index 6401e83..46177a3 100644 --- a/app-bridge/src/invokeFeature.ts +++ b/app-bridge/src/invokeFeature.ts @@ -1,26 +1,30 @@ import { v4 as uuidv4 } from 'uuid'; -import type { FeatureActionRequest } from '../../admin-frame/src/hooks/useMockBridge'; -import { type FeatureActionName, type FeatureActionPayload, type FeatureName } from '../../admin-frame/src/store/features'; -export function invokeFeature, P extends FeatureActionPayload>(feature: F, action: A, payload: P) { +export function invokeFeature(feature: string, action: string, payload: unknown) { return new Promise((resolve, reject) => { const actionId = uuidv4(); - const request: FeatureActionRequest = { + const request = { feature, action, payload, - } + }; + + const timeoutMs = + feature === 'resourcePicker' && action === 'open' ? 300_000 : 1_000; - window.parent.postMessage({ - type: 'FEATURE_ACTION_REQUEST', - action_id: actionId, - ...request, - }, '*'); + window.parent.postMessage( + { + type: 'FEATURE_ACTION_REQUEST', + action_id: actionId, + ...request, + }, + '*', + ); const rejectTimeout = setTimeout(() => { - reject(new Error('Feature action timed out after 1 second')); - }, 1000); + reject(new Error(`Feature action timed out after ${timeoutMs} ms`)); + }, timeoutMs); const handler = (event: MessageEvent) => { if (event.data && event.data.type === 'FEATURE_ACTION_RESPONSE') { @@ -35,4 +39,4 @@ export function invokeFeature { + const raw = (await invokeFeature( + 'resourcePicker', + 'open', + options, + )) as ResourcePickerBridgeResult; + if (!raw || typeof raw !== 'object') { + return { cancelled: true, selection: [] }; + } + return { + cancelled: Boolean(raw.cancelled), + selection: Array.isArray(raw.selection) ? raw.selection : [], + }; +} + +/** Map legacy App Bridge ResourcePicker option enums to bridge payload. */ +export function bridgePayloadFromLegacyResourcePickerOptions(options: { + type?: string; + multiple?: boolean; + selectionIds?: string[]; +}): ResourcePickerBridgeOpenPayload { + let type: ResourcePickerBridgeOpenPayload['type'] = 'product'; + if (options?.type === 'variant' || options?.type === 'product_variant') { + type = 'variant'; + } else if (options?.type === 'collection') { + type = 'collection'; + } + return { + type, + multiple: options?.multiple, + selectionIds: options?.selectionIds, + }; +} diff --git a/dist/client/mock-detector.d.ts b/dist/client/mock-detector.d.ts index 5d9bcc8..4884b50 100644 --- a/dist/client/mock-detector.d.ts +++ b/dist/client/mock-detector.d.ts @@ -5,7 +5,7 @@ * and automatically loads the appropriate App Bridge library. */ export interface MockEnvironmentConfig { - /** Timeout in milliseconds to wait for mock environment signal (default: 200ms) */ + /** Timeout in milliseconds to wait for mock environment signal before loading Shopify CDN App Bridge (default: 8000ms) */ timeout?: number; /** Enable debug logging (default: false) */ debug?: boolean; diff --git a/dist/client/mock-detector.d.ts.map b/dist/client/mock-detector.d.ts.map index cacbacf..c22cdca 100644 --- a/dist/client/mock-detector.d.ts.map +++ b/dist/client/mock-detector.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"mock-detector.d.ts","sourceRoot":"","sources":["../../src/client/mock-detector.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,qBAAqB;IACpC,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iDAAiD;IACjD,cAAc,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,yDAAyD;IACzD,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;CAChC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,GAAE,qBAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAwFhF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAEhD"} \ No newline at end of file +{"version":3,"file":"mock-detector.d.ts","sourceRoot":"","sources":["../../src/client/mock-detector.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,WAAW,qBAAqB;IACpC,0HAA0H;IAC1H,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iDAAiD;IACjD,cAAc,CAAC,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,yDAAyD;IACzD,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;CAChC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,GAAE,qBAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAyFhF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAEhD"} \ No newline at end of file diff --git a/dist/client/mock-detector.js b/dist/client/mock-detector.js index ce2ab1d..8e7cf9a 100644 --- a/dist/client/mock-detector.js +++ b/dist/client/mock-detector.js @@ -16,7 +16,8 @@ exports.getMockServerUrl = getMockServerUrl; * @returns Promise that resolves when App Bridge is loaded */ function setupAppBridge(config = {}) { - const { timeout = 200, debug = false, onMockDetected, onShopifyDetected } = config; + // Default long enough for slow iframe loads + JS bundles before falling back to Shopify CDN (avoids loading real App Bridge and breaking resourcePicker, etc.) + const { timeout = 8000, debug = false, onMockDetected, onShopifyDetected } = config; return new Promise((resolve, reject) => { let isResolved = false; let isMockEnvironment = false; diff --git a/dist/client/mock-detector.js.map b/dist/client/mock-detector.js.map index 21d80e9..a44d611 100644 --- a/dist/client/mock-detector.js.map +++ b/dist/client/mock-detector.js.map @@ -1 +1 @@ -{"version":3,"file":"mock-detector.js","sourceRoot":"","sources":["../../src/client/mock-detector.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"mock-detector.js","sourceRoot":"","sources":["../../src/client/mock-detector.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/mock-resource-picker-catalog.d.ts b/dist/mock-resource-picker-catalog.d.ts new file mode 100644 index 0000000..7dba18c --- /dev/null +++ b/dist/mock-resource-picker-catalog.d.ts @@ -0,0 +1,35 @@ +/** + * Shared mock catalog for resource picker API and optional server config override. + */ +export type MockResourcePickerCollection = { + id: string; + title: string; + handle: string; +}; +export type MockResourcePickerVariant = { + id: string; + title: string; + displayName: string; + price: string; + sku?: string; + inventoryQuantity?: number; + productId: string; + productTitle: string; + productHandle: string; +}; +export type MockResourcePickerProduct = { + id: string; + title: string; + handle: string; + status: string; + variants: MockResourcePickerVariant[]; +}; +export type ResourcePickerCatalogResponse = { + products: MockResourcePickerProduct[]; + collections: MockResourcePickerCollection[]; +}; +/** Flattened variant rows for variant-only picker */ +export declare function flattenVariants(products: MockResourcePickerProduct[]): MockResourcePickerVariant[]; +export declare function getDefaultResourcePickerCatalog(): ResourcePickerCatalogResponse; +export declare function mergeResourcePickerCatalog(base: ResourcePickerCatalogResponse, override?: Partial | null): ResourcePickerCatalogResponse; +//# sourceMappingURL=mock-resource-picker-catalog.d.ts.map \ No newline at end of file diff --git a/dist/mock-resource-picker-catalog.d.ts.map b/dist/mock-resource-picker-catalog.d.ts.map new file mode 100644 index 0000000..dd6f832 --- /dev/null +++ b/dist/mock-resource-picker-catalog.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"mock-resource-picker-catalog.d.ts","sourceRoot":"","sources":["../src/mock-resource-picker-catalog.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,4BAA4B,GAAG;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,yBAAyB,EAAE,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,6BAA6B,GAAG;IAC1C,QAAQ,EAAE,yBAAyB,EAAE,CAAC;IACtC,WAAW,EAAE,4BAA4B,EAAE,CAAC;CAC7C,CAAC;AAEF,qDAAqD;AACrD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,yBAAyB,EAAE,GAAG,yBAAyB,EAAE,CAQlG;AAED,wBAAgB,+BAA+B,IAAI,6BAA6B,CAmE/E;AAED,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,6BAA6B,EACnC,QAAQ,CAAC,EAAE,OAAO,CAAC,6BAA6B,CAAC,GAAG,IAAI,GACvD,6BAA6B,CAM/B"} \ No newline at end of file diff --git a/dist/mock-resource-picker-catalog.js b/dist/mock-resource-picker-catalog.js new file mode 100644 index 0000000..90a9c0a --- /dev/null +++ b/dist/mock-resource-picker-catalog.js @@ -0,0 +1,93 @@ +"use strict"; +/** + * Shared mock catalog for resource picker API and optional server config override. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.flattenVariants = flattenVariants; +exports.getDefaultResourcePickerCatalog = getDefaultResourcePickerCatalog; +exports.mergeResourcePickerCatalog = mergeResourcePickerCatalog; +/** Flattened variant rows for variant-only picker */ +function flattenVariants(products) { + const out = []; + for (const p of products) { + for (const v of p.variants) { + out.push(v); + } + } + return out; +} +function getDefaultResourcePickerCatalog() { + const products = [ + { + id: 'gid://shopify/Product/1', + title: 'Mock Product 1', + handle: 'mock-product-1', + status: 'ACTIVE', + variants: [ + { + id: 'gid://shopify/ProductVariant/101', + title: 'Default Title', + displayName: 'Mock Product 1 · Default', + price: '19.99', + sku: 'MOCK-1-DEFAULT', + inventoryQuantity: 100, + productId: 'gid://shopify/Product/1', + productTitle: 'Mock Product 1', + productHandle: 'mock-product-1', + }, + { + id: 'gid://shopify/ProductVariant/102', + title: 'Alternate', + displayName: 'Mock Product 1 · Alternate', + price: '21.99', + sku: 'MOCK-1-ALT', + inventoryQuantity: 25, + productId: 'gid://shopify/Product/1', + productTitle: 'Mock Product 1', + productHandle: 'mock-product-1', + }, + ], + }, + { + id: 'gid://shopify/Product/2', + title: 'Mock Product 2', + handle: 'mock-product-2', + status: 'ACTIVE', + variants: [ + { + id: 'gid://shopify/ProductVariant/201', + title: 'Default Title', + displayName: 'Mock Product 2 · Default', + price: '29.99', + sku: 'MOCK-2-DEFAULT', + inventoryQuantity: 50, + productId: 'gid://shopify/Product/2', + productTitle: 'Mock Product 2', + productHandle: 'mock-product-2', + }, + ], + }, + ]; + const collections = [ + { + id: 'gid://shopify/Collection/1', + title: 'Mock Collection — Featured', + handle: 'mock-featured', + }, + { + id: 'gid://shopify/Collection/2', + title: 'Mock Collection — Sale', + handle: 'mock-sale', + }, + ]; + return { products, collections }; +} +function mergeResourcePickerCatalog(base, override) { + if (!override) + return base; + return { + products: override.products?.length ? override.products : base.products, + collections: override.collections?.length ? override.collections : base.collections, + }; +} +//# sourceMappingURL=mock-resource-picker-catalog.js.map \ No newline at end of file diff --git a/dist/mock-resource-picker-catalog.js.map b/dist/mock-resource-picker-catalog.js.map new file mode 100644 index 0000000..e121339 --- /dev/null +++ b/dist/mock-resource-picker-catalog.js.map @@ -0,0 +1 @@ +{"version":3,"file":"mock-resource-picker-catalog.js","sourceRoot":"","sources":["../src/mock-resource-picker-catalog.ts"],"names":[],"mappings":";AAAA;;GAEG;;AAkCH,0CAQC;AAED,0EAmEC;AAED,gEASC;AAzFD,qDAAqD;AACrD,SAAgB,eAAe,CAAC,QAAqC;IACnE,MAAM,GAAG,GAAgC,EAAE,CAAC;IAC5C,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;YAC3B,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAgB,+BAA+B;IAC7C,MAAM,QAAQ,GAAgC;QAC5C;YACE,EAAE,EAAE,yBAAyB;YAC7B,KAAK,EAAE,gBAAgB;YACvB,MAAM,EAAE,gBAAgB;YACxB,MAAM,EAAE,QAAQ;YAChB,QAAQ,EAAE;gBACR;oBACE,EAAE,EAAE,kCAAkC;oBACtC,KAAK,EAAE,eAAe;oBACtB,WAAW,EAAE,0BAA0B;oBACvC,KAAK,EAAE,OAAO;oBACd,GAAG,EAAE,gBAAgB;oBACrB,iBAAiB,EAAE,GAAG;oBACtB,SAAS,EAAE,yBAAyB;oBACpC,YAAY,EAAE,gBAAgB;oBAC9B,aAAa,EAAE,gBAAgB;iBAChC;gBACD;oBACE,EAAE,EAAE,kCAAkC;oBACtC,KAAK,EAAE,WAAW;oBAClB,WAAW,EAAE,4BAA4B;oBACzC,KAAK,EAAE,OAAO;oBACd,GAAG,EAAE,YAAY;oBACjB,iBAAiB,EAAE,EAAE;oBACrB,SAAS,EAAE,yBAAyB;oBACpC,YAAY,EAAE,gBAAgB;oBAC9B,aAAa,EAAE,gBAAgB;iBAChC;aACF;SACF;QACD;YACE,EAAE,EAAE,yBAAyB;YAC7B,KAAK,EAAE,gBAAgB;YACvB,MAAM,EAAE,gBAAgB;YACxB,MAAM,EAAE,QAAQ;YAChB,QAAQ,EAAE;gBACR;oBACE,EAAE,EAAE,kCAAkC;oBACtC,KAAK,EAAE,eAAe;oBACtB,WAAW,EAAE,0BAA0B;oBACvC,KAAK,EAAE,OAAO;oBACd,GAAG,EAAE,gBAAgB;oBACrB,iBAAiB,EAAE,EAAE;oBACrB,SAAS,EAAE,yBAAyB;oBACpC,YAAY,EAAE,gBAAgB;oBAC9B,aAAa,EAAE,gBAAgB;iBAChC;aACF;SACF;KACF,CAAC;IAEF,MAAM,WAAW,GAAmC;QAClD;YACE,EAAE,EAAE,4BAA4B;YAChC,KAAK,EAAE,4BAA4B;YACnC,MAAM,EAAE,eAAe;SACxB;QACD;YACE,EAAE,EAAE,4BAA4B;YAChC,KAAK,EAAE,wBAAwB;YAC/B,MAAM,EAAE,WAAW;SACpB;KACF,CAAC;IAEF,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC;AACnC,CAAC;AAED,SAAgB,0BAA0B,CACxC,IAAmC,EACnC,QAAwD;IAExD,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,OAAO;QACL,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ;QACvE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW;KACpF,CAAC;AACJ,CAAC"} \ No newline at end of file diff --git a/dist/react/AppProvider.d.ts b/dist/react/AppProvider.d.ts new file mode 100644 index 0000000..82587f9 --- /dev/null +++ b/dist/react/AppProvider.d.ts @@ -0,0 +1,22 @@ +/** + * Mirrors @shopify/shopify-app-react-router AppProvider, with an optional App Bridge script URL. + * Shopify's AppProvider always loads the CDN script and ignores unknown props such as __APP_BRIDGE_URL. + */ +import React, { type ReactNode } from 'react'; +export declare const SHOPIFY_APP_BRIDGE_CDN = "https://cdn.shopify.com/shopifycloud/app-bridge.js"; +interface BaseProps { + children: ReactNode; +} +interface EmbeddedProps extends BaseProps { + embedded: true; + apiKey: string; + /** e.g. http://localhost:3080/app-bridge.js when using MockShopifyAdminServer */ + appBridgeUrl?: string; +} +interface NonEmbeddedProps extends BaseProps { + embedded?: false; +} +export type MockBridgeAppProviderProps = EmbeddedProps | NonEmbeddedProps; +export declare function MockBridgeAppProvider(props: MockBridgeAppProviderProps): React.FunctionComponentElement; +export {}; +//# sourceMappingURL=AppProvider.d.ts.map \ No newline at end of file diff --git a/dist/react/AppProvider.d.ts.map b/dist/react/AppProvider.d.ts.map new file mode 100644 index 0000000..55d37f6 --- /dev/null +++ b/dist/react/AppProvider.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"AppProvider.d.ts","sourceRoot":"","sources":["../../src/react/AppProvider.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,EAAa,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGzD,eAAO,MAAM,sBAAsB,uDACmB,CAAC;AAGvD,UAAU,SAAS;IACjB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,UAAU,aAAc,SAAQ,SAAS;IACvC,QAAQ,EAAE,IAAI,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,UAAU,gBAAiB,SAAQ,SAAS;IAC1C,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB;AAED,MAAM,MAAM,0BAA0B,GAAG,aAAa,GAAG,gBAAgB,CAAC;AA0B1E,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,uDAmBtE"} \ No newline at end of file diff --git a/dist/react/AppProvider.js b/dist/react/AppProvider.js new file mode 100644 index 0000000..3c10cec --- /dev/null +++ b/dist/react/AppProvider.js @@ -0,0 +1,80 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +exports.SHOPIFY_APP_BRIDGE_CDN = void 0; +exports.MockBridgeAppProvider = MockBridgeAppProvider; +/** + * Mirrors @shopify/shopify-app-react-router AppProvider, with an optional App Bridge script URL. + * Shopify's AppProvider always loads the CDN script and ignores unknown props such as __APP_BRIDGE_URL. + */ +const react_1 = __importStar(require("react")); +const react_router_1 = require("react-router"); +exports.SHOPIFY_APP_BRIDGE_CDN = 'https://cdn.shopify.com/shopifycloud/app-bridge.js'; +const POLARIS_CDN = 'https://cdn.shopify.com/shopifycloud/polaris.js'; +function AppBridgeScript({ apiKey, src }) { + const navigate = (0, react_router_1.useNavigate)(); + (0, react_1.useEffect)(() => { + const handleNavigate = (event) => { + const href = event.target?.getAttribute('href'); + if (href) { + navigate(href); + } + }; + document.addEventListener('shopify:navigate', handleNavigate); + return () => { + document.removeEventListener('shopify:navigate', handleNavigate); + }; + }, [navigate]); + return react_1.default.createElement('script', { + src, + 'data-api-key': apiKey, + }); +} +function MockBridgeAppProvider(props) { + const nodes = []; + if (props.embedded) { + const { apiKey, appBridgeUrl } = props; + const src = appBridgeUrl ?? exports.SHOPIFY_APP_BRIDGE_CDN; + nodes.push(react_1.default.createElement(AppBridgeScript, { + key: 'app-bridge', + apiKey, + src, + })); + } + nodes.push(react_1.default.createElement('script', { key: 'polaris', src: POLARIS_CDN })); + nodes.push(props.children); + return react_1.default.createElement(react_1.default.Fragment, null, ...nodes); +} +//# sourceMappingURL=AppProvider.js.map \ No newline at end of file diff --git a/dist/react/AppProvider.js.map b/dist/react/AppProvider.js.map new file mode 100644 index 0000000..9b7bcf3 --- /dev/null +++ b/dist/react/AppProvider.js.map @@ -0,0 +1 @@ +{"version":3,"file":"AppProvider.js","sourceRoot":"","sources":["../../src/react/AppProvider.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDA,sDAmBC;AAvED;;;GAGG;AACH,+CAAyD;AACzD,+CAA2C;AAE9B,QAAA,sBAAsB,GACjC,oDAAoD,CAAC;AACvD,MAAM,WAAW,GAAG,iDAAiD,CAAC;AAmBtE,SAAS,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,EAAmC;IACvE,MAAM,QAAQ,GAAG,IAAA,0BAAW,GAAE,CAAC;IAE/B,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,MAAM,cAAc,GAAG,CAAC,KAAY,EAAE,EAAE;YACtC,MAAM,IAAI,GAAI,KAAK,CAAC,MAAsB,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;YACjE,IAAI,IAAI,EAAE,CAAC;gBACT,QAAQ,CAAC,IAAI,CAAC,CAAC;YACjB,CAAC;QACH,CAAC,CAAC;QAEF,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,cAAc,CAAC,CAAC;QAE9D,OAAO,GAAG,EAAE;YACV,QAAQ,CAAC,mBAAmB,CAAC,kBAAkB,EAAE,cAAc,CAAC,CAAC;QACnE,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEf,OAAO,eAAK,CAAC,aAAa,CAAC,QAAQ,EAAE;QACnC,GAAG;QACH,cAAc,EAAE,MAAM;KACvB,CAAC,CAAC;AACL,CAAC;AAED,SAAgB,qBAAqB,CAAC,KAAiC;IACrE,MAAM,KAAK,GAAsB,EAAE,CAAC;IAEpC,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACnB,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,KAAK,CAAC;QACvC,MAAM,GAAG,GAAG,YAAY,IAAI,8BAAsB,CAAC;QACnD,KAAK,CAAC,IAAI,CACR,eAAK,CAAC,aAAa,CAAC,eAAe,EAAE;YACnC,GAAG,EAAE,YAAY;YACjB,MAAM;YACN,GAAG;SACJ,CAAC,CACH,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,eAAK,CAAC,aAAa,CAAC,QAAQ,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;IAChF,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAE3B,OAAO,eAAK,CAAC,aAAa,CAAC,eAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,CAAC;AAC7D,CAAC"} \ No newline at end of file diff --git a/dist/react/EmbeddedShopifyAppProvider.d.ts b/dist/react/EmbeddedShopifyAppProvider.d.ts new file mode 100644 index 0000000..d0ec339 --- /dev/null +++ b/dist/react/EmbeddedShopifyAppProvider.d.ts @@ -0,0 +1,15 @@ +/** + * TypeScript-friendly alternative to `const Provider = mock ? A : B` (unions are not valid JSX components). + */ +import React, { type ReactNode } from 'react'; +export type EmbeddedShopifyAppProviderProps = { + embedded: true; + apiKey: string; + children: ReactNode; + /** + * Mock admin origin (e.g. `http://localhost:3080`). When set, loads `app-bridge.js` from that host. + */ + mockOrigin?: string | null; +}; +export declare function EmbeddedShopifyAppProvider({ embedded, apiKey, children, mockOrigin, }: EmbeddedShopifyAppProviderProps): React.FunctionComponentElement | React.FunctionComponentElement; +//# sourceMappingURL=EmbeddedShopifyAppProvider.d.ts.map \ No newline at end of file diff --git a/dist/react/EmbeddedShopifyAppProvider.d.ts.map b/dist/react/EmbeddedShopifyAppProvider.d.ts.map new file mode 100644 index 0000000..274092d --- /dev/null +++ b/dist/react/EmbeddedShopifyAppProvider.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"EmbeddedShopifyAppProvider.d.ts","sourceRoot":"","sources":["../../src/react/EmbeddedShopifyAppProvider.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAI9C,MAAM,MAAM,+BAA+B,GAAG;IAC5C,QAAQ,EAAE,IAAI,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,SAAS,CAAC;IACpB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF,wBAAgB,0BAA0B,CAAC,EACzC,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,UAAU,GACX,EAAE,+BAA+B,2LAajC"} \ No newline at end of file diff --git a/dist/react/EmbeddedShopifyAppProvider.js b/dist/react/EmbeddedShopifyAppProvider.js new file mode 100644 index 0000000..0fe950d --- /dev/null +++ b/dist/react/EmbeddedShopifyAppProvider.js @@ -0,0 +1,26 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.EmbeddedShopifyAppProvider = EmbeddedShopifyAppProvider; +/** + * TypeScript-friendly alternative to `const Provider = mock ? A : B` (unions are not valid JSX components). + */ +const react_1 = __importDefault(require("react")); +const react_2 = require("@shopify/shopify-app-react-router/react"); +const AppProvider_1 = require("./AppProvider"); +function EmbeddedShopifyAppProvider({ embedded, apiKey, children, mockOrigin, }) { + const origin = mockOrigin?.trim(); + if (origin) { + const base = origin.replace(/\/$/, ''); + return react_1.default.createElement(AppProvider_1.MockBridgeAppProvider, { + embedded, + apiKey, + appBridgeUrl: `${base}/app-bridge.js`, + children, + }); + } + return react_1.default.createElement(react_2.AppProvider, { embedded, apiKey, children }); +} +//# sourceMappingURL=EmbeddedShopifyAppProvider.js.map \ No newline at end of file diff --git a/dist/react/EmbeddedShopifyAppProvider.js.map b/dist/react/EmbeddedShopifyAppProvider.js.map new file mode 100644 index 0000000..ebb3890 --- /dev/null +++ b/dist/react/EmbeddedShopifyAppProvider.js.map @@ -0,0 +1 @@ +{"version":3,"file":"EmbeddedShopifyAppProvider.js","sourceRoot":"","sources":["../../src/react/EmbeddedShopifyAppProvider.ts"],"names":[],"mappings":";;;;;AAiBA,gEAkBC;AAnCD;;GAEG;AACH,kDAA8C;AAC9C,mEAAsE;AACtE,+CAAsD;AAYtD,SAAgB,0BAA0B,CAAC,EACzC,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,UAAU,GACsB;IAChC,MAAM,MAAM,GAAG,UAAU,EAAE,IAAI,EAAE,CAAC;IAClC,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACvC,OAAO,eAAK,CAAC,aAAa,CAAC,mCAAqB,EAAE;YAChD,QAAQ;YACR,MAAM;YACN,YAAY,EAAE,GAAG,IAAI,gBAAgB;YACrC,QAAQ;SACT,CAAC,CAAC;IACL,CAAC;IAED,OAAO,eAAK,CAAC,aAAa,CAAC,mBAAW,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;AAC1E,CAAC"} \ No newline at end of file diff --git a/dist/react/index.d.ts b/dist/react/index.d.ts new file mode 100644 index 0000000..38883c6 --- /dev/null +++ b/dist/react/index.d.ts @@ -0,0 +1,3 @@ +export { MockBridgeAppProvider, type MockBridgeAppProviderProps, SHOPIFY_APP_BRIDGE_CDN, } from './AppProvider'; +export { EmbeddedShopifyAppProvider, type EmbeddedShopifyAppProviderProps, } from './EmbeddedShopifyAppProvider'; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/dist/react/index.d.ts.map b/dist/react/index.d.ts.map new file mode 100644 index 0000000..ff83f20 --- /dev/null +++ b/dist/react/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,KAAK,0BAA0B,EAC/B,sBAAsB,GACvB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,0BAA0B,EAC1B,KAAK,+BAA+B,GACrC,MAAM,8BAA8B,CAAC"} \ No newline at end of file diff --git a/dist/react/index.js b/dist/react/index.js new file mode 100644 index 0000000..f8b5c65 --- /dev/null +++ b/dist/react/index.js @@ -0,0 +1,9 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.EmbeddedShopifyAppProvider = exports.SHOPIFY_APP_BRIDGE_CDN = exports.MockBridgeAppProvider = void 0; +var AppProvider_1 = require("./AppProvider"); +Object.defineProperty(exports, "MockBridgeAppProvider", { enumerable: true, get: function () { return AppProvider_1.MockBridgeAppProvider; } }); +Object.defineProperty(exports, "SHOPIFY_APP_BRIDGE_CDN", { enumerable: true, get: function () { return AppProvider_1.SHOPIFY_APP_BRIDGE_CDN; } }); +var EmbeddedShopifyAppProvider_1 = require("./EmbeddedShopifyAppProvider"); +Object.defineProperty(exports, "EmbeddedShopifyAppProvider", { enumerable: true, get: function () { return EmbeddedShopifyAppProvider_1.EmbeddedShopifyAppProvider; } }); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/react/index.js.map b/dist/react/index.js.map new file mode 100644 index 0000000..87b2b15 --- /dev/null +++ b/dist/react/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":";;;AAAA,6CAIuB;AAHrB,oHAAA,qBAAqB,OAAA;AAErB,qHAAA,sBAAsB,OAAA;AAExB,2EAGsC;AAFpC,wIAAA,0BAA0B,OAAA"} \ No newline at end of file diff --git a/dist/server/index.d.ts b/dist/server/index.d.ts index 5062ff9..71ad3cf 100644 --- a/dist/server/index.d.ts +++ b/dist/server/index.d.ts @@ -6,6 +6,7 @@ export declare class MockShopifyAdminServer { private server; private mockShop; private mockUser; + private resourcePickerCatalog; constructor(config: MockShopifyAdminConfig); private setupMiddleware; private setupRoutes; diff --git a/dist/server/index.d.ts.map b/dist/server/index.d.ts.map index 53bc0b5..4da205c 100644 --- a/dist/server/index.d.ts.map +++ b/dist/server/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,sBAAsB,EAAsB,MAAM,UAAU,CAAC;AAGtE,qBAAa,sBAAsB;IACjC,OAAO,CAAC,GAAG,CAAU;IACrB,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,QAAQ,CAAW;gBAEf,MAAM,EAAE,sBAAsB;IAqC1C,OAAO,CAAC,eAAe;IAyCvB,OAAO,CAAC,WAAW;IA6InB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAwHzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA6EZ,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAa3B,SAAS,IAAI,sBAAsB;CAG3C"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,sBAAsB,EAAsB,MAAM,UAAU,CAAC;AAStE,qBAAa,sBAAsB;IACjC,OAAO,CAAC,GAAG,CAAU;IACrB,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,QAAQ,CAAW;IAC3B,OAAO,CAAC,qBAAqB,CAAgC;gBAEjD,MAAM,EAAE,sBAAsB;IA0C1C,OAAO,CAAC,eAAe;IAyCvB,OAAO,CAAC,WAAW;IAwKnB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAwHzB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA6EZ,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAa3B,SAAS,IAAI,sBAAsB;CAG3C"} \ No newline at end of file diff --git a/dist/server/index.js b/dist/server/index.js index 55366ea..ff9b1db 100644 --- a/dist/server/index.js +++ b/dist/server/index.js @@ -11,6 +11,7 @@ const path_1 = __importDefault(require("path")); const http_proxy_middleware_1 = require("http-proxy-middleware"); const token_generator_1 = require("../auth/token-generator"); const constants_1 = require("../auth/constants"); +const mock_resource_picker_catalog_1 = require("../mock-resource-picker-catalog"); class MockShopifyAdminServer { constructor(config) { this.config = { @@ -41,6 +42,7 @@ class MockShopifyAdminServer { lastName: 'User', displayName: 'Test User', }; + this.resourcePickerCatalog = (0, mock_resource_picker_catalog_1.mergeResourcePickerCatalog)((0, mock_resource_picker_catalog_1.getDefaultResourcePickerCatalog)(), this.config.resourcePickerCatalog ?? null); this.setupMiddleware(); this.setupRoutes(); } @@ -98,6 +100,23 @@ class MockShopifyAdminServer { proxy: this.config.proxy, }); }); + // Mock resource picker catalog (admin-frame UI) + this.app.get('/api/resource-picker-catalog', (req, res) => { + const q = String(req.query.q ?? '') + .trim() + .toLowerCase(); + const { products, collections } = this.resourcePickerCatalog; + const variants = (0, mock_resource_picker_catalog_1.flattenVariants)(products); + const match = (s) => !q || s.toLowerCase().includes(q); + res.json({ + products: products.filter((p) => match(p.title) || match(p.handle) || match(p.id)), + variants: variants.filter((v) => match(v.displayName) || + match(v.title) || + match(v.id) || + (v.sku ? match(v.sku) : false)), + collections: collections.filter((c) => match(c.title) || match(c.handle) || match(c.id)), + }); + }); // Session token endpoint this.app.post('/api/session-token', (req, res) => { const token = this.tokenGenerator.generateSessionToken({ diff --git a/dist/server/index.js.map b/dist/server/index.js.map index 91559de..62f67fe 100644 --- a/dist/server/index.js.map +++ b/dist/server/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";;;;;;AAAA,sDAA8D;AAC9D,gDAAwB;AACxB,8DAAqC;AACrC,gDAAwB;AACxB,iEAA8D;AAC9D,6DAAyD;AAEzD,iDAAkF;AAElF,MAAa,sBAAsB;IAQjC,YAAY,MAA8B;QACxC,IAAI,CAAC,MAAM,GAAG;YACZ,IAAI,EAAE,IAAI;YACV,IAAI,EAAE,yBAAyB;YAC/B,QAAQ,EAAE,mCAAuB;YACjC,YAAY,EAAE,gCAAoB;YAClC,UAAU,EAAE,SAAS;YACrB,MAAM,EAAE,CAAC,eAAe,EAAE,gBAAgB,EAAE,aAAa,EAAE,cAAc,CAAC;YAC1E,KAAK,EAAE,KAAK;YACZ,QAAQ,EAAE,MAAM;YAChB,GAAG,MAAM;SACV,CAAC;QAEF,IAAI,CAAC,GAAG,GAAG,IAAA,iBAAO,GAAE,CAAC;QACrB,IAAI,CAAC,cAAc,GAAG,IAAI,gCAAc,CAAC,IAAI,CAAC,MAAM,CAAC,YAAa,CAAC,CAAC;QAEpE,uBAAuB;QACvB,IAAI,CAAC,QAAQ,GAAG;YACd,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,IAAK;YACzB,IAAI,EAAE,WAAW;YACjB,KAAK,EAAE,eAAe;YACtB,IAAI,EAAE,WAAW;YACjB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC;QAEF,IAAI,CAAC,QAAQ,GAAG;YACd,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,WAAW;YACrC,KAAK,EAAE,mBAAmB;YAC1B,SAAS,EAAE,MAAM;YACjB,QAAQ,EAAE,MAAM;YAChB,WAAW,EAAE,WAAW;SACzB,CAAC;QAEF,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,IAAI,CAAC,WAAW,EAAE,CAAC;IACrB,CAAC;IAEO,eAAe;QACrB,2CAA2C;QAC3C,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAA,cAAI,EAAC;YAChB,MAAM,EAAE,IAAI;YACZ,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC,CAAC;QAEJ,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,qBAAU,CAAC,IAAI,EAAE,CAAC,CAAC;QAChC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,qBAAU,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAExD,2CAA2C;QAC3C,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,iBAAO,CAAC,MAAM,CAAC,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;QAC3E,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,MAAM,CAAC,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,wBAAwB,CAAC,CAAC,CAAC,CAAC;QAE7E,4CAA4C;QAC5C,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,uBAAuB,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,IAAI,EAAE,EAAE;YAC1E,oCAAoC;YAEpC,2CAA2C;YAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YAC/E,GAAG,CAAC,SAAS,CAAC,yBAAyB,EACrC,aAAa,QAAQ,IAAI;gBACzB,sCAAsC;gBACtC,kDAAkD,CACnD,CAAC;YAEF,+DAA+D;YAC/D,IAAI,EAAE,CAAC;QACT,CAAC,CAAC,CAAC;QAEH,0FAA0F;QAE1F,gBAAgB;QAChB,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACtB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;gBAC9B,OAAO,CAAC,GAAG,CAAC,iBAAiB,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;gBACtD,IAAI,EAAE,CAAC;YACT,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAEO,WAAW;QACjB,oBAAoB;QACpB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;YACpD,GAAG,CAAC,QAAQ,CAAC,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,6CAA6C,CAAC,CAAC,CAAC;QACpF,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;YAC3D,GAAG,CAAC,QAAQ,CAAC,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,6CAA6C,CAAC,CAAC,CAAC;QACpF,CAAC,CAAC,CAAC;QAEH,wDAAwD;QACxD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;YAChD,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;YACjF,GAAG,CAAC,QAAQ,CAAC,eAAe,IAAI,CAAC,MAAM,CAAC,QAAQ,SAAS,UAAU,SAAS,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QAClG,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;YAC1D,GAAG,CAAC,IAAI,CAAC;gBACP,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;gBAC9B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI;gBACtB,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;gBAC1B,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,OAAO;gBAC5B,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;gBAC9B,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK;aACzB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,yBAAyB;QACzB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,oBAAoB,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;YAClE,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,CAAC,oBAAoB,CAAC;gBACrD,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAK;gBACvB,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAS;gBAC/B,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,YAAa;gBACvC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,EAAE;aACzB,CAAC,CAAC;YAEH,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACtB,CAAC,CAAC,CAAC;QAEH,kCAAkC;QAClC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,kCAAkC,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;YAChF,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;YAE7C,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBACrD,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;oBAC1B,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;iBACtC,CAAC,CAAC;YACL,CAAC;YAED,4CAA4C;YAC5C,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI,EAAE;oBACJ,IAAI,EAAE;wBACJ,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI;wBACxB,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK;wBAC1B,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM;qBAC7B;iBACF;aACF,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,kFAAkF;QAClF,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;YAC/D,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC;YAEvC,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;gBACtB,OAAO,CAAC,GAAG,CAAC,kCAAkC,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC;YACjE,CAAC;YAED,0BAA0B;YAC1B,IAAI,GAAG,CAAC,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC;gBAClC,OAAO,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;YAChD,CAAC;YAED,2BAA2B;YAC3B,OAAO,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;QACvD,CAAC,CAAC,CAAC;QAEH,qCAAqC;QACrC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,2BAA2B,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;YACzE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC;YAE7D,IAAI,SAAS,KAAK,IAAI,CAAC,MAAM,CAAC,QAAS,IAAI,aAAa,KAAK,IAAI,CAAC,MAAM,CAAC,YAAa,EAAE,CAAC;gBACvF,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;YAC3D,CAAC;YAED,IAAI,CAAC;gBACH,2BAA2B;gBAC3B,IAAI,CAAC,cAAc,CAAC,kBAAkB,CAAC,aAAa,CAAC,CAAC;gBAEtD,4BAA4B;gBAC5B,GAAG,CAAC,IAAI,CAAC;oBACP,YAAY,EAAE,oBAAoB,GAAG,IAAI,CAAC,GAAG,EAAE;oBAC/C,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;oBAC1C,UAAU,EAAE,KAAK,EAAE,WAAW;iBAC/B,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;YACnD,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,0BAA0B;QAC1B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,+BAA+B,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;YAC5E,GAAG,CAAC,IAAI,CAAC;gBACP,IAAI,EAAE,IAAI,CAAC,QAAQ;aACpB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,oDAAoD;QACpD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;YAC7D,GAAG,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;YACnC,MAAM,OAAO,GAAG,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,gCAAgC,CAAC,CAAC;YACvE,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QACxB,CAAC,CAAC,CAAC;QAEH,sDAAsD;QACtD,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACtB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,IAAA,6CAAqB,EAAC;gBAC7C,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;gBAC1B,YAAY,EAAE,IAAI;gBAClB,EAAE,EAAE,IAAI;gBACR,WAAW,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE;gBAChC,EAAE,EAAE;oBACF,QAAQ,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,EAAE;wBAC1B,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;4BACtB,OAAO,CAAC,GAAG,CAAC,wBAAwB,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,GAAG,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;wBAClG,CAAC;oBACH,CAAC;iBACF;aACF,CAAC,CAAC,CAAC;QACN,CAAC;QAED,iCAAiC;QACjC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;YAChD,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;gBACtB,OAAO,CAAC,GAAG,CAAC,kCAAkC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;YACjF,CAAC;YACD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACK,iBAAiB,CAAC,GAAY,EAAE,GAAa,EAAE,IAAS;QAC9D,MAAM,KAAK,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;QAElE,2DAA2D;QAC3D,MAAM,QAAQ,GAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;QAEnC,eAAe;QACf,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,QAAQ,CAAC,IAAI,CAAC,IAAI,GAAG;gBACnB,EAAE,EAAE,sBAAsB;gBAC1B,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,IAAI;gBACxB,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,KAAK;gBAC1B,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM;gBAC5B,eAAe,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI;gBACjC,IAAI,EAAE,EAAE,WAAW,EAAE,WAAW,EAAE;gBAClC,aAAa,EAAE,EAAE,GAAG,EAAE,WAAW,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE;aAC1D,CAAC;QACJ,CAAC;QAED,mBAAmB;QACnB,IAAI,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/B,QAAQ,CAAC,IAAI,CAAC,QAAQ,GAAG;gBACvB,KAAK,EAAE;oBACL;wBACE,IAAI,EAAE;4BACJ,EAAE,EAAE,yBAAyB;4BAC7B,KAAK,EAAE,gBAAgB;4BACvB,MAAM,EAAE,gBAAgB;4BACxB,MAAM,EAAE,QAAQ;4BAChB,cAAc,EAAE,GAAG;4BACnB,YAAY,EAAE;gCACZ,eAAe,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE;gCACzD,eAAe,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE;6BAC1D;yBACF;wBACD,MAAM,EAAE,SAAS;qBAClB;oBACD;wBACE,IAAI,EAAE;4BACJ,EAAE,EAAE,yBAAyB;4BAC7B,KAAK,EAAE,gBAAgB;4BACvB,MAAM,EAAE,gBAAgB;4BACxB,MAAM,EAAE,QAAQ;4BAChB,cAAc,EAAE,EAAE;4BAClB,YAAY,EAAE;gCACZ,eAAe,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE;gCACzD,eAAe,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE;6BAC1D;yBACF;wBACD,MAAM,EAAE,SAAS;qBAClB;iBACF;gBACD,QAAQ,EAAE;oBACR,WAAW,EAAE,KAAK;oBAClB,eAAe,EAAE,KAAK;iBACvB;aACF,CAAC;QACJ,CAAC;QAED,iBAAiB;QACjB,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7B,QAAQ,CAAC,IAAI,CAAC,MAAM,GAAG;gBACrB,KAAK,EAAE;oBACL;wBACE,IAAI,EAAE;4BACJ,EAAE,EAAE,0BAA0B;4BAC9B,IAAI,EAAE,OAAO;4BACb,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;4BACnC,sBAAsB,EAAE,MAAM;4BAC9B,wBAAwB,EAAE,aAAa;4BACvC,aAAa,EAAE;gCACb,SAAS,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE;6BACpD;4BACD,QAAQ,EAAE;gCACR,EAAE,EAAE,0BAA0B;gCAC9B,WAAW,EAAE,UAAU;gCACvB,KAAK,EAAE,kBAAkB;6BAC1B;yBACF;wBACD,MAAM,EAAE,SAAS;qBAClB;iBACF;gBACD,QAAQ,EAAE;oBACR,WAAW,EAAE,KAAK;oBAClB,eAAe,EAAE,KAAK;iBACvB;aACF,CAAC;QACJ,CAAC;QAED,oBAAoB;QACpB,IAAI,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YAChC,QAAQ,CAAC,IAAI,CAAC,SAAS,GAAG;gBACxB,KAAK,EAAE;oBACL;wBACE,IAAI,EAAE;4BACJ,EAAE,EAAE,0BAA0B;4BAC9B,WAAW,EAAE,UAAU;4BACvB,KAAK,EAAE,kBAAkB;4BACzB,KAAK,EAAE,aAAa;4BACpB,WAAW,EAAE,GAAG;4BAChB,YAAY,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE;yBACxD;wBACD,MAAM,EAAE,SAAS;qBAClB;iBACF;gBACD,QAAQ,EAAE;oBACR,WAAW,EAAE,KAAK;oBAClB,eAAe,EAAE,KAAK;iBACvB;aACF,CAAC;QACJ,CAAC;QAED,wDAAwD;QACxD,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5C,QAAQ,CAAC,IAAI,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;QAC9C,CAAC;QAED,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACrB,CAAC;IAED;;OAEG;IACK,iBAAiB,CAAC,GAAY,EAAE,GAAa,EAAE,GAAW,EAAE,MAAc;QAChF,0CAA0C;QAC1C,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAChC,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CACzC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC,8BAA8B;SACvE,CAAC;QAEF,QAAQ,QAAQ,EAAE,CAAC;YACjB,KAAK,WAAW;gBACd,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;gBAClC,MAAM;YAER,KAAK,eAAe;gBAClB,GAAG,CAAC,IAAI,CAAC;oBACP,QAAQ,EAAE;wBACR;4BACE,EAAE,EAAE,CAAC;4BACL,KAAK,EAAE,gBAAgB;4BACvB,MAAM,EAAE,gBAAgB;4BACxB,MAAM,EAAE,QAAQ;4BAChB,QAAQ,EAAE;gCACR,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,GAAG,EAAE;6BACnD;yBACF;wBACD;4BACE,EAAE,EAAE,CAAC;4BACL,KAAK,EAAE,gBAAgB;4BACvB,MAAM,EAAE,gBAAgB;4BACxB,MAAM,EAAE,QAAQ;4BAChB,QAAQ,EAAE;gCACR,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,EAAE,EAAE;6BAClD;yBACF;qBACF;iBACF,CAAC,CAAC;gBACH,MAAM;YAER,KAAK,aAAa;gBAChB,GAAG,CAAC,IAAI,CAAC;oBACP,MAAM,EAAE;wBACN;4BACE,EAAE,EAAE,IAAI;4BACR,IAAI,EAAE,OAAO;4BACb,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;4BACpC,gBAAgB,EAAE,MAAM;4BACxB,kBAAkB,EAAE,IAAI;4BACxB,WAAW,EAAE,OAAO;4BACpB,QAAQ,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE;yBAC/C;qBACF;iBACF,CAAC,CAAC;gBACH,MAAM;YAER,KAAK,gBAAgB;gBACnB,GAAG,CAAC,IAAI,CAAC;oBACP,SAAS,EAAE;wBACT;4BACE,EAAE,EAAE,CAAC;4BACL,KAAK,EAAE,kBAAkB;4BACzB,UAAU,EAAE,MAAM;4BAClB,SAAS,EAAE,KAAK;4BAChB,YAAY,EAAE,CAAC;4BACf,WAAW,EAAE,QAAQ;yBACtB;qBACF;iBACF,CAAC,CAAC;gBACH,MAAM;YAER;gBACE,8CAA8C;gBAC9C,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;oBACtB,OAAO,CAAC,GAAG,CAAC,wCAAwC,QAAQ,EAAE,CAAC,CAAC;gBAClE,CAAC;gBACD,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,KAAK;QAChB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;gBACnD,OAAO,CAAC,GAAG,CAAC;;;2BAGO,IAAI,CAAC,MAAM,CAAC,IAAI;WAChC,IAAI,CAAC,MAAM,CAAC,IAAI;gBACX,IAAI,CAAC,MAAM,CAAC,QAAQ;cACtB,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,2BAA2B,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;SAEnG,CAAC,CAAC;gBACH,OAAO,EAAE,CAAC;YACZ,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAEM,KAAK,CAAC,IAAI;QACf,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC7B,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAChB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE;oBACrB,OAAO,CAAC,GAAG,CAAC,mCAAmC,CAAC,CAAC;oBACjD,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAEM,SAAS;QACd,OAAO,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;CACF;AA/cD,wDA+cC"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/types/index.d.ts b/dist/types/index.d.ts index 3863e26..ce45dc7 100644 --- a/dist/types/index.d.ts +++ b/dist/types/index.d.ts @@ -1,3 +1,4 @@ +import type { ResourcePickerCatalogResponse } from '../mock-resource-picker-catalog'; /** * Configuration for how Admin API requests are handled * - 'mock': Return mock data from the mock server (default, works offline) @@ -23,6 +24,8 @@ export interface MockShopifyAdminConfig { debug?: boolean; adminApi?: AdminApiConfig; proxy?: boolean; + /** Optional override for mock resource picker catalog (merged with defaults). */ + resourcePickerCatalog?: Partial; } export interface MockWebhook { topic: string; diff --git a/dist/types/index.d.ts.map b/dist/types/index.d.ts.map index 422223f..0836167 100644 --- a/dist/types/index.d.ts.map +++ b/dist/types/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GACtB,MAAM,GACN;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GACjB;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5B,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IAChC,SAAS,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,MAAM,IAAI,CAAC;IAC9C,QAAQ,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC;IAClC,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,MAAM,IAAI,CAAC;CAC3C;AAED,MAAM,WAAW,QAAQ;IACvB,WAAW,CAAC,EAAE;QACZ,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,IAAI,CAAC,EAAE;QACL,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,uBAAuB,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,wBAAwB,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,yBAA0B,SAAQ,WAAW;IAC5D,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,iCAAiC,CAAC;AAErF;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GACtB,MAAM,GACN;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GACjB;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5B,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iFAAiF;IACjF,qBAAqB,CAAC,EAAE,OAAO,CAAC,6BAA6B,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,IAAI,CAAC;IAChC,SAAS,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,MAAM,IAAI,CAAC;IAC9C,QAAQ,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC;IAClC,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,MAAM,IAAI,CAAC;CAC3C;AAED,MAAM,WAAW,QAAQ;IACvB,WAAW,CAAC,EAAE;QACZ,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,IAAI,CAAC,EAAE;QACL,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,uBAAuB,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,wBAAwB,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,GAAG,CAAC;IACd,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,yBAA0B,SAAQ,WAAW;IAC5D,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB"} \ No newline at end of file diff --git a/docs/RESOURCE_PICKER.md b/docs/RESOURCE_PICKER.md new file mode 100644 index 0000000..4bb61c5 --- /dev/null +++ b/docs/RESOURCE_PICKER.md @@ -0,0 +1,76 @@ +# Mock resource picker (products, variants, collections) + +In mock admin, `shopify.resourcePicker(options)` opens a **real modal** in the parent admin frame. Items come from **mock catalog data** served by the mock bridge server. The embedded app must load **`app-bridge.js` from the mock server** (not the Shopify CDN); with React Router, use **`EmbeddedShopifyAppProvider`** (or `MockBridgeAppProvider` + `appBridgeUrl`) — see [SHOPIFY_APP_REACT_ROUTER.md](./SHOPIFY_APP_REACT_ROUTER.md#app-bridge-script-mock). + +## Flow + +1. Embedded app calls `window.shopify.resourcePicker({ type, multiple, selectionIds })`. +2. Mock App Bridge (`app-bridge.js`) posts `FEATURE_ACTION_REQUEST` to the admin frame with a long timeout (picker is interactive). +3. Admin frame opens the picker UI, loads catalog from **`GET /api/resource-picker-catalog?q=`**. +4. On **Add** or **Cancel**, the frame posts `FEATURE_ACTION_RESPONSE` with `{ cancelled, selection }`. +5. The app receives an array of selected resources (empty array on cancel). + +## Catalog API + +- **URL:** `GET /api/resource-picker-catalog` +- **Query:** `q` — optional search string (title, handle, id, variant SKU, etc.) +- **Response:** + +```json +{ + "products": [ { "id": "gid://shopify/Product/1", "title": "...", "handle": "...", "status": "ACTIVE", "variants": [ ... ] } ], + "variants": [ { "id": "gid://shopify/ProductVariant/101", "displayName": "...", "price": "19.99", "productId": "...", "productTitle": "...", ... } ], + "collections": [ { "id": "gid://shopify/Collection/1", "title": "...", "handle": "..." } ] +} +``` + +The picker shows **one** list depending on `options.type`: `product` (default), `variant`, or `collection`. + +## Custom catalog (programmatic server) + +When creating `MockShopifyAdminServer`, pass **`resourcePickerCatalog`** (partial override merged with defaults): + +```typescript +import { MockShopifyAdminServer } from '@getverdict/mock-bridge'; + +await new MockShopifyAdminServer({ + appUrl: 'http://localhost:3000', + clientId: process.env.SHOPIFY_API_KEY!, + clientSecret: process.env.SHOPIFY_API_SECRET!, + resourcePickerCatalog: { + products: [ + { + id: 'gid://shopify/Product/99', + title: 'Custom Mug', + handle: 'custom-mug', + status: 'ACTIVE', + variants: [ + { + id: 'gid://shopify/ProductVariant/9901', + title: 'Default', + displayName: 'Custom Mug · Default', + price: '12.00', + sku: 'MUG-1', + inventoryQuantity: 10, + productId: 'gid://shopify/Product/99', + productTitle: 'Custom Mug', + productHandle: 'custom-mug', + }, + ], + }, + ], + }, +}).start(); +``` + +Override **`collections`** the same way if you use collection pickers. + +## Legacy App Bridge `Actions.ResourcePicker` + +`ShopifyAppBridge.actions.ResourcePicker.create(...)` **OPEN** now uses the same modal and fires **SELECT** with `{ selection: [...] }` or **CANCEL** handlers if the user cancels. + +## Differences vs Shopify native + +- UI is simplified (search + list) but behavior is deterministic for tests. +- Returned objects include useful GIDs and display fields; exact shapes may differ slightly from production Admin. +- `multiple` defaults to **single-select** unless `multiple: true` is set. diff --git a/docs/SHOPIFY_APP_REACT_ROUTER.md b/docs/SHOPIFY_APP_REACT_ROUTER.md index 24a41fa..002dafb 100644 --- a/docs/SHOPIFY_APP_REACT_ROUTER.md +++ b/docs/SHOPIFY_APP_REACT_ROUTER.md @@ -59,11 +59,66 @@ export const authenticate = Set `useOnlineTokens: true` in the reflect config if your app uses online tokens and you need the same session id strategy as production. -## App Bridge and CSP +## App Bridge script (mock) -Point embedded App Bridge at your mock server and relax CSP for the mock admin origin in development, similar to the Remix guide: +`@shopify/shopify-app-react-router/react` **`AppProvider`** always injects `https://cdn.shopify.com/shopifycloud/app-bridge.js`. Unknown props such as `__APP_BRIDGE_URL` are ignored, so the mock bridge script is never loaded and APIs like `shopify.resourcePicker()` will not talk to the mock admin. -- See [Shopify app remix](./SHOPIFY_APP_REMIX.md) for the `__APP_BRIDGE_URL` and `frame-ancestors` patterns (adjust host/port if yours differ). +Use **`EmbeddedShopifyAppProvider`** from `@getverdict/mock-bridge/react`: it picks **`MockBridgeAppProvider`** vs Shopify’s **`AppProvider`** internally, so you avoid the TypeScript error **`'Provider' cannot be used as a JSX component`** (a union of two components is not a valid JSX type). + +```tsx +import { Outlet, useLoaderData } from 'react-router'; +import { EmbeddedShopifyAppProvider } from '@getverdict/mock-bridge/react'; + +const mockOrigin = import.meta.env.VITE_MOCK_BRIDGE_ORIGIN as string | undefined; + +export default function App() { + const { apiKey } = useLoaderData() as { apiKey: string }; + + return ( + + {/* ... */} + + + ); +} +``` + +Resolve **`mockOrigin`** from loader/env/CI as you prefer. Omit it or pass **`undefined`** in production so the Shopify CDN App Bridge script is used. + +**Optional — `const Provider = mockOrigin ? MockBridgeAppProvider : AppProvider`** + +TypeScript will reject `` unless you assert a single component type, for example: + +```tsx +import type { ComponentType, ReactNode } from 'react'; +import { AppProvider } from '@shopify/shopify-app-react-router/react'; +import { MockBridgeAppProvider } from '@getverdict/mock-bridge/react'; + +type EmbeddedRootProps = { + embedded: true; + apiKey: string; + appBridgeUrl?: string; + children: ReactNode; +}; + +const Provider = ( + mockOrigin ? MockBridgeAppProvider : AppProvider +) as ComponentType; +``` + +Then pass **`appBridgeUrl`** only when mocking (same URL shape as before), or use two explicit JSX branches and skip the variable entirely. + +### `Cannot find module '@getverdict/mock-bridge/react'` + +The **`./react`** entry is defined only under the root **`package.json` `exports`** and resolves to **`dist/react/`** (compiled from **`src/react/`**). There is **no** separate top-level **`react/`** package folder in the repo: a nested **`react/package.json`** that pointed at **`dist/react`** was removed because it broke incremental **`tsc`** (TypeScript treated emitted **`.d.ts`** files as program inputs). + +TypeScript must use **`moduleResolution`** **`Bundler`** (typical Vite `tsconfig.app.json`), **`NodeNext`**, or **`Node16`**. Legacy **`moduleResolution`: `Node`** does not follow **`exports`**, so the subpath will not resolve. + +Also ensure the installed package includes a built **`dist/react/`** (run **`npm run build`** in the repo, or use a published version that ships that folder). + +## CSP + +Relax **`frame-ancestors`** (and related directives) for the mock admin origin in development so the embedded app can load inside the mock iframe. See [Shopify app remix](./SHOPIFY_APP_REMIX.md) for a `frame-ancestors` example (port may differ). ## Runtime dependency diff --git a/docs/SHOPIFY_APP_REMIX.md b/docs/SHOPIFY_APP_REMIX.md index 84fee9b..efef391 100644 --- a/docs/SHOPIFY_APP_REMIX.md +++ b/docs/SHOPIFY_APP_REMIX.md @@ -2,17 +2,13 @@ Follow this guide if you're using the Shopify app remix template. -## Set app bridge path +## Set App Bridge script URL -You likely have the `` component rendered in your `app.tsx`. Set the `__APP_BRIDGE_URL` prop: +Passing `__APP_BRIDGE_URL` (or any extra prop) into Shopify’s **Remix** or **React Router** `AppProvider` does **not** change which script loads: those providers render a ` + + + + +
+ + diff --git a/admin-frame/dist/vite.svg b/admin-frame/dist/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/admin-frame/dist/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file