From 48c6ee0f7b75cc14433bb5a0b18d0b69db420bce Mon Sep 17 00:00:00 2001 From: Paulo Margarido <64600052+paulomarg@users.noreply.github.com> Date: Tue, 16 Jan 2024 15:49:43 -0500 Subject: [PATCH 01/41] Include shopify-api types in docs --- .changeset/stale-glasses-reply.md | 2 + packages/shopify-app-remix/docs/build-docs.sh | 8 +- .../docs/generated/generated_docs_data.json | 16521 ++++++++++++---- packages/shopify-app-remix/package.json | 2 +- yarn.lock | 23 +- 5 files changed, 12934 insertions(+), 3622 deletions(-) create mode 100644 .changeset/stale-glasses-reply.md diff --git a/.changeset/stale-glasses-reply.md b/.changeset/stale-glasses-reply.md new file mode 100644 index 0000000000..a845151cc8 --- /dev/null +++ b/.changeset/stale-glasses-reply.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/shopify-app-remix/docs/build-docs.sh b/packages/shopify-app-remix/docs/build-docs.sh index 553121febe..1dee19ab41 100644 --- a/packages/shopify-app-remix/docs/build-docs.sh +++ b/packages/shopify-app-remix/docs/build-docs.sh @@ -1,10 +1,10 @@ -COMPILE_DOCS="yarn tsc --project docs/tsconfig.docs.json --types react --moduleResolution node --target esNext --module CommonJS && generate-docs --overridePath ./docs/typeOverride.json --input ./src --output ./docs/generated && find . -name '*.doc.js' -delete" -COMPILE_STATIC_PAGES="yarn tsc docs/staticPages/*.doc.ts --types react --moduleResolution node --target esNext --module CommonJS && generate-docs --isLandingPage --input ./docs/staticPages --output ./docs/generated && rm -rf docs/staticPages/*.doc.js" +COMPILE_DOCS="yarn tsc --project docs/tsconfig.docs.json --types react --moduleResolution node --target esNext --module CommonJS && generate-docs --overridePath ./docs/typeOverride.json --input ./src --declarationPath ../../node_modules/@shopify/shopify-api ../../node_modules/@shopify/polaris/build/ts --output ./docs/generated && find . -name '*.doc.js' -delete" +COMPILE_STATIC_PAGES="yarn tsc docs/staticPages/*.doc.ts --types react --moduleResolution node --target esNext --module CommonJS && generate-docs --isLandingPage --input ./docs/staticPages --declarationPath ../../node_modules/@shopify/shopify-api ../../node_modules/@shopify/polaris/build/ts --output ./docs/generated && rm -rf docs/staticPages/*.doc.js" if [ "$1" = "isTest" ]; then -COMPILE_DOCS="yarn tsc --project docs/tsconfig.docs.json --types react --moduleResolution node --target esNext --module CommonJS && generate-docs --overridePath ./docs/typeOverride.json --input ./src --output ./docs/temp && find . -name '*.doc.js' -delete" -COMPILE_STATIC_PAGES="yarn tsc docs/staticPages/*.doc.ts --types react --moduleResolution node --target esNext --module CommonJS && generate-docs --isLandingPage --input ./docs/staticPages --output ./docs/temp && rm -rf docs/staticPages/*.doc.js" +COMPILE_DOCS="yarn tsc --project docs/tsconfig.docs.json --types react --moduleResolution node --target esNext --module CommonJS && generate-docs --overridePath ./docs/typeOverride.json --input ./src --declarationPath ../../node_modules/@shopify/shopify-api ../../node_modules/@shopify/polaris/build/ts --output ./docs/temp && find . -name '*.doc.js' -delete" +COMPILE_STATIC_PAGES="yarn tsc docs/staticPages/*.doc.ts --types react --moduleResolution node --target esNext --module CommonJS && generate-docs --isLandingPage --input ./docs/staticPages --declarationPath ../../node_modules/@shopify/shopify-api ../../node_modules/@shopify/polaris/build/ts --output ./docs/temp && rm -rf docs/staticPages/*.doc.js" fi eval $COMPILE_DOCS diff --git a/packages/shopify-app-remix/docs/generated/generated_docs_data.json b/packages/shopify-app-remix/docs/generated/generated_docs_data.json index eecf09ad23..7d94cf2021 100644 --- a/packages/shopify-app-remix/docs/generated/generated_docs_data.json +++ b/packages/shopify-app-remix/docs/generated/generated_docs_data.json @@ -12,44 +12,27 @@ "type": "AppProviderProps", "typeDefinitions": { "AppProviderProps": { - "filePath": "/react/components/AppProvider/AppProvider.tsx", + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/components/AppProvider/AppProvider.d.ts", "name": "AppProviderProps", "description": "", "members": [ { - "filePath": "/react/components/AppProvider/AppProvider.tsx", - "syntaxKind": "PropertySignature", - "name": "apiKey", - "value": "string", - "description": "The API key for your Shopify app. This is the `Client ID` from the Partner Dashboard.\n\nWhen using the Shopify CLI, this is the `SHOPIFY_API_KEY` environment variable. If you're using the environment variable, then you need to pass it from the loader to the component." - }, - { - "filePath": "/react/components/AppProvider/AppProvider.tsx", - "syntaxKind": "PropertySignature", - "name": "isEmbeddedApp", - "value": "boolean", - "description": "Whether the app is loaded inside the Shopify Admin. Default is `true`.\n\n\n\n\n", - "isOptional": true - }, - { - "filePath": "/react/components/AppProvider/AppProvider.tsx", + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/components/AppProvider/AppProvider.d.ts", "syntaxKind": "PropertySignature", "name": "i18n", "value": "TranslationDictionary | TranslationDictionary[]", - "description": "The internationalization (i18n) configuration for your Polaris provider.\n\n\n\n\n", - "isOptional": true + "description": "A locale object or array of locale objects that overrides default translations. If specifying an array then your primary language dictionary should come first, followed by your fallback language dictionaries" }, { - "filePath": "/react/components/AppProvider/AppProvider.tsx", + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/components/AppProvider/AppProvider.d.ts", "syntaxKind": "PropertySignature", - "name": "__APP_BRIDGE_URL", - "value": "string", - "description": "Used internally by Shopify. You don't need to set this.", - "isOptional": true, - "isPrivate": true + "name": "linkComponent", + "value": "LinkLikeComponent", + "description": "A custom component to use for all links used by Polaris components", + "isOptional": true }, { - "filePath": "/react/components/AppProvider/AppProvider.tsx", + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/components/AppProvider/AppProvider.d.ts", "syntaxKind": "PropertySignature", "name": "features", "value": "FeaturesConfig", @@ -57,7 +40,7 @@ "isOptional": true }, { - "filePath": "/react/components/AppProvider/AppProvider.tsx", + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/components/AppProvider/AppProvider.d.ts", "syntaxKind": "PropertySignature", "name": "children", "value": "React.ReactNode", @@ -65,7 +48,56 @@ "isOptional": true } ], - "value": "export interface AppProviderProps\n extends Omit {\n /**\n * The API key for your Shopify app. This is the `Client ID` from the Partner Dashboard.\n *\n * When using the Shopify CLI, this is the `SHOPIFY_API_KEY` environment variable. If you're using the environment\n * variable, then you need to pass it from the loader to the component.\n */\n apiKey: string;\n /**\n * Whether the app is loaded inside the Shopify Admin. Default is `true`.\n *\n * {@link https://shopify.dev/docs/apps/admin/embedded-app-home}\n */\n isEmbeddedApp?: boolean;\n /**\n * The internationalization (i18n) configuration for your Polaris provider.\n *\n * {@link https://polaris.shopify.com/components/utilities/app-provider}\n */\n i18n?: PolarisAppProviderProps['i18n'];\n /**\n * Used internally by Shopify. You don't need to set this.\n * @private\n */\n __APP_BRIDGE_URL?: string;\n}" + "value": "export interface AppProviderProps {\n /** A locale object or array of locale objects that overrides default translations. If specifying an array then your primary language dictionary should come first, followed by your fallback language dictionaries */\n i18n: ConstructorParameters[0];\n /** A custom component to use for all links used by Polaris components */\n linkComponent?: LinkLikeComponent;\n /** For toggling features */\n features?: FeaturesConfig;\n /** Inner content of the application */\n children?: React.ReactNode;\n}" + }, + "TranslationDictionary": { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/utilities/i18n/I18n.d.ts", + "name": "TranslationDictionary", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/utilities/i18n/I18n.d.ts", + "name": "[key: string]", + "value": "string | TranslationDictionary" + } + ], + "value": "interface TranslationDictionary {\n [key: string]: string | TranslationDictionary;\n}" + }, + "LinkLikeComponent": { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/utilities/link/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "LinkLikeComponent", + "value": "LinkLikeComponent", + "description": "" + }, + "FeaturesConfig": { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/utilities/features/types.d.ts", + "name": "FeaturesConfig", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/utilities/features/types.d.ts", + "name": "[key: string]", + "value": "boolean | undefined" + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/utilities/features/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "polarisSummerEditions2023", + "value": "boolean", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/utilities/features/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "polarisSummerEditions2023ShadowBevelOptOut", + "value": "boolean", + "description": "", + "isOptional": true + } + ], + "value": "export interface FeaturesConfig {\n polarisSummerEditions2023?: boolean;\n polarisSummerEditions2023ShadowBevelOptOut?: boolean;\n [key: string]: boolean | undefined;\n}" } } } @@ -132,7 +164,7 @@ "type": "AuthenticateAdmin", "typeDefinitions": { "AuthenticateAdmin": { - "filePath": "/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/admin/types.ts", "name": "AuthenticateAdmin", "description": "", "params": [ @@ -140,11 +172,11 @@ "name": "request", "description": "", "value": "Request", - "filePath": "/server/authenticate/admin/types.ts" + "filePath": "src/server/authenticate/admin/types.ts" } ], "returns": { - "filePath": "/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/admin/types.ts", "description": "", "name": "Promise>", "value": "Promise>" @@ -152,19 +184,19 @@ "value": "export type AuthenticateAdmin<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> = (request: Request) => Promise>;" }, "AdminContext": { - "filePath": "/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "TypeAliasDeclaration", "name": "AdminContext", "value": "Config['isEmbeddedApp'] extends false\n ? NonEmbeddedAdminContext\n : EmbeddedAdminContext", "description": "" }, "NonEmbeddedAdminContext": { - "filePath": "/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/admin/types.ts", "name": "NonEmbeddedAdminContext", "description": "", "members": [ { - "filePath": "/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", "name": "session", "value": "Session", @@ -201,21 +233,21 @@ ] }, { - "filePath": "/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", "name": "admin", "value": "AdminApiContext", "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request." }, { - "filePath": "/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", "name": "billing", "value": "BillingContext", "description": "Billing methods for this store, based on the plans defined in the `billing` config option.\n\n\n\n\n" }, { - "filePath": "/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", "name": "cors", "value": "EnsureCORSFunction", @@ -236,13 +268,252 @@ ], "value": "export interface NonEmbeddedAdminContext<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends AdminContextInternal {}" }, + "Session": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "name": "Session", + "description": "Stores App information from logged in merchants so they can make authenticated requests to the Admin API.", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "shop", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "state", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "isOnline", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "scope", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "expires", + "value": "Date", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "accessToken", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "onlineAccessInfo", + "value": "OnlineAccessInfo", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isActive", + "value": "(scopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isScopeChanged", + "value": "(scopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isExpired", + "value": "(withinMillisecondsOfExpiry?: number) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toObject", + "value": "() => SessionParams", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "equals", + "value": "(other: Session) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toPropertyArray", + "value": "() => [string, string | number | boolean][]", + "description": "" + } + ], + "value": "export declare class Session {\n static fromPropertyArray(entries: [string, string | number | boolean][]): Session;\n readonly id: string;\n shop: string;\n state: string;\n isOnline: boolean;\n scope?: string;\n expires?: Date;\n accessToken?: string;\n onlineAccessInfo?: OnlineAccessInfo;\n constructor(params: SessionParams);\n isActive(scopes: AuthScopes | string | string[]): boolean;\n isScopeChanged(scopes: AuthScopes | string | string[]): boolean;\n isExpired(withinMillisecondsOfExpiry?: number): boolean;\n toObject(): SessionParams;\n equals(other: Session | undefined): boolean;\n toPropertyArray(): [string, string | number | boolean][];\n}" + }, + "OnlineAccessInfo": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "name": "OnlineAccessInfo", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "expires_in", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "associated_user_scope", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "associated_user", + "value": "{ id: number; first_name: string; last_name: string; email: string; email_verified: boolean; account_owner: boolean; locale: string; collaborator: boolean; }", + "description": "" + } + ], + "value": "export interface OnlineAccessInfo {\n expires_in: number;\n associated_user_scope: string;\n associated_user: {\n id: number;\n first_name: string;\n last_name: string;\n email: string;\n email_verified: boolean;\n account_owner: boolean;\n locale: string;\n collaborator: boolean;\n };\n}" + }, + "AuthScopes": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "name": "AuthScopes", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "has", + "value": "(scope: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "equals", + "value": "(otherScopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toString", + "value": "() => string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toArray", + "value": "() => string[]", + "description": "" + } + ], + "value": "declare class AuthScopes {\n static SCOPE_DELIMITER: string;\n private compressedScopes;\n private expandedScopes;\n constructor(scopes: string | string[] | AuthScopes | undefined);\n has(scope: string | string[] | AuthScopes | undefined): boolean;\n equals(otherScopes: string | string[] | AuthScopes | undefined): boolean;\n toString(): string;\n toArray(): string[];\n private getImpliedScopes;\n}" + }, + "SessionParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "name": "SessionParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "shop", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "state", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "isOnline", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "scope", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "expires", + "value": "Date", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "accessToken", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "onlineAccessInfo", + "value": "OnlineAccessInfo", + "description": "", + "isOptional": true + } + ], + "value": "export interface SessionParams {\n readonly id: string;\n shop: string;\n state: string;\n isOnline: boolean;\n scope?: string;\n expires?: Date;\n accessToken?: string;\n onlineAccessInfo?: OnlineAccessInfo;\n}" + }, "AdminApiContext": { - "filePath": "/server/clients/admin/types.ts", + "filePath": "src/server/clients/admin/types.ts", "name": "AdminApiContext", "description": "", "members": [ { - "filePath": "/server/clients/admin/types.ts", + "filePath": "src/server/clients/admin/types.ts", "syntaxKind": "PropertySignature", "name": "rest", "value": "RestClientWithResources", @@ -293,7 +564,7 @@ ] }, { - "filePath": "/server/clients/admin/types.ts", + "filePath": "src/server/clients/admin/types.ts", "syntaxKind": "PropertySignature", "name": "graphql", "value": "GraphQLClient", @@ -315,82 +586,328 @@ "value": "export interface AdminApiContext<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * Methods for interacting with the Shopify Admin REST API\n *\n * There are methods for interacting with individual REST resources. You can also make `GET`, `POST`, `PUT` and `DELETE` requests should the REST resources not meet your needs.\n *\n * {@link https://shopify.dev/docs/api/admin-rest}\n *\n * @example\n * Using REST resources.\n * Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource. \n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * return json(admin.rest.resources.Order.count({ session }));\n * };\n * ```\n *\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n *\n * @example\n * Performing a GET request to the REST API.\n * Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint\n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = await admin.rest.get({ path: \"/customers/count.json\" });\n * const customers = await response.json();\n * return json({ customers });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * @example\n * Performing a POST request to the REST API.\n * Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = admin.rest.post({\n * path: \"customers/7392136888625/send_invite.json\",\n * body: {\n * customer_invite: {\n * to: \"new_test_email@shopify.com\",\n * from: \"j.limited@example.com\",\n * bcc: [\"j.limited@example.com\"],\n * subject: \"Welcome to my new shop\",\n * custom_message: \"My awesome new store\",\n * },\n * },\n * });\n * const customerInvite = await response.json();\n * return json({ customerInvite });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n rest: RestClientWithResources;\n\n /**\n * Methods for interacting with the Shopify Admin GraphQL API\n *\n * {@link https://shopify.dev/docs/api/admin-graphql}\n * {@link https://github.com/Shopify/shopify-api-js/blob/main/packages/shopify-api/docs/reference/clients/Graphql.md}\n *\n * @example\n * Querying the GraphQL API.\n * Use `admin.graphql` to make query / mutation requests.\n * ```ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.admin(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n graphql: GraphQLClient;\n}" }, "RestClientWithResources": { - "filePath": "/server/clients/admin/rest.ts", + "filePath": "src/server/clients/admin/rest.ts", "syntaxKind": "TypeAliasDeclaration", "name": "RestClientWithResources", "value": "RemixRestClient & {resources: Resources}", "description": "" }, - "BillingContext": { - "filePath": "/server/authenticate/admin/billing/types.ts", - "name": "BillingContext", + "RemixRestClient": { + "filePath": "src/server/clients/admin/rest.ts", + "name": "RemixRestClient", + "description": "", + "members": [ + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "PropertyDeclaration", + "name": "session", + "value": "Session", + "description": "" + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "get", + "value": "(params: GetRequestParams) => Promise", + "description": "Performs a GET request on the given path." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "post", + "value": "(params: PostRequestParams) => Promise", + "description": "Performs a POST request on the given path." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "put", + "value": "(params: PostRequestParams) => Promise", + "description": "Performs a PUT request on the given path." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "delete", + "value": "(params: GetRequestParams) => Promise", + "description": "Performs a DELETE request on the given path." + } + ], + "value": "class RemixRestClient {\n public session: Session;\n private params: AdminClientOptions['params'];\n private handleClientError: AdminClientOptions['handleClientError'];\n\n constructor({params, session, handleClientError}: AdminClientOptions) {\n this.params = params;\n this.handleClientError = handleClientError;\n this.session = session;\n }\n\n /**\n * Performs a GET request on the given path.\n */\n public async get(params: GetRequestParams) {\n return this.makeRequest({\n method: 'GET' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a POST request on the given path.\n */\n public async post(params: PostRequestParams) {\n return this.makeRequest({\n method: 'POST' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a PUT request on the given path.\n */\n public async put(params: PutRequestParams) {\n return this.makeRequest({\n method: 'PUT' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a DELETE request on the given path.\n */\n public async delete(params: DeleteRequestParams) {\n return this.makeRequest({\n method: 'DELETE' as RequestParams['method'],\n ...params,\n });\n }\n\n protected async makeRequest(params: RequestParams): Promise {\n const originalClient = new this.params.api.clients.Rest({\n session: this.session,\n });\n const originalRequest = Reflect.get(originalClient, 'request');\n\n try {\n const apiResponse = await originalRequest.call(originalClient, params);\n\n // We use a separate client for REST requests and REST resources because we want to override the API library\n // client class to return a Response object instead.\n return new Response(JSON.stringify(apiResponse.body), {\n headers: apiResponse.headers,\n });\n } catch (error) {\n if (this.handleClientError) {\n throw await this.handleClientError({\n error,\n session: this.session,\n params: this.params,\n });\n } else throw new Error(error);\n }\n }\n}" + }, + "GetRequestParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "GetRequestParams", "description": "", "members": [ { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", "syntaxKind": "PropertySignature", - "name": "require", - "value": "(options: RequireBillingOptions) => Promise", - "description": "Checks if the shop has an active payment for any plan defined in the `billing` config option.", - "examples": [ - { - "title": "Requesting billing right away", - "description": "Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n isTest: true,\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n // App logic\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] - }, - { - "title": "Using a plan selection page", - "description": "When the app has multiple plans, create a page in your App that allows the merchant to select a plan. If a merchant does not have the required plan you can redirect them to page in your app to select one.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n isTest: true,\n onFailure: () => redirect('/select-plan'),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n\n // App logic\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] - } - ] + "name": "path", + "value": "string", + "description": "" }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", "syntaxKind": "PropertySignature", - "name": "request", - "value": "(options: RequestBillingOptions) => Promise", - "description": "Requests payment for the plan.", - "examples": [ - { - "title": "Using a custom return URL", - "description": "Change where the merchant is returned to after approving the purchase using the `returnUrl` option.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n }),\n });\n\n // App logic\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] - } - ] + "name": "type", + "value": "DataType", + "description": "", + "isOptional": true }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", "syntaxKind": "PropertySignature", - "name": "cancel", - "value": "(options: CancelBillingOptions) => Promise", + "name": "data", + "value": "string | Record", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "query", + "value": "SearchParams", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "extraHeaders", + "value": "HeaderParams", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "tries", + "value": "number", + "description": "", + "isOptional": true + } + ], + "value": "export interface GetRequestParams {\n path: string;\n type?: DataType;\n data?: Record | string;\n query?: SearchParams;\n extraHeaders?: HeaderParams;\n tries?: number;\n}" + }, + "DataType": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "DataType", + "value": "export declare enum DataType {\n JSON = \"application/json\",\n GraphQL = \"application/graphql\",\n URLEncoded = \"application/x-www-form-urlencoded\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "JSON", + "value": "application/json" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "GraphQL", + "value": "application/graphql" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "URLEncoded", + "value": "application/x-www-form-urlencoded" + } + ] + }, + "HeaderParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "HeaderParams", + "value": "Record", + "description": "", + "members": [] + }, + "PostRequestParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "PostRequestParams", + "value": "GetRequestParams & {\n data: Record | string;\n}", + "description": "" + }, + "GraphQLClient": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLClient", + "description": "", + "params": [ + { + "name": "query", + "description": "", + "value": "Operation extends keyof Operations", + "filePath": "src/server/clients/types.ts" + }, + { + "name": "options", + "description": "", + "value": "GraphQLQueryOptions", + "isOptional": true, + "filePath": "src/server/clients/types.ts" + } + ], + "returns": { + "filePath": "src/server/clients/types.ts", + "description": "", + "name": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}", + "value": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}" + }, + "value": "export type GraphQLClient = <\n Operation extends keyof Operations,\n>(\n query: Operation,\n options?: GraphQLQueryOptions,\n) => Promise<\n ResponseWithType>>\n>;" + }, + "GraphQLQueryOptions": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLQueryOptions", + "description": "", + "members": [ + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "variables", + "value": "ApiClientRequestOptions[\"variables\"]", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "apiVersion", + "value": "ApiVersion", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "headers", + "value": "{ [key: string]: any; }", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "tries", + "value": "number", + "description": "", + "isOptional": true + } + ], + "value": "export interface GraphQLQueryOptions<\n Operation extends keyof Operations,\n Operations extends AllOperations,\n> {\n variables?: ApiClientRequestOptions['variables'];\n apiVersion?: ApiVersion;\n headers?: {[key: string]: any};\n tries?: number;\n}" + }, + "ApiVersion": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "ApiVersion", + "value": "export declare enum ApiVersion {\n October22 = \"2022-10\",\n January23 = \"2023-01\",\n April23 = \"2023-04\",\n July23 = \"2023-07\",\n October23 = \"2023-10\",\n January24 = \"2024-01\",\n Unstable = \"unstable\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "October22", + "value": "2022-10" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "January23", + "value": "2023-01" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "April23", + "value": "2023-04" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "July23", + "value": "2023-07" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "October23", + "value": "2023-10" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "January24", + "value": "2024-01" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Unstable", + "value": "unstable" + } + ] + }, + "BillingContext": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "BillingContext", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "require", + "value": "(options: RequireBillingOptions) => Promise", + "description": "Checks if the shop has an active payment for any plan defined in the `billing` config option.", + "examples": [ + { + "title": "Requesting billing right away", + "description": "Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n isTest: true,\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n // App logic\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + }, + { + "title": "Using a plan selection page", + "description": "When the app has multiple plans, create a page in your App that allows the merchant to select a plan. If a merchant does not have the required plan you can redirect them to page in your app to select one.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n isTest: true,\n onFailure: () => redirect('/select-plan'),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n\n // App logic\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "request", + "value": "(options: RequestBillingOptions) => Promise", + "description": "Requests payment for the plan.", + "examples": [ + { + "title": "Using a custom return URL", + "description": "Change where the merchant is returned to after approving the purchase using the `returnUrl` option.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n }),\n });\n\n // App logic\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "cancel", + "value": "(options: CancelBillingOptions) => Promise", "description": "Cancels an ongoing subscription, given its ID.", "examples": [ { @@ -413,26 +930,26 @@ "value": "export interface BillingContext {\n /**\n * Checks if the shop has an active payment for any plan defined in the `billing` config option.\n *\n * @returns A promise that resolves to an object containing the active purchases for the shop.\n *\n * @example\n * Requesting billing right away.\n * Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * isTest: true,\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Using a plan selection page.\n * When the app has multiple plans, create a page in your App that allows the merchant to select a plan. If a merchant does not have the required plan you can redirect them to page in your app to select one.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n * isTest: true,\n * onFailure: () => redirect('/select-plan'),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n require: (\n options: RequireBillingOptions,\n ) => Promise;\n\n /**\n * Requests payment for the plan.\n *\n * @returns Redirects to the confirmation URL for the payment.\n *\n * @example\n * Using a custom return URL.\n * Change where the merchant is returned to after approving the purchase using the `returnUrl` option.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({\n * plan: MONTHLY_PLAN,\n * isTest: true,\n * returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n * }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n request: (options: RequestBillingOptions) => Promise;\n\n /**\n * Cancels an ongoing subscription, given its ID.\n *\n * @returns The cancelled subscription.\n *\n * @example\n * Cancelling a subscription.\n * Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.\n * ```ts\n * // /app/routes/cancel-subscription.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * const cancelledSubscription = await billing.cancel({\n * subscriptionId: subscription.id,\n * isTest: true,\n * prorate: true,\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n cancel: (options: CancelBillingOptions) => Promise;\n}" }, "RequireBillingOptions": { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "name": "RequireBillingOptions", "description": "", "members": [ { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "plans", "value": "(keyof Config[\"billing\"])[]", "description": "The plans to check for. Must be one of the values defined in the `billing` config option." }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "onFailure", "value": "(error: any) => Promise", "description": "How to handle the request if the shop doesn't have an active payment for any plan." }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "isTest", "value": "boolean", @@ -442,20 +959,114 @@ ], "value": "export interface RequireBillingOptions\n extends Omit {\n /**\n * The plans to check for. Must be one of the values defined in the `billing` config option.\n */\n plans: (keyof Config['billing'])[];\n /**\n * How to handle the request if the shop doesn't have an active payment for any plan.\n */\n onFailure: (error: any) => Promise;\n}" }, + "BillingCheckResponseObject": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingCheckResponseObject", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "hasActivePayment", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "oneTimePurchases", + "value": "OneTimePurchase[]", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "appSubscriptions", + "value": "AppSubscription[]", + "description": "" + } + ], + "value": "export interface BillingCheckResponseObject {\n hasActivePayment: boolean;\n oneTimePurchases: OneTimePurchase[];\n appSubscriptions: AppSubscription[];\n}" + }, + "OneTimePurchase": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "OneTimePurchase", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "name", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "test", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "status", + "value": "string", + "description": "" + } + ], + "value": "export interface OneTimePurchase {\n id: string;\n name: string;\n test: boolean;\n status: string;\n}" + }, + "AppSubscription": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "AppSubscription", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "name", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "test", + "value": "boolean", + "description": "" + } + ], + "value": "export interface AppSubscription {\n id: string;\n name: string;\n test: boolean;\n}" + }, "RequestBillingOptions": { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "name": "RequestBillingOptions", "description": "", "members": [ { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "plan", "value": "keyof Config[\"billing\"]", "description": "The plan to request. Must be one of the values defined in the `billing` config option." }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "isTest", "value": "boolean", @@ -463,7 +1074,7 @@ "isOptional": true }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "returnUrl", "value": "string", @@ -474,19 +1085,19 @@ "value": "export interface RequestBillingOptions\n extends Omit {\n /**\n * The plan to request. Must be one of the values defined in the `billing` config option.\n */\n plan: keyof Config['billing'];\n /**\n * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.\n */\n isTest?: boolean;\n /**\n * The URL to return to after the merchant approves the payment.\n */\n returnUrl?: string;\n}" }, "CancelBillingOptions": { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "name": "CancelBillingOptions", "description": "", "members": [ { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "subscriptionId", "value": "string", "description": "The ID of the subscription to cancel." }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "prorate", "value": "boolean", @@ -494,7 +1105,7 @@ "isOptional": true }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "isTest", "value": "boolean", @@ -504,13 +1115,20 @@ ], "value": "export interface CancelBillingOptions {\n /**\n * The ID of the subscription to cancel.\n */\n subscriptionId: string;\n /**\n * Whether to prorate the cancellation.\n *\n * {@link https://shopify.dev/docs/apps/billing/subscriptions/cancel-recurring-charges}\n */\n prorate?: boolean;\n /*\n * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.\n */\n isTest?: boolean;\n}" }, + "EnsureCORSFunction": { + "filePath": "src/server/authenticate/helpers/ensure-cors-headers.ts", + "name": "EnsureCORSFunction", + "description": "", + "members": [], + "value": "export interface EnsureCORSFunction {\n (response: Response): Response;\n}" + }, "EmbeddedAdminContext": { - "filePath": "/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/admin/types.ts", "name": "EmbeddedAdminContext", "description": "", "members": [ { - "filePath": "/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", "name": "sessionToken", "value": "JwtPayload", @@ -533,7 +1151,7 @@ ] }, { - "filePath": "/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", "name": "redirect", "value": "RedirectFunction", @@ -562,7 +1180,7 @@ ] }, { - "filePath": "/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", "name": "session", "value": "Session", @@ -599,21 +1217,21 @@ ] }, { - "filePath": "/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", "name": "admin", "value": "AdminApiContext", "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request." }, { - "filePath": "/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", "name": "billing", "value": "BillingContext", "description": "Billing methods for this store, based on the plans defined in the `billing` config option.\n\n\n\n\n" }, { - "filePath": "/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/admin/types.ts", "syntaxKind": "PropertySignature", "name": "cors", "value": "EnsureCORSFunction", @@ -634,44 +1252,115 @@ ], "value": "export interface EmbeddedAdminContext<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends AdminContextInternal {\n /**\n * The decoded and validated session token for the request.\n *\n * Returned only if `isEmbeddedApp` is `true`.\n *\n * {@link https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload}\n *\n * @example\n * Using the decoded session token.\n * Get user-specific data using the `sessionToken` object.\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * useOnlineTokens: true,\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken } = await authenticate.public.checkout(\n * request\n * );\n * return json(await getMyAppData({user: sessionToken.sub}));\n * };\n * ```\n */\n sessionToken: JwtPayload;\n\n /**\n * A function that redirects the user to a new page, ensuring that the appropriate parameters are set for embedded\n * apps.\n *\n * Returned only if `isEmbeddedApp` is `true`.\n *\n * @example\n * Redirecting to an app route.\n * Use the `redirect` helper to safely redirect between pages.\n * ```ts\n * // /app/routes/admin/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { session, redirect } = await authenticate.admin(request);\n * return redirect(\"/\");\n * };\n * ```\n *\n * @example\n * Redirecting outside of Shopify admin.\n * Pass in a `target` option of `_top` or `_parent` to go to an external URL.\n * ```ts\n * // /app/routes/admin/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { session, redirect } = await authenticate.admin(request);\n * return redirect(\"/\", { target: '_parent' });\n * };\n * ```\n */\n redirect: RedirectFunction;\n}" }, - "RedirectFunction": { - "filePath": "/server/authenticate/admin/helpers/redirect.ts", - "name": "RedirectFunction", + "JwtPayload": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "name": "JwtPayload", "description": "", - "params": [ + "members": [ { - "name": "url", - "description": "", + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "iss", "value": "string", - "filePath": "/server/authenticate/admin/helpers/redirect.ts" + "description": "" }, { - "name": "init", - "description": "", - "value": "RedirectInit", - "isOptional": true, - "filePath": "/server/authenticate/admin/helpers/redirect.ts" - } - ], - "returns": { - "filePath": "/server/authenticate/admin/helpers/redirect.ts", - "description": "", - "name": "TypedResponse", - "value": "TypedResponse" - }, - "value": "export type RedirectFunction = (\n url: string,\n init?: RedirectInit,\n) => TypedResponse;" - }, - "RedirectInit": { - "filePath": "/server/authenticate/admin/helpers/redirect.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "RedirectInit", - "value": "number | (ResponseInit & {target?: RedirectTarget})", - "description": "" - }, - "RedirectTarget": { - "filePath": "/server/authenticate/admin/helpers/redirect.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "RedirectTarget", + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "dest", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "aud", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "sub", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "exp", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "nbf", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "iat", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "jti", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "sid", + "value": "string", + "description": "" + } + ], + "value": "export interface JwtPayload {\n iss: string;\n dest: string;\n aud: string;\n sub: string;\n exp: number;\n nbf: number;\n iat: number;\n jti: string;\n sid: string;\n}" + }, + "RedirectFunction": { + "filePath": "src/server/authenticate/admin/helpers/redirect.ts", + "name": "RedirectFunction", + "description": "", + "params": [ + { + "name": "url", + "description": "", + "value": "string", + "filePath": "src/server/authenticate/admin/helpers/redirect.ts" + }, + { + "name": "init", + "description": "", + "value": "RedirectInit", + "isOptional": true, + "filePath": "src/server/authenticate/admin/helpers/redirect.ts" + } + ], + "returns": { + "filePath": "src/server/authenticate/admin/helpers/redirect.ts", + "description": "", + "name": "TypedResponse", + "value": "TypedResponse" + }, + "value": "export type RedirectFunction = (\n url: string,\n init?: RedirectInit,\n) => TypedResponse;" + }, + "RedirectInit": { + "filePath": "src/server/authenticate/admin/helpers/redirect.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RedirectInit", + "value": "number | (ResponseInit & {target?: RedirectTarget})", + "description": "" + }, + "RedirectTarget": { + "filePath": "src/server/authenticate/admin/helpers/redirect.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RedirectTarget", "value": "'_self' | '_parent' | '_top'", "description": "" } @@ -991,12 +1680,12 @@ "type": "BillingContext", "typeDefinitions": { "BillingContext": { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "name": "BillingContext", "description": "", "members": [ { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "require", "value": "(options: RequireBillingOptions) => Promise", @@ -1033,7 +1722,7 @@ ] }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "request", "value": "(options: RequestBillingOptions) => Promise", @@ -1056,7 +1745,7 @@ ] }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "cancel", "value": "(options: CancelBillingOptions) => Promise", @@ -1082,26 +1771,26 @@ "value": "export interface BillingContext {\n /**\n * Checks if the shop has an active payment for any plan defined in the `billing` config option.\n *\n * @returns A promise that resolves to an object containing the active purchases for the shop.\n *\n * @example\n * Requesting billing right away.\n * Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * isTest: true,\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Using a plan selection page.\n * When the app has multiple plans, create a page in your App that allows the merchant to select a plan. If a merchant does not have the required plan you can redirect them to page in your app to select one.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n * isTest: true,\n * onFailure: () => redirect('/select-plan'),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n require: (\n options: RequireBillingOptions,\n ) => Promise;\n\n /**\n * Requests payment for the plan.\n *\n * @returns Redirects to the confirmation URL for the payment.\n *\n * @example\n * Using a custom return URL.\n * Change where the merchant is returned to after approving the purchase using the `returnUrl` option.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({\n * plan: MONTHLY_PLAN,\n * isTest: true,\n * returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n * }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n request: (options: RequestBillingOptions) => Promise;\n\n /**\n * Cancels an ongoing subscription, given its ID.\n *\n * @returns The cancelled subscription.\n *\n * @example\n * Cancelling a subscription.\n * Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.\n * ```ts\n * // /app/routes/cancel-subscription.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * const cancelledSubscription = await billing.cancel({\n * subscriptionId: subscription.id,\n * isTest: true,\n * prorate: true,\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n cancel: (options: CancelBillingOptions) => Promise;\n}" }, "RequireBillingOptions": { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "name": "RequireBillingOptions", "description": "", "members": [ { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "plans", "value": "(keyof Config[\"billing\"])[]", "description": "The plans to check for. Must be one of the values defined in the `billing` config option." }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "onFailure", "value": "(error: any) => Promise", "description": "How to handle the request if the shop doesn't have an active payment for any plan." }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "isTest", "value": "boolean", @@ -1111,20 +1800,114 @@ ], "value": "export interface RequireBillingOptions\n extends Omit {\n /**\n * The plans to check for. Must be one of the values defined in the `billing` config option.\n */\n plans: (keyof Config['billing'])[];\n /**\n * How to handle the request if the shop doesn't have an active payment for any plan.\n */\n onFailure: (error: any) => Promise;\n}" }, + "BillingCheckResponseObject": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingCheckResponseObject", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "hasActivePayment", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "oneTimePurchases", + "value": "OneTimePurchase[]", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "appSubscriptions", + "value": "AppSubscription[]", + "description": "" + } + ], + "value": "export interface BillingCheckResponseObject {\n hasActivePayment: boolean;\n oneTimePurchases: OneTimePurchase[];\n appSubscriptions: AppSubscription[];\n}" + }, + "OneTimePurchase": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "OneTimePurchase", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "name", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "test", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "status", + "value": "string", + "description": "" + } + ], + "value": "export interface OneTimePurchase {\n id: string;\n name: string;\n test: boolean;\n status: string;\n}" + }, + "AppSubscription": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "AppSubscription", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "name", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "test", + "value": "boolean", + "description": "" + } + ], + "value": "export interface AppSubscription {\n id: string;\n name: string;\n test: boolean;\n}" + }, "RequestBillingOptions": { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "name": "RequestBillingOptions", "description": "", "members": [ { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "plan", "value": "keyof Config[\"billing\"]", "description": "The plan to request. Must be one of the values defined in the `billing` config option." }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "isTest", "value": "boolean", @@ -1132,7 +1915,7 @@ "isOptional": true }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "returnUrl", "value": "string", @@ -1143,19 +1926,19 @@ "value": "export interface RequestBillingOptions\n extends Omit {\n /**\n * The plan to request. Must be one of the values defined in the `billing` config option.\n */\n plan: keyof Config['billing'];\n /**\n * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.\n */\n isTest?: boolean;\n /**\n * The URL to return to after the merchant approves the payment.\n */\n returnUrl?: string;\n}" }, "CancelBillingOptions": { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "name": "CancelBillingOptions", "description": "", "members": [ { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "subscriptionId", "value": "string", "description": "The ID of the subscription to cancel." }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "prorate", "value": "boolean", @@ -1163,7 +1946,7 @@ "isOptional": true }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/admin/billing/types.ts", "syntaxKind": "PropertySignature", "name": "isTest", "value": "boolean", @@ -1293,7 +2076,7 @@ "type": "AuthenticateAppProxy", "typeDefinitions": { "AuthenticateAppProxy": { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "src/server/authenticate/public/appProxy/types.ts", "name": "AuthenticateAppProxy", "description": "", "params": [ @@ -1301,11 +2084,11 @@ "name": "request", "description": "", "value": "Request", - "filePath": "/server/authenticate/public/appProxy/types.ts" + "filePath": "src/server/authenticate/public/appProxy/types.ts" } ], "returns": { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "src/server/authenticate/public/appProxy/types.ts", "description": "", "name": "Promise", "value": "Promise" @@ -1313,33 +2096,33 @@ "value": "export type AuthenticateAppProxy = (\n request: Request,\n) => Promise;" }, "AppProxyContext": { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "src/server/authenticate/public/appProxy/types.ts", "name": "AppProxyContext", "description": "", "members": [ { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "src/server/authenticate/public/appProxy/types.ts", "syntaxKind": "PropertySignature", "name": "session", "value": "undefined", "description": "No session is available for the shop that made this request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice." }, { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "src/server/authenticate/public/appProxy/types.ts", "syntaxKind": "PropertySignature", "name": "admin", "value": "undefined", "description": "No session is available for the shop that made this request. Therefore no methods for interacting with the GraphQL / REST Admin APIs are available." }, { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "src/server/authenticate/public/appProxy/types.ts", "syntaxKind": "PropertySignature", "name": "storefront", "value": "undefined", "description": "No session is available for the shop that made this request. Therefore no method for interacting with the Storefront API is available." }, { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "src/server/authenticate/public/appProxy/types.ts", "syntaxKind": "PropertySignature", "name": "liquid", "value": "LiquidResponseFunction", @@ -1361,7 +2144,7 @@ "value": "export interface AppProxyContext extends Context {\n /**\n * No session is available for the shop that made this request.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n */\n session: undefined;\n\n /**\n * No session is available for the shop that made this request.\n * Therefore no methods for interacting with the GraphQL / REST Admin APIs are available.\n */\n admin: undefined;\n\n /**\n * No session is available for the shop that made this request.\n * Therefore no method for interacting with the Storefront API is available.\n */\n storefront: undefined;\n}" }, "LiquidResponseFunction": { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "src/server/authenticate/public/appProxy/types.ts", "name": "LiquidResponseFunction", "description": "", "params": [ @@ -1369,18 +2152,18 @@ "name": "body", "description": "", "value": "string", - "filePath": "/server/authenticate/public/appProxy/types.ts" + "filePath": "src/server/authenticate/public/appProxy/types.ts" }, { "name": "initAndOptions", "description": "", "value": "number | (ResponseInit & Options)", "isOptional": true, - "filePath": "/server/authenticate/public/appProxy/types.ts" + "filePath": "src/server/authenticate/public/appProxy/types.ts" } ], "returns": { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "src/server/authenticate/public/appProxy/types.ts", "description": "", "name": "Response", "value": "Response" @@ -1388,12 +2171,12 @@ "value": "export type LiquidResponseFunction = (\n body: string,\n initAndOptions?: number | (ResponseInit & Options),\n) => Response;" }, "Options": { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "src/server/authenticate/public/appProxy/types.ts", "name": "Options", "description": "", "members": [ { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "src/server/authenticate/public/appProxy/types.ts", "syntaxKind": "PropertySignature", "name": "layout", "value": "boolean", @@ -1404,12 +2187,12 @@ "value": "interface Options {\n layout?: boolean;\n}" }, "AppProxyContextWithSession": { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "src/server/authenticate/public/appProxy/types.ts", "name": "AppProxyContextWithSession", "description": "", "members": [ { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "src/server/authenticate/public/appProxy/types.ts", "syntaxKind": "PropertySignature", "name": "session", "value": "Session", @@ -1428,7 +2211,7 @@ ] }, { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "src/server/authenticate/public/appProxy/types.ts", "syntaxKind": "PropertySignature", "name": "admin", "value": "AdminApiContext", @@ -1447,7 +2230,7 @@ ] }, { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "src/server/authenticate/public/appProxy/types.ts", "syntaxKind": "PropertySignature", "name": "storefront", "value": "StorefrontContext", @@ -1466,7 +2249,7 @@ ] }, { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "src/server/authenticate/public/appProxy/types.ts", "syntaxKind": "PropertySignature", "name": "liquid", "value": "LiquidResponseFunction", @@ -1487,4115 +2270,11805 @@ ], "value": "export interface AppProxyContextWithSession<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends Context {\n /**\n * The session for the shop that made the request.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n *\n * Use this to get shop or user-specific data.\n *\n * @example\n * Using the session object.\n * Get the session for the shop that initiated the request to the app proxy.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppModelData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }) => {\n * // Get the session for the shop that initiated the request to the app proxy.\n * const { session } = await authenticate.public.appProxy(request);\n *\n * // Use the session data to make to queries to your database or additional requests.\n * return json(await getMyAppModelData({shop: session.shop));\n * };\n * ```\n */\n session: Session;\n\n /**\n * Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request.\n *\n * @example\n * Interacting with the Admin API.\n * Use the `admin` object to interact with the REST or GraphQL APIs.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.public.appProxy(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n admin: AdminApiContext;\n\n /**\n * Method for interacting with the Shopify Storefront Graphql API for the store that made the request.\n *\n * @example\n * Interacting with the Storefront API.\n * Use the `storefront` object to interact with the GraphQL API.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { storefront } = await authenticate.public.appProxy(request);\n *\n * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n *\n * return json(await response.json());\n * }\n * ```\n */\n storefront: StorefrontContext;\n}" }, - "AdminApiContext": { - "filePath": "/server/clients/admin/types.ts", - "name": "AdminApiContext", - "description": "", + "Session": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "name": "Session", + "description": "Stores App information from logged in merchants so they can make authenticated requests to the Admin API.", "members": [ { - "filePath": "/server/clients/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "rest", - "value": "RestClientWithResources", - "description": "Methods for interacting with the Shopify Admin REST API\n\nThere are methods for interacting with individual REST resources. You can also make `GET`, `POST`, `PUT` and `DELETE` requests should the REST resources not meet your needs.\n\n\n\n\n", - "examples": [ - { - "title": "Using REST resources", - "description": "Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n return json(admin.rest.resources.Order.count({ session }));\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" - } - ] - }, - { - "title": "Performing a GET request to the REST API", - "description": "Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = await admin.rest.get({ path: \"/customers/count.json\" });\n const customers = await response.json();\n return json({ customers });\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" - } - ] - }, - { - "title": "Performing a POST request to the REST API", - "description": "Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = admin.rest.post({\n path: \"customers/7392136888625/send_invite.json\",\n body: {\n customer_invite: {\n to: \"new_test_email@shopify.com\",\n from: \"j.limited@example.com\",\n bcc: [\"j.limited@example.com\"],\n subject: \"Welcome to my new shop\",\n custom_message: \"My awesome new store\",\n },\n },\n});\n const customerInvite = await response.json();\n return json({ customerInvite });\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" - } - ] - } - ] + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "id", + "value": "string", + "description": "" }, { - "filePath": "/server/clients/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "graphql", - "value": "GraphQLClient", - "description": "Methods for interacting with the Shopify Admin GraphQL API\n\n\n\n\n\n\n\n\n\n", - "examples": [ - { - "title": "Querying the GraphQL API", - "description": "Use `admin.graphql` to make query / mutation requests.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.admin(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", - "title": "Example" - } - ] - } - ] + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "shop", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "state", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "isOnline", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "scope", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "expires", + "value": "Date", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "accessToken", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "onlineAccessInfo", + "value": "OnlineAccessInfo", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isActive", + "value": "(scopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isScopeChanged", + "value": "(scopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isExpired", + "value": "(withinMillisecondsOfExpiry?: number) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toObject", + "value": "() => SessionParams", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "equals", + "value": "(other: Session) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toPropertyArray", + "value": "() => [string, string | number | boolean][]", + "description": "" } ], - "value": "export interface AdminApiContext<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * Methods for interacting with the Shopify Admin REST API\n *\n * There are methods for interacting with individual REST resources. You can also make `GET`, `POST`, `PUT` and `DELETE` requests should the REST resources not meet your needs.\n *\n * {@link https://shopify.dev/docs/api/admin-rest}\n *\n * @example\n * Using REST resources.\n * Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource. \n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * return json(admin.rest.resources.Order.count({ session }));\n * };\n * ```\n *\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n *\n * @example\n * Performing a GET request to the REST API.\n * Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint\n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = await admin.rest.get({ path: \"/customers/count.json\" });\n * const customers = await response.json();\n * return json({ customers });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * @example\n * Performing a POST request to the REST API.\n * Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = admin.rest.post({\n * path: \"customers/7392136888625/send_invite.json\",\n * body: {\n * customer_invite: {\n * to: \"new_test_email@shopify.com\",\n * from: \"j.limited@example.com\",\n * bcc: [\"j.limited@example.com\"],\n * subject: \"Welcome to my new shop\",\n * custom_message: \"My awesome new store\",\n * },\n * },\n * });\n * const customerInvite = await response.json();\n * return json({ customerInvite });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n rest: RestClientWithResources;\n\n /**\n * Methods for interacting with the Shopify Admin GraphQL API\n *\n * {@link https://shopify.dev/docs/api/admin-graphql}\n * {@link https://github.com/Shopify/shopify-api-js/blob/main/packages/shopify-api/docs/reference/clients/Graphql.md}\n *\n * @example\n * Querying the GraphQL API.\n * Use `admin.graphql` to make query / mutation requests.\n * ```ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.admin(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n graphql: GraphQLClient;\n}" - }, - "RestClientWithResources": { - "filePath": "/server/clients/admin/rest.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "RestClientWithResources", - "value": "RemixRestClient & {resources: Resources}", - "description": "" + "value": "export declare class Session {\n static fromPropertyArray(entries: [string, string | number | boolean][]): Session;\n readonly id: string;\n shop: string;\n state: string;\n isOnline: boolean;\n scope?: string;\n expires?: Date;\n accessToken?: string;\n onlineAccessInfo?: OnlineAccessInfo;\n constructor(params: SessionParams);\n isActive(scopes: AuthScopes | string | string[]): boolean;\n isScopeChanged(scopes: AuthScopes | string | string[]): boolean;\n isExpired(withinMillisecondsOfExpiry?: number): boolean;\n toObject(): SessionParams;\n equals(other: Session | undefined): boolean;\n toPropertyArray(): [string, string | number | boolean][];\n}" }, - "StorefrontContext": { - "filePath": "/server/clients/storefront/types.ts", - "name": "StorefrontContext", + "OnlineAccessInfo": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "name": "OnlineAccessInfo", "description": "", "members": [ { - "filePath": "/server/clients/storefront/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", "syntaxKind": "PropertySignature", - "name": "graphql", - "value": "GraphQLClient", - "description": "Method for interacting with the Shopify Storefront GraphQL API\n\nIf you're getting incorrect type hints in the Shopify template, follow [these instructions](https://github.com/Shopify/shopify-app-template-remix/tree/main#incorrect-graphql-hints).\n\n\n\n\n", - "examples": [ - { - "title": "Querying the GraphQL API", - "description": "Use `storefront.graphql` to make query / mutation requests.", - "tabs": [ - { - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", - "title": "app/routes/**\\/.ts" - } - ] - } - ] + "name": "expires_in", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "associated_user_scope", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "associated_user", + "value": "{ id: number; first_name: string; last_name: string; email: string; email_verified: boolean; account_owner: boolean; locale: string; collaborator: boolean; }", + "description": "" } ], - "value": "export interface StorefrontContext {\n /**\n * Method for interacting with the Shopify Storefront GraphQL API\n *\n * If you're getting incorrect type hints in the Shopify template, follow [these instructions](https://github.com/Shopify/shopify-app-template-remix/tree/main#incorrect-graphql-hints).\n *\n * {@link https://shopify.dev/docs/api/storefront}\n *\n * @example\n * Querying the GraphQL API.\n * Use `storefront.graphql` to make query / mutation requests.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { storefront } = await authenticate.public.appProxy(request);\n *\n * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n *\n * return json(await response.json());\n * }\n * ```\n */\n graphql: GraphQLClient;\n}" - } - } - } - ], - "jsDocTypeExamples": [ - "AppProxyContextWithSession" - ], - "related": [ - { - "name": "Admin API context", - "subtitle": "Interact with the Admin API.", - "url": "/docs/api/shopify-app-remix/apis/admin-api" - }, - { - "name": "Storefront API context", - "subtitle": "Interact with the Storefront API.", - "url": "/docs/api/shopify-app-remix/apis/storefront-api" - } - ], - "examples": { - "description": "", - "exampleGroups": [ - { - "title": "session", - "examples": [ - { - "description": "Get the session for the shop that initiated the request to the app proxy.", - "codeblock": { - "title": "Using the session object", - "tabs": [ - { - "title": "app/routes/**\\/.ts", - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppModelData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }) => {\n // Get the session for the shop that initiated the request to the app proxy.\n const { session } = await authenticate.public.appProxy(request);\n\n // Use the session data to make to queries to your database or additional requests.\n return json(await getMyAppModelData({shop: session.shop));\n};", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "admin", - "examples": [ - { - "description": "Use the `admin` object to interact with the REST or GraphQL APIs.", - "codeblock": { - "title": "Interacting with the Admin API", - "tabs": [ - { - "title": "app/routes/**\\/.ts", - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.public.appProxy(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "storefront", - "examples": [ - { - "description": "Use the `storefront` object to interact with the GraphQL API.", - "codeblock": { - "title": "Interacting with the Storefront API", - "tabs": [ - { - "title": "app/routes/**\\/.ts", - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "liquid", - "examples": [ - { - "description": "Use the `liquid` helper to render a `Response` with Liquid content.", - "codeblock": { - "title": "Rendering liquid content", - "tabs": [ - { - "title": "app/routes/**\\/.ts", - "code": "import {authenticate} from \"~/shopify.server\"\n\nexport async function loader({ request }) {\n const {liquid} = await authenticate.public.appProxy(request);\n\n return liquid(\"Hello {{shop.name}}\")\n}", - "language": "typescript" - } - ] - } - } - ] - } - ] - } - }, - { - "name": "Checkout", - "description": "The `authenticate.public.checkout` function ensures that checkout extension requests are coming from Shopify, and returns helpers to respond with the correct headers.", - "category": "Authenticate", - "subCategory": "Public", - "type": "object", - "isVisualComponent": false, - "definitions": [ - { - "title": "authenticate.public.checkout", - "description": "Authenticates requests coming from Shopify checkout extensions.", - "type": "AuthenticateCheckout", - "typeDefinitions": { - "AuthenticateCheckout": { - "filePath": "/server/authenticate/public/checkout/types.ts", - "name": "AuthenticateCheckout", + "value": "export interface OnlineAccessInfo {\n expires_in: number;\n associated_user_scope: string;\n associated_user: {\n id: number;\n first_name: string;\n last_name: string;\n email: string;\n email_verified: boolean;\n account_owner: boolean;\n locale: string;\n collaborator: boolean;\n };\n}" + }, + "AuthScopes": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "name": "AuthScopes", "description": "", - "params": [ + "members": [ { - "name": "request", - "description": "", - "value": "Request", - "filePath": "/server/authenticate/public/checkout/types.ts" + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "has", + "value": "(scope: string | string[] | AuthScopes) => boolean", + "description": "" }, { - "name": "options", - "description": "", - "value": "AuthenticateCheckoutOptions", - "isOptional": true, - "filePath": "/server/authenticate/public/checkout/types.ts" + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "equals", + "value": "(otherScopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toString", + "value": "() => string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toArray", + "value": "() => string[]", + "description": "" } ], - "returns": { - "filePath": "/server/authenticate/public/checkout/types.ts", - "description": "", - "name": "Promise", - "value": "Promise" - }, - "value": "export type AuthenticateCheckout = (\n request: Request,\n options?: AuthenticateCheckoutOptions,\n) => Promise;" + "value": "declare class AuthScopes {\n static SCOPE_DELIMITER: string;\n private compressedScopes;\n private expandedScopes;\n constructor(scopes: string | string[] | AuthScopes | undefined);\n has(scope: string | string[] | AuthScopes | undefined): boolean;\n equals(otherScopes: string | string[] | AuthScopes | undefined): boolean;\n toString(): string;\n toArray(): string[];\n private getImpliedScopes;\n}" }, - "AuthenticateCheckoutOptions": { - "filePath": "/server/authenticate/public/checkout/types.ts", - "name": "AuthenticateCheckoutOptions", + "SessionParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "name": "SessionParams", "description": "", "members": [ { - "filePath": "/server/authenticate/public/checkout/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", "syntaxKind": "PropertySignature", - "name": "corsHeaders", - "value": "string[]", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "shop", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "state", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "isOnline", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "scope", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "expires", + "value": "Date", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "accessToken", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "onlineAccessInfo", + "value": "OnlineAccessInfo", "description": "", "isOptional": true } ], - "value": "export interface AuthenticateCheckoutOptions {\n corsHeaders?: string[];\n}" + "value": "export interface SessionParams {\n readonly id: string;\n shop: string;\n state: string;\n isOnline: boolean;\n scope?: string;\n expires?: Date;\n accessToken?: string;\n onlineAccessInfo?: OnlineAccessInfo;\n}" }, - "CheckoutContext": { - "filePath": "/server/authenticate/public/checkout/types.ts", - "name": "CheckoutContext", - "description": "Authenticated Context for a checkout request", + "AdminApiContext": { + "filePath": "src/server/clients/admin/types.ts", + "name": "AdminApiContext", + "description": "", "members": [ { - "filePath": "/server/authenticate/public/checkout/types.ts", + "filePath": "src/server/clients/admin/types.ts", "syntaxKind": "PropertySignature", - "name": "sessionToken", - "value": "JwtPayload", - "description": "The decoded and validated session token for the request\n\nRefer to the OAuth docs for the [session token payload](https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload).", + "name": "rest", + "value": "RestClientWithResources", + "description": "Methods for interacting with the Shopify Admin REST API\n\nThere are methods for interacting with individual REST resources. You can also make `GET`, `POST`, `PUT` and `DELETE` requests should the REST resources not meet your needs.\n\n\n\n\n", "examples": [ { - "title": "Using the decoded session token", - "description": "Get store-specific data using the `sessionToken` object.", + "title": "Using REST resources", + "description": "Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource.", "tabs": [ { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.public.checkout(\n request\n );\n return json(await getMyAppData({shop: sessionToken.dest}));\n};", - "title": "app/routes/public/my-route.ts" + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n return json(admin.rest.resources.Order.count({ session }));\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" } ] - } - ] - }, - { - "filePath": "/server/authenticate/public/checkout/types.ts", - "syntaxKind": "PropertySignature", - "name": "cors", - "value": "EnsureCORSFunction", - "description": "A function that ensures the CORS headers are set correctly for the response.", - "examples": [ + }, { - "title": "Setting CORS headers for a public request", - "description": "Use the `cors` helper to ensure your app can respond to checkout extension requests.", + "title": "Performing a GET request to the REST API", + "description": "Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint", "tabs": [ { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken, cors } = await authenticate.public.checkout(\n request,\n { corsHeaders: [\"X-My-Custom-Header\"] }\n );\n const data = await getMyAppData({shop: sessionToken.dest});\n return cors(json(data));\n};", - "title": "app/routes/public/my-route.ts" + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = await admin.rest.get({ path: \"/customers/count.json\" });\n const customers = await response.json();\n return json({ customers });\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" } ] - } - ] - } - ], - "value": "export interface CheckoutContext {\n /**\n * The decoded and validated session token for the request\n *\n * Refer to the OAuth docs for the [session token payload](https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload).\n *\n * @example\n * Using the decoded session token.\n * Get store-specific data using the `sessionToken` object.\n * ```ts\n * // app/routes/public/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken } = await authenticate.public.checkout(\n * request\n * );\n * return json(await getMyAppData({shop: sessionToken.dest}));\n * };\n * ```\n */\n sessionToken: JwtPayload;\n\n /**\n * A function that ensures the CORS headers are set correctly for the response.\n *\n * @example\n * Setting CORS headers for a public request.\n * Use the `cors` helper to ensure your app can respond to checkout extension requests.\n * ```ts\n * // app/routes/public/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken, cors } = await authenticate.public.checkout(\n * request,\n * { corsHeaders: [\"X-My-Custom-Header\"] }\n * );\n * const data = await getMyAppData({shop: sessionToken.dest});\n * return cors(json(data));\n * };\n * ```\n */\n cors: EnsureCORSFunction;\n}" - } - } - } - ], - "jsDocTypeExamples": [ - "CheckoutContext" - ], - "related": [], - "examples": { - "description": "", - "exampleGroups": [ - { - "title": "sessionToken", - "examples": [ - { - "description": "Get store-specific data using the `sessionToken` object.", - "codeblock": { - "title": "Using the decoded session token", - "tabs": [ + }, { - "title": "app/routes/public/my-route.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.public.checkout(\n request\n );\n return json(await getMyAppData({shop: sessionToken.dest}));\n};", - "language": "typescript" + "title": "Performing a POST request to the REST API", + "description": "Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = admin.rest.post({\n path: \"customers/7392136888625/send_invite.json\",\n body: {\n customer_invite: {\n to: \"new_test_email@shopify.com\",\n from: \"j.limited@example.com\",\n bcc: [\"j.limited@example.com\"],\n subject: \"Welcome to my new shop\",\n custom_message: \"My awesome new store\",\n },\n },\n});\n const customerInvite = await response.json();\n return json({ customerInvite });\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] } ] - } - } - ] - }, - { - "title": "cors", - "examples": [ - { - "description": "Use the `cors` helper to ensure your app can respond to checkout extension requests.", - "codeblock": { - "title": "Setting CORS headers for a public request", - "tabs": [ + }, + { + "filePath": "src/server/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "graphql", + "value": "GraphQLClient", + "description": "Methods for interacting with the Shopify Admin GraphQL API\n\n\n\n\n\n\n\n\n\n", + "examples": [ { - "title": "app/routes/public/my-route.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken, cors } = await authenticate.public.checkout(\n request,\n { corsHeaders: [\"X-My-Custom-Header\"] }\n );\n const data = await getMyAppData({shop: sessionToken.dest});\n return cors(json(data));\n};", - "language": "typescript" + "title": "Querying the GraphQL API", + "description": "Use `admin.graphql` to make query / mutation requests.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.admin(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", + "title": "Example" + } + ] } ] } - } - ] - } - ] - } - }, - { - "name": "Webhook", - "description": "Contains functions for verifying Shopify webhooks.\n\n> Note: The format of the `admin` object returned by this function changes with the `v3_webhookAdminContext` future flag. Learn more about [gradual feature adoption](/docs/api/shopify-app-remix/guide-future-flags).", - "category": "Authenticate", - "type": "object", - "isVisualComponent": false, - "definitions": [ - { - "title": "authenticate.webhook", - "description": "Verifies requests coming from Shopify webhooks.", - "type": "AuthenticateWebhook", - "typeDefinitions": { - "AuthenticateWebhook": { - "filePath": "/server/authenticate/webhooks/types.ts", - "name": "AuthenticateWebhook", - "description": "", - "params": [ - { - "name": "request", - "description": "", - "value": "Request", - "filePath": "/server/authenticate/webhooks/types.ts" - } ], - "returns": { - "filePath": "/server/authenticate/webhooks/types.ts", - "description": "", - "name": "Promise>", - "value": "Promise>" - }, - "value": "export type AuthenticateWebhook<\n Future extends FutureFlagOptions,\n Resources extends ShopifyRestResources,\n Topics = string | number | symbol,\n> = (request: Request) => Promise>;" + "value": "export interface AdminApiContext<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * Methods for interacting with the Shopify Admin REST API\n *\n * There are methods for interacting with individual REST resources. You can also make `GET`, `POST`, `PUT` and `DELETE` requests should the REST resources not meet your needs.\n *\n * {@link https://shopify.dev/docs/api/admin-rest}\n *\n * @example\n * Using REST resources.\n * Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource. \n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * return json(admin.rest.resources.Order.count({ session }));\n * };\n * ```\n *\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n *\n * @example\n * Performing a GET request to the REST API.\n * Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint\n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = await admin.rest.get({ path: \"/customers/count.json\" });\n * const customers = await response.json();\n * return json({ customers });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * @example\n * Performing a POST request to the REST API.\n * Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = admin.rest.post({\n * path: \"customers/7392136888625/send_invite.json\",\n * body: {\n * customer_invite: {\n * to: \"new_test_email@shopify.com\",\n * from: \"j.limited@example.com\",\n * bcc: [\"j.limited@example.com\"],\n * subject: \"Welcome to my new shop\",\n * custom_message: \"My awesome new store\",\n * },\n * },\n * });\n * const customerInvite = await response.json();\n * return json({ customerInvite });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n rest: RestClientWithResources;\n\n /**\n * Methods for interacting with the Shopify Admin GraphQL API\n *\n * {@link https://shopify.dev/docs/api/admin-graphql}\n * {@link https://github.com/Shopify/shopify-api-js/blob/main/packages/shopify-api/docs/reference/clients/Graphql.md}\n *\n * @example\n * Querying the GraphQL API.\n * Use `admin.graphql` to make query / mutation requests.\n * ```ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.admin(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n graphql: GraphQLClient;\n}" }, - "WebhookContext": { - "filePath": "/server/authenticate/webhooks/types.ts", + "RestClientWithResources": { + "filePath": "src/server/clients/admin/rest.ts", "syntaxKind": "TypeAliasDeclaration", - "name": "WebhookContext", - "value": "WebhookContextWithoutSession | WebhookContextWithSession", + "name": "RestClientWithResources", + "value": "RemixRestClient & {resources: Resources}", "description": "" }, - "WebhookContextWithoutSession": { - "filePath": "/server/authenticate/webhooks/types.ts", - "name": "WebhookContextWithoutSession", + "RemixRestClient": { + "filePath": "src/server/clients/admin/rest.ts", + "name": "RemixRestClient", "description": "", "members": [ { - "filePath": "/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "PropertyDeclaration", "name": "session", - "value": "undefined", + "value": "Session", "description": "" }, { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "get", + "value": "(params: GetRequestParams) => Promise", + "description": "Performs a GET request on the given path." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "post", + "value": "(params: PostRequestParams) => Promise", + "description": "Performs a POST request on the given path." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "put", + "value": "(params: PostRequestParams) => Promise", + "description": "Performs a PUT request on the given path." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "delete", + "value": "(params: GetRequestParams) => Promise", + "description": "Performs a DELETE request on the given path." + } + ], + "value": "class RemixRestClient {\n public session: Session;\n private params: AdminClientOptions['params'];\n private handleClientError: AdminClientOptions['handleClientError'];\n\n constructor({params, session, handleClientError}: AdminClientOptions) {\n this.params = params;\n this.handleClientError = handleClientError;\n this.session = session;\n }\n\n /**\n * Performs a GET request on the given path.\n */\n public async get(params: GetRequestParams) {\n return this.makeRequest({\n method: 'GET' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a POST request on the given path.\n */\n public async post(params: PostRequestParams) {\n return this.makeRequest({\n method: 'POST' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a PUT request on the given path.\n */\n public async put(params: PutRequestParams) {\n return this.makeRequest({\n method: 'PUT' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a DELETE request on the given path.\n */\n public async delete(params: DeleteRequestParams) {\n return this.makeRequest({\n method: 'DELETE' as RequestParams['method'],\n ...params,\n });\n }\n\n protected async makeRequest(params: RequestParams): Promise {\n const originalClient = new this.params.api.clients.Rest({\n session: this.session,\n });\n const originalRequest = Reflect.get(originalClient, 'request');\n\n try {\n const apiResponse = await originalRequest.call(originalClient, params);\n\n // We use a separate client for REST requests and REST resources because we want to override the API library\n // client class to return a Response object instead.\n return new Response(JSON.stringify(apiResponse.body), {\n headers: apiResponse.headers,\n });\n } catch (error) {\n if (this.handleClientError) {\n throw await this.handleClientError({\n error,\n session: this.session,\n params: this.params,\n });\n } else throw new Error(error);\n }\n }\n}" + }, + "GetRequestParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "GetRequestParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", "syntaxKind": "PropertySignature", - "name": "admin", - "value": "undefined", + "name": "path", + "value": "string", "description": "" }, { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", "syntaxKind": "PropertySignature", - "name": "apiVersion", - "value": "string", - "description": "The API version used for the webhook.", - "examples": [ - { - "title": "Webhook API version", - "description": "Get the API version used for webhook request.", - "tabs": [ - { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { apiVersion } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] + "name": "type", + "value": "DataType", + "description": "", + "isOptional": true }, { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", "syntaxKind": "PropertySignature", - "name": "shop", - "value": "string", - "description": "The shop where the webhook was triggered.", - "examples": [ - { - "title": "Webhook shop", - "description": "Get the shop that triggered a webhook.", - "tabs": [ - { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { shop } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] + "name": "data", + "value": "string | Record", + "description": "", + "isOptional": true }, { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", "syntaxKind": "PropertySignature", - "name": "topic", - "value": "Topics", - "description": "The topic of the webhook.", - "examples": [ - { - "title": "Webhook topic", - "description": "Get the event topic for the webhook.", - "tabs": [ - { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { topic } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n // Do something when the app is uninstalled.\n break;\n }\n\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] + "name": "query", + "value": "SearchParams", + "description": "", + "isOptional": true }, { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", "syntaxKind": "PropertySignature", - "name": "webhookId", - "value": "string", - "description": "A unique ID for the webhook. Useful to keep track of which events your app has already processed.", - "examples": [ - { - "title": "Webhook ID", - "description": "Get the webhook ID.", - "tabs": [ - { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { webhookId } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] + "name": "extraHeaders", + "value": "HeaderParams", + "description": "", + "isOptional": true }, { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", "syntaxKind": "PropertySignature", - "name": "payload", - "value": "JSONValue", - "description": "The payload from the webhook request.", - "examples": [ - { - "title": "Webhook payload", - "description": "Get the request's POST payload.", - "tabs": [ - { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { payload } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] + "name": "tries", + "value": "number", + "description": "", + "isOptional": true } ], - "value": "export interface WebhookContextWithoutSession\n extends Context {\n session: undefined;\n admin: undefined;\n}" + "value": "export interface GetRequestParams {\n path: string;\n type?: DataType;\n data?: Record | string;\n query?: SearchParams;\n extraHeaders?: HeaderParams;\n tries?: number;\n}" }, - "JSONValue": { - "filePath": "/server/types.ts", + "DataType": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "DataType", + "value": "export declare enum DataType {\n JSON = \"application/json\",\n GraphQL = \"application/graphql\",\n URLEncoded = \"application/x-www-form-urlencoded\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "JSON", + "value": "application/json" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "GraphQL", + "value": "application/graphql" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "URLEncoded", + "value": "application/x-www-form-urlencoded" + } + ] + }, + "HeaderParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", "syntaxKind": "TypeAliasDeclaration", - "name": "JSONValue", - "value": "string | number | boolean | null | JSONObject | JSONArray", + "name": "HeaderParams", + "value": "Record", + "description": "", + "members": [] + }, + "PostRequestParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "PostRequestParams", + "value": "GetRequestParams & {\n data: Record | string;\n}", "description": "" }, - "JSONObject": { - "filePath": "/server/types.ts", - "name": "JSONObject", + "GraphQLClient": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLClient", "description": "", - "members": [ + "params": [ { - "filePath": "/server/types.ts", - "name": "[x: string]", - "value": "JSONValue" + "name": "query", + "description": "", + "value": "Operation extends keyof Operations", + "filePath": "src/server/clients/types.ts" + }, + { + "name": "options", + "description": "", + "value": "GraphQLQueryOptions", + "isOptional": true, + "filePath": "src/server/clients/types.ts" } ], - "value": "interface JSONObject {\n [x: string]: JSONValue;\n}" + "returns": { + "filePath": "src/server/clients/types.ts", + "description": "", + "name": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}", + "value": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}" + }, + "value": "export type GraphQLClient = <\n Operation extends keyof Operations,\n>(\n query: Operation,\n options?: GraphQLQueryOptions,\n) => Promise<\n ResponseWithType>>\n>;" }, - "JSONArray": { - "filePath": "/server/types.ts", - "name": "JSONArray", + "GraphQLQueryOptions": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLQueryOptions", "description": "", "members": [ { - "filePath": "/server/types.ts", + "filePath": "src/server/clients/types.ts", "syntaxKind": "PropertySignature", - "name": "length", - "value": "number", - "description": "Gets or sets the length of the array. This is a number one higher than the highest index in the array." + "name": "variables", + "value": "ApiClientRequestOptions[\"variables\"]", + "description": "", + "isOptional": true }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "toString", - "value": "() => string", - "description": "Returns a string representation of an array." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "toLocaleString", - "value": "() => string", - "description": "Returns a string representation of an array. The elements are converted to string using their toLocaleString methods." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "pop", - "value": "() => JSONValue", - "description": "Removes the last element from an array and returns it.\r\nIf the array is empty, undefined is returned and the array is not modified." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "push", - "value": "(...items: JSONValue[]) => number", - "description": "Appends new elements to the end of an array, and returns the new length of the array." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "concat", - "value": "{ (...items: ConcatArray[]): JSONValue[]; (...items: (JSONValue | ConcatArray)[]): JSONValue[]; }", - "description": "Combines two or more arrays.\r\nThis method returns a new array without modifying any existing arrays." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "join", - "value": "(separator?: string) => string", - "description": "Adds all the elements of an array into a string, separated by the specified separator string." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "reverse", - "value": "() => JSONValue[]", - "description": "Reverses the elements in an array in place.\r\nThis method mutates the array and returns a reference to the same array." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "shift", - "value": "() => JSONValue", - "description": "Removes the first element from an array and returns it.\r\nIf the array is empty, undefined is returned and the array is not modified." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "slice", - "value": "(start?: number, end?: number) => JSONValue[]", - "description": "Returns a copy of a section of an array.\r\nFor both start and end, a negative index can be used to indicate an offset from the end of the array.\r\nFor example, -2 refers to the second to last element of the array." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "sort", - "value": "(compareFn?: (a: JSONValue, b: JSONValue) => number) => JSONArray", - "description": "Sorts an array in place.\r\nThis method mutates the array and returns a reference to the same array." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "splice", - "value": "{ (start: number, deleteCount?: number): JSONValue[]; (start: number, deleteCount: number, ...items: JSONValue[]): JSONValue[]; }", - "description": "Removes elements from an array and, if necessary, inserts new elements in their place, returning the deleted elements." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "unshift", - "value": "(...items: JSONValue[]) => number", - "description": "Inserts new elements at the start of an array, and returns the new length of the array." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "indexOf", - "value": "(searchElement: JSONValue, fromIndex?: number) => number", - "description": "Returns the index of the first occurrence of a value in an array, or -1 if it is not present." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "lastIndexOf", - "value": "(searchElement: JSONValue, fromIndex?: number) => number", - "description": "Returns the index of the last occurrence of a specified value in an array, or -1 if it is not present." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "every", - "value": "{ (predicate: (value: JSONValue, index: number, array: JSONValue[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: JSONValue, index: number, array: JSONValue[]) => unknown, thisArg?: any): boolean; }", - "description": "Determines whether all the members of an array satisfy the specified test." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "some", - "value": "(predicate: (value: JSONValue, index: number, array: JSONValue[]) => unknown, thisArg?: any) => boolean", - "description": "Determines whether the specified callback function returns true for any element of an array." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "forEach", - "value": "(callbackfn: (value: JSONValue, index: number, array: JSONValue[]) => void, thisArg?: any) => void", - "description": "Performs the specified action for each element in an array." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "map", - "value": "(callbackfn: (value: JSONValue, index: number, array: JSONValue[]) => U, thisArg?: any) => U[]", - "description": "Calls a defined callback function on each element of an array, and returns an array that contains the results." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "filter", - "value": "{ (predicate: (value: JSONValue, index: number, array: JSONValue[]) => value is S, thisArg?: any): S[]; (predicate: (value: JSONValue, index: number, array: JSONValue[]) => unknown, thisArg?: any): JSONValue[]; }", - "description": "Returns the elements of an array that meet the condition specified in a callback function." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "reduce", - "value": "{ (callbackfn: (previousValue: JSONValue, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => JSONValue): JSONValue; (callbackfn: (previousValue: JSONValue, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => JSONValue, initialValue: JSONValue): JSONValue; (callbackfn: (previousValue: U, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => U, initialValue: U): U; }", - "description": "Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "reduceRight", - "value": "{ (callbackfn: (previousValue: JSONValue, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => JSONValue): JSONValue; (callbackfn: (previousValue: JSONValue, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => JSONValue, initialValue: JSONValue): JSONValue; (callbackfn: (previousValue: U, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => U, initialValue: U): U; }", - "description": "Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "find", - "value": "{ (predicate: (this: void, value: JSONValue, index: number, obj: JSONValue[]) => value is S, thisArg?: any): S; (predicate: (value: JSONValue, index: number, obj: JSONValue[]) => unknown, thisArg?: any): JSONValue; }", - "description": "Returns the value of the first element in the array where predicate is true, and undefined\r\notherwise." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "findIndex", - "value": "(predicate: (value: JSONValue, index: number, obj: JSONValue[]) => unknown, thisArg?: any) => number", - "description": "Returns the index of the first element in the array where predicate is true, and -1\r\notherwise." - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "fill", - "value": "(value: JSONValue, start?: number, end?: number) => JSONArray", - "description": "Changes all array elements from `start` to `end` index to a static `value` and returns the modified array" - }, - { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "copyWithin", - "value": "(target: number, start: number, end?: number) => JSONArray", - "description": "Returns the this object after copying a section of the array identified by start and end\r\nto the same array starting at position target" + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "apiVersion", + "value": "ApiVersion", + "description": "", + "isOptional": true }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "entries", - "value": "() => IterableIterator<[number, JSONValue]>", - "description": "Returns an iterable of key, value pairs for every entry in the array" + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "headers", + "value": "{ [key: string]: any; }", + "description": "", + "isOptional": true }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "keys", - "value": "() => IterableIterator", - "description": "Returns an iterable of keys in the array" - }, + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "tries", + "value": "number", + "description": "", + "isOptional": true + } + ], + "value": "export interface GraphQLQueryOptions<\n Operation extends keyof Operations,\n Operations extends AllOperations,\n> {\n variables?: ApiClientRequestOptions['variables'];\n apiVersion?: ApiVersion;\n headers?: {[key: string]: any};\n tries?: number;\n}" + }, + "ApiVersion": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "ApiVersion", + "value": "export declare enum ApiVersion {\n October22 = \"2022-10\",\n January23 = \"2023-01\",\n April23 = \"2023-04\",\n July23 = \"2023-07\",\n October23 = \"2023-10\",\n January24 = \"2024-01\",\n Unstable = \"unstable\"\n}", + "members": [ { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "values", - "value": "() => IterableIterator", - "description": "Returns an iterable of values in the array" + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "October22", + "value": "2022-10" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "includes", - "value": "(searchElement: JSONValue, fromIndex?: number) => boolean", - "description": "Determines whether an array includes a certain element, returning true or false as appropriate." + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "January23", + "value": "2023-01" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "flatMap", - "value": "(callback: (this: This, value: JSONValue, index: number, array: JSONValue[]) => U | readonly U[], thisArg?: This) => U[]", - "description": "Calls a defined callback function on each element of an array. Then, flattens the result into\r\na new array.\r\nThis is identical to a map followed by flat with depth 1." + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "April23", + "value": "2023-04" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "flat", - "value": "(this: A, depth?: D) => FlatArray[]", - "description": "Returns a new array with all sub-array elements concatenated into it recursively up to the\r\nspecified depth." + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "July23", + "value": "2023-07" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "__@iterator@716", - "value": "() => IterableIterator", - "description": "Iterator" + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "October23", + "value": "2023-10" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "__@unscopables@718", - "value": "() => { copyWithin: boolean; entries: boolean; fill: boolean; find: boolean; findIndex: boolean; keys: boolean; values: boolean; }", - "description": "Returns an object whose properties have the value 'true'\r\nwhen they will be absent when used in a 'with' statement." + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "January24", + "value": "2024-01" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "at", - "value": "(index: number) => JSONValue", - "description": "Takes an integer value and returns the item at that index, allowing for positive and negative integers. Negative integers count back from the last item in the array." + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Unstable", + "value": "unstable" } - ], - "value": "interface JSONArray extends Array {}" + ] }, - "WebhookContextWithSession": { - "filePath": "/server/authenticate/webhooks/types.ts", - "name": "WebhookContextWithSession", + "StorefrontContext": { + "filePath": "src/server/clients/storefront/types.ts", + "name": "StorefrontContext", "description": "", "members": [ { - "filePath": "/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "session", - "value": "Session", - "description": "A session with an offline token for the shop.\n\nReturned only if there is a session for the shop." - }, - { - "filePath": "/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "admin", - "value": "WebhookAdminContext", - "description": "An admin context for the webhook.\n\nReturned only if there is a session for the shop.", - "examples": [ - { - "title": "[V3] Webhook admin context", - "description": "With the `v3_webhookAdminContext` future flag enabled, use the `admin` object in the context to interact with the Admin API.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.webhook(request);\n\n const response = await admin?.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", - "title": "/app/routes/webhooks.tsx" - } - ] - }, - { - "title": "Webhook admin context", - "description": "Use the `admin` object in the context to interact with the Admin API. This format will be removed in V3 of the package.", - "tabs": [ - { - "code": "import { json, ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.webhook(request);\n\n const response = await admin?.graphql.query({\n data: {\n query: `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n variables: { input: { title: \"Product Name\" } },\n },\n });\n\n const productData = response?.body.data;\n return json({ data: productData.data });\n}", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] - }, - { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "src/server/clients/storefront/types.ts", "syntaxKind": "PropertySignature", - "name": "apiVersion", - "value": "string", - "description": "The API version used for the webhook.", - "examples": [ - { - "title": "Webhook API version", - "description": "Get the API version used for webhook request.", - "tabs": [ - { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { apiVersion } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] - }, - { - "filePath": "/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "shop", - "value": "string", - "description": "The shop where the webhook was triggered.", + "name": "graphql", + "value": "GraphQLClient", + "description": "Method for interacting with the Shopify Storefront GraphQL API\n\nIf you're getting incorrect type hints in the Shopify template, follow [these instructions](https://github.com/Shopify/shopify-app-template-remix/tree/main#incorrect-graphql-hints).\n\n\n\n\n", "examples": [ { - "title": "Webhook shop", - "description": "Get the shop that triggered a webhook.", + "title": "Querying the GraphQL API", + "description": "Use `storefront.graphql` to make query / mutation requests.", "tabs": [ { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { shop } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", + "title": "app/routes/**\\/.ts" } ] } ] - }, - { - "filePath": "/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "topic", - "value": "Topics", - "description": "The topic of the webhook.", - "examples": [ + } + ], + "value": "export interface StorefrontContext {\n /**\n * Method for interacting with the Shopify Storefront GraphQL API\n *\n * If you're getting incorrect type hints in the Shopify template, follow [these instructions](https://github.com/Shopify/shopify-app-template-remix/tree/main#incorrect-graphql-hints).\n *\n * {@link https://shopify.dev/docs/api/storefront}\n *\n * @example\n * Querying the GraphQL API.\n * Use `storefront.graphql` to make query / mutation requests.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { storefront } = await authenticate.public.appProxy(request);\n *\n * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n *\n * return json(await response.json());\n * }\n * ```\n */\n graphql: GraphQLClient;\n}" + } + } + } + ], + "jsDocTypeExamples": [ + "AppProxyContextWithSession" + ], + "related": [ + { + "name": "Admin API context", + "subtitle": "Interact with the Admin API.", + "url": "/docs/api/shopify-app-remix/apis/admin-api" + }, + { + "name": "Storefront API context", + "subtitle": "Interact with the Storefront API.", + "url": "/docs/api/shopify-app-remix/apis/storefront-api" + } + ], + "examples": { + "description": "", + "exampleGroups": [ + { + "title": "session", + "examples": [ + { + "description": "Get the session for the shop that initiated the request to the app proxy.", + "codeblock": { + "title": "Using the session object", + "tabs": [ { - "title": "Webhook topic", - "description": "Get the event topic for the webhook.", - "tabs": [ - { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { topic } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n // Do something when the app is uninstalled.\n break;\n }\n\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] + "title": "app/routes/**\\/.ts", + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppModelData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }) => {\n // Get the session for the shop that initiated the request to the app proxy.\n const { session } = await authenticate.public.appProxy(request);\n\n // Use the session data to make to queries to your database or additional requests.\n return json(await getMyAppModelData({shop: session.shop));\n};", + "language": "typescript" } ] - }, - { - "filePath": "/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "webhookId", - "value": "string", - "description": "A unique ID for the webhook. Useful to keep track of which events your app has already processed.", - "examples": [ + } + } + ] + }, + { + "title": "admin", + "examples": [ + { + "description": "Use the `admin` object to interact with the REST or GraphQL APIs.", + "codeblock": { + "title": "Interacting with the Admin API", + "tabs": [ { - "title": "Webhook ID", - "description": "Get the webhook ID.", - "tabs": [ - { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { webhookId } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] + "title": "app/routes/**\\/.ts", + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.public.appProxy(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", + "language": "typescript" } ] - }, - { - "filePath": "/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "payload", - "value": "JSONValue", - "description": "The payload from the webhook request.", - "examples": [ + } + } + ] + }, + { + "title": "storefront", + "examples": [ + { + "description": "Use the `storefront` object to interact with the GraphQL API.", + "codeblock": { + "title": "Interacting with the Storefront API", + "tabs": [ { - "title": "Webhook payload", - "description": "Get the request's POST payload.", - "tabs": [ - { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { payload } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] + "title": "app/routes/**\\/.ts", + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", + "language": "typescript" } ] } - ], - "value": "export interface WebhookContextWithSession<\n Future extends FutureFlagOptions,\n Resources extends ShopifyRestResources,\n Topics = string | number | symbol,\n> extends Context {\n /**\n * A session with an offline token for the shop.\n *\n * Returned only if there is a session for the shop.\n */\n session: Session;\n\n /**\n * An admin context for the webhook.\n *\n * Returned only if there is a session for the shop.\n *\n * @example\n * [V3] Webhook admin context.\n * With the `v3_webhookAdminContext` future flag enabled, use the `admin` object in the context to interact with the Admin API.\n * ```ts\n * // /app/routes/webhooks.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.webhook(request);\n *\n * const response = await admin?.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n *\n * @example\n * Webhook admin context.\n * Use the `admin` object in the context to interact with the Admin API. This format will be removed in V3 of the package.\n * ```ts\n * // /app/routes/webhooks.tsx\n * import { json, ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.webhook(request);\n *\n * const response = await admin?.graphql.query({\n * data: {\n * query: `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * variables: { input: { title: \"Product Name\" } },\n * },\n * });\n *\n * const productData = response?.body.data;\n * return json({ data: productData.data });\n * }\n * ```\n */\n admin: WebhookAdminContext;\n}" - }, - "WebhookAdminContext": { - "filePath": "/server/authenticate/webhooks/types.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "WebhookAdminContext", - "value": "FeatureEnabled extends true\n ? AdminApiContext\n : LegacyWebhookAdminApiContext", - "description": "" - }, - "FeatureEnabled": { - "filePath": "/server/future/flags.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "FeatureEnabled", - "value": "Future extends FutureFlags\n ? Future[Flag] extends true\n ? true\n : false\n : false", - "description": "" - }, - "FutureFlags": { - "filePath": "/server/future/flags.ts", - "name": "FutureFlags", + } + ] + }, + { + "title": "liquid", + "examples": [ + { + "description": "Use the `liquid` helper to render a `Response` with Liquid content.", + "codeblock": { + "title": "Rendering liquid content", + "tabs": [ + { + "title": "app/routes/**\\/.ts", + "code": "import {authenticate} from \"~/shopify.server\"\n\nexport async function loader({ request }) {\n const {liquid} = await authenticate.public.appProxy(request);\n\n return liquid(\"Hello {{shop.name}}\")\n}", + "language": "typescript" + } + ] + } + } + ] + } + ] + } + }, + { + "name": "Checkout", + "description": "The `authenticate.public.checkout` function ensures that checkout extension requests are coming from Shopify, and returns helpers to respond with the correct headers.", + "category": "Authenticate", + "subCategory": "Public", + "type": "object", + "isVisualComponent": false, + "definitions": [ + { + "title": "authenticate.public.checkout", + "description": "Authenticates requests coming from Shopify checkout extensions.", + "type": "AuthenticateCheckout", + "typeDefinitions": { + "AuthenticateCheckout": { + "filePath": "src/server/authenticate/public/checkout/types.ts", + "name": "AuthenticateCheckout", "description": "", - "members": [ + "params": [ { - "filePath": "/server/future/flags.ts", - "syntaxKind": "PropertySignature", - "name": "v3_webhookAdminContext", - "value": "boolean", - "description": "When enabled, returns the same `admin` context (`AdminApiContext`) from `authenticate.webhook` that is returned from `authenticate.admin`.", - "isOptional": true, - "defaultValue": "false" + "name": "request", + "description": "", + "value": "Request", + "filePath": "src/server/authenticate/public/checkout/types.ts" }, { - "filePath": "/server/future/flags.ts", - "syntaxKind": "PropertySignature", - "name": "v3_authenticatePublic", - "value": "boolean", - "description": "When enabled authenticate.public() will not work. Use authenticate.public.checkout() instead.", + "name": "options", + "description": "", + "value": "AuthenticateCheckoutOptions", "isOptional": true, - "defaultValue": "false" + "filePath": "src/server/authenticate/public/checkout/types.ts" } ], - "value": "export interface FutureFlags {\n /**\n * When enabled, returns the same `admin` context (`AdminApiContext`) from `authenticate.webhook` that is returned from `authenticate.admin`.\n *\n * @default false\n */\n v3_webhookAdminContext?: boolean;\n\n /**\n * When enabled authenticate.public() will not work. Use authenticate.public.checkout() instead.\n *\n * @default false\n */\n v3_authenticatePublic?: boolean;\n}" - }, - "AdminContext": { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "AdminContext", - "value": "Config['isEmbeddedApp'] extends false\n ? NonEmbeddedAdminContext\n : EmbeddedAdminContext", - "description": "" + "returns": { + "filePath": "src/server/authenticate/public/checkout/types.ts", + "description": "", + "name": "Promise", + "value": "Promise" + }, + "value": "export type AuthenticateCheckout = (\n request: Request,\n options?: AuthenticateCheckoutOptions,\n) => Promise;" }, - "NonEmbeddedAdminContext": { - "filePath": "/server/authenticate/admin/types.ts", - "name": "NonEmbeddedAdminContext", + "AuthenticateCheckoutOptions": { + "filePath": "src/server/authenticate/public/checkout/types.ts", + "name": "AuthenticateCheckoutOptions", "description": "", "members": [ { - "filePath": "/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/public/checkout/types.ts", "syntaxKind": "PropertySignature", - "name": "session", - "value": "Session", - "description": "The session for the user who made the request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nUse this to get shop or user-specific data.", + "name": "corsHeaders", + "value": "string[]", + "description": "", + "isOptional": true + } + ], + "value": "export interface AuthenticateCheckoutOptions {\n corsHeaders?: string[];\n}" + }, + "CheckoutContext": { + "filePath": "src/server/authenticate/public/checkout/types.ts", + "name": "CheckoutContext", + "description": "Authenticated Context for a checkout request", + "members": [ + { + "filePath": "src/server/authenticate/public/checkout/types.ts", + "syntaxKind": "PropertySignature", + "name": "sessionToken", + "value": "JwtPayload", + "description": "The decoded and validated session token for the request\n\nRefer to the OAuth docs for the [session token payload](https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload).", "examples": [ { - "title": "Using offline sessions", - "description": "Get your app's shop-specific data using an offline session.", - "tabs": [ - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - }, - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({shop: session.shop));\n};", - "title": "/app/routes/**\\/*.ts" - } - ] - }, - { - "title": "Using online sessions", - "description": "Get your app's user-specific data using an online session.", + "title": "Using the decoded session token", + "description": "Get store-specific data using the `sessionToken` object.", "tabs": [ { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - }, - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({user: session.onlineAccessInfo!.id}));\n};", - "title": "/app/routes/**\\/*.ts" + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.public.checkout(\n request\n );\n return json(await getMyAppData({shop: sessionToken.dest}));\n};", + "title": "app/routes/public/my-route.ts" } ] } ] }, { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "admin", - "value": "AdminApiContext", - "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request." - }, - { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "billing", - "value": "BillingContext", - "description": "Billing methods for this store, based on the plans defined in the `billing` config option.\n\n\n\n\n" - }, - { - "filePath": "/server/authenticate/admin/types.ts", + "filePath": "src/server/authenticate/public/checkout/types.ts", "syntaxKind": "PropertySignature", "name": "cors", "value": "EnsureCORSFunction", "description": "A function that ensures the CORS headers are set correctly for the response.", "examples": [ { - "title": "Setting CORS headers for a admin request", - "description": "Use the `cors` helper to ensure your app can respond to requests from admin extensions.", + "title": "Setting CORS headers for a public request", + "description": "Use the `cors` helper to ensure your app can respond to checkout extension requests.", "tabs": [ { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, cors } = await authenticate.admin(request);\n return cors(json(await getMyAppData({user: session.onlineAccessInfo!.id})));\n};", - "title": "/app/routes/admin/my-route.ts" + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken, cors } = await authenticate.public.checkout(\n request,\n { corsHeaders: [\"X-My-Custom-Header\"] }\n );\n const data = await getMyAppData({shop: sessionToken.dest});\n return cors(json(data));\n};", + "title": "app/routes/public/my-route.ts" } ] } ] } ], - "value": "export interface NonEmbeddedAdminContext<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends AdminContextInternal {}" + "value": "export interface CheckoutContext {\n /**\n * The decoded and validated session token for the request\n *\n * Refer to the OAuth docs for the [session token payload](https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload).\n *\n * @example\n * Using the decoded session token.\n * Get store-specific data using the `sessionToken` object.\n * ```ts\n * // app/routes/public/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken } = await authenticate.public.checkout(\n * request\n * );\n * return json(await getMyAppData({shop: sessionToken.dest}));\n * };\n * ```\n */\n sessionToken: JwtPayload;\n\n /**\n * A function that ensures the CORS headers are set correctly for the response.\n *\n * @example\n * Setting CORS headers for a public request.\n * Use the `cors` helper to ensure your app can respond to checkout extension requests.\n * ```ts\n * // app/routes/public/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken, cors } = await authenticate.public.checkout(\n * request,\n * { corsHeaders: [\"X-My-Custom-Header\"] }\n * );\n * const data = await getMyAppData({shop: sessionToken.dest});\n * return cors(json(data));\n * };\n * ```\n */\n cors: EnsureCORSFunction;\n}" }, - "AdminApiContext": { - "filePath": "/server/clients/admin/types.ts", - "name": "AdminApiContext", + "JwtPayload": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "name": "JwtPayload", "description": "", "members": [ { - "filePath": "/server/clients/admin/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", "syntaxKind": "PropertySignature", - "name": "rest", - "value": "RestClientWithResources", - "description": "Methods for interacting with the Shopify Admin REST API\n\nThere are methods for interacting with individual REST resources. You can also make `GET`, `POST`, `PUT` and `DELETE` requests should the REST resources not meet your needs.\n\n\n\n\n", - "examples": [ - { - "title": "Using REST resources", - "description": "Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n return json(admin.rest.resources.Order.count({ session }));\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" - } - ] - }, - { - "title": "Performing a GET request to the REST API", - "description": "Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = await admin.rest.get({ path: \"/customers/count.json\" });\n const customers = await response.json();\n return json({ customers });\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" - } - ] - }, - { - "title": "Performing a POST request to the REST API", - "description": "Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = admin.rest.post({\n path: \"customers/7392136888625/send_invite.json\",\n body: {\n customer_invite: {\n to: \"new_test_email@shopify.com\",\n from: \"j.limited@example.com\",\n bcc: [\"j.limited@example.com\"],\n subject: \"Welcome to my new shop\",\n custom_message: \"My awesome new store\",\n },\n },\n});\n const customerInvite = await response.json();\n return json({ customerInvite });\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" - } - ] - } - ] + "name": "iss", + "value": "string", + "description": "" }, { - "filePath": "/server/clients/admin/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", "syntaxKind": "PropertySignature", - "name": "graphql", - "value": "GraphQLClient", - "description": "Methods for interacting with the Shopify Admin GraphQL API\n\n\n\n\n\n\n\n\n\n", - "examples": [ + "name": "dest", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "aud", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "sub", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "exp", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "nbf", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "iat", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "jti", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "sid", + "value": "string", + "description": "" + } + ], + "value": "export interface JwtPayload {\n iss: string;\n dest: string;\n aud: string;\n sub: string;\n exp: number;\n nbf: number;\n iat: number;\n jti: string;\n sid: string;\n}" + }, + "EnsureCORSFunction": { + "filePath": "src/server/authenticate/helpers/ensure-cors-headers.ts", + "name": "EnsureCORSFunction", + "description": "", + "members": [], + "value": "export interface EnsureCORSFunction {\n (response: Response): Response;\n}" + } + } + } + ], + "jsDocTypeExamples": [ + "CheckoutContext" + ], + "related": [], + "examples": { + "description": "", + "exampleGroups": [ + { + "title": "sessionToken", + "examples": [ + { + "description": "Get store-specific data using the `sessionToken` object.", + "codeblock": { + "title": "Using the decoded session token", + "tabs": [ { - "title": "Querying the GraphQL API", - "description": "Use `admin.graphql` to make query / mutation requests.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.admin(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", - "title": "Example" - } - ] + "title": "app/routes/public/my-route.ts", + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.public.checkout(\n request\n );\n return json(await getMyAppData({shop: sessionToken.dest}));\n};", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "cors", + "examples": [ + { + "description": "Use the `cors` helper to ensure your app can respond to checkout extension requests.", + "codeblock": { + "title": "Setting CORS headers for a public request", + "tabs": [ + { + "title": "app/routes/public/my-route.ts", + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken, cors } = await authenticate.public.checkout(\n request,\n { corsHeaders: [\"X-My-Custom-Header\"] }\n );\n const data = await getMyAppData({shop: sessionToken.dest});\n return cors(json(data));\n};", + "language": "typescript" } ] } + } + ] + } + ] + } + }, + { + "name": "Webhook", + "description": "Contains functions for verifying Shopify webhooks.\n\n> Note: The format of the `admin` object returned by this function changes with the `v3_webhookAdminContext` future flag. Learn more about [gradual feature adoption](/docs/api/shopify-app-remix/guide-future-flags).", + "category": "Authenticate", + "type": "object", + "isVisualComponent": false, + "definitions": [ + { + "title": "authenticate.webhook", + "description": "Verifies requests coming from Shopify webhooks.", + "type": "AuthenticateWebhook", + "typeDefinitions": { + "AuthenticateWebhook": { + "filePath": "src/server/authenticate/webhooks/types.ts", + "name": "AuthenticateWebhook", + "description": "", + "params": [ + { + "name": "request", + "description": "", + "value": "Request", + "filePath": "src/server/authenticate/webhooks/types.ts" + } ], - "value": "export interface AdminApiContext<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * Methods for interacting with the Shopify Admin REST API\n *\n * There are methods for interacting with individual REST resources. You can also make `GET`, `POST`, `PUT` and `DELETE` requests should the REST resources not meet your needs.\n *\n * {@link https://shopify.dev/docs/api/admin-rest}\n *\n * @example\n * Using REST resources.\n * Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource. \n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * return json(admin.rest.resources.Order.count({ session }));\n * };\n * ```\n *\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n *\n * @example\n * Performing a GET request to the REST API.\n * Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint\n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = await admin.rest.get({ path: \"/customers/count.json\" });\n * const customers = await response.json();\n * return json({ customers });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * @example\n * Performing a POST request to the REST API.\n * Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = admin.rest.post({\n * path: \"customers/7392136888625/send_invite.json\",\n * body: {\n * customer_invite: {\n * to: \"new_test_email@shopify.com\",\n * from: \"j.limited@example.com\",\n * bcc: [\"j.limited@example.com\"],\n * subject: \"Welcome to my new shop\",\n * custom_message: \"My awesome new store\",\n * },\n * },\n * });\n * const customerInvite = await response.json();\n * return json({ customerInvite });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n rest: RestClientWithResources;\n\n /**\n * Methods for interacting with the Shopify Admin GraphQL API\n *\n * {@link https://shopify.dev/docs/api/admin-graphql}\n * {@link https://github.com/Shopify/shopify-api-js/blob/main/packages/shopify-api/docs/reference/clients/Graphql.md}\n *\n * @example\n * Querying the GraphQL API.\n * Use `admin.graphql` to make query / mutation requests.\n * ```ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.admin(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n graphql: GraphQLClient;\n}" + "returns": { + "filePath": "src/server/authenticate/webhooks/types.ts", + "description": "", + "name": "Promise>", + "value": "Promise>" + }, + "value": "export type AuthenticateWebhook<\n Future extends FutureFlagOptions,\n Resources extends ShopifyRestResources,\n Topics = string | number | symbol,\n> = (request: Request) => Promise>;" }, - "RestClientWithResources": { - "filePath": "/server/clients/admin/rest.ts", + "WebhookContext": { + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "TypeAliasDeclaration", - "name": "RestClientWithResources", - "value": "RemixRestClient & {resources: Resources}", + "name": "WebhookContext", + "value": "WebhookContextWithoutSession | WebhookContextWithSession", "description": "" }, - "BillingContext": { - "filePath": "/server/authenticate/admin/billing/types.ts", - "name": "BillingContext", + "WebhookContextWithoutSession": { + "filePath": "src/server/authenticate/webhooks/types.ts", + "name": "WebhookContextWithoutSession", "description": "", "members": [ { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "require", - "value": "(options: RequireBillingOptions) => Promise", - "description": "Checks if the shop has an active payment for any plan defined in the `billing` config option.", + "name": "session", + "value": "undefined", + "description": "" + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "admin", + "value": "undefined", + "description": "" + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "apiVersion", + "value": "string", + "description": "The API version used for the webhook.", "examples": [ { - "title": "Requesting billing right away", - "description": "Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n isTest: true,\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n // App logic\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] - }, - { - "title": "Using a plan selection page", - "description": "When the app has multiple plans, create a page in your App that allows the merchant to select a plan. If a merchant does not have the required plan you can redirect them to page in your app to select one.", + "title": "Webhook API version", + "description": "Get the API version used for webhook request.", "tabs": [ { - "code": "import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n isTest: true,\n onFailure: () => redirect('/select-plan'),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n\n // App logic\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { apiVersion } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" } ] } ] }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "request", - "value": "(options: RequestBillingOptions) => Promise", - "description": "Requests payment for the plan.", + "name": "shop", + "value": "string", + "description": "The shop where the webhook was triggered.", "examples": [ { - "title": "Using a custom return URL", - "description": "Change where the merchant is returned to after approving the purchase using the `returnUrl` option.", + "title": "Webhook shop", + "description": "Get the shop that triggered a webhook.", "tabs": [ { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n }),\n });\n\n // App logic\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { shop } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" } ] } ] }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "cancel", - "value": "(options: CancelBillingOptions) => Promise", - "description": "Cancels an ongoing subscription, given its ID.", + "name": "topic", + "value": "Topics", + "description": "The topic of the webhook.", "examples": [ { - "title": "Cancelling a subscription", - "description": "Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.", + "title": "Webhook topic", + "description": "Get the event topic for the webhook.", "tabs": [ { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n const cancelledSubscription = await billing.cancel({\n subscriptionId: subscription.id,\n isTest: true,\n prorate: true,\n });\n\n // App logic\n};", - "title": "/app/routes/cancel-subscription.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { topic } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n // Do something when the app is uninstalled.\n break;\n }\n\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" } ] } ] - } - ], - "value": "export interface BillingContext {\n /**\n * Checks if the shop has an active payment for any plan defined in the `billing` config option.\n *\n * @returns A promise that resolves to an object containing the active purchases for the shop.\n *\n * @example\n * Requesting billing right away.\n * Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * isTest: true,\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Using a plan selection page.\n * When the app has multiple plans, create a page in your App that allows the merchant to select a plan. If a merchant does not have the required plan you can redirect them to page in your app to select one.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n * isTest: true,\n * onFailure: () => redirect('/select-plan'),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n require: (\n options: RequireBillingOptions,\n ) => Promise;\n\n /**\n * Requests payment for the plan.\n *\n * @returns Redirects to the confirmation URL for the payment.\n *\n * @example\n * Using a custom return URL.\n * Change where the merchant is returned to after approving the purchase using the `returnUrl` option.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({\n * plan: MONTHLY_PLAN,\n * isTest: true,\n * returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n * }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n request: (options: RequestBillingOptions) => Promise;\n\n /**\n * Cancels an ongoing subscription, given its ID.\n *\n * @returns The cancelled subscription.\n *\n * @example\n * Cancelling a subscription.\n * Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.\n * ```ts\n * // /app/routes/cancel-subscription.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * const cancelledSubscription = await billing.cancel({\n * subscriptionId: subscription.id,\n * isTest: true,\n * prorate: true,\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n cancel: (options: CancelBillingOptions) => Promise;\n}" - }, - "RequireBillingOptions": { - "filePath": "/server/authenticate/admin/billing/types.ts", - "name": "RequireBillingOptions", - "description": "", - "members": [ - { - "filePath": "/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "plans", - "value": "(keyof Config[\"billing\"])[]", - "description": "The plans to check for. Must be one of the values defined in the `billing` config option." }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "onFailure", - "value": "(error: any) => Promise", - "description": "How to handle the request if the shop doesn't have an active payment for any plan." + "name": "webhookId", + "value": "string", + "description": "A unique ID for the webhook. Useful to keep track of which events your app has already processed.", + "examples": [ + { + "title": "Webhook ID", + "description": "Get the webhook ID.", + "tabs": [ + { + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { webhookId } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" + } + ] + } + ] }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "isTest", - "value": "boolean", - "description": "", - "isOptional": true + "name": "payload", + "value": "JSONValue", + "description": "The payload from the webhook request.", + "examples": [ + { + "title": "Webhook payload", + "description": "Get the request's POST payload.", + "tabs": [ + { + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { payload } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" + } + ] + } + ] } ], - "value": "export interface RequireBillingOptions\n extends Omit {\n /**\n * The plans to check for. Must be one of the values defined in the `billing` config option.\n */\n plans: (keyof Config['billing'])[];\n /**\n * How to handle the request if the shop doesn't have an active payment for any plan.\n */\n onFailure: (error: any) => Promise;\n}" + "value": "export interface WebhookContextWithoutSession\n extends Context {\n session: undefined;\n admin: undefined;\n}" }, - "RequestBillingOptions": { - "filePath": "/server/authenticate/admin/billing/types.ts", - "name": "RequestBillingOptions", + "JSONValue": { + "filePath": "src/server/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "JSONValue", + "value": "string | number | boolean | null | JSONObject | JSONArray", + "description": "" + }, + "JSONObject": { + "filePath": "src/server/types.ts", + "name": "JSONObject", "description": "", "members": [ { - "filePath": "/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "plan", - "value": "keyof Config[\"billing\"]", - "description": "The plan to request. Must be one of the values defined in the `billing` config option." - }, - { - "filePath": "/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "isTest", - "value": "boolean", - "description": "Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.", - "isOptional": true - }, - { - "filePath": "/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "returnUrl", - "value": "string", - "description": "The URL to return to after the merchant approves the payment.", - "isOptional": true + "filePath": "src/server/types.ts", + "name": "[x: string]", + "value": "JSONValue" } ], - "value": "export interface RequestBillingOptions\n extends Omit {\n /**\n * The plan to request. Must be one of the values defined in the `billing` config option.\n */\n plan: keyof Config['billing'];\n /**\n * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.\n */\n isTest?: boolean;\n /**\n * The URL to return to after the merchant approves the payment.\n */\n returnUrl?: string;\n}" + "value": "interface JSONObject {\n [x: string]: JSONValue;\n}" }, - "CancelBillingOptions": { - "filePath": "/server/authenticate/admin/billing/types.ts", - "name": "CancelBillingOptions", + "JSONArray": { + "filePath": "src/server/types.ts", + "name": "JSONArray", "description": "", "members": [ { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", - "name": "subscriptionId", - "value": "string", - "description": "The ID of the subscription to cancel." + "name": "length", + "value": "number", + "description": "Gets or sets the length of the array. This is a number one higher than the highest index in the array." }, { - "filePath": "/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "prorate", - "value": "boolean", - "description": "Whether to prorate the cancellation.\n\n\n\n\n", - "isOptional": true + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "toString", + "value": "() => string", + "description": "Returns a string representation of an array." }, { - "filePath": "/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "isTest", - "value": "boolean", - "description": "", - "isOptional": true - } - ], - "value": "export interface CancelBillingOptions {\n /**\n * The ID of the subscription to cancel.\n */\n subscriptionId: string;\n /**\n * Whether to prorate the cancellation.\n *\n * {@link https://shopify.dev/docs/apps/billing/subscriptions/cancel-recurring-charges}\n */\n prorate?: boolean;\n /*\n * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.\n */\n isTest?: boolean;\n}" - }, - "EmbeddedAdminContext": { - "filePath": "/server/authenticate/admin/types.ts", - "name": "EmbeddedAdminContext", - "description": "", - "members": [ + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "toLocaleString", + "value": "() => string", + "description": "Returns a string representation of an array. The elements are converted to string using their toLocaleString methods." + }, { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "sessionToken", - "value": "JwtPayload", - "description": "The decoded and validated session token for the request.\n\nReturned only if `isEmbeddedApp` is `true`.\n\n\n\n\n", - "examples": [ - { - "title": "Using the decoded session token", - "description": "Get user-specific data using the `sessionToken` object.", - "tabs": [ - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - }, - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.public.checkout(\n request\n );\n return json(await getMyAppData({user: sessionToken.sub}));\n};", - "title": "/app/routes/**\\/*.ts" - } - ] - } - ] + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "pop", + "value": "() => JSONValue", + "description": "Removes the last element from an array and returns it. If the array is empty, undefined is returned and the array is not modified." }, { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "redirect", - "value": "RedirectFunction", - "description": "A function that redirects the user to a new page, ensuring that the appropriate parameters are set for embedded apps.\n\nReturned only if `isEmbeddedApp` is `true`.", - "examples": [ - { - "title": "Redirecting to an app route", - "description": "Use the `redirect` helper to safely redirect between pages.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, redirect } = await authenticate.admin(request);\n return redirect(\"/\");\n};", - "title": "/app/routes/admin/my-route.ts" - } - ] - }, - { - "title": "Redirecting outside of Shopify admin", - "description": "Pass in a `target` option of `_top` or `_parent` to go to an external URL.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, redirect } = await authenticate.admin(request);\n return redirect(\"/\", { target: '_parent' });\n};", - "title": "/app/routes/admin/my-route.ts" - } - ] - } - ] + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "push", + "value": "(...items: JSONValue[]) => number", + "description": "Appends new elements to the end of an array, and returns the new length of the array." }, { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "session", - "value": "Session", - "description": "The session for the user who made the request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nUse this to get shop or user-specific data.", - "examples": [ - { - "title": "Using offline sessions", - "description": "Get your app's shop-specific data using an offline session.", - "tabs": [ - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - }, - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({shop: session.shop));\n};", - "title": "/app/routes/**\\/*.ts" - } - ] - }, - { - "title": "Using online sessions", - "description": "Get your app's user-specific data using an online session.", - "tabs": [ - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - }, - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({user: session.onlineAccessInfo!.id}));\n};", - "title": "/app/routes/**\\/*.ts" - } - ] - } - ] + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "concat", + "value": "{ (...items: ConcatArray[]): JSONValue[]; (...items: (JSONValue | ConcatArray)[]): JSONValue[]; }", + "description": "Combines two or more arrays. This method returns a new array without modifying any existing arrays." }, { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "admin", - "value": "AdminApiContext", - "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request." + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "join", + "value": "(separator?: string) => string", + "description": "Adds all the elements of an array into a string, separated by the specified separator string." }, { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "billing", - "value": "BillingContext", - "description": "Billing methods for this store, based on the plans defined in the `billing` config option.\n\n\n\n\n" + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "reverse", + "value": "() => JSONValue[]", + "description": "Reverses the elements in an array in place. This method mutates the array and returns a reference to the same array." }, { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "cors", - "value": "EnsureCORSFunction", - "description": "A function that ensures the CORS headers are set correctly for the response.", - "examples": [ - { - "title": "Setting CORS headers for a admin request", - "description": "Use the `cors` helper to ensure your app can respond to requests from admin extensions.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, cors } = await authenticate.admin(request);\n return cors(json(await getMyAppData({user: session.onlineAccessInfo!.id})));\n};", - "title": "/app/routes/admin/my-route.ts" - } + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "shift", + "value": "() => JSONValue", + "description": "Removes the first element from an array and returns it. If the array is empty, undefined is returned and the array is not modified." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "slice", + "value": "(start?: number, end?: number) => JSONValue[]", + "description": "Returns a copy of a section of an array. For both start and end, a negative index can be used to indicate an offset from the end of the array. For example, -2 refers to the second to last element of the array." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "sort", + "value": "(compareFn?: (a: JSONValue, b: JSONValue) => number) => JSONArray", + "description": "Sorts an array in place. This method mutates the array and returns a reference to the same array." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "splice", + "value": "{ (start: number, deleteCount?: number): JSONValue[]; (start: number, deleteCount: number, ...items: JSONValue[]): JSONValue[]; }", + "description": "Removes elements from an array and, if necessary, inserts new elements in their place, returning the deleted elements." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "unshift", + "value": "(...items: JSONValue[]) => number", + "description": "Inserts new elements at the start of an array, and returns the new length of the array." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "indexOf", + "value": "(searchElement: JSONValue, fromIndex?: number) => number", + "description": "Returns the index of the first occurrence of a value in an array, or -1 if it is not present." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "lastIndexOf", + "value": "(searchElement: JSONValue, fromIndex?: number) => number", + "description": "Returns the index of the last occurrence of a specified value in an array, or -1 if it is not present." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "every", + "value": "{ (predicate: (value: JSONValue, index: number, array: JSONValue[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: JSONValue, index: number, array: JSONValue[]) => unknown, thisArg?: any): boolean; }", + "description": "Determines whether all the members of an array satisfy the specified test." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "some", + "value": "(predicate: (value: JSONValue, index: number, array: JSONValue[]) => unknown, thisArg?: any) => boolean", + "description": "Determines whether the specified callback function returns true for any element of an array." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "forEach", + "value": "(callbackfn: (value: JSONValue, index: number, array: JSONValue[]) => void, thisArg?: any) => void", + "description": "Performs the specified action for each element in an array." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "map", + "value": "(callbackfn: (value: JSONValue, index: number, array: JSONValue[]) => U, thisArg?: any) => U[]", + "description": "Calls a defined callback function on each element of an array, and returns an array that contains the results." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "filter", + "value": "{ (predicate: (value: JSONValue, index: number, array: JSONValue[]) => value is S, thisArg?: any): S[]; (predicate: (value: JSONValue, index: number, array: JSONValue[]) => unknown, thisArg?: any): JSONValue[]; }", + "description": "Returns the elements of an array that meet the condition specified in a callback function." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "reduce", + "value": "{ (callbackfn: (previousValue: JSONValue, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => JSONValue): JSONValue; (callbackfn: (previousValue: JSONValue, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => JSONValue, initialValue: JSONValue): JSONValue; (callbackfn: (previousValue: U, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => U, initialValue: U): U; }", + "description": "Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "reduceRight", + "value": "{ (callbackfn: (previousValue: JSONValue, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => JSONValue): JSONValue; (callbackfn: (previousValue: JSONValue, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => JSONValue, initialValue: JSONValue): JSONValue; (callbackfn: (previousValue: U, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => U, initialValue: U): U; }", + "description": "Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "find", + "value": "{ (predicate: (value: JSONValue, index: number, obj: JSONValue[]) => value is S, thisArg?: any): S; (predicate: (value: JSONValue, index: number, obj: JSONValue[]) => unknown, thisArg?: any): JSONValue; }", + "description": "Returns the value of the first element in the array where predicate is true, and undefined otherwise." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "findIndex", + "value": "(predicate: (value: JSONValue, index: number, obj: JSONValue[]) => unknown, thisArg?: any) => number", + "description": "Returns the index of the first element in the array where predicate is true, and -1 otherwise." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "fill", + "value": "(value: JSONValue, start?: number, end?: number) => JSONArray", + "description": "Changes all array elements from `start` to `end` index to a static `value` and returns the modified array" + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "copyWithin", + "value": "(target: number, start: number, end?: number) => JSONArray", + "description": "Returns the this object after copying a section of the array identified by start and end to the same array starting at position target" + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "entries", + "value": "() => IterableIterator<[number, JSONValue]>", + "description": "Returns an iterable of key, value pairs for every entry in the array" + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "keys", + "value": "() => IterableIterator", + "description": "Returns an iterable of keys in the array" + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "values", + "value": "() => IterableIterator", + "description": "Returns an iterable of values in the array" + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "includes", + "value": "(searchElement: JSONValue, fromIndex?: number) => boolean", + "description": "Determines whether an array includes a certain element, returning true or false as appropriate." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "flatMap", + "value": "(callback: (this: This, value: JSONValue, index: number, array: JSONValue[]) => U | readonly U[], thisArg?: This) => U[]", + "description": "Calls a defined callback function on each element of an array. Then, flattens the result into a new array. This is identical to a map followed by flat with depth 1." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "flat", + "value": "(this: A, depth?: D) => FlatArray[]", + "description": "Returns a new array with all sub-array elements concatenated into it recursively up to the specified depth." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "__@iterator@1657", + "value": "() => IterableIterator", + "description": "Iterator" + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "PropertySignature", + "name": "__@unscopables@1659", + "value": "{ [x: number]: boolean; length?: boolean; toString?: boolean; toLocaleString?: boolean; pop?: boolean; push?: boolean; concat?: boolean; join?: boolean; reverse?: boolean; shift?: boolean; slice?: boolean; sort?: boolean; splice?: boolean; unshift?: boolean; indexOf?: boolean; lastIndexOf?: boolean; every?: boolean; some?: boolean; forEach?: boolean; map?: boolean; filter?: boolean; reduce?: boolean; reduceRight?: boolean; find?: boolean; findIndex?: boolean; fill?: boolean; copyWithin?: boolean; entries?: boolean; keys?: boolean; values?: boolean; includes?: boolean; flatMap?: boolean; flat?: boolean; [Symbol.iterator]?: boolean; readonly [Symbol.unscopables]?: boolean; at?: boolean; }", + "description": "Is an object whose properties have the value 'true' when they will be absent when used in a 'with' statement." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "at", + "value": "(index: number) => JSONValue", + "description": "Takes an integer value and returns the item at that index, allowing for positive and negative integers. Negative integers count back from the last item in the array." + } + ], + "value": "interface JSONArray extends Array {}" + }, + "WebhookContextWithSession": { + "filePath": "src/server/authenticate/webhooks/types.ts", + "name": "WebhookContextWithSession", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "A session with an offline token for the shop.\n\nReturned only if there is a session for the shop." + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "admin", + "value": "WebhookAdminContext", + "description": "An admin context for the webhook.\n\nReturned only if there is a session for the shop.", + "examples": [ + { + "title": "[V3] Webhook admin context", + "description": "With the `v3_webhookAdminContext` future flag enabled, use the `admin` object in the context to interact with the Admin API.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.webhook(request);\n\n const response = await admin?.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", + "title": "/app/routes/webhooks.tsx" + } + ] + }, + { + "title": "Webhook admin context", + "description": "Use the `admin` object in the context to interact with the Admin API. This format will be removed in V3 of the package.", + "tabs": [ + { + "code": "import { json, ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.webhook(request);\n\n const response = await admin?.graphql.query({\n data: {\n query: `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n variables: { input: { title: \"Product Name\" } },\n },\n });\n\n const productData = response?.body.data;\n return json({ data: productData.data });\n}", + "title": "/app/routes/webhooks.tsx" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "apiVersion", + "value": "string", + "description": "The API version used for the webhook.", + "examples": [ + { + "title": "Webhook API version", + "description": "Get the API version used for webhook request.", + "tabs": [ + { + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { apiVersion } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "shop", + "value": "string", + "description": "The shop where the webhook was triggered.", + "examples": [ + { + "title": "Webhook shop", + "description": "Get the shop that triggered a webhook.", + "tabs": [ + { + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { shop } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "topic", + "value": "Topics", + "description": "The topic of the webhook.", + "examples": [ + { + "title": "Webhook topic", + "description": "Get the event topic for the webhook.", + "tabs": [ + { + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { topic } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n // Do something when the app is uninstalled.\n break;\n }\n\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "webhookId", + "value": "string", + "description": "A unique ID for the webhook. Useful to keep track of which events your app has already processed.", + "examples": [ + { + "title": "Webhook ID", + "description": "Get the webhook ID.", + "tabs": [ + { + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { webhookId } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "payload", + "value": "JSONValue", + "description": "The payload from the webhook request.", + "examples": [ + { + "title": "Webhook payload", + "description": "Get the request's POST payload.", + "tabs": [ + { + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { payload } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" + } + ] + } + ] + } + ], + "value": "export interface WebhookContextWithSession<\n Future extends FutureFlagOptions,\n Resources extends ShopifyRestResources,\n Topics = string | number | symbol,\n> extends Context {\n /**\n * A session with an offline token for the shop.\n *\n * Returned only if there is a session for the shop.\n */\n session: Session;\n\n /**\n * An admin context for the webhook.\n *\n * Returned only if there is a session for the shop.\n *\n * @example\n * [V3] Webhook admin context.\n * With the `v3_webhookAdminContext` future flag enabled, use the `admin` object in the context to interact with the Admin API.\n * ```ts\n * // /app/routes/webhooks.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.webhook(request);\n *\n * const response = await admin?.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n *\n * @example\n * Webhook admin context.\n * Use the `admin` object in the context to interact with the Admin API. This format will be removed in V3 of the package.\n * ```ts\n * // /app/routes/webhooks.tsx\n * import { json, ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.webhook(request);\n *\n * const response = await admin?.graphql.query({\n * data: {\n * query: `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * variables: { input: { title: \"Product Name\" } },\n * },\n * });\n *\n * const productData = response?.body.data;\n * return json({ data: productData.data });\n * }\n * ```\n */\n admin: WebhookAdminContext;\n}" + }, + "Session": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "name": "Session", + "description": "Stores App information from logged in merchants so they can make authenticated requests to the Admin API.", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "shop", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "state", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "isOnline", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "scope", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "expires", + "value": "Date", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "accessToken", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "onlineAccessInfo", + "value": "OnlineAccessInfo", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isActive", + "value": "(scopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isScopeChanged", + "value": "(scopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isExpired", + "value": "(withinMillisecondsOfExpiry?: number) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toObject", + "value": "() => SessionParams", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "equals", + "value": "(other: Session) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toPropertyArray", + "value": "() => [string, string | number | boolean][]", + "description": "" + } + ], + "value": "export declare class Session {\n static fromPropertyArray(entries: [string, string | number | boolean][]): Session;\n readonly id: string;\n shop: string;\n state: string;\n isOnline: boolean;\n scope?: string;\n expires?: Date;\n accessToken?: string;\n onlineAccessInfo?: OnlineAccessInfo;\n constructor(params: SessionParams);\n isActive(scopes: AuthScopes | string | string[]): boolean;\n isScopeChanged(scopes: AuthScopes | string | string[]): boolean;\n isExpired(withinMillisecondsOfExpiry?: number): boolean;\n toObject(): SessionParams;\n equals(other: Session | undefined): boolean;\n toPropertyArray(): [string, string | number | boolean][];\n}" + }, + "OnlineAccessInfo": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "name": "OnlineAccessInfo", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "expires_in", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "associated_user_scope", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "associated_user", + "value": "{ id: number; first_name: string; last_name: string; email: string; email_verified: boolean; account_owner: boolean; locale: string; collaborator: boolean; }", + "description": "" + } + ], + "value": "export interface OnlineAccessInfo {\n expires_in: number;\n associated_user_scope: string;\n associated_user: {\n id: number;\n first_name: string;\n last_name: string;\n email: string;\n email_verified: boolean;\n account_owner: boolean;\n locale: string;\n collaborator: boolean;\n };\n}" + }, + "AuthScopes": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "name": "AuthScopes", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "has", + "value": "(scope: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "equals", + "value": "(otherScopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toString", + "value": "() => string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toArray", + "value": "() => string[]", + "description": "" + } + ], + "value": "declare class AuthScopes {\n static SCOPE_DELIMITER: string;\n private compressedScopes;\n private expandedScopes;\n constructor(scopes: string | string[] | AuthScopes | undefined);\n has(scope: string | string[] | AuthScopes | undefined): boolean;\n equals(otherScopes: string | string[] | AuthScopes | undefined): boolean;\n toString(): string;\n toArray(): string[];\n private getImpliedScopes;\n}" + }, + "SessionParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "name": "SessionParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "shop", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "state", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "isOnline", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "scope", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "expires", + "value": "Date", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "accessToken", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "onlineAccessInfo", + "value": "OnlineAccessInfo", + "description": "", + "isOptional": true + } + ], + "value": "export interface SessionParams {\n readonly id: string;\n shop: string;\n state: string;\n isOnline: boolean;\n scope?: string;\n expires?: Date;\n accessToken?: string;\n onlineAccessInfo?: OnlineAccessInfo;\n}" + }, + "WebhookAdminContext": { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "WebhookAdminContext", + "value": "FeatureEnabled extends true\n ? AdminApiContext\n : LegacyWebhookAdminApiContext", + "description": "" + }, + "AdminContext": { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "AdminContext", + "value": "Config['isEmbeddedApp'] extends false\n ? NonEmbeddedAdminContext\n : EmbeddedAdminContext", + "description": "" + }, + "NonEmbeddedAdminContext": { + "filePath": "src/server/authenticate/admin/types.ts", + "name": "NonEmbeddedAdminContext", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "The session for the user who made the request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nUse this to get shop or user-specific data.", + "examples": [ + { + "title": "Using offline sessions", + "description": "Get your app's shop-specific data using an offline session.", + "tabs": [ + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + }, + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({shop: session.shop));\n};", + "title": "/app/routes/**\\/*.ts" + } + ] + }, + { + "title": "Using online sessions", + "description": "Get your app's user-specific data using an online session.", + "tabs": [ + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + }, + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({user: session.onlineAccessInfo!.id}));\n};", + "title": "/app/routes/**\\/*.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "admin", + "value": "AdminApiContext", + "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request." + }, + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "billing", + "value": "BillingContext", + "description": "Billing methods for this store, based on the plans defined in the `billing` config option.\n\n\n\n\n" + }, + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "cors", + "value": "EnsureCORSFunction", + "description": "A function that ensures the CORS headers are set correctly for the response.", + "examples": [ + { + "title": "Setting CORS headers for a admin request", + "description": "Use the `cors` helper to ensure your app can respond to requests from admin extensions.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, cors } = await authenticate.admin(request);\n return cors(json(await getMyAppData({user: session.onlineAccessInfo!.id})));\n};", + "title": "/app/routes/admin/my-route.ts" + } + ] + } + ] + } + ], + "value": "export interface NonEmbeddedAdminContext<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends AdminContextInternal {}" + }, + "AdminApiContext": { + "filePath": "src/server/clients/admin/types.ts", + "name": "AdminApiContext", + "description": "", + "members": [ + { + "filePath": "src/server/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "rest", + "value": "RestClientWithResources", + "description": "Methods for interacting with the Shopify Admin REST API\n\nThere are methods for interacting with individual REST resources. You can also make `GET`, `POST`, `PUT` and `DELETE` requests should the REST resources not meet your needs.\n\n\n\n\n", + "examples": [ + { + "title": "Using REST resources", + "description": "Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n return json(admin.rest.resources.Order.count({ session }));\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] + }, + { + "title": "Performing a GET request to the REST API", + "description": "Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = await admin.rest.get({ path: \"/customers/count.json\" });\n const customers = await response.json();\n return json({ customers });\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] + }, + { + "title": "Performing a POST request to the REST API", + "description": "Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = admin.rest.post({\n path: \"customers/7392136888625/send_invite.json\",\n body: {\n customer_invite: {\n to: \"new_test_email@shopify.com\",\n from: \"j.limited@example.com\",\n bcc: [\"j.limited@example.com\"],\n subject: \"Welcome to my new shop\",\n custom_message: \"My awesome new store\",\n },\n },\n});\n const customerInvite = await response.json();\n return json({ customerInvite });\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "graphql", + "value": "GraphQLClient", + "description": "Methods for interacting with the Shopify Admin GraphQL API\n\n\n\n\n\n\n\n\n\n", + "examples": [ + { + "title": "Querying the GraphQL API", + "description": "Use `admin.graphql` to make query / mutation requests.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.admin(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", + "title": "Example" + } + ] + } + ] + } + ], + "value": "export interface AdminApiContext<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * Methods for interacting with the Shopify Admin REST API\n *\n * There are methods for interacting with individual REST resources. You can also make `GET`, `POST`, `PUT` and `DELETE` requests should the REST resources not meet your needs.\n *\n * {@link https://shopify.dev/docs/api/admin-rest}\n *\n * @example\n * Using REST resources.\n * Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource. \n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * return json(admin.rest.resources.Order.count({ session }));\n * };\n * ```\n *\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n *\n * @example\n * Performing a GET request to the REST API.\n * Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint\n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = await admin.rest.get({ path: \"/customers/count.json\" });\n * const customers = await response.json();\n * return json({ customers });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * @example\n * Performing a POST request to the REST API.\n * Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = admin.rest.post({\n * path: \"customers/7392136888625/send_invite.json\",\n * body: {\n * customer_invite: {\n * to: \"new_test_email@shopify.com\",\n * from: \"j.limited@example.com\",\n * bcc: [\"j.limited@example.com\"],\n * subject: \"Welcome to my new shop\",\n * custom_message: \"My awesome new store\",\n * },\n * },\n * });\n * const customerInvite = await response.json();\n * return json({ customerInvite });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n rest: RestClientWithResources;\n\n /**\n * Methods for interacting with the Shopify Admin GraphQL API\n *\n * {@link https://shopify.dev/docs/api/admin-graphql}\n * {@link https://github.com/Shopify/shopify-api-js/blob/main/packages/shopify-api/docs/reference/clients/Graphql.md}\n *\n * @example\n * Querying the GraphQL API.\n * Use `admin.graphql` to make query / mutation requests.\n * ```ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.admin(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n graphql: GraphQLClient;\n}" + }, + "RestClientWithResources": { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RestClientWithResources", + "value": "RemixRestClient & {resources: Resources}", + "description": "" + }, + "RemixRestClient": { + "filePath": "src/server/clients/admin/rest.ts", + "name": "RemixRestClient", + "description": "", + "members": [ + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "PropertyDeclaration", + "name": "session", + "value": "Session", + "description": "" + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "get", + "value": "(params: GetRequestParams) => Promise", + "description": "Performs a GET request on the given path." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "post", + "value": "(params: PostRequestParams) => Promise", + "description": "Performs a POST request on the given path." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "put", + "value": "(params: PostRequestParams) => Promise", + "description": "Performs a PUT request on the given path." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "delete", + "value": "(params: GetRequestParams) => Promise", + "description": "Performs a DELETE request on the given path." + } + ], + "value": "class RemixRestClient {\n public session: Session;\n private params: AdminClientOptions['params'];\n private handleClientError: AdminClientOptions['handleClientError'];\n\n constructor({params, session, handleClientError}: AdminClientOptions) {\n this.params = params;\n this.handleClientError = handleClientError;\n this.session = session;\n }\n\n /**\n * Performs a GET request on the given path.\n */\n public async get(params: GetRequestParams) {\n return this.makeRequest({\n method: 'GET' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a POST request on the given path.\n */\n public async post(params: PostRequestParams) {\n return this.makeRequest({\n method: 'POST' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a PUT request on the given path.\n */\n public async put(params: PutRequestParams) {\n return this.makeRequest({\n method: 'PUT' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a DELETE request on the given path.\n */\n public async delete(params: DeleteRequestParams) {\n return this.makeRequest({\n method: 'DELETE' as RequestParams['method'],\n ...params,\n });\n }\n\n protected async makeRequest(params: RequestParams): Promise {\n const originalClient = new this.params.api.clients.Rest({\n session: this.session,\n });\n const originalRequest = Reflect.get(originalClient, 'request');\n\n try {\n const apiResponse = await originalRequest.call(originalClient, params);\n\n // We use a separate client for REST requests and REST resources because we want to override the API library\n // client class to return a Response object instead.\n return new Response(JSON.stringify(apiResponse.body), {\n headers: apiResponse.headers,\n });\n } catch (error) {\n if (this.handleClientError) {\n throw await this.handleClientError({\n error,\n session: this.session,\n params: this.params,\n });\n } else throw new Error(error);\n }\n }\n}" + }, + "GetRequestParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "GetRequestParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "path", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "type", + "value": "DataType", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "data", + "value": "string | Record", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "query", + "value": "SearchParams", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "extraHeaders", + "value": "HeaderParams", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "tries", + "value": "number", + "description": "", + "isOptional": true + } + ], + "value": "export interface GetRequestParams {\n path: string;\n type?: DataType;\n data?: Record | string;\n query?: SearchParams;\n extraHeaders?: HeaderParams;\n tries?: number;\n}" + }, + "DataType": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "DataType", + "value": "export declare enum DataType {\n JSON = \"application/json\",\n GraphQL = \"application/graphql\",\n URLEncoded = \"application/x-www-form-urlencoded\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "JSON", + "value": "application/json" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "GraphQL", + "value": "application/graphql" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "URLEncoded", + "value": "application/x-www-form-urlencoded" + } + ] + }, + "HeaderParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "HeaderParams", + "value": "Record", + "description": "", + "members": [] + }, + "PostRequestParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "PostRequestParams", + "value": "GetRequestParams & {\n data: Record | string;\n}", + "description": "" + }, + "GraphQLClient": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLClient", + "description": "", + "params": [ + { + "name": "query", + "description": "", + "value": "Operation extends keyof Operations", + "filePath": "src/server/clients/types.ts" + }, + { + "name": "options", + "description": "", + "value": "GraphQLQueryOptions", + "isOptional": true, + "filePath": "src/server/clients/types.ts" + } + ], + "returns": { + "filePath": "src/server/clients/types.ts", + "description": "", + "name": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}", + "value": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}" + }, + "value": "export type GraphQLClient = <\n Operation extends keyof Operations,\n>(\n query: Operation,\n options?: GraphQLQueryOptions,\n) => Promise<\n ResponseWithType>>\n>;" + }, + "GraphQLQueryOptions": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLQueryOptions", + "description": "", + "members": [ + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "variables", + "value": "ApiClientRequestOptions[\"variables\"]", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "apiVersion", + "value": "ApiVersion", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "headers", + "value": "{ [key: string]: any; }", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "tries", + "value": "number", + "description": "", + "isOptional": true + } + ], + "value": "export interface GraphQLQueryOptions<\n Operation extends keyof Operations,\n Operations extends AllOperations,\n> {\n variables?: ApiClientRequestOptions['variables'];\n apiVersion?: ApiVersion;\n headers?: {[key: string]: any};\n tries?: number;\n}" + }, + "ApiVersion": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "ApiVersion", + "value": "export declare enum ApiVersion {\n October22 = \"2022-10\",\n January23 = \"2023-01\",\n April23 = \"2023-04\",\n July23 = \"2023-07\",\n October23 = \"2023-10\",\n January24 = \"2024-01\",\n Unstable = \"unstable\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "October22", + "value": "2022-10" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "January23", + "value": "2023-01" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "April23", + "value": "2023-04" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "July23", + "value": "2023-07" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "October23", + "value": "2023-10" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "January24", + "value": "2024-01" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Unstable", + "value": "unstable" + } + ] + }, + "BillingContext": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "BillingContext", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "require", + "value": "(options: RequireBillingOptions) => Promise", + "description": "Checks if the shop has an active payment for any plan defined in the `billing` config option.", + "examples": [ + { + "title": "Requesting billing right away", + "description": "Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n isTest: true,\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n // App logic\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + }, + { + "title": "Using a plan selection page", + "description": "When the app has multiple plans, create a page in your App that allows the merchant to select a plan. If a merchant does not have the required plan you can redirect them to page in your app to select one.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n isTest: true,\n onFailure: () => redirect('/select-plan'),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n\n // App logic\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "request", + "value": "(options: RequestBillingOptions) => Promise", + "description": "Requests payment for the plan.", + "examples": [ + { + "title": "Using a custom return URL", + "description": "Change where the merchant is returned to after approving the purchase using the `returnUrl` option.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n }),\n });\n\n // App logic\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "cancel", + "value": "(options: CancelBillingOptions) => Promise", + "description": "Cancels an ongoing subscription, given its ID.", + "examples": [ + { + "title": "Cancelling a subscription", + "description": "Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n const cancelledSubscription = await billing.cancel({\n subscriptionId: subscription.id,\n isTest: true,\n prorate: true,\n });\n\n // App logic\n};", + "title": "/app/routes/cancel-subscription.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + } + ] + } + ], + "value": "export interface BillingContext {\n /**\n * Checks if the shop has an active payment for any plan defined in the `billing` config option.\n *\n * @returns A promise that resolves to an object containing the active purchases for the shop.\n *\n * @example\n * Requesting billing right away.\n * Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * isTest: true,\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Using a plan selection page.\n * When the app has multiple plans, create a page in your App that allows the merchant to select a plan. If a merchant does not have the required plan you can redirect them to page in your app to select one.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n * isTest: true,\n * onFailure: () => redirect('/select-plan'),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n require: (\n options: RequireBillingOptions,\n ) => Promise;\n\n /**\n * Requests payment for the plan.\n *\n * @returns Redirects to the confirmation URL for the payment.\n *\n * @example\n * Using a custom return URL.\n * Change where the merchant is returned to after approving the purchase using the `returnUrl` option.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({\n * plan: MONTHLY_PLAN,\n * isTest: true,\n * returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n * }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n request: (options: RequestBillingOptions) => Promise;\n\n /**\n * Cancels an ongoing subscription, given its ID.\n *\n * @returns The cancelled subscription.\n *\n * @example\n * Cancelling a subscription.\n * Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.\n * ```ts\n * // /app/routes/cancel-subscription.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * const cancelledSubscription = await billing.cancel({\n * subscriptionId: subscription.id,\n * isTest: true,\n * prorate: true,\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n cancel: (options: CancelBillingOptions) => Promise;\n}" + }, + "RequireBillingOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "RequireBillingOptions", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "plans", + "value": "(keyof Config[\"billing\"])[]", + "description": "The plans to check for. Must be one of the values defined in the `billing` config option." + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "onFailure", + "value": "(error: any) => Promise", + "description": "How to handle the request if the shop doesn't have an active payment for any plan." + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "isTest", + "value": "boolean", + "description": "", + "isOptional": true + } + ], + "value": "export interface RequireBillingOptions\n extends Omit {\n /**\n * The plans to check for. Must be one of the values defined in the `billing` config option.\n */\n plans: (keyof Config['billing'])[];\n /**\n * How to handle the request if the shop doesn't have an active payment for any plan.\n */\n onFailure: (error: any) => Promise;\n}" + }, + "BillingCheckResponseObject": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingCheckResponseObject", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "hasActivePayment", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "oneTimePurchases", + "value": "OneTimePurchase[]", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "appSubscriptions", + "value": "AppSubscription[]", + "description": "" + } + ], + "value": "export interface BillingCheckResponseObject {\n hasActivePayment: boolean;\n oneTimePurchases: OneTimePurchase[];\n appSubscriptions: AppSubscription[];\n}" + }, + "OneTimePurchase": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "OneTimePurchase", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "name", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "test", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "status", + "value": "string", + "description": "" + } + ], + "value": "export interface OneTimePurchase {\n id: string;\n name: string;\n test: boolean;\n status: string;\n}" + }, + "AppSubscription": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "AppSubscription", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "name", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "test", + "value": "boolean", + "description": "" + } + ], + "value": "export interface AppSubscription {\n id: string;\n name: string;\n test: boolean;\n}" + }, + "RequestBillingOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "RequestBillingOptions", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "plan", + "value": "keyof Config[\"billing\"]", + "description": "The plan to request. Must be one of the values defined in the `billing` config option." + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "isTest", + "value": "boolean", + "description": "Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.", + "isOptional": true + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "returnUrl", + "value": "string", + "description": "The URL to return to after the merchant approves the payment.", + "isOptional": true + } + ], + "value": "export interface RequestBillingOptions\n extends Omit {\n /**\n * The plan to request. Must be one of the values defined in the `billing` config option.\n */\n plan: keyof Config['billing'];\n /**\n * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.\n */\n isTest?: boolean;\n /**\n * The URL to return to after the merchant approves the payment.\n */\n returnUrl?: string;\n}" + }, + "CancelBillingOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "CancelBillingOptions", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "subscriptionId", + "value": "string", + "description": "The ID of the subscription to cancel." + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "prorate", + "value": "boolean", + "description": "Whether to prorate the cancellation.\n\n\n\n\n", + "isOptional": true + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "isTest", + "value": "boolean", + "description": "", + "isOptional": true + } + ], + "value": "export interface CancelBillingOptions {\n /**\n * The ID of the subscription to cancel.\n */\n subscriptionId: string;\n /**\n * Whether to prorate the cancellation.\n *\n * {@link https://shopify.dev/docs/apps/billing/subscriptions/cancel-recurring-charges}\n */\n prorate?: boolean;\n /*\n * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.\n */\n isTest?: boolean;\n}" + }, + "EnsureCORSFunction": { + "filePath": "src/server/authenticate/helpers/ensure-cors-headers.ts", + "name": "EnsureCORSFunction", + "description": "", + "members": [], + "value": "export interface EnsureCORSFunction {\n (response: Response): Response;\n}" + }, + "EmbeddedAdminContext": { + "filePath": "src/server/authenticate/admin/types.ts", + "name": "EmbeddedAdminContext", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "sessionToken", + "value": "JwtPayload", + "description": "The decoded and validated session token for the request.\n\nReturned only if `isEmbeddedApp` is `true`.\n\n\n\n\n", + "examples": [ + { + "title": "Using the decoded session token", + "description": "Get user-specific data using the `sessionToken` object.", + "tabs": [ + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + }, + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.public.checkout(\n request\n );\n return json(await getMyAppData({user: sessionToken.sub}));\n};", + "title": "/app/routes/**\\/*.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "redirect", + "value": "RedirectFunction", + "description": "A function that redirects the user to a new page, ensuring that the appropriate parameters are set for embedded apps.\n\nReturned only if `isEmbeddedApp` is `true`.", + "examples": [ + { + "title": "Redirecting to an app route", + "description": "Use the `redirect` helper to safely redirect between pages.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, redirect } = await authenticate.admin(request);\n return redirect(\"/\");\n};", + "title": "/app/routes/admin/my-route.ts" + } + ] + }, + { + "title": "Redirecting outside of Shopify admin", + "description": "Pass in a `target` option of `_top` or `_parent` to go to an external URL.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, redirect } = await authenticate.admin(request);\n return redirect(\"/\", { target: '_parent' });\n};", + "title": "/app/routes/admin/my-route.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "The session for the user who made the request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nUse this to get shop or user-specific data.", + "examples": [ + { + "title": "Using offline sessions", + "description": "Get your app's shop-specific data using an offline session.", + "tabs": [ + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + }, + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({shop: session.shop));\n};", + "title": "/app/routes/**\\/*.ts" + } + ] + }, + { + "title": "Using online sessions", + "description": "Get your app's user-specific data using an online session.", + "tabs": [ + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + }, + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({user: session.onlineAccessInfo!.id}));\n};", + "title": "/app/routes/**\\/*.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "admin", + "value": "AdminApiContext", + "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request." + }, + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "billing", + "value": "BillingContext", + "description": "Billing methods for this store, based on the plans defined in the `billing` config option.\n\n\n\n\n" + }, + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "cors", + "value": "EnsureCORSFunction", + "description": "A function that ensures the CORS headers are set correctly for the response.", + "examples": [ + { + "title": "Setting CORS headers for a admin request", + "description": "Use the `cors` helper to ensure your app can respond to requests from admin extensions.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, cors } = await authenticate.admin(request);\n return cors(json(await getMyAppData({user: session.onlineAccessInfo!.id})));\n};", + "title": "/app/routes/admin/my-route.ts" + } + ] + } + ] + } + ], + "value": "export interface EmbeddedAdminContext<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends AdminContextInternal {\n /**\n * The decoded and validated session token for the request.\n *\n * Returned only if `isEmbeddedApp` is `true`.\n *\n * {@link https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload}\n *\n * @example\n * Using the decoded session token.\n * Get user-specific data using the `sessionToken` object.\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * useOnlineTokens: true,\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken } = await authenticate.public.checkout(\n * request\n * );\n * return json(await getMyAppData({user: sessionToken.sub}));\n * };\n * ```\n */\n sessionToken: JwtPayload;\n\n /**\n * A function that redirects the user to a new page, ensuring that the appropriate parameters are set for embedded\n * apps.\n *\n * Returned only if `isEmbeddedApp` is `true`.\n *\n * @example\n * Redirecting to an app route.\n * Use the `redirect` helper to safely redirect between pages.\n * ```ts\n * // /app/routes/admin/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { session, redirect } = await authenticate.admin(request);\n * return redirect(\"/\");\n * };\n * ```\n *\n * @example\n * Redirecting outside of Shopify admin.\n * Pass in a `target` option of `_top` or `_parent` to go to an external URL.\n * ```ts\n * // /app/routes/admin/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { session, redirect } = await authenticate.admin(request);\n * return redirect(\"/\", { target: '_parent' });\n * };\n * ```\n */\n redirect: RedirectFunction;\n}" + }, + "JwtPayload": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "name": "JwtPayload", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "iss", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "dest", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "aud", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "sub", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "exp", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "nbf", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "iat", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "jti", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "sid", + "value": "string", + "description": "" + } + ], + "value": "export interface JwtPayload {\n iss: string;\n dest: string;\n aud: string;\n sub: string;\n exp: number;\n nbf: number;\n iat: number;\n jti: string;\n sid: string;\n}" + }, + "RedirectFunction": { + "filePath": "src/server/authenticate/admin/helpers/redirect.ts", + "name": "RedirectFunction", + "description": "", + "params": [ + { + "name": "url", + "description": "", + "value": "string", + "filePath": "src/server/authenticate/admin/helpers/redirect.ts" + }, + { + "name": "init", + "description": "", + "value": "RedirectInit", + "isOptional": true, + "filePath": "src/server/authenticate/admin/helpers/redirect.ts" + } + ], + "returns": { + "filePath": "src/server/authenticate/admin/helpers/redirect.ts", + "description": "", + "name": "TypedResponse", + "value": "TypedResponse" + }, + "value": "export type RedirectFunction = (\n url: string,\n init?: RedirectInit,\n) => TypedResponse;" + }, + "RedirectInit": { + "filePath": "src/server/authenticate/admin/helpers/redirect.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RedirectInit", + "value": "number | (ResponseInit & {target?: RedirectTarget})", + "description": "" + }, + "RedirectTarget": { + "filePath": "src/server/authenticate/admin/helpers/redirect.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RedirectTarget", + "value": "'_self' | '_parent' | '_top'", + "description": "" + }, + "LegacyWebhookAdminApiContext": { + "filePath": "src/server/authenticate/webhooks/types.ts", + "name": "LegacyWebhookAdminApiContext", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "rest", + "value": "RestClient & Resources", + "description": "A REST client." + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "graphql", + "value": "InstanceType", + "description": "A GraphQL client." + } + ], + "value": "export interface LegacyWebhookAdminApiContext<\n Resources extends ShopifyRestResources,\n> {\n /** A REST client. */\n rest: InstanceType & Resources;\n /** A GraphQL client. */\n graphql: InstanceType;\n}" + }, + "RestClient": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/rest/client.d.ts", + "name": "RestClient", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/rest/client.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "loggedDeprecations", + "value": "Record", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/rest/client.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "client", + "value": "AdminRestApiClient", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/rest/client.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "session", + "value": "Session", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/rest/client.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "apiVersion", + "value": "ApiVersion", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/rest/client.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "get", + "value": "(params: GetRequestParams) => Promise>", + "description": "Performs a GET request on the given path." + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/rest/client.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "post", + "value": "(params: PostRequestParams) => Promise>", + "description": "Performs a POST request on the given path." + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/rest/client.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "put", + "value": "(params: PostRequestParams) => Promise>", + "description": "Performs a PUT request on the given path." + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/rest/client.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "delete", + "value": "(params: GetRequestParams) => Promise>", + "description": "Performs a DELETE request on the given path." + } + ], + "value": "export declare class RestClient {\n static config: ConfigInterface;\n static formatPaths: boolean;\n static LINK_HEADER_REGEXP: RegExp;\n static DEFAULT_LIMIT: string;\n static RETRY_WAIT_TIME: number;\n static readonly DEPRECATION_ALERT_DELAY = 300000;\n loggedDeprecations: Record;\n readonly client: AdminRestApiClient;\n readonly session: Session;\n readonly apiVersion: ApiVersion;\n constructor({ session, apiVersion }: RestClientParams);\n /**\n * Performs a GET request on the given path.\n */\n get(params: GetRequestParams): Promise>;\n /**\n * Performs a POST request on the given path.\n */\n post(params: PostRequestParams): Promise>;\n /**\n * Performs a PUT request on the given path.\n */\n put(params: PutRequestParams): Promise>;\n /**\n * Performs a DELETE request on the given path.\n */\n delete(params: DeleteRequestParams): Promise>;\n protected request(params: RequestParams): Promise>;\n private restClass;\n private buildRequestParams;\n private logDeprecations;\n}" + }, + "RestRequestReturn": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "name": "RestRequestReturn", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "body", + "value": "T", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "headers", + "value": "Headers", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "pageInfo", + "value": "PageInfo", + "description": "", + "isOptional": true + } + ], + "value": "export interface RestRequestReturn {\n body: T;\n headers: Headers;\n pageInfo?: PageInfo;\n}" + }, + "Headers": { + "filePath": "../../node_modules/@shopify/shopify-api/runtime/http/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "Headers", + "value": "Record", + "description": "", + "members": [] + }, + "PageInfo": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "name": "PageInfo", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "limit", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "fields", + "value": "string[]", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "previousPageUrl", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "nextPageUrl", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "prevPage", + "value": "PageInfoParams", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "nextPage", + "value": "PageInfoParams", + "description": "", + "isOptional": true + } + ], + "value": "export interface PageInfo {\n limit: string;\n fields?: string[];\n previousPageUrl?: string;\n nextPageUrl?: string;\n prevPage?: PageInfoParams;\n nextPage?: PageInfoParams;\n}" + }, + "PageInfoParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "name": "PageInfoParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "path", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "query", + "value": "SearchParams", + "description": "" + } + ], + "value": "export interface PageInfoParams {\n path: string;\n query: SearchParams;\n}" + }, + "Shopify": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "name": "Shopify", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "config", + "value": "ConfigInterface", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "clients", + "value": "ShopifyClients", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "auth", + "value": "ShopifyAuth", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "ShopifySession", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "utils", + "value": "ShopifyUtils", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "webhooks", + "value": "ShopifyWebhooks", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "billing", + "value": "ShopifyBilling", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "logger", + "value": "ShopifyLogger", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "rest", + "value": "Resources", + "description": "" + } + ], + "value": "export interface Shopify {\n config: ConfigInterface;\n clients: ShopifyClients;\n auth: ShopifyAuth;\n session: ShopifySession;\n utils: ShopifyUtils;\n webhooks: ShopifyWebhooks;\n billing: ShopifyBilling;\n logger: ShopifyLogger;\n rest: Resources;\n}" + }, + "ConfigInterface": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/base-types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "ConfigInterface", + "value": "Omit & {\n apiKey: string;\n hostScheme: 'http' | 'https';\n scopes: AuthScopes;\n isCustomStoreApp: boolean;\n billing?: BillingConfig;\n logger: {\n log: LogFunction;\n level: LogSeverity;\n httpRequests: boolean;\n timestamps: boolean;\n };\n future: FutureFlagOptions;\n}", + "description": "" + }, + "Key": { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "Key", + "value": "export declare enum Key {\n Backspace = 8,\n Tab = 9,\n Enter = 13,\n Shift = 16,\n Ctrl = 17,\n Alt = 18,\n Pause = 19,\n CapsLock = 20,\n Escape = 27,\n Space = 32,\n PageUp = 33,\n PageDown = 34,\n End = 35,\n Home = 36,\n LeftArrow = 37,\n UpArrow = 38,\n RightArrow = 39,\n DownArrow = 40,\n Insert = 45,\n Delete = 46,\n Key0 = 48,\n Key1 = 49,\n Key2 = 50,\n Key3 = 51,\n Key4 = 52,\n Key5 = 53,\n Key6 = 54,\n Key7 = 55,\n Key8 = 56,\n Key9 = 57,\n KeyA = 65,\n KeyB = 66,\n KeyC = 67,\n KeyD = 68,\n KeyE = 69,\n KeyF = 70,\n KeyG = 71,\n KeyH = 72,\n KeyI = 73,\n KeyJ = 74,\n KeyK = 75,\n KeyL = 76,\n KeyM = 77,\n KeyN = 78,\n KeyO = 79,\n KeyP = 80,\n KeyQ = 81,\n KeyR = 82,\n KeyS = 83,\n KeyT = 84,\n KeyU = 85,\n KeyV = 86,\n KeyW = 87,\n KeyX = 88,\n KeyY = 89,\n KeyZ = 90,\n LeftMeta = 91,\n RightMeta = 92,\n Select = 93,\n Numpad0 = 96,\n Numpad1 = 97,\n Numpad2 = 98,\n Numpad3 = 99,\n Numpad4 = 100,\n Numpad5 = 101,\n Numpad6 = 102,\n Numpad7 = 103,\n Numpad8 = 104,\n Numpad9 = 105,\n Multiply = 106,\n Add = 107,\n Subtract = 109,\n Decimal = 110,\n Divide = 111,\n F1 = 112,\n F2 = 113,\n F3 = 114,\n F4 = 115,\n F5 = 116,\n F6 = 117,\n F7 = 118,\n F8 = 119,\n F9 = 120,\n F10 = 121,\n F11 = 122,\n F12 = 123,\n NumLock = 144,\n ScrollLock = 145,\n Semicolon = 186,\n Equals = 187,\n Comma = 188,\n Dash = 189,\n Period = 190,\n ForwardSlash = 191,\n GraveAccent = 192,\n OpenBracket = 219,\n BackSlash = 220,\n CloseBracket = 221,\n SingleQuote = 222\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Backspace", + "value": 8 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Tab", + "value": 9 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Enter", + "value": 13 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Shift", + "value": 16 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Ctrl", + "value": 17 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Alt", + "value": 18 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Pause", + "value": 19 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "CapsLock", + "value": 20 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Escape", + "value": 27 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Space", + "value": 32 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "PageUp", + "value": 33 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "PageDown", + "value": 34 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "End", + "value": 35 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Home", + "value": 36 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "LeftArrow", + "value": 37 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "UpArrow", + "value": 38 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "RightArrow", + "value": 39 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "DownArrow", + "value": 40 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Insert", + "value": 45 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Delete", + "value": 46 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key0", + "value": 48 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key1", + "value": 49 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key2", + "value": 50 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key3", + "value": 51 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key4", + "value": 52 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key5", + "value": 53 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key6", + "value": 54 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key7", + "value": 55 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key8", + "value": 56 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key9", + "value": 57 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyA", + "value": 65 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyB", + "value": 66 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyC", + "value": 67 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyD", + "value": 68 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyE", + "value": 69 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyF", + "value": 70 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyG", + "value": 71 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyH", + "value": 72 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyI", + "value": 73 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyJ", + "value": 74 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyK", + "value": 75 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyL", + "value": 76 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyM", + "value": 77 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyN", + "value": 78 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyO", + "value": 79 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyP", + "value": 80 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyQ", + "value": 81 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyR", + "value": 82 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyS", + "value": 83 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyT", + "value": 84 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyU", + "value": 85 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyV", + "value": 86 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyW", + "value": 87 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyX", + "value": 88 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyY", + "value": 89 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyZ", + "value": 90 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "LeftMeta", + "value": 91 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "RightMeta", + "value": 92 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Select", + "value": 93 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad0", + "value": 96 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad1", + "value": 97 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad2", + "value": 98 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad3", + "value": 99 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad4", + "value": 100 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad5", + "value": 101 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad6", + "value": 102 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad7", + "value": 103 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad8", + "value": 104 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad9", + "value": 105 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Multiply", + "value": 106 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Add", + "value": 107 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Subtract", + "value": 109 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Decimal", + "value": 110 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Divide", + "value": 111 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F1", + "value": 112 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F2", + "value": 113 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F3", + "value": 114 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F4", + "value": 115 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F5", + "value": 116 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F6", + "value": 117 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F7", + "value": 118 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F8", + "value": 119 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F9", + "value": 120 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F10", + "value": 121 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F11", + "value": 122 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F12", + "value": 123 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "NumLock", + "value": 144 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "ScrollLock", + "value": 145 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Semicolon", + "value": 186 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Equals", + "value": 187 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Comma", + "value": 188 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Dash", + "value": 189 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Period", + "value": 190 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "ForwardSlash", + "value": 191 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "GraveAccent", + "value": 192 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "OpenBracket", + "value": 219 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "BackSlash", + "value": 220 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "CloseBracket", + "value": 221 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "SingleQuote", + "value": 222 + } + ] + }, + "BillingConfig": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "BillingConfig", + "value": "Record>", + "description": "", + "members": [] + }, + "LogFunction": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/base-types.d.ts", + "name": "LogFunction", + "description": "", + "params": [ + { + "name": "severity", + "description": "", + "value": "LogSeverity", + "filePath": "../../node_modules/@shopify/shopify-api/lib/base-types.d.ts" + }, + { + "name": "msg", + "description": "", + "value": "string", + "filePath": "../../node_modules/@shopify/shopify-api/lib/base-types.d.ts" + } + ], + "returns": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/base-types.d.ts", + "description": "", + "name": "void", + "value": "void" + }, + "value": "export type LogFunction = (severity: LogSeverity, msg: string) => void;" + }, + "LogSeverity": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "LogSeverity", + "value": "export declare enum LogSeverity {\n Error = 0,\n Warning = 1,\n Info = 2,\n Debug = 3\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Error", + "value": 0 + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Warning", + "value": 1 + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Info", + "value": 2 + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Debug", + "value": 3 + } + ] + }, + "ShopifyClients": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "ShopifyClients", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "Rest", + "value": "typeof RestClient", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "Graphql", + "value": "typeof GraphqlClient", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "Storefront", + "value": "typeof StorefrontClient", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "graphqlProxy", + "value": "GraphqlProxy", + "description": "" + } + ], + "value": "export interface ShopifyClients {\n Rest: typeof RestClient;\n Graphql: typeof GraphqlClient;\n Storefront: typeof StorefrontClient;\n graphqlProxy: GraphqlProxy;\n}" + }, + "GraphqlClient": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/graphql/client.d.ts", + "name": "GraphqlClient", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/graphql/client.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "session", + "value": "Session", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/graphql/client.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "client", + "value": "AdminApiClient", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/graphql/client.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "apiVersion", + "value": "ApiVersion", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/graphql/client.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "query", + "value": "(params: GraphqlParams) => Promise>", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/graphql/client.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "request", + "value": "(operation: Operation, options?: GraphqlQueryOptions) => Promise : T>>", + "description": "" + } + ], + "value": "export declare class GraphqlClient {\n static config: ConfigInterface;\n readonly session: Session;\n readonly client: AdminApiClient;\n readonly apiVersion?: ApiVersion;\n constructor(params: GraphqlClientParams);\n query(params: GraphqlParams): Promise>;\n request(operation: Operation, options?: GraphqlQueryOptions): Promise : T>>;\n private graphqlClass;\n}" + }, + "GraphqlParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "GraphqlParams", + "value": "Omit", + "description": "", + "members": [] + }, + "RequestReturn": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "RequestReturn", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "body", + "value": "T", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "headers", + "value": "Headers", + "description": "" + } + ], + "value": "export interface RequestReturn {\n body: T;\n headers: Headers;\n}" + }, + "GraphqlQueryOptions": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "GraphqlQueryOptions", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "variables", + "value": "ApiClientRequestOptions[\"variables\"]", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "headers", + "value": "Record", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "retries", + "value": "number", + "description": "", + "isOptional": true + } + ], + "value": "export interface GraphqlQueryOptions {\n variables?: ApiClientRequestOptions['variables'];\n headers?: Record;\n retries?: number;\n}" + }, + "StorefrontClient": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/storefront/client.d.ts", + "name": "StorefrontClient", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/storefront/client.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "session", + "value": "Session", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/storefront/client.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "client", + "value": "StorefrontApiClient", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/storefront/client.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "apiVersion", + "value": "ApiVersion", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/storefront/client.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "query", + "value": "(params: GraphqlParams) => Promise>", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/storefront/client.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "request", + "value": "(operation: Operation, options?: GraphqlQueryOptions) => Promise : T>>", + "description": "" + } + ], + "value": "export declare class StorefrontClient {\n static config: ConfigInterface;\n readonly session: Session;\n readonly client: StorefrontApiClient;\n readonly apiVersion?: ApiVersion;\n constructor(params: GraphqlClientParams);\n query(params: GraphqlParams): Promise>;\n request(operation: Operation, options?: GraphqlQueryOptions): Promise : T>>;\n private storefrontClass;\n}" + }, + "GraphqlProxy": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/graphql_proxy/types.d.ts", + "name": "GraphqlProxy", + "description": "", + "params": [ + { + "name": "params", + "description": "", + "value": "GraphqlProxyParams", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/graphql_proxy/types.d.ts" + } + ], + "returns": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/graphql_proxy/types.d.ts", + "description": "", + "name": "Promise", + "value": "Promise" + }, + "value": "export type GraphqlProxy = (params: GraphqlProxyParams) => Promise;" + }, + "GraphqlProxyParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/graphql_proxy/types.d.ts", + "name": "GraphqlProxyParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/graphql_proxy/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/graphql_proxy/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawBody", + "value": "string | Record", + "description": "" + } + ], + "value": "export interface GraphqlProxyParams {\n session: Session;\n rawBody: string | Record;\n}" + }, + "ShopifyAuth": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/index.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "ShopifyAuth", + "value": "{\n begin: OAuthBegin;\n callback: OAuthCallback;\n nonce: Nonce;\n safeCompare: SafeCompare;\n getEmbeddedAppUrl: GetEmbeddedAppUrl;\n buildEmbeddedAppUrl: BuildEmbeddedAppUrl;\n} & (FeatureEnabled extends true ? {\n tokenExchange: TokenExchange;\n} : Record)", + "description": "" + }, + "OAuthBegin": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/oauth.d.ts", + "name": "OAuthBegin", + "description": "", + "params": [ + { + "name": "beginParams", + "description": "", + "value": "BeginParams", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/oauth.d.ts" + } + ], + "returns": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/oauth.d.ts", + "description": "", + "name": "Promise", + "value": "Promise" + }, + "value": "export type OAuthBegin = (beginParams: BeginParams) => Promise;" + }, + "BeginParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "name": "BeginParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "shop", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "callbackPath", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "isOnline", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawRequest", + "value": "AdapterRequest", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawResponse", + "value": "AdapterResponse", + "description": "", + "isOptional": true + } + ], + "value": "export interface BeginParams extends AdapterArgs {\n shop: string;\n callbackPath: string;\n isOnline: boolean;\n}" + }, + "AdapterRequest": { + "filePath": "../../node_modules/@shopify/shopify-api/runtime/http/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "AdapterRequest", + "value": "any", + "description": "" + }, + "AdapterResponse": { + "filePath": "../../node_modules/@shopify/shopify-api/runtime/http/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "AdapterResponse", + "value": "any", + "description": "" + }, + "OAuthCallback": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/oauth.d.ts", + "name": "OAuthCallback", + "description": "", + "params": [ + { + "name": "callbackParams", + "description": "", + "value": "CallbackParams", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/oauth.d.ts" + } + ], + "returns": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/oauth.d.ts", + "description": "", + "name": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}", + "value": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}" + }, + "value": "export type OAuthCallback = (callbackParams: CallbackParams) => Promise>;" + }, + "CallbackParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "name": "CallbackParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawRequest", + "value": "AdapterRequest", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawResponse", + "value": "AdapterResponse", + "description": "", + "isOptional": true + } + ], + "value": "export interface CallbackParams extends AdapterArgs {\n}" + }, + "Nonce": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/nonce.d.ts", + "name": "Nonce", + "description": "", + "params": [], + "returns": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/nonce.d.ts", + "description": "", + "name": "string", + "value": "string" + }, + "value": "export type Nonce = () => string;" + }, + "SafeCompare": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/safe-compare.d.ts", + "name": "SafeCompare", + "description": "", + "params": [ + { + "name": "strA", + "description": "", + "value": "string | string[] | Record | number[]", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/safe-compare.d.ts" + }, + { + "name": "strB", + "description": "", + "value": "string | string[] | Record | number[]", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/safe-compare.d.ts" + } + ], + "returns": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/safe-compare.d.ts", + "description": "", + "name": "boolean", + "value": "boolean" + }, + "value": "export type SafeCompare = (strA: string | Record | string[] | number[], strB: string | Record | string[] | number[]) => boolean;" + }, + "GetEmbeddedAppUrl": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/get-embedded-app-url.d.ts", + "name": "GetEmbeddedAppUrl", + "description": "", + "params": [ + { + "name": "params", + "description": "", + "value": "GetEmbeddedAppUrlParams", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/get-embedded-app-url.d.ts" + } + ], + "returns": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/get-embedded-app-url.d.ts", + "description": "", + "name": "Promise", + "value": "Promise" + }, + "value": "export type GetEmbeddedAppUrl = (params: GetEmbeddedAppUrlParams) => Promise;" + }, + "GetEmbeddedAppUrlParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/types.d.ts", + "name": "GetEmbeddedAppUrlParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawRequest", + "value": "AdapterRequest", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawResponse", + "value": "AdapterResponse", + "description": "", + "isOptional": true + } + ], + "value": "export interface GetEmbeddedAppUrlParams extends AdapterArgs {\n}" + }, + "BuildEmbeddedAppUrl": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/get-embedded-app-url.d.ts", + "name": "BuildEmbeddedAppUrl", + "description": "", + "params": [ + { + "name": "host", + "description": "", + "value": "string", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/get-embedded-app-url.d.ts" + } + ], + "returns": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/get-embedded-app-url.d.ts", + "description": "", + "name": "string", + "value": "string" + }, + "value": "export type BuildEmbeddedAppUrl = (host: string) => string;" + }, + "TokenExchange": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts", + "name": "TokenExchange", + "description": "", + "params": [ + { + "name": "params", + "description": "", + "value": "TokenExchangeParams", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts" + } + ], + "returns": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts", + "description": "", + "name": "Promise<{\n session: Session;\n}>", + "value": "Promise<{\n session: Session;\n}>" + }, + "value": "export type TokenExchange = (params: TokenExchangeParams) => Promise<{\n session: Session;\n}>;" + }, + "TokenExchangeParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts", + "name": "TokenExchangeParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts", + "syntaxKind": "PropertySignature", + "name": "shop", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts", + "syntaxKind": "PropertySignature", + "name": "sessionToken", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts", + "syntaxKind": "PropertySignature", + "name": "requestedTokenType", + "value": "RequestedTokenType", + "description": "" + } + ], + "value": "export interface TokenExchangeParams {\n shop: string;\n sessionToken: string;\n requestedTokenType: RequestedTokenType;\n}" + }, + "RequestedTokenType": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "RequestedTokenType", + "value": "export declare enum RequestedTokenType {\n OnlineAccessToken = \"urn:shopify:params:oauth:token-type:online-access-token\",\n OfflineAccessToken = \"urn:shopify:params:oauth:token-type:offline-access-token\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts", + "name": "OnlineAccessToken", + "value": "urn:shopify:params:oauth:token-type:online-access-token" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts", + "name": "OfflineAccessToken", + "value": "urn:shopify:params:oauth:token-type:offline-access-token" + } + ] + }, + "ShopifySession": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/index.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "ShopifySession", + "value": "ReturnType", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "customAppSession", + "value": "(shop: string) => Session", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "getCurrentId", + "value": "({ isOnline, ...adapterArgs }: GetCurrentSessionIdParams) => Promise", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "getOfflineId", + "value": "(shop: string) => string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "getJwtSessionId", + "value": "(shop: string, userId: string) => string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "decodeSessionToken", + "value": "(token: string, { checkAudience }?: DecodeSessionTokenOptions) => Promise", + "description": "" + } + ] + }, + "GetCurrentSessionIdParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "name": "GetCurrentSessionIdParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "isOnline", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawRequest", + "value": "AdapterRequest", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawResponse", + "value": "AdapterResponse", + "description": "", + "isOptional": true + } + ], + "value": "export interface GetCurrentSessionIdParams extends AdapterArgs {\n isOnline: boolean;\n}" + }, + "DecodeSessionTokenOptions": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/decode-session-token.d.ts", + "name": "DecodeSessionTokenOptions", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/decode-session-token.d.ts", + "syntaxKind": "PropertySignature", + "name": "checkAudience", + "value": "boolean", + "description": "", + "isOptional": true + } + ], + "value": "export interface DecodeSessionTokenOptions {\n checkAudience?: boolean;\n}" + }, + "ShopifyUtils": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/utils/index.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "ShopifyUtils", + "value": "ReturnType", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/utils/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "sanitizeShop", + "value": "(shop: string, throwOnInvalid?: boolean) => string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/utils/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "sanitizeHost", + "value": "(host: string, throwOnInvalid?: boolean) => string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/utils/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "validateHmac", + "value": "(query: AuthQuery, { signator }?: { signator: HMACSignator; }) => Promise", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/utils/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "versionCompatible", + "value": "(referenceVersion: ApiVersion, currentVersion?: ApiVersion) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/utils/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "versionPriorTo", + "value": "(referenceVersion: ApiVersion, currentVersion?: ApiVersion) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/utils/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "shopAdminUrlToLegacyUrl", + "value": "(shopAdminUrl: string) => string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/utils/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "legacyUrlToShopAdminUrl", + "value": "(legacyAdminUrl: string) => string", + "description": "" + } + ] + }, + "AuthQuery": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "name": "AuthQuery", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "name": "[key: string]", + "value": "string | undefined" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "hmac", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "signature", + "value": "string", + "description": "", + "isOptional": true + } + ], + "value": "export interface AuthQuery {\n [key: string]: string | undefined;\n hmac?: string;\n signature?: string;\n}" + }, + "HMACSignator": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/utils/hmac-validator.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "HMACSignator", + "value": "'admin' | 'appProxy'", + "description": "" + }, + "ShopifyWebhooks": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/index.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "ShopifyWebhooks", + "value": "ReturnType", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "addHandlers", + "value": "(handlersToAdd: AddHandlersParams) => void", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "getTopicsAdded", + "value": "() => string[]", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "getHandlers", + "value": "(topic: string) => WebhookHandler[]", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "register", + "value": "({ session, }: RegisterParams) => Promise", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "process", + "value": "({ rawBody, ...adapterArgs }: WebhookProcessParams) => Promise", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "validate", + "value": "({ rawBody, ...adapterArgs }: WebhookValidateParams) => Promise", + "description": "" + } + ] + }, + "AddHandlersParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "AddHandlersParams", + "value": "Record", + "description": "", + "members": [] + }, + "WebhookHandler": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "WebhookHandler", + "value": "HttpWebhookHandler | HttpWebhookHandlerWithCallback | EventBridgeWebhookHandler | PubSubWebhookHandler", + "description": "" + }, + "HttpWebhookHandler": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "HttpWebhookHandler", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "deliveryMethod", + "value": "DeliveryMethod.Http", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "callbackUrl", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "includeFields", + "value": "string[]", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "metafieldNamespaces", + "value": "string[]", + "description": "", + "isOptional": true + } + ], + "value": "export interface HttpWebhookHandler extends BaseWebhookHandler {\n deliveryMethod: DeliveryMethod.Http;\n callbackUrl: string;\n}" + }, + "DeliveryMethod": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "DeliveryMethod", + "value": "export declare enum DeliveryMethod {\n Http = \"http\",\n EventBridge = \"eventbridge\",\n PubSub = \"pubsub\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "Http", + "value": "http" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "EventBridge", + "value": "eventbridge" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "PubSub", + "value": "pubsub" + } + ] + }, + "HttpWebhookHandlerWithCallback": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "HttpWebhookHandlerWithCallback", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "callback", + "value": "WebhookHandlerFunction", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "deliveryMethod", + "value": "DeliveryMethod.Http", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "callbackUrl", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "includeFields", + "value": "string[]", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "metafieldNamespaces", + "value": "string[]", + "description": "", + "isOptional": true + } + ], + "value": "export interface HttpWebhookHandlerWithCallback extends HttpWebhookHandler {\n callback: WebhookHandlerFunction;\n}" + }, + "WebhookHandlerFunction": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "WebhookHandlerFunction", + "description": "", + "params": [ + { + "name": "topic", + "description": "", + "value": "string", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts" + }, + { + "name": "shop_domain", + "description": "", + "value": "string", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts" + }, + { + "name": "body", + "description": "", + "value": "string", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts" + }, + { + "name": "webhookId", + "description": "", + "value": "string", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts" + }, + { + "name": "apiVersion", + "description": "", + "value": "string", + "isOptional": true, + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts" + } + ], + "returns": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "description": "", + "name": "Promise", + "value": "Promise" + }, + "value": "export type WebhookHandlerFunction = (topic: string, shop_domain: string, body: string, webhookId: string, apiVersion?: string) => Promise;" + }, + "EventBridgeWebhookHandler": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "EventBridgeWebhookHandler", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "deliveryMethod", + "value": "DeliveryMethod.EventBridge", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "arn", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "includeFields", + "value": "string[]", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "metafieldNamespaces", + "value": "string[]", + "description": "", + "isOptional": true + } + ], + "value": "export interface EventBridgeWebhookHandler extends BaseWebhookHandler {\n deliveryMethod: DeliveryMethod.EventBridge;\n arn: string;\n}" + }, + "PubSubWebhookHandler": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "PubSubWebhookHandler", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "deliveryMethod", + "value": "DeliveryMethod.PubSub", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "pubSubProject", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "pubSubTopic", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "includeFields", + "value": "string[]", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "metafieldNamespaces", + "value": "string[]", + "description": "", + "isOptional": true + } + ], + "value": "export interface PubSubWebhookHandler extends BaseWebhookHandler {\n deliveryMethod: DeliveryMethod.PubSub;\n pubSubProject: string;\n pubSubTopic: string;\n}" + }, + "RegisterParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "RegisterParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "" + } + ], + "value": "export interface RegisterParams {\n session: Session;\n}" + }, + "RegisterReturn": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RegisterReturn", + "value": "Record", + "description": "", + "members": [] + }, + "Body": { + "filePath": "../../node_modules/@shopify/shopify-api/rest/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "Body", + "value": "Record", + "description": "", + "members": [] + }, + "WebhookProcessParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "WebhookProcessParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawBody", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawRequest", + "value": "AdapterRequest", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawResponse", + "value": "AdapterResponse", + "description": "", + "isOptional": true + } + ], + "value": "export interface WebhookProcessParams extends AdapterArgs {\n rawBody: string;\n}" + }, + "WebhookValidateParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "WebhookValidateParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawBody", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawRequest", + "value": "AdapterRequest", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawResponse", + "value": "AdapterResponse", + "description": "", + "isOptional": true + } + ], + "value": "export interface WebhookValidateParams extends WebhookProcessParams {\n}" + }, + "WebhookValidation": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "WebhookValidation", + "value": "WebhookValidationValid | WebhookValidationInvalid | WebhookValidationMissingHeaders", + "description": "" + }, + "WebhookValidationValid": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "WebhookValidationValid", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "valid", + "value": "true", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "webhookId", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "apiVersion", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "domain", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "hmac", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "topic", + "value": "string", + "description": "" + } + ], + "value": "export interface WebhookValidationValid extends WebhookFields {\n valid: true;\n}" + }, + "WebhookValidationInvalid": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "WebhookValidationInvalid", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "valid", + "value": "false", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "reason", + "value": "WebhookValidationErrorReason", + "description": "" + } + ], + "value": "export interface WebhookValidationInvalid {\n valid: false;\n reason: WebhookValidationErrorReason;\n}" + }, + "WebhookValidationErrorReason": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "WebhookValidationErrorReason", + "value": "export declare enum WebhookValidationErrorReason {\n MissingHeaders = \"missing_headers\",\n MissingBody = \"missing_body\",\n InvalidHmac = \"invalid_hmac\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "MissingHeaders", + "value": "missing_headers" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "MissingBody", + "value": "missing_body" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "InvalidHmac", + "value": "invalid_hmac" + } + ] + }, + "WebhookValidationMissingHeaders": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "WebhookValidationMissingHeaders", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "reason", + "value": "WebhookValidationErrorReason.MissingHeaders", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "missingHeaders", + "value": "string[]", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "valid", + "value": "false", + "description": "" + } + ], + "value": "export interface WebhookValidationMissingHeaders extends WebhookValidationInvalid {\n reason: WebhookValidationErrorReason.MissingHeaders;\n missingHeaders: string[];\n}" + }, + "ShopifyBilling": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/index.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "ShopifyBilling", + "value": "ReturnType", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "check", + "value": "({ session, plans, isTest, returnObject, }: Params_1) => Promise>", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "request", + "value": "({ session, plan, isTest, returnUrl: returnUrlParam, returnObject, ...overrides }: Params_2) => Promise>", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "cancel", + "value": "(subscriptionInfo: BillingCancelParams) => Promise", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "subscriptions", + "value": "({ session, }: BillingSubscriptionParams) => Promise", + "description": "" + } + ] + }, + "BillingCheckParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingCheckParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "plans", + "value": "string | string[]", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "isTest", + "value": "boolean", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "returnObject", + "value": "boolean", + "description": "", + "isOptional": true + } + ], + "value": "export interface BillingCheckParams {\n session: Session;\n plans: string[] | string;\n isTest?: boolean;\n returnObject?: boolean;\n}" + }, + "BillingCheckResponse": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "BillingCheckResponse", + "value": "Params['returnObject'] extends true ? BillingCheckResponseObject : boolean", + "description": "" + }, + "BillingRequestParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "BillingRequestParams", + "value": "{\n session: Session;\n plan: string;\n isTest?: boolean;\n returnUrl?: string;\n returnObject?: boolean;\n} & RequestConfigOverrides", + "description": "" + }, + "RequestConfigOverrides": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RequestConfigOverrides", + "value": "Partial | Partial | Partial", + "description": "" + }, + "BillingConfigOneTimePlan": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingConfigOneTimePlan", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "interval", + "value": "BillingInterval.OneTime", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "amount", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "currencyCode", + "value": "string", + "description": "" + } + ], + "value": "export interface BillingConfigOneTimePlan extends BillingConfigPlan {\n interval: BillingInterval.OneTime;\n}" + }, + "BillingInterval": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "BillingInterval", + "value": "export declare enum BillingInterval {\n OneTime = \"ONE_TIME\",\n Every30Days = \"EVERY_30_DAYS\",\n Annual = \"ANNUAL\",\n Usage = \"USAGE\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "OneTime", + "value": "ONE_TIME" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Every30Days", + "value": "EVERY_30_DAYS" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Annual", + "value": "ANNUAL" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Usage", + "value": "USAGE" + } + ] + }, + "BillingConfigSubscriptionPlan": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingConfigSubscriptionPlan", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "interval", + "value": "Exclude", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "trialDays", + "value": "number", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "replacementBehavior", + "value": "BillingReplacementBehavior", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "discount", + "value": "BillingConfigSubscriptionPlanDiscount", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "amount", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "currencyCode", + "value": "string", + "description": "" + } + ], + "value": "export interface BillingConfigSubscriptionPlan extends BillingConfigPlan {\n interval: Exclude;\n trialDays?: number;\n replacementBehavior?: BillingReplacementBehavior;\n discount?: BillingConfigSubscriptionPlanDiscount;\n}" + }, + "RecurringBillingIntervals": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RecurringBillingIntervals", + "value": "RecurringBillingIntervals", + "description": "" + }, + "BillingReplacementBehavior": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "BillingReplacementBehavior", + "value": "export declare enum BillingReplacementBehavior {\n ApplyImmediately = \"APPLY_IMMEDIATELY\",\n ApplyOnNextBillingCycle = \"APPLY_ON_NEXT_BILLING_CYCLE\",\n Standard = \"STANDARD\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "ApplyImmediately", + "value": "APPLY_IMMEDIATELY" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "ApplyOnNextBillingCycle", + "value": "APPLY_ON_NEXT_BILLING_CYCLE" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Standard", + "value": "STANDARD" + } + ] + }, + "BillingConfigSubscriptionPlanDiscount": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingConfigSubscriptionPlanDiscount", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "durationLimitInIntervals", + "value": "number", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "value", + "value": "BillingConfigSubscriptionPlanDiscountAmount | BillingConfigSubscriptionPlanDiscountPercentage", + "description": "" + } + ], + "value": "export interface BillingConfigSubscriptionPlanDiscount {\n durationLimitInIntervals?: number;\n value: BillingConfigSubscriptionPlanDiscountAmount | BillingConfigSubscriptionPlanDiscountPercentage;\n}" + }, + "BillingConfigSubscriptionPlanDiscountAmount": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingConfigSubscriptionPlanDiscountAmount", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "amount", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "percentage", + "value": "never", + "description": "", + "isOptional": true + } + ], + "value": "export interface BillingConfigSubscriptionPlanDiscountAmount {\n amount: number;\n percentage?: never;\n}" + }, + "BillingConfigSubscriptionPlanDiscountPercentage": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingConfigSubscriptionPlanDiscountPercentage", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "amount", + "value": "never", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "percentage", + "value": "number", + "description": "" + } + ], + "value": "export interface BillingConfigSubscriptionPlanDiscountPercentage {\n amount?: never;\n percentage: number;\n}" + }, + "BillingConfigUsagePlan": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingConfigUsagePlan", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "interval", + "value": "BillingInterval.Usage", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "usageTerms", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "trialDays", + "value": "number", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "replacementBehavior", + "value": "BillingReplacementBehavior", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "amount", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "currencyCode", + "value": "string", + "description": "" + } + ], + "value": "export interface BillingConfigUsagePlan extends BillingConfigPlan {\n interval: BillingInterval.Usage;\n usageTerms: string;\n trialDays?: number;\n replacementBehavior?: BillingReplacementBehavior;\n}" + }, + "BillingRequestResponse": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "BillingRequestResponse", + "value": "Params['returnObject'] extends true ? BillingRequestResponseObject : string", + "description": "" + }, + "BillingRequestResponseObject": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingRequestResponseObject", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "confirmationUrl", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "oneTimePurchase", + "value": "OneTimePurchase", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "appSubscription", + "value": "AppSubscription", + "description": "", + "isOptional": true + } + ], + "value": "export interface BillingRequestResponseObject {\n confirmationUrl: string;\n oneTimePurchase?: OneTimePurchase;\n appSubscription?: AppSubscription;\n}" + }, + "BillingCancelParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingCancelParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "subscriptionId", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "prorate", + "value": "boolean", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "isTest", + "value": "boolean", + "description": "", + "isOptional": true + } + ], + "value": "export interface BillingCancelParams {\n session: Session;\n subscriptionId: string;\n prorate?: boolean;\n isTest?: boolean;\n}" + }, + "BillingSubscriptionParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingSubscriptionParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "" + } + ], + "value": "export interface BillingSubscriptionParams {\n session: Session;\n}" + }, + "ActiveSubscriptions": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "ActiveSubscriptions", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "activeSubscriptions", + "value": "AppSubscription[]", + "description": "" + } + ], + "value": "export interface ActiveSubscriptions {\n activeSubscriptions: AppSubscription[];\n}" + }, + "ShopifyLogger": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/index.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "ShopifyLogger", + "value": "ReturnType", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "log", + "value": "LoggerFunction", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "debug", + "value": "(message: string, context?: LogContext) => Promise", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "info", + "value": "(message: string, context?: LogContext) => Promise", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "warning", + "value": "(message: string, context?: LogContext) => Promise", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "error", + "value": "(message: string, context?: LogContext) => Promise", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "deprecated", + "value": "(version: string, message: string) => void", + "description": "" + } + ] + }, + "LoggerFunction": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/log.d.ts", + "name": "LoggerFunction", + "description": "", + "params": [ + { + "name": "severity", + "description": "", + "value": "LogSeverity", + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/log.d.ts" + }, + { + "name": "message", + "description": "", + "value": "string", + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/log.d.ts" + }, + { + "name": "context", + "description": "", + "value": "Record", + "isOptional": true, + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/log.d.ts" + } + ], + "returns": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/log.d.ts", + "description": "", + "name": "void", + "value": "void" + }, + "value": "export type LoggerFunction = (severity: LogSeverity, message: string, context?: Record) => void;" + }, + "LogContext": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "LogContext", + "value": "Record", + "description": "", + "members": [] + } + } + } + ], + "jsDocTypeExamples": [ + "WebhookContextWithSession" + ], + "related": [ + { + "name": "Admin API context", + "subtitle": "Interact with the Admin API.", + "url": "/docs/api/shopify-app-remix/apis/admin-api" + } + ], + "examples": { + "description": "", + "exampleGroups": [ + { + "title": "admin", + "examples": [ + { + "description": "With the `v3_webhookAdminContext` future flag enabled, use the `admin` object in the context to interact with the Admin API.", + "codeblock": { + "title": "[V3] Webhook admin context", + "tabs": [ + { + "title": "/app/routes/webhooks.tsx", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.webhook(request);\n\n const response = await admin?.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", + "language": "typescript" + } + ] + } + }, + { + "description": "Use the `admin` object in the context to interact with the Admin API. This format will be removed in V3 of the package.", + "codeblock": { + "title": "Webhook admin context", + "tabs": [ + { + "title": "/app/routes/webhooks.tsx", + "code": "import { json, ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.webhook(request);\n\n const response = await admin?.graphql.query<any>({\n data: {\n query: `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n variables: { input: { title: \"Product Name\" } },\n },\n });\n\n const productData = response?.body.data;\n return json({ data: productData.data });\n}", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "apiVersion", + "examples": [ + { + "description": "Get the API version used for webhook request.", + "codeblock": { + "title": "Webhook API version", + "tabs": [ + { + "title": "/app/routes/webhooks.tsx", + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { apiVersion } = await authenticate.webhook(request);\n return new Response();\n};", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "shop", + "examples": [ + { + "description": "Get the shop that triggered a webhook.", + "codeblock": { + "title": "Webhook shop", + "tabs": [ + { + "title": "/app/routes/webhooks.tsx", + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { shop } = await authenticate.webhook(request);\n return new Response();\n};", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "topic", + "examples": [ + { + "description": "Get the event topic for the webhook.", + "codeblock": { + "title": "Webhook topic", + "tabs": [ + { + "title": "/app/routes/webhooks.tsx", + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { topic } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n // Do something when the app is uninstalled.\n break;\n }\n\n return new Response();\n};", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "webhookId", + "examples": [ + { + "description": "Get the webhook ID.", + "codeblock": { + "title": "Webhook ID", + "tabs": [ + { + "title": "/app/routes/webhooks.tsx", + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { webhookId } = await authenticate.webhook(request);\n return new Response();\n};", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "payload", + "examples": [ + { + "description": "Get the request's POST payload.", + "codeblock": { + "title": "Webhook payload", + "tabs": [ + { + "title": "/app/routes/webhooks.tsx", + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { payload } = await authenticate.webhook(request);\n return new Response();\n};", + "language": "typescript" + } + ] + } + } + ] + } + ] + } + }, + { + "name": "Admin API", + "description": "Contains objects used to interact with the Admin API.\n\nThis object is returned as part of different contexts, such as [`admin`](/docs/api/shopify-app-remix/authenticate/admin), [`unauthenticated.admin`](/docs/api/shopify-app-remix/unauthenticated/unauthenticated-admin), and [`webhook`](/docs/api/shopify-app-remix/authenticate/webhook).", + "category": "APIs", + "type": "object", + "isVisualComponent": false, + "definitions": [ + { + "title": "admin", + "description": "Provides utilities that apps can use to make requests to the Admin API.", + "type": "AdminApiContext", + "typeDefinitions": { + "AdminApiContext": { + "filePath": "src/server/clients/admin/types.ts", + "name": "AdminApiContext", + "description": "", + "members": [ + { + "filePath": "src/server/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "rest", + "value": "RestClientWithResources", + "description": "Methods for interacting with the Shopify Admin REST API\n\nThere are methods for interacting with individual REST resources. You can also make `GET`, `POST`, `PUT` and `DELETE` requests should the REST resources not meet your needs.\n\n\n\n\n", + "examples": [ + { + "title": "Using REST resources", + "description": "Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n return json(admin.rest.resources.Order.count({ session }));\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] + }, + { + "title": "Performing a GET request to the REST API", + "description": "Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = await admin.rest.get({ path: \"/customers/count.json\" });\n const customers = await response.json();\n return json({ customers });\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] + }, + { + "title": "Performing a POST request to the REST API", + "description": "Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = admin.rest.post({\n path: \"customers/7392136888625/send_invite.json\",\n body: {\n customer_invite: {\n to: \"new_test_email@shopify.com\",\n from: \"j.limited@example.com\",\n bcc: [\"j.limited@example.com\"],\n subject: \"Welcome to my new shop\",\n custom_message: \"My awesome new store\",\n },\n },\n});\n const customerInvite = await response.json();\n return json({ customerInvite });\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "graphql", + "value": "GraphQLClient", + "description": "Methods for interacting with the Shopify Admin GraphQL API\n\n\n\n\n\n\n\n\n\n", + "examples": [ + { + "title": "Querying the GraphQL API", + "description": "Use `admin.graphql` to make query / mutation requests.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.admin(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", + "title": "Example" + } + ] + } + ] + } + ], + "value": "export interface AdminApiContext<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * Methods for interacting with the Shopify Admin REST API\n *\n * There are methods for interacting with individual REST resources. You can also make `GET`, `POST`, `PUT` and `DELETE` requests should the REST resources not meet your needs.\n *\n * {@link https://shopify.dev/docs/api/admin-rest}\n *\n * @example\n * Using REST resources.\n * Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource. \n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * return json(admin.rest.resources.Order.count({ session }));\n * };\n * ```\n *\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n *\n * @example\n * Performing a GET request to the REST API.\n * Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint\n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = await admin.rest.get({ path: \"/customers/count.json\" });\n * const customers = await response.json();\n * return json({ customers });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * @example\n * Performing a POST request to the REST API.\n * Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = admin.rest.post({\n * path: \"customers/7392136888625/send_invite.json\",\n * body: {\n * customer_invite: {\n * to: \"new_test_email@shopify.com\",\n * from: \"j.limited@example.com\",\n * bcc: [\"j.limited@example.com\"],\n * subject: \"Welcome to my new shop\",\n * custom_message: \"My awesome new store\",\n * },\n * },\n * });\n * const customerInvite = await response.json();\n * return json({ customerInvite });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n rest: RestClientWithResources;\n\n /**\n * Methods for interacting with the Shopify Admin GraphQL API\n *\n * {@link https://shopify.dev/docs/api/admin-graphql}\n * {@link https://github.com/Shopify/shopify-api-js/blob/main/packages/shopify-api/docs/reference/clients/Graphql.md}\n *\n * @example\n * Querying the GraphQL API.\n * Use `admin.graphql` to make query / mutation requests.\n * ```ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.admin(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n graphql: GraphQLClient;\n}" + }, + "RestClientWithResources": { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RestClientWithResources", + "value": "RemixRestClient & {resources: Resources}", + "description": "" + }, + "RemixRestClient": { + "filePath": "src/server/clients/admin/rest.ts", + "name": "RemixRestClient", + "description": "", + "members": [ + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "PropertyDeclaration", + "name": "session", + "value": "Session", + "description": "" + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "get", + "value": "(params: GetRequestParams) => Promise", + "description": "Performs a GET request on the given path." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "post", + "value": "(params: PostRequestParams) => Promise", + "description": "Performs a POST request on the given path." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "put", + "value": "(params: PostRequestParams) => Promise", + "description": "Performs a PUT request on the given path." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "delete", + "value": "(params: GetRequestParams) => Promise", + "description": "Performs a DELETE request on the given path." + } + ], + "value": "class RemixRestClient {\n public session: Session;\n private params: AdminClientOptions['params'];\n private handleClientError: AdminClientOptions['handleClientError'];\n\n constructor({params, session, handleClientError}: AdminClientOptions) {\n this.params = params;\n this.handleClientError = handleClientError;\n this.session = session;\n }\n\n /**\n * Performs a GET request on the given path.\n */\n public async get(params: GetRequestParams) {\n return this.makeRequest({\n method: 'GET' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a POST request on the given path.\n */\n public async post(params: PostRequestParams) {\n return this.makeRequest({\n method: 'POST' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a PUT request on the given path.\n */\n public async put(params: PutRequestParams) {\n return this.makeRequest({\n method: 'PUT' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a DELETE request on the given path.\n */\n public async delete(params: DeleteRequestParams) {\n return this.makeRequest({\n method: 'DELETE' as RequestParams['method'],\n ...params,\n });\n }\n\n protected async makeRequest(params: RequestParams): Promise {\n const originalClient = new this.params.api.clients.Rest({\n session: this.session,\n });\n const originalRequest = Reflect.get(originalClient, 'request');\n\n try {\n const apiResponse = await originalRequest.call(originalClient, params);\n\n // We use a separate client for REST requests and REST resources because we want to override the API library\n // client class to return a Response object instead.\n return new Response(JSON.stringify(apiResponse.body), {\n headers: apiResponse.headers,\n });\n } catch (error) {\n if (this.handleClientError) {\n throw await this.handleClientError({\n error,\n session: this.session,\n params: this.params,\n });\n } else throw new Error(error);\n }\n }\n}" + }, + "Session": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "name": "Session", + "description": "Stores App information from logged in merchants so they can make authenticated requests to the Admin API.", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "shop", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "state", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "isOnline", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "scope", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "expires", + "value": "Date", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "accessToken", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "onlineAccessInfo", + "value": "OnlineAccessInfo", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isActive", + "value": "(scopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isScopeChanged", + "value": "(scopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isExpired", + "value": "(withinMillisecondsOfExpiry?: number) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toObject", + "value": "() => SessionParams", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "equals", + "value": "(other: Session) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toPropertyArray", + "value": "() => [string, string | number | boolean][]", + "description": "" + } + ], + "value": "export declare class Session {\n static fromPropertyArray(entries: [string, string | number | boolean][]): Session;\n readonly id: string;\n shop: string;\n state: string;\n isOnline: boolean;\n scope?: string;\n expires?: Date;\n accessToken?: string;\n onlineAccessInfo?: OnlineAccessInfo;\n constructor(params: SessionParams);\n isActive(scopes: AuthScopes | string | string[]): boolean;\n isScopeChanged(scopes: AuthScopes | string | string[]): boolean;\n isExpired(withinMillisecondsOfExpiry?: number): boolean;\n toObject(): SessionParams;\n equals(other: Session | undefined): boolean;\n toPropertyArray(): [string, string | number | boolean][];\n}" + }, + "OnlineAccessInfo": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "name": "OnlineAccessInfo", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "expires_in", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "associated_user_scope", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "associated_user", + "value": "{ id: number; first_name: string; last_name: string; email: string; email_verified: boolean; account_owner: boolean; locale: string; collaborator: boolean; }", + "description": "" + } + ], + "value": "export interface OnlineAccessInfo {\n expires_in: number;\n associated_user_scope: string;\n associated_user: {\n id: number;\n first_name: string;\n last_name: string;\n email: string;\n email_verified: boolean;\n account_owner: boolean;\n locale: string;\n collaborator: boolean;\n };\n}" + }, + "AuthScopes": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "name": "AuthScopes", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "has", + "value": "(scope: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "equals", + "value": "(otherScopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toString", + "value": "() => string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toArray", + "value": "() => string[]", + "description": "" + } + ], + "value": "declare class AuthScopes {\n static SCOPE_DELIMITER: string;\n private compressedScopes;\n private expandedScopes;\n constructor(scopes: string | string[] | AuthScopes | undefined);\n has(scope: string | string[] | AuthScopes | undefined): boolean;\n equals(otherScopes: string | string[] | AuthScopes | undefined): boolean;\n toString(): string;\n toArray(): string[];\n private getImpliedScopes;\n}" + }, + "SessionParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "name": "SessionParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "shop", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "state", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "isOnline", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "scope", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "expires", + "value": "Date", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "accessToken", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "onlineAccessInfo", + "value": "OnlineAccessInfo", + "description": "", + "isOptional": true + } + ], + "value": "export interface SessionParams {\n readonly id: string;\n shop: string;\n state: string;\n isOnline: boolean;\n scope?: string;\n expires?: Date;\n accessToken?: string;\n onlineAccessInfo?: OnlineAccessInfo;\n}" + }, + "GetRequestParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "GetRequestParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "path", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "type", + "value": "DataType", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "data", + "value": "string | Record", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "query", + "value": "SearchParams", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "extraHeaders", + "value": "HeaderParams", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "tries", + "value": "number", + "description": "", + "isOptional": true + } + ], + "value": "export interface GetRequestParams {\n path: string;\n type?: DataType;\n data?: Record | string;\n query?: SearchParams;\n extraHeaders?: HeaderParams;\n tries?: number;\n}" + }, + "DataType": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "DataType", + "value": "export declare enum DataType {\n JSON = \"application/json\",\n GraphQL = \"application/graphql\",\n URLEncoded = \"application/x-www-form-urlencoded\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "JSON", + "value": "application/json" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "GraphQL", + "value": "application/graphql" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "URLEncoded", + "value": "application/x-www-form-urlencoded" + } + ] + }, + "HeaderParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "HeaderParams", + "value": "Record", + "description": "", + "members": [] + }, + "PostRequestParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "PostRequestParams", + "value": "GetRequestParams & {\n data: Record | string;\n}", + "description": "" + }, + "GraphQLClient": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLClient", + "description": "", + "params": [ + { + "name": "query", + "description": "", + "value": "Operation extends keyof Operations", + "filePath": "src/server/clients/types.ts" + }, + { + "name": "options", + "description": "", + "value": "GraphQLQueryOptions", + "isOptional": true, + "filePath": "src/server/clients/types.ts" + } + ], + "returns": { + "filePath": "src/server/clients/types.ts", + "description": "", + "name": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}", + "value": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}" + }, + "value": "export type GraphQLClient = <\n Operation extends keyof Operations,\n>(\n query: Operation,\n options?: GraphQLQueryOptions,\n) => Promise<\n ResponseWithType>>\n>;" + }, + "GraphQLQueryOptions": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLQueryOptions", + "description": "", + "members": [ + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "variables", + "value": "ApiClientRequestOptions[\"variables\"]", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "apiVersion", + "value": "ApiVersion", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "headers", + "value": "{ [key: string]: any; }", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "tries", + "value": "number", + "description": "", + "isOptional": true + } + ], + "value": "export interface GraphQLQueryOptions<\n Operation extends keyof Operations,\n Operations extends AllOperations,\n> {\n variables?: ApiClientRequestOptions['variables'];\n apiVersion?: ApiVersion;\n headers?: {[key: string]: any};\n tries?: number;\n}" + }, + "ApiVersion": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "ApiVersion", + "value": "export declare enum ApiVersion {\n October22 = \"2022-10\",\n January23 = \"2023-01\",\n April23 = \"2023-04\",\n July23 = \"2023-07\",\n October23 = \"2023-10\",\n January24 = \"2024-01\",\n Unstable = \"unstable\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "October22", + "value": "2022-10" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "January23", + "value": "2023-01" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "April23", + "value": "2023-04" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "July23", + "value": "2023-07" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "October23", + "value": "2023-10" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "January24", + "value": "2024-01" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Unstable", + "value": "unstable" + } + ] + } + } + } + ], + "jsDocTypeExamples": [ + "AdminApiContext" + ], + "related": [ + { + "name": "Authenticated context", + "subtitle": "Authenticate requests from Shopify Admin.", + "url": "/docs/api/shopify-app-remix/authenticate/admin" + }, + { + "name": "Unauthenticated context", + "subtitle": "Interact with the Admin API on non-Shopify requests.", + "url": "/docs/api/shopify-app-remix/unauthenticated/unauthenticated-admin" + } + ], + "examples": { + "description": "", + "exampleGroups": [ + { + "title": "rest", + "examples": [ + { + "description": "Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource.", + "codeblock": { + "title": "Using REST resources", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n return json(admin.rest.resources.Order.count({ session }));\n};", + "language": "typescript" + }, + { + "title": "/app/shopify.server.ts", + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "language": "typescript" + } + ] + } + }, + { + "description": "Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint", + "codeblock": { + "title": "Performing a GET request to the REST API", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = await admin.rest.get({ path: \"/customers/count.json\" });\n const customers = await response.json();\n return json({ customers });\n};", + "language": "typescript" + }, + { + "title": "/app/shopify.server.ts", + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "language": "typescript" + } + ] + } + }, + { + "description": "Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email", + "codeblock": { + "title": "Performing a POST request to the REST API", + "tabs": [ + { + "title": "/app/routes/**\\/*.ts", + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = admin.rest.post({\n path: \"customers/7392136888625/send_invite.json\",\n body: {\n customer_invite: {\n to: \"new_test_email@shopify.com\",\n from: \"j.limited@example.com\",\n bcc: [\"j.limited@example.com\"],\n subject: \"Welcome to my new shop\",\n custom_message: \"My awesome new store\",\n },\n },\n});\n const customerInvite = await response.json();\n return json({ customerInvite });\n};", + "language": "typescript" + }, + { + "title": "/app/shopify.server.ts", + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "language": "typescript" + } + ] + } + } + ] + }, + { + "title": "graphql", + "examples": [ + { + "description": "Use `admin.graphql` to make query / mutation requests.", + "codeblock": { + "title": "Querying the GraphQL API", + "tabs": [ + { + "title": "Example", + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.admin(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", + "language": "typescript" + } + ] + } + } + ] + } + ] + } + }, + { + "name": "Storefront API", + "description": "Contains objects used to interact with the Storefront API.\n\nThis object is returned as part of different contexts, such as [`appProxy`](/docs/api/shopify-app-remix/authenticate/public/app-proxy), and [`unauthenticated.storefront`](/docs/api/shopify-app-remix/unauthenticated/unauthenticated-storefront).", + "category": "APIs", + "type": "object", + "isVisualComponent": false, + "definitions": [ + { + "title": "storefront", + "description": "Provides utilities that apps can use to make requests to the Storefront API.", + "type": "StorefrontContext", + "typeDefinitions": { + "StorefrontContext": { + "filePath": "src/server/clients/storefront/types.ts", + "name": "StorefrontContext", + "description": "", + "members": [ + { + "filePath": "src/server/clients/storefront/types.ts", + "syntaxKind": "PropertySignature", + "name": "graphql", + "value": "GraphQLClient", + "description": "Method for interacting with the Shopify Storefront GraphQL API\n\nIf you're getting incorrect type hints in the Shopify template, follow [these instructions](https://github.com/Shopify/shopify-app-template-remix/tree/main#incorrect-graphql-hints).\n\n\n\n\n", + "examples": [ + { + "title": "Querying the GraphQL API", + "description": "Use `storefront.graphql` to make query / mutation requests.", + "tabs": [ + { + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", + "title": "app/routes/**\\/.ts" + } + ] + } + ] + } + ], + "value": "export interface StorefrontContext {\n /**\n * Method for interacting with the Shopify Storefront GraphQL API\n *\n * If you're getting incorrect type hints in the Shopify template, follow [these instructions](https://github.com/Shopify/shopify-app-template-remix/tree/main#incorrect-graphql-hints).\n *\n * {@link https://shopify.dev/docs/api/storefront}\n *\n * @example\n * Querying the GraphQL API.\n * Use `storefront.graphql` to make query / mutation requests.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { storefront } = await authenticate.public.appProxy(request);\n *\n * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n *\n * return json(await response.json());\n * }\n * ```\n */\n graphql: GraphQLClient;\n}" + }, + "GraphQLClient": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLClient", + "description": "", + "params": [ + { + "name": "query", + "description": "", + "value": "Operation extends keyof Operations", + "filePath": "src/server/clients/types.ts" + }, + { + "name": "options", + "description": "", + "value": "GraphQLQueryOptions", + "isOptional": true, + "filePath": "src/server/clients/types.ts" + } + ], + "returns": { + "filePath": "src/server/clients/types.ts", + "description": "", + "name": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}", + "value": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}" + }, + "value": "export type GraphQLClient = <\n Operation extends keyof Operations,\n>(\n query: Operation,\n options?: GraphQLQueryOptions,\n) => Promise<\n ResponseWithType>>\n>;" + }, + "GraphQLQueryOptions": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLQueryOptions", + "description": "", + "members": [ + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "variables", + "value": "ApiClientRequestOptions[\"variables\"]", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "apiVersion", + "value": "ApiVersion", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "headers", + "value": "{ [key: string]: any; }", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "tries", + "value": "number", + "description": "", + "isOptional": true + } + ], + "value": "export interface GraphQLQueryOptions<\n Operation extends keyof Operations,\n Operations extends AllOperations,\n> {\n variables?: ApiClientRequestOptions['variables'];\n apiVersion?: ApiVersion;\n headers?: {[key: string]: any};\n tries?: number;\n}" + }, + "ApiVersion": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "ApiVersion", + "value": "export declare enum ApiVersion {\n October22 = \"2022-10\",\n January23 = \"2023-01\",\n April23 = \"2023-04\",\n July23 = \"2023-07\",\n October23 = \"2023-10\",\n January24 = \"2024-01\",\n Unstable = \"unstable\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "October22", + "value": "2022-10" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "January23", + "value": "2023-01" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "April23", + "value": "2023-04" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "July23", + "value": "2023-07" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "October23", + "value": "2023-10" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "January24", + "value": "2024-01" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Unstable", + "value": "unstable" + } + ] + } + } + } + ], + "jsDocTypeExamples": [ + "StorefrontContext" + ], + "related": [ + { + "name": "App proxy context", + "subtitle": "Authenticate requests from Shopify app proxies.", + "url": "/docs/api/shopify-app-remix/authenticate/public/app-proxy" + }, + { + "name": "Unauthenticated context", + "subtitle": "Interact with the Storefront API on non-Shopify requests.", + "url": "/docs/api/shopify-app-remix/unauthenticated/unauthenticated-storefront" + } + ], + "examples": { + "description": "", + "exampleGroups": [ + { + "title": "graphql", + "examples": [ + { + "description": "Use `storefront.graphql` to make query / mutation requests.", + "codeblock": { + "title": "Querying the GraphQL API", + "tabs": [ + { + "title": "app/routes/**\\/.ts", + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", + "language": "typescript" + } + ] + } + } + ] + } + ] + } + }, + { + "name": "shopifyApp", + "description": "Returns a set of functions that can be used by the app's backend to be able to respond to all Shopify requests.\n\nThe shape of the returned object changes depending on the value of `distribution`. If it is `AppDistribution.ShopifyAdmin`, then only `ShopifyAppBase` objects are returned, otherwise `ShopifyAppLogin` objects are included.", + "category": "Entrypoints", + "type": "function", + "isVisualComponent": false, + "definitions": [ + { + "title": "shopifyApp", + "description": "Function to create a new Shopify API object.", + "type": "ShopifyAppGeneratedType", + "typeDefinitions": { + "ShopifyAppGeneratedType": { + "filePath": "src/server/shopify-app.ts", + "name": "ShopifyAppGeneratedType", + "description": "Creates an object your app will use to interact with Shopify.", + "params": [ + { + "name": "appConfig", + "description": "Configuration options for your Shopify app, such as the scopes your app needs.", + "value": "Config extends AppConfigArg", + "filePath": "src/server/shopify-app.ts" + } + ], + "returns": { + "filePath": "src/server/shopify-app.ts", + "description": "`ShopifyApp` An object constructed using your appConfig. It has methods for interacting with Shopify.", + "name": "ShopifyApp>", + "value": "ShopifyApp>" + }, + "value": "export function shopifyApp<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources,\n Storage extends SessionStorage,\n>(appConfig: Config): ShopifyApp {\n const api = deriveApi(appConfig);\n const config = deriveConfig(appConfig, api.config);\n const logger = overrideLogger(api.logger);\n\n if (appConfig.webhooks) {\n api.webhooks.addHandlers(appConfig.webhooks);\n }\n\n const params: BasicParams = {api, config, logger};\n const oauth = new AuthCodeFlowStrategy(params);\n const authStrategy = authStrategyFactory({\n ...params,\n strategy: oauth,\n });\n\n const shopify:\n | AdminApp\n | AppStoreApp\n | SingleMerchantApp = {\n sessionStorage: config.sessionStorage,\n addDocumentResponseHeaders: addDocumentResponseHeadersFactory(params),\n registerWebhooks: registerWebhooksFactory(params),\n authenticate: {\n admin: authStrategy,\n public: authenticatePublicFactory(params),\n webhook: authenticateWebhookFactory<\n Config['future'],\n Resources,\n keyof Config['webhooks'] | MandatoryTopics\n >(params),\n },\n unauthenticated: {\n admin: unauthenticatedAdminContextFactory(params),\n storefront: unauthenticatedStorefrontContextFactory(params),\n },\n };\n\n if (\n isAppStoreApp(shopify, appConfig) ||\n isSingleMerchantApp(shopify, appConfig)\n ) {\n shopify.login = loginFactory(params);\n }\n\n return shopify as ShopifyApp;\n}", + "examples": [ + { + "title": "The minimum viable configuration", + "description": "", + "tabs": [ + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n apiKey: process.env.SHOPIFY_API_KEY!,\n apiSecretKey: process.env.SHOPIFY_API_SECRET!,\n scopes: process.env.SCOPES?.split(\",\")!,\n appUrl: process.env.SHOPIFY_APP_URL!,\n});\nexport default shopify;", + "title": "/shopify.server.ts" + } + ] + } + ] + }, + "AppConfigArg": { + "filePath": "src/server/config-types.ts", + "name": "AppConfigArg", + "description": "", + "members": [ + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "appUrl", + "value": "string", + "description": "The URL your app is running on.\n\nThe `@shopify/cli` provides this URL as `process.env.SHOPIFY_APP_URL`. For development this is probably a tunnel URL that points to your local machine. If this is a production app, this is your production URL." + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "sessionStorage", + "value": "Storage", + "description": "An adaptor for storing sessions in your database of choice.\n\nShopify provides multiple session storage adaptors and you can create your own.\n\n\n\n\n", + "examples": [ + { + "title": "Storing sessions with Prisma", + "description": "Add the `@shopify/shopify-app-session-storage-prisma` package to use the Prisma session storage.", + "tabs": [ + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\n\nimport prisma from \"~/db.server\";\n\nconst shopify = shopifyApp({\n // ... etc\n sessionStorage: new PrismaSessionStorage(prisma),\n});\nexport default shopify;", + "title": "Example" + } + ] + } + ] + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "useOnlineTokens", + "value": "boolean", + "description": "Whether your app use online or offline tokens.\n\nIf your app uses online tokens, then both online and offline tokens will be saved to your database. This ensures your app can perform background jobs.\n\n\n\n\n", + "isOptional": true, + "defaultValue": "`false`" + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "webhooks", + "value": "WebhookConfig", + "description": "The config for the webhook topics your app would like to subscribe to.\n\n\n\n\n\n\n\nThis can be in used in conjunction with the afterAuth hook to register webhook topics when a user installs your app. Or you can use this function in other processes such as background jobs.", + "isOptional": true, + "examples": [ + { + "title": "Registering for a webhook when a merchant uninstalls your app", + "description": "", + "tabs": [ + { + "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n webhooks: {\n APP_UNINSTALLED: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: \"/webhooks\",\n },\n },\n hooks: {\n afterAuth: async ({ session }) => {\n shopify.registerWebhooks({ session });\n }\n },\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;\n\n// /app/routes/webhooks.jsx\nimport { ActionFunctionArgs } from \"@remix-run/node\";\n\nimport { authenticate } from \"../shopify.server\";\nimport db from \"../db.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { topic, shop } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n await db.session.deleteMany({ where: { shop } });\n break;\n case \"CUSTOMERS_DATA_REQUEST\":\n case \"CUSTOMERS_REDACT\":\n case \"SHOP_REDACT\":\n default:\n throw new Response(\"Unhandled webhook topic\", { status: 404 });\n }\n throw new Response();\n};", + "title": "/app/shopify.server.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "hooks", + "value": "HooksConfig", + "description": "Functions to call at key places during your apps lifecycle.\n\nThese functions are called in the context of the request that triggered them. This means you can access the session.", + "isOptional": true, + "examples": [ + { + "title": "Seeding your database custom data when a merchant installs your app", + "description": "", + "tabs": [ + { + "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { seedStoreData } from \"~/db/seeds\"\n\nconst shopify = shopifyApp({\n hooks: {\n afterAuth: async ({ session }) => {\n seedStoreData({session})\n }\n },\n // ...etc\n});", + "title": "Example" + } + ] + } + ] + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "isEmbeddedApp", + "value": "boolean", + "description": "Does your app render embedded inside the Shopify Admin or on its own.\n\nUnless you have very specific needs, this should be true.", + "isOptional": true, + "defaultValue": "`true`" + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "distribution", + "value": "AppDistribution", + "description": "How your app is distributed. Default is `AppDistribution.AppStore`.\n\n\n\n\n", + "isOptional": true + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "apiVersion", + "value": "ApiVersion", + "description": "What version of Shopify's Admin API's would you like to use.\n\n\n\n\n", + "isOptional": true, + "defaultValue": "`LATEST_API_VERSION` from `@shopify/shopify-app-remix`", + "examples": [ + { + "title": "Using the latest API Version (Recommended)", + "description": "", + "tabs": [ + { + "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n apiVersion: LATEST_API_VERSION,\n});", + "title": "Example" + } + ] + } + ] + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "authPathPrefix", + "value": "string", + "description": "A path that Shopify can reserve for auth related endpoints.\n\nThis must match a $ route in your Remix app. That route must export a loader function that calls `shopify.authenticate.admin(request)`.", + "isOptional": true, + "defaultValue": "`\"/auth\"`", + "examples": [ + { + "title": "Using the latest API Version (Recommended)", + "description": "", + "tabs": [ + { + "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n apiVersion: LATEST_API_VERSION,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;\n\n// /app/routes/auth/$.jsx\nimport { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n await authenticate.admin(request);\n\n return null\n}", + "title": "/app/shopify.server.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "future", + "value": "Future", + "description": "Features that will be introduced in future releases of this package.\n\nYou can opt in to these features by setting the corresponding flags. By doing so, you can prepare for future releases in advance and provide feedback on the new features.", + "isOptional": true + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "apiKey", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "apiSecretKey", + "value": "string", + "description": "" + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "scopes", + "value": "string[] | AuthScopes", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "adminApiAccessToken", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "userAgentPrefix", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "privateAppStorefrontAccessToken", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "customShopDomains", + "value": "(string | RegExp)[]", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "billing", + "value": "BillingConfig", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "restResources", + "value": "Resources", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "logger", + "value": "{ log?: LogFunction; level?: LogSeverity; httpRequests?: boolean; timestamps?: boolean; }", + "description": "", + "isOptional": true + } + ], + "value": "export interface AppConfigArg<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n Storage extends SessionStorage = SessionStorage,\n Future extends FutureFlagOptions = FutureFlagOptions,\n> extends Omit<\n ApiConfigArg,\n | 'hostName'\n | 'hostScheme'\n | 'isEmbeddedApp'\n | 'apiVersion'\n | 'isCustomStoreApp'\n | 'future'\n > {\n /**\n * The URL your app is running on.\n *\n * The `@shopify/cli` provides this URL as `process.env.SHOPIFY_APP_URL`. For development this is probably a tunnel URL that points to your local machine. If this is a production app, this is your production URL.\n */\n appUrl: string;\n\n /**\n * An adaptor for storing sessions in your database of choice.\n *\n * Shopify provides multiple session storage adaptors and you can create your own.\n *\n * {@link https://github.com/Shopify/shopify-app-js/blob/main/README.md#session-storage-options}\n *\n * @example\n * Storing sessions with Prisma.\n * Add the `@shopify/shopify-app-session-storage-prisma` package to use the Prisma session storage.\n * ```ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\n *\n * import prisma from \"~/db.server\";\n *\n * const shopify = shopifyApp({\n * // ... etc\n * sessionStorage: new PrismaSessionStorage(prisma),\n * });\n * export default shopify;\n * ```\n */\n sessionStorage: Storage;\n\n /**\n * Whether your app use online or offline tokens.\n *\n * If your app uses online tokens, then both online and offline tokens will be saved to your database. This ensures your app can perform background jobs.\n *\n * {@link https://shopify.dev/docs/apps/auth/oauth/access-modes}\n *\n * @defaultValue `false`\n */\n useOnlineTokens?: boolean;\n\n /**\n * The config for the webhook topics your app would like to subscribe to.\n *\n * {@link https://shopify.dev/docs/apps/webhooks}\n *\n * This can be in used in conjunction with the afterAuth hook to register webhook topics when a user installs your app. Or you can use this function in other processes such as background jobs.\n *\n * @example\n * Registering for a webhook when a merchant uninstalls your app.\n * ```ts\n * // /app/shopify.server.ts\n * import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * webhooks: {\n * APP_UNINSTALLED: {\n * deliveryMethod: DeliveryMethod.Http,\n * callbackUrl: \"/webhooks\",\n * },\n * },\n * hooks: {\n * afterAuth: async ({ session }) => {\n * shopify.registerWebhooks({ session });\n * }\n * },\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n *\n * // /app/routes/webhooks.jsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n *\n * import { authenticate } from \"../shopify.server\";\n * import db from \"../db.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { topic, shop } = await authenticate.webhook(request);\n *\n * switch (topic) {\n * case \"APP_UNINSTALLED\":\n * await db.session.deleteMany({ where: { shop } });\n * break;\n * case \"CUSTOMERS_DATA_REQUEST\":\n * case \"CUSTOMERS_REDACT\":\n * case \"SHOP_REDACT\":\n * default:\n * throw new Response(\"Unhandled webhook topic\", { status: 404 });\n * }\n * throw new Response();\n * };\n * ```\n */\n webhooks?: WebhookConfig;\n\n /**\n * Functions to call at key places during your apps lifecycle.\n *\n * These functions are called in the context of the request that triggered them. This means you can access the session.\n *\n * @example\n * Seeding your database custom data when a merchant installs your app.\n * ```ts\n * import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { seedStoreData } from \"~/db/seeds\"\n *\n * const shopify = shopifyApp({\n * hooks: {\n * afterAuth: async ({ session }) => {\n * seedStoreData({session})\n * }\n * },\n * // ...etc\n * });\n * ```\n */\n hooks?: HooksConfig;\n\n /**\n * Does your app render embedded inside the Shopify Admin or on its own.\n *\n * Unless you have very specific needs, this should be true.\n *\n * @defaultValue `true`\n */\n isEmbeddedApp?: boolean;\n\n /**\n * How your app is distributed. Default is `AppDistribution.AppStore`.\n *\n * {@link https://shopify.dev/docs/apps/distribution}\n */\n distribution?: AppDistribution;\n\n /**\n * What version of Shopify's Admin API's would you like to use.\n *\n * {@link https://shopify.dev/docs/api/}\n *\n * @defaultValue `LATEST_API_VERSION` from `@shopify/shopify-app-remix`\n *\n * @example\n * Using the latest API Version (Recommended)\n * ```ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * apiVersion: LATEST_API_VERSION,\n * });\n * ```\n */\n apiVersion?: ApiVersion;\n\n /**\n * A path that Shopify can reserve for auth related endpoints.\n *\n * This must match a $ route in your Remix app. That route must export a loader function that calls `shopify.authenticate.admin(request)`.\n *\n * @default `\"/auth\"`\n *\n * @example\n * Using the latest API Version (Recommended)\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * apiVersion: LATEST_API_VERSION,\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n *\n * // /app/routes/auth/$.jsx\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * await authenticate.admin(request);\n *\n * return null\n * }\n * ```\n */\n authPathPrefix?: string;\n\n /**\n * Features that will be introduced in future releases of this package.\n *\n * You can opt in to these features by setting the corresponding flags. By doing so, you can prepare for future\n * releases in advance and provide feedback on the new features.\n */\n future?: Future;\n}" + }, + "WebhookConfig": { + "filePath": "src/server/config-types.ts", + "name": "WebhookConfig", + "description": "", + "members": [ + { + "filePath": "src/server/config-types.ts", + "name": "[key: string]", + "value": "WebhookHandler | WebhookHandler[]" + } + ], + "value": "export interface WebhookConfig {\n [key: string]: WebhookHandler | WebhookHandler[];\n}" + }, + "WebhookHandler": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "WebhookHandler", + "value": "HttpWebhookHandler | HttpWebhookHandlerWithCallback | EventBridgeWebhookHandler | PubSubWebhookHandler", + "description": "" + }, + "HttpWebhookHandler": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "HttpWebhookHandler", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "deliveryMethod", + "value": "DeliveryMethod.Http", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "callbackUrl", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "includeFields", + "value": "string[]", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "metafieldNamespaces", + "value": "string[]", + "description": "", + "isOptional": true + } + ], + "value": "export interface HttpWebhookHandler extends BaseWebhookHandler {\n deliveryMethod: DeliveryMethod.Http;\n callbackUrl: string;\n}" + }, + "DeliveryMethod": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "DeliveryMethod", + "value": "export declare enum DeliveryMethod {\n Http = \"http\",\n EventBridge = \"eventbridge\",\n PubSub = \"pubsub\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "Http", + "value": "http" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "EventBridge", + "value": "eventbridge" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "PubSub", + "value": "pubsub" + } + ] + }, + "HttpWebhookHandlerWithCallback": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "HttpWebhookHandlerWithCallback", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "callback", + "value": "WebhookHandlerFunction", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "deliveryMethod", + "value": "DeliveryMethod.Http", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "callbackUrl", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "includeFields", + "value": "string[]", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "metafieldNamespaces", + "value": "string[]", + "description": "", + "isOptional": true + } + ], + "value": "export interface HttpWebhookHandlerWithCallback extends HttpWebhookHandler {\n callback: WebhookHandlerFunction;\n}" + }, + "WebhookHandlerFunction": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "WebhookHandlerFunction", + "description": "", + "params": [ + { + "name": "topic", + "description": "", + "value": "string", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts" + }, + { + "name": "shop_domain", + "description": "", + "value": "string", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts" + }, + { + "name": "body", + "description": "", + "value": "string", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts" + }, + { + "name": "webhookId", + "description": "", + "value": "string", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts" + }, + { + "name": "apiVersion", + "description": "", + "value": "string", + "isOptional": true, + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts" + } + ], + "returns": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "description": "", + "name": "Promise", + "value": "Promise" + }, + "value": "export type WebhookHandlerFunction = (topic: string, shop_domain: string, body: string, webhookId: string, apiVersion?: string) => Promise;" + }, + "EventBridgeWebhookHandler": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "EventBridgeWebhookHandler", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "deliveryMethod", + "value": "DeliveryMethod.EventBridge", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "arn", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "includeFields", + "value": "string[]", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "metafieldNamespaces", + "value": "string[]", + "description": "", + "isOptional": true + } + ], + "value": "export interface EventBridgeWebhookHandler extends BaseWebhookHandler {\n deliveryMethod: DeliveryMethod.EventBridge;\n arn: string;\n}" + }, + "PubSubWebhookHandler": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "PubSubWebhookHandler", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "deliveryMethod", + "value": "DeliveryMethod.PubSub", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "pubSubProject", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "pubSubTopic", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "includeFields", + "value": "string[]", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "metafieldNamespaces", + "value": "string[]", + "description": "", + "isOptional": true + } + ], + "value": "export interface PubSubWebhookHandler extends BaseWebhookHandler {\n deliveryMethod: DeliveryMethod.PubSub;\n pubSubProject: string;\n pubSubTopic: string;\n}" + }, + "HooksConfig": { + "filePath": "src/server/config-types.ts", + "name": "HooksConfig", + "description": "", + "members": [ + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "afterAuth", + "value": "(options: AfterAuthOptions) => void | Promise", + "description": "A function to call after a merchant installs your app", + "isOptional": true, + "examples": [ + { + "title": "Registering webhooks and seeding data when a merchant installs your app", + "description": "", + "tabs": [ + { + "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { seedStoreData } from \"~/db/seeds\"\n\nconst shopify = shopifyApp({\n hooks: {\n afterAuth: async ({ session }) => {\n shopify.registerWebhooks({ session });\n seedStoreData({session})\n }\n },\n webhooks: {\n APP_UNINSTALLED: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: \"/webhooks\",\n },\n },\n // ...etc\n});", + "title": "Example" + } + ] + } + ] + } + ], + "value": "interface HooksConfig {\n /**\n * A function to call after a merchant installs your app\n *\n * @param context - An object with context about the request that triggered the hook.\n * @param context.session - The session of the merchant that installed your app. This is the output of sessionStorage.loadSession in case people want to load their own.\n * @param context.admin - An object with access to the Shopify Admin API's.\n *\n * @example\n * Registering webhooks and seeding data when a merchant installs your app.\n * ```ts\n * import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { seedStoreData } from \"~/db/seeds\"\n *\n * const shopify = shopifyApp({\n * hooks: {\n * afterAuth: async ({ session }) => {\n * shopify.registerWebhooks({ session });\n * seedStoreData({session})\n * }\n * },\n * webhooks: {\n * APP_UNINSTALLED: {\n * deliveryMethod: DeliveryMethod.Http,\n * callbackUrl: \"/webhooks\",\n * },\n * },\n * // ...etc\n * });\n * ```\n */\n afterAuth?: (options: AfterAuthOptions) => void | Promise;\n}" + }, + "AfterAuthOptions": { + "filePath": "src/server/config-types.ts", + "name": "AfterAuthOptions", + "description": "", + "members": [ + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "" + }, + { + "filePath": "src/server/config-types.ts", + "syntaxKind": "PropertySignature", + "name": "admin", + "value": "AdminApiContext", + "description": "" + } + ], + "value": "export interface AfterAuthOptions<\n R extends ShopifyRestResources = ShopifyRestResources,\n> {\n session: Session;\n admin: AdminApiContext;\n}" + }, + "Session": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "name": "Session", + "description": "Stores App information from logged in merchants so they can make authenticated requests to the Admin API.", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "shop", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "state", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "isOnline", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "scope", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "expires", + "value": "Date", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "accessToken", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "onlineAccessInfo", + "value": "OnlineAccessInfo", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isActive", + "value": "(scopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isScopeChanged", + "value": "(scopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isExpired", + "value": "(withinMillisecondsOfExpiry?: number) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toObject", + "value": "() => SessionParams", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "equals", + "value": "(other: Session) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toPropertyArray", + "value": "() => [string, string | number | boolean][]", + "description": "" + } + ], + "value": "export declare class Session {\n static fromPropertyArray(entries: [string, string | number | boolean][]): Session;\n readonly id: string;\n shop: string;\n state: string;\n isOnline: boolean;\n scope?: string;\n expires?: Date;\n accessToken?: string;\n onlineAccessInfo?: OnlineAccessInfo;\n constructor(params: SessionParams);\n isActive(scopes: AuthScopes | string | string[]): boolean;\n isScopeChanged(scopes: AuthScopes | string | string[]): boolean;\n isExpired(withinMillisecondsOfExpiry?: number): boolean;\n toObject(): SessionParams;\n equals(other: Session | undefined): boolean;\n toPropertyArray(): [string, string | number | boolean][];\n}" + }, + "OnlineAccessInfo": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "name": "OnlineAccessInfo", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "expires_in", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "associated_user_scope", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "associated_user", + "value": "{ id: number; first_name: string; last_name: string; email: string; email_verified: boolean; account_owner: boolean; locale: string; collaborator: boolean; }", + "description": "" + } + ], + "value": "export interface OnlineAccessInfo {\n expires_in: number;\n associated_user_scope: string;\n associated_user: {\n id: number;\n first_name: string;\n last_name: string;\n email: string;\n email_verified: boolean;\n account_owner: boolean;\n locale: string;\n collaborator: boolean;\n };\n}" + }, + "AuthScopes": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "name": "AuthScopes", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "has", + "value": "(scope: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "equals", + "value": "(otherScopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toString", + "value": "() => string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toArray", + "value": "() => string[]", + "description": "" + } + ], + "value": "declare class AuthScopes {\n static SCOPE_DELIMITER: string;\n private compressedScopes;\n private expandedScopes;\n constructor(scopes: string | string[] | AuthScopes | undefined);\n has(scope: string | string[] | AuthScopes | undefined): boolean;\n equals(otherScopes: string | string[] | AuthScopes | undefined): boolean;\n toString(): string;\n toArray(): string[];\n private getImpliedScopes;\n}" + }, + "SessionParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "name": "SessionParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "shop", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "state", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "isOnline", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "scope", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "expires", + "value": "Date", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "accessToken", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "onlineAccessInfo", + "value": "OnlineAccessInfo", + "description": "", + "isOptional": true + } + ], + "value": "export interface SessionParams {\n readonly id: string;\n shop: string;\n state: string;\n isOnline: boolean;\n scope?: string;\n expires?: Date;\n accessToken?: string;\n onlineAccessInfo?: OnlineAccessInfo;\n}" + }, + "AdminApiContext": { + "filePath": "src/server/clients/admin/types.ts", + "name": "AdminApiContext", + "description": "", + "members": [ + { + "filePath": "src/server/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "rest", + "value": "RestClientWithResources", + "description": "Methods for interacting with the Shopify Admin REST API\n\nThere are methods for interacting with individual REST resources. You can also make `GET`, `POST`, `PUT` and `DELETE` requests should the REST resources not meet your needs.\n\n\n\n\n", + "examples": [ + { + "title": "Using REST resources", + "description": "Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n return json(admin.rest.resources.Order.count({ session }));\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] + }, + { + "title": "Performing a GET request to the REST API", + "description": "Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = await admin.rest.get({ path: \"/customers/count.json\" });\n const customers = await response.json();\n return json({ customers });\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] + }, + { + "title": "Performing a POST request to the REST API", + "description": "Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = admin.rest.post({\n path: \"customers/7392136888625/send_invite.json\",\n body: {\n customer_invite: {\n to: \"new_test_email@shopify.com\",\n from: \"j.limited@example.com\",\n bcc: [\"j.limited@example.com\"],\n subject: \"Welcome to my new shop\",\n custom_message: \"My awesome new store\",\n },\n },\n});\n const customerInvite = await response.json();\n return json({ customerInvite });\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "graphql", + "value": "GraphQLClient", + "description": "Methods for interacting with the Shopify Admin GraphQL API\n\n\n\n\n\n\n\n\n\n", + "examples": [ + { + "title": "Querying the GraphQL API", + "description": "Use `admin.graphql` to make query / mutation requests.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.admin(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", + "title": "Example" + } + ] + } + ] + } + ], + "value": "export interface AdminApiContext<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * Methods for interacting with the Shopify Admin REST API\n *\n * There are methods for interacting with individual REST resources. You can also make `GET`, `POST`, `PUT` and `DELETE` requests should the REST resources not meet your needs.\n *\n * {@link https://shopify.dev/docs/api/admin-rest}\n *\n * @example\n * Using REST resources.\n * Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource. \n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * return json(admin.rest.resources.Order.count({ session }));\n * };\n * ```\n *\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n *\n * @example\n * Performing a GET request to the REST API.\n * Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint\n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = await admin.rest.get({ path: \"/customers/count.json\" });\n * const customers = await response.json();\n * return json({ customers });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * @example\n * Performing a POST request to the REST API.\n * Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = admin.rest.post({\n * path: \"customers/7392136888625/send_invite.json\",\n * body: {\n * customer_invite: {\n * to: \"new_test_email@shopify.com\",\n * from: \"j.limited@example.com\",\n * bcc: [\"j.limited@example.com\"],\n * subject: \"Welcome to my new shop\",\n * custom_message: \"My awesome new store\",\n * },\n * },\n * });\n * const customerInvite = await response.json();\n * return json({ customerInvite });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n rest: RestClientWithResources;\n\n /**\n * Methods for interacting with the Shopify Admin GraphQL API\n *\n * {@link https://shopify.dev/docs/api/admin-graphql}\n * {@link https://github.com/Shopify/shopify-api-js/blob/main/packages/shopify-api/docs/reference/clients/Graphql.md}\n *\n * @example\n * Querying the GraphQL API.\n * Use `admin.graphql` to make query / mutation requests.\n * ```ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.admin(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n graphql: GraphQLClient;\n}" + }, + "RestClientWithResources": { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RestClientWithResources", + "value": "RemixRestClient & {resources: Resources}", + "description": "" + }, + "RemixRestClient": { + "filePath": "src/server/clients/admin/rest.ts", + "name": "RemixRestClient", + "description": "", + "members": [ + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "PropertyDeclaration", + "name": "session", + "value": "Session", + "description": "" + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "get", + "value": "(params: GetRequestParams) => Promise", + "description": "Performs a GET request on the given path." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "post", + "value": "(params: PostRequestParams) => Promise", + "description": "Performs a POST request on the given path." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "put", + "value": "(params: PostRequestParams) => Promise", + "description": "Performs a PUT request on the given path." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "delete", + "value": "(params: GetRequestParams) => Promise", + "description": "Performs a DELETE request on the given path." + } + ], + "value": "class RemixRestClient {\n public session: Session;\n private params: AdminClientOptions['params'];\n private handleClientError: AdminClientOptions['handleClientError'];\n\n constructor({params, session, handleClientError}: AdminClientOptions) {\n this.params = params;\n this.handleClientError = handleClientError;\n this.session = session;\n }\n\n /**\n * Performs a GET request on the given path.\n */\n public async get(params: GetRequestParams) {\n return this.makeRequest({\n method: 'GET' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a POST request on the given path.\n */\n public async post(params: PostRequestParams) {\n return this.makeRequest({\n method: 'POST' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a PUT request on the given path.\n */\n public async put(params: PutRequestParams) {\n return this.makeRequest({\n method: 'PUT' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a DELETE request on the given path.\n */\n public async delete(params: DeleteRequestParams) {\n return this.makeRequest({\n method: 'DELETE' as RequestParams['method'],\n ...params,\n });\n }\n\n protected async makeRequest(params: RequestParams): Promise {\n const originalClient = new this.params.api.clients.Rest({\n session: this.session,\n });\n const originalRequest = Reflect.get(originalClient, 'request');\n\n try {\n const apiResponse = await originalRequest.call(originalClient, params);\n\n // We use a separate client for REST requests and REST resources because we want to override the API library\n // client class to return a Response object instead.\n return new Response(JSON.stringify(apiResponse.body), {\n headers: apiResponse.headers,\n });\n } catch (error) {\n if (this.handleClientError) {\n throw await this.handleClientError({\n error,\n session: this.session,\n params: this.params,\n });\n } else throw new Error(error);\n }\n }\n}" + }, + "GetRequestParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "GetRequestParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "path", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "type", + "value": "DataType", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "data", + "value": "string | Record", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "query", + "value": "SearchParams", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "extraHeaders", + "value": "HeaderParams", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "tries", + "value": "number", + "description": "", + "isOptional": true + } + ], + "value": "export interface GetRequestParams {\n path: string;\n type?: DataType;\n data?: Record | string;\n query?: SearchParams;\n extraHeaders?: HeaderParams;\n tries?: number;\n}" + }, + "DataType": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "DataType", + "value": "export declare enum DataType {\n JSON = \"application/json\",\n GraphQL = \"application/graphql\",\n URLEncoded = \"application/x-www-form-urlencoded\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "JSON", + "value": "application/json" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "GraphQL", + "value": "application/graphql" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "URLEncoded", + "value": "application/x-www-form-urlencoded" + } + ] + }, + "HeaderParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "HeaderParams", + "value": "Record", + "description": "", + "members": [] + }, + "PostRequestParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "PostRequestParams", + "value": "GetRequestParams & {\n data: Record | string;\n}", + "description": "" + }, + "GraphQLClient": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLClient", + "description": "", + "params": [ + { + "name": "query", + "description": "", + "value": "Operation extends keyof Operations", + "filePath": "src/server/clients/types.ts" + }, + { + "name": "options", + "description": "", + "value": "GraphQLQueryOptions", + "isOptional": true, + "filePath": "src/server/clients/types.ts" + } + ], + "returns": { + "filePath": "src/server/clients/types.ts", + "description": "", + "name": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}", + "value": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}" + }, + "value": "export type GraphQLClient = <\n Operation extends keyof Operations,\n>(\n query: Operation,\n options?: GraphQLQueryOptions,\n) => Promise<\n ResponseWithType>>\n>;" + }, + "GraphQLQueryOptions": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLQueryOptions", + "description": "", + "members": [ + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "variables", + "value": "ApiClientRequestOptions[\"variables\"]", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "apiVersion", + "value": "ApiVersion", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "headers", + "value": "{ [key: string]: any; }", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "tries", + "value": "number", + "description": "", + "isOptional": true + } + ], + "value": "export interface GraphQLQueryOptions<\n Operation extends keyof Operations,\n Operations extends AllOperations,\n> {\n variables?: ApiClientRequestOptions['variables'];\n apiVersion?: ApiVersion;\n headers?: {[key: string]: any};\n tries?: number;\n}" + }, + "ApiVersion": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "ApiVersion", + "value": "export declare enum ApiVersion {\n October22 = \"2022-10\",\n January23 = \"2023-01\",\n April23 = \"2023-04\",\n July23 = \"2023-07\",\n October23 = \"2023-10\",\n January24 = \"2024-01\",\n Unstable = \"unstable\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "October22", + "value": "2022-10" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "January23", + "value": "2023-01" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "April23", + "value": "2023-04" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "July23", + "value": "2023-07" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "October23", + "value": "2023-10" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "January24", + "value": "2024-01" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Unstable", + "value": "unstable" + } + ] + }, + "ShopifyRestResources": { + "filePath": "../../node_modules/@shopify/shopify-api/rest/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "ShopifyRestResources", + "value": "Record", + "description": "", + "members": [] + }, + "AppDistribution": { + "filePath": "src/server/types.ts", + "syntaxKind": "EnumDeclaration", + "name": "AppDistribution", + "value": "export enum AppDistribution {\n AppStore = 'app_store',\n SingleMerchant = 'single_merchant',\n ShopifyAdmin = 'shopify_admin',\n}", + "members": [ + { + "filePath": "src/server/types.ts", + "name": "AppStore", + "value": "app_store" + }, + { + "filePath": "src/server/types.ts", + "name": "SingleMerchant", + "value": "single_merchant" + }, + { + "filePath": "src/server/types.ts", + "name": "ShopifyAdmin", + "value": "shopify_admin" + } + ] + }, + "BillingConfig": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "BillingConfig", + "value": "Record>", + "description": "", + "members": [] + }, + "LogFunction": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/base-types.d.ts", + "name": "LogFunction", + "description": "", + "params": [ + { + "name": "severity", + "description": "", + "value": "LogSeverity", + "filePath": "../../node_modules/@shopify/shopify-api/lib/base-types.d.ts" + }, + { + "name": "msg", + "description": "", + "value": "string", + "filePath": "../../node_modules/@shopify/shopify-api/lib/base-types.d.ts" + } + ], + "returns": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/base-types.d.ts", + "description": "", + "name": "void", + "value": "void" + }, + "value": "export type LogFunction = (severity: LogSeverity, msg: string) => void;" + }, + "LogSeverity": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "LogSeverity", + "value": "export declare enum LogSeverity {\n Error = 0,\n Warning = 1,\n Info = 2,\n Debug = 3\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Error", + "value": 0 + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Warning", + "value": 1 + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Info", + "value": 2 + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Debug", + "value": 3 + } + ] + }, + "ShopifyApp": { + "filePath": "src/server/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "ShopifyApp", + "value": "Config['distribution'] extends AppDistribution.ShopifyAdmin\n ? AdminApp\n : Config['distribution'] extends AppDistribution.SingleMerchant\n ? SingleMerchantApp\n : Config['distribution'] extends AppDistribution.AppStore\n ? AppStoreApp\n : AppStoreApp", + "description": "An object your app can use to interact with Shopify.\n\nBy default, the app's distribution is `AppStore`." + }, + "AdminApp": { + "filePath": "src/server/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "AdminApp", + "value": "ShopifyAppBase", + "description": "", + "members": [ + { + "filePath": "src/server/types.ts", + "syntaxKind": "PropertySignature", + "name": "sessionStorage", + "value": "SessionStorageType", + "description": "The `SessionStorage` instance you passed in as a config option.", + "examples": [ + { + "title": "Storing sessions with Prisma", + "description": "Import the `@shopify/shopify-app-session-storage-prisma` package to store sessions in your Prisma database.", + "tabs": [ + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\nimport prisma from \"~/db.server\";\n\nconst shopify = shopifyApp({\n sesssionStorage: new PrismaSessionStorage(prisma),\n // ...etc\n})\n\n// shopify.sessionStorage is an instance of PrismaSessionStorage", + "title": "/app/shopify.server.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "PropertySignature", + "name": "addDocumentResponseHeaders", + "value": "AddDocumentResponseHeaders", + "description": "Adds the required Content Security Policy headers for Shopify apps to the given Headers object.\n\n\n\n\n", + "examples": [ + { + "title": "Return headers on all requests", + "description": "Add headers to all HTML requests by calling `shopify.addDocumentResponseHeaders` in `entry.server.tsx`.", + "tabs": [ + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const addDocumentResponseheaders = shopify.addDocumentResponseheaders;", + "title": "~/shopify.server.ts" + }, + { + "code": "import { addDocumentResponseHeaders } from \"~/shopify.server\";\n\nexport default function handleRequest(\n request: Request,\n responseStatusCode: number,\n responseHeaders: Headers,\n remixContext: EntryContext\n) {\n const markup = renderToString(\n \n );\n\n responseHeaders.set(\"Content-Type\", \"text/html\");\n addDocumentResponseHeaders(request, responseHeaders);\n\n return new Response(\"\" + markup, {\n status: responseStatusCode,\n headers: responseHeaders,\n });\n}", + "title": "entry.server.tsx" + } + ] + } + ] + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "PropertySignature", + "name": "registerWebhooks", + "value": "RegisterWebhooks", + "description": "Register webhook topics for a store using the given session. Most likely you want to use this in combination with the afterAuth hook.", + "examples": [ + { + "title": "Registering webhooks after install", + "description": "Trigger the registration to create the webhook subscriptions after a merchant installs your app using the `afterAuth` hook. Learn more about [subscribing to webhooks.](/docs/api/shopify-app-remix/v1/guide-webhooks)", + "tabs": [ + { + "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n hooks: {\n afterAuth: async ({ session }) => {\n shopify.registerWebhooks({ session });\n }\n },\n webhooks: {\n APP_UNINSTALLED: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: \"/webhooks\",\n },\n },\n // ...etc\n});", + "title": "/app/shopify.server.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "PropertySignature", + "name": "authenticate", + "value": "Authenticate", + "description": "Ways to authenticate requests from different surfaces across Shopify.", + "examples": [ + { + "title": "Authenticate Shopify requests", + "description": "Use the functions in `authenticate` to validate requests coming from Shopify.", + "tabs": [ + { + "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;", + "title": "/app/shopify.server.ts" + }, + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const {admin, session, sessionToken, billing} = shopify.authenticate.admin(request);\n\n return json(await admin.rest.resources.Product.count({ session }));\n}", + "title": "/app/routes/**\\/*.jsx" + } + ] + } + ] + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "PropertySignature", + "name": "unauthenticated", + "value": "Unauthenticated>", + "description": "Ways to get Contexts from requests that do not originate from Shopify.", + "examples": [ + { + "title": "Using unauthenticated contexts", + "description": "Create contexts for requests that don't come from Shopify.", + "tabs": [ + { + "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;", + "title": "/app/shopify.server.ts" + }, + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticateExternal } from \"~/helpers/authenticate\"\nimport shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const shop = await authenticateExternal(request)\n const {admin} = await shopify.unauthenticated.admin(shop);\n\n return json(await admin.rest.resources.Product.count({ session }));\n}", + "title": "/app/routes/**\\/*.jsx" + } + ] + } + ] + } + ] + }, + "SessionStorageType": { + "filePath": "src/server/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "SessionStorageType", + "value": "Config['sessionStorage'] extends SessionStorage\n ? Config['sessionStorage']\n : SessionStorage", + "description": "" + }, + "AddDocumentResponseHeaders": { + "filePath": "src/server/types.ts", + "name": "AddDocumentResponseHeaders", + "description": "", + "params": [ + { + "name": "request", + "description": "", + "value": "Request", + "filePath": "src/server/types.ts" + }, + { + "name": "headers", + "description": "", + "value": "Headers", + "filePath": "src/server/types.ts" + } + ], + "returns": { + "filePath": "src/server/types.ts", + "description": "", + "name": "void", + "value": "void" + }, + "value": "type AddDocumentResponseHeaders = (request: Request, headers: Headers) => void;" + }, + "Headers": { + "filePath": "../../node_modules/@shopify/shopify-api/runtime/http/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "Headers", + "value": "Record", + "description": "", + "members": [] + }, + "RegisterWebhooks": { + "filePath": "src/server/types.ts", + "name": "RegisterWebhooks", + "description": "", + "params": [ + { + "name": "options", + "description": "", + "value": "RegisterWebhooksOptions", + "filePath": "src/server/types.ts" + } + ], + "returns": { + "filePath": "src/server/types.ts", + "description": "", + "name": "Promise", + "value": "Promise" + }, + "value": "type RegisterWebhooks = (\n options: RegisterWebhooksOptions,\n) => Promise;" + }, + "RegisterWebhooksOptions": { + "filePath": "src/server/authenticate/webhooks/types.ts", + "name": "RegisterWebhooksOptions", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "The Shopify session used to register webhooks using the Admin API." + } + ], + "value": "export interface RegisterWebhooksOptions {\n /**\n * The Shopify session used to register webhooks using the Admin API.\n */\n session: Session;\n}" + }, + "RegisterReturn": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RegisterReturn", + "value": "Record", + "description": "", + "members": [] + }, + "Authenticate": { + "filePath": "src/server/types.ts", + "name": "Authenticate", + "description": "", + "members": [ + { + "filePath": "src/server/types.ts", + "syntaxKind": "PropertySignature", + "name": "admin", + "value": "AuthenticateAdmin>", + "description": "Authenticate an admin Request and get back an authenticated admin context. Use the authenticated admin context to interact with Shopify.\n\nExamples of when to use this are requests from your app's UI, or requests from admin extensions.\n\nIf there is no session for the Request, this will redirect the merchant to correct auth flows.", + "examples": [ + { + "title": "Authenticating a request for an embedded app", + "description": "", + "tabs": [ + { + "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + }, + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const {admin, session, sessionToken, billing} = authenticate.admin(request);\n\n return json(await admin.rest.resources.Product.count({ session }));\n}", + "title": "/app/routes/**\\/*.jsx" + } + ] + } + ] + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "PropertySignature", + "name": "public", + "value": "AuthenticatePublic", + "description": "Authenticate a public request and get back a session token.", + "examples": [ + { + "title": "Authenticating a request from a checkout extension", + "description": "", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../../shopify.server\";\nimport { getWidgets } from \"~/db/widgets\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const {sessionToken} = authenticate.public.checkout(request);\n\n return json(await getWidgets(sessionToken));\n}", + "title": "/app/routes/api/checkout.jsx" + } + ] + } + ] + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "PropertySignature", + "name": "webhook", + "value": "AuthenticateWebhook<\n Config['future'],\n RestResourcesType,\n keyof Config['webhooks'] | MandatoryTopics\n >", + "description": "Authenticate a Shopify webhook request, get back an authenticated admin context and details on the webhook request", + "examples": [ + { + "title": "Authenticating a webhook request", + "description": "", + "tabs": [ + { + "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n webhooks: {\n APP_UNINSTALLED: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: \"/webhooks\",\n },\n },\n hooks: {\n afterAuth: async ({ session }) => {\n shopify.registerWebhooks({ session });\n },\n },\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "/app/shopify.server.ts" + }, + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport db from \"../db.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { topic, shop, session } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n if (session) {\n await db.session.deleteMany({ where: { shop } });\n }\n break;\n case \"CUSTOMERS_DATA_REQUEST\":\n case \"CUSTOMERS_REDACT\":\n case \"SHOP_REDACT\":\n default:\n throw new Response(\"Unhandled webhook topic\", { status: 404 });\n }\n\n throw new Response();\n};", + "title": "/app/routes/webhooks.ts" + } + ] + } + ] + } + ], + "value": "interface Authenticate {\n /**\n * Authenticate an admin Request and get back an authenticated admin context. Use the authenticated admin context to interact with Shopify.\n *\n * Examples of when to use this are requests from your app's UI, or requests from admin extensions.\n *\n * If there is no session for the Request, this will redirect the merchant to correct auth flows.\n *\n * @example\n * Authenticating a request for an embedded app.\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * ```ts\n * // /app/routes/**\\/*.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const {admin, session, sessionToken, billing} = authenticate.admin(request);\n *\n * return json(await admin.rest.resources.Product.count({ session }));\n * }\n * ```\n */\n admin: AuthenticateAdmin>;\n\n /**\n * Authenticate a public request and get back a session token.\n *\n * @example\n * Authenticating a request from a checkout extension\n *\n * ```ts\n * // /app/routes/api/checkout.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../../shopify.server\";\n * import { getWidgets } from \"~/db/widgets\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const {sessionToken} = authenticate.public.checkout(request);\n *\n * return json(await getWidgets(sessionToken));\n * }\n * ```\n */\n public: AuthenticatePublic;\n\n /**\n * Authenticate a Shopify webhook request, get back an authenticated admin context and details on the webhook request\n *\n * @example\n * Authenticating a webhook request\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * webhooks: {\n * APP_UNINSTALLED: {\n * deliveryMethod: DeliveryMethod.Http,\n * callbackUrl: \"/webhooks\",\n * },\n * },\n * hooks: {\n * afterAuth: async ({ session }) => {\n * shopify.registerWebhooks({ session });\n * },\n * },\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * ```ts\n * // /app/routes/webhooks.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import db from \"../db.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { topic, shop, session } = await authenticate.webhook(request);\n *\n * switch (topic) {\n * case \"APP_UNINSTALLED\":\n * if (session) {\n * await db.session.deleteMany({ where: { shop } });\n * }\n * break;\n * case \"CUSTOMERS_DATA_REQUEST\":\n * case \"CUSTOMERS_REDACT\":\n * case \"SHOP_REDACT\":\n * default:\n * throw new Response(\"Unhandled webhook topic\", { status: 404 });\n * }\n *\n * throw new Response();\n * };\n * ```\n */\n webhook: AuthenticateWebhook<\n Config['future'],\n RestResourcesType,\n keyof Config['webhooks'] | MandatoryTopics\n >;\n}" + }, + "AuthenticateAdmin": { + "filePath": "src/server/types.ts", + "name": "AuthenticateAdmin", + "description": "", + "params": [ + { + "name": "request", + "description": "", + "value": "Request", + "filePath": "src/server/types.ts" + } + ], + "returns": { + "filePath": "src/server/types.ts", + "description": "", + "name": "Promise>", + "value": "Promise>" + }, + "value": "type AuthenticateAdmin<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> = (request: Request) => Promise>;" + }, + "AdminContext": { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "AdminContext", + "value": "Config['isEmbeddedApp'] extends false\n ? NonEmbeddedAdminContext\n : EmbeddedAdminContext", + "description": "" + }, + "NonEmbeddedAdminContext": { + "filePath": "src/server/authenticate/admin/types.ts", + "name": "NonEmbeddedAdminContext", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "The session for the user who made the request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nUse this to get shop or user-specific data.", + "examples": [ + { + "title": "Using offline sessions", + "description": "Get your app's shop-specific data using an offline session.", + "tabs": [ + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + }, + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({shop: session.shop));\n};", + "title": "/app/routes/**\\/*.ts" + } + ] + }, + { + "title": "Using online sessions", + "description": "Get your app's user-specific data using an online session.", + "tabs": [ + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + }, + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({user: session.onlineAccessInfo!.id}));\n};", + "title": "/app/routes/**\\/*.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "admin", + "value": "AdminApiContext", + "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request." + }, + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "billing", + "value": "BillingContext", + "description": "Billing methods for this store, based on the plans defined in the `billing` config option.\n\n\n\n\n" + }, + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "cors", + "value": "EnsureCORSFunction", + "description": "A function that ensures the CORS headers are set correctly for the response.", + "examples": [ + { + "title": "Setting CORS headers for a admin request", + "description": "Use the `cors` helper to ensure your app can respond to requests from admin extensions.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, cors } = await authenticate.admin(request);\n return cors(json(await getMyAppData({user: session.onlineAccessInfo!.id})));\n};", + "title": "/app/routes/admin/my-route.ts" + } + ] + } + ] + } + ], + "value": "export interface NonEmbeddedAdminContext<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends AdminContextInternal {}" + }, + "BillingContext": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "BillingContext", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "require", + "value": "(options: RequireBillingOptions) => Promise", + "description": "Checks if the shop has an active payment for any plan defined in the `billing` config option.", + "examples": [ + { + "title": "Requesting billing right away", + "description": "Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n isTest: true,\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n // App logic\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + }, + { + "title": "Using a plan selection page", + "description": "When the app has multiple plans, create a page in your App that allows the merchant to select a plan. If a merchant does not have the required plan you can redirect them to page in your app to select one.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n isTest: true,\n onFailure: () => redirect('/select-plan'),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n\n // App logic\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "request", + "value": "(options: RequestBillingOptions) => Promise", + "description": "Requests payment for the plan.", + "examples": [ + { + "title": "Using a custom return URL", + "description": "Change where the merchant is returned to after approving the purchase using the `returnUrl` option.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n }),\n });\n\n // App logic\n};", + "title": "/app/routes/**\\/*.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "cancel", + "value": "(options: CancelBillingOptions) => Promise", + "description": "Cancels an ongoing subscription, given its ID.", + "examples": [ + { + "title": "Cancelling a subscription", + "description": "Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n const cancelledSubscription = await billing.cancel({\n subscriptionId: subscription.id,\n isTest: true,\n prorate: true,\n });\n\n // App logic\n};", + "title": "/app/routes/cancel-subscription.ts" + }, + { + "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + } + ] + } + ] + } + ], + "value": "export interface BillingContext {\n /**\n * Checks if the shop has an active payment for any plan defined in the `billing` config option.\n *\n * @returns A promise that resolves to an object containing the active purchases for the shop.\n *\n * @example\n * Requesting billing right away.\n * Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * isTest: true,\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Using a plan selection page.\n * When the app has multiple plans, create a page in your App that allows the merchant to select a plan. If a merchant does not have the required plan you can redirect them to page in your app to select one.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n * isTest: true,\n * onFailure: () => redirect('/select-plan'),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n require: (\n options: RequireBillingOptions,\n ) => Promise;\n\n /**\n * Requests payment for the plan.\n *\n * @returns Redirects to the confirmation URL for the payment.\n *\n * @example\n * Using a custom return URL.\n * Change where the merchant is returned to after approving the purchase using the `returnUrl` option.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({\n * plan: MONTHLY_PLAN,\n * isTest: true,\n * returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n * }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n request: (options: RequestBillingOptions) => Promise;\n\n /**\n * Cancels an ongoing subscription, given its ID.\n *\n * @returns The cancelled subscription.\n *\n * @example\n * Cancelling a subscription.\n * Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.\n * ```ts\n * // /app/routes/cancel-subscription.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * const cancelledSubscription = await billing.cancel({\n * subscriptionId: subscription.id,\n * isTest: true,\n * prorate: true,\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n cancel: (options: CancelBillingOptions) => Promise;\n}" + }, + "RequireBillingOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "RequireBillingOptions", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "plans", + "value": "(keyof Config[\"billing\"])[]", + "description": "The plans to check for. Must be one of the values defined in the `billing` config option." + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "onFailure", + "value": "(error: any) => Promise", + "description": "How to handle the request if the shop doesn't have an active payment for any plan." + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "isTest", + "value": "boolean", + "description": "", + "isOptional": true + } + ], + "value": "export interface RequireBillingOptions\n extends Omit {\n /**\n * The plans to check for. Must be one of the values defined in the `billing` config option.\n */\n plans: (keyof Config['billing'])[];\n /**\n * How to handle the request if the shop doesn't have an active payment for any plan.\n */\n onFailure: (error: any) => Promise;\n}" + }, + "BillingCheckResponseObject": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingCheckResponseObject", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "hasActivePayment", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "oneTimePurchases", + "value": "OneTimePurchase[]", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "appSubscriptions", + "value": "AppSubscription[]", + "description": "" + } + ], + "value": "export interface BillingCheckResponseObject {\n hasActivePayment: boolean;\n oneTimePurchases: OneTimePurchase[];\n appSubscriptions: AppSubscription[];\n}" + }, + "OneTimePurchase": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "OneTimePurchase", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "name", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "test", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "status", + "value": "string", + "description": "" + } + ], + "value": "export interface OneTimePurchase {\n id: string;\n name: string;\n test: boolean;\n status: string;\n}" + }, + "AppSubscription": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "AppSubscription", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "name", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "test", + "value": "boolean", + "description": "" + } + ], + "value": "export interface AppSubscription {\n id: string;\n name: string;\n test: boolean;\n}" + }, + "RequestBillingOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "RequestBillingOptions", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "plan", + "value": "keyof Config[\"billing\"]", + "description": "The plan to request. Must be one of the values defined in the `billing` config option." + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "isTest", + "value": "boolean", + "description": "Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.", + "isOptional": true + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "returnUrl", + "value": "string", + "description": "The URL to return to after the merchant approves the payment.", + "isOptional": true + } + ], + "value": "export interface RequestBillingOptions\n extends Omit {\n /**\n * The plan to request. Must be one of the values defined in the `billing` config option.\n */\n plan: keyof Config['billing'];\n /**\n * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.\n */\n isTest?: boolean;\n /**\n * The URL to return to after the merchant approves the payment.\n */\n returnUrl?: string;\n}" + }, + "CancelBillingOptions": { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "name": "CancelBillingOptions", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "subscriptionId", + "value": "string", + "description": "The ID of the subscription to cancel." + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "prorate", + "value": "boolean", + "description": "Whether to prorate the cancellation.\n\n\n\n\n", + "isOptional": true + }, + { + "filePath": "src/server/authenticate/admin/billing/types.ts", + "syntaxKind": "PropertySignature", + "name": "isTest", + "value": "boolean", + "description": "", + "isOptional": true + } + ], + "value": "export interface CancelBillingOptions {\n /**\n * The ID of the subscription to cancel.\n */\n subscriptionId: string;\n /**\n * Whether to prorate the cancellation.\n *\n * {@link https://shopify.dev/docs/apps/billing/subscriptions/cancel-recurring-charges}\n */\n prorate?: boolean;\n /*\n * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.\n */\n isTest?: boolean;\n}" + }, + "EnsureCORSFunction": { + "filePath": "src/server/authenticate/helpers/ensure-cors-headers.ts", + "name": "EnsureCORSFunction", + "description": "", + "members": [], + "value": "export interface EnsureCORSFunction {\n (response: Response): Response;\n}" + }, + "EmbeddedAdminContext": { + "filePath": "src/server/authenticate/admin/types.ts", + "name": "EmbeddedAdminContext", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "sessionToken", + "value": "JwtPayload", + "description": "The decoded and validated session token for the request.\n\nReturned only if `isEmbeddedApp` is `true`.\n\n\n\n\n", + "examples": [ + { + "title": "Using the decoded session token", + "description": "Get user-specific data using the `sessionToken` object.", + "tabs": [ + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + }, + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.public.checkout(\n request\n );\n return json(await getMyAppData({user: sessionToken.sub}));\n};", + "title": "/app/routes/**\\/*.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "redirect", + "value": "RedirectFunction", + "description": "A function that redirects the user to a new page, ensuring that the appropriate parameters are set for embedded apps.\n\nReturned only if `isEmbeddedApp` is `true`.", + "examples": [ + { + "title": "Redirecting to an app route", + "description": "Use the `redirect` helper to safely redirect between pages.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, redirect } = await authenticate.admin(request);\n return redirect(\"/\");\n};", + "title": "/app/routes/admin/my-route.ts" + } + ] + }, + { + "title": "Redirecting outside of Shopify admin", + "description": "Pass in a `target` option of `_top` or `_parent` to go to an external URL.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, redirect } = await authenticate.admin(request);\n return redirect(\"/\", { target: '_parent' });\n};", + "title": "/app/routes/admin/my-route.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "The session for the user who made the request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nUse this to get shop or user-specific data.", + "examples": [ + { + "title": "Using offline sessions", + "description": "Get your app's shop-specific data using an offline session.", + "tabs": [ + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + }, + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({shop: session.shop));\n};", + "title": "/app/routes/**\\/*.ts" + } + ] + }, + { + "title": "Using online sessions", + "description": "Get your app's user-specific data using an online session.", + "tabs": [ + { + "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", + "title": "shopify.server.ts" + }, + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({user: session.onlineAccessInfo!.id}));\n};", + "title": "/app/routes/**\\/*.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "admin", + "value": "AdminApiContext", + "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request." + }, + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "billing", + "value": "BillingContext", + "description": "Billing methods for this store, based on the plans defined in the `billing` config option.\n\n\n\n\n" + }, + { + "filePath": "src/server/authenticate/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "cors", + "value": "EnsureCORSFunction", + "description": "A function that ensures the CORS headers are set correctly for the response.", + "examples": [ + { + "title": "Setting CORS headers for a admin request", + "description": "Use the `cors` helper to ensure your app can respond to requests from admin extensions.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, cors } = await authenticate.admin(request);\n return cors(json(await getMyAppData({user: session.onlineAccessInfo!.id})));\n};", + "title": "/app/routes/admin/my-route.ts" + } + ] + } + ] + } + ], + "value": "export interface EmbeddedAdminContext<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends AdminContextInternal {\n /**\n * The decoded and validated session token for the request.\n *\n * Returned only if `isEmbeddedApp` is `true`.\n *\n * {@link https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload}\n *\n * @example\n * Using the decoded session token.\n * Get user-specific data using the `sessionToken` object.\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * useOnlineTokens: true,\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken } = await authenticate.public.checkout(\n * request\n * );\n * return json(await getMyAppData({user: sessionToken.sub}));\n * };\n * ```\n */\n sessionToken: JwtPayload;\n\n /**\n * A function that redirects the user to a new page, ensuring that the appropriate parameters are set for embedded\n * apps.\n *\n * Returned only if `isEmbeddedApp` is `true`.\n *\n * @example\n * Redirecting to an app route.\n * Use the `redirect` helper to safely redirect between pages.\n * ```ts\n * // /app/routes/admin/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { session, redirect } = await authenticate.admin(request);\n * return redirect(\"/\");\n * };\n * ```\n *\n * @example\n * Redirecting outside of Shopify admin.\n * Pass in a `target` option of `_top` or `_parent` to go to an external URL.\n * ```ts\n * // /app/routes/admin/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { session, redirect } = await authenticate.admin(request);\n * return redirect(\"/\", { target: '_parent' });\n * };\n * ```\n */\n redirect: RedirectFunction;\n}" + }, + "JwtPayload": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "name": "JwtPayload", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "iss", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "dest", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "aud", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "sub", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "exp", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "nbf", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "iat", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "jti", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "sid", + "value": "string", + "description": "" + } + ], + "value": "export interface JwtPayload {\n iss: string;\n dest: string;\n aud: string;\n sub: string;\n exp: number;\n nbf: number;\n iat: number;\n jti: string;\n sid: string;\n}" + }, + "RedirectFunction": { + "filePath": "src/server/authenticate/admin/helpers/redirect.ts", + "name": "RedirectFunction", + "description": "", + "params": [ + { + "name": "url", + "description": "", + "value": "string", + "filePath": "src/server/authenticate/admin/helpers/redirect.ts" + }, + { + "name": "init", + "description": "", + "value": "RedirectInit", + "isOptional": true, + "filePath": "src/server/authenticate/admin/helpers/redirect.ts" + } + ], + "returns": { + "filePath": "src/server/authenticate/admin/helpers/redirect.ts", + "description": "", + "name": "TypedResponse", + "value": "TypedResponse" + }, + "value": "export type RedirectFunction = (\n url: string,\n init?: RedirectInit,\n) => TypedResponse;" + }, + "RedirectInit": { + "filePath": "src/server/authenticate/admin/helpers/redirect.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RedirectInit", + "value": "number | (ResponseInit & {target?: RedirectTarget})", + "description": "" + }, + "RedirectTarget": { + "filePath": "src/server/authenticate/admin/helpers/redirect.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RedirectTarget", + "value": "'_self' | '_parent' | '_top'", + "description": "" + }, + "RestResourcesType": { + "filePath": "src/server/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RestResourcesType", + "value": "Config['restResources'] extends ShopifyRestResources\n ? Config['restResources']\n : ShopifyRestResources", + "description": "" + }, + "AuthenticatePublic": { + "filePath": "src/server/authenticate/public/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "AuthenticatePublic", + "value": "FeatureEnabled extends true\n ? AuthenticatePublicObject\n : AuthenticatePublicLegacy", + "description": "" + }, + "AuthenticatePublicObject": { + "filePath": "src/server/authenticate/public/types.ts", + "name": "AuthenticatePublicObject", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/public/types.ts", + "syntaxKind": "PropertySignature", + "name": "checkout", + "value": "AuthenticateCheckout", + "description": "Authenticate a request from a checkout extension", + "examples": [ + { + "title": "Authenticating a checkout extension request", + "description": "", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken, cors } = await authenticate.public.checkout(\n request,\n );\n return cors(json({my: \"data\", shop: sessionToken.dest}));\n};", + "title": "/app/routes/public/widgets.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/public/types.ts", + "syntaxKind": "PropertySignature", + "name": "appProxy", + "value": "AuthenticateAppProxy", + "description": "Authenticate a request from an app proxy", + "examples": [ + { + "title": "Authenticating an app proxy request", + "description": "", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n await authenticate.public.appProxy(\n request,\n );\n\n const {searchParams} = new URL(request.url);\n const shop = searchParams.get(\"shop\");\n const customerId = searchParams.get(\"logged_in_customer_id\")\n\n return json({my: \"data\", shop, customerId});\n};", + "title": "/app/routes/public/widgets.ts" + } + ] + } + ] + } + ], + "value": "export interface AuthenticatePublicObject {\n /**\n * Authenticate a request from a checkout extension\n *\n * @example\n * Authenticating a checkout extension request\n * ```ts\n * // /app/routes/public/widgets.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken, cors } = await authenticate.public.checkout(\n * request,\n * );\n * return cors(json({my: \"data\", shop: sessionToken.dest}));\n * };\n * ```\n */\n checkout: AuthenticateCheckout;\n\n /**\n * Authenticate a request from an app proxy\n *\n * @example\n * Authenticating an app proxy request\n * ```ts\n * // /app/routes/public/widgets.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * await authenticate.public.appProxy(\n * request,\n * );\n *\n * const {searchParams} = new URL(request.url);\n * const shop = searchParams.get(\"shop\");\n * const customerId = searchParams.get(\"logged_in_customer_id\")\n *\n * return json({my: \"data\", shop, customerId});\n * };\n * ```\n */\n appProxy: AuthenticateAppProxy;\n}" + }, + "AuthenticateCheckout": { + "filePath": "src/server/authenticate/public/checkout/types.ts", + "name": "AuthenticateCheckout", + "description": "", + "params": [ + { + "name": "request", + "description": "", + "value": "Request", + "filePath": "src/server/authenticate/public/checkout/types.ts" + }, + { + "name": "options", + "description": "", + "value": "AuthenticateCheckoutOptions", + "isOptional": true, + "filePath": "src/server/authenticate/public/checkout/types.ts" + } + ], + "returns": { + "filePath": "src/server/authenticate/public/checkout/types.ts", + "description": "", + "name": "Promise", + "value": "Promise" + }, + "value": "export type AuthenticateCheckout = (\n request: Request,\n options?: AuthenticateCheckoutOptions,\n) => Promise;" + }, + "AuthenticateCheckoutOptions": { + "filePath": "src/server/authenticate/public/checkout/types.ts", + "name": "AuthenticateCheckoutOptions", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/public/checkout/types.ts", + "syntaxKind": "PropertySignature", + "name": "corsHeaders", + "value": "string[]", + "description": "", + "isOptional": true + } + ], + "value": "export interface AuthenticateCheckoutOptions {\n corsHeaders?: string[];\n}" + }, + "CheckoutContext": { + "filePath": "src/server/authenticate/public/checkout/types.ts", + "name": "CheckoutContext", + "description": "Authenticated Context for a checkout request", + "members": [ + { + "filePath": "src/server/authenticate/public/checkout/types.ts", + "syntaxKind": "PropertySignature", + "name": "sessionToken", + "value": "JwtPayload", + "description": "The decoded and validated session token for the request\n\nRefer to the OAuth docs for the [session token payload](https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload).", + "examples": [ + { + "title": "Using the decoded session token", + "description": "Get store-specific data using the `sessionToken` object.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.public.checkout(\n request\n );\n return json(await getMyAppData({shop: sessionToken.dest}));\n};", + "title": "app/routes/public/my-route.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/public/checkout/types.ts", + "syntaxKind": "PropertySignature", + "name": "cors", + "value": "EnsureCORSFunction", + "description": "A function that ensures the CORS headers are set correctly for the response.", + "examples": [ + { + "title": "Setting CORS headers for a public request", + "description": "Use the `cors` helper to ensure your app can respond to checkout extension requests.", + "tabs": [ + { + "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken, cors } = await authenticate.public.checkout(\n request,\n { corsHeaders: [\"X-My-Custom-Header\"] }\n );\n const data = await getMyAppData({shop: sessionToken.dest});\n return cors(json(data));\n};", + "title": "app/routes/public/my-route.ts" + } + ] + } + ] + } + ], + "value": "export interface CheckoutContext {\n /**\n * The decoded and validated session token for the request\n *\n * Refer to the OAuth docs for the [session token payload](https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload).\n *\n * @example\n * Using the decoded session token.\n * Get store-specific data using the `sessionToken` object.\n * ```ts\n * // app/routes/public/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken } = await authenticate.public.checkout(\n * request\n * );\n * return json(await getMyAppData({shop: sessionToken.dest}));\n * };\n * ```\n */\n sessionToken: JwtPayload;\n\n /**\n * A function that ensures the CORS headers are set correctly for the response.\n *\n * @example\n * Setting CORS headers for a public request.\n * Use the `cors` helper to ensure your app can respond to checkout extension requests.\n * ```ts\n * // app/routes/public/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken, cors } = await authenticate.public.checkout(\n * request,\n * { corsHeaders: [\"X-My-Custom-Header\"] }\n * );\n * const data = await getMyAppData({shop: sessionToken.dest});\n * return cors(json(data));\n * };\n * ```\n */\n cors: EnsureCORSFunction;\n}" + }, + "AuthenticateAppProxy": { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "name": "AuthenticateAppProxy", + "description": "", + "params": [ + { + "name": "request", + "description": "", + "value": "Request", + "filePath": "src/server/authenticate/public/appProxy/types.ts" + } + ], + "returns": { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "description": "", + "name": "Promise", + "value": "Promise" + }, + "value": "export type AuthenticateAppProxy = (\n request: Request,\n) => Promise;" + }, + "AppProxyContext": { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "name": "AppProxyContext", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "undefined", + "description": "No session is available for the shop that made this request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice." + }, + { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "syntaxKind": "PropertySignature", + "name": "admin", + "value": "undefined", + "description": "No session is available for the shop that made this request. Therefore no methods for interacting with the GraphQL / REST Admin APIs are available." + }, + { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "syntaxKind": "PropertySignature", + "name": "storefront", + "value": "undefined", + "description": "No session is available for the shop that made this request. Therefore no method for interacting with the Storefront API is available." + }, + { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "syntaxKind": "PropertySignature", + "name": "liquid", + "value": "LiquidResponseFunction", + "description": "A utility for creating a Liquid Response.", + "examples": [ + { + "title": "Rendering liquid content", + "description": "Use the `liquid` helper to render a `Response` with Liquid content.", + "tabs": [ + { + "code": "import {authenticate} from \"~/shopify.server\"\n\nexport async function loader({ request }) {\n const {liquid} = await authenticate.public.appProxy(request);\n\n return liquid(\"Hello {{shop.name}}\")\n}", + "title": "app/routes/**\\/.ts" + } + ] + } + ] + } + ], + "value": "export interface AppProxyContext extends Context {\n /**\n * No session is available for the shop that made this request.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n */\n session: undefined;\n\n /**\n * No session is available for the shop that made this request.\n * Therefore no methods for interacting with the GraphQL / REST Admin APIs are available.\n */\n admin: undefined;\n\n /**\n * No session is available for the shop that made this request.\n * Therefore no method for interacting with the Storefront API is available.\n */\n storefront: undefined;\n}" + }, + "LiquidResponseFunction": { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "name": "LiquidResponseFunction", + "description": "", + "params": [ + { + "name": "body", + "description": "", + "value": "string", + "filePath": "src/server/authenticate/public/appProxy/types.ts" + }, + { + "name": "initAndOptions", + "description": "", + "value": "number | (ResponseInit & Options)", + "isOptional": true, + "filePath": "src/server/authenticate/public/appProxy/types.ts" + } + ], + "returns": { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "description": "", + "name": "Response", + "value": "Response" + }, + "value": "export type LiquidResponseFunction = (\n body: string,\n initAndOptions?: number | (ResponseInit & Options),\n) => Response;" + }, + "Options": { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "name": "Options", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "syntaxKind": "PropertySignature", + "name": "layout", + "value": "boolean", + "description": "", + "isOptional": true + } + ], + "value": "interface Options {\n layout?: boolean;\n}" + }, + "AppProxyContextWithSession": { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "name": "AppProxyContextWithSession", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "The session for the shop that made the request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nUse this to get shop or user-specific data.", + "examples": [ + { + "title": "Using the session object", + "description": "Get the session for the shop that initiated the request to the app proxy.", + "tabs": [ + { + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppModelData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }) => {\n // Get the session for the shop that initiated the request to the app proxy.\n const { session } = await authenticate.public.appProxy(request);\n\n // Use the session data to make to queries to your database or additional requests.\n return json(await getMyAppModelData({shop: session.shop));\n};", + "title": "app/routes/**\\/.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "syntaxKind": "PropertySignature", + "name": "admin", + "value": "AdminApiContext", + "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request.", + "examples": [ + { + "title": "Interacting with the Admin API", + "description": "Use the `admin` object to interact with the REST or GraphQL APIs.", + "tabs": [ + { + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.public.appProxy(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", + "title": "app/routes/**\\/.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "syntaxKind": "PropertySignature", + "name": "storefront", + "value": "StorefrontContext", + "description": "Method for interacting with the Shopify Storefront Graphql API for the store that made the request.", + "examples": [ + { + "title": "Interacting with the Storefront API", + "description": "Use the `storefront` object to interact with the GraphQL API.", + "tabs": [ + { + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", + "title": "app/routes/**\\/.ts" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/public/appProxy/types.ts", + "syntaxKind": "PropertySignature", + "name": "liquid", + "value": "LiquidResponseFunction", + "description": "A utility for creating a Liquid Response.", + "examples": [ + { + "title": "Rendering liquid content", + "description": "Use the `liquid` helper to render a `Response` with Liquid content.", + "tabs": [ + { + "code": "import {authenticate} from \"~/shopify.server\"\n\nexport async function loader({ request }) {\n const {liquid} = await authenticate.public.appProxy(request);\n\n return liquid(\"Hello {{shop.name}}\")\n}", + "title": "app/routes/**\\/.ts" + } + ] + } + ] + } + ], + "value": "export interface AppProxyContextWithSession<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends Context {\n /**\n * The session for the shop that made the request.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n *\n * Use this to get shop or user-specific data.\n *\n * @example\n * Using the session object.\n * Get the session for the shop that initiated the request to the app proxy.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppModelData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }) => {\n * // Get the session for the shop that initiated the request to the app proxy.\n * const { session } = await authenticate.public.appProxy(request);\n *\n * // Use the session data to make to queries to your database or additional requests.\n * return json(await getMyAppModelData({shop: session.shop));\n * };\n * ```\n */\n session: Session;\n\n /**\n * Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request.\n *\n * @example\n * Interacting with the Admin API.\n * Use the `admin` object to interact with the REST or GraphQL APIs.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.public.appProxy(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n admin: AdminApiContext;\n\n /**\n * Method for interacting with the Shopify Storefront Graphql API for the store that made the request.\n *\n * @example\n * Interacting with the Storefront API.\n * Use the `storefront` object to interact with the GraphQL API.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { storefront } = await authenticate.public.appProxy(request);\n *\n * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n *\n * return json(await response.json());\n * }\n * ```\n */\n storefront: StorefrontContext;\n}" + }, + "StorefrontContext": { + "filePath": "src/server/clients/storefront/types.ts", + "name": "StorefrontContext", + "description": "", + "members": [ + { + "filePath": "src/server/clients/storefront/types.ts", + "syntaxKind": "PropertySignature", + "name": "graphql", + "value": "GraphQLClient", + "description": "Method for interacting with the Shopify Storefront GraphQL API\n\nIf you're getting incorrect type hints in the Shopify template, follow [these instructions](https://github.com/Shopify/shopify-app-template-remix/tree/main#incorrect-graphql-hints).\n\n\n\n\n", + "examples": [ + { + "title": "Querying the GraphQL API", + "description": "Use `storefront.graphql` to make query / mutation requests.", + "tabs": [ + { + "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", + "title": "app/routes/**\\/.ts" + } + ] + } + ] + } + ], + "value": "export interface StorefrontContext {\n /**\n * Method for interacting with the Shopify Storefront GraphQL API\n *\n * If you're getting incorrect type hints in the Shopify template, follow [these instructions](https://github.com/Shopify/shopify-app-template-remix/tree/main#incorrect-graphql-hints).\n *\n * {@link https://shopify.dev/docs/api/storefront}\n *\n * @example\n * Querying the GraphQL API.\n * Use `storefront.graphql` to make query / mutation requests.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { storefront } = await authenticate.public.appProxy(request);\n *\n * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n *\n * return json(await response.json());\n * }\n * ```\n */\n graphql: GraphQLClient;\n}" + }, + "AuthenticatePublicLegacy": { + "filePath": "src/server/authenticate/public/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "AuthenticatePublicLegacy", + "value": "AuthenticateCheckout & AuthenticatePublicObject", + "description": "Methods for authenticating Requests from Shopify's public surfaces\n\nTo maintain backwards compatability this is a function and an object.\n\nDo not use `authenticate.public()`. Use `authenticate.public.checkout()` instead. `authenticate.public()` will be removed in v2.\n\nMethods are:\n\n- `authenticate.public.checkout()` for authenticating requests from checkout extensions - `authenticate.public.appProxy()` for authenticating requests from app proxies" + }, + "AuthenticateWebhook": { + "filePath": "src/server/authenticate/webhooks/types.ts", + "name": "AuthenticateWebhook", + "description": "", + "params": [ + { + "name": "request", + "description": "", + "value": "Request", + "filePath": "src/server/authenticate/webhooks/types.ts" + } + ], + "returns": { + "filePath": "src/server/authenticate/webhooks/types.ts", + "description": "", + "name": "Promise>", + "value": "Promise>" + }, + "value": "export type AuthenticateWebhook<\n Future extends FutureFlagOptions,\n Resources extends ShopifyRestResources,\n Topics = string | number | symbol,\n> = (request: Request) => Promise>;" + }, + "WebhookContext": { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "WebhookContext", + "value": "WebhookContextWithoutSession | WebhookContextWithSession", + "description": "" + }, + "WebhookContextWithoutSession": { + "filePath": "src/server/authenticate/webhooks/types.ts", + "name": "WebhookContextWithoutSession", + "description": "", + "members": [ + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "undefined", + "description": "" + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "admin", + "value": "undefined", + "description": "" + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "apiVersion", + "value": "string", + "description": "The API version used for the webhook.", + "examples": [ + { + "title": "Webhook API version", + "description": "Get the API version used for webhook request.", + "tabs": [ + { + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { apiVersion } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "shop", + "value": "string", + "description": "The shop where the webhook was triggered.", + "examples": [ + { + "title": "Webhook shop", + "description": "Get the shop that triggered a webhook.", + "tabs": [ + { + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { shop } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "topic", + "value": "Topics", + "description": "The topic of the webhook.", + "examples": [ + { + "title": "Webhook topic", + "description": "Get the event topic for the webhook.", + "tabs": [ + { + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { topic } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n // Do something when the app is uninstalled.\n break;\n }\n\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "webhookId", + "value": "string", + "description": "A unique ID for the webhook. Useful to keep track of which events your app has already processed.", + "examples": [ + { + "title": "Webhook ID", + "description": "Get the webhook ID.", + "tabs": [ + { + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { webhookId } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" + } + ] + } + ] + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "payload", + "value": "JSONValue", + "description": "The payload from the webhook request.", + "examples": [ + { + "title": "Webhook payload", + "description": "Get the request's POST payload.", + "tabs": [ + { + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { payload } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" + } ] } ] } ], - "value": "export interface EmbeddedAdminContext<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends AdminContextInternal {\n /**\n * The decoded and validated session token for the request.\n *\n * Returned only if `isEmbeddedApp` is `true`.\n *\n * {@link https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload}\n *\n * @example\n * Using the decoded session token.\n * Get user-specific data using the `sessionToken` object.\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * useOnlineTokens: true,\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken } = await authenticate.public.checkout(\n * request\n * );\n * return json(await getMyAppData({user: sessionToken.sub}));\n * };\n * ```\n */\n sessionToken: JwtPayload;\n\n /**\n * A function that redirects the user to a new page, ensuring that the appropriate parameters are set for embedded\n * apps.\n *\n * Returned only if `isEmbeddedApp` is `true`.\n *\n * @example\n * Redirecting to an app route.\n * Use the `redirect` helper to safely redirect between pages.\n * ```ts\n * // /app/routes/admin/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { session, redirect } = await authenticate.admin(request);\n * return redirect(\"/\");\n * };\n * ```\n *\n * @example\n * Redirecting outside of Shopify admin.\n * Pass in a `target` option of `_top` or `_parent` to go to an external URL.\n * ```ts\n * // /app/routes/admin/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { session, redirect } = await authenticate.admin(request);\n * return redirect(\"/\", { target: '_parent' });\n * };\n * ```\n */\n redirect: RedirectFunction;\n}" + "value": "export interface WebhookContextWithoutSession\n extends Context {\n session: undefined;\n admin: undefined;\n}" }, - "RedirectFunction": { - "filePath": "/server/authenticate/admin/helpers/redirect.ts", - "name": "RedirectFunction", + "JSONValue": { + "filePath": "src/server/types.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "JSONValue", + "value": "string | number | boolean | null | JSONObject | JSONArray", + "description": "" + }, + "JSONObject": { + "filePath": "src/server/types.ts", + "name": "JSONObject", "description": "", - "params": [ + "members": [ + { + "filePath": "src/server/types.ts", + "name": "[x: string]", + "value": "JSONValue" + } + ], + "value": "interface JSONObject {\n [x: string]: JSONValue;\n}" + }, + "JSONArray": { + "filePath": "src/server/types.ts", + "name": "JSONArray", + "description": "", + "members": [ + { + "filePath": "src/server/types.ts", + "syntaxKind": "PropertySignature", + "name": "length", + "value": "number", + "description": "Gets or sets the length of the array. This is a number one higher than the highest index in the array." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "toString", + "value": "() => string", + "description": "Returns a string representation of an array." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "toLocaleString", + "value": "() => string", + "description": "Returns a string representation of an array. The elements are converted to string using their toLocaleString methods." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "pop", + "value": "() => JSONValue", + "description": "Removes the last element from an array and returns it. If the array is empty, undefined is returned and the array is not modified." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "push", + "value": "(...items: JSONValue[]) => number", + "description": "Appends new elements to the end of an array, and returns the new length of the array." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "concat", + "value": "{ (...items: ConcatArray[]): JSONValue[]; (...items: (JSONValue | ConcatArray)[]): JSONValue[]; }", + "description": "Combines two or more arrays. This method returns a new array without modifying any existing arrays." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "join", + "value": "(separator?: string) => string", + "description": "Adds all the elements of an array into a string, separated by the specified separator string." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "reverse", + "value": "() => JSONValue[]", + "description": "Reverses the elements in an array in place. This method mutates the array and returns a reference to the same array." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "shift", + "value": "() => JSONValue", + "description": "Removes the first element from an array and returns it. If the array is empty, undefined is returned and the array is not modified." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "slice", + "value": "(start?: number, end?: number) => JSONValue[]", + "description": "Returns a copy of a section of an array. For both start and end, a negative index can be used to indicate an offset from the end of the array. For example, -2 refers to the second to last element of the array." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "sort", + "value": "(compareFn?: (a: JSONValue, b: JSONValue) => number) => JSONArray", + "description": "Sorts an array in place. This method mutates the array and returns a reference to the same array." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "splice", + "value": "{ (start: number, deleteCount?: number): JSONValue[]; (start: number, deleteCount: number, ...items: JSONValue[]): JSONValue[]; }", + "description": "Removes elements from an array and, if necessary, inserts new elements in their place, returning the deleted elements." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "unshift", + "value": "(...items: JSONValue[]) => number", + "description": "Inserts new elements at the start of an array, and returns the new length of the array." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "indexOf", + "value": "(searchElement: JSONValue, fromIndex?: number) => number", + "description": "Returns the index of the first occurrence of a value in an array, or -1 if it is not present." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "lastIndexOf", + "value": "(searchElement: JSONValue, fromIndex?: number) => number", + "description": "Returns the index of the last occurrence of a specified value in an array, or -1 if it is not present." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "every", + "value": "{ (predicate: (value: JSONValue, index: number, array: JSONValue[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: JSONValue, index: number, array: JSONValue[]) => unknown, thisArg?: any): boolean; }", + "description": "Determines whether all the members of an array satisfy the specified test." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "some", + "value": "(predicate: (value: JSONValue, index: number, array: JSONValue[]) => unknown, thisArg?: any) => boolean", + "description": "Determines whether the specified callback function returns true for any element of an array." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "forEach", + "value": "(callbackfn: (value: JSONValue, index: number, array: JSONValue[]) => void, thisArg?: any) => void", + "description": "Performs the specified action for each element in an array." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "map", + "value": "(callbackfn: (value: JSONValue, index: number, array: JSONValue[]) => U, thisArg?: any) => U[]", + "description": "Calls a defined callback function on each element of an array, and returns an array that contains the results." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "filter", + "value": "{ (predicate: (value: JSONValue, index: number, array: JSONValue[]) => value is S, thisArg?: any): S[]; (predicate: (value: JSONValue, index: number, array: JSONValue[]) => unknown, thisArg?: any): JSONValue[]; }", + "description": "Returns the elements of an array that meet the condition specified in a callback function." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "reduce", + "value": "{ (callbackfn: (previousValue: JSONValue, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => JSONValue): JSONValue; (callbackfn: (previousValue: JSONValue, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => JSONValue, initialValue: JSONValue): JSONValue; (callbackfn: (previousValue: U, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => U, initialValue: U): U; }", + "description": "Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "reduceRight", + "value": "{ (callbackfn: (previousValue: JSONValue, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => JSONValue): JSONValue; (callbackfn: (previousValue: JSONValue, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => JSONValue, initialValue: JSONValue): JSONValue; (callbackfn: (previousValue: U, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => U, initialValue: U): U; }", + "description": "Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "find", + "value": "{ (predicate: (value: JSONValue, index: number, obj: JSONValue[]) => value is S, thisArg?: any): S; (predicate: (value: JSONValue, index: number, obj: JSONValue[]) => unknown, thisArg?: any): JSONValue; }", + "description": "Returns the value of the first element in the array where predicate is true, and undefined otherwise." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "findIndex", + "value": "(predicate: (value: JSONValue, index: number, obj: JSONValue[]) => unknown, thisArg?: any) => number", + "description": "Returns the index of the first element in the array where predicate is true, and -1 otherwise." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "fill", + "value": "(value: JSONValue, start?: number, end?: number) => JSONArray", + "description": "Changes all array elements from `start` to `end` index to a static `value` and returns the modified array" + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "copyWithin", + "value": "(target: number, start: number, end?: number) => JSONArray", + "description": "Returns the this object after copying a section of the array identified by start and end to the same array starting at position target" + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "entries", + "value": "() => IterableIterator<[number, JSONValue]>", + "description": "Returns an iterable of key, value pairs for every entry in the array" + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "keys", + "value": "() => IterableIterator", + "description": "Returns an iterable of keys in the array" + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "values", + "value": "() => IterableIterator", + "description": "Returns an iterable of values in the array" + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "includes", + "value": "(searchElement: JSONValue, fromIndex?: number) => boolean", + "description": "Determines whether an array includes a certain element, returning true or false as appropriate." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "flatMap", + "value": "(callback: (this: This, value: JSONValue, index: number, array: JSONValue[]) => U | readonly U[], thisArg?: This) => U[]", + "description": "Calls a defined callback function on each element of an array. Then, flattens the result into a new array. This is identical to a map followed by flat with depth 1." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "flat", + "value": "(this: A, depth?: D) => FlatArray[]", + "description": "Returns a new array with all sub-array elements concatenated into it recursively up to the specified depth." + }, + { + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "__@iterator@1657", + "value": "() => IterableIterator", + "description": "Iterator" + }, { - "name": "url", - "description": "", - "value": "string", - "filePath": "/server/authenticate/admin/helpers/redirect.ts" + "filePath": "src/server/types.ts", + "syntaxKind": "PropertySignature", + "name": "__@unscopables@1659", + "value": "{ [x: number]: boolean; length?: boolean; toString?: boolean; toLocaleString?: boolean; pop?: boolean; push?: boolean; concat?: boolean; join?: boolean; reverse?: boolean; shift?: boolean; slice?: boolean; sort?: boolean; splice?: boolean; unshift?: boolean; indexOf?: boolean; lastIndexOf?: boolean; every?: boolean; some?: boolean; forEach?: boolean; map?: boolean; filter?: boolean; reduce?: boolean; reduceRight?: boolean; find?: boolean; findIndex?: boolean; fill?: boolean; copyWithin?: boolean; entries?: boolean; keys?: boolean; values?: boolean; includes?: boolean; flatMap?: boolean; flat?: boolean; [Symbol.iterator]?: boolean; readonly [Symbol.unscopables]?: boolean; at?: boolean; }", + "description": "Is an object whose properties have the value 'true' when they will be absent when used in a 'with' statement." }, { - "name": "init", - "description": "", - "value": "RedirectInit", - "isOptional": true, - "filePath": "/server/authenticate/admin/helpers/redirect.ts" + "filePath": "src/server/types.ts", + "syntaxKind": "MethodSignature", + "name": "at", + "value": "(index: number) => JSONValue", + "description": "Takes an integer value and returns the item at that index, allowing for positive and negative integers. Negative integers count back from the last item in the array." } ], - "returns": { - "filePath": "/server/authenticate/admin/helpers/redirect.ts", - "description": "", - "name": "TypedResponse", - "value": "TypedResponse" - }, - "value": "export type RedirectFunction = (\n url: string,\n init?: RedirectInit,\n) => TypedResponse;" - }, - "RedirectInit": { - "filePath": "/server/authenticate/admin/helpers/redirect.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "RedirectInit", - "value": "number | (ResponseInit & {target?: RedirectTarget})", - "description": "" - }, - "RedirectTarget": { - "filePath": "/server/authenticate/admin/helpers/redirect.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "RedirectTarget", - "value": "'_self' | '_parent' | '_top'", - "description": "" + "value": "interface JSONArray extends Array {}" }, - "LegacyWebhookAdminApiContext": { - "filePath": "/server/authenticate/webhooks/types.ts", - "name": "LegacyWebhookAdminApiContext", + "WebhookContextWithSession": { + "filePath": "src/server/authenticate/webhooks/types.ts", + "name": "WebhookContextWithSession", "description": "", "members": [ { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "rest", - "value": "RestClient & Resources", - "description": "A REST client." + "name": "session", + "value": "Session", + "description": "A session with an offline token for the shop.\n\nReturned only if there is a session for the shop." }, { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "graphql", - "value": "InstanceType", - "description": "A GraphQL client." - } - ], - "value": "export interface LegacyWebhookAdminApiContext<\n Resources extends ShopifyRestResources,\n> {\n /** A REST client. */\n rest: InstanceType & Resources;\n /** A GraphQL client. */\n graphql: InstanceType;\n}" - } - } - } - ], - "jsDocTypeExamples": [ - "WebhookContextWithSession" - ], - "related": [ - { - "name": "Admin API context", - "subtitle": "Interact with the Admin API.", - "url": "/docs/api/shopify-app-remix/apis/admin-api" - } - ], - "examples": { - "description": "", - "exampleGroups": [ - { - "title": "admin", - "examples": [ - { - "description": "With the `v3_webhookAdminContext` future flag enabled, use the `admin` object in the context to interact with the Admin API.", - "codeblock": { - "title": "[V3] Webhook admin context", - "tabs": [ - { - "title": "/app/routes/webhooks.tsx", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.webhook(request);\n\n const response = await admin?.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", - "language": "typescript" - } - ] - } - }, - { - "description": "Use the `admin` object in the context to interact with the Admin API. This format will be removed in V3 of the package.", - "codeblock": { - "title": "Webhook admin context", - "tabs": [ - { - "title": "/app/routes/webhooks.tsx", - "code": "import { json, ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.webhook(request);\n\n const response = await admin?.graphql.query<any>({\n data: {\n query: `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n variables: { input: { title: \"Product Name\" } },\n },\n });\n\n const productData = response?.body.data;\n return json({ data: productData.data });\n}", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "apiVersion", - "examples": [ - { - "description": "Get the API version used for webhook request.", - "codeblock": { - "title": "Webhook API version", - "tabs": [ - { - "title": "/app/routes/webhooks.tsx", - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { apiVersion } = await authenticate.webhook(request);\n return new Response();\n};", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "shop", - "examples": [ - { - "description": "Get the shop that triggered a webhook.", - "codeblock": { - "title": "Webhook shop", - "tabs": [ - { - "title": "/app/routes/webhooks.tsx", - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { shop } = await authenticate.webhook(request);\n return new Response();\n};", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "topic", - "examples": [ - { - "description": "Get the event topic for the webhook.", - "codeblock": { - "title": "Webhook topic", - "tabs": [ + "name": "admin", + "value": "WebhookAdminContext", + "description": "An admin context for the webhook.\n\nReturned only if there is a session for the shop.", + "examples": [ { - "title": "/app/routes/webhooks.tsx", - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { topic } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n // Do something when the app is uninstalled.\n break;\n }\n\n return new Response();\n};", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "webhookId", - "examples": [ - { - "description": "Get the webhook ID.", - "codeblock": { - "title": "Webhook ID", - "tabs": [ + "title": "[V3] Webhook admin context", + "description": "With the `v3_webhookAdminContext` future flag enabled, use the `admin` object in the context to interact with the Admin API.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.webhook(request);\n\n const response = await admin?.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", + "title": "/app/routes/webhooks.tsx" + } + ] + }, { - "title": "/app/routes/webhooks.tsx", - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { webhookId } = await authenticate.webhook(request);\n return new Response();\n};", - "language": "typescript" + "title": "Webhook admin context", + "description": "Use the `admin` object in the context to interact with the Admin API. This format will be removed in V3 of the package.", + "tabs": [ + { + "code": "import { json, ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.webhook(request);\n\n const response = await admin?.graphql.query({\n data: {\n query: `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n variables: { input: { title: \"Product Name\" } },\n },\n });\n\n const productData = response?.body.data;\n return json({ data: productData.data });\n}", + "title": "/app/routes/webhooks.tsx" + } + ] } ] - } - } - ] - }, - { - "title": "payload", - "examples": [ - { - "description": "Get the request's POST payload.", - "codeblock": { - "title": "Webhook payload", - "tabs": [ + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "apiVersion", + "value": "string", + "description": "The API version used for the webhook.", + "examples": [ { - "title": "/app/routes/webhooks.tsx", - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { payload } = await authenticate.webhook(request);\n return new Response();\n};", - "language": "typescript" + "title": "Webhook API version", + "description": "Get the API version used for webhook request.", + "tabs": [ + { + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { apiVersion } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" + } + ] } ] - } - } - ] - } - ] - } - }, - { - "name": "Admin API", - "description": "Contains objects used to interact with the Admin API.\n\nThis object is returned as part of different contexts, such as [`admin`](/docs/api/shopify-app-remix/authenticate/admin), [`unauthenticated.admin`](/docs/api/shopify-app-remix/unauthenticated/unauthenticated-admin), and [`webhook`](/docs/api/shopify-app-remix/authenticate/webhook).", - "category": "APIs", - "type": "object", - "isVisualComponent": false, - "definitions": [ - { - "title": "admin", - "description": "Provides utilities that apps can use to make requests to the Admin API.", - "type": "AdminApiContext", - "typeDefinitions": { - "AdminApiContext": { - "filePath": "/server/clients/admin/types.ts", - "name": "AdminApiContext", - "description": "", - "members": [ + }, { - "filePath": "/server/clients/admin/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "rest", - "value": "RestClientWithResources", - "description": "Methods for interacting with the Shopify Admin REST API\n\nThere are methods for interacting with individual REST resources. You can also make `GET`, `POST`, `PUT` and `DELETE` requests should the REST resources not meet your needs.\n\n\n\n\n", + "name": "shop", + "value": "string", + "description": "The shop where the webhook was triggered.", "examples": [ { - "title": "Using REST resources", - "description": "Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource.", + "title": "Webhook shop", + "description": "Get the shop that triggered a webhook.", "tabs": [ { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n return json(admin.rest.resources.Order.count({ session }));\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { shop } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" } ] - }, + } + ] + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "topic", + "value": "Topics", + "description": "The topic of the webhook.", + "examples": [ { - "title": "Performing a GET request to the REST API", - "description": "Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint", + "title": "Webhook topic", + "description": "Get the event topic for the webhook.", "tabs": [ { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = await admin.rest.get({ path: \"/customers/count.json\" });\n const customers = await response.json();\n return json({ customers });\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { topic } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n // Do something when the app is uninstalled.\n break;\n }\n\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" } ] - }, + } + ] + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "webhookId", + "value": "string", + "description": "A unique ID for the webhook. Useful to keep track of which events your app has already processed.", + "examples": [ { - "title": "Performing a POST request to the REST API", - "description": "Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email", + "title": "Webhook ID", + "description": "Get the webhook ID.", "tabs": [ { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = admin.rest.post({\n path: \"customers/7392136888625/send_invite.json\",\n body: {\n customer_invite: {\n to: \"new_test_email@shopify.com\",\n from: \"j.limited@example.com\",\n bcc: [\"j.limited@example.com\"],\n subject: \"Welcome to my new shop\",\n custom_message: \"My awesome new store\",\n },\n },\n});\n const customerInvite = await response.json();\n return json({ customerInvite });\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { webhookId } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" } ] } ] }, { - "filePath": "/server/clients/admin/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", - "name": "graphql", - "value": "GraphQLClient", - "description": "Methods for interacting with the Shopify Admin GraphQL API\n\n\n\n\n\n\n\n\n\n", + "name": "payload", + "value": "JSONValue", + "description": "The payload from the webhook request.", "examples": [ { - "title": "Querying the GraphQL API", - "description": "Use `admin.graphql` to make query / mutation requests.", + "title": "Webhook payload", + "description": "Get the request's POST payload.", "tabs": [ { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.admin(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", - "title": "Example" + "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { payload } = await authenticate.webhook(request);\n return new Response();\n};", + "title": "/app/routes/webhooks.tsx" } ] } ] } ], - "value": "export interface AdminApiContext<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * Methods for interacting with the Shopify Admin REST API\n *\n * There are methods for interacting with individual REST resources. You can also make `GET`, `POST`, `PUT` and `DELETE` requests should the REST resources not meet your needs.\n *\n * {@link https://shopify.dev/docs/api/admin-rest}\n *\n * @example\n * Using REST resources.\n * Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource. \n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * return json(admin.rest.resources.Order.count({ session }));\n * };\n * ```\n *\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n *\n * @example\n * Performing a GET request to the REST API.\n * Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint\n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = await admin.rest.get({ path: \"/customers/count.json\" });\n * const customers = await response.json();\n * return json({ customers });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * @example\n * Performing a POST request to the REST API.\n * Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = admin.rest.post({\n * path: \"customers/7392136888625/send_invite.json\",\n * body: {\n * customer_invite: {\n * to: \"new_test_email@shopify.com\",\n * from: \"j.limited@example.com\",\n * bcc: [\"j.limited@example.com\"],\n * subject: \"Welcome to my new shop\",\n * custom_message: \"My awesome new store\",\n * },\n * },\n * });\n * const customerInvite = await response.json();\n * return json({ customerInvite });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n rest: RestClientWithResources;\n\n /**\n * Methods for interacting with the Shopify Admin GraphQL API\n *\n * {@link https://shopify.dev/docs/api/admin-graphql}\n * {@link https://github.com/Shopify/shopify-api-js/blob/main/packages/shopify-api/docs/reference/clients/Graphql.md}\n *\n * @example\n * Querying the GraphQL API.\n * Use `admin.graphql` to make query / mutation requests.\n * ```ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.admin(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n graphql: GraphQLClient;\n}" + "value": "export interface WebhookContextWithSession<\n Future extends FutureFlagOptions,\n Resources extends ShopifyRestResources,\n Topics = string | number | symbol,\n> extends Context {\n /**\n * A session with an offline token for the shop.\n *\n * Returned only if there is a session for the shop.\n */\n session: Session;\n\n /**\n * An admin context for the webhook.\n *\n * Returned only if there is a session for the shop.\n *\n * @example\n * [V3] Webhook admin context.\n * With the `v3_webhookAdminContext` future flag enabled, use the `admin` object in the context to interact with the Admin API.\n * ```ts\n * // /app/routes/webhooks.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.webhook(request);\n *\n * const response = await admin?.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n *\n * @example\n * Webhook admin context.\n * Use the `admin` object in the context to interact with the Admin API. This format will be removed in V3 of the package.\n * ```ts\n * // /app/routes/webhooks.tsx\n * import { json, ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.webhook(request);\n *\n * const response = await admin?.graphql.query({\n * data: {\n * query: `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * variables: { input: { title: \"Product Name\" } },\n * },\n * });\n *\n * const productData = response?.body.data;\n * return json({ data: productData.data });\n * }\n * ```\n */\n admin: WebhookAdminContext;\n}" }, - "RestClientWithResources": { - "filePath": "/server/clients/admin/rest.ts", + "WebhookAdminContext": { + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "TypeAliasDeclaration", - "name": "RestClientWithResources", - "value": "RemixRestClient & {resources: Resources}", + "name": "WebhookAdminContext", + "value": "FeatureEnabled extends true\n ? AdminApiContext\n : LegacyWebhookAdminApiContext", "description": "" - } - } - } - ], - "jsDocTypeExamples": [ - "AdminApiContext" - ], - "related": [ - { - "name": "Authenticated context", - "subtitle": "Authenticate requests from Shopify Admin.", - "url": "/docs/api/shopify-app-remix/authenticate/admin" - }, - { - "name": "Unauthenticated context", - "subtitle": "Interact with the Admin API on non-Shopify requests.", - "url": "/docs/api/shopify-app-remix/unauthenticated/unauthenticated-admin" - } - ], - "examples": { - "description": "", - "exampleGroups": [ - { - "title": "rest", - "examples": [ - { - "description": "Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource.", - "codeblock": { - "title": "Using REST resources", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n return json(admin.rest.resources.Order.count({ session }));\n};", - "language": "typescript" - }, - { - "title": "/app/shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - }, - { - "description": "Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint", - "codeblock": { - "title": "Performing a GET request to the REST API", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = await admin.rest.get({ path: \"/customers/count.json\" });\n const customers = await response.json();\n return json({ customers });\n};", - "language": "typescript" - }, - { - "title": "/app/shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - }, - { - "description": "Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email", - "codeblock": { - "title": "Performing a POST request to the REST API", - "tabs": [ - { - "title": "/app/routes/**\\/*.ts", - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = admin.rest.post({\n path: \"customers/7392136888625/send_invite.json\",\n body: {\n customer_invite: {\n to: \"new_test_email@shopify.com\",\n from: \"j.limited@example.com\",\n bcc: [\"j.limited@example.com\"],\n subject: \"Welcome to my new shop\",\n custom_message: \"My awesome new store\",\n },\n },\n});\n const customerInvite = await response.json();\n return json({ customerInvite });\n};", - "language": "typescript" - }, - { - "title": "/app/shopify.server.ts", - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "language": "typescript" - } - ] - } - } - ] - }, - { - "title": "graphql", - "examples": [ - { - "description": "Use `admin.graphql` to make query / mutation requests.", - "codeblock": { - "title": "Querying the GraphQL API", - "tabs": [ - { - "title": "Example", - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.admin(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", - "language": "typescript" - } - ] - } - } - ] - } - ] - } - }, - { - "name": "Storefront API", - "description": "Contains objects used to interact with the Storefront API.\n\nThis object is returned as part of different contexts, such as [`appProxy`](/docs/api/shopify-app-remix/authenticate/public/app-proxy), and [`unauthenticated.storefront`](/docs/api/shopify-app-remix/unauthenticated/unauthenticated-storefront).", - "category": "APIs", - "type": "object", - "isVisualComponent": false, - "definitions": [ - { - "title": "storefront", - "description": "Provides utilities that apps can use to make requests to the Storefront API.", - "type": "StorefrontContext", - "typeDefinitions": { - "StorefrontContext": { - "filePath": "/server/clients/storefront/types.ts", - "name": "StorefrontContext", + }, + "LegacyWebhookAdminApiContext": { + "filePath": "src/server/authenticate/webhooks/types.ts", + "name": "LegacyWebhookAdminApiContext", "description": "", "members": [ { - "filePath": "/server/clients/storefront/types.ts", + "filePath": "src/server/authenticate/webhooks/types.ts", + "syntaxKind": "PropertySignature", + "name": "rest", + "value": "RestClient & Resources", + "description": "A REST client." + }, + { + "filePath": "src/server/authenticate/webhooks/types.ts", "syntaxKind": "PropertySignature", "name": "graphql", - "value": "GraphQLClient", - "description": "Method for interacting with the Shopify Storefront GraphQL API\n\nIf you're getting incorrect type hints in the Shopify template, follow [these instructions](https://github.com/Shopify/shopify-app-template-remix/tree/main#incorrect-graphql-hints).\n\n\n\n\n", - "examples": [ - { - "title": "Querying the GraphQL API", - "description": "Use `storefront.graphql` to make query / mutation requests.", - "tabs": [ - { - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", - "title": "app/routes/**\\/.ts" - } - ] - } - ] + "value": "InstanceType", + "description": "A GraphQL client." } ], - "value": "export interface StorefrontContext {\n /**\n * Method for interacting with the Shopify Storefront GraphQL API\n *\n * If you're getting incorrect type hints in the Shopify template, follow [these instructions](https://github.com/Shopify/shopify-app-template-remix/tree/main#incorrect-graphql-hints).\n *\n * {@link https://shopify.dev/docs/api/storefront}\n *\n * @example\n * Querying the GraphQL API.\n * Use `storefront.graphql` to make query / mutation requests.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { storefront } = await authenticate.public.appProxy(request);\n *\n * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n *\n * return json(await response.json());\n * }\n * ```\n */\n graphql: GraphQLClient;\n}" - } - } - } - ], - "jsDocTypeExamples": [ - "StorefrontContext" - ], - "related": [ - { - "name": "App proxy context", - "subtitle": "Authenticate requests from Shopify app proxies.", - "url": "/docs/api/shopify-app-remix/authenticate/public/app-proxy" - }, - { - "name": "Unauthenticated context", - "subtitle": "Interact with the Storefront API on non-Shopify requests.", - "url": "/docs/api/shopify-app-remix/unauthenticated/unauthenticated-storefront" - } - ], - "examples": { - "description": "", - "exampleGroups": [ - { - "title": "graphql", - "examples": [ - { - "description": "Use `storefront.graphql` to make query / mutation requests.", - "codeblock": { - "title": "Querying the GraphQL API", - "tabs": [ - { - "title": "app/routes/**\\/.ts", - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", - "language": "typescript" - } - ] + "value": "export interface LegacyWebhookAdminApiContext<\n Resources extends ShopifyRestResources,\n> {\n /** A REST client. */\n rest: InstanceType & Resources;\n /** A GraphQL client. */\n graphql: InstanceType;\n}" + }, + "RestClient": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/rest/client.d.ts", + "name": "RestClient", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/rest/client.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "loggedDeprecations", + "value": "Record", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/rest/client.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "client", + "value": "AdminRestApiClient", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/rest/client.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "session", + "value": "Session", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/rest/client.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "apiVersion", + "value": "ApiVersion", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/rest/client.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "get", + "value": "(params: GetRequestParams) => Promise>", + "description": "Performs a GET request on the given path." + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/rest/client.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "post", + "value": "(params: PostRequestParams) => Promise>", + "description": "Performs a POST request on the given path." + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/rest/client.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "put", + "value": "(params: PostRequestParams) => Promise>", + "description": "Performs a PUT request on the given path." + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/rest/client.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "delete", + "value": "(params: GetRequestParams) => Promise>", + "description": "Performs a DELETE request on the given path." } - } - ] - } - ] - } - }, - { - "name": "shopifyApp", - "description": "Returns a set of functions that can be used by the app's backend to be able to respond to all Shopify requests.\n\nThe shape of the returned object changes depending on the value of `distribution`. If it is `AppDistribution.ShopifyAdmin`, then only `ShopifyAppBase` objects are returned, otherwise `ShopifyAppLogin` objects are included.", - "category": "Entrypoints", - "type": "function", - "isVisualComponent": false, - "definitions": [ - { - "title": "shopifyApp", - "description": "Function to create a new Shopify API object.", - "type": "ShopifyAppGeneratedType", - "typeDefinitions": { - "ShopifyAppGeneratedType": { - "filePath": "/server/shopify-app.ts", - "name": "ShopifyAppGeneratedType", - "description": "Creates an object your app will use to interact with Shopify.", - "params": [ + ], + "value": "export declare class RestClient {\n static config: ConfigInterface;\n static formatPaths: boolean;\n static LINK_HEADER_REGEXP: RegExp;\n static DEFAULT_LIMIT: string;\n static RETRY_WAIT_TIME: number;\n static readonly DEPRECATION_ALERT_DELAY = 300000;\n loggedDeprecations: Record;\n readonly client: AdminRestApiClient;\n readonly session: Session;\n readonly apiVersion: ApiVersion;\n constructor({ session, apiVersion }: RestClientParams);\n /**\n * Performs a GET request on the given path.\n */\n get(params: GetRequestParams): Promise>;\n /**\n * Performs a POST request on the given path.\n */\n post(params: PostRequestParams): Promise>;\n /**\n * Performs a PUT request on the given path.\n */\n put(params: PutRequestParams): Promise>;\n /**\n * Performs a DELETE request on the given path.\n */\n delete(params: DeleteRequestParams): Promise>;\n protected request(params: RequestParams): Promise>;\n private restClass;\n private buildRequestParams;\n private logDeprecations;\n}" + }, + "RestRequestReturn": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "name": "RestRequestReturn", + "description": "", + "members": [ { - "name": "appConfig", - "description": "Configuration options for your Shopify app, such as the scopes your app needs.", - "value": "Config extends AppConfigArg", - "filePath": "/server/shopify-app.ts" + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "body", + "value": "T", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "headers", + "value": "Headers", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "pageInfo", + "value": "PageInfo", + "description": "", + "isOptional": true } ], - "returns": { - "filePath": "/server/shopify-app.ts", - "description": "`ShopifyApp` An object constructed using your appConfig. It has methods for interacting with Shopify.", - "name": "ShopifyApp>", - "value": "ShopifyApp>" - }, - "value": "export function shopifyApp<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources,\n Storage extends SessionStorage,\n>(appConfig: Config): ShopifyApp {\n const api = deriveApi(appConfig);\n const config = deriveConfig(appConfig, api.config);\n const logger = overrideLogger(api.logger);\n\n if (appConfig.webhooks) {\n api.webhooks.addHandlers(appConfig.webhooks);\n }\n\n const params: BasicParams = {api, config, logger};\n const oauth = new AuthCodeFlowStrategy(params);\n const authStrategy = authStrategyFactory({\n ...params,\n strategy: oauth,\n });\n\n const shopify:\n | AdminApp\n | AppStoreApp\n | SingleMerchantApp = {\n sessionStorage: config.sessionStorage,\n addDocumentResponseHeaders: addDocumentResponseHeadersFactory(params),\n registerWebhooks: registerWebhooksFactory(params),\n authenticate: {\n admin: authStrategy,\n public: authenticatePublicFactory(params),\n webhook: authenticateWebhookFactory<\n Config['future'],\n Resources,\n keyof Config['webhooks'] | MandatoryTopics\n >(params),\n },\n unauthenticated: {\n admin: unauthenticatedAdminContextFactory(params),\n storefront: unauthenticatedStorefrontContextFactory(params),\n },\n };\n\n if (\n isAppStoreApp(shopify, appConfig) ||\n isSingleMerchantApp(shopify, appConfig)\n ) {\n shopify.login = loginFactory(params);\n }\n\n return shopify as ShopifyApp;\n}", - "examples": [ + "value": "export interface RestRequestReturn {\n body: T;\n headers: Headers;\n pageInfo?: PageInfo;\n}" + }, + "PageInfo": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "name": "PageInfo", + "description": "", + "members": [ { - "title": "The minimum viable configuration", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "limit", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "fields", + "value": "string[]", "description": "", - "tabs": [ - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n apiKey: process.env.SHOPIFY_API_KEY!,\n apiSecretKey: process.env.SHOPIFY_API_SECRET!,\n scopes: process.env.SCOPES?.split(\",\")!,\n appUrl: process.env.SHOPIFY_APP_URL!,\n});\nexport default shopify;", - "title": "/shopify.server.ts" - } - ] + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "previousPageUrl", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "nextPageUrl", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "prevPage", + "value": "PageInfoParams", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "nextPage", + "value": "PageInfoParams", + "description": "", + "isOptional": true } - ] + ], + "value": "export interface PageInfo {\n limit: string;\n fields?: string[];\n previousPageUrl?: string;\n nextPageUrl?: string;\n prevPage?: PageInfoParams;\n nextPage?: PageInfoParams;\n}" }, - "AppConfigArg": { - "filePath": "/server/config-types.ts", - "name": "AppConfigArg", + "PageInfoParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", + "name": "PageInfoParams", "description": "", "members": [ { - "filePath": "/server/config-types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", "syntaxKind": "PropertySignature", - "name": "appUrl", + "name": "path", "value": "string", - "description": "The URL your app is running on.\n\nThe `@shopify/cli` provides this URL as `process.env.SHOPIFY_APP_URL`. For development this is probably a tunnel URL that points to your local machine. If this is a production app, this is your production URL." + "description": "" }, { - "filePath": "/server/config-types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/types.d.ts", "syntaxKind": "PropertySignature", - "name": "sessionStorage", - "value": "Storage", - "description": "An adaptor for storing sessions in your database of choice.\n\nShopify provides multiple session storage adaptors and you can create your own.\n\n\n\n\n", - "examples": [ - { - "title": "Storing sessions with Prisma", - "description": "Add the `@shopify/shopify-app-session-storage-prisma` package to use the Prisma session storage.", - "tabs": [ - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\n\nimport prisma from \"~/db.server\";\n\nconst shopify = shopifyApp({\n // ... etc\n sessionStorage: new PrismaSessionStorage(prisma),\n});\nexport default shopify;", - "title": "Example" - } - ] - } - ] + "name": "query", + "value": "SearchParams", + "description": "" + } + ], + "value": "export interface PageInfoParams {\n path: string;\n query: SearchParams;\n}" + }, + "Shopify": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "name": "Shopify", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "config", + "value": "ConfigInterface", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "clients", + "value": "ShopifyClients", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "auth", + "value": "ShopifyAuth", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "ShopifySession", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "utils", + "value": "ShopifyUtils", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "webhooks", + "value": "ShopifyWebhooks", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "billing", + "value": "ShopifyBilling", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "logger", + "value": "ShopifyLogger", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "rest", + "value": "Resources", + "description": "" + } + ], + "value": "export interface Shopify {\n config: ConfigInterface;\n clients: ShopifyClients;\n auth: ShopifyAuth;\n session: ShopifySession;\n utils: ShopifyUtils;\n webhooks: ShopifyWebhooks;\n billing: ShopifyBilling;\n logger: ShopifyLogger;\n rest: Resources;\n}" + }, + "ConfigInterface": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/base-types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "ConfigInterface", + "value": "Omit & {\n apiKey: string;\n hostScheme: 'http' | 'https';\n scopes: AuthScopes;\n isCustomStoreApp: boolean;\n billing?: BillingConfig;\n logger: {\n log: LogFunction;\n level: LogSeverity;\n httpRequests: boolean;\n timestamps: boolean;\n };\n future: FutureFlagOptions;\n}", + "description": "" + }, + "Key": { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "Key", + "value": "export declare enum Key {\n Backspace = 8,\n Tab = 9,\n Enter = 13,\n Shift = 16,\n Ctrl = 17,\n Alt = 18,\n Pause = 19,\n CapsLock = 20,\n Escape = 27,\n Space = 32,\n PageUp = 33,\n PageDown = 34,\n End = 35,\n Home = 36,\n LeftArrow = 37,\n UpArrow = 38,\n RightArrow = 39,\n DownArrow = 40,\n Insert = 45,\n Delete = 46,\n Key0 = 48,\n Key1 = 49,\n Key2 = 50,\n Key3 = 51,\n Key4 = 52,\n Key5 = 53,\n Key6 = 54,\n Key7 = 55,\n Key8 = 56,\n Key9 = 57,\n KeyA = 65,\n KeyB = 66,\n KeyC = 67,\n KeyD = 68,\n KeyE = 69,\n KeyF = 70,\n KeyG = 71,\n KeyH = 72,\n KeyI = 73,\n KeyJ = 74,\n KeyK = 75,\n KeyL = 76,\n KeyM = 77,\n KeyN = 78,\n KeyO = 79,\n KeyP = 80,\n KeyQ = 81,\n KeyR = 82,\n KeyS = 83,\n KeyT = 84,\n KeyU = 85,\n KeyV = 86,\n KeyW = 87,\n KeyX = 88,\n KeyY = 89,\n KeyZ = 90,\n LeftMeta = 91,\n RightMeta = 92,\n Select = 93,\n Numpad0 = 96,\n Numpad1 = 97,\n Numpad2 = 98,\n Numpad3 = 99,\n Numpad4 = 100,\n Numpad5 = 101,\n Numpad6 = 102,\n Numpad7 = 103,\n Numpad8 = 104,\n Numpad9 = 105,\n Multiply = 106,\n Add = 107,\n Subtract = 109,\n Decimal = 110,\n Divide = 111,\n F1 = 112,\n F2 = 113,\n F3 = 114,\n F4 = 115,\n F5 = 116,\n F6 = 117,\n F7 = 118,\n F8 = 119,\n F9 = 120,\n F10 = 121,\n F11 = 122,\n F12 = 123,\n NumLock = 144,\n ScrollLock = 145,\n Semicolon = 186,\n Equals = 187,\n Comma = 188,\n Dash = 189,\n Period = 190,\n ForwardSlash = 191,\n GraveAccent = 192,\n OpenBracket = 219,\n BackSlash = 220,\n CloseBracket = 221,\n SingleQuote = 222\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Backspace", + "value": 8 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Tab", + "value": 9 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Enter", + "value": 13 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Shift", + "value": 16 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Ctrl", + "value": 17 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Alt", + "value": 18 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Pause", + "value": 19 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "CapsLock", + "value": 20 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Escape", + "value": 27 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Space", + "value": 32 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "PageUp", + "value": 33 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "PageDown", + "value": 34 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "End", + "value": 35 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Home", + "value": 36 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "LeftArrow", + "value": 37 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "UpArrow", + "value": 38 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "RightArrow", + "value": 39 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "DownArrow", + "value": 40 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Insert", + "value": 45 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Delete", + "value": 46 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key0", + "value": 48 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key1", + "value": 49 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key2", + "value": 50 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key3", + "value": 51 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key4", + "value": 52 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key5", + "value": 53 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key6", + "value": 54 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key7", + "value": 55 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key8", + "value": 56 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Key9", + "value": 57 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyA", + "value": 65 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyB", + "value": 66 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyC", + "value": 67 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyD", + "value": 68 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyE", + "value": 69 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyF", + "value": 70 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyG", + "value": 71 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyH", + "value": 72 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyI", + "value": 73 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyJ", + "value": 74 }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "useOnlineTokens", - "value": "boolean", - "description": "Whether your app use online or offline tokens.\n\nIf your app uses online tokens, then both online and offline tokens will be saved to your database. This ensures your app can perform background jobs.\n\n\n\n\n", - "isOptional": true, - "defaultValue": "`false`" + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyK", + "value": 75 }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "webhooks", - "value": "WebhookConfig", - "description": "The config for the webhook topics your app would like to subscribe to.\n\n\n\n\n\n\n\nThis can be in used in conjunction with the afterAuth hook to register webhook topics when a user installs your app. Or you can use this function in other processes such as background jobs.", - "isOptional": true, - "examples": [ - { - "title": "Registering for a webhook when a merchant uninstalls your app", - "description": "", - "tabs": [ - { - "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n webhooks: {\n APP_UNINSTALLED: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: \"/webhooks\",\n },\n },\n hooks: {\n afterAuth: async ({ session }) => {\n shopify.registerWebhooks({ session });\n }\n },\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;\n\n// /app/routes/webhooks.jsx\nimport { ActionFunctionArgs } from \"@remix-run/node\";\n\nimport { authenticate } from \"../shopify.server\";\nimport db from \"../db.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { topic, shop } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n await db.session.deleteMany({ where: { shop } });\n break;\n case \"CUSTOMERS_DATA_REQUEST\":\n case \"CUSTOMERS_REDACT\":\n case \"SHOP_REDACT\":\n default:\n throw new Response(\"Unhandled webhook topic\", { status: 404 });\n }\n throw new Response();\n};", - "title": "/app/shopify.server.ts" - } - ] - } - ] + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyL", + "value": 76 }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "hooks", - "value": "HooksConfig", - "description": "Functions to call at key places during your apps lifecycle.\n\nThese functions are called in the context of the request that triggered them. This means you can access the session.", - "isOptional": true, - "examples": [ - { - "title": "Seeding your database custom data when a merchant installs your app", - "description": "", - "tabs": [ - { - "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { seedStoreData } from \"~/db/seeds\"\n\nconst shopify = shopifyApp({\n hooks: {\n afterAuth: async ({ session }) => {\n seedStoreData({session})\n }\n },\n // ...etc\n});", - "title": "Example" - } - ] - } - ] + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyM", + "value": 77 }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "isEmbeddedApp", - "value": "boolean", - "description": "Does your app render embedded inside the Shopify Admin or on its own.\n\nUnless you have very specific needs, this should be true.", - "isOptional": true, - "defaultValue": "`true`" + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyN", + "value": 78 }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "distribution", - "value": "AppDistribution", - "description": "How your app is distributed. Default is `AppDistribution.AppStore`.\n\n\n\n\n", - "isOptional": true + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyO", + "value": 79 }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "apiVersion", - "value": "ApiVersion", - "description": "What version of Shopify's Admin API's would you like to use.\n\n\n\n\n", - "isOptional": true, - "defaultValue": "`LATEST_API_VERSION` from `@shopify/shopify-app-remix`", - "examples": [ - { - "title": "Using the latest API Version (Recommended)", - "description": "", - "tabs": [ - { - "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n apiVersion: LATEST_API_VERSION,\n});", - "title": "Example" - } - ] - } - ] + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyP", + "value": 80 }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "authPathPrefix", - "value": "string", - "description": "A path that Shopify can reserve for auth related endpoints.\n\nThis must match a $ route in your Remix app. That route must export a loader function that calls `shopify.authenticate.admin(request)`.", - "isOptional": true, - "defaultValue": "`\"/auth\"`", - "examples": [ - { - "title": "Using the latest API Version (Recommended)", - "description": "", - "tabs": [ - { - "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n apiVersion: LATEST_API_VERSION,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;\n\n// /app/routes/auth/$.jsx\nimport { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n await authenticate.admin(request);\n\n return null\n}", - "title": "/app/shopify.server.ts" - } - ] - } - ] + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyQ", + "value": 81 }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "future", - "value": "Future", - "description": "Features that will be introduced in future releases of this package.\n\nYou can opt in to these features by setting the corresponding flags. By doing so, you can prepare for future releases in advance and provide feedback on the new features.", - "isOptional": true + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyR", + "value": 82 }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "apiKey", - "value": "string", - "description": "", - "isOptional": true + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyS", + "value": 83 }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "apiSecretKey", - "value": "string", - "description": "" + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyT", + "value": 84 }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "scopes", - "value": "string[] | AuthScopes", - "description": "", - "isOptional": true + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyU", + "value": 85 }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "adminApiAccessToken", - "value": "string", - "description": "", - "isOptional": true + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyV", + "value": 86 }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "userAgentPrefix", - "value": "string", - "description": "", - "isOptional": true + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyW", + "value": 87 }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "privateAppStorefrontAccessToken", - "value": "string", - "description": "", - "isOptional": true + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyX", + "value": 88 }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "customShopDomains", - "value": "(string | RegExp)[]", - "description": "", - "isOptional": true + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyY", + "value": 89 }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "billing", - "value": "BillingConfig", - "description": "", - "isOptional": true + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "KeyZ", + "value": 90 }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "restResources", - "value": "Resources", - "description": "", - "isOptional": true + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "LeftMeta", + "value": 91 }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "logger", - "value": "{ log?: LogFunction; level?: LogSeverity; httpRequests?: boolean; timestamps?: boolean; }", - "description": "", - "isOptional": true - } - ], - "value": "export interface AppConfigArg<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n Storage extends SessionStorage = SessionStorage,\n Future extends FutureFlagOptions = FutureFlagOptions,\n> extends Omit<\n ApiConfigArg,\n | 'hostName'\n | 'hostScheme'\n | 'isEmbeddedApp'\n | 'apiVersion'\n | 'isCustomStoreApp'\n | 'future'\n > {\n /**\n * The URL your app is running on.\n *\n * The `@shopify/cli` provides this URL as `process.env.SHOPIFY_APP_URL`. For development this is probably a tunnel URL that points to your local machine. If this is a production app, this is your production URL.\n */\n appUrl: string;\n\n /**\n * An adaptor for storing sessions in your database of choice.\n *\n * Shopify provides multiple session storage adaptors and you can create your own.\n *\n * {@link https://github.com/Shopify/shopify-app-js/blob/main/README.md#session-storage-options}\n *\n * @example\n * Storing sessions with Prisma.\n * Add the `@shopify/shopify-app-session-storage-prisma` package to use the Prisma session storage.\n * ```ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\n *\n * import prisma from \"~/db.server\";\n *\n * const shopify = shopifyApp({\n * // ... etc\n * sessionStorage: new PrismaSessionStorage(prisma),\n * });\n * export default shopify;\n * ```\n */\n sessionStorage: Storage;\n\n /**\n * Whether your app use online or offline tokens.\n *\n * If your app uses online tokens, then both online and offline tokens will be saved to your database. This ensures your app can perform background jobs.\n *\n * {@link https://shopify.dev/docs/apps/auth/oauth/access-modes}\n *\n * @defaultValue `false`\n */\n useOnlineTokens?: boolean;\n\n /**\n * The config for the webhook topics your app would like to subscribe to.\n *\n * {@link https://shopify.dev/docs/apps/webhooks}\n *\n * This can be in used in conjunction with the afterAuth hook to register webhook topics when a user installs your app. Or you can use this function in other processes such as background jobs.\n *\n * @example\n * Registering for a webhook when a merchant uninstalls your app.\n * ```ts\n * // /app/shopify.server.ts\n * import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * webhooks: {\n * APP_UNINSTALLED: {\n * deliveryMethod: DeliveryMethod.Http,\n * callbackUrl: \"/webhooks\",\n * },\n * },\n * hooks: {\n * afterAuth: async ({ session }) => {\n * shopify.registerWebhooks({ session });\n * }\n * },\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n *\n * // /app/routes/webhooks.jsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n *\n * import { authenticate } from \"../shopify.server\";\n * import db from \"../db.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { topic, shop } = await authenticate.webhook(request);\n *\n * switch (topic) {\n * case \"APP_UNINSTALLED\":\n * await db.session.deleteMany({ where: { shop } });\n * break;\n * case \"CUSTOMERS_DATA_REQUEST\":\n * case \"CUSTOMERS_REDACT\":\n * case \"SHOP_REDACT\":\n * default:\n * throw new Response(\"Unhandled webhook topic\", { status: 404 });\n * }\n * throw new Response();\n * };\n * ```\n */\n webhooks?: WebhookConfig;\n\n /**\n * Functions to call at key places during your apps lifecycle.\n *\n * These functions are called in the context of the request that triggered them. This means you can access the session.\n *\n * @example\n * Seeding your database custom data when a merchant installs your app.\n * ```ts\n * import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { seedStoreData } from \"~/db/seeds\"\n *\n * const shopify = shopifyApp({\n * hooks: {\n * afterAuth: async ({ session }) => {\n * seedStoreData({session})\n * }\n * },\n * // ...etc\n * });\n * ```\n */\n hooks?: HooksConfig;\n\n /**\n * Does your app render embedded inside the Shopify Admin or on its own.\n *\n * Unless you have very specific needs, this should be true.\n *\n * @defaultValue `true`\n */\n isEmbeddedApp?: boolean;\n\n /**\n * How your app is distributed. Default is `AppDistribution.AppStore`.\n *\n * {@link https://shopify.dev/docs/apps/distribution}\n */\n distribution?: AppDistribution;\n\n /**\n * What version of Shopify's Admin API's would you like to use.\n *\n * {@link https://shopify.dev/docs/api/}\n *\n * @defaultValue `LATEST_API_VERSION` from `@shopify/shopify-app-remix`\n *\n * @example\n * Using the latest API Version (Recommended)\n * ```ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * apiVersion: LATEST_API_VERSION,\n * });\n * ```\n */\n apiVersion?: ApiVersion;\n\n /**\n * A path that Shopify can reserve for auth related endpoints.\n *\n * This must match a $ route in your Remix app. That route must export a loader function that calls `shopify.authenticate.admin(request)`.\n *\n * @default `\"/auth\"`\n *\n * @example\n * Using the latest API Version (Recommended)\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * apiVersion: LATEST_API_VERSION,\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n *\n * // /app/routes/auth/$.jsx\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * await authenticate.admin(request);\n *\n * return null\n * }\n * ```\n */\n authPathPrefix?: string;\n\n /**\n * Features that will be introduced in future releases of this package.\n *\n * You can opt in to these features by setting the corresponding flags. By doing so, you can prepare for future\n * releases in advance and provide feedback on the new features.\n */\n future?: Future;\n}" - }, - "WebhookConfig": { - "filePath": "/server/config-types.ts", - "name": "WebhookConfig", - "description": "", - "members": [ + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "RightMeta", + "value": 92 + }, { - "filePath": "/server/config-types.ts", - "name": "[key: string]", - "value": "WebhookHandler | WebhookHandler[]" - } - ], - "value": "export interface WebhookConfig {\n [key: string]: WebhookHandler | WebhookHandler[];\n}" - }, - "HooksConfig": { - "filePath": "/server/config-types.ts", - "name": "HooksConfig", - "description": "", - "members": [ + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Select", + "value": 93 + }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "afterAuth", - "value": "(options: AfterAuthOptions) => void | Promise", - "description": "A function to call after a merchant installs your app", - "isOptional": true, - "examples": [ - { - "title": "Registering webhooks and seeding data when a merchant installs your app", - "description": "", - "tabs": [ - { - "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { seedStoreData } from \"~/db/seeds\"\n\nconst shopify = shopifyApp({\n hooks: {\n afterAuth: async ({ session }) => {\n shopify.registerWebhooks({ session });\n seedStoreData({session})\n }\n },\n webhooks: {\n APP_UNINSTALLED: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: \"/webhooks\",\n },\n },\n // ...etc\n});", - "title": "Example" - } - ] - } - ] - } - ], - "value": "interface HooksConfig {\n /**\n * A function to call after a merchant installs your app\n *\n * @param context - An object with context about the request that triggered the hook.\n * @param context.session - The session of the merchant that installed your app. This is the output of sessionStorage.loadSession in case people want to load their own.\n * @param context.admin - An object with access to the Shopify Admin API's.\n *\n * @example\n * Registering webhooks and seeding data when a merchant installs your app.\n * ```ts\n * import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { seedStoreData } from \"~/db/seeds\"\n *\n * const shopify = shopifyApp({\n * hooks: {\n * afterAuth: async ({ session }) => {\n * shopify.registerWebhooks({ session });\n * seedStoreData({session})\n * }\n * },\n * webhooks: {\n * APP_UNINSTALLED: {\n * deliveryMethod: DeliveryMethod.Http,\n * callbackUrl: \"/webhooks\",\n * },\n * },\n * // ...etc\n * });\n * ```\n */\n afterAuth?: (options: AfterAuthOptions) => void | Promise;\n}" - }, - "AfterAuthOptions": { - "filePath": "/server/config-types.ts", - "name": "AfterAuthOptions", - "description": "", - "members": [ + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad0", + "value": 96 + }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "session", - "value": "Session", - "description": "" + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad1", + "value": 97 }, { - "filePath": "/server/config-types.ts", - "syntaxKind": "PropertySignature", - "name": "admin", - "value": "AdminApiContext", - "description": "" - } - ], - "value": "export interface AfterAuthOptions<\n R extends ShopifyRestResources = ShopifyRestResources,\n> {\n session: Session;\n admin: AdminApiContext;\n}" - }, - "AdminApiContext": { - "filePath": "/server/clients/admin/types.ts", - "name": "AdminApiContext", - "description": "", - "members": [ + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad2", + "value": 98 + }, { - "filePath": "/server/clients/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "rest", - "value": "RestClientWithResources", - "description": "Methods for interacting with the Shopify Admin REST API\n\nThere are methods for interacting with individual REST resources. You can also make `GET`, `POST`, `PUT` and `DELETE` requests should the REST resources not meet your needs.\n\n\n\n\n", - "examples": [ - { - "title": "Using REST resources", - "description": "Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n return json(admin.rest.resources.Order.count({ session }));\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" - } - ] - }, - { - "title": "Performing a GET request to the REST API", - "description": "Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = await admin.rest.get({ path: \"/customers/count.json\" });\n const customers = await response.json();\n return json({ customers });\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" - } - ] - }, - { - "title": "Performing a POST request to the REST API", - "description": "Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { admin, session } = await authenticate.admin(request);\n const response = admin.rest.post({\n path: \"customers/7392136888625/send_invite.json\",\n body: {\n customer_invite: {\n to: \"new_test_email@shopify.com\",\n from: \"j.limited@example.com\",\n bcc: [\"j.limited@example.com\"],\n subject: \"Welcome to my new shop\",\n custom_message: \"My awesome new store\",\n },\n },\n});\n const customerInvite = await response.json();\n return json({ customerInvite });\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" - } - ] - } - ] + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad3", + "value": 99 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad4", + "value": 100 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad5", + "value": 101 }, { - "filePath": "/server/clients/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "graphql", - "value": "GraphQLClient", - "description": "Methods for interacting with the Shopify Admin GraphQL API\n\n\n\n\n\n\n\n\n\n", - "examples": [ - { - "title": "Querying the GraphQL API", - "description": "Use `admin.graphql` to make query / mutation requests.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.admin(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", - "title": "Example" - } - ] - } - ] - } - ], - "value": "export interface AdminApiContext<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * Methods for interacting with the Shopify Admin REST API\n *\n * There are methods for interacting with individual REST resources. You can also make `GET`, `POST`, `PUT` and `DELETE` requests should the REST resources not meet your needs.\n *\n * {@link https://shopify.dev/docs/api/admin-rest}\n *\n * @example\n * Using REST resources.\n * Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource. \n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * return json(admin.rest.resources.Order.count({ session }));\n * };\n * ```\n *\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n *\n * @example\n * Performing a GET request to the REST API.\n * Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint\n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = await admin.rest.get({ path: \"/customers/count.json\" });\n * const customers = await response.json();\n * return json({ customers });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * @example\n * Performing a POST request to the REST API.\n * Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = admin.rest.post({\n * path: \"customers/7392136888625/send_invite.json\",\n * body: {\n * customer_invite: {\n * to: \"new_test_email@shopify.com\",\n * from: \"j.limited@example.com\",\n * bcc: [\"j.limited@example.com\"],\n * subject: \"Welcome to my new shop\",\n * custom_message: \"My awesome new store\",\n * },\n * },\n * });\n * const customerInvite = await response.json();\n * return json({ customerInvite });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n rest: RestClientWithResources;\n\n /**\n * Methods for interacting with the Shopify Admin GraphQL API\n *\n * {@link https://shopify.dev/docs/api/admin-graphql}\n * {@link https://github.com/Shopify/shopify-api-js/blob/main/packages/shopify-api/docs/reference/clients/Graphql.md}\n *\n * @example\n * Querying the GraphQL API.\n * Use `admin.graphql` to make query / mutation requests.\n * ```ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.admin(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n graphql: GraphQLClient;\n}" - }, - "RestClientWithResources": { - "filePath": "/server/clients/admin/rest.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "RestClientWithResources", - "value": "RemixRestClient & {resources: Resources}", - "description": "" - }, - "AppDistribution": { - "filePath": "/server/types.ts", - "syntaxKind": "EnumDeclaration", - "name": "AppDistribution", - "value": "export enum AppDistribution {\n AppStore = 'app_store',\n SingleMerchant = 'single_merchant',\n ShopifyAdmin = 'shopify_admin',\n}", - "members": [ + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad6", + "value": 102 + }, { - "filePath": "/server/types.ts", - "name": "AppStore", - "value": "app_store" + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad7", + "value": 103 }, { - "filePath": "/server/types.ts", - "name": "SingleMerchant", - "value": "single_merchant" + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad8", + "value": 104 }, { - "filePath": "/server/types.ts", - "name": "ShopifyAdmin", - "value": "shopify_admin" - } - ] - }, - "FutureFlags": { - "filePath": "/server/future/flags.ts", - "name": "FutureFlags", - "description": "", - "members": [ + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Numpad9", + "value": 105 + }, { - "filePath": "/server/future/flags.ts", - "syntaxKind": "PropertySignature", - "name": "v3_webhookAdminContext", - "value": "boolean", - "description": "When enabled, returns the same `admin` context (`AdminApiContext`) from `authenticate.webhook` that is returned from `authenticate.admin`.", - "isOptional": true, - "defaultValue": "false" + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Multiply", + "value": 106 }, { - "filePath": "/server/future/flags.ts", - "syntaxKind": "PropertySignature", - "name": "v3_authenticatePublic", - "value": "boolean", - "description": "When enabled authenticate.public() will not work. Use authenticate.public.checkout() instead.", - "isOptional": true, - "defaultValue": "false" - } - ], - "value": "export interface FutureFlags {\n /**\n * When enabled, returns the same `admin` context (`AdminApiContext`) from `authenticate.webhook` that is returned from `authenticate.admin`.\n *\n * @default false\n */\n v3_webhookAdminContext?: boolean;\n\n /**\n * When enabled authenticate.public() will not work. Use authenticate.public.checkout() instead.\n *\n * @default false\n */\n v3_authenticatePublic?: boolean;\n}" - }, - "ShopifyApp": { - "filePath": "/server/types.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "ShopifyApp", - "value": "Config['distribution'] extends AppDistribution.ShopifyAdmin\n ? AdminApp\n : Config['distribution'] extends AppDistribution.SingleMerchant\n ? SingleMerchantApp\n : Config['distribution'] extends AppDistribution.AppStore\n ? AppStoreApp\n : AppStoreApp", - "description": "An object your app can use to interact with Shopify.\n\nBy default, the app's distribution is `AppStore`." - }, - "AdminApp": { - "filePath": "/server/types.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "AdminApp", - "value": "ShopifyAppBase", - "description": "", - "members": [ + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Add", + "value": 107 + }, { - "filePath": "/server/types.ts", - "syntaxKind": "PropertySignature", - "name": "sessionStorage", - "value": "SessionStorageType", - "description": "The `SessionStorage` instance you passed in as a config option.", - "examples": [ - { - "title": "Storing sessions with Prisma", - "description": "Import the `@shopify/shopify-app-session-storage-prisma` package to store sessions in your Prisma database.", - "tabs": [ - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\nimport prisma from \"~/db.server\";\n\nconst shopify = shopifyApp({\n sesssionStorage: new PrismaSessionStorage(prisma),\n // ...etc\n})\n\n// shopify.sessionStorage is an instance of PrismaSessionStorage", - "title": "/app/shopify.server.ts" - } - ] - } - ] + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Subtract", + "value": 109 }, { - "filePath": "/server/types.ts", - "syntaxKind": "PropertySignature", - "name": "addDocumentResponseHeaders", - "value": "AddDocumentResponseHeaders", - "description": "Adds the required Content Security Policy headers for Shopify apps to the given Headers object.\n\n\n\n\n", - "examples": [ - { - "title": "Return headers on all requests", - "description": "Add headers to all HTML requests by calling `shopify.addDocumentResponseHeaders` in `entry.server.tsx`.", - "tabs": [ - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const addDocumentResponseheaders = shopify.addDocumentResponseheaders;", - "title": "~/shopify.server.ts" - }, - { - "code": "import { addDocumentResponseHeaders } from \"~/shopify.server\";\n\nexport default function handleRequest(\n request: Request,\n responseStatusCode: number,\n responseHeaders: Headers,\n remixContext: EntryContext\n) {\n const markup = renderToString(\n \n );\n\n responseHeaders.set(\"Content-Type\", \"text/html\");\n addDocumentResponseHeaders(request, responseHeaders);\n\n return new Response(\"\" + markup, {\n status: responseStatusCode,\n headers: responseHeaders,\n });\n}", - "title": "entry.server.tsx" - } - ] - } - ] + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Decimal", + "value": 110 }, { - "filePath": "/server/types.ts", - "syntaxKind": "PropertySignature", - "name": "registerWebhooks", - "value": "RegisterWebhooks", - "description": "Register webhook topics for a store using the given session. Most likely you want to use this in combination with the afterAuth hook.", - "examples": [ - { - "title": "Registering webhooks after install", - "description": "Trigger the registration to create the webhook subscriptions after a merchant installs your app using the `afterAuth` hook. Learn more about [subscribing to webhooks.](/docs/api/shopify-app-remix/v1/guide-webhooks)", - "tabs": [ - { - "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n hooks: {\n afterAuth: async ({ session }) => {\n shopify.registerWebhooks({ session });\n }\n },\n webhooks: {\n APP_UNINSTALLED: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: \"/webhooks\",\n },\n },\n // ...etc\n});", - "title": "/app/shopify.server.ts" - } - ] - } - ] + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Divide", + "value": 111 }, { - "filePath": "/server/types.ts", - "syntaxKind": "PropertySignature", - "name": "authenticate", - "value": "Authenticate", - "description": "Ways to authenticate requests from different surfaces across Shopify.", - "examples": [ - { - "title": "Authenticate Shopify requests", - "description": "Use the functions in `authenticate` to validate requests coming from Shopify.", - "tabs": [ - { - "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;", - "title": "/app/shopify.server.ts" - }, - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const {admin, session, sessionToken, billing} = shopify.authenticate.admin(request);\n\n return json(await admin.rest.resources.Product.count({ session }));\n}", - "title": "/app/routes/**\\/*.jsx" - } - ] - } - ] + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F1", + "value": 112 }, { - "filePath": "/server/types.ts", - "syntaxKind": "PropertySignature", - "name": "unauthenticated", - "value": "Unauthenticated>", - "description": "Ways to get Contexts from requests that do not originate from Shopify.", - "examples": [ - { - "title": "Using unauthenticated contexts", - "description": "Create contexts for requests that don't come from Shopify.", - "tabs": [ - { - "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;", - "title": "/app/shopify.server.ts" - }, - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticateExternal } from \"~/helpers/authenticate\"\nimport shopify from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const shop = await authenticateExternal(request)\n const {admin} = await shopify.unauthenticated.admin(shop);\n\n return json(await admin.rest.resources.Product.count({ session }));\n}", - "title": "/app/routes/**\\/*.jsx" - } - ] - } - ] - } - ] - }, - "SessionStorageType": { - "filePath": "/server/types.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "SessionStorageType", - "value": "Config['sessionStorage'] extends SessionStorage\n ? Config['sessionStorage']\n : SessionStorage", - "description": "" - }, - "AddDocumentResponseHeaders": { - "filePath": "/server/types.ts", - "name": "AddDocumentResponseHeaders", - "description": "", - "params": [ + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F2", + "value": 113 + }, { - "name": "request", - "description": "", - "value": "Request", - "filePath": "/server/types.ts" + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F3", + "value": 114 }, { - "name": "headers", - "description": "", - "value": "Headers", - "filePath": "/server/types.ts" - } - ], - "returns": { - "filePath": "/server/types.ts", - "description": "", - "name": "void", - "value": "void" - }, - "value": "type AddDocumentResponseHeaders = (request: Request, headers: Headers) => void;" - }, - "RegisterWebhooks": { - "filePath": "/server/types.ts", - "name": "RegisterWebhooks", - "description": "", - "params": [ + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F4", + "value": 115 + }, { - "name": "options", - "description": "", - "value": "RegisterWebhooksOptions", - "filePath": "/server/types.ts" - } - ], - "returns": { - "filePath": "/server/types.ts", - "description": "", - "name": "Promise", - "value": "Promise" - }, - "value": "type RegisterWebhooks = (\n options: RegisterWebhooksOptions,\n) => Promise;" - }, - "RegisterWebhooksOptions": { - "filePath": "/server/authenticate/webhooks/types.ts", - "name": "RegisterWebhooksOptions", - "description": "", - "members": [ + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F5", + "value": 116 + }, { - "filePath": "/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "session", - "value": "Session", - "description": "The Shopify session used to register webhooks using the Admin API." - } - ], - "value": "export interface RegisterWebhooksOptions {\n /**\n * The Shopify session used to register webhooks using the Admin API.\n */\n session: Session;\n}" - }, - "Authenticate": { - "filePath": "/server/types.ts", - "name": "Authenticate", - "description": "", - "members": [ + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F6", + "value": 117 + }, { - "filePath": "/server/types.ts", - "syntaxKind": "PropertySignature", - "name": "admin", - "value": "AuthenticateAdmin>", - "description": "Authenticate an admin Request and get back an authenticated admin context. Use the authenticated admin context to interact with Shopify.\n\nExamples of when to use this are requests from your app's UI, or requests from admin extensions.\n\nIf there is no session for the Request, this will redirect the merchant to correct auth flows.", - "examples": [ - { - "title": "Authenticating a request for an embedded app", - "description": "", - "tabs": [ - { - "code": "import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\nimport { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n\nconst shopify = shopifyApp({\n restResources,\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" - }, - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../../shopify.server\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const {admin, session, sessionToken, billing} = authenticate.admin(request);\n\n return json(await admin.rest.resources.Product.count({ session }));\n}", - "title": "/app/routes/**\\/*.jsx" - } - ] - } - ] + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F7", + "value": 118 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F8", + "value": 119 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F9", + "value": 120 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F10", + "value": 121 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F11", + "value": 122 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "F12", + "value": 123 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "NumLock", + "value": 144 }, { - "filePath": "/server/types.ts", - "syntaxKind": "PropertySignature", - "name": "public", - "value": "AuthenticatePublic", - "description": "Authenticate a public request and get back a session token.", - "examples": [ - { - "title": "Authenticating a request from a checkout extension", - "description": "", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../../shopify.server\";\nimport { getWidgets } from \"~/db/widgets\";\n\nexport async function loader({ request }: LoaderFunctionArgs) {\n const {sessionToken} = authenticate.public.checkout(request);\n\n return json(await getWidgets(sessionToken));\n}", - "title": "/app/routes/api/checkout.jsx" - } - ] - } - ] + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "ScrollLock", + "value": 145 }, { - "filePath": "/server/types.ts", - "syntaxKind": "PropertySignature", - "name": "webhook", - "value": "AuthenticateWebhook<\n Config['future'],\n RestResourcesType,\n keyof Config['webhooks'] | MandatoryTopics\n >", - "description": "Authenticate a Shopify webhook request, get back an authenticated admin context and details on the webhook request", - "examples": [ - { - "title": "Authenticating a webhook request", - "description": "", - "tabs": [ - { - "code": "import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n webhooks: {\n APP_UNINSTALLED: {\n deliveryMethod: DeliveryMethod.Http,\n callbackUrl: \"/webhooks\",\n },\n },\n hooks: {\n afterAuth: async ({ session }) => {\n shopify.registerWebhooks({ session });\n },\n },\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "/app/shopify.server.ts" - }, - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport db from \"../db.server\";\n\nexport const action = async ({ request }: ActionFunctionArgs) => {\n const { topic, shop, session } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n if (session) {\n await db.session.deleteMany({ where: { shop } });\n }\n break;\n case \"CUSTOMERS_DATA_REQUEST\":\n case \"CUSTOMERS_REDACT\":\n case \"SHOP_REDACT\":\n default:\n throw new Response(\"Unhandled webhook topic\", { status: 404 });\n }\n\n throw new Response();\n};", - "title": "/app/routes/webhooks.ts" - } - ] - } - ] - } - ], - "value": "interface Authenticate {\n /**\n * Authenticate an admin Request and get back an authenticated admin context. Use the authenticated admin context to interact with Shopify.\n *\n * Examples of when to use this are requests from your app's UI, or requests from admin extensions.\n *\n * If there is no session for the Request, this will redirect the merchant to correct auth flows.\n *\n * @example\n * Authenticating a request for an embedded app.\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * ```ts\n * // /app/routes/**\\/*.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const {admin, session, sessionToken, billing} = authenticate.admin(request);\n *\n * return json(await admin.rest.resources.Product.count({ session }));\n * }\n * ```\n */\n admin: AuthenticateAdmin>;\n\n /**\n * Authenticate a public request and get back a session token.\n *\n * @example\n * Authenticating a request from a checkout extension\n *\n * ```ts\n * // /app/routes/api/checkout.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../../shopify.server\";\n * import { getWidgets } from \"~/db/widgets\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const {sessionToken} = authenticate.public.checkout(request);\n *\n * return json(await getWidgets(sessionToken));\n * }\n * ```\n */\n public: AuthenticatePublic;\n\n /**\n * Authenticate a Shopify webhook request, get back an authenticated admin context and details on the webhook request\n *\n * @example\n * Authenticating a webhook request\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * webhooks: {\n * APP_UNINSTALLED: {\n * deliveryMethod: DeliveryMethod.Http,\n * callbackUrl: \"/webhooks\",\n * },\n * },\n * hooks: {\n * afterAuth: async ({ session }) => {\n * shopify.registerWebhooks({ session });\n * },\n * },\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * ```ts\n * // /app/routes/webhooks.ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import db from \"../db.server\";\n *\n * export const action = async ({ request }: ActionFunctionArgs) => {\n * const { topic, shop, session } = await authenticate.webhook(request);\n *\n * switch (topic) {\n * case \"APP_UNINSTALLED\":\n * if (session) {\n * await db.session.deleteMany({ where: { shop } });\n * }\n * break;\n * case \"CUSTOMERS_DATA_REQUEST\":\n * case \"CUSTOMERS_REDACT\":\n * case \"SHOP_REDACT\":\n * default:\n * throw new Response(\"Unhandled webhook topic\", { status: 404 });\n * }\n *\n * throw new Response();\n * };\n * ```\n */\n webhook: AuthenticateWebhook<\n Config['future'],\n RestResourcesType,\n keyof Config['webhooks'] | MandatoryTopics\n >;\n}" - }, - "AuthenticateAdmin": { - "filePath": "/server/types.ts", - "name": "AuthenticateAdmin", - "description": "", - "params": [ + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Semicolon", + "value": 186 + }, { - "name": "request", - "description": "", - "value": "Request", - "filePath": "/server/types.ts" - } - ], - "returns": { - "filePath": "/server/types.ts", - "description": "", - "name": "Promise>", - "value": "Promise>" - }, - "value": "type AuthenticateAdmin<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> = (request: Request) => Promise>;" - }, - "AdminContext": { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "AdminContext", - "value": "Config['isEmbeddedApp'] extends false\n ? NonEmbeddedAdminContext\n : EmbeddedAdminContext", - "description": "" - }, - "NonEmbeddedAdminContext": { - "filePath": "/server/authenticate/admin/types.ts", - "name": "NonEmbeddedAdminContext", - "description": "", - "members": [ + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Equals", + "value": 187 + }, { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "session", - "value": "Session", - "description": "The session for the user who made the request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nUse this to get shop or user-specific data.", - "examples": [ - { - "title": "Using offline sessions", - "description": "Get your app's shop-specific data using an offline session.", - "tabs": [ - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - }, - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({shop: session.shop));\n};", - "title": "/app/routes/**\\/*.ts" - } - ] - }, - { - "title": "Using online sessions", - "description": "Get your app's user-specific data using an online session.", - "tabs": [ - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - }, - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({user: session.onlineAccessInfo!.id}));\n};", - "title": "/app/routes/**\\/*.ts" - } - ] - } - ] + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Comma", + "value": 188 }, { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "admin", - "value": "AdminApiContext", - "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request." + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Dash", + "value": 189 }, { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "billing", - "value": "BillingContext", - "description": "Billing methods for this store, based on the plans defined in the `billing` config option.\n\n\n\n\n" + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "Period", + "value": 190 }, { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "cors", - "value": "EnsureCORSFunction", - "description": "A function that ensures the CORS headers are set correctly for the response.", - "examples": [ - { - "title": "Setting CORS headers for a admin request", - "description": "Use the `cors` helper to ensure your app can respond to requests from admin extensions.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, cors } = await authenticate.admin(request);\n return cors(json(await getMyAppData({user: session.onlineAccessInfo!.id})));\n};", - "title": "/app/routes/admin/my-route.ts" - } - ] - } - ] + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "ForwardSlash", + "value": 191 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "GraveAccent", + "value": 192 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "OpenBracket", + "value": 219 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "BackSlash", + "value": 220 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "CloseBracket", + "value": 221 + }, + { + "filePath": "../../node_modules/@shopify/polaris/build/ts/src/types.d.ts", + "name": "SingleQuote", + "value": 222 } - ], - "value": "export interface NonEmbeddedAdminContext<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends AdminContextInternal {}" + ] }, - "BillingContext": { - "filePath": "/server/authenticate/admin/billing/types.ts", - "name": "BillingContext", + "ShopifyClients": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "ShopifyClients", "description": "", "members": [ { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", "syntaxKind": "PropertySignature", - "name": "require", - "value": "(options: RequireBillingOptions) => Promise", - "description": "Checks if the shop has an active payment for any plan defined in the `billing` config option.", - "examples": [ - { - "title": "Requesting billing right away", - "description": "Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n isTest: true,\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n // App logic\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] - }, - { - "title": "Using a plan selection page", - "description": "When the app has multiple plans, create a page in your App that allows the merchant to select a plan. If a merchant does not have the required plan you can redirect them to page in your app to select one.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n isTest: true,\n onFailure: () => redirect('/select-plan'),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n\n // App logic\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] - } - ] + "name": "Rest", + "value": "typeof RestClient", + "description": "" }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", "syntaxKind": "PropertySignature", - "name": "request", - "value": "(options: RequestBillingOptions) => Promise", - "description": "Requests payment for the plan.", - "examples": [ - { - "title": "Using a custom return URL", - "description": "Change where the merchant is returned to after approving the purchase using the `returnUrl` option.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({\n plan: MONTHLY_PLAN,\n isTest: true,\n returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n }),\n });\n\n // App logic\n};", - "title": "/app/routes/**\\/*.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] - } - ] + "name": "Graphql", + "value": "typeof GraphqlClient", + "description": "" }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", "syntaxKind": "PropertySignature", - "name": "cancel", - "value": "(options: CancelBillingOptions) => Promise", - "description": "Cancels an ongoing subscription, given its ID.", - "examples": [ - { - "title": "Cancelling a subscription", - "description": "Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs } from \"@remix-run/node\";\nimport { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { billing } = await authenticate.admin(request);\n const billingCheck = await billing.require({\n plans: [MONTHLY_PLAN],\n onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n });\n\n const subscription = billingCheck.appSubscriptions[0];\n const cancelledSubscription = await billing.cancel({\n subscriptionId: subscription.id,\n isTest: true,\n prorate: true,\n });\n\n // App logic\n};", - "title": "/app/routes/cancel-subscription.ts" - }, - { - "code": "import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n\nexport const MONTHLY_PLAN = 'Monthly subscription';\nexport const ANNUAL_PLAN = 'Annual subscription';\n\nconst shopify = shopifyApp({\n // ...etc\n billing: {\n [MONTHLY_PLAN]: {\n amount: 5,\n currencyCode: 'USD',\n interval: BillingInterval.Every30Days,\n },\n [ANNUAL_PLAN]: {\n amount: 50,\n currencyCode: 'USD',\n interval: BillingInterval.Annual,\n },\n }\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - } - ] - } - ] + "name": "Storefront", + "value": "typeof StorefrontClient", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "graphqlProxy", + "value": "GraphqlProxy", + "description": "" } ], - "value": "export interface BillingContext {\n /**\n * Checks if the shop has an active payment for any plan defined in the `billing` config option.\n *\n * @returns A promise that resolves to an object containing the active purchases for the shop.\n *\n * @example\n * Requesting billing right away.\n * Call `billing.request` in the `onFailure` callback to immediately redirect to the Shopify page to request payment.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * isTest: true,\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n * @example\n * Using a plan selection page.\n * When the app has multiple plans, create a page in your App that allows the merchant to select a plan. If a merchant does not have the required plan you can redirect them to page in your app to select one.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, redirect } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN, ANNUAL_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN, ANNUAL_PLAN],\n * isTest: true,\n * onFailure: () => redirect('/select-plan'),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * console.log(`Shop is on ${subscription.name} (id ${subscription.id})`);\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n require: (\n options: RequireBillingOptions,\n ) => Promise;\n\n /**\n * Requests payment for the plan.\n *\n * @returns Redirects to the confirmation URL for the payment.\n *\n * @example\n * Using a custom return URL.\n * Change where the merchant is returned to after approving the purchase using the `returnUrl` option.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({\n * plan: MONTHLY_PLAN,\n * isTest: true,\n * returnUrl: 'https://admin.shopify.com/store/my-store/apps/my-app/billing-page',\n * }),\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n request: (options: RequestBillingOptions) => Promise;\n\n /**\n * Cancels an ongoing subscription, given its ID.\n *\n * @returns The cancelled subscription.\n *\n * @example\n * Cancelling a subscription.\n * Use the `billing.cancel` function to cancel an active subscription with the id returned from `billing.require`.\n * ```ts\n * // /app/routes/cancel-subscription.ts\n * import { LoaderFunctionArgs } from \"@remix-run/node\";\n * import { authenticate, MONTHLY_PLAN } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { billing } = await authenticate.admin(request);\n * const billingCheck = await billing.require({\n * plans: [MONTHLY_PLAN],\n * onFailure: async () => billing.request({ plan: MONTHLY_PLAN }),\n * });\n *\n * const subscription = billingCheck.appSubscriptions[0];\n * const cancelledSubscription = await billing.cancel({\n * subscriptionId: subscription.id,\n * isTest: true,\n * prorate: true,\n * });\n *\n * // App logic\n * };\n * ```\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp, BillingInterval } from \"@shopify/shopify-app-remix/server\";\n *\n * export const MONTHLY_PLAN = 'Monthly subscription';\n * export const ANNUAL_PLAN = 'Annual subscription';\n *\n * const shopify = shopifyApp({\n * // ...etc\n * billing: {\n * [MONTHLY_PLAN]: {\n * amount: 5,\n * currencyCode: 'USD',\n * interval: BillingInterval.Every30Days,\n * },\n * [ANNUAL_PLAN]: {\n * amount: 50,\n * currencyCode: 'USD',\n * interval: BillingInterval.Annual,\n * },\n * }\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n cancel: (options: CancelBillingOptions) => Promise;\n}" + "value": "export interface ShopifyClients {\n Rest: typeof RestClient;\n Graphql: typeof GraphqlClient;\n Storefront: typeof StorefrontClient;\n graphqlProxy: GraphqlProxy;\n}" }, - "RequireBillingOptions": { - "filePath": "/server/authenticate/admin/billing/types.ts", - "name": "RequireBillingOptions", + "GraphqlClient": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/graphql/client.d.ts", + "name": "GraphqlClient", "description": "", "members": [ { - "filePath": "/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "plans", - "value": "(keyof Config[\"billing\"])[]", - "description": "The plans to check for. Must be one of the values defined in the `billing` config option." + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/graphql/client.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "session", + "value": "Session", + "description": "" }, { - "filePath": "/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "onFailure", - "value": "(error: any) => Promise", - "description": "How to handle the request if the shop doesn't have an active payment for any plan." + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/graphql/client.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "client", + "value": "AdminApiClient", + "description": "" }, { - "filePath": "/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "isTest", - "value": "boolean", - "description": "", - "isOptional": true + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/graphql/client.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "apiVersion", + "value": "ApiVersion", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/graphql/client.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "query", + "value": "(params: GraphqlParams) => Promise>", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/admin/graphql/client.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "request", + "value": "(operation: Operation, options?: GraphqlQueryOptions) => Promise : T>>", + "description": "" } ], - "value": "export interface RequireBillingOptions\n extends Omit {\n /**\n * The plans to check for. Must be one of the values defined in the `billing` config option.\n */\n plans: (keyof Config['billing'])[];\n /**\n * How to handle the request if the shop doesn't have an active payment for any plan.\n */\n onFailure: (error: any) => Promise;\n}" + "value": "export declare class GraphqlClient {\n static config: ConfigInterface;\n readonly session: Session;\n readonly client: AdminApiClient;\n readonly apiVersion?: ApiVersion;\n constructor(params: GraphqlClientParams);\n query(params: GraphqlParams): Promise>;\n request(operation: Operation, options?: GraphqlQueryOptions): Promise : T>>;\n private graphqlClass;\n}" }, - "RequestBillingOptions": { - "filePath": "/server/authenticate/admin/billing/types.ts", - "name": "RequestBillingOptions", + "GraphqlParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "GraphqlParams", + "value": "Omit", + "description": "", + "members": [] + }, + "RequestReturn": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "RequestReturn", "description": "", "members": [ { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", "syntaxKind": "PropertySignature", - "name": "plan", - "value": "keyof Config[\"billing\"]", - "description": "The plan to request. Must be one of the values defined in the `billing` config option." - }, - { - "filePath": "/server/authenticate/admin/billing/types.ts", - "syntaxKind": "PropertySignature", - "name": "isTest", - "value": "boolean", - "description": "Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.", - "isOptional": true + "name": "body", + "value": "T", + "description": "" }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", "syntaxKind": "PropertySignature", - "name": "returnUrl", - "value": "string", - "description": "The URL to return to after the merchant approves the payment.", - "isOptional": true + "name": "headers", + "value": "Headers", + "description": "" } ], - "value": "export interface RequestBillingOptions\n extends Omit {\n /**\n * The plan to request. Must be one of the values defined in the `billing` config option.\n */\n plan: keyof Config['billing'];\n /**\n * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.\n */\n isTest?: boolean;\n /**\n * The URL to return to after the merchant approves the payment.\n */\n returnUrl?: string;\n}" + "value": "export interface RequestReturn {\n body: T;\n headers: Headers;\n}" }, - "CancelBillingOptions": { - "filePath": "/server/authenticate/admin/billing/types.ts", - "name": "CancelBillingOptions", + "GraphqlQueryOptions": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "GraphqlQueryOptions", "description": "", "members": [ { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", "syntaxKind": "PropertySignature", - "name": "subscriptionId", - "value": "string", - "description": "The ID of the subscription to cancel." + "name": "variables", + "value": "ApiClientRequestOptions[\"variables\"]", + "description": "", + "isOptional": true }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", "syntaxKind": "PropertySignature", - "name": "prorate", - "value": "boolean", - "description": "Whether to prorate the cancellation.\n\n\n\n\n", + "name": "headers", + "value": "Record", + "description": "", "isOptional": true }, { - "filePath": "/server/authenticate/admin/billing/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", "syntaxKind": "PropertySignature", - "name": "isTest", - "value": "boolean", + "name": "retries", + "value": "number", "description": "", "isOptional": true } ], - "value": "export interface CancelBillingOptions {\n /**\n * The ID of the subscription to cancel.\n */\n subscriptionId: string;\n /**\n * Whether to prorate the cancellation.\n *\n * {@link https://shopify.dev/docs/apps/billing/subscriptions/cancel-recurring-charges}\n */\n prorate?: boolean;\n /*\n * Whether to use the test mode. This prevents the credit card from being charged. Test shops and demo shops cannot be charged.\n */\n isTest?: boolean;\n}" + "value": "export interface GraphqlQueryOptions {\n variables?: ApiClientRequestOptions['variables'];\n headers?: Record;\n retries?: number;\n}" }, - "EmbeddedAdminContext": { - "filePath": "/server/authenticate/admin/types.ts", - "name": "EmbeddedAdminContext", + "StorefrontClient": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/storefront/client.d.ts", + "name": "StorefrontClient", "description": "", "members": [ { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "sessionToken", - "value": "JwtPayload", - "description": "The decoded and validated session token for the request.\n\nReturned only if `isEmbeddedApp` is `true`.\n\n\n\n\n", - "examples": [ - { - "title": "Using the decoded session token", - "description": "Get user-specific data using the `sessionToken` object.", - "tabs": [ - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - }, - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.public.checkout(\n request\n );\n return json(await getMyAppData({user: sessionToken.sub}));\n};", - "title": "/app/routes/**\\/*.ts" - } - ] - } - ] - }, - { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "redirect", - "value": "RedirectFunction", - "description": "A function that redirects the user to a new page, ensuring that the appropriate parameters are set for embedded apps.\n\nReturned only if `isEmbeddedApp` is `true`.", - "examples": [ - { - "title": "Redirecting to an app route", - "description": "Use the `redirect` helper to safely redirect between pages.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, redirect } = await authenticate.admin(request);\n return redirect(\"/\");\n};", - "title": "/app/routes/admin/my-route.ts" - } - ] - }, - { - "title": "Redirecting outside of Shopify admin", - "description": "Pass in a `target` option of `_top` or `_parent` to go to an external URL.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, redirect } = await authenticate.admin(request);\n return redirect(\"/\", { target: '_parent' });\n};", - "title": "/app/routes/admin/my-route.ts" - } - ] - } - ] - }, - { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/storefront/client.d.ts", + "syntaxKind": "PropertyDeclaration", "name": "session", "value": "Session", - "description": "The session for the user who made the request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nUse this to get shop or user-specific data.", - "examples": [ - { - "title": "Using offline sessions", - "description": "Get your app's shop-specific data using an offline session.", - "tabs": [ - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - }, - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({shop: session.shop));\n};", - "title": "/app/routes/**\\/*.ts" - } - ] - }, - { - "title": "Using online sessions", - "description": "Get your app's user-specific data using an online session.", - "tabs": [ - { - "code": "import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n\nconst shopify = shopifyApp({\n // ...etc\n useOnlineTokens: true,\n});\nexport default shopify;\nexport const authenticate = shopify.authenticate;", - "title": "shopify.server.ts" - }, - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session } = await authenticate.admin(request);\n return json(await getMyAppData({user: session.onlineAccessInfo!.id}));\n};", - "title": "/app/routes/**\\/*.ts" - } - ] - } - ] + "description": "" }, { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "admin", - "value": "AdminApiContext", - "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request." + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/storefront/client.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "client", + "value": "StorefrontApiClient", + "description": "" }, { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "billing", - "value": "BillingContext", - "description": "Billing methods for this store, based on the plans defined in the `billing` config option.\n\n\n\n\n" + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/storefront/client.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "apiVersion", + "value": "ApiVersion", + "description": "" }, { - "filePath": "/server/authenticate/admin/types.ts", - "syntaxKind": "PropertySignature", - "name": "cors", - "value": "EnsureCORSFunction", - "description": "A function that ensures the CORS headers are set correctly for the response.", - "examples": [ - { - "title": "Setting CORS headers for a admin request", - "description": "Use the `cors` helper to ensure your app can respond to requests from admin extensions.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { session, cors } = await authenticate.admin(request);\n return cors(json(await getMyAppData({user: session.onlineAccessInfo!.id})));\n};", - "title": "/app/routes/admin/my-route.ts" - } - ] - } - ] + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/storefront/client.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "query", + "value": "(params: GraphqlParams) => Promise>", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/storefront/client.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "request", + "value": "(operation: Operation, options?: GraphqlQueryOptions) => Promise : T>>", + "description": "" } ], - "value": "export interface EmbeddedAdminContext<\n Config extends AppConfigArg,\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends AdminContextInternal {\n /**\n * The decoded and validated session token for the request.\n *\n * Returned only if `isEmbeddedApp` is `true`.\n *\n * {@link https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload}\n *\n * @example\n * Using the decoded session token.\n * Get user-specific data using the `sessionToken` object.\n * ```ts\n * // shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * useOnlineTokens: true,\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken } = await authenticate.public.checkout(\n * request\n * );\n * return json(await getMyAppData({user: sessionToken.sub}));\n * };\n * ```\n */\n sessionToken: JwtPayload;\n\n /**\n * A function that redirects the user to a new page, ensuring that the appropriate parameters are set for embedded\n * apps.\n *\n * Returned only if `isEmbeddedApp` is `true`.\n *\n * @example\n * Redirecting to an app route.\n * Use the `redirect` helper to safely redirect between pages.\n * ```ts\n * // /app/routes/admin/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { session, redirect } = await authenticate.admin(request);\n * return redirect(\"/\");\n * };\n * ```\n *\n * @example\n * Redirecting outside of Shopify admin.\n * Pass in a `target` option of `_top` or `_parent` to go to an external URL.\n * ```ts\n * // /app/routes/admin/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { session, redirect } = await authenticate.admin(request);\n * return redirect(\"/\", { target: '_parent' });\n * };\n * ```\n */\n redirect: RedirectFunction;\n}" + "value": "export declare class StorefrontClient {\n static config: ConfigInterface;\n readonly session: Session;\n readonly client: StorefrontApiClient;\n readonly apiVersion?: ApiVersion;\n constructor(params: GraphqlClientParams);\n query(params: GraphqlParams): Promise>;\n request(operation: Operation, options?: GraphqlQueryOptions): Promise : T>>;\n private storefrontClass;\n}" }, - "RedirectFunction": { - "filePath": "/server/authenticate/admin/helpers/redirect.ts", - "name": "RedirectFunction", + "GraphqlProxy": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/graphql_proxy/types.d.ts", + "name": "GraphqlProxy", "description": "", "params": [ { - "name": "url", - "description": "", - "value": "string", - "filePath": "/server/authenticate/admin/helpers/redirect.ts" - }, - { - "name": "init", + "name": "params", "description": "", - "value": "RedirectInit", - "isOptional": true, - "filePath": "/server/authenticate/admin/helpers/redirect.ts" + "value": "GraphqlProxyParams", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/graphql_proxy/types.d.ts" } ], "returns": { - "filePath": "/server/authenticate/admin/helpers/redirect.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/graphql_proxy/types.d.ts", "description": "", - "name": "TypedResponse", - "value": "TypedResponse" + "name": "Promise", + "value": "Promise" }, - "value": "export type RedirectFunction = (\n url: string,\n init?: RedirectInit,\n) => TypedResponse;" + "value": "export type GraphqlProxy = (params: GraphqlProxyParams) => Promise;" }, - "RedirectInit": { - "filePath": "/server/authenticate/admin/helpers/redirect.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "RedirectInit", - "value": "number | (ResponseInit & {target?: RedirectTarget})", - "description": "" + "GraphqlProxyParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/graphql_proxy/types.d.ts", + "name": "GraphqlProxyParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/graphql_proxy/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/graphql_proxy/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawBody", + "value": "string | Record", + "description": "" + } + ], + "value": "export interface GraphqlProxyParams {\n session: Session;\n rawBody: string | Record;\n}" }, - "RedirectTarget": { - "filePath": "/server/authenticate/admin/helpers/redirect.ts", + "ShopifyAuth": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/index.d.ts", "syntaxKind": "TypeAliasDeclaration", - "name": "RedirectTarget", - "value": "'_self' | '_parent' | '_top'", + "name": "ShopifyAuth", + "value": "{\n begin: OAuthBegin;\n callback: OAuthCallback;\n nonce: Nonce;\n safeCompare: SafeCompare;\n getEmbeddedAppUrl: GetEmbeddedAppUrl;\n buildEmbeddedAppUrl: BuildEmbeddedAppUrl;\n} & (FeatureEnabled extends true ? {\n tokenExchange: TokenExchange;\n} : Record)", "description": "" }, - "RestResourcesType": { - "filePath": "/server/types.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "RestResourcesType", - "value": "Config['restResources'] extends ShopifyRestResources\n ? Config['restResources']\n : ShopifyRestResources", - "description": "" + "OAuthBegin": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/oauth.d.ts", + "name": "OAuthBegin", + "description": "", + "params": [ + { + "name": "beginParams", + "description": "", + "value": "BeginParams", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/oauth.d.ts" + } + ], + "returns": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/oauth.d.ts", + "description": "", + "name": "Promise", + "value": "Promise" + }, + "value": "export type OAuthBegin = (beginParams: BeginParams) => Promise;" }, - "AuthenticatePublic": { - "filePath": "/server/authenticate/public/types.ts", + "BeginParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "name": "BeginParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "shop", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "callbackPath", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "isOnline", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawRequest", + "value": "AdapterRequest", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawResponse", + "value": "AdapterResponse", + "description": "", + "isOptional": true + } + ], + "value": "export interface BeginParams extends AdapterArgs {\n shop: string;\n callbackPath: string;\n isOnline: boolean;\n}" + }, + "AdapterRequest": { + "filePath": "../../node_modules/@shopify/shopify-api/runtime/http/types.d.ts", "syntaxKind": "TypeAliasDeclaration", - "name": "AuthenticatePublic", - "value": "FeatureEnabled extends true\n ? AuthenticatePublicObject\n : AuthenticatePublicLegacy", + "name": "AdapterRequest", + "value": "any", "description": "" }, - "FeatureEnabled": { - "filePath": "/server/future/flags.ts", + "AdapterResponse": { + "filePath": "../../node_modules/@shopify/shopify-api/runtime/http/types.d.ts", "syntaxKind": "TypeAliasDeclaration", - "name": "FeatureEnabled", - "value": "Future extends FutureFlags\n ? Future[Flag] extends true\n ? true\n : false\n : false", + "name": "AdapterResponse", + "value": "any", "description": "" }, - "AuthenticatePublicObject": { - "filePath": "/server/authenticate/public/types.ts", - "name": "AuthenticatePublicObject", - "description": "", - "members": [ - { - "filePath": "/server/authenticate/public/types.ts", - "syntaxKind": "PropertySignature", - "name": "checkout", - "value": "AuthenticateCheckout", - "description": "Authenticate a request from a checkout extension", - "examples": [ - { - "title": "Authenticating a checkout extension request", - "description": "", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken, cors } = await authenticate.public.checkout(\n request,\n );\n return cors(json({my: \"data\", shop: sessionToken.dest}));\n};", - "title": "/app/routes/public/widgets.ts" - } - ] - } - ] + "OAuthCallback": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/oauth.d.ts", + "name": "OAuthCallback", + "description": "", + "params": [ + { + "name": "callbackParams", + "description": "", + "value": "CallbackParams", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/oauth.d.ts" + } + ], + "returns": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/oauth.d.ts", + "description": "", + "name": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}", + "value": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}" + }, + "value": "export type OAuthCallback = (callbackParams: CallbackParams) => Promise>;" + }, + "CallbackParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "name": "CallbackParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawRequest", + "value": "AdapterRequest", + "description": "" }, { - "filePath": "/server/authenticate/public/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", "syntaxKind": "PropertySignature", - "name": "appProxy", - "value": "AuthenticateAppProxy", - "description": "Authenticate a request from an app proxy", - "examples": [ - { - "title": "Authenticating an app proxy request", - "description": "", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n await authenticate.public.appProxy(\n request,\n );\n\n const {searchParams} = new URL(request.url);\n const shop = searchParams.get(\"shop\");\n const customerId = searchParams.get(\"logged_in_customer_id\")\n\n return json({my: \"data\", shop, customerId});\n};", - "title": "/app/routes/public/widgets.ts" - } - ] - } - ] + "name": "rawResponse", + "value": "AdapterResponse", + "description": "", + "isOptional": true } ], - "value": "export interface AuthenticatePublicObject {\n /**\n * Authenticate a request from a checkout extension\n *\n * @example\n * Authenticating a checkout extension request\n * ```ts\n * // /app/routes/public/widgets.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken, cors } = await authenticate.public.checkout(\n * request,\n * );\n * return cors(json({my: \"data\", shop: sessionToken.dest}));\n * };\n * ```\n */\n checkout: AuthenticateCheckout;\n\n /**\n * Authenticate a request from an app proxy\n *\n * @example\n * Authenticating an app proxy request\n * ```ts\n * // /app/routes/public/widgets.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * await authenticate.public.appProxy(\n * request,\n * );\n *\n * const {searchParams} = new URL(request.url);\n * const shop = searchParams.get(\"shop\");\n * const customerId = searchParams.get(\"logged_in_customer_id\")\n *\n * return json({my: \"data\", shop, customerId});\n * };\n * ```\n */\n appProxy: AuthenticateAppProxy;\n}" + "value": "export interface CallbackParams extends AdapterArgs {\n}" }, - "AuthenticateCheckout": { - "filePath": "/server/authenticate/public/checkout/types.ts", - "name": "AuthenticateCheckout", + "Nonce": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/nonce.d.ts", + "name": "Nonce", + "description": "", + "params": [], + "returns": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/nonce.d.ts", + "description": "", + "name": "string", + "value": "string" + }, + "value": "export type Nonce = () => string;" + }, + "SafeCompare": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/safe-compare.d.ts", + "name": "SafeCompare", "description": "", "params": [ { - "name": "request", + "name": "strA", "description": "", - "value": "Request", - "filePath": "/server/authenticate/public/checkout/types.ts" + "value": "string | string[] | Record | number[]", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/safe-compare.d.ts" }, { - "name": "options", + "name": "strB", "description": "", - "value": "AuthenticateCheckoutOptions", - "isOptional": true, - "filePath": "/server/authenticate/public/checkout/types.ts" + "value": "string | string[] | Record | number[]", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/safe-compare.d.ts" } ], "returns": { - "filePath": "/server/authenticate/public/checkout/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/safe-compare.d.ts", "description": "", - "name": "Promise", - "value": "Promise" + "name": "boolean", + "value": "boolean" }, - "value": "export type AuthenticateCheckout = (\n request: Request,\n options?: AuthenticateCheckoutOptions,\n) => Promise;" + "value": "export type SafeCompare = (strA: string | Record | string[] | number[], strB: string | Record | string[] | number[]) => boolean;" }, - "AuthenticateCheckoutOptions": { - "filePath": "/server/authenticate/public/checkout/types.ts", - "name": "AuthenticateCheckoutOptions", + "GetEmbeddedAppUrl": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/get-embedded-app-url.d.ts", + "name": "GetEmbeddedAppUrl", "description": "", - "members": [ + "params": [ { - "filePath": "/server/authenticate/public/checkout/types.ts", - "syntaxKind": "PropertySignature", - "name": "corsHeaders", - "value": "string[]", + "name": "params", "description": "", - "isOptional": true + "value": "GetEmbeddedAppUrlParams", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/get-embedded-app-url.d.ts" } ], - "value": "export interface AuthenticateCheckoutOptions {\n corsHeaders?: string[];\n}" + "returns": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/get-embedded-app-url.d.ts", + "description": "", + "name": "Promise", + "value": "Promise" + }, + "value": "export type GetEmbeddedAppUrl = (params: GetEmbeddedAppUrlParams) => Promise;" }, - "CheckoutContext": { - "filePath": "/server/authenticate/public/checkout/types.ts", - "name": "CheckoutContext", - "description": "Authenticated Context for a checkout request", + "GetEmbeddedAppUrlParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/types.d.ts", + "name": "GetEmbeddedAppUrlParams", + "description": "", "members": [ { - "filePath": "/server/authenticate/public/checkout/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/types.d.ts", "syntaxKind": "PropertySignature", - "name": "sessionToken", - "value": "JwtPayload", - "description": "The decoded and validated session token for the request\n\nRefer to the OAuth docs for the [session token payload](https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload).", - "examples": [ - { - "title": "Using the decoded session token", - "description": "Get store-specific data using the `sessionToken` object.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken } = await authenticate.public.checkout(\n request\n );\n return json(await getMyAppData({shop: sessionToken.dest}));\n};", - "title": "app/routes/public/my-route.ts" - } - ] - } - ] + "name": "rawRequest", + "value": "AdapterRequest", + "description": "" }, { - "filePath": "/server/authenticate/public/checkout/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/types.d.ts", "syntaxKind": "PropertySignature", - "name": "cors", - "value": "EnsureCORSFunction", - "description": "A function that ensures the CORS headers are set correctly for the response.", - "examples": [ - { - "title": "Setting CORS headers for a public request", - "description": "Use the `cors` helper to ensure your app can respond to checkout extension requests.", - "tabs": [ - { - "code": "import { LoaderFunctionArgs, json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }: LoaderFunctionArgs) => {\n const { sessionToken, cors } = await authenticate.public.checkout(\n request,\n { corsHeaders: [\"X-My-Custom-Header\"] }\n );\n const data = await getMyAppData({shop: sessionToken.dest});\n return cors(json(data));\n};", - "title": "app/routes/public/my-route.ts" - } - ] - } - ] + "name": "rawResponse", + "value": "AdapterResponse", + "description": "", + "isOptional": true } ], - "value": "export interface CheckoutContext {\n /**\n * The decoded and validated session token for the request\n *\n * Refer to the OAuth docs for the [session token payload](https://shopify.dev/docs/apps/auth/oauth/session-tokens#payload).\n *\n * @example\n * Using the decoded session token.\n * Get store-specific data using the `sessionToken` object.\n * ```ts\n * // app/routes/public/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken } = await authenticate.public.checkout(\n * request\n * );\n * return json(await getMyAppData({shop: sessionToken.dest}));\n * };\n * ```\n */\n sessionToken: JwtPayload;\n\n /**\n * A function that ensures the CORS headers are set correctly for the response.\n *\n * @example\n * Setting CORS headers for a public request.\n * Use the `cors` helper to ensure your app can respond to checkout extension requests.\n * ```ts\n * // app/routes/public/my-route.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { sessionToken, cors } = await authenticate.public.checkout(\n * request,\n * { corsHeaders: [\"X-My-Custom-Header\"] }\n * );\n * const data = await getMyAppData({shop: sessionToken.dest});\n * return cors(json(data));\n * };\n * ```\n */\n cors: EnsureCORSFunction;\n}" + "value": "export interface GetEmbeddedAppUrlParams extends AdapterArgs {\n}" }, - "AuthenticateAppProxy": { - "filePath": "/server/authenticate/public/appProxy/types.ts", - "name": "AuthenticateAppProxy", + "BuildEmbeddedAppUrl": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/get-embedded-app-url.d.ts", + "name": "BuildEmbeddedAppUrl", "description": "", "params": [ { - "name": "request", + "name": "host", "description": "", - "value": "Request", - "filePath": "/server/authenticate/public/appProxy/types.ts" + "value": "string", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/get-embedded-app-url.d.ts" } ], "returns": { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/get-embedded-app-url.d.ts", "description": "", - "name": "Promise", - "value": "Promise" + "name": "string", + "value": "string" }, - "value": "export type AuthenticateAppProxy = (\n request: Request,\n) => Promise;" + "value": "export type BuildEmbeddedAppUrl = (host: string) => string;" }, - "AppProxyContext": { - "filePath": "/server/authenticate/public/appProxy/types.ts", - "name": "AppProxyContext", + "TokenExchange": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts", + "name": "TokenExchange", + "description": "", + "params": [ + { + "name": "params", + "description": "", + "value": "TokenExchangeParams", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts" + } + ], + "returns": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts", + "description": "", + "name": "Promise<{\n session: Session;\n}>", + "value": "Promise<{\n session: Session;\n}>" + }, + "value": "export type TokenExchange = (params: TokenExchangeParams) => Promise<{\n session: Session;\n}>;" + }, + "TokenExchangeParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts", + "name": "TokenExchangeParams", "description": "", "members": [ { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts", "syntaxKind": "PropertySignature", - "name": "session", - "value": "undefined", - "description": "No session is available for the shop that made this request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice." + "name": "shop", + "value": "string", + "description": "" }, { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts", "syntaxKind": "PropertySignature", - "name": "admin", - "value": "undefined", - "description": "No session is available for the shop that made this request. Therefore no methods for interacting with the GraphQL / REST Admin APIs are available." + "name": "sessionToken", + "value": "string", + "description": "" }, { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts", "syntaxKind": "PropertySignature", - "name": "storefront", - "value": "undefined", - "description": "No session is available for the shop that made this request. Therefore no method for interacting with the Storefront API is available." + "name": "requestedTokenType", + "value": "RequestedTokenType", + "description": "" + } + ], + "value": "export interface TokenExchangeParams {\n shop: string;\n sessionToken: string;\n requestedTokenType: RequestedTokenType;\n}" + }, + "RequestedTokenType": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "RequestedTokenType", + "value": "export declare enum RequestedTokenType {\n OnlineAccessToken = \"urn:shopify:params:oauth:token-type:online-access-token\",\n OfflineAccessToken = \"urn:shopify:params:oauth:token-type:offline-access-token\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts", + "name": "OnlineAccessToken", + "value": "urn:shopify:params:oauth:token-type:online-access-token" }, { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/token-exchange.d.ts", + "name": "OfflineAccessToken", + "value": "urn:shopify:params:oauth:token-type:offline-access-token" + } + ] + }, + "ShopifySession": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/index.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "ShopifySession", + "value": "ReturnType", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/index.d.ts", "syntaxKind": "PropertySignature", - "name": "liquid", - "value": "LiquidResponseFunction", - "description": "A utility for creating a Liquid Response.", - "examples": [ - { - "title": "Rendering liquid content", - "description": "Use the `liquid` helper to render a `Response` with Liquid content.", - "tabs": [ - { - "code": "import {authenticate} from \"~/shopify.server\"\n\nexport async function loader({ request }) {\n const {liquid} = await authenticate.public.appProxy(request);\n\n return liquid(\"Hello {{shop.name}}\")\n}", - "title": "app/routes/**\\/.ts" - } - ] - } - ] + "name": "customAppSession", + "value": "(shop: string) => Session", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "getCurrentId", + "value": "({ isOnline, ...adapterArgs }: GetCurrentSessionIdParams) => Promise", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "getOfflineId", + "value": "(shop: string) => string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "getJwtSessionId", + "value": "(shop: string, userId: string) => string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "decodeSessionToken", + "value": "(token: string, { checkAudience }?: DecodeSessionTokenOptions) => Promise", + "description": "" } - ], - "value": "export interface AppProxyContext extends Context {\n /**\n * No session is available for the shop that made this request.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n */\n session: undefined;\n\n /**\n * No session is available for the shop that made this request.\n * Therefore no methods for interacting with the GraphQL / REST Admin APIs are available.\n */\n admin: undefined;\n\n /**\n * No session is available for the shop that made this request.\n * Therefore no method for interacting with the Storefront API is available.\n */\n storefront: undefined;\n}" + ] }, - "LiquidResponseFunction": { - "filePath": "/server/authenticate/public/appProxy/types.ts", - "name": "LiquidResponseFunction", + "GetCurrentSessionIdParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "name": "GetCurrentSessionIdParams", "description": "", - "params": [ + "members": [ { - "name": "body", - "description": "", - "value": "string", - "filePath": "/server/authenticate/public/appProxy/types.ts" + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "isOnline", + "value": "boolean", + "description": "" }, { - "name": "initAndOptions", + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawRequest", + "value": "AdapterRequest", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawResponse", + "value": "AdapterResponse", "description": "", - "value": "number | (ResponseInit & Options)", - "isOptional": true, - "filePath": "/server/authenticate/public/appProxy/types.ts" + "isOptional": true } ], - "returns": { - "filePath": "/server/authenticate/public/appProxy/types.ts", - "description": "", - "name": "Response", - "value": "Response" - }, - "value": "export type LiquidResponseFunction = (\n body: string,\n initAndOptions?: number | (ResponseInit & Options),\n) => Response;" + "value": "export interface GetCurrentSessionIdParams extends AdapterArgs {\n isOnline: boolean;\n}" }, - "Options": { - "filePath": "/server/authenticate/public/appProxy/types.ts", - "name": "Options", + "DecodeSessionTokenOptions": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/decode-session-token.d.ts", + "name": "DecodeSessionTokenOptions", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/decode-session-token.d.ts", + "syntaxKind": "PropertySignature", + "name": "checkAudience", + "value": "boolean", + "description": "", + "isOptional": true + } + ], + "value": "export interface DecodeSessionTokenOptions {\n checkAudience?: boolean;\n}" + }, + "ShopifyUtils": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/utils/index.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "ShopifyUtils", + "value": "ReturnType", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/utils/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "sanitizeShop", + "value": "(shop: string, throwOnInvalid?: boolean) => string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/utils/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "sanitizeHost", + "value": "(host: string, throwOnInvalid?: boolean) => string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/utils/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "validateHmac", + "value": "(query: AuthQuery, { signator }?: { signator: HMACSignator; }) => Promise", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/utils/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "versionCompatible", + "value": "(referenceVersion: ApiVersion, currentVersion?: ApiVersion) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/utils/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "versionPriorTo", + "value": "(referenceVersion: ApiVersion, currentVersion?: ApiVersion) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/utils/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "shopAdminUrlToLegacyUrl", + "value": "(shopAdminUrl: string) => string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/utils/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "legacyUrlToShopAdminUrl", + "value": "(legacyAdminUrl: string) => string", + "description": "" + } + ] + }, + "AuthQuery": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "name": "AuthQuery", "description": "", "members": [ { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "name": "[key: string]", + "value": "string | undefined" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "hmac", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", "syntaxKind": "PropertySignature", - "name": "layout", - "value": "boolean", + "name": "signature", + "value": "string", "description": "", "isOptional": true } ], - "value": "interface Options {\n layout?: boolean;\n}" + "value": "export interface AuthQuery {\n [key: string]: string | undefined;\n hmac?: string;\n signature?: string;\n}" }, - "AppProxyContextWithSession": { - "filePath": "/server/authenticate/public/appProxy/types.ts", - "name": "AppProxyContextWithSession", + "HMACSignator": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/utils/hmac-validator.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "HMACSignator", + "value": "'admin' | 'appProxy'", + "description": "" + }, + "ShopifyWebhooks": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/index.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "ShopifyWebhooks", + "value": "ReturnType", "description": "", "members": [ { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/index.d.ts", "syntaxKind": "PropertySignature", - "name": "session", - "value": "Session", - "description": "The session for the shop that made the request.\n\nThis comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n\nUse this to get shop or user-specific data.", - "examples": [ - { - "title": "Using the session object", - "description": "Get the session for the shop that initiated the request to the app proxy.", - "tabs": [ - { - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\nimport { getMyAppModelData } from \"~/db/model.server\";\n\nexport const loader = async ({ request }) => {\n // Get the session for the shop that initiated the request to the app proxy.\n const { session } = await authenticate.public.appProxy(request);\n\n // Use the session data to make to queries to your database or additional requests.\n return json(await getMyAppModelData({shop: session.shop));\n};", - "title": "app/routes/**\\/.ts" - } - ] - } - ] + "name": "addHandlers", + "value": "(handlersToAdd: AddHandlersParams) => void", + "description": "" }, { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/index.d.ts", "syntaxKind": "PropertySignature", - "name": "admin", - "value": "AdminApiContext", - "description": "Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request.", - "examples": [ - { - "title": "Interacting with the Admin API", - "description": "Use the `admin` object to interact with the REST or GraphQL APIs.", - "tabs": [ - { - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.public.appProxy(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", - "title": "app/routes/**\\/.ts" - } - ] - } - ] + "name": "getTopicsAdded", + "value": "() => string[]", + "description": "" }, { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/index.d.ts", "syntaxKind": "PropertySignature", - "name": "storefront", - "value": "StorefrontContext", - "description": "Method for interacting with the Shopify Storefront Graphql API for the store that made the request.", - "examples": [ - { - "title": "Interacting with the Storefront API", - "description": "Use the `storefront` object to interact with the GraphQL API.", - "tabs": [ - { - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", - "title": "app/routes/**\\/.ts" - } - ] - } - ] + "name": "getHandlers", + "value": "(topic: string) => WebhookHandler[]", + "description": "" }, { - "filePath": "/server/authenticate/public/appProxy/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/index.d.ts", "syntaxKind": "PropertySignature", - "name": "liquid", - "value": "LiquidResponseFunction", - "description": "A utility for creating a Liquid Response.", - "examples": [ - { - "title": "Rendering liquid content", - "description": "Use the `liquid` helper to render a `Response` with Liquid content.", - "tabs": [ - { - "code": "import {authenticate} from \"~/shopify.server\"\n\nexport async function loader({ request }) {\n const {liquid} = await authenticate.public.appProxy(request);\n\n return liquid(\"Hello {{shop.name}}\")\n}", - "title": "app/routes/**\\/.ts" - } - ] - } - ] + "name": "register", + "value": "({ session, }: RegisterParams) => Promise", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "process", + "value": "({ rawBody, ...adapterArgs }: WebhookProcessParams) => Promise", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "validate", + "value": "({ rawBody, ...adapterArgs }: WebhookValidateParams) => Promise", + "description": "" } - ], - "value": "export interface AppProxyContextWithSession<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> extends Context {\n /**\n * The session for the shop that made the request.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n *\n * Use this to get shop or user-specific data.\n *\n * @example\n * Using the session object.\n * Get the session for the shop that initiated the request to the app proxy.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n * import { getMyAppModelData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }) => {\n * // Get the session for the shop that initiated the request to the app proxy.\n * const { session } = await authenticate.public.appProxy(request);\n *\n * // Use the session data to make to queries to your database or additional requests.\n * return json(await getMyAppModelData({shop: session.shop));\n * };\n * ```\n */\n session: Session;\n\n /**\n * Methods for interacting with the GraphQL / REST Admin APIs for the store that made the request.\n *\n * @example\n * Interacting with the Admin API.\n * Use the `admin` object to interact with the REST or GraphQL APIs.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.public.appProxy(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n admin: AdminApiContext;\n\n /**\n * Method for interacting with the Shopify Storefront Graphql API for the store that made the request.\n *\n * @example\n * Interacting with the Storefront API.\n * Use the `storefront` object to interact with the GraphQL API.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { storefront } = await authenticate.public.appProxy(request);\n *\n * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n *\n * return json(await response.json());\n * }\n * ```\n */\n storefront: StorefrontContext;\n}" + ] }, - "StorefrontContext": { - "filePath": "/server/clients/storefront/types.ts", - "name": "StorefrontContext", + "AddHandlersParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "AddHandlersParams", + "value": "Record", + "description": "", + "members": [] + }, + "RegisterParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "RegisterParams", "description": "", "members": [ { - "filePath": "/server/clients/storefront/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", "syntaxKind": "PropertySignature", - "name": "graphql", - "value": "GraphQLClient", - "description": "Method for interacting with the Shopify Storefront GraphQL API\n\nIf you're getting incorrect type hints in the Shopify template, follow [these instructions](https://github.com/Shopify/shopify-app-template-remix/tree/main#incorrect-graphql-hints).\n\n\n\n\n", - "examples": [ - { - "title": "Querying the GraphQL API", - "description": "Use `storefront.graphql` to make query / mutation requests.", - "tabs": [ - { - "code": "import { json } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { storefront } = await authenticate.public.appProxy(request);\n\n const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n\n return json(await response.json());\n}", - "title": "app/routes/**\\/.ts" - } - ] - } - ] + "name": "session", + "value": "Session", + "description": "" } ], - "value": "export interface StorefrontContext {\n /**\n * Method for interacting with the Shopify Storefront GraphQL API\n *\n * If you're getting incorrect type hints in the Shopify template, follow [these instructions](https://github.com/Shopify/shopify-app-template-remix/tree/main#incorrect-graphql-hints).\n *\n * {@link https://shopify.dev/docs/api/storefront}\n *\n * @example\n * Querying the GraphQL API.\n * Use `storefront.graphql` to make query / mutation requests.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { storefront } = await authenticate.public.appProxy(request);\n *\n * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n *\n * return json(await response.json());\n * }\n * ```\n */\n graphql: GraphQLClient;\n}" + "value": "export interface RegisterParams {\n session: Session;\n}" }, - "AuthenticatePublicLegacy": { - "filePath": "/server/authenticate/public/types.ts", + "Body": { + "filePath": "../../node_modules/@shopify/shopify-api/rest/types.d.ts", "syntaxKind": "TypeAliasDeclaration", - "name": "AuthenticatePublicLegacy", - "value": "AuthenticateCheckout & AuthenticatePublicObject", - "description": "Methods for authenticating Requests from Shopify's public surfaces\n\nTo maintain backwards compatability this is a function and an object.\n\nDo not use `authenticate.public()`. Use `authenticate.public.checkout()` instead. `authenticate.public()` will be removed in v2.\n\nMethods are:\n\n- `authenticate.public.checkout()` for authenticating requests from checkout extensions - `authenticate.public.appProxy()` for authenticating requests from app proxies" + "name": "Body", + "value": "Record", + "description": "", + "members": [] }, - "AuthenticateWebhook": { - "filePath": "/server/authenticate/webhooks/types.ts", - "name": "AuthenticateWebhook", + "WebhookProcessParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "WebhookProcessParams", "description": "", - "params": [ + "members": [ { - "name": "request", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawBody", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawRequest", + "value": "AdapterRequest", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawResponse", + "value": "AdapterResponse", "description": "", - "value": "Request", - "filePath": "/server/authenticate/webhooks/types.ts" + "isOptional": true } ], - "returns": { - "filePath": "/server/authenticate/webhooks/types.ts", - "description": "", - "name": "Promise>", - "value": "Promise>" - }, - "value": "export type AuthenticateWebhook<\n Future extends FutureFlagOptions,\n Resources extends ShopifyRestResources,\n Topics = string | number | symbol,\n> = (request: Request) => Promise>;" + "value": "export interface WebhookProcessParams extends AdapterArgs {\n rawBody: string;\n}" }, - "WebhookContext": { - "filePath": "/server/authenticate/webhooks/types.ts", + "WebhookValidateParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "WebhookValidateParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawBody", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawRequest", + "value": "AdapterRequest", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "rawResponse", + "value": "AdapterResponse", + "description": "", + "isOptional": true + } + ], + "value": "export interface WebhookValidateParams extends WebhookProcessParams {\n}" + }, + "WebhookValidation": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", "syntaxKind": "TypeAliasDeclaration", - "name": "WebhookContext", - "value": "WebhookContextWithoutSession | WebhookContextWithSession", + "name": "WebhookValidation", + "value": "WebhookValidationValid | WebhookValidationInvalid | WebhookValidationMissingHeaders", "description": "" }, - "WebhookContextWithoutSession": { - "filePath": "/server/authenticate/webhooks/types.ts", - "name": "WebhookContextWithoutSession", + "WebhookValidationValid": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "WebhookValidationValid", "description": "", "members": [ { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", "syntaxKind": "PropertySignature", - "name": "session", - "value": "undefined", + "name": "valid", + "value": "true", "description": "" }, { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", "syntaxKind": "PropertySignature", - "name": "admin", - "value": "undefined", + "name": "webhookId", + "value": "string", "description": "" }, { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", "syntaxKind": "PropertySignature", "name": "apiVersion", "value": "string", - "description": "The API version used for the webhook.", - "examples": [ - { - "title": "Webhook API version", - "description": "Get the API version used for webhook request.", - "tabs": [ - { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { apiVersion } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] + "description": "" }, { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", "syntaxKind": "PropertySignature", - "name": "shop", + "name": "domain", "value": "string", - "description": "The shop where the webhook was triggered.", - "examples": [ - { - "title": "Webhook shop", - "description": "Get the shop that triggered a webhook.", - "tabs": [ - { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { shop } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] + "description": "" }, { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", "syntaxKind": "PropertySignature", - "name": "topic", - "value": "Topics", - "description": "The topic of the webhook.", - "examples": [ - { - "title": "Webhook topic", - "description": "Get the event topic for the webhook.", - "tabs": [ - { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { topic } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n // Do something when the app is uninstalled.\n break;\n }\n\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] + "name": "hmac", + "value": "string", + "description": "" }, { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", "syntaxKind": "PropertySignature", - "name": "webhookId", + "name": "topic", "value": "string", - "description": "A unique ID for the webhook. Useful to keep track of which events your app has already processed.", - "examples": [ - { - "title": "Webhook ID", - "description": "Get the webhook ID.", - "tabs": [ - { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { webhookId } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] + "description": "" + } + ], + "value": "export interface WebhookValidationValid extends WebhookFields {\n valid: true;\n}" + }, + "WebhookValidationInvalid": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "WebhookValidationInvalid", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "valid", + "value": "false", + "description": "" }, { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", "syntaxKind": "PropertySignature", - "name": "payload", - "value": "JSONValue", - "description": "The payload from the webhook request.", - "examples": [ - { - "title": "Webhook payload", - "description": "Get the request's POST payload.", - "tabs": [ - { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { payload } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] + "name": "reason", + "value": "WebhookValidationErrorReason", + "description": "" } ], - "value": "export interface WebhookContextWithoutSession\n extends Context {\n session: undefined;\n admin: undefined;\n}" + "value": "export interface WebhookValidationInvalid {\n valid: false;\n reason: WebhookValidationErrorReason;\n}" }, - "JSONValue": { - "filePath": "/server/types.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "JSONValue", - "value": "string | number | boolean | null | JSONObject | JSONArray", - "description": "" + "WebhookValidationErrorReason": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "WebhookValidationErrorReason", + "value": "export declare enum WebhookValidationErrorReason {\n MissingHeaders = \"missing_headers\",\n MissingBody = \"missing_body\",\n InvalidHmac = \"invalid_hmac\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "MissingHeaders", + "value": "missing_headers" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "MissingBody", + "value": "missing_body" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "InvalidHmac", + "value": "invalid_hmac" + } + ] }, - "JSONObject": { - "filePath": "/server/types.ts", - "name": "JSONObject", + "WebhookValidationMissingHeaders": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "name": "WebhookValidationMissingHeaders", "description": "", "members": [ { - "filePath": "/server/types.ts", - "name": "[x: string]", - "value": "JSONValue" + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "reason", + "value": "WebhookValidationErrorReason.MissingHeaders", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "missingHeaders", + "value": "string[]", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/webhooks/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "valid", + "value": "false", + "description": "" } ], - "value": "interface JSONObject {\n [x: string]: JSONValue;\n}" + "value": "export interface WebhookValidationMissingHeaders extends WebhookValidationInvalid {\n reason: WebhookValidationErrorReason.MissingHeaders;\n missingHeaders: string[];\n}" }, - "JSONArray": { - "filePath": "/server/types.ts", - "name": "JSONArray", + "ShopifyBilling": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/index.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "ShopifyBilling", + "value": "ReturnType", "description": "", "members": [ { - "filePath": "/server/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/index.d.ts", "syntaxKind": "PropertySignature", - "name": "length", - "value": "number", - "description": "Gets or sets the length of the array. This is a number one higher than the highest index in the array." + "name": "check", + "value": "({ session, plans, isTest, returnObject, }: Params_1) => Promise>", + "description": "" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "toString", - "value": "() => string", - "description": "Returns a string representation of an array." + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "request", + "value": "({ session, plan, isTest, returnUrl: returnUrlParam, returnObject, ...overrides }: Params_2) => Promise>", + "description": "" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "toLocaleString", - "value": "() => string", - "description": "Returns a string representation of an array. The elements are converted to string using their toLocaleString methods." + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "cancel", + "value": "(subscriptionInfo: BillingCancelParams) => Promise", + "description": "" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "pop", - "value": "() => JSONValue", - "description": "Removes the last element from an array and returns it.\r\nIf the array is empty, undefined is returned and the array is not modified." - }, + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "subscriptions", + "value": "({ session, }: BillingSubscriptionParams) => Promise", + "description": "" + } + ] + }, + "BillingCheckParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingCheckParams", + "description": "", + "members": [ { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "push", - "value": "(...items: JSONValue[]) => number", - "description": "Appends new elements to the end of an array, and returns the new length of the array." + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "concat", - "value": "{ (...items: ConcatArray[]): JSONValue[]; (...items: (JSONValue | ConcatArray)[]): JSONValue[]; }", - "description": "Combines two or more arrays.\r\nThis method returns a new array without modifying any existing arrays." + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "plans", + "value": "string | string[]", + "description": "" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "join", - "value": "(separator?: string) => string", - "description": "Adds all the elements of an array into a string, separated by the specified separator string." + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "isTest", + "value": "boolean", + "description": "", + "isOptional": true }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "reverse", - "value": "() => JSONValue[]", - "description": "Reverses the elements in an array in place.\r\nThis method mutates the array and returns a reference to the same array." - }, + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "returnObject", + "value": "boolean", + "description": "", + "isOptional": true + } + ], + "value": "export interface BillingCheckParams {\n session: Session;\n plans: string[] | string;\n isTest?: boolean;\n returnObject?: boolean;\n}" + }, + "BillingCheckResponse": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "BillingCheckResponse", + "value": "Params['returnObject'] extends true ? BillingCheckResponseObject : boolean", + "description": "" + }, + "BillingRequestParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "BillingRequestParams", + "value": "{\n session: Session;\n plan: string;\n isTest?: boolean;\n returnUrl?: string;\n returnObject?: boolean;\n} & RequestConfigOverrides", + "description": "" + }, + "RequestConfigOverrides": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RequestConfigOverrides", + "value": "Partial | Partial | Partial", + "description": "" + }, + "BillingConfigOneTimePlan": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingConfigOneTimePlan", + "description": "", + "members": [ { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "shift", - "value": "() => JSONValue", - "description": "Removes the first element from an array and returns it.\r\nIf the array is empty, undefined is returned and the array is not modified." + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "interval", + "value": "BillingInterval.OneTime", + "description": "" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "slice", - "value": "(start?: number, end?: number) => JSONValue[]", - "description": "Returns a copy of a section of an array.\r\nFor both start and end, a negative index can be used to indicate an offset from the end of the array.\r\nFor example, -2 refers to the second to last element of the array." + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "amount", + "value": "number", + "description": "" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "sort", - "value": "(compareFn?: (a: JSONValue, b: JSONValue) => number) => JSONArray", - "description": "Sorts an array in place.\r\nThis method mutates the array and returns a reference to the same array." - }, + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "currencyCode", + "value": "string", + "description": "" + } + ], + "value": "export interface BillingConfigOneTimePlan extends BillingConfigPlan {\n interval: BillingInterval.OneTime;\n}" + }, + "BillingInterval": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "BillingInterval", + "value": "export declare enum BillingInterval {\n OneTime = \"ONE_TIME\",\n Every30Days = \"EVERY_30_DAYS\",\n Annual = \"ANNUAL\",\n Usage = \"USAGE\"\n}", + "members": [ { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "splice", - "value": "{ (start: number, deleteCount?: number): JSONValue[]; (start: number, deleteCount: number, ...items: JSONValue[]): JSONValue[]; }", - "description": "Removes elements from an array and, if necessary, inserts new elements in their place, returning the deleted elements." + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "OneTime", + "value": "ONE_TIME" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "unshift", - "value": "(...items: JSONValue[]) => number", - "description": "Inserts new elements at the start of an array, and returns the new length of the array." + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Every30Days", + "value": "EVERY_30_DAYS" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "indexOf", - "value": "(searchElement: JSONValue, fromIndex?: number) => number", - "description": "Returns the index of the first occurrence of a value in an array, or -1 if it is not present." + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Annual", + "value": "ANNUAL" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "lastIndexOf", - "value": "(searchElement: JSONValue, fromIndex?: number) => number", - "description": "Returns the index of the last occurrence of a specified value in an array, or -1 if it is not present." + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Usage", + "value": "USAGE" + } + ] + }, + "BillingConfigSubscriptionPlan": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingConfigSubscriptionPlan", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "interval", + "value": "Exclude", + "description": "" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "every", - "value": "{ (predicate: (value: JSONValue, index: number, array: JSONValue[]) => value is S, thisArg?: any): this is S[]; (predicate: (value: JSONValue, index: number, array: JSONValue[]) => unknown, thisArg?: any): boolean; }", - "description": "Determines whether all the members of an array satisfy the specified test." + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "trialDays", + "value": "number", + "description": "", + "isOptional": true }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "some", - "value": "(predicate: (value: JSONValue, index: number, array: JSONValue[]) => unknown, thisArg?: any) => boolean", - "description": "Determines whether the specified callback function returns true for any element of an array." + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "replacementBehavior", + "value": "BillingReplacementBehavior", + "description": "", + "isOptional": true }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "forEach", - "value": "(callbackfn: (value: JSONValue, index: number, array: JSONValue[]) => void, thisArg?: any) => void", - "description": "Performs the specified action for each element in an array." + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "discount", + "value": "BillingConfigSubscriptionPlanDiscount", + "description": "", + "isOptional": true }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "map", - "value": "(callbackfn: (value: JSONValue, index: number, array: JSONValue[]) => U, thisArg?: any) => U[]", - "description": "Calls a defined callback function on each element of an array, and returns an array that contains the results." + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "amount", + "value": "number", + "description": "" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "filter", - "value": "{ (predicate: (value: JSONValue, index: number, array: JSONValue[]) => value is S, thisArg?: any): S[]; (predicate: (value: JSONValue, index: number, array: JSONValue[]) => unknown, thisArg?: any): JSONValue[]; }", - "description": "Returns the elements of an array that meet the condition specified in a callback function." + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "currencyCode", + "value": "string", + "description": "" + } + ], + "value": "export interface BillingConfigSubscriptionPlan extends BillingConfigPlan {\n interval: Exclude;\n trialDays?: number;\n replacementBehavior?: BillingReplacementBehavior;\n discount?: BillingConfigSubscriptionPlanDiscount;\n}" + }, + "RecurringBillingIntervals": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RecurringBillingIntervals", + "value": "RecurringBillingIntervals", + "description": "" + }, + "BillingReplacementBehavior": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "BillingReplacementBehavior", + "value": "export declare enum BillingReplacementBehavior {\n ApplyImmediately = \"APPLY_IMMEDIATELY\",\n ApplyOnNextBillingCycle = \"APPLY_ON_NEXT_BILLING_CYCLE\",\n Standard = \"STANDARD\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "ApplyImmediately", + "value": "APPLY_IMMEDIATELY" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "reduce", - "value": "{ (callbackfn: (previousValue: JSONValue, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => JSONValue): JSONValue; (callbackfn: (previousValue: JSONValue, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => JSONValue, initialValue: JSONValue): JSONValue; (callbackfn: (previousValue: U, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => U, initialValue: U): U; }", - "description": "Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function." + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "ApplyOnNextBillingCycle", + "value": "APPLY_ON_NEXT_BILLING_CYCLE" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "reduceRight", - "value": "{ (callbackfn: (previousValue: JSONValue, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => JSONValue): JSONValue; (callbackfn: (previousValue: JSONValue, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => JSONValue, initialValue: JSONValue): JSONValue; (callbackfn: (previousValue: U, currentValue: JSONValue, currentIndex: number, array: JSONValue[]) => U, initialValue: U): U; }", - "description": "Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function." + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Standard", + "value": "STANDARD" + } + ] + }, + "BillingConfigSubscriptionPlanDiscount": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingConfigSubscriptionPlanDiscount", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "durationLimitInIntervals", + "value": "number", + "description": "", + "isOptional": true }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "find", - "value": "{ (predicate: (this: void, value: JSONValue, index: number, obj: JSONValue[]) => value is S, thisArg?: any): S; (predicate: (value: JSONValue, index: number, obj: JSONValue[]) => unknown, thisArg?: any): JSONValue; }", - "description": "Returns the value of the first element in the array where predicate is true, and undefined\r\notherwise." + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "value", + "value": "BillingConfigSubscriptionPlanDiscountAmount | BillingConfigSubscriptionPlanDiscountPercentage", + "description": "" + } + ], + "value": "export interface BillingConfigSubscriptionPlanDiscount {\n durationLimitInIntervals?: number;\n value: BillingConfigSubscriptionPlanDiscountAmount | BillingConfigSubscriptionPlanDiscountPercentage;\n}" + }, + "BillingConfigSubscriptionPlanDiscountAmount": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingConfigSubscriptionPlanDiscountAmount", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "amount", + "value": "number", + "description": "" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "findIndex", - "value": "(predicate: (value: JSONValue, index: number, obj: JSONValue[]) => unknown, thisArg?: any) => number", - "description": "Returns the index of the first element in the array where predicate is true, and -1\r\notherwise." + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "percentage", + "value": "never", + "description": "", + "isOptional": true + } + ], + "value": "export interface BillingConfigSubscriptionPlanDiscountAmount {\n amount: number;\n percentage?: never;\n}" + }, + "BillingConfigSubscriptionPlanDiscountPercentage": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingConfigSubscriptionPlanDiscountPercentage", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "amount", + "value": "never", + "description": "", + "isOptional": true }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "fill", - "value": "(value: JSONValue, start?: number, end?: number) => JSONArray", - "description": "Changes all array elements from `start` to `end` index to a static `value` and returns the modified array" + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "percentage", + "value": "number", + "description": "" + } + ], + "value": "export interface BillingConfigSubscriptionPlanDiscountPercentage {\n amount?: never;\n percentage: number;\n}" + }, + "BillingConfigUsagePlan": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingConfigUsagePlan", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "interval", + "value": "BillingInterval.Usage", + "description": "" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "copyWithin", - "value": "(target: number, start: number, end?: number) => JSONArray", - "description": "Returns the this object after copying a section of the array identified by start and end\r\nto the same array starting at position target" + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "usageTerms", + "value": "string", + "description": "" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "entries", - "value": "() => IterableIterator<[number, JSONValue]>", - "description": "Returns an iterable of key, value pairs for every entry in the array" + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "trialDays", + "value": "number", + "description": "", + "isOptional": true }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "keys", - "value": "() => IterableIterator", - "description": "Returns an iterable of keys in the array" + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "replacementBehavior", + "value": "BillingReplacementBehavior", + "description": "", + "isOptional": true }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "values", - "value": "() => IterableIterator", - "description": "Returns an iterable of values in the array" + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "amount", + "value": "number", + "description": "" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "includes", - "value": "(searchElement: JSONValue, fromIndex?: number) => boolean", - "description": "Determines whether an array includes a certain element, returning true or false as appropriate." + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "currencyCode", + "value": "string", + "description": "" + } + ], + "value": "export interface BillingConfigUsagePlan extends BillingConfigPlan {\n interval: BillingInterval.Usage;\n usageTerms: string;\n trialDays?: number;\n replacementBehavior?: BillingReplacementBehavior;\n}" + }, + "BillingRequestResponse": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "BillingRequestResponse", + "value": "Params['returnObject'] extends true ? BillingRequestResponseObject : string", + "description": "" + }, + "BillingRequestResponseObject": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingRequestResponseObject", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "confirmationUrl", + "value": "string", + "description": "" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "flatMap", - "value": "(callback: (this: This, value: JSONValue, index: number, array: JSONValue[]) => U | readonly U[], thisArg?: This) => U[]", - "description": "Calls a defined callback function on each element of an array. Then, flattens the result into\r\na new array.\r\nThis is identical to a map followed by flat with depth 1." + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "oneTimePurchase", + "value": "OneTimePurchase", + "description": "", + "isOptional": true }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "flat", - "value": "(this: A, depth?: D) => FlatArray[]", - "description": "Returns a new array with all sub-array elements concatenated into it recursively up to the\r\nspecified depth." + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "appSubscription", + "value": "AppSubscription", + "description": "", + "isOptional": true + } + ], + "value": "export interface BillingRequestResponseObject {\n confirmationUrl: string;\n oneTimePurchase?: OneTimePurchase;\n appSubscription?: AppSubscription;\n}" + }, + "BillingCancelParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingCancelParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "session", + "value": "Session", + "description": "" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "__@iterator@716", - "value": "() => IterableIterator", - "description": "Iterator" + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "subscriptionId", + "value": "string", + "description": "" }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "__@unscopables@718", - "value": "() => { copyWithin: boolean; entries: boolean; fill: boolean; find: boolean; findIndex: boolean; keys: boolean; values: boolean; }", - "description": "Returns an object whose properties have the value 'true'\r\nwhen they will be absent when used in a 'with' statement." + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "prorate", + "value": "boolean", + "description": "", + "isOptional": true }, { - "filePath": "/server/types.ts", - "syntaxKind": "MethodSignature", - "name": "at", - "value": "(index: number) => JSONValue", - "description": "Takes an integer value and returns the item at that index, allowing for positive and negative integers. Negative integers count back from the last item in the array." + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "isTest", + "value": "boolean", + "description": "", + "isOptional": true } ], - "value": "interface JSONArray extends Array {}" + "value": "export interface BillingCancelParams {\n session: Session;\n subscriptionId: string;\n prorate?: boolean;\n isTest?: boolean;\n}" }, - "WebhookContextWithSession": { - "filePath": "/server/authenticate/webhooks/types.ts", - "name": "WebhookContextWithSession", + "BillingSubscriptionParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "BillingSubscriptionParams", "description": "", "members": [ { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", "syntaxKind": "PropertySignature", "name": "session", "value": "Session", - "description": "A session with an offline token for the shop.\n\nReturned only if there is a session for the shop." - }, + "description": "" + } + ], + "value": "export interface BillingSubscriptionParams {\n session: Session;\n}" + }, + "ActiveSubscriptions": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", + "name": "ActiveSubscriptions", + "description": "", + "members": [ { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/billing/types.d.ts", "syntaxKind": "PropertySignature", - "name": "admin", - "value": "WebhookAdminContext", - "description": "An admin context for the webhook.\n\nReturned only if there is a session for the shop.", - "examples": [ - { - "title": "[V3] Webhook admin context", - "description": "With the `v3_webhookAdminContext` future flag enabled, use the `admin` object in the context to interact with the Admin API.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.webhook(request);\n\n const response = await admin?.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", - "title": "/app/routes/webhooks.tsx" - } - ] - }, - { - "title": "Webhook admin context", - "description": "Use the `admin` object in the context to interact with the Admin API. This format will be removed in V3 of the package.", - "tabs": [ - { - "code": "import { json, ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.webhook(request);\n\n const response = await admin?.graphql.query({\n data: {\n query: `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n variables: { input: { title: \"Product Name\" } },\n },\n });\n\n const productData = response?.body.data;\n return json({ data: productData.data });\n}", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] + "name": "activeSubscriptions", + "value": "AppSubscription[]", + "description": "" + } + ], + "value": "export interface ActiveSubscriptions {\n activeSubscriptions: AppSubscription[];\n}" + }, + "ShopifyLogger": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/index.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "ShopifyLogger", + "value": "ReturnType", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/index.d.ts", + "syntaxKind": "PropertySignature", + "name": "log", + "value": "LoggerFunction", + "description": "" }, { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/index.d.ts", "syntaxKind": "PropertySignature", - "name": "apiVersion", - "value": "string", - "description": "The API version used for the webhook.", - "examples": [ - { - "title": "Webhook API version", - "description": "Get the API version used for webhook request.", - "tabs": [ - { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { apiVersion } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] + "name": "debug", + "value": "(message: string, context?: LogContext) => Promise", + "description": "" }, { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/index.d.ts", "syntaxKind": "PropertySignature", - "name": "shop", - "value": "string", - "description": "The shop where the webhook was triggered.", - "examples": [ - { - "title": "Webhook shop", - "description": "Get the shop that triggered a webhook.", - "tabs": [ - { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { shop } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] + "name": "info", + "value": "(message: string, context?: LogContext) => Promise", + "description": "" }, { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/index.d.ts", "syntaxKind": "PropertySignature", - "name": "topic", - "value": "Topics", - "description": "The topic of the webhook.", - "examples": [ - { - "title": "Webhook topic", - "description": "Get the event topic for the webhook.", - "tabs": [ - { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { topic } = await authenticate.webhook(request);\n\n switch (topic) {\n case \"APP_UNINSTALLED\":\n // Do something when the app is uninstalled.\n break;\n }\n\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] + "name": "warning", + "value": "(message: string, context?: LogContext) => Promise", + "description": "" }, { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/index.d.ts", "syntaxKind": "PropertySignature", - "name": "webhookId", - "value": "string", - "description": "A unique ID for the webhook. Useful to keep track of which events your app has already processed.", - "examples": [ - { - "title": "Webhook ID", - "description": "Get the webhook ID.", - "tabs": [ - { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { webhookId } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] + "name": "error", + "value": "(message: string, context?: LogContext) => Promise", + "description": "" }, { - "filePath": "/server/authenticate/webhooks/types.ts", + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/index.d.ts", "syntaxKind": "PropertySignature", - "name": "payload", - "value": "JSONValue", - "description": "The payload from the webhook request.", - "examples": [ - { - "title": "Webhook payload", - "description": "Get the request's POST payload.", - "tabs": [ - { - "code": "import { ActionFunction } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport const action: ActionFunction = async ({ request }) => {\n const { payload } = await authenticate.webhook(request);\n return new Response();\n};", - "title": "/app/routes/webhooks.tsx" - } - ] - } - ] + "name": "deprecated", + "value": "(version: string, message: string) => void", + "description": "" } - ], - "value": "export interface WebhookContextWithSession<\n Future extends FutureFlagOptions,\n Resources extends ShopifyRestResources,\n Topics = string | number | symbol,\n> extends Context {\n /**\n * A session with an offline token for the shop.\n *\n * Returned only if there is a session for the shop.\n */\n session: Session;\n\n /**\n * An admin context for the webhook.\n *\n * Returned only if there is a session for the shop.\n *\n * @example\n * [V3] Webhook admin context.\n * With the `v3_webhookAdminContext` future flag enabled, use the `admin` object in the context to interact with the Admin API.\n * ```ts\n * // /app/routes/webhooks.tsx\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.webhook(request);\n *\n * const response = await admin?.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n *\n * @example\n * Webhook admin context.\n * Use the `admin` object in the context to interact with the Admin API. This format will be removed in V3 of the package.\n * ```ts\n * // /app/routes/webhooks.tsx\n * import { json, ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.webhook(request);\n *\n * const response = await admin?.graphql.query({\n * data: {\n * query: `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * variables: { input: { title: \"Product Name\" } },\n * },\n * });\n *\n * const productData = response?.body.data;\n * return json({ data: productData.data });\n * }\n * ```\n */\n admin: WebhookAdminContext;\n}" - }, - "WebhookAdminContext": { - "filePath": "/server/authenticate/webhooks/types.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "WebhookAdminContext", - "value": "FeatureEnabled extends true\n ? AdminApiContext\n : LegacyWebhookAdminApiContext", - "description": "" + ] }, - "LegacyWebhookAdminApiContext": { - "filePath": "/server/authenticate/webhooks/types.ts", - "name": "LegacyWebhookAdminApiContext", + "LoggerFunction": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/log.d.ts", + "name": "LoggerFunction", "description": "", - "members": [ + "params": [ { - "filePath": "/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "rest", - "value": "RestClient & Resources", - "description": "A REST client." + "name": "severity", + "description": "", + "value": "LogSeverity", + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/log.d.ts" }, { - "filePath": "/server/authenticate/webhooks/types.ts", - "syntaxKind": "PropertySignature", - "name": "graphql", - "value": "InstanceType", - "description": "A GraphQL client." + "name": "message", + "description": "", + "value": "string", + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/log.d.ts" + }, + { + "name": "context", + "description": "", + "value": "Record", + "isOptional": true, + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/log.d.ts" } ], - "value": "export interface LegacyWebhookAdminApiContext<\n Resources extends ShopifyRestResources,\n> {\n /** A REST client. */\n rest: InstanceType & Resources;\n /** A GraphQL client. */\n graphql: InstanceType;\n}" + "returns": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/log.d.ts", + "description": "", + "name": "void", + "value": "void" + }, + "value": "export type LoggerFunction = (severity: LogSeverity, message: string, context?: Record) => void;" + }, + "LogContext": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/logger/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "LogContext", + "value": "Record", + "description": "", + "members": [] }, "MandatoryTopics": { - "filePath": "/server/types.ts", + "filePath": "src/server/types.ts", "syntaxKind": "TypeAliasDeclaration", "name": "MandatoryTopics", "value": "'CUSTOMERS_DATA_REQUEST' | 'CUSTOMERS_REDACT' | 'SHOP_REDACT'", "description": "" }, "Unauthenticated": { - "filePath": "/server/unauthenticated/types.ts", + "filePath": "src/server/unauthenticated/types.ts", "name": "Unauthenticated", "description": "", "members": [ { - "filePath": "/server/unauthenticated/types.ts", + "filePath": "src/server/unauthenticated/types.ts", "syntaxKind": "PropertySignature", "name": "admin", "value": "GetUnauthenticatedAdminContext", @@ -5618,7 +14091,7 @@ ] }, { - "filePath": "/server/unauthenticated/types.ts", + "filePath": "src/server/unauthenticated/types.ts", "syntaxKind": "PropertySignature", "name": "storefront", "value": "GetUnauthenticatedStorefrontContext", @@ -5640,7 +14113,7 @@ "value": "export interface Unauthenticated {\n /**\n * Get an admin context by passing a shop\n *\n * **Warning** This should only be used for Requests that do not originate from Shopify.\n * You must do your own authentication before using this method.\n * This method throws an error if there is no session for the shop.\n *\n * @example\n * Responding to a request not controlled by Shopify.\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * ```\n * ```ts\n * // /app/routes/**\\/*.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticateExternal } from \"~/helpers/authenticate\"\n * import shopify from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const shop = await authenticateExternal(request)\n * const {admin} = await shopify.unauthenticated.admin(shop);\n *\n * return json(await admin.rest.resources.Product.count({ session }));\n * }\n * ```\n */\n admin: GetUnauthenticatedAdminContext;\n\n /**\n * Get a storefront context by passing a shop\n *\n * **Warning** This should only be used for Requests that do not originate from Shopify.\n * You must do your own authentication before using this method.\n * This method throws an error if there is no session for the shop.\n *\n * @example\n * Responding to a request not controlled by Shopify\n * ```ts\n * // /app/routes/**\\/*.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticateExternal } from \"~/helpers/authenticate\"\n * import shopify from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const shop = await authenticateExternal(request)\n * const {storefront} = await shopify.unauthenticated.storefront(shop);\n * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n *\n * return json(await response.json());\n * }\n * ```\n */\n storefront: GetUnauthenticatedStorefrontContext;\n}" }, "GetUnauthenticatedAdminContext": { - "filePath": "/server/unauthenticated/admin/types.ts", + "filePath": "src/server/unauthenticated/admin/types.ts", "name": "GetUnauthenticatedAdminContext", "description": "", "params": [ @@ -5648,11 +14121,11 @@ "name": "shop", "description": "", "value": "string", - "filePath": "/server/unauthenticated/admin/types.ts" + "filePath": "src/server/unauthenticated/admin/types.ts" } ], "returns": { - "filePath": "/server/unauthenticated/admin/types.ts", + "filePath": "src/server/unauthenticated/admin/types.ts", "description": "", "name": "Promise>", "value": "Promise>" @@ -5660,12 +14133,12 @@ "value": "export type GetUnauthenticatedAdminContext<\n Resources extends ShopifyRestResources,\n> = (shop: string) => Promise>;" }, "UnauthenticatedAdminContext": { - "filePath": "/server/unauthenticated/admin/types.ts", + "filePath": "src/server/unauthenticated/admin/types.ts", "name": "UnauthenticatedAdminContext", "description": "", "members": [ { - "filePath": "/server/unauthenticated/admin/types.ts", + "filePath": "src/server/unauthenticated/admin/types.ts", "syntaxKind": "PropertySignature", "name": "session", "value": "Session", @@ -5684,7 +14157,7 @@ ] }, { - "filePath": "/server/unauthenticated/admin/types.ts", + "filePath": "src/server/unauthenticated/admin/types.ts", "syntaxKind": "PropertySignature", "name": "admin", "value": "AdminApiContext", @@ -5694,7 +14167,7 @@ "value": "export interface UnauthenticatedAdminContext<\n Resources extends ShopifyRestResources,\n> {\n /**\n * The session for the given shop.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n *\n * This will always be an offline session. You can use to get shop-specific data.\n *\n * @example\n * Using the offline session.\n * Get your app's shop-specific data using the returned offline `session` object.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { unauthenticated } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const shop = getShopFromExternalRequest(request);\n * const { session } = await unauthenticated.admin(shop);\n * return json(await getMyAppData({shop: session.shop));\n * };\n * ```\n */\n session: Session;\n\n /**\n * Methods for interacting with the GraphQL / REST Admin APIs for the given store.\n */\n admin: AdminApiContext;\n}" }, "GetUnauthenticatedStorefrontContext": { - "filePath": "/server/unauthenticated/storefront/types.ts", + "filePath": "src/server/unauthenticated/storefront/types.ts", "name": "GetUnauthenticatedStorefrontContext", "description": "", "params": [ @@ -5702,11 +14175,11 @@ "name": "shop", "description": "", "value": "string", - "filePath": "/server/unauthenticated/storefront/types.ts" + "filePath": "src/server/unauthenticated/storefront/types.ts" } ], "returns": { - "filePath": "/server/unauthenticated/storefront/types.ts", + "filePath": "src/server/unauthenticated/storefront/types.ts", "description": "", "name": "Promise", "value": "Promise" @@ -5714,12 +14187,12 @@ "value": "export type GetUnauthenticatedStorefrontContext = (\n shop: string,\n) => Promise;" }, "UnauthenticatedStorefrontContext": { - "filePath": "/server/unauthenticated/storefront/types.ts", + "filePath": "src/server/unauthenticated/storefront/types.ts", "name": "UnauthenticatedStorefrontContext", "description": "", "members": [ { - "filePath": "/server/unauthenticated/storefront/types.ts", + "filePath": "src/server/unauthenticated/storefront/types.ts", "syntaxKind": "PropertySignature", "name": "session", "value": "Session", @@ -5738,7 +14211,7 @@ ] }, { - "filePath": "/server/unauthenticated/storefront/types.ts", + "filePath": "src/server/unauthenticated/storefront/types.ts", "syntaxKind": "PropertySignature", "name": "storefront", "value": "StorefrontContext", @@ -5748,19 +14221,19 @@ "value": "export interface UnauthenticatedStorefrontContext {\n /**\n * The session for the given shop.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n *\n * This will always be an offline session. You can use this to get shop specific data.\n *\n * @example\n * Using the offline session.\n * Get your app's shop-specific data using the returned offline `session` object.\n * ```ts\n * // app/routes/**\\/.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { unauthenticated } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const shop = getShopFromExternalRequest(request);\n * const { session } = await unauthenticated.storefront(shop);\n * return json(await getMyAppData({shop: session.shop));\n * };\n * ```\n */\n session: Session;\n\n /**\n * Method for interacting with the Shopify GraphQL Storefront API for the given store.\n */\n storefront: StorefrontContext;\n}" }, "SingleMerchantApp": { - "filePath": "/server/types.ts", + "filePath": "src/server/types.ts", "syntaxKind": "TypeAliasDeclaration", "name": "SingleMerchantApp", "value": "ShopifyAppBase & ShopifyAppLogin", "description": "" }, "ShopifyAppBase": { - "filePath": "/server/types.ts", + "filePath": "src/server/types.ts", "name": "ShopifyAppBase", "description": "", "members": [ { - "filePath": "/server/types.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", "name": "sessionStorage", "value": "SessionStorageType", @@ -5779,7 +14252,7 @@ ] }, { - "filePath": "/server/types.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", "name": "addDocumentResponseHeaders", "value": "AddDocumentResponseHeaders", @@ -5802,7 +14275,7 @@ ] }, { - "filePath": "/server/types.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", "name": "registerWebhooks", "value": "RegisterWebhooks", @@ -5821,7 +14294,7 @@ ] }, { - "filePath": "/server/types.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", "name": "authenticate", "value": "Authenticate", @@ -5844,7 +14317,7 @@ ] }, { - "filePath": "/server/types.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", "name": "unauthenticated", "value": "Unauthenticated>", @@ -5870,12 +14343,12 @@ "value": "export interface ShopifyAppBase {\n /**\n * The `SessionStorage` instance you passed in as a config option.\n *\n * @example\n * Storing sessions with Prisma.\n * Import the `@shopify/shopify-app-session-storage-prisma` package to store sessions in your Prisma database.\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { PrismaSessionStorage } from \"@shopify/shopify-app-session-storage-prisma\";\n * import prisma from \"~/db.server\";\n *\n * const shopify = shopifyApp({\n * sesssionStorage: new PrismaSessionStorage(prisma),\n * // ...etc\n * })\n *\n * // shopify.sessionStorage is an instance of PrismaSessionStorage\n * ```\n */\n sessionStorage: SessionStorageType;\n\n /**\n * Adds the required Content Security Policy headers for Shopify apps to the given Headers object.\n *\n * {@link https://shopify.dev/docs/apps/store/security/iframe-protection}\n *\n * @example\n * Return headers on all requests.\n * Add headers to all HTML requests by calling `shopify.addDocumentResponseHeaders` in `entry.server.tsx`.\n *\n * ```\n * // ~/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * });\n * export default shopify;\n * export const addDocumentResponseheaders = shopify.addDocumentResponseheaders;\n * ```\n *\n * ```ts\n * // entry.server.tsx\n * import { addDocumentResponseHeaders } from \"~/shopify.server\";\n *\n * export default function handleRequest(\n * request: Request,\n * responseStatusCode: number,\n * responseHeaders: Headers,\n * remixContext: EntryContext\n * ) {\n * const markup = renderToString(\n * \n * );\n *\n * responseHeaders.set(\"Content-Type\", \"text/html\");\n * addDocumentResponseHeaders(request, responseHeaders);\n *\n * return new Response(\"\" + markup, {\n * status: responseStatusCode,\n * headers: responseHeaders,\n * });\n * }\n * ```\n */\n addDocumentResponseHeaders: AddDocumentResponseHeaders;\n\n /**\n * Register webhook topics for a store using the given session. Most likely you want to use this in combination with the afterAuth hook.\n *\n * @example\n * Registering webhooks after install\n * Trigger the registration to create the webhook subscriptions after a merchant installs your app using the `afterAuth` hook. Learn more about [subscribing to webhooks.](/docs/api/shopify-app-remix/v1/guide-webhooks)\n * ```ts\n * // /app/shopify.server.ts\n * import { DeliveryMethod, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * hooks: {\n * afterAuth: async ({ session }) => {\n * shopify.registerWebhooks({ session });\n * }\n * },\n * webhooks: {\n * APP_UNINSTALLED: {\n * deliveryMethod: DeliveryMethod.Http,\n * callbackUrl: \"/webhooks\",\n * },\n * },\n * // ...etc\n * });\n * ```\n */\n registerWebhooks: RegisterWebhooks;\n\n /**\n * Ways to authenticate requests from different surfaces across Shopify.\n *\n * @example\n * Authenticate Shopify requests.\n * Use the functions in `authenticate` to validate requests coming from Shopify.\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * ```\n * ```ts\n * // /app/routes/**\\/*.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import shopify from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const {admin, session, sessionToken, billing} = shopify.authenticate.admin(request);\n *\n * return json(await admin.rest.resources.Product.count({ session }));\n * }\n * ```\n */\n authenticate: Authenticate;\n\n /**\n * Ways to get Contexts from requests that do not originate from Shopify.\n *\n * @example\n * Using unauthenticated contexts.\n * Create contexts for requests that don't come from Shopify.\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * ```\n * ```ts\n * // /app/routes/**\\/*.jsx\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticateExternal } from \"~/helpers/authenticate\"\n * import shopify from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const shop = await authenticateExternal(request)\n * const {admin} = await shopify.unauthenticated.admin(shop);\n *\n * return json(await admin.rest.resources.Product.count({ session }));\n * }\n * ```\n */\n unauthenticated: Unauthenticated>;\n}" }, "ShopifyAppLogin": { - "filePath": "/server/types.ts", + "filePath": "src/server/types.ts", "name": "ShopifyAppLogin", "description": "", "members": [ { - "filePath": "/server/types.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", "name": "login", "value": "Login", @@ -5901,7 +14374,7 @@ "value": "interface ShopifyAppLogin {\n /**\n * Log a merchant in, and redirect them to the app root. Will redirect the merchant to authentication if a shop is\n * present in the URL search parameters or form data.\n *\n * This function won't be present when the `distribution` config option is set to `AppDistribution.ShopifyAdmin`,\n * because Admin apps aren't allowed to show a login page.\n *\n * @example\n * Creating a login page.\n * Use `shopify.login` to create a login form, in a route that can handle GET and POST requests.\n * ```ts\n * // /app/shopify.server.ts\n * import { LATEST_API_VERSION, shopifyApp } from \"@shopify/shopify-app-remix/server\";\n *\n * const shopify = shopifyApp({\n * // ...etc\n * });\n * export default shopify;\n * ```\n * ```ts\n * // /app/routes/auth/login.tsx\n * import shopify from \"../../shopify.server\";\n *\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const errors = shopify.login(request);\n *\n * return json(errors);\n * }\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const errors = shopify.login(request);\n *\n * return json(errors);\n * }\n *\n * export default function Auth() {\n * const actionData = useActionData();\n * const [shop, setShop] = useState(\"\");\n *\n * return (\n * \n * \n *
\n * \n * \n * Login\n * \n * \n * \n * \n *
\n *
\n *
\n * );\n * }\n * ```\n */\n login: Login;\n}" }, "Login": { - "filePath": "/server/types.ts", + "filePath": "src/server/types.ts", "name": "Login", "description": "", "params": [ @@ -5909,11 +14382,11 @@ "name": "request", "description": "", "value": "Request", - "filePath": "/server/types.ts" + "filePath": "src/server/types.ts" } ], "returns": { - "filePath": "/server/types.ts", + "filePath": "src/server/types.ts", "description": "", "name": "Promise", "value": "Promise" @@ -5921,12 +14394,12 @@ "value": "type Login = (request: Request) => Promise;" }, "LoginError": { - "filePath": "/server/types.ts", + "filePath": "src/server/types.ts", "name": "LoginError", "description": "", "members": [ { - "filePath": "/server/types.ts", + "filePath": "src/server/types.ts", "syntaxKind": "PropertySignature", "name": "shop", "value": "LoginErrorType", @@ -5937,25 +14410,25 @@ "value": "export interface LoginError {\n shop?: LoginErrorType;\n}" }, "LoginErrorType": { - "filePath": "/server/types.ts", + "filePath": "src/server/types.ts", "syntaxKind": "EnumDeclaration", "name": "LoginErrorType", "value": "export enum LoginErrorType {\n MissingShop = 'MISSING_SHOP',\n InvalidShop = 'INVALID_SHOP',\n}", "members": [ { - "filePath": "/server/types.ts", + "filePath": "src/server/types.ts", "name": "MissingShop", "value": "MISSING_SHOP" }, { - "filePath": "/server/types.ts", + "filePath": "src/server/types.ts", "name": "InvalidShop", "value": "INVALID_SHOP" } ] }, "AppStoreApp": { - "filePath": "/server/types.ts", + "filePath": "src/server/types.ts", "syntaxKind": "TypeAliasDeclaration", "name": "AppStoreApp", "value": "ShopifyAppBase & ShopifyAppLogin", @@ -5969,30 +14442,28 @@ "type": "FutureFlags", "typeDefinitions": { "FutureFlags": { - "filePath": "/server/future/flags.ts", + "filePath": "../../node_modules/@shopify/shopify-api/future/flags.d.ts", "name": "FutureFlags", "description": "", "members": [ { - "filePath": "/server/future/flags.ts", + "filePath": "../../node_modules/@shopify/shopify-api/future/flags.d.ts", "syntaxKind": "PropertySignature", - "name": "v3_webhookAdminContext", + "name": "unstable_tokenExchange", "value": "boolean", - "description": "When enabled, returns the same `admin` context (`AdminApiContext`) from `authenticate.webhook` that is returned from `authenticate.admin`.", - "isOptional": true, - "defaultValue": "false" + "description": "", + "isOptional": true }, { - "filePath": "/server/future/flags.ts", + "filePath": "../../node_modules/@shopify/shopify-api/future/flags.d.ts", "syntaxKind": "PropertySignature", - "name": "v3_authenticatePublic", + "name": "unstable_lineItemBilling", "value": "boolean", - "description": "When enabled authenticate.public() will not work. Use authenticate.public.checkout() instead.", - "isOptional": true, - "defaultValue": "false" + "description": "", + "isOptional": true } ], - "value": "export interface FutureFlags {\n /**\n * When enabled, returns the same `admin` context (`AdminApiContext`) from `authenticate.webhook` that is returned from `authenticate.admin`.\n *\n * @default false\n */\n v3_webhookAdminContext?: boolean;\n\n /**\n * When enabled authenticate.public() will not work. Use authenticate.public.checkout() instead.\n *\n * @default false\n */\n v3_authenticatePublic?: boolean;\n}" + "value": "export interface FutureFlags {\n unstable_tokenExchange?: boolean;\n unstable_lineItemBilling?: boolean;\n}" } } } @@ -6174,7 +14645,7 @@ "type": "GetUnauthenticatedAdminContext", "typeDefinitions": { "GetUnauthenticatedAdminContext": { - "filePath": "/server/unauthenticated/admin/types.ts", + "filePath": "src/server/unauthenticated/admin/types.ts", "name": "GetUnauthenticatedAdminContext", "description": "", "params": [ @@ -6182,11 +14653,11 @@ "name": "shop", "description": "", "value": "string", - "filePath": "/server/unauthenticated/admin/types.ts" + "filePath": "src/server/unauthenticated/admin/types.ts" } ], "returns": { - "filePath": "/server/unauthenticated/admin/types.ts", + "filePath": "src/server/unauthenticated/admin/types.ts", "description": "", "name": "Promise>", "value": "Promise>" @@ -6194,12 +14665,12 @@ "value": "export type GetUnauthenticatedAdminContext<\n Resources extends ShopifyRestResources,\n> = (shop: string) => Promise>;" }, "UnauthenticatedAdminContext": { - "filePath": "/server/unauthenticated/admin/types.ts", + "filePath": "src/server/unauthenticated/admin/types.ts", "name": "UnauthenticatedAdminContext", "description": "", "members": [ { - "filePath": "/server/unauthenticated/admin/types.ts", + "filePath": "src/server/unauthenticated/admin/types.ts", "syntaxKind": "PropertySignature", "name": "session", "value": "Session", @@ -6218,7 +14689,7 @@ ] }, { - "filePath": "/server/unauthenticated/admin/types.ts", + "filePath": "src/server/unauthenticated/admin/types.ts", "syntaxKind": "PropertySignature", "name": "admin", "value": "AdminApiContext", @@ -6227,13 +14698,252 @@ ], "value": "export interface UnauthenticatedAdminContext<\n Resources extends ShopifyRestResources,\n> {\n /**\n * The session for the given shop.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n *\n * This will always be an offline session. You can use to get shop-specific data.\n *\n * @example\n * Using the offline session.\n * Get your app's shop-specific data using the returned offline `session` object.\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { unauthenticated } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const shop = getShopFromExternalRequest(request);\n * const { session } = await unauthenticated.admin(shop);\n * return json(await getMyAppData({shop: session.shop));\n * };\n * ```\n */\n session: Session;\n\n /**\n * Methods for interacting with the GraphQL / REST Admin APIs for the given store.\n */\n admin: AdminApiContext;\n}" }, + "Session": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "name": "Session", + "description": "Stores App information from logged in merchants so they can make authenticated requests to the Admin API.", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "shop", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "state", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "isOnline", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "scope", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "expires", + "value": "Date", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "accessToken", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "onlineAccessInfo", + "value": "OnlineAccessInfo", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isActive", + "value": "(scopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isScopeChanged", + "value": "(scopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isExpired", + "value": "(withinMillisecondsOfExpiry?: number) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toObject", + "value": "() => SessionParams", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "equals", + "value": "(other: Session) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toPropertyArray", + "value": "() => [string, string | number | boolean][]", + "description": "" + } + ], + "value": "export declare class Session {\n static fromPropertyArray(entries: [string, string | number | boolean][]): Session;\n readonly id: string;\n shop: string;\n state: string;\n isOnline: boolean;\n scope?: string;\n expires?: Date;\n accessToken?: string;\n onlineAccessInfo?: OnlineAccessInfo;\n constructor(params: SessionParams);\n isActive(scopes: AuthScopes | string | string[]): boolean;\n isScopeChanged(scopes: AuthScopes | string | string[]): boolean;\n isExpired(withinMillisecondsOfExpiry?: number): boolean;\n toObject(): SessionParams;\n equals(other: Session | undefined): boolean;\n toPropertyArray(): [string, string | number | boolean][];\n}" + }, + "OnlineAccessInfo": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "name": "OnlineAccessInfo", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "expires_in", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "associated_user_scope", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "associated_user", + "value": "{ id: number; first_name: string; last_name: string; email: string; email_verified: boolean; account_owner: boolean; locale: string; collaborator: boolean; }", + "description": "" + } + ], + "value": "export interface OnlineAccessInfo {\n expires_in: number;\n associated_user_scope: string;\n associated_user: {\n id: number;\n first_name: string;\n last_name: string;\n email: string;\n email_verified: boolean;\n account_owner: boolean;\n locale: string;\n collaborator: boolean;\n };\n}" + }, + "AuthScopes": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "name": "AuthScopes", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "has", + "value": "(scope: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "equals", + "value": "(otherScopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toString", + "value": "() => string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toArray", + "value": "() => string[]", + "description": "" + } + ], + "value": "declare class AuthScopes {\n static SCOPE_DELIMITER: string;\n private compressedScopes;\n private expandedScopes;\n constructor(scopes: string | string[] | AuthScopes | undefined);\n has(scope: string | string[] | AuthScopes | undefined): boolean;\n equals(otherScopes: string | string[] | AuthScopes | undefined): boolean;\n toString(): string;\n toArray(): string[];\n private getImpliedScopes;\n}" + }, + "SessionParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "name": "SessionParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "shop", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "state", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "isOnline", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "scope", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "expires", + "value": "Date", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "accessToken", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "onlineAccessInfo", + "value": "OnlineAccessInfo", + "description": "", + "isOptional": true + } + ], + "value": "export interface SessionParams {\n readonly id: string;\n shop: string;\n state: string;\n isOnline: boolean;\n scope?: string;\n expires?: Date;\n accessToken?: string;\n onlineAccessInfo?: OnlineAccessInfo;\n}" + }, "AdminApiContext": { - "filePath": "/server/clients/admin/types.ts", + "filePath": "src/server/clients/admin/types.ts", "name": "AdminApiContext", "description": "", "members": [ { - "filePath": "/server/clients/admin/types.ts", + "filePath": "src/server/clients/admin/types.ts", "syntaxKind": "PropertySignature", "name": "rest", "value": "RestClientWithResources", @@ -6284,33 +14994,279 @@ ] }, { - "filePath": "/server/clients/admin/types.ts", + "filePath": "src/server/clients/admin/types.ts", + "syntaxKind": "PropertySignature", + "name": "graphql", + "value": "GraphQLClient", + "description": "Methods for interacting with the Shopify Admin GraphQL API\n\n\n\n\n\n\n\n\n\n", + "examples": [ + { + "title": "Querying the GraphQL API", + "description": "Use `admin.graphql` to make query / mutation requests.", + "tabs": [ + { + "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.admin(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", + "title": "Example" + } + ] + } + ] + } + ], + "value": "export interface AdminApiContext<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * Methods for interacting with the Shopify Admin REST API\n *\n * There are methods for interacting with individual REST resources. You can also make `GET`, `POST`, `PUT` and `DELETE` requests should the REST resources not meet your needs.\n *\n * {@link https://shopify.dev/docs/api/admin-rest}\n *\n * @example\n * Using REST resources.\n * Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource. \n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * return json(admin.rest.resources.Order.count({ session }));\n * };\n * ```\n *\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n *\n * @example\n * Performing a GET request to the REST API.\n * Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint\n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = await admin.rest.get({ path: \"/customers/count.json\" });\n * const customers = await response.json();\n * return json({ customers });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * @example\n * Performing a POST request to the REST API.\n * Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = admin.rest.post({\n * path: \"customers/7392136888625/send_invite.json\",\n * body: {\n * customer_invite: {\n * to: \"new_test_email@shopify.com\",\n * from: \"j.limited@example.com\",\n * bcc: [\"j.limited@example.com\"],\n * subject: \"Welcome to my new shop\",\n * custom_message: \"My awesome new store\",\n * },\n * },\n * });\n * const customerInvite = await response.json();\n * return json({ customerInvite });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n rest: RestClientWithResources;\n\n /**\n * Methods for interacting with the Shopify Admin GraphQL API\n *\n * {@link https://shopify.dev/docs/api/admin-graphql}\n * {@link https://github.com/Shopify/shopify-api-js/blob/main/packages/shopify-api/docs/reference/clients/Graphql.md}\n *\n * @example\n * Querying the GraphQL API.\n * Use `admin.graphql` to make query / mutation requests.\n * ```ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.admin(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n graphql: GraphQLClient;\n}" + }, + "RestClientWithResources": { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "RestClientWithResources", + "value": "RemixRestClient & {resources: Resources}", + "description": "" + }, + "RemixRestClient": { + "filePath": "src/server/clients/admin/rest.ts", + "name": "RemixRestClient", + "description": "", + "members": [ + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "PropertyDeclaration", + "name": "session", + "value": "Session", + "description": "" + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "get", + "value": "(params: GetRequestParams) => Promise", + "description": "Performs a GET request on the given path." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "post", + "value": "(params: PostRequestParams) => Promise", + "description": "Performs a POST request on the given path." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "put", + "value": "(params: PostRequestParams) => Promise", + "description": "Performs a PUT request on the given path." + }, + { + "filePath": "src/server/clients/admin/rest.ts", + "syntaxKind": "MethodDeclaration", + "name": "delete", + "value": "(params: GetRequestParams) => Promise", + "description": "Performs a DELETE request on the given path." + } + ], + "value": "class RemixRestClient {\n public session: Session;\n private params: AdminClientOptions['params'];\n private handleClientError: AdminClientOptions['handleClientError'];\n\n constructor({params, session, handleClientError}: AdminClientOptions) {\n this.params = params;\n this.handleClientError = handleClientError;\n this.session = session;\n }\n\n /**\n * Performs a GET request on the given path.\n */\n public async get(params: GetRequestParams) {\n return this.makeRequest({\n method: 'GET' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a POST request on the given path.\n */\n public async post(params: PostRequestParams) {\n return this.makeRequest({\n method: 'POST' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a PUT request on the given path.\n */\n public async put(params: PutRequestParams) {\n return this.makeRequest({\n method: 'PUT' as RequestParams['method'],\n ...params,\n });\n }\n\n /**\n * Performs a DELETE request on the given path.\n */\n public async delete(params: DeleteRequestParams) {\n return this.makeRequest({\n method: 'DELETE' as RequestParams['method'],\n ...params,\n });\n }\n\n protected async makeRequest(params: RequestParams): Promise {\n const originalClient = new this.params.api.clients.Rest({\n session: this.session,\n });\n const originalRequest = Reflect.get(originalClient, 'request');\n\n try {\n const apiResponse = await originalRequest.call(originalClient, params);\n\n // We use a separate client for REST requests and REST resources because we want to override the API library\n // client class to return a Response object instead.\n return new Response(JSON.stringify(apiResponse.body), {\n headers: apiResponse.headers,\n });\n } catch (error) {\n if (this.handleClientError) {\n throw await this.handleClientError({\n error,\n session: this.session,\n params: this.params,\n });\n } else throw new Error(error);\n }\n }\n}" + }, + "GetRequestParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "GetRequestParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "path", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "type", + "value": "DataType", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "data", + "value": "string | Record", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "query", + "value": "SearchParams", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "extraHeaders", + "value": "HeaderParams", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "tries", + "value": "number", + "description": "", + "isOptional": true + } + ], + "value": "export interface GetRequestParams {\n path: string;\n type?: DataType;\n data?: Record | string;\n query?: SearchParams;\n extraHeaders?: HeaderParams;\n tries?: number;\n}" + }, + "DataType": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "DataType", + "value": "export declare enum DataType {\n JSON = \"application/json\",\n GraphQL = \"application/graphql\",\n URLEncoded = \"application/x-www-form-urlencoded\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "JSON", + "value": "application/json" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "GraphQL", + "value": "application/graphql" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "name": "URLEncoded", + "value": "application/x-www-form-urlencoded" + } + ] + }, + "HeaderParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "HeaderParams", + "value": "Record", + "description": "", + "members": [] + }, + "PostRequestParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/clients/types.d.ts", + "syntaxKind": "TypeAliasDeclaration", + "name": "PostRequestParams", + "value": "GetRequestParams & {\n data: Record | string;\n}", + "description": "" + }, + "GraphQLClient": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLClient", + "description": "", + "params": [ + { + "name": "query", + "description": "", + "value": "Operation extends keyof Operations", + "filePath": "src/server/clients/types.ts" + }, + { + "name": "options", + "description": "", + "value": "GraphQLQueryOptions", + "isOptional": true, + "filePath": "src/server/clients/types.ts" + } + ], + "returns": { + "filePath": "src/server/clients/types.ts", + "description": "", + "name": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}", + "value": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}" + }, + "value": "export type GraphQLClient = <\n Operation extends keyof Operations,\n>(\n query: Operation,\n options?: GraphQLQueryOptions,\n) => Promise<\n ResponseWithType>>\n>;" + }, + "GraphQLQueryOptions": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLQueryOptions", + "description": "", + "members": [ + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "variables", + "value": "ApiClientRequestOptions[\"variables\"]", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", "syntaxKind": "PropertySignature", - "name": "graphql", - "value": "GraphQLClient", - "description": "Methods for interacting with the Shopify Admin GraphQL API\n\n\n\n\n\n\n\n\n\n", - "examples": [ - { - "title": "Querying the GraphQL API", - "description": "Use `admin.graphql` to make query / mutation requests.", - "tabs": [ - { - "code": "import { ActionFunctionArgs } from \"@remix-run/node\";\nimport { authenticate } from \"../shopify.server\";\n\nexport async function action({ request }: ActionFunctionArgs) {\n const { admin } = await authenticate.admin(request);\n\n const response = await admin.graphql(\n `#graphql\n mutation populateProduct($input: ProductInput!) {\n productCreate(input: $input) {\n product {\n id\n }\n }\n }`,\n { variables: { input: { title: \"Product Name\" } } }\n );\n\n const productData = await response.json();\n return json({ data: productData.data });\n}", - "title": "Example" - } - ] - } - ] + "name": "apiVersion", + "value": "ApiVersion", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "headers", + "value": "{ [key: string]: any; }", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "tries", + "value": "number", + "description": "", + "isOptional": true } ], - "value": "export interface AdminApiContext<\n Resources extends ShopifyRestResources = ShopifyRestResources,\n> {\n /**\n * Methods for interacting with the Shopify Admin REST API\n *\n * There are methods for interacting with individual REST resources. You can also make `GET`, `POST`, `PUT` and `DELETE` requests should the REST resources not meet your needs.\n *\n * {@link https://shopify.dev/docs/api/admin-rest}\n *\n * @example\n * Using REST resources.\n * Getting the number of orders in a store using REST resources. Visit the [Admin REST API references](/docs/api/admin-rest) for examples on using each resource. \n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * return json(admin.rest.resources.Order.count({ session }));\n * };\n * ```\n *\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-07\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n *\n *\n * @example\n * Performing a GET request to the REST API.\n * Use `admin.rest.get` to make custom requests to make a request to to the `customer/count` endpoint\n *\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = await admin.rest.get({ path: \"/customers/count.json\" });\n * const customers = await response.json();\n * return json({ customers });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n * @example\n * Performing a POST request to the REST API.\n * Use `admin.rest.post` to make custom requests to make a request to to the `customers.json` endpoint to send a welcome email\n * ```ts\n * // /app/routes/**\\/*.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const { admin, session } = await authenticate.admin(request);\n * const response = admin.rest.post({\n * path: \"customers/7392136888625/send_invite.json\",\n * body: {\n * customer_invite: {\n * to: \"new_test_email@shopify.com\",\n * from: \"j.limited@example.com\",\n * bcc: [\"j.limited@example.com\"],\n * subject: \"Welcome to my new shop\",\n * custom_message: \"My awesome new store\",\n * },\n * },\n * });\n * const customerInvite = await response.json();\n * return json({ customerInvite });\n * };\n * ```\n *\n * ```ts\n * // /app/shopify.server.ts\n * import { shopifyApp } from \"@shopify/shopify-app-remix/server\";\n * import { restResources } from \"@shopify/shopify-api/rest/admin/2023-04\";\n *\n * const shopify = shopifyApp({\n * restResources,\n * // ...etc\n * });\n * export default shopify;\n * export const authenticate = shopify.authenticate;\n * ```\n */\n rest: RestClientWithResources;\n\n /**\n * Methods for interacting with the Shopify Admin GraphQL API\n *\n * {@link https://shopify.dev/docs/api/admin-graphql}\n * {@link https://github.com/Shopify/shopify-api-js/blob/main/packages/shopify-api/docs/reference/clients/Graphql.md}\n *\n * @example\n * Querying the GraphQL API.\n * Use `admin.graphql` to make query / mutation requests.\n * ```ts\n * import { ActionFunctionArgs } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { admin } = await authenticate.admin(request);\n *\n * const response = await admin.graphql(\n * `#graphql\n * mutation populateProduct($input: ProductInput!) {\n * productCreate(input: $input) {\n * product {\n * id\n * }\n * }\n * }`,\n * { variables: { input: { title: \"Product Name\" } } }\n * );\n *\n * const productData = await response.json();\n * return json({ data: productData.data });\n * }\n * ```\n */\n graphql: GraphQLClient;\n}" + "value": "export interface GraphQLQueryOptions<\n Operation extends keyof Operations,\n Operations extends AllOperations,\n> {\n variables?: ApiClientRequestOptions['variables'];\n apiVersion?: ApiVersion;\n headers?: {[key: string]: any};\n tries?: number;\n}" }, - "RestClientWithResources": { - "filePath": "/server/clients/admin/rest.ts", - "syntaxKind": "TypeAliasDeclaration", - "name": "RestClientWithResources", - "value": "RemixRestClient & {resources: Resources}", - "description": "" + "ApiVersion": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "ApiVersion", + "value": "export declare enum ApiVersion {\n October22 = \"2022-10\",\n January23 = \"2023-01\",\n April23 = \"2023-04\",\n July23 = \"2023-07\",\n October23 = \"2023-10\",\n January24 = \"2024-01\",\n Unstable = \"unstable\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "October22", + "value": "2022-10" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "January23", + "value": "2023-01" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "April23", + "value": "2023-04" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "July23", + "value": "2023-07" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "October23", + "value": "2023-10" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "January24", + "value": "2024-01" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Unstable", + "value": "unstable" + } + ] } } } @@ -6440,7 +15396,7 @@ "type": "GetUnauthenticatedStorefrontContext", "typeDefinitions": { "GetUnauthenticatedStorefrontContext": { - "filePath": "/server/unauthenticated/storefront/types.ts", + "filePath": "src/server/unauthenticated/storefront/types.ts", "name": "GetUnauthenticatedStorefrontContext", "description": "", "params": [ @@ -6448,11 +15404,11 @@ "name": "shop", "description": "", "value": "string", - "filePath": "/server/unauthenticated/storefront/types.ts" + "filePath": "src/server/unauthenticated/storefront/types.ts" } ], "returns": { - "filePath": "/server/unauthenticated/storefront/types.ts", + "filePath": "src/server/unauthenticated/storefront/types.ts", "description": "", "name": "Promise", "value": "Promise" @@ -6460,12 +15416,12 @@ "value": "export type GetUnauthenticatedStorefrontContext = (\n shop: string,\n) => Promise;" }, "UnauthenticatedStorefrontContext": { - "filePath": "/server/unauthenticated/storefront/types.ts", + "filePath": "src/server/unauthenticated/storefront/types.ts", "name": "UnauthenticatedStorefrontContext", "description": "", "members": [ { - "filePath": "/server/unauthenticated/storefront/types.ts", + "filePath": "src/server/unauthenticated/storefront/types.ts", "syntaxKind": "PropertySignature", "name": "session", "value": "Session", @@ -6484,7 +15440,7 @@ ] }, { - "filePath": "/server/unauthenticated/storefront/types.ts", + "filePath": "src/server/unauthenticated/storefront/types.ts", "syntaxKind": "PropertySignature", "name": "storefront", "value": "StorefrontContext", @@ -6493,13 +15449,252 @@ ], "value": "export interface UnauthenticatedStorefrontContext {\n /**\n * The session for the given shop.\n *\n * This comes from the session storage which `shopifyApp` uses to store sessions in your database of choice.\n *\n * This will always be an offline session. You can use this to get shop specific data.\n *\n * @example\n * Using the offline session.\n * Get your app's shop-specific data using the returned offline `session` object.\n * ```ts\n * // app/routes/**\\/.ts\n * import { LoaderFunctionArgs, json } from \"@remix-run/node\";\n * import { unauthenticated } from \"../shopify.server\";\n * import { getMyAppData } from \"~/db/model.server\";\n *\n * export const loader = async ({ request }: LoaderFunctionArgs) => {\n * const shop = getShopFromExternalRequest(request);\n * const { session } = await unauthenticated.storefront(shop);\n * return json(await getMyAppData({shop: session.shop));\n * };\n * ```\n */\n session: Session;\n\n /**\n * Method for interacting with the Shopify GraphQL Storefront API for the given store.\n */\n storefront: StorefrontContext;\n}" }, + "Session": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "name": "Session", + "description": "Stores App information from logged in merchants so they can make authenticated requests to the Admin API.", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "shop", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "state", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "isOnline", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "scope", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "expires", + "value": "Date", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "accessToken", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "PropertyDeclaration", + "name": "onlineAccessInfo", + "value": "OnlineAccessInfo", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isActive", + "value": "(scopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isScopeChanged", + "value": "(scopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "isExpired", + "value": "(withinMillisecondsOfExpiry?: number) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toObject", + "value": "() => SessionParams", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "equals", + "value": "(other: Session) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/session.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toPropertyArray", + "value": "() => [string, string | number | boolean][]", + "description": "" + } + ], + "value": "export declare class Session {\n static fromPropertyArray(entries: [string, string | number | boolean][]): Session;\n readonly id: string;\n shop: string;\n state: string;\n isOnline: boolean;\n scope?: string;\n expires?: Date;\n accessToken?: string;\n onlineAccessInfo?: OnlineAccessInfo;\n constructor(params: SessionParams);\n isActive(scopes: AuthScopes | string | string[]): boolean;\n isScopeChanged(scopes: AuthScopes | string | string[]): boolean;\n isExpired(withinMillisecondsOfExpiry?: number): boolean;\n toObject(): SessionParams;\n equals(other: Session | undefined): boolean;\n toPropertyArray(): [string, string | number | boolean][];\n}" + }, + "OnlineAccessInfo": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "name": "OnlineAccessInfo", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "expires_in", + "value": "number", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "associated_user_scope", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/oauth/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "associated_user", + "value": "{ id: number; first_name: string; last_name: string; email: string; email_verified: boolean; account_owner: boolean; locale: string; collaborator: boolean; }", + "description": "" + } + ], + "value": "export interface OnlineAccessInfo {\n expires_in: number;\n associated_user_scope: string;\n associated_user: {\n id: number;\n first_name: string;\n last_name: string;\n email: string;\n email_verified: boolean;\n account_owner: boolean;\n locale: string;\n collaborator: boolean;\n };\n}" + }, + "AuthScopes": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "name": "AuthScopes", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "has", + "value": "(scope: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "equals", + "value": "(otherScopes: string | string[] | AuthScopes) => boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toString", + "value": "() => string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/auth/scopes/index.d.ts", + "syntaxKind": "MethodDeclaration", + "name": "toArray", + "value": "() => string[]", + "description": "" + } + ], + "value": "declare class AuthScopes {\n static SCOPE_DELIMITER: string;\n private compressedScopes;\n private expandedScopes;\n constructor(scopes: string | string[] | AuthScopes | undefined);\n has(scope: string | string[] | AuthScopes | undefined): boolean;\n equals(otherScopes: string | string[] | AuthScopes | undefined): boolean;\n toString(): string;\n toArray(): string[];\n private getImpliedScopes;\n}" + }, + "SessionParams": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "name": "SessionParams", + "description": "", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "id", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "shop", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "state", + "value": "string", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "isOnline", + "value": "boolean", + "description": "" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "scope", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "expires", + "value": "Date", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "accessToken", + "value": "string", + "description": "", + "isOptional": true + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/session/types.d.ts", + "syntaxKind": "PropertySignature", + "name": "onlineAccessInfo", + "value": "OnlineAccessInfo", + "description": "", + "isOptional": true + } + ], + "value": "export interface SessionParams {\n readonly id: string;\n shop: string;\n state: string;\n isOnline: boolean;\n scope?: string;\n expires?: Date;\n accessToken?: string;\n onlineAccessInfo?: OnlineAccessInfo;\n}" + }, "StorefrontContext": { - "filePath": "/server/clients/storefront/types.ts", + "filePath": "src/server/clients/storefront/types.ts", "name": "StorefrontContext", "description": "", "members": [ { - "filePath": "/server/clients/storefront/types.ts", + "filePath": "src/server/clients/storefront/types.ts", "syntaxKind": "PropertySignature", "name": "graphql", "value": "GraphQLClient", @@ -6519,6 +15714,116 @@ } ], "value": "export interface StorefrontContext {\n /**\n * Method for interacting with the Shopify Storefront GraphQL API\n *\n * If you're getting incorrect type hints in the Shopify template, follow [these instructions](https://github.com/Shopify/shopify-app-template-remix/tree/main#incorrect-graphql-hints).\n *\n * {@link https://shopify.dev/docs/api/storefront}\n *\n * @example\n * Querying the GraphQL API.\n * Use `storefront.graphql` to make query / mutation requests.\n * ```ts\n * // app/routes/**\\/.ts\n * import { json } from \"@remix-run/node\";\n * import { authenticate } from \"../shopify.server\";\n *\n * export async function action({ request }: ActionFunctionArgs) {\n * const { storefront } = await authenticate.public.appProxy(request);\n *\n * const response = await storefront.graphql(`{blogs(first: 10) { edges { node { id } } } }`);\n *\n * return json(await response.json());\n * }\n * ```\n */\n graphql: GraphQLClient;\n}" + }, + "GraphQLClient": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLClient", + "description": "", + "params": [ + { + "name": "query", + "description": "", + "value": "Operation extends keyof Operations", + "filePath": "src/server/clients/types.ts" + }, + { + "name": "options", + "description": "", + "value": "GraphQLQueryOptions", + "isOptional": true, + "filePath": "src/server/clients/types.ts" + } + ], + "returns": { + "filePath": "src/server/clients/types.ts", + "description": "", + "name": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}", + "value": "interface Promise {\n /**\n * Attaches callbacks for the resolution and/or rejection of the Promise.\n * @param onfulfilled The callback to execute when the Promise is resolved.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of which ever callback is executed.\n */\n then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise;\n\n /**\n * Attaches a callback for only the rejection of the Promise.\n * @param onrejected The callback to execute when the Promise is rejected.\n * @returns A Promise for the completion of the callback.\n */\n catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise;\n}, interface Promise {}, Promise: PromiseConstructor, interface Promise {\n readonly [Symbol.toStringTag]: string;\n}, interface Promise {\n /**\n * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The\n * resolved value cannot be modified from the callback.\n * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected).\n * @returns A Promise for the completion of the callback.\n */\n finally(onfinally?: (() => void) | undefined | null): Promise;\n}" + }, + "value": "export type GraphQLClient = <\n Operation extends keyof Operations,\n>(\n query: Operation,\n options?: GraphQLQueryOptions,\n) => Promise<\n ResponseWithType>>\n>;" + }, + "GraphQLQueryOptions": { + "filePath": "src/server/clients/types.ts", + "name": "GraphQLQueryOptions", + "description": "", + "members": [ + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "variables", + "value": "ApiClientRequestOptions[\"variables\"]", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "apiVersion", + "value": "ApiVersion", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "headers", + "value": "{ [key: string]: any; }", + "description": "", + "isOptional": true + }, + { + "filePath": "src/server/clients/types.ts", + "syntaxKind": "PropertySignature", + "name": "tries", + "value": "number", + "description": "", + "isOptional": true + } + ], + "value": "export interface GraphQLQueryOptions<\n Operation extends keyof Operations,\n Operations extends AllOperations,\n> {\n variables?: ApiClientRequestOptions['variables'];\n apiVersion?: ApiVersion;\n headers?: {[key: string]: any};\n tries?: number;\n}" + }, + "ApiVersion": { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "syntaxKind": "EnumDeclaration", + "name": "ApiVersion", + "value": "export declare enum ApiVersion {\n October22 = \"2022-10\",\n January23 = \"2023-01\",\n April23 = \"2023-04\",\n July23 = \"2023-07\",\n October23 = \"2023-10\",\n January24 = \"2024-01\",\n Unstable = \"unstable\"\n}", + "members": [ + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "October22", + "value": "2022-10" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "January23", + "value": "2023-01" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "April23", + "value": "2023-04" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "July23", + "value": "2023-07" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "October23", + "value": "2023-10" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "January24", + "value": "2024-01" + }, + { + "filePath": "../../node_modules/@shopify/shopify-api/lib/types.d.ts", + "name": "Unstable", + "value": "unstable" + } + ] } } } diff --git a/packages/shopify-app-remix/package.json b/packages/shopify-app-remix/package.json index d59a0102fc..dc9c2488cc 100644 --- a/packages/shopify-app-remix/package.json +++ b/packages/shopify-app-remix/package.json @@ -51,7 +51,7 @@ ], "devDependencies": { "@remix-run/react": "^2.5.0", - "@shopify/generate-docs": "^0.11.1", + "@shopify/generate-docs": "^0.13.1", "@shopify/polaris": "^11.8.0", "@shopify/react-testing": "^5.1.3", "@shopify/shopify-app-session-storage-memory": "^2.0.3", diff --git a/yarn.lock b/yarn.lock index 73d520f74e..b4483d0af0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2839,14 +2839,14 @@ pkg-dir "^5.0.0" pluralize "^8.0.0" -"@shopify/generate-docs@^0.11.1": - version "0.11.1" - resolved "https://registry.yarnpkg.com/@shopify/generate-docs/-/generate-docs-0.11.1.tgz#4b4d159cb5578dc98fa4218bbc9edade1872d0f1" - integrity sha512-pZxCkUtO6MXJaetEdd4Tyk6t7tjJTUNd3VGCXyOeZqKAWrXEob+8gkost0x23EmuES4GGqQKSB9nnKdFvbdk1w== +"@shopify/generate-docs@^0.13.1": + version "0.13.1" + resolved "https://registry.yarnpkg.com/@shopify/generate-docs/-/generate-docs-0.13.1.tgz#c2f41671c488a70e4bf91648ffc5631c56febc3e" + integrity sha512-V3vk6oemlqX/1joFYLPjvzephgVTzbuysw9lig7UgSbuMah0kXq+VSvF9IxgQMnfloTT32nWbkBtl+AJTQurtw== dependencies: "@types/react" "^18.0.21" globby "^11.1.0" - typescript "^4.8.3" + typescript "^4.8.3 || ^5.0.0" "@shopify/graphql-client@^0.9.1": version "0.9.1" @@ -3718,9 +3718,9 @@ csstype "^3.0.2" "@types/react@^18.0.21": - version "18.2.21" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.21.tgz#774c37fd01b522d0b91aed04811b58e4e0514ed9" - integrity sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA== + version "18.2.48" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.48.tgz#11df5664642d0bd879c1f58bc1d37205b064e8f1" + integrity sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -10197,11 +10197,16 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@4.9.5, typescript@^4.3.5, typescript@^4.8.3, typescript@^4.9.5: +typescript@4.9.5, typescript@^4.3.5, typescript@^4.9.5: version "4.9.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +"typescript@^4.8.3 || ^5.0.0": + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" From cd654fb0e2fea30c1d162b46658ecd3135708812 Mon Sep 17 00:00:00 2001 From: Rachel Carvalho Date: Wed, 29 Nov 2023 11:54:13 -0500 Subject: [PATCH 02/41] add unstable_newEmbeddedAuthStrateg future flag --- .../src/server/__test-helpers/test-config.ts | 1 + packages/shopify-app-remix/src/server/future/flags.ts | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/packages/shopify-app-remix/src/server/__test-helpers/test-config.ts b/packages/shopify-app-remix/src/server/__test-helpers/test-config.ts index d323b072b7..9f9104f793 100644 --- a/packages/shopify-app-remix/src/server/__test-helpers/test-config.ts +++ b/packages/shopify-app-remix/src/server/__test-helpers/test-config.ts @@ -21,6 +21,7 @@ import {API_KEY, API_SECRET_KEY, APP_URL} from './const'; const TEST_FUTURE_FLAGS: Required<{[key in keyof FutureFlags]: true}> = { v3_authenticatePublic: true, v3_webhookAdminContext: true, + unstable_newEmbeddedAuthStrategy: true, } as const; const TEST_CONFIG = { diff --git a/packages/shopify-app-remix/src/server/future/flags.ts b/packages/shopify-app-remix/src/server/future/flags.ts index 8b8f1ccfd7..d7d0b7b9f6 100644 --- a/packages/shopify-app-remix/src/server/future/flags.ts +++ b/packages/shopify-app-remix/src/server/future/flags.ts @@ -14,6 +14,13 @@ export interface FutureFlags { * @default false */ v3_authenticatePublic?: boolean; + + /** + * TODO + * + * @default false + */ + unstable_newEmbeddedAuthStrategy?: boolean; } export type FutureFlagOptions = FutureFlags | undefined; From 05103bd788bc58c384b77fdbf9d9d8b4a437c610 Mon Sep 17 00:00:00 2001 From: Rachel Carvalho Date: Wed, 29 Nov 2023 13:44:37 -0500 Subject: [PATCH 03/41] add token exchange strategy --- .../server/authenticate/admin/authenticate.ts | 18 ++- .../admin/helpers/redirect-to-bounce-page.ts | 7 ++ .../admin/strategies/auth-code-flow.ts | 7 +- .../admin/strategies/token-exchange.ts | 113 ++++++++++++++++++ .../authenticate/admin/strategies/types.ts | 10 +- .../src/server/authenticate/admin/types.ts | 5 - .../src/server/authenticate/helpers/index.ts | 1 + .../respond-to-invalid-session-token.ts | 21 ++++ 8 files changed, 162 insertions(+), 20 deletions(-) create mode 100644 packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts create mode 100644 packages/shopify-app-remix/src/server/authenticate/helpers/respond-to-invalid-session-token.ts diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts b/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts index 87689cd97f..664b6786a0 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts @@ -116,21 +116,19 @@ export function authStrategyFactory< logger.info('Authenticating admin request'); - const {payload, shop, sessionId} = await getSessionTokenContext( - params, - request, - ); + const {payload, shop, sessionId, sessionToken} = + await getSessionTokenContext(params, request); logger.debug('Loading session from storage', {sessionId}); const existingSession = sessionId ? await config.sessionStorage.loadSession(sessionId) : undefined; - const session = await strategy.authenticate( - request, - existingSession, + const session = await strategy.authenticate(request, { + session: existingSession, + sessionToken, shop, - ); + }); logger.debug('Request is valid, loaded session from session token', { shop: session.shop, @@ -178,7 +176,7 @@ async function getSessionTokenContext( ? api.session.getJwtSessionId(shop, payload.sub) : api.session.getOfflineId(shop); - return {shop, payload, sessionId}; + return {shop, payload, sessionId, sessionToken}; } const url = new URL(request.url); @@ -189,5 +187,5 @@ async function getSessionTokenContext( rawRequest: request, }); - return {shop, sessionId, payload: undefined}; + return {shop, sessionId, payload: undefined, sessionToken}; } diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-bounce-page.ts b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-bounce-page.ts index 9797c34af2..e7b2f1dd55 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-bounce-page.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-bounce-page.ts @@ -7,6 +7,13 @@ export const redirectToBouncePage = (params: BasicParams, url: URL): never => { // Make sure we always point to the configured app URL so it also works behind reverse proxies (that alter the Host // header). + // const searchParams = new URLSearchParams(url.search); + // searchParams.delete('id_token'); + // searchParams.set( + // 'shopify-reload', + // `${config.appUrl}${url.pathname}?${searchParams.toString()}`, + // ); + url.searchParams.set( 'shopify-reload', `${config.appUrl}${url.pathname}${url.search}`, diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts index 20b87cbad1..b13e01a08c 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts @@ -21,7 +21,7 @@ import { import {AppConfig} from '../../../config-types'; import {getSessionTokenHeader} from '../../helpers'; -import {AuthorizationStrategy} from './types'; +import {AuthorizationStrategy, SessionContext} from './types'; export class AuthCodeFlowStrategy< Resources extends ShopifyRestResources = ShopifyRestResources, @@ -65,11 +65,12 @@ export class AuthCodeFlowStrategy< public async authenticate( request: Request, - session: Session | undefined, - shop: string, + sessionContext: SessionContext, ): Promise { const {api, config, logger} = this; + const {shop, session} = sessionContext; + if (!session) { logger.debug('No session found, redirecting to OAuth', {shop}); await redirectToAuthPage({config, logger, api}, request, shop); diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts new file mode 100644 index 0000000000..444ffe4421 --- /dev/null +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts @@ -0,0 +1,113 @@ +import { + HttpResponseError, + InvalidJwtError, + RequestedTokenType, + Session, + Shopify, + ShopifyRestResources, +} from '@shopify/shopify-api'; +import {AppConfig, AppConfigArg} from 'src/server/config-types'; +import {BasicParams} from 'src/server/types'; +import {MockApiConfig} from 'src/server/shopify-app'; + +import {triggerAfterAuthHook} from '../helpers'; +import {respondToInvalidSessionToken} from '../../helpers'; + +import {AuthorizationStrategy, SessionContext} from './types'; + +export class TokenExchangeStrategy< + Config extends AppConfigArg, + Resources extends ShopifyRestResources = ShopifyRestResources, +> implements AuthorizationStrategy +{ + protected api: Shopify>; + protected config: AppConfig; + protected logger: Shopify['logger']; + + public constructor({api, config, logger}: BasicParams) { + this.api = api; + this.config = config; + this.logger = logger; + } + + public async respondToOAuthRequests(_request: Request): Promise {} + + public async authenticate( + request: Request, + sessionContext: SessionContext, + ): Promise { + const {api, config, logger} = this; + const {shop, session, sessionToken} = sessionContext; + + if (!sessionToken) throw new InvalidJwtError(); + + if (!session /* || session.isExpired() */) { + logger.info('Requesting offline access token'); + const {session: offlineSession} = await this.exchangeToken({ + request, + sessionToken, + shop, + requestedTokenType: RequestedTokenType.OfflineAccessToken, + }); + + await config.sessionStorage.storeSession(offlineSession); + + let newSession = offlineSession; + + if (config.useOnlineTokens) { + logger.info('Requesting online access token'); + const {session: onlineSession} = await this.exchangeToken({ + request, + sessionToken, + shop, + requestedTokenType: RequestedTokenType.OnlineAccessToken, + }); + + await config.sessionStorage.storeSession(onlineSession); + newSession = onlineSession; + } + + await triggerAfterAuthHook( + {api, config, logger}, + newSession, + request, + ); + + return newSession; + } + + return session!; + } + + private async exchangeToken({ + request, + shop, + sessionToken, + requestedTokenType, + }: { + request: Request; + shop: string; + sessionToken: string; + requestedTokenType: RequestedTokenType; + }): Promise<{session: Session}> { + const {api, config, logger} = this; + + try { + return await api.auth.tokenExchange({ + sessionToken, + shop, + requestedTokenType, + }); + } catch (error) { + if ( + error instanceof InvalidJwtError || + (error instanceof HttpResponseError && + error.response.code === 400 && + error.response.body?.error === 'invalid_subject_token') + ) { + throw respondToInvalidSessionToken({api, config, logger}, request); + } + throw error; + } + } +} diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/types.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/types.ts index aa472df4c0..ecfd270567 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/types.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/types.ts @@ -3,14 +3,20 @@ import {JwtPayload, Session} from '@shopify/shopify-api'; export interface SessionTokenContext { shop: string; sessionId?: string; + sessionToken?: string; payload?: JwtPayload; } +export interface SessionContext { + shop: string; + session?: Session; + sessionToken?: string; +} + export interface AuthorizationStrategy { respondToOAuthRequests: (request: Request) => Promise; authenticate: ( request: Request, - session: Session | undefined, - shop: string, + sessionContext: SessionContext, ) => Promise; } diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/types.ts b/packages/shopify-app-remix/src/server/authenticate/admin/types.ts index 3000e937ef..96dedc4885 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/types.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/types.ts @@ -197,8 +197,3 @@ export type AuthenticateAdmin< Config extends AppConfigArg, Resources extends ShopifyRestResources = ShopifyRestResources, > = (request: Request) => Promise>; - -export interface SessionContext { - session: Session; - token?: JwtPayload; -} diff --git a/packages/shopify-app-remix/src/server/authenticate/helpers/index.ts b/packages/shopify-app-remix/src/server/authenticate/helpers/index.ts index 351221a750..383a37bd9a 100644 --- a/packages/shopify-app-remix/src/server/authenticate/helpers/index.ts +++ b/packages/shopify-app-remix/src/server/authenticate/helpers/index.ts @@ -5,3 +5,4 @@ export * from './validate-session-token'; export * from './get-session-token-header'; export * from './reject-bot-request'; export * from './respond-to-options-request'; +export * from './respond-to-invalid-session-token'; diff --git a/packages/shopify-app-remix/src/server/authenticate/helpers/respond-to-invalid-session-token.ts b/packages/shopify-app-remix/src/server/authenticate/helpers/respond-to-invalid-session-token.ts new file mode 100644 index 0000000000..6129abab13 --- /dev/null +++ b/packages/shopify-app-remix/src/server/authenticate/helpers/respond-to-invalid-session-token.ts @@ -0,0 +1,21 @@ +import {BasicParams} from 'src/server/types'; + +import {redirectToBouncePage} from '../admin/helpers/redirect-to-bounce-page'; + +export function respondToInvalidSessionToken( + params: BasicParams, + request: Request, +) { + const {api, logger, config} = params; + + const isDocumentRequest = !request.headers.get('authorization'); + if (isDocumentRequest) { + return redirectToBouncePage({api, logger, config}, new URL(request.url)); + } + + // TODO add header so app bridge retries with new session token + throw new Response(undefined, { + status: 401, + statusText: 'Unauthorized', + }); +} From 1dbfb62e2930280862f38b37a428a4552e256069 Mon Sep 17 00:00:00 2001 From: Rezaan Syed Date: Wed, 29 Nov 2023 15:23:42 -0500 Subject: [PATCH 04/41] Enable token exchange strategy when future flag is enabled --- .../server/authenticate/admin/strategies/token-exchange.ts | 2 +- packages/shopify-app-remix/src/server/shopify-app.ts | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts index 444ffe4421..20a9350bfd 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts @@ -41,7 +41,7 @@ export class TokenExchangeStrategy< if (!sessionToken) throw new InvalidJwtError(); - if (!session /* || session.isExpired() */) { + if (!session || session.isExpired()) { logger.info('Requesting offline access token'); const {session: offlineSession} = await this.exchangeToken({ request, diff --git a/packages/shopify-app-remix/src/server/shopify-app.ts b/packages/shopify-app-remix/src/server/shopify-app.ts index 4b55649a9b..2224ec1470 100644 --- a/packages/shopify-app-remix/src/server/shopify-app.ts +++ b/packages/shopify-app-remix/src/server/shopify-app.ts @@ -30,6 +30,7 @@ import {unauthenticatedAdminContextFactory} from './unauthenticated/admin'; import {authenticatePublicFactory} from './authenticate/public'; import {unauthenticatedStorefrontContextFactory} from './unauthenticated/storefront'; import {AuthCodeFlowStrategy} from './authenticate/admin/strategies/auth-code-flow'; +import {TokenExchangeStrategy} from './authenticate/admin/strategies/token-exchange'; /** * Creates an object your app will use to interact with Shopify. @@ -67,9 +68,13 @@ export function shopifyApp< const params: BasicParams = {api, config, logger}; const oauth = new AuthCodeFlowStrategy(params); + const tokenExchange = new TokenExchangeStrategy(params); const authStrategy = authStrategyFactory({ ...params, - strategy: oauth, + strategy: + config.future.unstable_tokenExchange && config.isEmbeddedApp + ? tokenExchange + : oauth, }); const shopify: From 8183077cc1ba8455ca50f36e24ac9d6ce3708863 Mon Sep 17 00:00:00 2001 From: Rachel Carvalho Date: Thu, 30 Nov 2023 10:42:14 -0500 Subject: [PATCH 05/41] setup auth code flow tests with future flag off --- .../src/server/__test-helpers/test-config.ts | 14 ++++++++ .../auth-callback-path.test.ts | 29 +++++++++-------- .../{auth-code-flow => }/auth-path.test.ts | 14 ++++---- .../{auth-code-flow => }/authenticate.test.ts | 28 ++++++++++------ .../ensure-installed-on-shop.test.ts | 32 ++++++++++--------- 5 files changed, 71 insertions(+), 46 deletions(-) rename packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/{auth-code-flow => }/auth-callback-path.test.ts (93%) rename packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/{auth-code-flow => }/auth-path.test.ts (85%) rename packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/{auth-code-flow => }/authenticate.test.ts (86%) rename packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/{auth-code-flow => }/ensure-installed-on-shop.test.ts (89%) diff --git a/packages/shopify-app-remix/src/server/__test-helpers/test-config.ts b/packages/shopify-app-remix/src/server/__test-helpers/test-config.ts index 9f9104f793..0f91063dc6 100644 --- a/packages/shopify-app-remix/src/server/__test-helpers/test-config.ts +++ b/packages/shopify-app-remix/src/server/__test-helpers/test-config.ts @@ -45,6 +45,20 @@ beforeEach(() => { (TEST_CONFIG as any).sessionStorage = new MemorySessionStorage(); }); +export function testConfigAuthCodeFlow< + Overrides extends TestOverridesArg, + Future extends FutureFlagOptions, +>( + {future, ...overrides}: Overrides & {future?: Future} = {} as Overrides & { + future?: Future; + }, +) { + return testConfig({ + ...overrides, + future: {...future, unstable_tokenExchange: false}, + }); +} + export function testConfig< Overrides extends TestOverridesArg, Future extends FutureFlagOptions, diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-code-flow/auth-callback-path.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-callback-path.test.ts similarity index 93% rename from packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-code-flow/auth-callback-path.test.ts rename to packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-callback-path.test.ts index 2d637d4833..32f0c553bc 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-code-flow/auth-callback-path.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-callback-path.test.ts @@ -1,6 +1,6 @@ import {HashFormat, createSHA256HMAC} from '@shopify/shopify-api/runtime'; -import {shopifyApp} from '../../../../../..'; +import {shopifyApp} from '../../../../..'; import { BASE64_HOST, TEST_SHOP, @@ -8,13 +8,14 @@ import { signRequestCookie, testConfig, mockExternalRequest, -} from '../../../../../../__test-helpers'; + testConfigAuthCodeFlow, +} from '../../../../../__test-helpers'; describe('authorize.admin auth callback path', () => { describe('errors', () => { test('throws an error if the shop param is missing', async () => { // GIVEN - const config = testConfig(); + const config = testConfigAuthCodeFlow(); const shopify = shopifyApp(config); // WHEN @@ -30,7 +31,7 @@ describe('authorize.admin auth callback path', () => { test('throws an error if the shop param is not valid', async () => { // GIVEN - const config = testConfig(); + const config = testConfigAuthCodeFlow(); const shopify = shopifyApp(config); // WHEN @@ -46,7 +47,7 @@ describe('authorize.admin auth callback path', () => { test('throws an 302 Response to begin auth if CookieNotFound error', async () => { // GIVEN - const config = testConfig(); + const config = testConfigAuthCodeFlow(); const shopify = shopifyApp(config); // WHEN @@ -71,7 +72,7 @@ describe('authorize.admin auth callback path', () => { test('throws a 400 if there is no HMAC param', async () => { // GIVEN - const config = testConfig(); + const config = testConfigAuthCodeFlow(); const shopify = shopifyApp(config); // WHEN @@ -98,7 +99,7 @@ describe('authorize.admin auth callback path', () => { test('throws a 400 if the HMAC param is invalid', async () => { // GIVEN - const config = testConfig(); + const config = testConfigAuthCodeFlow(); const shopify = shopifyApp(config); // WHEN @@ -126,7 +127,7 @@ describe('authorize.admin auth callback path', () => { test('throws a 500 if any other errors are thrown', async () => { // GIVEN - const config = testConfig(); + const config = testConfigAuthCodeFlow(); const shopify = shopifyApp({ ...config, hooks: { @@ -151,7 +152,7 @@ describe('authorize.admin auth callback path', () => { describe('Success states', () => { test('Exchanges the code for a token and saves it to SessionStorage', async () => { // GIVEN - const config = testConfig(); + const config = testConfigAuthCodeFlow(); const shopify = shopifyApp(config); // WHEN @@ -178,7 +179,7 @@ describe('authorize.admin auth callback path', () => { test('throws an 302 Response to begin auth if token was offline and useOnlineTokens is true', async () => { // GIVEN - const config = testConfig({useOnlineTokens: true}); + const config = testConfigAuthCodeFlow({useOnlineTokens: true}); const shopify = shopifyApp(config); // WHEN @@ -203,7 +204,7 @@ describe('authorize.admin auth callback path', () => { test('Does not throw a 302 Response to begin auth if token was online', async () => { // GIVEN - const config = testConfig({useOnlineTokens: true}); + const config = testConfigAuthCodeFlow({useOnlineTokens: true}); const shopify = shopifyApp(config); // WHEN @@ -221,7 +222,7 @@ describe('authorize.admin auth callback path', () => { test('Runs the afterAuth hooks passing', async () => { // GIVEN const afterAuthMock = jest.fn(); - const config = testConfig({ + const config = testConfigAuthCodeFlow({ hooks: { afterAuth: afterAuthMock, }, @@ -241,7 +242,7 @@ describe('authorize.admin auth callback path', () => { test('throws a 302 response to the emebdded app URL if isEmbeddedApp is true', async () => { // GIVEN - const config = testConfig(); + const config = testConfigAuthCodeFlow(); const shopify = shopifyApp(config); // WHEN @@ -260,7 +261,7 @@ describe('authorize.admin auth callback path', () => { test('throws a 302 to / if embedded is not true', async () => { // GIVEN - const config = testConfig({ + const config = testConfigAuthCodeFlow({ isEmbeddedApp: false, }); const shopify = shopifyApp(config); diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-code-flow/auth-path.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-path.test.ts similarity index 85% rename from packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-code-flow/auth-path.test.ts rename to packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-path.test.ts index 3108e1f569..7b7d3ee716 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-code-flow/auth-path.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-path.test.ts @@ -1,17 +1,17 @@ -import {shopifyApp} from '../../../../../..'; +import {shopifyApp} from '../../../../..'; import { APP_URL, TEST_SHOP, expectBeginAuthRedirect, expectExitIframeRedirect, getThrownResponse, - testConfig, -} from '../../../../../../__test-helpers'; + testConfigAuthCodeFlow, +} from '../../../../../__test-helpers'; describe('authorize.admin auth path', () => { test('throws an 400 Response if the shop param is missing', async () => { // GIVEN - const config = testConfig(); + const config = testConfigAuthCodeFlow(); const shopify = shopifyApp(config); // WHEN @@ -27,7 +27,7 @@ describe('authorize.admin auth path', () => { test('throws an 400 Response if the shop param is invalid', async () => { // GIVEN - const config = testConfig(); + const config = testConfigAuthCodeFlow(); const shopify = shopifyApp(config); // WHEN @@ -43,7 +43,7 @@ describe('authorize.admin auth path', () => { test('throws an 302 Response to begin auth', async () => { // GIVEN - const config = testConfig(); + const config = testConfigAuthCodeFlow(); const shopify = shopifyApp(config); // WHEN @@ -59,7 +59,7 @@ describe('authorize.admin auth path', () => { test('redirects to exit-iframe when loading the auth path while in an iframe request', async () => { // GIVEN - const config = testConfig(); + const config = testConfigAuthCodeFlow(); const shopify = shopifyApp(config); // WHEN diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-code-flow/authenticate.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/authenticate.test.ts similarity index 86% rename from packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-code-flow/authenticate.test.ts rename to packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/authenticate.test.ts index 5777a663bb..815a9f0e68 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-code-flow/authenticate.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/authenticate.test.ts @@ -1,6 +1,6 @@ import {SESSION_COOKIE_NAME, Session} from '@shopify/shopify-api'; -import {shopifyApp} from '../../../../../..'; +import {shopifyApp} from '../../../../..'; import { APP_URL, BASE64_HOST, @@ -10,15 +10,17 @@ import { getJwt, getThrownResponse, setUpValidSession, - testConfig, + testConfigAuthCodeFlow, signRequestCookie, -} from '../../../../../../__test-helpers'; +} from '../../../../../__test-helpers'; describe('authenticate', () => { describe('errors', () => { it('redirects to exit-iframe if app is embedded and the session is no longer valid for the id_token when embedded', async () => { // GIVEN - const shopify = shopifyApp(testConfig({scopes: ['otherTestScope']})); + const shopify = shopifyApp( + testConfigAuthCodeFlow({scopes: ['otherTestScope']}), + ); await setUpValidSession(shopify.sessionStorage); // WHEN @@ -37,8 +39,10 @@ describe('authenticate', () => { // manageAccessToken or ensureInstalledOnShop it('redirects to auth if there is no session cookie for non-embedded apps when at the top level', async () => { // GIVEN - const config = testConfig(); - const shopify = shopifyApp(testConfig({isEmbeddedApp: false})); + const config = testConfigAuthCodeFlow(); + const shopify = shopifyApp( + testConfigAuthCodeFlow({isEmbeddedApp: false}), + ); await setUpValidSession(shopify.sessionStorage); // WHEN @@ -57,7 +61,7 @@ describe('authenticate', () => { it('redirects to auth if the session is no longer valid for non-embedded apps when at the top level', async () => { // GIVEN - const config = testConfig({ + const config = testConfigAuthCodeFlow({ isEmbeddedApp: false, scopes: ['otherTestScope'], }); @@ -89,7 +93,9 @@ describe('authenticate', () => { (isOnline) => { it('returns the context if the session is valid and the app is embedded', async () => { // GIVEN - const shopify = shopifyApp(testConfig({useOnlineTokens: isOnline})); + const shopify = shopifyApp( + testConfigAuthCodeFlow({useOnlineTokens: isOnline}), + ); let testSession: Session; testSession = await setUpValidSession(shopify.sessionStorage); @@ -115,7 +121,9 @@ describe('authenticate', () => { it('returns the context if the session is valid and the app is not embedded', async () => { // GIVEN - const shopify = shopifyApp(testConfig({isEmbeddedApp: false})); + const shopify = shopifyApp( + testConfigAuthCodeFlow({isEmbeddedApp: false}), + ); let testSession: Session; testSession = await setUpValidSession(shopify.sessionStorage); @@ -148,7 +156,7 @@ describe('authenticate', () => { // manageAccessToken & ensureInstalledOnShop it('loads a session from the cookie from a request with no search params when not embedded', async () => { // GIVEN - const shopify = shopifyApp(testConfig({isEmbeddedApp: false})); + const shopify = shopifyApp(testConfigAuthCodeFlow({isEmbeddedApp: false})); const testSession = await setUpValidSession(shopify.sessionStorage); // WHEN diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-code-flow/ensure-installed-on-shop.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/ensure-installed-on-shop.test.ts similarity index 89% rename from packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-code-flow/ensure-installed-on-shop.test.ts rename to packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/ensure-installed-on-shop.test.ts index 22b7e53130..28756e8e68 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-code-flow/ensure-installed-on-shop.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/ensure-installed-on-shop.test.ts @@ -1,6 +1,6 @@ import {LogSeverity, SESSION_COOKIE_NAME} from '@shopify/shopify-api'; -import {shopifyApp} from '../../../../../..'; +import {shopifyApp} from '../../../../..'; import { API_KEY, APP_URL, @@ -13,16 +13,16 @@ import { getJwt, getThrownResponse, setUpValidSession, - testConfig, + testConfigAuthCodeFlow, signRequestCookie, mockExternalRequest, -} from '../../../../../../__test-helpers'; +} from '../../../../../__test-helpers'; describe('authorize.admin doc request path', () => { describe('errors', () => { it('redirects to auth when not embedded and there is no offline session', async () => { // GIVEN - const config = testConfig(); + const config = testConfigAuthCodeFlow(); const shopify = shopifyApp(config); // WHEN @@ -37,7 +37,7 @@ describe('authorize.admin doc request path', () => { it('redirects to exit-iframe when embedded and there is no offline session', async () => { // GIVEN - const shopify = shopifyApp(testConfig()); + const shopify = shopifyApp(testConfigAuthCodeFlow()); // WHEN const response = await getThrownResponse( @@ -53,7 +53,7 @@ describe('authorize.admin doc request path', () => { it('redirects to auth when not embedded on an embedded app, and the API token is invalid', async () => { // GIVEN - const config = testConfig(); + const config = testConfigAuthCodeFlow(); const shopify = shopifyApp(config); await setUpValidSession(shopify.sessionStorage); @@ -74,7 +74,7 @@ describe('authorize.admin doc request path', () => { it('returns non-401 codes when not embedded on an embedded app and the request fails', async () => { // GIVEN - const config = testConfig(); + const config = testConfigAuthCodeFlow(); const shopify = shopifyApp(config); await setUpValidSession(shopify.sessionStorage); @@ -102,7 +102,7 @@ describe('authorize.admin doc request path', () => { it('returns a 500 when not embedded on an embedded app and the request fails', async () => { // GIVEN - const config = testConfig(); + const config = testConfigAuthCodeFlow(); const shopify = shopifyApp(config); await setUpValidSession(shopify.sessionStorage); @@ -129,8 +129,8 @@ describe('authorize.admin doc request path', () => { it("redirects to the embedded app URL if there is a valid session but the app isn't embedded yet", async () => { // GIVEN - const config = testConfig(); - const shopify = shopifyApp(testConfig()); + const config = testConfigAuthCodeFlow(); + const shopify = shopifyApp(testConfigAuthCodeFlow()); await setUpValidSession(shopify.sessionStorage); await mockExternalRequest({ @@ -154,7 +154,7 @@ describe('authorize.admin doc request path', () => { it('redirects to exit-iframe if app is embedded and there is no session for the id_token when embedded', async () => { // GIVEN - const shopify = shopifyApp(testConfig()); + const shopify = shopifyApp(testConfigAuthCodeFlow()); await setUpValidSession(shopify.sessionStorage); const otherShopDomain = 'other-shop.myshopify.io'; @@ -174,8 +174,10 @@ describe('authorize.admin doc request path', () => { // manageAccessToken or ensureInstalledOnShop it('redirects to auth if there is no session cookie for non-embedded apps when at the top level', async () => { // GIVEN - const config = testConfig(); - const shopify = shopifyApp(testConfig({isEmbeddedApp: false})); + const config = testConfigAuthCodeFlow(); + const shopify = shopifyApp( + testConfigAuthCodeFlow({isEmbeddedApp: false}), + ); await setUpValidSession(shopify.sessionStorage); // WHEN @@ -194,7 +196,7 @@ describe('authorize.admin doc request path', () => { it('redirects to auth if there is no session for non-embedded apps when at the top level', async () => { // GIVEN - const config = testConfig({isEmbeddedApp: false}); + const config = testConfigAuthCodeFlow({isEmbeddedApp: false}); const shopify = shopifyApp(config); await setUpValidSession(shopify.sessionStorage); @@ -221,7 +223,7 @@ describe('authorize.admin doc request path', () => { // manageAccessToken & ensureInstalledOnShop it('loads a session from the cookie from a request with no search params when not embedded', async () => { // GIVEN - const shopify = shopifyApp(testConfig({isEmbeddedApp: false})); + const shopify = shopifyApp(testConfigAuthCodeFlow({isEmbeddedApp: false})); const testSession = await setUpValidSession(shopify.sessionStorage); // WHEN From 693c6cf299eaf889b987d4e603dcf848ce99cdd9 Mon Sep 17 00:00:00 2001 From: Rachel Carvalho Date: Thu, 30 Nov 2023 12:21:41 -0500 Subject: [PATCH 06/41] add tests to token exchange strategy --- .../__test-helpers/setup-valid-session.ts | 3 +- .../token-exchange/authenticate.test.ts | 216 ++++++++++++++++++ .../admin/strategies/token-exchange.ts | 17 +- 3 files changed, 230 insertions(+), 6 deletions(-) create mode 100644 packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts diff --git a/packages/shopify-app-remix/src/server/__test-helpers/setup-valid-session.ts b/packages/shopify-app-remix/src/server/__test-helpers/setup-valid-session.ts index f79601bbbb..f43ca5a289 100644 --- a/packages/shopify-app-remix/src/server/__test-helpers/setup-valid-session.ts +++ b/packages/shopify-app-remix/src/server/__test-helpers/setup-valid-session.ts @@ -6,13 +6,14 @@ import {TEST_SHOP, USER_ID} from './const'; export async function setUpValidSession( sessionStorage: SessionStorage, isOnline = false, + expires: Date | undefined = undefined, ): Promise { const overrides: Partial = {}; let id = `offline_${TEST_SHOP}`; if (isOnline) { id = `${TEST_SHOP}_${USER_ID}`; // Expires one day from now - overrides.expires = new Date(Date.now() + 1000 * 3600 * 24); + overrides.expires = expires || new Date(Date.now() + 1000 * 3600 * 24); overrides.onlineAccessInfo = { associated_user_scope: 'testScope', expires_in: 3600 * 24, diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts new file mode 100644 index 0000000000..77c642232a --- /dev/null +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts @@ -0,0 +1,216 @@ +import {SESSION_COOKIE_NAME, Session} from '@shopify/shopify-api'; + +import {shopifyApp} from '../../../../..'; +import { + API_KEY, + API_SECRET_KEY, + APP_URL, + BASE64_HOST, + TEST_SHOP, + getJwt, + getThrownResponse, + mockExternalRequest, + setUpValidSession, + testConfig, +} from '../../../../../__test-helpers'; + +const USER_ID = 902541635; + +describe('authenticate', () => { + it('performs token exchange when there is no offline session', async () => { + // GIVEN + const config = testConfig(); + const shopify = shopifyApp(config); + + const {token} = getJwt(); + await mockTokenExchangeRequest(token, 'offline'); + + // WHEN + const {session} = await shopify.authenticate.admin( + new Request( + `${APP_URL}?embedded=1&shop=${TEST_SHOP}&host=${BASE64_HOST}&id_token=${token}`, + ), + ); + + // THEN + const [persistedSession] = await config.sessionStorage.findSessionsByShop( + TEST_SHOP, + ); + + expect(persistedSession).toEqual(session); + expect(session).toMatchObject({ + accessToken: '123abc-exchanged-from-session-token', + id: `offline_${TEST_SHOP}`, + isOnline: false, + scope: 'read_orders', + shop: TEST_SHOP, + state: '', + }); + }); + + it('performs token exchange when existing session is no longer valid', async () => { + // GIVEN + const config = testConfig({useOnlineTokens: true}); + const shopify = shopifyApp(config); + const anHourAgo = new Date(Date.now() - 1000 * 3600); + await setUpValidSession(shopify.sessionStorage, true, anHourAgo); + + const {token} = getJwt(); + await mockTokenExchangeRequest(token, 'offline'); + await mockTokenExchangeRequest(token, 'online'); + + // WHEN + const {session} = await shopify.authenticate.admin( + new Request( + `${APP_URL}?embedded=1&shop=${TEST_SHOP}&host=${BASE64_HOST}&id_token=${token}`, + ), + ); + + // THEN + const [_, onlineSession] = await config.sessionStorage.findSessionsByShop( + TEST_SHOP, + ); + + expect(onlineSession).toEqual(session); + expect(session).toMatchObject({ + accessToken: '123abc-exchanged-from-session-token', + id: `${TEST_SHOP}_${USER_ID}`, + isOnline: true, + scope: 'read_orders', + shop: TEST_SHOP, + state: '', + onlineAccessInfo: expect.any(Object), + }); + }); + + describe.each([true, false])( + 'existing sessions when isOnline: %s', + (isOnline) => { + it('returns the context if the session is valid', async () => { + // GIVEN + const shopify = shopifyApp(testConfig({useOnlineTokens: isOnline})); + + let testSession: Session; + testSession = await setUpValidSession(shopify.sessionStorage); + if (isOnline) { + testSession = await setUpValidSession( + shopify.sessionStorage, + isOnline, + ); + } + + // WHEN + const {token} = getJwt(); + const {admin, session} = await shopify.authenticate.admin( + new Request( + `${APP_URL}?embedded=1&shop=${TEST_SHOP}&host=${BASE64_HOST}&id_token=${token}`, + ), + ); + + // THEN + expect(session).toBe(testSession); + expect(admin.rest.session).toBe(testSession); + expect(session.isOnline).toEqual(isOnline); + }); + }, + ); + + test('throws a 500 if afterAuth hook throws an error', async () => { + // GIVEN + const config = testConfig(); + const shopify = shopifyApp({ + ...config, + hooks: { + afterAuth: () => { + throw new Error('test'); + }, + }, + }); + + const {token} = getJwt(); + await mockTokenExchangeRequest(token, 'offline'); + + // WHEN + const response = await getThrownResponse( + shopify.authenticate.admin, + new Request( + `${APP_URL}?embedded=1&shop=${TEST_SHOP}&host=${BASE64_HOST}&id_token=${token}`, + ), + ); + + // THEN + expect(response.status).toBe(500); + }); + + test('Runs the afterAuth hooks passing', async () => { + // GIVEN + const afterAuthMock = jest.fn(); + const config = testConfig({ + hooks: { + afterAuth: afterAuthMock, + }, + }); + const shopify = shopifyApp(config); + + const {token} = getJwt(); + await mockTokenExchangeRequest(token, 'offline'); + + // WHEN + const {session} = await shopify.authenticate.admin( + new Request( + `${APP_URL}?embedded=1&shop=${TEST_SHOP}&host=${BASE64_HOST}&id_token=${token}`, + ), + ); + + // THEN + expect(session).toBeDefined(); + expect(afterAuthMock).toHaveBeenCalledTimes(1); + }); +}); + +async function mockTokenExchangeRequest( + sessionToken, + tokenType: 'online' | 'offline' = 'offline', +) { + const responseBody = { + access_token: '123abc-exchanged-from-session-token', + scope: 'read_orders', + }; + + await mockExternalRequest({ + request: new Request(`https://${TEST_SHOP}/admin/oauth/access_token`, { + method: 'POST', + body: JSON.stringify({ + client_id: API_KEY, + client_secret: API_SECRET_KEY, + grant_type: 'urn:ietf:params:oauth:grant-type:token-exchange', + subject_token: sessionToken, + subject_token_type: 'urn:ietf:params:oauth:token-type:id_token', + requested_token_type: + tokenType === 'offline' + ? 'urn:shopify:params:oauth:token-type:offline-access-token' + : 'urn:shopify:params:oauth:token-type:online-access-token', + }), + }), + response: + tokenType === 'offline' + ? new Response(JSON.stringify(responseBody)) + : new Response( + JSON.stringify({ + ...responseBody, + expires_in: Math.trunc(Date.now() / 1000) + 3600, + associated_user_scope: 'read_orders', + associated_user: { + id: USER_ID, + first_name: 'John', + last_name: 'Smith', + email: 'john@example.com', + email_verified: true, + account_owner: true, + locale: 'en', + collaborator: false, + }, + }), + ), + }); +} diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts index 20a9350bfd..f73915c2d3 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts @@ -67,11 +67,18 @@ export class TokenExchangeStrategy< newSession = onlineSession; } - await triggerAfterAuthHook( - {api, config, logger}, - newSession, - request, - ); + try { + await triggerAfterAuthHook( + {api, config, logger}, + newSession, + request, + ); + } catch (error) { + throw new Response(undefined, { + status: 500, + statusText: 'Internal Server Error', + }); + } return newSession; } From 3e447c3847cfc03bb9c0cc849ce71ec9995aaff2 Mon Sep 17 00:00:00 2001 From: Rachel Carvalho Date: Thu, 30 Nov 2023 12:44:11 -0500 Subject: [PATCH 07/41] move tests that redirect when no session is found into auth code flow test folder move embedded search param check to generic doc request tests --- .../admin/__tests__/doc-request-path.test.ts | 20 +++++ .../session-token-header-path.test.ts | 53 ------------- .../ensure-installed-on-shop.test.ts | 25 ------ .../session-token-header-path.test.ts | 76 +++++++++++++++++++ .../token-exchange/authenticate.test.ts | 2 +- 5 files changed, 97 insertions(+), 79 deletions(-) create mode 100644 packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/session-token-header-path.test.ts diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/__tests__/doc-request-path.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/__tests__/doc-request-path.test.ts index d5735c93b2..8f98ff3391 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/__tests__/doc-request-path.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/__tests__/doc-request-path.test.ts @@ -128,4 +128,24 @@ describe('authorize.admin doc request path', () => { // THEN expect(response.status).toBe(400); }); + + it("redirects to the embedded app URL if the app isn't embedded yet", async () => { + // GIVEN + const config = testConfig(); + const shopify = shopifyApp(config); + await setUpValidSession(shopify.sessionStorage); + + // WHEN + const response = await getThrownResponse( + shopify.authenticate.admin, + new Request(`${APP_URL}?shop=${TEST_SHOP}&host=${BASE64_HOST}`), + ); + + // THEN + const {hostname, pathname} = new URL(response.headers.get('location')!); + + expect(response.status).toBe(302); + expect(hostname).toBe(SHOPIFY_HOST); + expect(pathname).toBe(`/apps/${API_KEY}`); + }); }); diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/__tests__/session-token-header-path.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/__tests__/session-token-header-path.test.ts index 06dac552c6..deed90d5df 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/__tests__/session-token-header-path.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/__tests__/session-token-header-path.test.ts @@ -30,59 +30,6 @@ describe('authorize.session token header path', () => { // THEN expect(response.status).toBe(401); }); - - describe.each([true, false])('when isOnline: %s', (isOnline) => { - it(`returns app bridge redirection headers if there is no session`, async () => { - // GIVEN - const shopify = shopifyApp(testConfig({useOnlineTokens: isOnline})); - - // WHEN - const {token} = getJwt(); - const response = await getThrownResponse( - shopify.authenticate.admin, - new Request(`${APP_URL}?shop=${TEST_SHOP}&host=${BASE64_HOST}`, { - headers: {Authorization: `Bearer ${token}`}, - }), - ); - - // THEN - const {origin, pathname, searchParams} = new URL( - response.headers.get(REAUTH_URL_HEADER)!, - ); - - expect(response.status).toBe(401); - expect(origin).toBe(APP_URL); - expect(pathname).toBe('/auth'); - expect(searchParams.get('shop')).toBe(TEST_SHOP); - }); - - it(`returns app bridge redirection headers if the session is no longer valid`, async () => { - // GIVEN - const shopify = shopifyApp( - testConfig({useOnlineTokens: isOnline, scopes: ['otherTestScope']}), - ); - // The session scopes don't match the configured scopes, so it needs to be reset - await setUpValidSession(shopify.sessionStorage, isOnline); - - // WHEN - const {token} = getJwt(); - const response = await getThrownResponse( - shopify.authenticate.admin, - new Request(`${APP_URL}?shop=${TEST_SHOP}&host=${BASE64_HOST}`, { - headers: {Authorization: `Bearer ${token}`}, - }), - ); - - // THEN - const {origin, pathname, searchParams} = new URL( - response.headers.get(REAUTH_URL_HEADER)!, - ); - expect(response.status).toBe(401); - expect(origin).toBe(APP_URL); - expect(pathname).toBe('/auth'); - expect(searchParams.get('shop')).toBe(TEST_SHOP); - }); - }); }); describe.each([true, false])( diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/ensure-installed-on-shop.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/ensure-installed-on-shop.test.ts index 28756e8e68..eac16909b8 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/ensure-installed-on-shop.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/ensure-installed-on-shop.test.ts @@ -127,31 +127,6 @@ describe('authorize.admin doc request path', () => { ); }); - it("redirects to the embedded app URL if there is a valid session but the app isn't embedded yet", async () => { - // GIVEN - const config = testConfigAuthCodeFlow(); - const shopify = shopifyApp(testConfigAuthCodeFlow()); - await setUpValidSession(shopify.sessionStorage); - - await mockExternalRequest({ - request: new Request(GRAPHQL_URL, {method: 'POST'}), - response: new Response(undefined, {status: 200}), - }); - - // WHEN - const response = await getThrownResponse( - shopify.authenticate.admin, - new Request(`${APP_URL}?shop=${TEST_SHOP}&host=${BASE64_HOST}`), - ); - - // THEN - const {hostname, pathname} = new URL(response.headers.get('location')!); - - expect(response.status).toBe(302); - expect(hostname).toBe(SHOPIFY_HOST); - expect(pathname).toBe(`/apps/${API_KEY}`); - }); - it('redirects to exit-iframe if app is embedded and there is no session for the id_token when embedded', async () => { // GIVEN const shopify = shopifyApp(testConfigAuthCodeFlow()); diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/session-token-header-path.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/session-token-header-path.test.ts new file mode 100644 index 0000000000..30ce886968 --- /dev/null +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/session-token-header-path.test.ts @@ -0,0 +1,76 @@ +import {SESSION_COOKIE_NAME, Session} from '@shopify/shopify-api'; + +import {shopifyApp} from '../../../../..'; +import { + APP_URL, + BASE64_HOST, + TEST_SHOP, + getJwt, + getThrownResponse, + setUpValidSession, + signRequestCookie, + testConfigAuthCodeFlow, +} from '../../../../../__test-helpers'; +import {REAUTH_URL_HEADER} from '../../../../const'; + +describe('authorize.session token header path', () => { + describe('errors', () => { + describe.each([true, false])('when isOnline: %s', (isOnline) => { + it(`returns app bridge redirection headers if there is no session`, async () => { + // GIVEN + const shopify = shopifyApp( + testConfigAuthCodeFlow({useOnlineTokens: isOnline}), + ); + + // WHEN + const {token} = getJwt(); + const response = await getThrownResponse( + shopify.authenticate.admin, + new Request(`${APP_URL}?shop=${TEST_SHOP}&host=${BASE64_HOST}`, { + headers: {Authorization: `Bearer ${token}`}, + }), + ); + + // THEN + const {origin, pathname, searchParams} = new URL( + response.headers.get(REAUTH_URL_HEADER)!, + ); + + expect(response.status).toBe(401); + expect(origin).toBe(APP_URL); + expect(pathname).toBe('/auth'); + expect(searchParams.get('shop')).toBe(TEST_SHOP); + }); + + it(`returns app bridge redirection headers if the session is no longer valid`, async () => { + // GIVEN + const shopify = shopifyApp( + testConfigAuthCodeFlow({ + useOnlineTokens: isOnline, + scopes: ['otherTestScope'], + }), + ); + // The session scopes don't match the configured scopes, so it needs to be reset + await setUpValidSession(shopify.sessionStorage, isOnline); + + // WHEN + const {token} = getJwt(); + const response = await getThrownResponse( + shopify.authenticate.admin, + new Request(`${APP_URL}?shop=${TEST_SHOP}&host=${BASE64_HOST}`, { + headers: {Authorization: `Bearer ${token}`}, + }), + ); + + // THEN + const {origin, pathname, searchParams} = new URL( + response.headers.get(REAUTH_URL_HEADER)!, + ); + expect(response.status).toBe(401); + expect(origin).toBe(APP_URL); + expect(pathname).toBe('/auth'); + expect(searchParams.get('shop')).toBe(TEST_SHOP); + }); + }); + }); +}); diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts index 77c642232a..1adfe0b2a7 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts @@ -1,4 +1,4 @@ -import {SESSION_COOKIE_NAME, Session} from '@shopify/shopify-api'; +import {Session} from '@shopify/shopify-api'; import {shopifyApp} from '../../../../..'; import { From b83327de6f1f951c8a09891080f4b9391d0b45d1 Mon Sep 17 00:00:00 2001 From: Rezaan Syed Date: Fri, 1 Dec 2023 12:09:35 -0500 Subject: [PATCH 08/41] throw 500 for most token exchange errors and complete code coverage --- .../admin/helpers/redirect-to-bounce-page.ts | 17 ++-- .../token-exchange/authenticate.test.ts | 94 +++++++++++++++++++ .../admin/strategies/token-exchange.ts | 6 +- .../respond-to-invalid-session-token.ts | 1 + 4 files changed, 107 insertions(+), 11 deletions(-) diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-bounce-page.ts b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-bounce-page.ts index e7b2f1dd55..359916a0ec 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-bounce-page.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-bounce-page.ts @@ -7,20 +7,17 @@ export const redirectToBouncePage = (params: BasicParams, url: URL): never => { // Make sure we always point to the configured app URL so it also works behind reverse proxies (that alter the Host // header). - // const searchParams = new URLSearchParams(url.search); - // searchParams.delete('id_token'); - // searchParams.set( - // 'shopify-reload', - // `${config.appUrl}${url.pathname}?${searchParams.toString()}`, - // ); - - url.searchParams.set( + const searchParams = new URLSearchParams(url.search); + searchParams.delete('id_token'); + searchParams.set( 'shopify-reload', - `${config.appUrl}${url.pathname}${url.search}`, + `${config.appUrl}${url.pathname}?${searchParams.toString()}`, ); // eslint-disable-next-line no-warning-comments // TODO Make sure this works on chrome without a tunnel (weird HTTPS redirect issue) // https://github.com/orgs/Shopify/projects/6899/views/1?pane=issue&itemId=28376650 - throw redirect(`${config.auth.patchSessionTokenPath}${url.search}`); + throw redirect( + `${config.auth.patchSessionTokenPath}?${searchParams.toString()}`, + ); }; diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts index 1adfe0b2a7..89758cf008 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts @@ -115,6 +115,84 @@ describe('authenticate', () => { }, ); + test('redirects to bounce page when receiving an invalid subject token response from token exchange API', async () => { + // GIVEN + const config = testConfig(); + const shopify = shopifyApp(config); + + const {token} = getJwt(); + await mockInvalidTokenExchangeRequest('invalid_subject_token'); + + // WHEN + const response = await getThrownResponse( + shopify.authenticate.admin, + new Request( + `${APP_URL}?embedded=1&shop=${TEST_SHOP}&host=${BASE64_HOST}&id_token=${token}`, + ), + ); + + // THEN + const {pathname, searchParams} = new URL( + response.headers.get('location')!, + APP_URL, + ); + + expect(response.status).toBe(302); + expect(pathname).toBe('/auth/session-token'); + expect(searchParams.get('shop')).toBe(TEST_SHOP); + expect(searchParams.get('host')).toBe(BASE64_HOST); + expect(searchParams.get('shopify-reload')).toBe( + `${APP_URL}/?embedded=1&shop=${TEST_SHOP}&host=${BASE64_HOST}`, + ); + expect(fetchMock.mock.calls).toHaveLength(1); + }); + + test('throws 401 unauthorized when receiving an invalid subject token response from token exchange API', async () => { + // GIVEN + const config = testConfig(); + const shopify = shopifyApp(config); + + const {token} = getJwt(); + await mockInvalidTokenExchangeRequest('invalid_subject_token'); + + // WHEN + const response = await getThrownResponse( + shopify.authenticate.admin, + new Request(APP_URL, { + headers: { + Authorization: `Bearer ${token}`, + }, + }), + ); + + // THEN + expect(response.status).toBe(401); + expect(fetchMock.mock.calls).toHaveLength(1); + }); + + test('throws 500 for any other error from token exchange API', async () => { + // GIVEN + const config = testConfig(); + const shopify = shopifyApp(config); + + const {token} = getJwt(); + await mockInvalidTokenExchangeRequest('im_broke', 401); + + // WHEN + const response = await getThrownResponse( + shopify.authenticate.admin, + new Request(APP_URL, { + headers: { + Authorization: `Bearer ${token}`, + }, + }), + ); + + // THEN + expect(response.status).toBe(500); + expect(fetchMock.mock.calls).toHaveLength(1); + }); + test('throws a 500 if afterAuth hook throws an error', async () => { // GIVEN const config = testConfig(); @@ -214,3 +292,19 @@ async function mockTokenExchangeRequest( ), }); } + +async function mockInvalidTokenExchangeRequest(error: string, status = 400) { + await mockExternalRequest({ + request: new Request(`https://${TEST_SHOP}/admin/oauth/access_token`, { + method: 'POST', + }), + response: new Response( + JSON.stringify({ + error, + }), + { + status, + }, + ), + }); +} diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts index f73915c2d3..dec692bcc0 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts @@ -114,7 +114,11 @@ export class TokenExchangeStrategy< ) { throw respondToInvalidSessionToken({api, config, logger}, request); } - throw error; + + throw new Response(undefined, { + status: 500, + statusText: 'Internal Server Error', + }); } } } diff --git a/packages/shopify-app-remix/src/server/authenticate/helpers/respond-to-invalid-session-token.ts b/packages/shopify-app-remix/src/server/authenticate/helpers/respond-to-invalid-session-token.ts index 6129abab13..5c4cf51eea 100644 --- a/packages/shopify-app-remix/src/server/authenticate/helpers/respond-to-invalid-session-token.ts +++ b/packages/shopify-app-remix/src/server/authenticate/helpers/respond-to-invalid-session-token.ts @@ -13,6 +13,7 @@ export function respondToInvalidSessionToken( return redirectToBouncePage({api, logger, config}, new URL(request.url)); } + // eslint-disable-next-line no-warning-comments // TODO add header so app bridge retries with new session token throw new Response(undefined, { status: 401, From 9ba3d38eadcbafbf07c8f78dfdc15730da9a40d3 Mon Sep 17 00:00:00 2001 From: Rezaan Syed Date: Fri, 1 Dec 2023 12:40:21 -0500 Subject: [PATCH 09/41] Fix logging --- .../src/server/authenticate/admin/authenticate.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts b/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts index 664b6786a0..796492b893 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts @@ -157,10 +157,10 @@ async function getSessionTokenContext( const sessionToken = (headerSessionToken || searchParamSessionToken)!; logger.debug('Attempting to authenticate session token', { - sessionToken: { + sessionToken: JSON.stringify({ header: headerSessionToken, search: searchParamSessionToken, - }, + }), }); if (config.isEmbeddedApp) { From 3913c15e2ed4abdba6d81d240df9e2fe3a91457f Mon Sep 17 00:00:00 2001 From: Rezaan Syed Date: Fri, 1 Dec 2023 16:07:41 -0500 Subject: [PATCH 10/41] Update types to account for unstable_tokenExchange and unstable_newEmbeddedAuthStrategy future flags --- .../src/server/__test-helpers/test-config.ts | 2 +- .../admin/strategies/token-exchange.ts | 3 +-- .../shopify-app-remix/src/server/shopify-app.ts | 7 +++++-- packages/shopify-app-remix/src/server/types.ts | 16 ++++++++++++++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/packages/shopify-app-remix/src/server/__test-helpers/test-config.ts b/packages/shopify-app-remix/src/server/__test-helpers/test-config.ts index 0f91063dc6..9644cc2433 100644 --- a/packages/shopify-app-remix/src/server/__test-helpers/test-config.ts +++ b/packages/shopify-app-remix/src/server/__test-helpers/test-config.ts @@ -55,7 +55,7 @@ export function testConfigAuthCodeFlow< ) { return testConfig({ ...overrides, - future: {...future, unstable_tokenExchange: false}, + future: {...future, unstable_newEmbeddedAuthStrategy: false}, }); } diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts index dec692bcc0..8f836c43bf 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts @@ -7,8 +7,7 @@ import { ShopifyRestResources, } from '@shopify/shopify-api'; import {AppConfig, AppConfigArg} from 'src/server/config-types'; -import {BasicParams} from 'src/server/types'; -import {MockApiConfig} from 'src/server/shopify-app'; +import {BasicParams, MockApiConfig} from 'src/server/types'; import {triggerAfterAuthHook} from '../helpers'; import {respondToInvalidSessionToken} from '../../helpers'; diff --git a/packages/shopify-app-remix/src/server/shopify-app.ts b/packages/shopify-app-remix/src/server/shopify-app.ts index 2224ec1470..f112c0dad5 100644 --- a/packages/shopify-app-remix/src/server/shopify-app.ts +++ b/packages/shopify-app-remix/src/server/shopify-app.ts @@ -72,7 +72,7 @@ export function shopifyApp< const authStrategy = authStrategyFactory({ ...params, strategy: - config.future.unstable_tokenExchange && config.isEmbeddedApp + config.future.unstable_newEmbeddedAuthStrategy && config.isEmbeddedApp ? tokenExchange : oauth, }); @@ -153,7 +153,10 @@ function deriveApi(appConfig: AppConfigArg) { isEmbeddedApp: appConfig.isEmbeddedApp ?? true, apiVersion: appConfig.apiVersion ?? LATEST_API_VERSION, isCustomStoreApp: appConfig.distribution === AppDistribution.ShopifyAdmin, - future: {}, + future: { + unstable_tokenExchange: + appConfig.future?.unstable_newEmbeddedAuthStrategy, + }, }); } diff --git a/packages/shopify-app-remix/src/server/types.ts b/packages/shopify-app-remix/src/server/types.ts index a219a8ee5a..1605c6957e 100644 --- a/packages/shopify-app-remix/src/server/types.ts +++ b/packages/shopify-app-remix/src/server/types.ts @@ -1,4 +1,5 @@ import { + ConfigParams, RegisterReturn, Shopify, ShopifyRestResources, @@ -13,13 +14,24 @@ import type { import type {AuthenticatePublic} from './authenticate/public/types'; import type {AdminContext} from './authenticate/admin/types'; import type {Unauthenticated} from './unauthenticated/types'; +import {FutureFlagOptions, FutureFlags} from './future/flags'; -export interface BasicParams { - api: Shopify; +export interface BasicParams< + Future extends FutureFlagOptions = FutureFlagOptions, +> { + api: Shopify>; config: AppConfig; logger: Shopify['logger']; } +export type MockApiConfig = ConfigParams & { + future?: { + unstable_tokenExchange?: Future extends FutureFlags + ? Future['unstable_newEmbeddedAuthStrategy'] + : boolean; + }; +}; + export type JSONValue = | string | number From 2473c85b4edafbc42b8036d017911d6310eb9414 Mon Sep 17 00:00:00 2001 From: Rezaan Syed Date: Fri, 1 Dec 2023 16:25:15 -0500 Subject: [PATCH 11/41] Add changeset --- .changeset/pink-horses-unite.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/pink-horses-unite.md diff --git a/.changeset/pink-horses-unite.md b/.changeset/pink-horses-unite.md new file mode 100644 index 0000000000..2bb64e4289 --- /dev/null +++ b/.changeset/pink-horses-unite.md @@ -0,0 +1,5 @@ +--- +'@shopify/shopify-app-remix': minor +--- + +Add new embedded authorization strategy relying on Shopify managed install and OAuth token exchange From 9ee1225e3a82661acab577297db6089381b3cf2f Mon Sep 17 00:00:00 2001 From: Rezaan Syed Date: Mon, 4 Dec 2023 12:11:16 -0500 Subject: [PATCH 12/41] Update future flag description --- packages/shopify-app-remix/src/server/future/flags.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shopify-app-remix/src/server/future/flags.ts b/packages/shopify-app-remix/src/server/future/flags.ts index d7d0b7b9f6..b70a1b1a77 100644 --- a/packages/shopify-app-remix/src/server/future/flags.ts +++ b/packages/shopify-app-remix/src/server/future/flags.ts @@ -16,7 +16,7 @@ export interface FutureFlags { v3_authenticatePublic?: boolean; /** - * TODO + * Enable token exchange token acquisition strategy with shopify managed install. * * @default false */ From c0007585a5ed9a5638b3c234dbf68f15ea79778e Mon Sep 17 00:00:00 2001 From: Rezaan Syed Date: Mon, 4 Dec 2023 14:52:29 -0500 Subject: [PATCH 13/41] Log throttling errors on webhook registration --- .../webhooks/__tests__/mock-responses.ts | 11 +++ .../webhooks/__tests__/register.test.ts | 97 ++++++++++++++++++- .../server/authenticate/webhooks/register.ts | 55 +++++++---- .../shopify-app-remix/src/server/types.ts | 2 +- 4 files changed, 144 insertions(+), 21 deletions(-) diff --git a/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/mock-responses.ts b/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/mock-responses.ts index 5377ca670a..fd0533599b 100644 --- a/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/mock-responses.ts +++ b/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/mock-responses.ts @@ -32,3 +32,14 @@ export const HTTP_WEBHOOK_CREATE_ERROR_RESPONSE = { }, }, }; + +export const HTTP_WEBHOOK_THROTTLING_ERROR_RESPONSE = { + errors: [ + { + message: 'Throttled', + extensions: { + code: 'THROTTLED', + }, + }, + ], +}; diff --git a/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/register.test.ts b/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/register.test.ts index b3dfd241ad..221c25b6c6 100644 --- a/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/register.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/register.test.ts @@ -1,4 +1,4 @@ -import {DeliveryMethod, Session} from '@shopify/shopify-api'; +import {DeliveryMethod, GraphqlQueryError, Session} from '@shopify/shopify-api'; import {shopifyApp} from '../../..'; import { @@ -110,4 +110,99 @@ describe('Webhook registration', () => { NOT_A_VALID_TOPIC: [expect.objectContaining({success: false})], }); }); + + it('logs throttling errors', async () => { + // GIVEN + const shopify = shopifyApp( + testConfig({ + webhooks: { + NOT_A_VALID_TOPIC: { + deliveryMethod: DeliveryMethod.Http, + callbackUrl: '/webhooks', + }, + }, + }), + ); + const session = new Session({ + id: `offline_${TEST_SHOP}`, + shop: TEST_SHOP, + isOnline: false, + state: 'test', + accessToken: 'totally_real_token', + }); + + await mockExternalRequests( + { + request: new Request(GRAPHQL_URL, { + method: 'POST', + body: 'webhookSubscriptions', + }), + response: new Response( + JSON.stringify(mockResponses.EMPTY_WEBHOOK_RESPONSE), + ), + }, + { + request: new Request(GRAPHQL_URL, { + method: 'POST', + body: 'webhookSubscriptionCreate', + }), + response: new Response( + JSON.stringify(mockResponses.HTTP_WEBHOOK_THROTTLING_ERROR_RESPONSE), + ), + }, + ); + + // WHEN + const results = await shopify.registerWebhooks({session}); + + // THEN + expect(results).toBeUndefined(); + }); + + it('throws other errors', async () => { + // GIVEN + const shopify = shopifyApp( + testConfig({ + webhooks: { + NOT_A_VALID_TOPIC: { + deliveryMethod: DeliveryMethod.Http, + callbackUrl: '/webhooks', + }, + }, + }), + ); + const session = new Session({ + id: `offline_${TEST_SHOP}`, + shop: TEST_SHOP, + isOnline: false, + state: 'test', + accessToken: 'totally_real_token', + }); + + await mockExternalRequests( + { + request: new Request(GRAPHQL_URL, { + method: 'POST', + body: 'webhookSubscriptions', + }), + response: new Response( + JSON.stringify(mockResponses.EMPTY_WEBHOOK_RESPONSE), + ), + }, + { + request: new Request(GRAPHQL_URL, { + method: 'POST', + body: 'webhookSubscriptionCreate', + }), + response: new Response( + JSON.stringify({errors: [{extensions: {code: 'FAILED_REQUEST'}}]}), + ), + }, + ); + + // THEN + expect(shopify.registerWebhooks({session})).rejects.toThrowError( + GraphqlQueryError, + ); + }); }); diff --git a/packages/shopify-app-remix/src/server/authenticate/webhooks/register.ts b/packages/shopify-app-remix/src/server/authenticate/webhooks/register.ts index 0acc99c6a7..e661742c38 100644 --- a/packages/shopify-app-remix/src/server/authenticate/webhooks/register.ts +++ b/packages/shopify-app-remix/src/server/authenticate/webhooks/register.ts @@ -4,26 +4,43 @@ import type {RegisterWebhooksOptions} from './types'; export function registerWebhooksFactory({api, logger}: BasicParams) { return async function registerWebhooks({session}: RegisterWebhooksOptions) { - return api.webhooks.register({session}).then((response) => { - Object.entries(response).forEach(([topic, topicResults]) => { - topicResults.forEach(({success, ...rest}) => { - if (success) { - logger.debug('Registered webhook', { - topic, - shop: session.shop, - operation: rest.operation, - }); - } else { - logger.error('Failed to register webhook', { - topic, - shop: session.shop, - result: JSON.stringify(rest.result), - }); - } + return api.webhooks + .register({session}) + .then((response) => { + Object.entries(response).forEach(([topic, topicResults]) => { + topicResults.forEach(({success, ...rest}) => { + if (success) { + logger.debug('Registered webhook', { + topic, + shop: session.shop, + operation: rest.operation, + }); + } else { + logger.error('Failed to register webhook', { + topic, + shop: session.shop, + result: JSON.stringify(rest.result), + }); + } + }); }); - }); - return response; - }); + return response; + }) + .catch((error) => { + if ( + error.response?.errors?.find( + (responseError: {extensions: {code: string}}) => + responseError?.extensions?.code === 'THROTTLED', + ) + ) { + logger.error('Failed to register webhooks', { + shop: session.shop, + error: JSON.stringify(error), + }); + } else { + throw error; + } + }); }; } diff --git a/packages/shopify-app-remix/src/server/types.ts b/packages/shopify-app-remix/src/server/types.ts index 1605c6957e..ec3d42dad2 100644 --- a/packages/shopify-app-remix/src/server/types.ts +++ b/packages/shopify-app-remix/src/server/types.ts @@ -61,7 +61,7 @@ interface JSONArray extends Array {} type RegisterWebhooks = ( options: RegisterWebhooksOptions, -) => Promise; +) => Promise; export enum LoginErrorType { MissingShop = 'MISSING_SHOP', From bed814bdbea60e29747f2e2226d6ae6e8e6d37e6 Mon Sep 17 00:00:00 2001 From: Rezaan Syed Date: Tue, 5 Dec 2023 12:32:03 -0500 Subject: [PATCH 14/41] Refactor to address review comments --- .../__test-helpers/setup-valid-session.ts | 10 +- .../src/server/__test-helpers/test-config.ts | 14 - .../session-token-header-path.test.ts | 9 +- .../admin/helpers/redirect-to-bounce-page.ts | 2 +- .../auth-code-flow/auth-callback-path.test.ts | 557 ++++++++++-------- .../auth-code-flow/auth-path.test.ts | 18 +- .../auth-code-flow/authenticate.test.ts | 42 +- .../ensure-installed-on-shop.test.ts | 47 +- .../session-token-header-path.test.ts | 13 +- .../token-exchange/authenticate.test.ts | 17 +- .../admin/strategies/token-exchange.ts | 5 +- .../appProxy/__tests__/authenticate.test.ts | 14 +- .../webhooks/__tests__/register.test.ts | 2 + .../src/server/future/flags.ts | 2 +- .../src/server/shopify-app.ts | 6 +- .../shopify-app-remix/src/server/types.ts | 15 +- .../admin/__tests__/factory.test.ts | 7 +- .../storefront/__tests__/factory.test.ts | 7 +- 18 files changed, 423 insertions(+), 364 deletions(-) diff --git a/packages/shopify-app-remix/src/server/__test-helpers/setup-valid-session.ts b/packages/shopify-app-remix/src/server/__test-helpers/setup-valid-session.ts index f43ca5a289..ac4cfa4035 100644 --- a/packages/shopify-app-remix/src/server/__test-helpers/setup-valid-session.ts +++ b/packages/shopify-app-remix/src/server/__test-helpers/setup-valid-session.ts @@ -5,15 +5,15 @@ import {TEST_SHOP, USER_ID} from './const'; export async function setUpValidSession( sessionStorage: SessionStorage, - isOnline = false, - expires: Date | undefined = undefined, + sessionParams?: Partial, ): Promise { const overrides: Partial = {}; let id = `offline_${TEST_SHOP}`; - if (isOnline) { + if (sessionParams?.isOnline) { id = `${TEST_SHOP}_${USER_ID}`; // Expires one day from now - overrides.expires = expires || new Date(Date.now() + 1000 * 3600 * 24); + overrides.expires = + sessionParams.expires || new Date(Date.now() + 1000 * 3600 * 24); overrides.onlineAccessInfo = { associated_user_scope: 'testScope', expires_in: 3600 * 24, @@ -33,7 +33,7 @@ export async function setUpValidSession( const session = new Session({ id, shop: TEST_SHOP, - isOnline, + isOnline: Boolean(sessionParams?.isOnline), state: 'test', accessToken: 'totally_real_token', scope: 'testScope', diff --git a/packages/shopify-app-remix/src/server/__test-helpers/test-config.ts b/packages/shopify-app-remix/src/server/__test-helpers/test-config.ts index 9644cc2433..9f9104f793 100644 --- a/packages/shopify-app-remix/src/server/__test-helpers/test-config.ts +++ b/packages/shopify-app-remix/src/server/__test-helpers/test-config.ts @@ -45,20 +45,6 @@ beforeEach(() => { (TEST_CONFIG as any).sessionStorage = new MemorySessionStorage(); }); -export function testConfigAuthCodeFlow< - Overrides extends TestOverridesArg, - Future extends FutureFlagOptions, ->( - {future, ...overrides}: Overrides & {future?: Future} = {} as Overrides & { - future?: Future; - }, -) { - return testConfig({ - ...overrides, - future: {...future, unstable_newEmbeddedAuthStrategy: false}, - }); -} - export function testConfig< Overrides extends TestOverridesArg, Future extends FutureFlagOptions, diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/__tests__/session-token-header-path.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/__tests__/session-token-header-path.test.ts index deed90d5df..4c582fce57 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/__tests__/session-token-header-path.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/__tests__/session-token-header-path.test.ts @@ -39,10 +39,9 @@ describe('authorize.session token header path', () => { // GIVEN const shopify = shopifyApp(testConfig({useOnlineTokens: isOnline})); - const testSession = await setUpValidSession( - shopify.sessionStorage, + const testSession = await setUpValidSession(shopify.sessionStorage, { isOnline, - ); + }); // WHEN const {token, payload} = getJwt(); @@ -67,7 +66,9 @@ describe('authorize.session token header path', () => { let testSession: Session; testSession = await setUpValidSession(shopify.sessionStorage); if (isOnline) { - testSession = await setUpValidSession(shopify.sessionStorage, true); + testSession = await setUpValidSession(shopify.sessionStorage, { + isOnline: true, + }); } // WHEN diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-bounce-page.ts b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-bounce-page.ts index 359916a0ec..0ba5c46ffc 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-bounce-page.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/redirect-to-bounce-page.ts @@ -7,7 +7,7 @@ export const redirectToBouncePage = (params: BasicParams, url: URL): never => { // Make sure we always point to the configured app URL so it also works behind reverse proxies (that alter the Host // header). - const searchParams = new URLSearchParams(url.search); + const searchParams = url.searchParams; searchParams.delete('id_token'); searchParams.set( 'shopify-reload', diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-callback-path.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-callback-path.test.ts index 32f0c553bc..fac4593afd 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-callback-path.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-callback-path.test.ts @@ -8,286 +8,325 @@ import { signRequestCookie, testConfig, mockExternalRequest, - testConfigAuthCodeFlow, + APP_URL, } from '../../../../../__test-helpers'; describe('authorize.admin auth callback path', () => { - describe('errors', () => { - test('throws an error if the shop param is missing', async () => { - // GIVEN - const config = testConfigAuthCodeFlow(); - const shopify = shopifyApp(config); - - // WHEN - const response = await getThrownResponse( - shopify.authenticate.admin, - new Request(getCallbackUrl(config)), - ); - - // THEN - expect(response.status).toBe(400); - expect(await response.text()).toBe('Shop param is invalid'); - }); - - test('throws an error if the shop param is not valid', async () => { - // GIVEN - const config = testConfigAuthCodeFlow(); - const shopify = shopifyApp(config); - - // WHEN - const response = await getThrownResponse( - shopify.authenticate.admin, - new Request(`${getCallbackUrl(config)}?shop=invalid`), - ); - - // THEN - expect(response.status).toBe(400); - expect(await response.text()).toBe('Shop param is invalid'); - }); - - test('throws an 302 Response to begin auth if CookieNotFound error', async () => { - // GIVEN - const config = testConfigAuthCodeFlow(); - const shopify = shopifyApp(config); - - // WHEN - const callbackUrl = getCallbackUrl(config); - const response = await getThrownResponse( - shopify.authenticate.admin, - new Request(`${callbackUrl}?shop=${TEST_SHOP}`), - ); - - // THEN - const {searchParams, hostname} = new URL( - response.headers.get('location')!, - ); - - expect(response.status).toBe(302); - expect(hostname).toBe(TEST_SHOP); - expect(searchParams.get('client_id')).toBe(config.apiKey); - expect(searchParams.get('scope')).toBe(config.scopes!.toString()); - expect(searchParams.get('redirect_uri')).toBe(callbackUrl); - expect(searchParams.get('state')).toStrictEqual(expect.any(String)); - }); - - test('throws a 400 if there is no HMAC param', async () => { - // GIVEN - const config = testConfigAuthCodeFlow(); - const shopify = shopifyApp(config); - - // WHEN - const state = 'nonce'; - const request = new Request( - `${getCallbackUrl(config)}?shop=${TEST_SHOP}&state=${state}`, - ); - - signRequestCookie({ - request, - cookieName: 'shopify_app_state', - cookieValue: state, + describe.each([true, false])('when isEmbeddedApp: %s', (isEmbeddedApp) => { + describe('errors', () => { + test('throws an error if the shop param is missing', async () => { + // GIVEN + const config = testConfig({ + future: {unstable_newEmbeddedAuthStrategy: !isEmbeddedApp}, + isEmbeddedApp, + }); + const shopify = shopifyApp(config); + + // WHEN + const response = await getThrownResponse( + shopify.authenticate.admin, + new Request(getCallbackUrl(config)), + ); + + // THEN + expect(response.status).toBe(400); + expect(await response.text()).toBe('Shop param is invalid'); }); - const response = await getThrownResponse( - shopify.authenticate.admin, - request, - ); - - // THEN - expect(response.status).toBe(400); - expect(response.statusText).toBe('Invalid OAuth Request'); - }); - - test('throws a 400 if the HMAC param is invalid', async () => { - // GIVEN - const config = testConfigAuthCodeFlow(); - const shopify = shopifyApp(config); - - // WHEN - const state = 'nonce'; - const request = new Request( - `${getCallbackUrl( - config, - )}?shop=${TEST_SHOP}&state=${state}&hmac=invalid`, - ); - - signRequestCookie({ - request, - cookieName: 'shopify_app_state', - cookieValue: state, + test('throws an error if the shop param is not valid', async () => { + // GIVEN + const config = testConfig({ + future: {unstable_newEmbeddedAuthStrategy: !isEmbeddedApp}, + isEmbeddedApp, + }); + const shopify = shopifyApp(config); + + // WHEN + const response = await getThrownResponse( + shopify.authenticate.admin, + new Request(`${getCallbackUrl(config)}?shop=invalid`), + ); + + // THEN + expect(response.status).toBe(400); + expect(await response.text()).toBe('Shop param is invalid'); }); - const response = await getThrownResponse( - shopify.authenticate.admin, - request, - ); + test('throws an 302 Response to begin auth if CookieNotFound error', async () => { + // GIVEN + const config = testConfig({ + future: {unstable_newEmbeddedAuthStrategy: !isEmbeddedApp}, + isEmbeddedApp, + }); + const shopify = shopifyApp(config); + + // WHEN + const callbackUrl = getCallbackUrl(config); + const response = await getThrownResponse( + shopify.authenticate.admin, + new Request(`${callbackUrl}?shop=${TEST_SHOP}`), + ); + + // THEN + const {searchParams, hostname} = new URL( + response.headers.get('location')!, + ); + + expect(response.status).toBe(302); + expect(hostname).toBe(TEST_SHOP); + expect(searchParams.get('client_id')).toBe(config.apiKey); + expect(searchParams.get('scope')).toBe(config.scopes!.toString()); + expect(searchParams.get('redirect_uri')).toBe(callbackUrl); + expect(searchParams.get('state')).toStrictEqual(expect.any(String)); + }); - // THEN - expect(response.status).toBe(400); - }); + test('throws a 400 if there is no HMAC param', async () => { + // GIVEN + const config = testConfig({ + future: {unstable_newEmbeddedAuthStrategy: !isEmbeddedApp}, + isEmbeddedApp, + }); + const shopify = shopifyApp(config); + + // WHEN + const state = 'nonce'; + const request = new Request( + `${getCallbackUrl(config)}?shop=${TEST_SHOP}&state=${state}`, + ); + + signRequestCookie({ + request, + cookieName: 'shopify_app_state', + cookieValue: state, + }); + + const response = await getThrownResponse( + shopify.authenticate.admin, + request, + ); + + // THEN + expect(response.status).toBe(400); + expect(response.statusText).toBe('Invalid OAuth Request'); + }); - test('throws a 500 if any other errors are thrown', async () => { - // GIVEN - const config = testConfigAuthCodeFlow(); - const shopify = shopifyApp({ - ...config, - hooks: { - afterAuth: () => { - throw new Error('test'); - }, - }, + test('throws a 400 if the HMAC param is invalid', async () => { + // GIVEN + const config = testConfig({ + future: {unstable_newEmbeddedAuthStrategy: !isEmbeddedApp}, + isEmbeddedApp, + }); + const shopify = shopifyApp(config); + + // WHEN + const state = 'nonce'; + const request = new Request( + `${getCallbackUrl( + config, + )}?shop=${TEST_SHOP}&state=${state}&hmac=invalid`, + ); + + signRequestCookie({ + request, + cookieName: 'shopify_app_state', + cookieValue: state, + }); + + const response = await getThrownResponse( + shopify.authenticate.admin, + request, + ); + + // THEN + expect(response.status).toBe(400); }); - // WHEN - await mockCodeExchangeRequest('offline'); - const response = await getThrownResponse( - shopify.authenticate.admin, - await getValidCallbackRequest(config), - ); + test('throws a 500 if any other errors are thrown', async () => { + // GIVEN + const config = testConfig({ + future: {unstable_newEmbeddedAuthStrategy: !isEmbeddedApp}, + isEmbeddedApp, + }); + const shopify = shopifyApp({ + ...config, + hooks: { + afterAuth: () => { + throw new Error('test'); + }, + }, + }); - // THEN - expect(response.status).toBe(500); - }); - }); + // WHEN + await mockCodeExchangeRequest('offline'); + const response = await getThrownResponse( + shopify.authenticate.admin, + await getValidCallbackRequest(config), + ); - describe('Success states', () => { - test('Exchanges the code for a token and saves it to SessionStorage', async () => { - // GIVEN - const config = testConfigAuthCodeFlow(); - const shopify = shopifyApp(config); - - // WHEN - await mockCodeExchangeRequest('offline'); - const response = await getThrownResponse( - shopify.authenticate.admin, - await getValidCallbackRequest(config), - ); - - // THEN - const [session] = await config.sessionStorage!.findSessionsByShop( - TEST_SHOP, - ); - - expect(session).toMatchObject({ - accessToken: '123abc', - id: `offline_${TEST_SHOP}`, - isOnline: false, - scope: 'read_products', - shop: TEST_SHOP, - state: 'nonce', + // THEN + expect(response.status).toBe(500); }); }); - test('throws an 302 Response to begin auth if token was offline and useOnlineTokens is true', async () => { - // GIVEN - const config = testConfigAuthCodeFlow({useOnlineTokens: true}); - const shopify = shopifyApp(config); - - // WHEN - await mockCodeExchangeRequest('offline'); - const response = await getThrownResponse( - shopify.authenticate.admin, - await getValidCallbackRequest(config), - ); - - // THEN - const {searchParams, hostname} = new URL( - response.headers.get('location')!, - ); - - expect(response.status).toBe(302); - expect(hostname).toBe(TEST_SHOP); - expect(searchParams.get('client_id')).toBe(config.apiKey); - expect(searchParams.get('scope')).toBe(config.scopes!.toString()); - expect(searchParams.get('redirect_uri')).toBe(getCallbackUrl(config)); - expect(searchParams.get('state')).toStrictEqual(expect.any(String)); - }); - - test('Does not throw a 302 Response to begin auth if token was online', async () => { - // GIVEN - const config = testConfigAuthCodeFlow({useOnlineTokens: true}); - const shopify = shopifyApp(config); - - // WHEN - await mockCodeExchangeRequest('online'); - const response = await getThrownResponse( - shopify.authenticate.admin, - await getValidCallbackRequest(config), - ); - - // THEN - const {hostname} = new URL(response.headers.get('location')!); - expect(hostname).not.toBe(TEST_SHOP); - }); - - test('Runs the afterAuth hooks passing', async () => { - // GIVEN - const afterAuthMock = jest.fn(); - const config = testConfigAuthCodeFlow({ - hooks: { - afterAuth: afterAuthMock, - }, + describe('Success states', () => { + test('Exchanges the code for a token and saves it to SessionStorage', async () => { + // GIVEN + const config = testConfig({ + future: {unstable_newEmbeddedAuthStrategy: !isEmbeddedApp}, + isEmbeddedApp, + }); + const shopify = shopifyApp(config); + + // WHEN + await mockCodeExchangeRequest('offline'); + const response = await getThrownResponse( + shopify.authenticate.admin, + await getValidCallbackRequest(config), + ); + + // THEN + const [session] = await config.sessionStorage!.findSessionsByShop( + TEST_SHOP, + ); + + expect(session).toMatchObject({ + accessToken: '123abc', + id: `offline_${TEST_SHOP}`, + isOnline: false, + scope: 'read_products', + shop: TEST_SHOP, + state: 'nonce', + }); }); - const shopify = shopifyApp(config); - // WHEN - await mockCodeExchangeRequest(); - const response = await getThrownResponse( - shopify.authenticate.admin, - await getValidCallbackRequest(config), - ); - - // THEN - expect(afterAuthMock).toHaveBeenCalledTimes(1); - }); + test('throws an 302 Response to begin auth if token was offline and useOnlineTokens is true', async () => { + // GIVEN + const config = testConfig({ + useOnlineTokens: true, + future: {unstable_newEmbeddedAuthStrategy: !isEmbeddedApp}, + isEmbeddedApp, + }); + const shopify = shopifyApp(config); + + // WHEN + await mockCodeExchangeRequest('offline'); + const response = await getThrownResponse( + shopify.authenticate.admin, + await getValidCallbackRequest(config), + ); + + // THEN + const {searchParams, hostname} = new URL( + response.headers.get('location')!, + ); + + expect(response.status).toBe(302); + expect(hostname).toBe(TEST_SHOP); + expect(searchParams.get('client_id')).toBe(config.apiKey); + expect(searchParams.get('scope')).toBe(config.scopes!.toString()); + expect(searchParams.get('redirect_uri')).toBe(getCallbackUrl(config)); + expect(searchParams.get('state')).toStrictEqual(expect.any(String)); + }); - test('throws a 302 response to the emebdded app URL if isEmbeddedApp is true', async () => { - // GIVEN - const config = testConfigAuthCodeFlow(); - const shopify = shopifyApp(config); - - // WHEN - await mockCodeExchangeRequest('offline'); - const response = await getThrownResponse( - shopify.authenticate.admin, - await getValidCallbackRequest(config), - ); - - // THEN - expect(response.status).toBe(302); - expect(response.headers.get('location')).toBe( - 'https://totally-real-host.myshopify.io/apps/testApiKey', - ); - }); + test('Does not throw a 302 Response to begin auth if token was online', async () => { + // GIVEN + const config = testConfig({ + useOnlineTokens: true, + future: {unstable_newEmbeddedAuthStrategy: !isEmbeddedApp}, + isEmbeddedApp, + }); + const shopify = shopifyApp(config); + + // WHEN + await mockCodeExchangeRequest('online'); + const response = await getThrownResponse( + shopify.authenticate.admin, + await getValidCallbackRequest(config), + ); + + // THEN + const base = `http://${APP_URL}`; + const {hostname} = new URL(response.headers.get('location')!, base); + expect(hostname).not.toBe(TEST_SHOP); + }); - test('throws a 302 to / if embedded is not true', async () => { - // GIVEN - const config = testConfigAuthCodeFlow({ - isEmbeddedApp: false, + test('Runs the afterAuth hooks passing', async () => { + // GIVEN + const afterAuthMock = jest.fn(); + const config = testConfig({ + hooks: { + afterAuth: afterAuthMock, + }, + future: {unstable_newEmbeddedAuthStrategy: !isEmbeddedApp}, + isEmbeddedApp, + }); + const shopify = shopifyApp(config); + + // WHEN + await mockCodeExchangeRequest(); + const response = await getThrownResponse( + shopify.authenticate.admin, + await getValidCallbackRequest(config), + ); + + // THEN + expect(afterAuthMock).toHaveBeenCalledTimes(1); }); - const shopify = shopifyApp(config); - - // WHEN - await mockCodeExchangeRequest('offline'); - const request = await getValidCallbackRequest(config); - const response = await getThrownResponse( - shopify.authenticate.admin, - request, - ); - - // THEN - const url = new URL(request.url); - const host = url.searchParams.get('host'); - expect(response.status).toBe(302); - expect(response.headers.get('location')).toBe( - `/?shop=${TEST_SHOP}&host=${host}`, - ); - expect(response.headers.get('set-cookie')).toBe( - [ - 'shopify_app_state=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT', - `shopify_app_session=offline_${TEST_SHOP};sameSite=lax; secure=true; path=/`, - 'shopify_app_session.sig=0qSrbSUpq8Cr+fev917WGyO1IU3Py1fTwZukcHd4hVE=;sameSite=lax; secure=true; path=/', - ].join(', '), - ); + + if (isEmbeddedApp) { + test('throws a 302 response to the embedded app URL', async () => { + // GIVEN + const config = testConfig({ + future: {unstable_newEmbeddedAuthStrategy: false}, + isEmbeddedApp: true, + }); + const shopify = shopifyApp(config); + + // WHEN + await mockCodeExchangeRequest('offline'); + const response = await getThrownResponse( + shopify.authenticate.admin, + await getValidCallbackRequest(config), + ); + + // THEN + expect(response.status).toBe(302); + expect(response.headers.get('location')).toBe( + 'https://totally-real-host.myshopify.io/apps/testApiKey', + ); + }); + } else { + test('throws a 302 to /', async () => { + // GIVEN + const config = testConfig({ + isEmbeddedApp: false, + }); + const shopify = shopifyApp(config); + + // WHEN + await mockCodeExchangeRequest('offline'); + const request = await getValidCallbackRequest(config); + const response = await getThrownResponse( + shopify.authenticate.admin, + request, + ); + + // THEN + const url = new URL(request.url); + const host = url.searchParams.get('host'); + expect(response.status).toBe(302); + expect(response.headers.get('location')).toBe( + `/?shop=${TEST_SHOP}&host=${host}`, + ); + expect(response.headers.get('set-cookie')).toBe( + [ + 'shopify_app_state=;path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT', + `shopify_app_session=offline_${TEST_SHOP};sameSite=lax; secure=true; path=/`, + 'shopify_app_session.sig=0qSrbSUpq8Cr+fev917WGyO1IU3Py1fTwZukcHd4hVE=;sameSite=lax; secure=true; path=/', + ].join(', '), + ); + }); + } }); }); }); diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-path.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-path.test.ts index 7b7d3ee716..d3b78c6ce9 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-path.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/auth-path.test.ts @@ -5,13 +5,15 @@ import { expectBeginAuthRedirect, expectExitIframeRedirect, getThrownResponse, - testConfigAuthCodeFlow, + testConfig, } from '../../../../../__test-helpers'; describe('authorize.admin auth path', () => { test('throws an 400 Response if the shop param is missing', async () => { // GIVEN - const config = testConfigAuthCodeFlow(); + const config = testConfig({ + future: {unstable_newEmbeddedAuthStrategy: false}, + }); const shopify = shopifyApp(config); // WHEN @@ -27,7 +29,9 @@ describe('authorize.admin auth path', () => { test('throws an 400 Response if the shop param is invalid', async () => { // GIVEN - const config = testConfigAuthCodeFlow(); + const config = testConfig({ + future: {unstable_newEmbeddedAuthStrategy: false}, + }); const shopify = shopifyApp(config); // WHEN @@ -43,7 +47,9 @@ describe('authorize.admin auth path', () => { test('throws an 302 Response to begin auth', async () => { // GIVEN - const config = testConfigAuthCodeFlow(); + const config = testConfig({ + future: {unstable_newEmbeddedAuthStrategy: false}, + }); const shopify = shopifyApp(config); // WHEN @@ -59,7 +65,9 @@ describe('authorize.admin auth path', () => { test('redirects to exit-iframe when loading the auth path while in an iframe request', async () => { // GIVEN - const config = testConfigAuthCodeFlow(); + const config = testConfig({ + future: {unstable_newEmbeddedAuthStrategy: false}, + }); const shopify = shopifyApp(config); // WHEN diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/authenticate.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/authenticate.test.ts index 815a9f0e68..93ac46f027 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/authenticate.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/authenticate.test.ts @@ -10,7 +10,7 @@ import { getJwt, getThrownResponse, setUpValidSession, - testConfigAuthCodeFlow, + testConfig, signRequestCookie, } from '../../../../../__test-helpers'; @@ -19,7 +19,11 @@ describe('authenticate', () => { it('redirects to exit-iframe if app is embedded and the session is no longer valid for the id_token when embedded', async () => { // GIVEN const shopify = shopifyApp( - testConfigAuthCodeFlow({scopes: ['otherTestScope']}), + testConfig({ + scopes: ['otherTestScope'], + future: {unstable_newEmbeddedAuthStrategy: false}, + isEmbeddedApp: true, + }), ); await setUpValidSession(shopify.sessionStorage); @@ -39,10 +43,10 @@ describe('authenticate', () => { // manageAccessToken or ensureInstalledOnShop it('redirects to auth if there is no session cookie for non-embedded apps when at the top level', async () => { // GIVEN - const config = testConfigAuthCodeFlow(); - const shopify = shopifyApp( - testConfigAuthCodeFlow({isEmbeddedApp: false}), - ); + const config = testConfig({ + isEmbeddedApp: false, + }); + const shopify = shopifyApp(config); await setUpValidSession(shopify.sessionStorage); // WHEN @@ -61,9 +65,9 @@ describe('authenticate', () => { it('redirects to auth if the session is no longer valid for non-embedded apps when at the top level', async () => { // GIVEN - const config = testConfigAuthCodeFlow({ - isEmbeddedApp: false, + const config = testConfig({ scopes: ['otherTestScope'], + isEmbeddedApp: false, }); const shopify = shopifyApp(config); const session = await setUpValidSession(shopify.sessionStorage); @@ -94,16 +98,19 @@ describe('authenticate', () => { it('returns the context if the session is valid and the app is embedded', async () => { // GIVEN const shopify = shopifyApp( - testConfigAuthCodeFlow({useOnlineTokens: isOnline}), + testConfig({ + useOnlineTokens: isOnline, + future: {unstable_newEmbeddedAuthStrategy: false}, + isEmbeddedApp: true, + }), ); let testSession: Session; testSession = await setUpValidSession(shopify.sessionStorage); if (isOnline) { - testSession = await setUpValidSession( - shopify.sessionStorage, + testSession = await setUpValidSession(shopify.sessionStorage, { isOnline, - ); + }); } // WHEN @@ -122,16 +129,17 @@ describe('authenticate', () => { it('returns the context if the session is valid and the app is not embedded', async () => { // GIVEN const shopify = shopifyApp( - testConfigAuthCodeFlow({isEmbeddedApp: false}), + testConfig({ + isEmbeddedApp: false, + }), ); let testSession: Session; testSession = await setUpValidSession(shopify.sessionStorage); if (isOnline) { - testSession = await setUpValidSession( - shopify.sessionStorage, + testSession = await setUpValidSession(shopify.sessionStorage, { isOnline, - ); + }); } // WHEN @@ -156,7 +164,7 @@ describe('authenticate', () => { // manageAccessToken & ensureInstalledOnShop it('loads a session from the cookie from a request with no search params when not embedded', async () => { // GIVEN - const shopify = shopifyApp(testConfigAuthCodeFlow({isEmbeddedApp: false})); + const shopify = shopifyApp(testConfig({isEmbeddedApp: false})); const testSession = await setUpValidSession(shopify.sessionStorage); // WHEN diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/ensure-installed-on-shop.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/ensure-installed-on-shop.test.ts index eac16909b8..7f33a0182d 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/ensure-installed-on-shop.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/ensure-installed-on-shop.test.ts @@ -13,16 +13,16 @@ import { getJwt, getThrownResponse, setUpValidSession, - testConfigAuthCodeFlow, signRequestCookie, mockExternalRequest, + testConfig, } from '../../../../../__test-helpers'; describe('authorize.admin doc request path', () => { describe('errors', () => { it('redirects to auth when not embedded and there is no offline session', async () => { // GIVEN - const config = testConfigAuthCodeFlow(); + const config = testConfig({isEmbeddedApp: false}); const shopify = shopifyApp(config); // WHEN @@ -37,7 +37,12 @@ describe('authorize.admin doc request path', () => { it('redirects to exit-iframe when embedded and there is no offline session', async () => { // GIVEN - const shopify = shopifyApp(testConfigAuthCodeFlow()); + const shopify = shopifyApp( + testConfig({ + future: {unstable_newEmbeddedAuthStrategy: false}, + isEmbeddedApp: true, + }), + ); // WHEN const response = await getThrownResponse( @@ -53,7 +58,10 @@ describe('authorize.admin doc request path', () => { it('redirects to auth when not embedded on an embedded app, and the API token is invalid', async () => { // GIVEN - const config = testConfigAuthCodeFlow(); + const config = testConfig({ + future: {unstable_newEmbeddedAuthStrategy: false}, + isEmbeddedApp: true, + }); const shopify = shopifyApp(config); await setUpValidSession(shopify.sessionStorage); @@ -74,7 +82,10 @@ describe('authorize.admin doc request path', () => { it('returns non-401 codes when not embedded on an embedded app and the request fails', async () => { // GIVEN - const config = testConfigAuthCodeFlow(); + const config = testConfig({ + future: {unstable_newEmbeddedAuthStrategy: false}, + isEmbeddedApp: true, + }); const shopify = shopifyApp(config); await setUpValidSession(shopify.sessionStorage); @@ -102,7 +113,10 @@ describe('authorize.admin doc request path', () => { it('returns a 500 when not embedded on an embedded app and the request fails', async () => { // GIVEN - const config = testConfigAuthCodeFlow(); + const config = testConfig({ + future: {unstable_newEmbeddedAuthStrategy: false}, + isEmbeddedApp: true, + }); const shopify = shopifyApp(config); await setUpValidSession(shopify.sessionStorage); @@ -129,7 +143,12 @@ describe('authorize.admin doc request path', () => { it('redirects to exit-iframe if app is embedded and there is no session for the id_token when embedded', async () => { // GIVEN - const shopify = shopifyApp(testConfigAuthCodeFlow()); + const shopify = shopifyApp( + testConfig({ + future: {unstable_newEmbeddedAuthStrategy: false}, + isEmbeddedApp: true, + }), + ); await setUpValidSession(shopify.sessionStorage); const otherShopDomain = 'other-shop.myshopify.io'; @@ -146,13 +165,12 @@ describe('authorize.admin doc request path', () => { expectExitIframeRedirect(response, {shop: otherShopDomain}); }); - // manageAccessToken or ensureInstalledOnShop it('redirects to auth if there is no session cookie for non-embedded apps when at the top level', async () => { // GIVEN - const config = testConfigAuthCodeFlow(); - const shopify = shopifyApp( - testConfigAuthCodeFlow({isEmbeddedApp: false}), - ); + const config = testConfig({ + isEmbeddedApp: false, + }); + const shopify = shopifyApp(config); await setUpValidSession(shopify.sessionStorage); // WHEN @@ -171,7 +189,7 @@ describe('authorize.admin doc request path', () => { it('redirects to auth if there is no session for non-embedded apps when at the top level', async () => { // GIVEN - const config = testConfigAuthCodeFlow({isEmbeddedApp: false}); + const config = testConfig({isEmbeddedApp: false}); const shopify = shopifyApp(config); await setUpValidSession(shopify.sessionStorage); @@ -195,10 +213,9 @@ describe('authorize.admin doc request path', () => { }); }); - // manageAccessToken & ensureInstalledOnShop it('loads a session from the cookie from a request with no search params when not embedded', async () => { // GIVEN - const shopify = shopifyApp(testConfigAuthCodeFlow({isEmbeddedApp: false})); + const shopify = shopifyApp(testConfig({isEmbeddedApp: false})); const testSession = await setUpValidSession(shopify.sessionStorage); // WHEN diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/session-token-header-path.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/session-token-header-path.test.ts index 30ce886968..56d14ae7ea 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/session-token-header-path.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/session-token-header-path.test.ts @@ -8,8 +8,7 @@ import { getJwt, getThrownResponse, setUpValidSession, - signRequestCookie, - testConfigAuthCodeFlow, + testConfig, } from '../../../../../__test-helpers'; import {REAUTH_URL_HEADER} from '../../../../const'; @@ -19,7 +18,10 @@ describe('authorize.session token header path', () => { it(`returns app bridge redirection headers if there is no session`, async () => { // GIVEN const shopify = shopifyApp( - testConfigAuthCodeFlow({useOnlineTokens: isOnline}), + testConfig({ + useOnlineTokens: isOnline, + future: {unstable_newEmbeddedAuthStrategy: false}, + }), ); // WHEN @@ -45,13 +47,14 @@ describe('authorize.session token header path', () => { it(`returns app bridge redirection headers if the session is no longer valid`, async () => { // GIVEN const shopify = shopifyApp( - testConfigAuthCodeFlow({ + testConfig({ useOnlineTokens: isOnline, scopes: ['otherTestScope'], + future: {unstable_newEmbeddedAuthStrategy: false}, }), ); // The session scopes don't match the configured scopes, so it needs to be reset - await setUpValidSession(shopify.sessionStorage, isOnline); + await setUpValidSession(shopify.sessionStorage, {isOnline}); // WHEN const {token} = getJwt(); diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts index 89758cf008..5cfe1f2689 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts @@ -53,7 +53,10 @@ describe('authenticate', () => { const config = testConfig({useOnlineTokens: true}); const shopify = shopifyApp(config); const anHourAgo = new Date(Date.now() - 1000 * 3600); - await setUpValidSession(shopify.sessionStorage, true, anHourAgo); + await setUpValidSession(shopify.sessionStorage, { + isOnline: true, + expires: anHourAgo, + }); const {token} = getJwt(); await mockTokenExchangeRequest(token, 'offline'); @@ -93,10 +96,9 @@ describe('authenticate', () => { let testSession: Session; testSession = await setUpValidSession(shopify.sessionStorage); if (isOnline) { - testSession = await setUpValidSession( - shopify.sessionStorage, + testSession = await setUpValidSession(shopify.sessionStorage, { isOnline, - ); + }); } // WHEN @@ -115,7 +117,7 @@ describe('authenticate', () => { }, ); - test('redirects to bounce page when receiving an invalid subject token response from token exchange API', async () => { + test('redirects to bounce page on document request when receiving an invalid subject token response from token exchange API', async () => { // GIVEN const config = testConfig(); const shopify = shopifyApp(config); @@ -144,10 +146,9 @@ describe('authenticate', () => { expect(searchParams.get('shopify-reload')).toBe( `${APP_URL}/?embedded=1&shop=${TEST_SHOP}&host=${BASE64_HOST}`, ); - expect(fetchMock.mock.calls).toHaveLength(1); }); - test('throws 401 unauthorized when receiving an invalid subject token response from token exchange API', async () => { + test('throws 401 unauthorized on XHR request when receiving an invalid subject token response from token exchange API', async () => { // GIVEN const config = testConfig(); const shopify = shopifyApp(config); @@ -167,7 +168,6 @@ describe('authenticate', () => { // THEN expect(response.status).toBe(401); - expect(fetchMock.mock.calls).toHaveLength(1); }); test('throws 500 for any other error from token exchange API', async () => { @@ -190,7 +190,6 @@ describe('authenticate', () => { // THEN expect(response.status).toBe(500); - expect(fetchMock.mock.calls).toHaveLength(1); }); test('throws a 500 if afterAuth hook throws an error', async () => { diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts index 8f836c43bf..7ee32e12c7 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts @@ -7,7 +7,7 @@ import { ShopifyRestResources, } from '@shopify/shopify-api'; import {AppConfig, AppConfigArg} from 'src/server/config-types'; -import {BasicParams, MockApiConfig} from 'src/server/types'; +import {BasicParams, ApiConfigWithFutureFlags} from 'src/server/types'; import {triggerAfterAuthHook} from '../helpers'; import {respondToInvalidSessionToken} from '../../helpers'; @@ -19,7 +19,7 @@ export class TokenExchangeStrategy< Resources extends ShopifyRestResources = ShopifyRestResources, > implements AuthorizationStrategy { - protected api: Shopify>; + protected api: Shopify>; protected config: AppConfig; protected logger: Shopify['logger']; @@ -41,6 +41,7 @@ export class TokenExchangeStrategy< if (!sessionToken) throw new InvalidJwtError(); if (!session || session.isExpired()) { + logger.info('No valid session found'); logger.info('Requesting offline access token'); const {session: offlineSession} = await this.exchangeToken({ request, diff --git a/packages/shopify-app-remix/src/server/authenticate/public/appProxy/__tests__/authenticate.test.ts b/packages/shopify-app-remix/src/server/authenticate/public/appProxy/__tests__/authenticate.test.ts index 81eb4bc146..9d9ff62644 100644 --- a/packages/shopify-app-remix/src/server/authenticate/public/appProxy/__tests__/authenticate.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/public/appProxy/__tests__/authenticate.test.ts @@ -215,10 +215,9 @@ describe('authenticating app proxy requests', () => { describe('Valid requests with a session return an admin API client', () => { expectAdminApiClient(async () => { const shopify = shopifyApp(testConfig()); - const expectedSession = await setUpValidSession( - shopify.sessionStorage, - false, - ); + const expectedSession = await setUpValidSession(shopify.sessionStorage, { + isOnline: false, + }); const {admin, session: actualSession} = await shopify.authenticate.public.appProxy(await getValidRequest()); @@ -234,10 +233,9 @@ describe('authenticating app proxy requests', () => { describe('Valid requests with a session return a Storefront API client', () => { expectStorefrontApiClient(async () => { const shopify = shopifyApp(testConfig()); - const expectedSession = await setUpValidSession( - shopify.sessionStorage, - false, - ); + const expectedSession = await setUpValidSession(shopify.sessionStorage, { + isOnline: false, + }); const {storefront, session: actualSession} = await shopify.authenticate.public.appProxy(await getValidRequest()); diff --git a/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/register.test.ts b/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/register.test.ts index 221c25b6c6..c8cec6fdc4 100644 --- a/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/register.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/register.test.ts @@ -111,6 +111,8 @@ describe('Webhook registration', () => { }); }); + // eslint-disable-next-line no-warning-comments + // TODO: Remove tests once we have a better solution to parallel afterAuth calls it('logs throttling errors', async () => { // GIVEN const shopify = shopifyApp( diff --git a/packages/shopify-app-remix/src/server/future/flags.ts b/packages/shopify-app-remix/src/server/future/flags.ts index b70a1b1a77..e8d75841ea 100644 --- a/packages/shopify-app-remix/src/server/future/flags.ts +++ b/packages/shopify-app-remix/src/server/future/flags.ts @@ -16,7 +16,7 @@ export interface FutureFlags { v3_authenticatePublic?: boolean; /** - * Enable token exchange token acquisition strategy with shopify managed install. + * When enabled, embedded apps will fetch access tokens via token exchange. This assumes app are using declarative scopes with Shopify managing installs. * * @default false */ diff --git a/packages/shopify-app-remix/src/server/shopify-app.ts b/packages/shopify-app-remix/src/server/shopify-app.ts index f112c0dad5..eea9e28ba7 100644 --- a/packages/shopify-app-remix/src/server/shopify-app.ts +++ b/packages/shopify-app-remix/src/server/shopify-app.ts @@ -67,14 +67,12 @@ export function shopifyApp< } const params: BasicParams = {api, config, logger}; - const oauth = new AuthCodeFlowStrategy(params); - const tokenExchange = new TokenExchangeStrategy(params); const authStrategy = authStrategyFactory({ ...params, strategy: config.future.unstable_newEmbeddedAuthStrategy && config.isEmbeddedApp - ? tokenExchange - : oauth, + ? new TokenExchangeStrategy(params) + : new AuthCodeFlowStrategy(params), }); const shopify: diff --git a/packages/shopify-app-remix/src/server/types.ts b/packages/shopify-app-remix/src/server/types.ts index ec3d42dad2..323f7b0af4 100644 --- a/packages/shopify-app-remix/src/server/types.ts +++ b/packages/shopify-app-remix/src/server/types.ts @@ -19,18 +19,19 @@ import {FutureFlagOptions, FutureFlags} from './future/flags'; export interface BasicParams< Future extends FutureFlagOptions = FutureFlagOptions, > { - api: Shopify>; + api: Shopify>; config: AppConfig; logger: Shopify['logger']; } -export type MockApiConfig = ConfigParams & { - future?: { - unstable_tokenExchange?: Future extends FutureFlags - ? Future['unstable_newEmbeddedAuthStrategy'] - : boolean; +export type ApiConfigWithFutureFlags = + ConfigParams & { + future?: { + unstable_tokenExchange?: Future extends FutureFlags + ? Future['unstable_newEmbeddedAuthStrategy'] + : boolean; + }; }; -}; export type JSONValue = | string diff --git a/packages/shopify-app-remix/src/server/unauthenticated/admin/__tests__/factory.test.ts b/packages/shopify-app-remix/src/server/unauthenticated/admin/__tests__/factory.test.ts index 7e74ec6280..c7cbe53ef0 100644 --- a/packages/shopify-app-remix/src/server/unauthenticated/admin/__tests__/factory.test.ts +++ b/packages/shopify-app-remix/src/server/unauthenticated/admin/__tests__/factory.test.ts @@ -17,10 +17,9 @@ describe('unauthenticated admin context', () => { expectAdminApiClient(async () => { const shopify = shopifyApp(testConfig()); - const expectedSession = await setUpValidSession( - shopify.sessionStorage, - false, - ); + const expectedSession = await setUpValidSession(shopify.sessionStorage, { + isOnline: false, + }); const {admin, session: actualSession} = await shopify.unauthenticated.admin( TEST_SHOP, ); diff --git a/packages/shopify-app-remix/src/server/unauthenticated/storefront/__tests__/factory.test.ts b/packages/shopify-app-remix/src/server/unauthenticated/storefront/__tests__/factory.test.ts index 5ac01e34e6..c747ab4d25 100644 --- a/packages/shopify-app-remix/src/server/unauthenticated/storefront/__tests__/factory.test.ts +++ b/packages/shopify-app-remix/src/server/unauthenticated/storefront/__tests__/factory.test.ts @@ -19,10 +19,9 @@ describe('unauthenticated storefront context', () => { expectStorefrontApiClient(async () => { const shopify = shopifyApp(testConfig()); - const expectedSession = await setUpValidSession( - shopify.sessionStorage, - false, - ); + const expectedSession = await setUpValidSession(shopify.sessionStorage, { + isOnline: false, + }); const {storefront, session: actualSession} = await shopify.unauthenticated.storefront(TEST_SHOP); From 54f939b1c9b96e108cf1ae3a64cf56e16c05eb79 Mon Sep 17 00:00:00 2001 From: Rezaan Syed Date: Thu, 7 Dec 2023 11:36:39 -0500 Subject: [PATCH 15/41] Introduce idempotent promise handler --- .../admin/strategies/token-exchange.ts | 27 +++++++--- .../idempotent-promise-handler.test.ts | 51 +++++++++++++++++++ .../helpers/idempotent-promise-handler.ts | 33 ++++++++++++ .../src/server/config-types.ts | 2 + .../src/server/shopify-app.ts | 2 + 5 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 packages/shopify-app-remix/src/server/authenticate/helpers/__tests__/idempotent-promise-handler.test.ts create mode 100644 packages/shopify-app-remix/src/server/authenticate/helpers/idempotent-promise-handler.ts diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts index 7ee32e12c7..e43b0ecfa7 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts @@ -4,20 +4,17 @@ import { RequestedTokenType, Session, Shopify, - ShopifyRestResources, } from '@shopify/shopify-api'; import {AppConfig, AppConfigArg} from 'src/server/config-types'; import {BasicParams, ApiConfigWithFutureFlags} from 'src/server/types'; -import {triggerAfterAuthHook} from '../helpers'; import {respondToInvalidSessionToken} from '../../helpers'; +import {triggerAfterAuthHook} from '../helpers'; import {AuthorizationStrategy, SessionContext} from './types'; -export class TokenExchangeStrategy< - Config extends AppConfigArg, - Resources extends ShopifyRestResources = ShopifyRestResources, -> implements AuthorizationStrategy +export class TokenExchangeStrategy + implements AuthorizationStrategy { protected api: Shopify>; protected config: AppConfig; @@ -68,10 +65,11 @@ export class TokenExchangeStrategy< } try { - await triggerAfterAuthHook( + await this.handleAfterAuthHook( {api, config, logger}, newSession, request, + sessionToken, ); } catch (error) { throw new Response(undefined, { @@ -121,4 +119,19 @@ export class TokenExchangeStrategy< }); } } + + private async handleAfterAuthHook( + params: BasicParams, + session: Session, + request: Request, + sessionToken: string, + ) { + const {config} = params; + await config.idempotentPromiseHandler.handlePromise({ + promiseFunction: () => { + return triggerAfterAuthHook(params, session, request); + }, + identifier: sessionToken, + }); + } } diff --git a/packages/shopify-app-remix/src/server/authenticate/helpers/__tests__/idempotent-promise-handler.test.ts b/packages/shopify-app-remix/src/server/authenticate/helpers/__tests__/idempotent-promise-handler.test.ts new file mode 100644 index 0000000000..424a8046c7 --- /dev/null +++ b/packages/shopify-app-remix/src/server/authenticate/helpers/__tests__/idempotent-promise-handler.test.ts @@ -0,0 +1,51 @@ +import {IdempotentPromiseHandler} from '../idempotent-promise-handler'; + +const mockFunction = jest.fn(); +async function promiseFunction() { + mockFunction(); +} + +afterEach(() => { + mockFunction.mockReset(); +}); + +describe('IdempotentPromiseHandler', () => { + it('runs the promise function only once for the same identifier', async () => { + // GIVEN + const promiseHandler = new IdempotentPromiseHandler(); + + // WHEN + promiseHandler.handlePromise({ + promiseFunction, + identifier: 'first-promise', + }); + + promiseHandler.handlePromise({ + promiseFunction, + identifier: 'first-promise', + }); + + // THEN + expect(mockFunction).toHaveBeenCalledTimes(1); + }); + + it('runs the promise function once for each identifier', async () => { + // GIVEN + // const promiseFunction = jest.fn(); + const promiseHandler = new IdempotentPromiseHandler(); + + // WHEN + promiseHandler.handlePromise({ + promiseFunction, + identifier: 'first-promise', + }); + + promiseHandler.handlePromise({ + promiseFunction, + identifier: 'second-promise', + }); + + // THEN + expect(mockFunction).toHaveBeenCalledTimes(2); + }); +}); diff --git a/packages/shopify-app-remix/src/server/authenticate/helpers/idempotent-promise-handler.ts b/packages/shopify-app-remix/src/server/authenticate/helpers/idempotent-promise-handler.ts new file mode 100644 index 0000000000..e08fb9c9d1 --- /dev/null +++ b/packages/shopify-app-remix/src/server/authenticate/helpers/idempotent-promise-handler.ts @@ -0,0 +1,33 @@ +export interface IdempotentHandlePromiseParams { + promiseFunction: () => Promise; + identifier: string; +} + +export class IdempotentPromiseHandler { + protected identifiers: Set; + + constructor() { + this.identifiers = new Set(); + } + + async handlePromise({ + promiseFunction, + identifier, + }: IdempotentHandlePromiseParams): Promise { + if (this.isPromiseRunnable(identifier)) { + await promiseFunction(); + } + + return Promise.resolve(); + } + + private isPromiseRunnable(identifier?: string) { + if (!identifier) return true; + + if (!this.identifiers.has(identifier)) { + this.identifiers.add(identifier); + return true; + } + return false; + } +} diff --git a/packages/shopify-app-remix/src/server/config-types.ts b/packages/shopify-app-remix/src/server/config-types.ts index 78e9b6a3b7..4f68ce8df6 100644 --- a/packages/shopify-app-remix/src/server/config-types.ts +++ b/packages/shopify-app-remix/src/server/config-types.ts @@ -11,6 +11,7 @@ import {SessionStorage} from '@shopify/shopify-app-session-storage'; import type {FutureFlagOptions, FutureFlags} from './future/flags'; import type {AppDistribution} from './types'; import type {AdminApiContext} from './clients'; +import {IdempotentPromiseHandler} from './authenticate/helpers/idempotent-promise-handler'; export interface AppConfigArg< Resources extends ShopifyRestResources = ShopifyRestResources, @@ -233,6 +234,7 @@ export interface AppConfig useOnlineTokens: boolean; hooks: HooksConfig; future: FutureFlags; + idempotentPromiseHandler: IdempotentPromiseHandler; } export interface AuthConfig { diff --git a/packages/shopify-app-remix/src/server/shopify-app.ts b/packages/shopify-app-remix/src/server/shopify-app.ts index eea9e28ba7..b3f5ac187a 100644 --- a/packages/shopify-app-remix/src/server/shopify-app.ts +++ b/packages/shopify-app-remix/src/server/shopify-app.ts @@ -31,6 +31,7 @@ import {authenticatePublicFactory} from './authenticate/public'; import {unauthenticatedStorefrontContextFactory} from './unauthenticated/storefront'; import {AuthCodeFlowStrategy} from './authenticate/admin/strategies/auth-code-flow'; import {TokenExchangeStrategy} from './authenticate/admin/strategies/token-exchange'; +import {IdempotentPromiseHandler} from './authenticate/helpers/idempotent-promise-handler'; /** * Creates an object your app will use to interact with Shopify. @@ -174,6 +175,7 @@ function deriveConfig( return { ...appConfig, ...apiConfig, + idempotentPromiseHandler: new IdempotentPromiseHandler(), canUseLoginForm: appConfig.distribution !== AppDistribution.ShopifyAdmin, useOnlineTokens: appConfig.useOnlineTokens ?? false, hooks: appConfig.hooks ?? {}, From c9b47116507670ee998a45a7ee430cacbbbe8698 Mon Sep 17 00:00:00 2001 From: Zoey Lan Date: Thu, 7 Dec 2023 15:01:55 -0700 Subject: [PATCH 16/41] Redirect to install from app home page if future flag is on and app is embedded --- .../login/__tests__/login.test.ts | 104 ++++++++++-------- .../src/server/authenticate/login/login.ts | 7 +- 2 files changed, 67 insertions(+), 44 deletions(-) diff --git a/packages/shopify-app-remix/src/server/authenticate/login/__tests__/login.test.ts b/packages/shopify-app-remix/src/server/authenticate/login/__tests__/login.test.ts index ce957516ef..62a6c20269 100644 --- a/packages/shopify-app-remix/src/server/authenticate/login/__tests__/login.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/login/__tests__/login.test.ts @@ -79,25 +79,63 @@ describe('login helper', () => { }, ); - it.each([ - {urlShop: null, formShop: TEST_SHOP, method: 'POST'}, - {urlShop: TEST_SHOP, formShop: null, method: 'GET'}, - {urlShop: null, formShop: 'test-shop', method: 'POST'}, - {urlShop: 'test-shop', formShop: null, method: 'GET'}, - {urlShop: null, formShop: 'test-shop.myshopify.com', method: 'POST'}, - {urlShop: 'test-shop.myshopify.com', formShop: null, method: 'GET'}, - ])( - 'returns a redirect to /auth if the shop is valid: %s', - async ({urlShop, formShop, method}) => { + describe.each([ + {isEmbeddedApp: false, futureFlag: false, redirectToInstall: false}, + {isEmbeddedApp: true, futureFlag: false, redirectToInstall: false}, + {isEmbeddedApp: false, futureFlag: true, redirectToInstall: false}, + {isEmbeddedApp: true, futureFlag: true, redirectToInstall: true}, + ])('Given setup: %s', (testCaseConfig) => { + it.each([ + {urlShop: null, formShop: TEST_SHOP, method: 'POST'}, + {urlShop: TEST_SHOP, formShop: null, method: 'GET'}, + {urlShop: null, formShop: 'test-shop', method: 'POST'}, + {urlShop: 'test-shop', formShop: null, method: 'GET'}, + {urlShop: null, formShop: 'test-shop.myshopify.com', method: 'POST'}, + {urlShop: 'test-shop.myshopify.com', formShop: null, method: 'GET'}, + ])( + 'returns a redirect to auth or install if the shop is valid: %s', + async ({urlShop, formShop, method}) => { + // GIVEN + const config = testConfig({ + future: {unstable_newEmbeddedAuthStrategy: testCaseConfig.futureFlag}, + isEmbeddedApp: testCaseConfig.isEmbeddedApp, + }); + const shopify = shopifyApp(config); + const requestMock = { + url: urlShop + ? `${APP_URL}/auth/login?shop=${urlShop}` + : `${APP_URL}/auth/login`, + formData: async () => ({get: () => formShop}), + method, + }; + + // WHEN + const response = await getThrownResponse( + shopify.login, + requestMock as any as Request, + ); + + // THEN + const expectedPath = testCaseConfig.redirectToInstall + ? `https://${TEST_SHOP}/admin/oauth/install?client_id=${config.apiKey}` + : `${APP_URL}/auth?shop=${TEST_SHOP}`; + + expect(response.status).toEqual(302); + expect(response.headers.get('location')).toEqual(expectedPath); + }, + ); + + it('sanitizes the shop parameter', async () => { // GIVEN - const config = testConfig(); - const shopify = shopifyApp(testConfig()); + const config = testConfig({ + future: {unstable_newEmbeddedAuthStrategy: testCaseConfig.futureFlag}, + isEmbeddedApp: testCaseConfig.isEmbeddedApp, + }); + const shopify = shopifyApp(config); const requestMock = { - url: urlShop - ? `${APP_URL}/auth/login?shop=${urlShop}` - : `${APP_URL}/auth/login`, - formData: async () => ({get: () => formShop}), - method, + url: `${APP_URL}/auth/login`, + formData: async () => ({get: () => `https://${TEST_SHOP}/`}), + method: 'POST', }; // WHEN @@ -106,33 +144,13 @@ describe('login helper', () => { requestMock as any as Request, ); + const expectedPath = testCaseConfig.redirectToInstall + ? `https://${TEST_SHOP}/admin/oauth/install?client_id=${config.apiKey}` + : `${APP_URL}/auth?shop=${TEST_SHOP}`; + // THEN expect(response.status).toEqual(302); - expect(response.headers.get('location')).toEqual( - `${APP_URL}/auth?shop=${TEST_SHOP}`, - ); - }, - ); - - it('sanitizes the shop parameter', async () => { - // GIVEN - const shopify = shopifyApp(testConfig()); - const requestMock = { - url: `${APP_URL}/auth/login`, - formData: async () => ({get: () => `https://${TEST_SHOP}/`}), - method: 'POST', - }; - - // WHEN - const response = await getThrownResponse( - shopify.login, - requestMock as any as Request, - ); - - // THEN - expect(response.status).toEqual(302); - expect(response.headers.get('location')).toEqual( - `${APP_URL}/auth?shop=${TEST_SHOP}`, - ); + expect(response.headers.get('location')).toEqual(expectedPath); + }); }); }); diff --git a/packages/shopify-app-remix/src/server/authenticate/login/login.ts b/packages/shopify-app-remix/src/server/authenticate/login/login.ts index 900600a89f..d9f0641acb 100644 --- a/packages/shopify-app-remix/src/server/authenticate/login/login.ts +++ b/packages/shopify-app-remix/src/server/authenticate/login/login.ts @@ -35,7 +35,12 @@ export function loginFactory(params: BasicParams) { return {shop: LoginErrorType.InvalidShop}; } - const redirectUrl = `${config.appUrl}${config.auth.path}?shop=${sanitizedShop}`; + const authPath = `${config.appUrl}${config.auth.path}?shop=${sanitizedShop}`; + const installPath = `https://${sanitizedShop}/admin/oauth/install?client_id=${config.apiKey}`; + + const shouldInstall = + config.isEmbeddedApp && config.future.unstable_newEmbeddedAuthStrategy; + const redirectUrl = shouldInstall ? installPath : authPath; logger.info(`Redirecting login request to ${redirectUrl}`, { shop: sanitizedShop, From 231f10d0bd76cb22a080503cf21308f000078a00 Mon Sep 17 00:00:00 2001 From: Zoey Lan Date: Tue, 12 Dec 2023 08:26:35 -0700 Subject: [PATCH 17/41] Redirect to new admin URL instead of legacy --- .../shopify-app-remix/src/server/__test-helpers/const.ts | 3 ++- .../src/server/authenticate/login/__tests__/login.test.ts | 5 +++-- .../shopify-app-remix/src/server/authenticate/login/login.ts | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/shopify-app-remix/src/server/__test-helpers/const.ts b/packages/shopify-app-remix/src/server/__test-helpers/const.ts index d41729a69c..63442bc23f 100644 --- a/packages/shopify-app-remix/src/server/__test-helpers/const.ts +++ b/packages/shopify-app-remix/src/server/__test-helpers/const.ts @@ -5,6 +5,7 @@ export const API_KEY = 'testApiKey'; export const APP_URL = 'https://my-test-app.myshopify.io'; export const SHOPIFY_HOST = 'totally-real-host.myshopify.io'; export const BASE64_HOST = Buffer.from(SHOPIFY_HOST).toString('base64'); -export const TEST_SHOP = 'test-shop.myshopify.com'; +export const TEST_SHOP_NAME = 'test-shop'; +export const TEST_SHOP = `${TEST_SHOP_NAME}.myshopify.com`; export const GRAPHQL_URL = `https://${TEST_SHOP}/admin/api/${LATEST_API_VERSION}/graphql.json`; export const USER_ID = 12345; diff --git a/packages/shopify-app-remix/src/server/authenticate/login/__tests__/login.test.ts b/packages/shopify-app-remix/src/server/authenticate/login/__tests__/login.test.ts index 62a6c20269..f7bd8de25b 100644 --- a/packages/shopify-app-remix/src/server/authenticate/login/__tests__/login.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/login/__tests__/login.test.ts @@ -2,6 +2,7 @@ import {LoginErrorType, shopifyApp} from '../../../index'; import { APP_URL, TEST_SHOP, + TEST_SHOP_NAME, getThrownResponse, testConfig, } from '../../../__test-helpers'; @@ -117,7 +118,7 @@ describe('login helper', () => { // THEN const expectedPath = testCaseConfig.redirectToInstall - ? `https://${TEST_SHOP}/admin/oauth/install?client_id=${config.apiKey}` + ? `https://admin.shopify.com/store/${TEST_SHOP_NAME}/oauth/install?client_id=${config.apiKey}` : `${APP_URL}/auth?shop=${TEST_SHOP}`; expect(response.status).toEqual(302); @@ -145,7 +146,7 @@ describe('login helper', () => { ); const expectedPath = testCaseConfig.redirectToInstall - ? `https://${TEST_SHOP}/admin/oauth/install?client_id=${config.apiKey}` + ? `https://admin.shopify.com/store/${TEST_SHOP_NAME}/oauth/install?client_id=${config.apiKey}` : `${APP_URL}/auth?shop=${TEST_SHOP}`; // THEN diff --git a/packages/shopify-app-remix/src/server/authenticate/login/login.ts b/packages/shopify-app-remix/src/server/authenticate/login/login.ts index d9f0641acb..0b1e1355e6 100644 --- a/packages/shopify-app-remix/src/server/authenticate/login/login.ts +++ b/packages/shopify-app-remix/src/server/authenticate/login/login.ts @@ -36,7 +36,9 @@ export function loginFactory(params: BasicParams) { } const authPath = `${config.appUrl}${config.auth.path}?shop=${sanitizedShop}`; - const installPath = `https://${sanitizedShop}/admin/oauth/install?client_id=${config.apiKey}`; + + const storeName = sanitizedShop.split('.')[0]; + const installPath = `https://admin.shopify.com/store/${storeName}/oauth/install?client_id=${config.apiKey}`; const shouldInstall = config.isEmbeddedApp && config.future.unstable_newEmbeddedAuthStrategy; From bfba78a35e3935d66f113c2e3897026181a3aa6b Mon Sep 17 00:00:00 2001 From: Rezaan Syed Date: Fri, 8 Dec 2023 15:28:54 -0500 Subject: [PATCH 18/41] Clear stale identifiers in IdempotentPromiseHandler --- .../idempotent-promise-handler.test.ts | 53 +++++++++++++++++++ .../helpers/idempotent-promise-handler.ts | 28 +++++++--- 2 files changed, 73 insertions(+), 8 deletions(-) diff --git a/packages/shopify-app-remix/src/server/authenticate/helpers/__tests__/idempotent-promise-handler.test.ts b/packages/shopify-app-remix/src/server/authenticate/helpers/__tests__/idempotent-promise-handler.test.ts index 424a8046c7..235ab2d986 100644 --- a/packages/shopify-app-remix/src/server/authenticate/helpers/__tests__/idempotent-promise-handler.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/helpers/__tests__/idempotent-promise-handler.test.ts @@ -1,3 +1,5 @@ +import {ShopifyError} from '@shopify/shopify-api'; + import {IdempotentPromiseHandler} from '../idempotent-promise-handler'; const mockFunction = jest.fn(); @@ -48,4 +50,55 @@ describe('IdempotentPromiseHandler', () => { // THEN expect(mockFunction).toHaveBeenCalledTimes(2); }); + + it('clears stale identifier from hash', async () => { + // GIVEN + const currentDate = Date.now(); + jest.useFakeTimers().setSystemTime(currentDate); + const promiseHandler = new IdempotentPromiseHandler() as any; + + // WHEN + promiseHandler.handlePromise({ + promiseFunction, + identifier: 'old-promise', + }); + + jest.useFakeTimers().setSystemTime(currentDate + 70000); + + await promiseHandler.handlePromise({ + promiseFunction, + identifier: 'new-promise', + }); + + expect(promiseHandler.identifiers.size).toBe(1); + }); + + it('clears stale identifier from hash even when promise fails', async () => { + // GIVEN + const promiseFunctionErr = () => { + throw new ShopifyError(); + }; + const currentDate = Date.now(); + jest.useFakeTimers().setSystemTime(currentDate); + const promiseHandler = new IdempotentPromiseHandler() as any; + + // WHEN + expect( + promiseHandler.handlePromise({ + promiseFunction: promiseFunctionErr, + identifier: 'old-promise', + }), + ).rejects.toThrow(); + + jest.useFakeTimers().setSystemTime(currentDate + 70000); + + expect( + promiseHandler.handlePromise({ + promiseFunction: promiseFunctionErr, + identifier: 'new-promise', + }), + ).rejects.toThrow(); + + expect(promiseHandler.identifiers.size).toBe(1); + }); }); diff --git a/packages/shopify-app-remix/src/server/authenticate/helpers/idempotent-promise-handler.ts b/packages/shopify-app-remix/src/server/authenticate/helpers/idempotent-promise-handler.ts index e08fb9c9d1..e944546ad0 100644 --- a/packages/shopify-app-remix/src/server/authenticate/helpers/idempotent-promise-handler.ts +++ b/packages/shopify-app-remix/src/server/authenticate/helpers/idempotent-promise-handler.ts @@ -3,31 +3,43 @@ export interface IdempotentHandlePromiseParams { identifier: string; } +const IDENTIFIER_TTL_MS = 60000; + export class IdempotentPromiseHandler { - protected identifiers: Set; + protected identifiers: Map; constructor() { - this.identifiers = new Set(); + this.identifiers = new Map(); } async handlePromise({ promiseFunction, identifier, }: IdempotentHandlePromiseParams): Promise { - if (this.isPromiseRunnable(identifier)) { - await promiseFunction(); + try { + if (this.isPromiseRunnable(identifier)) { + await promiseFunction(); + } + } finally { + this.clearStaleIdentifiers(); } return Promise.resolve(); } - private isPromiseRunnable(identifier?: string) { - if (!identifier) return true; - + private isPromiseRunnable(identifier: string) { if (!this.identifiers.has(identifier)) { - this.identifiers.add(identifier); + this.identifiers.set(identifier, Date.now()); return true; } return false; } + + private async clearStaleIdentifiers() { + this.identifiers.forEach((date, identifier, map) => { + if (Date.now() - date > IDENTIFIER_TTL_MS) { + map.delete(identifier); + } + }); + } } From 240e54d686198f15865a1f8c6262fb78f9a333ca Mon Sep 17 00:00:00 2001 From: Rachel Carvalho Date: Wed, 6 Dec 2023 16:33:51 -0500 Subject: [PATCH 19/41] move invalid access token redirect to auth tests to auth code flow strategy --- .../admin/__tests__/admin-client.test.ts | 73 ------ .../auth-code-flow/admin-client.test.ts | 223 ++++++++++++++++++ 2 files changed, 223 insertions(+), 73 deletions(-) create mode 100644 packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/admin-client.test.ts diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/__tests__/admin-client.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/__tests__/admin-client.test.ts index 8d189ce97b..37307ef2a5 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/__tests__/admin-client.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/__tests__/admin-client.test.ts @@ -11,7 +11,6 @@ import { APP_URL, BASE64_HOST, TEST_SHOP, - expectExitIframeRedirect, getJwt, getThrownResponse, setUpValidSession, @@ -21,7 +20,6 @@ import { expectAdminApiClient, } from '../../../__test-helpers'; import {shopifyApp} from '../../..'; -import {REAUTH_URL_HEADER} from '../../const'; import {AdminApiContext} from '../../../clients'; describe('admin.authenticate context', () => { @@ -102,25 +100,6 @@ describe('admin.authenticate context', () => { ])( '$testGroup re-authentication', ({testGroup: _testGroup, mockRequest, action}) => { - it('redirects to auth when request receives a 401 response and not embedded', async () => { - // GIVEN - const {admin, session} = await setUpNonEmbeddedFlow(); - const requestMock = await mockRequest(401); - - // WHEN - const response = await getThrownResponse( - async () => action(admin, session), - requestMock, - ); - - // THEN - expect(response.status).toEqual(302); - - const {hostname, pathname} = new URL(response.headers.get('Location')!); - expect(hostname).toEqual(TEST_SHOP); - expect(pathname).toEqual('/admin/oauth/authorize'); - }); - it('throws a response when request receives a non-401 response and not embedded', async () => { // GIVEN const {admin, session} = await setUpNonEmbeddedFlow(); @@ -135,43 +114,6 @@ describe('admin.authenticate context', () => { // THEN expect(response.status).toEqual(403); }); - - it('redirects to exit iframe when request receives a 401 response and embedded', async () => { - // GIVEN - const {admin, session} = await setUpEmbeddedFlow(); - const requestMock = await mockRequest(401); - - // WHEN - const response = await getThrownResponse( - async () => action(admin, session), - requestMock, - ); - - // THEN - expectExitIframeRedirect(response); - }); - - it('returns app bridge redirection headers when request receives a 401 response on fetch requests', async () => { - // GIVEN - const {admin, session} = await setUpFetchFlow(); - const requestMock = await mockRequest(401); - - // WHEN - const response = await getThrownResponse( - async () => action(admin, session), - requestMock, - ); - - // THEN - expect(response.status).toEqual(401); - - const {origin, pathname, searchParams} = new URL( - response.headers.get(REAUTH_URL_HEADER)!, - ); - expect(origin).toEqual(APP_URL); - expect(pathname).toEqual('/auth'); - expect(searchParams.get('shop')).toEqual(TEST_SHOP); - }); }, ); }); @@ -192,21 +134,6 @@ async function setUpEmbeddedFlow() { }; } -async function setUpFetchFlow() { - const shopify = shopifyApp(testConfig({restResources})); - await setUpValidSession(shopify.sessionStorage); - - const {token} = getJwt(); - const request = new Request(APP_URL, { - headers: {Authorization: `Bearer ${token}`}, - }); - - return { - shopify, - ...(await shopify.authenticate.admin(request)), - }; -} - async function setUpNonEmbeddedFlow() { const shopify = shopifyApp(testConfig({restResources, isEmbeddedApp: false})); const session = await setUpValidSession(shopify.sessionStorage); diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/admin-client.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/admin-client.test.ts new file mode 100644 index 0000000000..4edccc22d3 --- /dev/null +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/admin-client.test.ts @@ -0,0 +1,223 @@ +import { + ApiVersion, + HttpMaxRetriesError, + LATEST_API_VERSION, + SESSION_COOKIE_NAME, + Session, +} from '@shopify/shopify-api'; +import {restResources} from '@shopify/shopify-api/rest/admin/2023-04'; + +import { + APP_URL, + BASE64_HOST, + TEST_SHOP, + expectExitIframeRedirect, + getJwt, + getThrownResponse, + setUpValidSession, + signRequestCookie, + testConfig, + mockExternalRequest, + expectAdminApiClient, +} from '../../../../../__test-helpers'; +import {shopifyApp} from '../../../../..'; +import {REAUTH_URL_HEADER} from '../../../../const'; +import {AdminApiContext} from '../../../../../clients'; + +describe('admin.authenticate context', () => { + expectAdminApiClient(async () => { + const { + admin, + expectedSession, + session: actualSession, + } = await setUpEmbeddedFlow(); + + return {admin, expectedSession, actualSession}; + }); + describe.each([ + { + testGroup: 'REST client', + mockRequest: mockRestRequest, + action: async (admin: AdminApiContext, _session: Session) => + admin.rest.get({path: '/customers.json'}), + }, + { + testGroup: 'REST resources', + mockRequest: mockRestRequest, + action: async (admin: AdminApiContext, session: Session) => + admin.rest.resources.Customer.all({session}), + }, + { + testGroup: 'GraphQL client', + mockRequest: mockGraphqlRequest(), + action: async (admin: AdminApiContext, _session: Session) => + admin.graphql('{ shop { name } }'), + }, + { + testGroup: 'GraphQL client with options', + mockRequest: mockGraphqlRequest('2021-01' as ApiVersion), + action: async (admin: AdminApiContext, _session: Session) => + admin.graphql( + 'mutation myMutation($ID: String!) { shop(ID: $ID) { name } }', + { + variables: {ID: '123'}, + apiVersion: '2021-01' as ApiVersion, + headers: {custom: 'header'}, + tries: 2, + }, + ), + }, + ])( + '$testGroup re-authentication', + ({testGroup: _testGroup, mockRequest, action}) => { + it('redirects to auth when request receives a 401 response and not embedded', async () => { + // GIVEN + const {admin, session} = await setUpNonEmbeddedFlow(); + const requestMock = await mockRequest(401); + + // WHEN + const response = await getThrownResponse( + async () => action(admin, session), + requestMock, + ); + + // THEN + expect(response.status).toEqual(302); + + const {hostname, pathname} = new URL(response.headers.get('Location')!); + expect(hostname).toEqual(TEST_SHOP); + expect(pathname).toEqual('/admin/oauth/authorize'); + }); + + it('redirects to exit iframe when request receives a 401 response and embedded', async () => { + // GIVEN + const {admin, session} = await setUpEmbeddedFlow(); + const requestMock = await mockRequest(401); + + // WHEN + const response = await getThrownResponse( + async () => action(admin, session), + requestMock, + ); + + // THEN + expectExitIframeRedirect(response); + }); + + it('returns app bridge redirection headers when request receives a 401 response on fetch requests', async () => { + // GIVEN + const {admin, session} = await setUpFetchFlow(); + const requestMock = await mockRequest(401); + + // WHEN + const response = await getThrownResponse( + async () => action(admin, session), + requestMock, + ); + + // THEN + expect(response.status).toEqual(401); + + const {origin, pathname, searchParams} = new URL( + response.headers.get(REAUTH_URL_HEADER)!, + ); + expect(origin).toEqual(APP_URL); + expect(pathname).toEqual('/auth'); + expect(searchParams.get('shop')).toEqual(TEST_SHOP); + }); + }, + ); +}); + +async function setUpEmbeddedFlow() { + const shopify = shopifyApp( + testConfig({ + future: {unstable_newEmbeddedAuthStrategy: false}, + restResources, + }), + ); + const expectedSession = await setUpValidSession(shopify.sessionStorage); + + const {token} = getJwt(); + const request = new Request( + `${APP_URL}?embedded=1&shop=${TEST_SHOP}&host=${BASE64_HOST}&id_token=${token}`, + ); + + return { + shopify, + expectedSession, + ...(await shopify.authenticate.admin(request)), + }; +} + +async function setUpFetchFlow() { + const shopify = shopifyApp( + testConfig({ + future: {unstable_newEmbeddedAuthStrategy: false}, + restResources, + }), + ); + await setUpValidSession(shopify.sessionStorage); + + const {token} = getJwt(); + const request = new Request(APP_URL, { + headers: {Authorization: `Bearer ${token}`}, + }); + + return { + shopify, + ...(await shopify.authenticate.admin(request)), + }; +} + +async function setUpNonEmbeddedFlow() { + const shopify = shopifyApp( + testConfig({ + future: {unstable_newEmbeddedAuthStrategy: false}, + restResources, + isEmbeddedApp: false, + }), + ); + const session = await setUpValidSession(shopify.sessionStorage); + + const request = new Request(`${APP_URL}?shop=${TEST_SHOP}`); + signRequestCookie({ + request, + cookieName: SESSION_COOKIE_NAME, + cookieValue: session.id, + }); + + return { + shopify, + ...(await shopify.authenticate.admin(request)), + }; +} + +async function mockRestRequest(status) { + const requestMock = new Request( + `https://${TEST_SHOP}/admin/api/${LATEST_API_VERSION}/customers.json`, + ); + + await mockExternalRequest({ + request: requestMock, + response: new Response('{}', {status}), + }); + + return requestMock; +} + +function mockGraphqlRequest(apiVersion = LATEST_API_VERSION) { + return async function (status = 401) { + const requestMock = new Request( + `https://${TEST_SHOP}/admin/api/${apiVersion}/graphql.json`, + {method: 'POST'}, + ); + + await mockExternalRequest({ + request: requestMock, + response: new Response(undefined, {status}), + }); + + return requestMock; + }; +} From 9a6443876cb9d938f11cf2f8b986716fd3be7a99 Mon Sep 17 00:00:00 2001 From: Rachel Carvalho Date: Wed, 6 Dec 2023 16:44:20 -0500 Subject: [PATCH 20/41] delegate handling invalid access token errors to auth strategy --- .../server/authenticate/admin/authenticate.ts | 14 ++++++++------ .../admin/helpers/create-admin-api-context.ts | 3 +++ .../admin/helpers/handle-client-error.ts | 16 ++++++++-------- .../auth-code-flow/admin-client.test.ts | 1 - .../admin/strategies/auth-code-flow.ts | 18 +++++++++++++++++- .../admin/strategies/token-exchange.ts | 2 ++ .../authenticate/admin/strategies/types.ts | 14 ++++++++++++++ 7 files changed, 52 insertions(+), 16 deletions(-) diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts b/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts index 796492b893..6fdc1810b8 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts @@ -68,16 +68,18 @@ export function authStrategyFactory< function createContext( request: Request, session: Session, + authStrategy: AuthorizationStrategy, sessionToken?: JwtPayload, ): AdminContext { const context: | EmbeddedAdminContext | NonEmbeddedAdminContext = { - admin: createAdminApiContext(request, session, { - api, - logger, - config, - }), + admin: createAdminApiContext( + request, + session, + params, + authStrategy, + ), billing: { require: requireBillingFactory(params, request, session), request: requestBillingFactory(params, request, session), @@ -135,7 +137,7 @@ export function authStrategyFactory< isOnline: session.isOnline, }); - return createContext(request, session, payload); + return createContext(request, session, strategy, payload); } catch (errorOrResponse) { if (errorOrResponse instanceof Response) { ensureCORSHeadersFactory(params, request)(errorOrResponse); diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/create-admin-api-context.ts b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/create-admin-api-context.ts index 3699bc810d..1c2c574957 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/create-admin-api-context.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/create-admin-api-context.ts @@ -2,6 +2,7 @@ import {Session, ShopifyRestResources} from '@shopify/shopify-api'; import type {BasicParams} from '../../../types'; import {AdminApiContext, adminClientFactory} from '../../../clients/admin'; +import {AuthorizationStrategy} from '../strategies/types'; import {handleClientErrorFactory} from './handle-client-error'; @@ -11,12 +12,14 @@ export function createAdminApiContext< request: Request, session: Session, params: BasicParams, + authStrategy?: AuthorizationStrategy, ): AdminApiContext { return adminClientFactory({ session, params, handleClientError: handleClientErrorFactory({ request, + authStrategy, }), }); } diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/handle-client-error.ts b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/handle-client-error.ts index 1661533fb4..3b59611cde 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/handle-client-error.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/handle-client-error.ts @@ -1,15 +1,11 @@ import {HttpResponseError} from '@shopify/shopify-api'; import type {HandleAdminClientError} from '../../../clients/admin/types'; - -import {redirectToAuthPage} from './redirect-to-auth-page'; - -interface HandleClientErrorOptions { - request: Request; -} +import {HandleClientErrorOptions} from '../strategies/types'; export function handleClientErrorFactory({ request, + authStrategy, }: HandleClientErrorOptions): HandleAdminClientError { return async function handleClientError({ error, @@ -32,8 +28,12 @@ export function handleClientErrorFactory({ }, ); - if (error.response.code === 401) { - throw await redirectToAuthPage(params, request, session.shop); + if (error.response.code === 401 && authStrategy) { + await authStrategy.handleInvalidAccessTokenError({ + request, + session, + error, + }); } // forward a minimal copy of the upstream HTTP response instead of an Error: diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/admin-client.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/admin-client.test.ts index 4edccc22d3..729e37f67f 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/admin-client.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/auth-code-flow/admin-client.test.ts @@ -1,6 +1,5 @@ import { ApiVersion, - HttpMaxRetriesError, LATEST_API_VERSION, SESSION_COOKIE_NAME, Session, diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts index b13e01a08c..f4f5373119 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts @@ -21,7 +21,11 @@ import { import {AppConfig} from '../../../config-types'; import {getSessionTokenHeader} from '../../helpers'; -import {AuthorizationStrategy, SessionContext} from './types'; +import { + AuthorizationStrategy, + HandleInvalidAccessTokenOptions, + SessionContext, +} from './types'; export class AuthCodeFlowStrategy< Resources extends ShopifyRestResources = ShopifyRestResources, @@ -87,6 +91,18 @@ export class AuthCodeFlowStrategy< return session!; } + public async handleInvalidAccessTokenError({ + request, + session, + }: HandleInvalidAccessTokenOptions): Promise { + const {api, config, logger} = this; + throw await redirectToAuthPage( + {api, config, logger}, + request, + session.shop, + ); + } + private async ensureInstalledOnShop(request: Request) { const {api, config, logger} = this; diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts index e43b0ecfa7..334e0361ba 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts @@ -84,6 +84,8 @@ export class TokenExchangeStrategy return session!; } + public async handleInvalidAccessTokenError(): Promise {} + private async exchangeToken({ request, shop, diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/types.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/types.ts index ecfd270567..31663e853a 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/types.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/types.ts @@ -13,10 +13,24 @@ export interface SessionContext { sessionToken?: string; } +export interface HandleClientErrorOptions { + request: Request; + authStrategy?: AuthorizationStrategy; +} + +export interface HandleInvalidAccessTokenOptions { + request: Request; + session: Session; + error: any; +} export interface AuthorizationStrategy { respondToOAuthRequests: (request: Request) => Promise; authenticate: ( request: Request, sessionContext: SessionContext, ) => Promise; + handleInvalidAccessTokenError: ({ + request, + session, + }: HandleInvalidAccessTokenOptions) => Promise; } From 4f84ef8ac372be7f624ccb099e527176108cd18d Mon Sep 17 00:00:00 2001 From: Rezaan Syed Date: Mon, 11 Dec 2023 15:57:15 -0500 Subject: [PATCH 21/41] Add error handling for token exchange strategy on invalid API requests --- .../token-exchange/admin-client.test.ts | 189 ++++++++++++++++++ .../token-exchange/authenticate.test.ts | 3 + .../admin/strategies/token-exchange.ts | 26 ++- .../src/server/authenticate/const.ts | 4 + .../respond-to-invalid-session-token.ts | 19 +- 5 files changed, 232 insertions(+), 9 deletions(-) create mode 100644 packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/admin-client.test.ts diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/admin-client.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/admin-client.test.ts new file mode 100644 index 0000000000..7155fc9f64 --- /dev/null +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/admin-client.test.ts @@ -0,0 +1,189 @@ +import { + ApiVersion, + HttpMaxRetriesError, + LATEST_API_VERSION, + SESSION_COOKIE_NAME, + Session, +} from '@shopify/shopify-api'; +import {restResources} from '@shopify/shopify-api/rest/admin/2023-04'; + +import { + APP_URL, + BASE64_HOST, + TEST_SHOP, + expectExitIframeRedirect, + getJwt, + getThrownResponse, + setUpValidSession, + signRequestCookie, + testConfig, + mockExternalRequest, + expectAdminApiClient, +} from '../../../../../__test-helpers'; +import {shopifyApp} from '../../../../..'; +import { + REAUTH_URL_HEADER, + RETRY_INVALID_SESSION_HEADER, +} from '../../../../const'; +import {AdminApiContext} from '../../../../../clients'; + +describe('admin.authenticate context', () => { + expectAdminApiClient(async () => { + const { + admin, + expectedSession, + session: actualSession, + } = await setUpDocumentFlow(); + + return {admin, expectedSession, actualSession}; + }); + describe.each([ + { + testGroup: 'REST client', + mockRequest: mockRestRequest, + action: async (admin: AdminApiContext, _session: Session) => + admin.rest.get({path: '/customers.json'}), + }, + { + testGroup: 'REST resources', + mockRequest: mockRestRequest, + action: async (admin: AdminApiContext, session: Session) => + admin.rest.resources.Customer.all({session}), + }, + { + testGroup: 'GraphQL client', + mockRequest: mockGraphqlRequest(), + action: async (admin: AdminApiContext, _session: Session) => + admin.graphql('{ shop { name } }'), + }, + { + testGroup: 'GraphQL client with options', + mockRequest: mockGraphqlRequest('2021-01' as ApiVersion), + action: async (admin: AdminApiContext, _session: Session) => + admin.graphql( + 'mutation myMutation($ID: String!) { shop(ID: $ID) { name } }', + { + variables: {ID: '123'}, + apiVersion: '2021-01' as ApiVersion, + headers: {custom: 'header'}, + tries: 2, + }, + ), + }, + ])( + '$testGroup re-authentication', + ({testGroup: _testGroup, mockRequest, action}) => { + it('redirects to exit bounce page when document request receives a 401 response', async () => { + // GIVEN + const {admin, session, shopify} = await setUpDocumentFlow(); + const requestMock = await mockRequest(); + + // WHEN + const response = await getThrownResponse( + async () => action(admin, session), + requestMock, + ); + + // THEN + expect(response.status).toBe(302); + + const {pathname} = new URL(response.headers.get('location')!, APP_URL); + expect(pathname).toBe('/auth/session-token'); + + expect( + await shopify.sessionStorage.loadSession(session.id), + ).toBeUndefined(); + }); + + it('returns 401 when receives a 401 response on fetch requests', async () => { + // GIVEN + const {admin, session, shopify} = await setUpFetchFlow(); + const requestMock = await mockRequest(); + + // WHEN + const response = await getThrownResponse( + async () => action(admin, session), + requestMock, + ); + + // THEN + expect(response.status).toEqual(401); + expect( + response.headers.get('X-Shopify-Retry-Invalid-Session-Request'), + ).toBeNull(); + + expect( + await shopify.sessionStorage.loadSession(session.id), + ).toBeUndefined(); + }); + }, + ); +}); + +async function setUpDocumentFlow() { + const shopify = shopifyApp( + testConfig({ + restResources, + }), + ); + const expectedSession = await setUpValidSession(shopify.sessionStorage); + + const {token} = getJwt(); + const request = new Request( + `${APP_URL}?embedded=1&shop=${TEST_SHOP}&host=${BASE64_HOST}&id_token=${token}`, + ); + + return { + shopify, + expectedSession, + ...(await shopify.authenticate.admin(request)), + }; +} + +async function setUpFetchFlow() { + const shopify = shopifyApp( + testConfig({ + restResources, + }), + ); + await setUpValidSession(shopify.sessionStorage); + + const {token} = getJwt(); + const request = new Request(APP_URL, { + headers: {Authorization: `Bearer ${token}`}, + }); + + return { + shopify, + ...(await shopify.authenticate.admin(request)), + }; +} + +async function mockRestRequest(status = 401) { + const requestMock = new Request( + `https://${TEST_SHOP}/admin/api/${LATEST_API_VERSION}/customers.json`, + ); + + await mockExternalRequest({ + request: requestMock, + response: new Response(undefined, {status}), + }); + + return requestMock; +} + +function mockGraphqlRequest(apiVersion = LATEST_API_VERSION) { + return async function (status = 401) { + const requestMock = new Request( + `https://${TEST_SHOP}/admin/api/${apiVersion}/graphql.json`, + {method: 'POST'}, + ); + + await mockExternalRequest({ + request: requestMock, + response: new Response(undefined, {status}), + }); + + return requestMock; + }; +} diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts index 5cfe1f2689..563288e299 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/authenticate.test.ts @@ -168,6 +168,9 @@ describe('authenticate', () => { // THEN expect(response.status).toBe(401); + expect( + response.headers.get('X-Shopify-Retry-Invalid-Session-Request'), + ).toEqual('1'); }); test('throws 500 for any other error from token exchange API', async () => { diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts index 334e0361ba..ed2a530e5d 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts @@ -11,7 +11,11 @@ import {BasicParams, ApiConfigWithFutureFlags} from 'src/server/types'; import {respondToInvalidSessionToken} from '../../helpers'; import {triggerAfterAuthHook} from '../helpers'; -import {AuthorizationStrategy, SessionContext} from './types'; +import { + AuthorizationStrategy, + HandleInvalidAccessTokenOptions, + SessionContext, +} from './types'; export class TokenExchangeStrategy implements AuthorizationStrategy @@ -84,7 +88,19 @@ export class TokenExchangeStrategy return session!; } - public async handleInvalidAccessTokenError(): Promise {} + public async handleInvalidAccessTokenError({ + request, + session, + }: HandleInvalidAccessTokenOptions): Promise { + const {config, api, logger} = this; + + config.sessionStorage.deleteSession(session.id); + + respondToInvalidSessionToken({ + params: {config, api, logger}, + request, + }); + } private async exchangeToken({ request, @@ -112,7 +128,11 @@ export class TokenExchangeStrategy error.response.code === 400 && error.response.body?.error === 'invalid_subject_token') ) { - throw respondToInvalidSessionToken({api, config, logger}, request); + throw respondToInvalidSessionToken({ + params: {api, config, logger}, + request, + retryRequest: true, + }); } throw new Response(undefined, { diff --git a/packages/shopify-app-remix/src/server/authenticate/const.ts b/packages/shopify-app-remix/src/server/authenticate/const.ts index 4aa2a4a218..481f931385 100644 --- a/packages/shopify-app-remix/src/server/authenticate/const.ts +++ b/packages/shopify-app-remix/src/server/authenticate/const.ts @@ -4,6 +4,10 @@ export const APP_BRIDGE_URL = export const REAUTH_URL_HEADER = 'X-Shopify-API-Request-Failure-Reauthorize-Url'; +export const RETRY_INVALID_SESSION_HEADER = { + 'X-Shopify-Retry-Invalid-Session-Request': '1', +}; + export const CORS_HEADERS = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'Authorization', diff --git a/packages/shopify-app-remix/src/server/authenticate/helpers/respond-to-invalid-session-token.ts b/packages/shopify-app-remix/src/server/authenticate/helpers/respond-to-invalid-session-token.ts index 5c4cf51eea..8b215d781a 100644 --- a/packages/shopify-app-remix/src/server/authenticate/helpers/respond-to-invalid-session-token.ts +++ b/packages/shopify-app-remix/src/server/authenticate/helpers/respond-to-invalid-session-token.ts @@ -1,11 +1,19 @@ import {BasicParams} from 'src/server/types'; import {redirectToBouncePage} from '../admin/helpers/redirect-to-bounce-page'; +import {RETRY_INVALID_SESSION_HEADER} from '../const'; -export function respondToInvalidSessionToken( - params: BasicParams, - request: Request, -) { +interface RespondToInvalidSessionTokenParams { + params: BasicParams; + request: Request; + retryRequest?: boolean; +} + +export function respondToInvalidSessionToken({ + params, + request, + retryRequest = false, +}: RespondToInvalidSessionTokenParams) { const {api, logger, config} = params; const isDocumentRequest = !request.headers.get('authorization'); @@ -13,10 +21,9 @@ export function respondToInvalidSessionToken( return redirectToBouncePage({api, logger, config}, new URL(request.url)); } - // eslint-disable-next-line no-warning-comments - // TODO add header so app bridge retries with new session token throw new Response(undefined, { status: 401, statusText: 'Unauthorized', + headers: retryRequest ? RETRY_INVALID_SESSION_HEADER : {}, }); } From c83475698576b3617955a0b3cb01f64583c560e8 Mon Sep 17 00:00:00 2001 From: Rezaan Syed Date: Tue, 12 Dec 2023 14:32:53 -0500 Subject: [PATCH 22/41] Move SessionContext to authenticate as it is only used there --- .../src/server/authenticate/admin/authenticate.ts | 9 ++++++++- .../src/server/authenticate/admin/strategies/types.ts | 9 +-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts b/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts index 6fdc1810b8..75ede4487f 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts @@ -30,7 +30,14 @@ import { renderAppBridge, validateShopAndHostParams, } from './helpers'; -import {AuthorizationStrategy, SessionTokenContext} from './strategies/types'; +import {AuthorizationStrategy} from './strategies/types'; + +export interface SessionTokenContext { + shop: string; + sessionId?: string; + sessionToken?: string; + payload?: JwtPayload; +} interface AuthStrategyParams extends BasicParams { strategy: AuthorizationStrategy; diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/types.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/types.ts index 31663e853a..b28cb8f46c 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/types.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/types.ts @@ -1,11 +1,4 @@ -import {JwtPayload, Session} from '@shopify/shopify-api'; - -export interface SessionTokenContext { - shop: string; - sessionId?: string; - sessionToken?: string; - payload?: JwtPayload; -} +import {Session} from '@shopify/shopify-api'; export interface SessionContext { shop: string; From 06def6b4d19d68dbafec271567a39075d41f087a Mon Sep 17 00:00:00 2001 From: Rezaan Syed Date: Tue, 12 Dec 2023 14:41:05 -0500 Subject: [PATCH 23/41] Inject error handling strategy on after auth API calls --- .../admin/helpers/trigger-after-auth-hook.ts | 15 +++++++++++++-- .../admin/strategies/auth-code-flow.ts | 1 + .../admin/strategies/token-exchange.ts | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/trigger-after-auth-hook.ts b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/trigger-after-auth-hook.ts index 18eb238738..c0a672ab4d 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/trigger-after-auth-hook.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/trigger-after-auth-hook.ts @@ -1,18 +1,29 @@ import {Session, ShopifyRestResources} from '@shopify/shopify-api'; import type {BasicParams} from '../../../types'; +import {AuthorizationStrategy} from '../strategies/types'; import {createAdminApiContext} from './create-admin-api-context'; export async function triggerAfterAuthHook< Resources extends ShopifyRestResources = ShopifyRestResources, ->(params: BasicParams, session: Session, request: Request) { +>( + params: BasicParams, + session: Session, + request: Request, + authStrategy: AuthorizationStrategy, +) { const {config, logger} = params; if (config.hooks.afterAuth) { logger.info('Running afterAuth hook'); await config.hooks.afterAuth({ session, - admin: createAdminApiContext(request, session, params), + admin: createAdminApiContext( + request, + session, + params, + authStrategy, + ), }); } } diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts index f4f5373119..dbdeb94a82 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts @@ -196,6 +196,7 @@ export class AuthCodeFlowStrategy< {api, config, logger}, session, request, + this, ); throw await redirectToShopifyOrAppRoot( diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts index ed2a530e5d..0856c34f58 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts @@ -151,7 +151,7 @@ export class TokenExchangeStrategy const {config} = params; await config.idempotentPromiseHandler.handlePromise({ promiseFunction: () => { - return triggerAfterAuthHook(params, session, request); + return triggerAfterAuthHook(params, session, request, this); }, identifier: sessionToken, }); From 0ef8049e5b9ce2965563c452c7475e69677a2413 Mon Sep 17 00:00:00 2001 From: Rezaan Syed Date: Tue, 12 Dec 2023 15:37:56 -0500 Subject: [PATCH 24/41] Pass in handleClientError method from the strategy --- .../server/authenticate/admin/authenticate.ts | 3 +- .../admin/helpers/create-admin-api-context.ts | 17 ++++------ .../admin/helpers/handle-client-error.ts | 10 ++---- .../admin/helpers/trigger-after-auth-hook.ts | 3 +- .../admin/strategies/auth-code-flow.ts | 28 ++++++++-------- .../admin/strategies/token-exchange.ts | 32 +++++++++---------- .../authenticate/admin/strategies/types.ts | 20 ++++++------ 7 files changed, 53 insertions(+), 60 deletions(-) diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts b/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts index 75ede4487f..8782e8ca1e 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/authenticate.ts @@ -82,10 +82,9 @@ export function authStrategyFactory< | EmbeddedAdminContext | NonEmbeddedAdminContext = { admin: createAdminApiContext( - request, session, params, - authStrategy, + authStrategy.handleClientError(request), ), billing: { require: requireBillingFactory(params, request, session), diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/create-admin-api-context.ts b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/create-admin-api-context.ts index 1c2c574957..00caf4bb87 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/create-admin-api-context.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/create-admin-api-context.ts @@ -1,25 +1,22 @@ import {Session, ShopifyRestResources} from '@shopify/shopify-api'; import type {BasicParams} from '../../../types'; -import {AdminApiContext, adminClientFactory} from '../../../clients/admin'; -import {AuthorizationStrategy} from '../strategies/types'; - -import {handleClientErrorFactory} from './handle-client-error'; +import { + AdminApiContext, + HandleAdminClientError, + adminClientFactory, +} from '../../../clients/admin'; export function createAdminApiContext< Resources extends ShopifyRestResources = ShopifyRestResources, >( - request: Request, session: Session, params: BasicParams, - authStrategy?: AuthorizationStrategy, + handleClientError: HandleAdminClientError, ): AdminApiContext { return adminClientFactory({ session, params, - handleClientError: handleClientErrorFactory({ - request, - authStrategy, - }), + handleClientError, }); } diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/handle-client-error.ts b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/handle-client-error.ts index 3b59611cde..fda47f8092 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/handle-client-error.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/handle-client-error.ts @@ -5,7 +5,7 @@ import {HandleClientErrorOptions} from '../strategies/types'; export function handleClientErrorFactory({ request, - authStrategy, + onError, }: HandleClientErrorOptions): HandleAdminClientError { return async function handleClientError({ error, @@ -28,12 +28,8 @@ export function handleClientErrorFactory({ }, ); - if (error.response.code === 401 && authStrategy) { - await authStrategy.handleInvalidAccessTokenError({ - request, - session, - error, - }); + if (onError) { + await onError({request, session, error}); } // forward a minimal copy of the upstream HTTP response instead of an Error: diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/trigger-after-auth-hook.ts b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/trigger-after-auth-hook.ts index c0a672ab4d..b27dd3f1a2 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/trigger-after-auth-hook.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/trigger-after-auth-hook.ts @@ -19,10 +19,9 @@ export async function triggerAfterAuthHook< await config.hooks.afterAuth({ session, admin: createAdminApiContext( - request, session, params, - authStrategy, + authStrategy.handleClientError(request), ), }); } diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts index dbdeb94a82..69a6dba4cc 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts @@ -12,6 +12,7 @@ import { import type {BasicParams} from '../../../types'; import { beginAuth, + handleClientErrorFactory, redirectToAuthPage, redirectToShopifyOrAppRoot, redirectWithExitIframe, @@ -20,12 +21,9 @@ import { } from '../helpers'; import {AppConfig} from '../../../config-types'; import {getSessionTokenHeader} from '../../helpers'; +import {HandleAdminClientError} from '../../../clients'; -import { - AuthorizationStrategy, - HandleInvalidAccessTokenOptions, - SessionContext, -} from './types'; +import {AuthorizationStrategy, SessionContext, OnErrorOptions} from './types'; export class AuthCodeFlowStrategy< Resources extends ShopifyRestResources = ShopifyRestResources, @@ -91,16 +89,20 @@ export class AuthCodeFlowStrategy< return session!; } - public async handleInvalidAccessTokenError({ - request, - session, - }: HandleInvalidAccessTokenOptions): Promise { + public handleClientError(request: Request): HandleAdminClientError { const {api, config, logger} = this; - throw await redirectToAuthPage( - {api, config, logger}, + return handleClientErrorFactory({ request, - session.shop, - ); + onError: async ({session, error}: OnErrorOptions) => { + if (error.response.code === 401) { + throw await redirectToAuthPage( + {api, config, logger}, + request, + session.shop, + ); + } + }, + }); } private async ensureInstalledOnShop(request: Request) { diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts index 0856c34f58..0d3909d1cc 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts @@ -9,13 +9,10 @@ import {AppConfig, AppConfigArg} from 'src/server/config-types'; import {BasicParams, ApiConfigWithFutureFlags} from 'src/server/types'; import {respondToInvalidSessionToken} from '../../helpers'; -import {triggerAfterAuthHook} from '../helpers'; +import {handleClientErrorFactory, triggerAfterAuthHook} from '../helpers'; +import {HandleAdminClientError} from '../../../clients'; -import { - AuthorizationStrategy, - HandleInvalidAccessTokenOptions, - SessionContext, -} from './types'; +import {AuthorizationStrategy, SessionContext, OnErrorOptions} from './types'; export class TokenExchangeStrategy implements AuthorizationStrategy @@ -88,17 +85,20 @@ export class TokenExchangeStrategy return session!; } - public async handleInvalidAccessTokenError({ - request, - session, - }: HandleInvalidAccessTokenOptions): Promise { - const {config, api, logger} = this; - - config.sessionStorage.deleteSession(session.id); - - respondToInvalidSessionToken({ - params: {config, api, logger}, + public handleClientError(request: Request): HandleAdminClientError { + const {api, config, logger} = this; + return handleClientErrorFactory({ request, + onError: async ({session, error}: OnErrorOptions) => { + if (error.response.code === 401) { + config.sessionStorage.deleteSession(session.id); + + respondToInvalidSessionToken({ + params: {config, api, logger}, + request, + }); + } + }, }); } diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/types.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/types.ts index b28cb8f46c..f852a9d185 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/types.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/types.ts @@ -1,4 +1,6 @@ -import {Session} from '@shopify/shopify-api'; +import {HttpResponseError, Session} from '@shopify/shopify-api'; + +import {HandleAdminClientError} from '../../../clients'; export interface SessionContext { shop: string; @@ -6,24 +8,22 @@ export interface SessionContext { sessionToken?: string; } -export interface HandleClientErrorOptions { +export interface OnErrorOptions { request: Request; - authStrategy?: AuthorizationStrategy; + session: Session; + error: HttpResponseError; } -export interface HandleInvalidAccessTokenOptions { +export interface HandleClientErrorOptions { request: Request; - session: Session; - error: any; + onError?: ({session, error}: OnErrorOptions) => Promise; } + export interface AuthorizationStrategy { respondToOAuthRequests: (request: Request) => Promise; authenticate: ( request: Request, sessionContext: SessionContext, ) => Promise; - handleInvalidAccessTokenError: ({ - request, - session, - }: HandleInvalidAccessTokenOptions) => Promise; + handleClientError: (request: Request) => HandleAdminClientError; } From 6b9da3e22ed82cc84527021dcbfdbbec6350d3e1 Mon Sep 17 00:00:00 2001 From: Paulo Margarido <64600052+paulomarg@users.noreply.github.com> Date: Tue, 16 Jan 2024 09:51:04 -0500 Subject: [PATCH 25/41] Fix future flag conversion to api object --- .../admin/strategies/token-exchange.ts | 16 ++++++++++++--- .../shopify-app-remix/src/server/types.ts | 20 +++++++++++-------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts index 0d3909d1cc..f1c0308ae0 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/token-exchange.ts @@ -4,10 +4,15 @@ import { RequestedTokenType, Session, Shopify, + ShopifyRestResources, } from '@shopify/shopify-api'; -import {AppConfig, AppConfigArg} from 'src/server/config-types'; -import {BasicParams, ApiConfigWithFutureFlags} from 'src/server/types'; +import {AppConfig, AppConfigArg} from '../../../config-types'; +import { + BasicParams, + ApiConfigWithFutureFlags, + ApiFutureFlags, +} from '../../../types'; import {respondToInvalidSessionToken} from '../../helpers'; import {handleClientErrorFactory, triggerAfterAuthHook} from '../helpers'; import {HandleAdminClientError} from '../../../clients'; @@ -17,7 +22,12 @@ import {AuthorizationStrategy, SessionContext, OnErrorOptions} from './types'; export class TokenExchangeStrategy implements AuthorizationStrategy { - protected api: Shopify>; + protected api: Shopify< + ApiConfigWithFutureFlags, + ShopifyRestResources, + ApiFutureFlags + >; + protected config: AppConfig; protected logger: Shopify['logger']; diff --git a/packages/shopify-app-remix/src/server/types.ts b/packages/shopify-app-remix/src/server/types.ts index 323f7b0af4..e3cfaa55ac 100644 --- a/packages/shopify-app-remix/src/server/types.ts +++ b/packages/shopify-app-remix/src/server/types.ts @@ -19,19 +19,23 @@ import {FutureFlagOptions, FutureFlags} from './future/flags'; export interface BasicParams< Future extends FutureFlagOptions = FutureFlagOptions, > { - api: Shopify>; + api: Shopify< + ApiConfigWithFutureFlags, + ShopifyRestResources, + ApiFutureFlags + >; config: AppConfig; logger: Shopify['logger']; } +export interface ApiFutureFlags { + unstable_tokenExchange?: Future extends FutureFlags + ? Future['unstable_newEmbeddedAuthStrategy'] + : boolean; +} + export type ApiConfigWithFutureFlags = - ConfigParams & { - future?: { - unstable_tokenExchange?: Future extends FutureFlags - ? Future['unstable_newEmbeddedAuthStrategy'] - : boolean; - }; - }; + ConfigParams>; export type JSONValue = | string From c0c44e1127592922052cbe8486176de8900fb119 Mon Sep 17 00:00:00 2001 From: Rachel Carvalho Date: Tue, 16 Jan 2024 13:54:27 -0300 Subject: [PATCH 26/41] return empty object on rest 401 test --- .../strategies/__tests__/token-exchange/admin-client.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/admin-client.test.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/admin-client.test.ts index 7155fc9f64..d3c6f5ff49 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/admin-client.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/__tests__/token-exchange/admin-client.test.ts @@ -166,7 +166,7 @@ async function mockRestRequest(status = 401) { await mockExternalRequest({ request: requestMock, - response: new Response(undefined, {status}), + response: new Response('{}', {status}), }); return requestMock; From 4683a0ecbe32b90e170c9c86131ff603d2b185f1 Mon Sep 17 00:00:00 2001 From: Rezaan Syed Date: Wed, 17 Jan 2024 11:40:20 -0500 Subject: [PATCH 27/41] Revert "Log throttling errors on webhook registration" This reverts commit 7888756951fa69af2a12a8e9a6063da9486181c1. --- .../webhooks/__tests__/mock-responses.ts | 11 --- .../webhooks/__tests__/register.test.ts | 99 +------------------ .../server/authenticate/webhooks/register.ts | 55 ++++------- .../shopify-app-remix/src/server/types.ts | 2 +- 4 files changed, 21 insertions(+), 146 deletions(-) diff --git a/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/mock-responses.ts b/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/mock-responses.ts index fd0533599b..5377ca670a 100644 --- a/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/mock-responses.ts +++ b/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/mock-responses.ts @@ -32,14 +32,3 @@ export const HTTP_WEBHOOK_CREATE_ERROR_RESPONSE = { }, }, }; - -export const HTTP_WEBHOOK_THROTTLING_ERROR_RESPONSE = { - errors: [ - { - message: 'Throttled', - extensions: { - code: 'THROTTLED', - }, - }, - ], -}; diff --git a/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/register.test.ts b/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/register.test.ts index c8cec6fdc4..b3dfd241ad 100644 --- a/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/register.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/register.test.ts @@ -1,4 +1,4 @@ -import {DeliveryMethod, GraphqlQueryError, Session} from '@shopify/shopify-api'; +import {DeliveryMethod, Session} from '@shopify/shopify-api'; import {shopifyApp} from '../../..'; import { @@ -110,101 +110,4 @@ describe('Webhook registration', () => { NOT_A_VALID_TOPIC: [expect.objectContaining({success: false})], }); }); - - // eslint-disable-next-line no-warning-comments - // TODO: Remove tests once we have a better solution to parallel afterAuth calls - it('logs throttling errors', async () => { - // GIVEN - const shopify = shopifyApp( - testConfig({ - webhooks: { - NOT_A_VALID_TOPIC: { - deliveryMethod: DeliveryMethod.Http, - callbackUrl: '/webhooks', - }, - }, - }), - ); - const session = new Session({ - id: `offline_${TEST_SHOP}`, - shop: TEST_SHOP, - isOnline: false, - state: 'test', - accessToken: 'totally_real_token', - }); - - await mockExternalRequests( - { - request: new Request(GRAPHQL_URL, { - method: 'POST', - body: 'webhookSubscriptions', - }), - response: new Response( - JSON.stringify(mockResponses.EMPTY_WEBHOOK_RESPONSE), - ), - }, - { - request: new Request(GRAPHQL_URL, { - method: 'POST', - body: 'webhookSubscriptionCreate', - }), - response: new Response( - JSON.stringify(mockResponses.HTTP_WEBHOOK_THROTTLING_ERROR_RESPONSE), - ), - }, - ); - - // WHEN - const results = await shopify.registerWebhooks({session}); - - // THEN - expect(results).toBeUndefined(); - }); - - it('throws other errors', async () => { - // GIVEN - const shopify = shopifyApp( - testConfig({ - webhooks: { - NOT_A_VALID_TOPIC: { - deliveryMethod: DeliveryMethod.Http, - callbackUrl: '/webhooks', - }, - }, - }), - ); - const session = new Session({ - id: `offline_${TEST_SHOP}`, - shop: TEST_SHOP, - isOnline: false, - state: 'test', - accessToken: 'totally_real_token', - }); - - await mockExternalRequests( - { - request: new Request(GRAPHQL_URL, { - method: 'POST', - body: 'webhookSubscriptions', - }), - response: new Response( - JSON.stringify(mockResponses.EMPTY_WEBHOOK_RESPONSE), - ), - }, - { - request: new Request(GRAPHQL_URL, { - method: 'POST', - body: 'webhookSubscriptionCreate', - }), - response: new Response( - JSON.stringify({errors: [{extensions: {code: 'FAILED_REQUEST'}}]}), - ), - }, - ); - - // THEN - expect(shopify.registerWebhooks({session})).rejects.toThrowError( - GraphqlQueryError, - ); - }); }); diff --git a/packages/shopify-app-remix/src/server/authenticate/webhooks/register.ts b/packages/shopify-app-remix/src/server/authenticate/webhooks/register.ts index e661742c38..0acc99c6a7 100644 --- a/packages/shopify-app-remix/src/server/authenticate/webhooks/register.ts +++ b/packages/shopify-app-remix/src/server/authenticate/webhooks/register.ts @@ -4,43 +4,26 @@ import type {RegisterWebhooksOptions} from './types'; export function registerWebhooksFactory({api, logger}: BasicParams) { return async function registerWebhooks({session}: RegisterWebhooksOptions) { - return api.webhooks - .register({session}) - .then((response) => { - Object.entries(response).forEach(([topic, topicResults]) => { - topicResults.forEach(({success, ...rest}) => { - if (success) { - logger.debug('Registered webhook', { - topic, - shop: session.shop, - operation: rest.operation, - }); - } else { - logger.error('Failed to register webhook', { - topic, - shop: session.shop, - result: JSON.stringify(rest.result), - }); - } - }); + return api.webhooks.register({session}).then((response) => { + Object.entries(response).forEach(([topic, topicResults]) => { + topicResults.forEach(({success, ...rest}) => { + if (success) { + logger.debug('Registered webhook', { + topic, + shop: session.shop, + operation: rest.operation, + }); + } else { + logger.error('Failed to register webhook', { + topic, + shop: session.shop, + result: JSON.stringify(rest.result), + }); + } }); - - return response; - }) - .catch((error) => { - if ( - error.response?.errors?.find( - (responseError: {extensions: {code: string}}) => - responseError?.extensions?.code === 'THROTTLED', - ) - ) { - logger.error('Failed to register webhooks', { - shop: session.shop, - error: JSON.stringify(error), - }); - } else { - throw error; - } }); + + return response; + }); }; } diff --git a/packages/shopify-app-remix/src/server/types.ts b/packages/shopify-app-remix/src/server/types.ts index e3cfaa55ac..9d942981f5 100644 --- a/packages/shopify-app-remix/src/server/types.ts +++ b/packages/shopify-app-remix/src/server/types.ts @@ -66,7 +66,7 @@ interface JSONArray extends Array {} type RegisterWebhooks = ( options: RegisterWebhooksOptions, -) => Promise; +) => Promise; export enum LoginErrorType { MissingShop = 'MISSING_SHOP', From 35b74dd7033be92a363ffee7a4e7b74590d5f745 Mon Sep 17 00:00:00 2001 From: Elizabeth Kenyon Date: Wed, 17 Jan 2024 16:21:02 -0600 Subject: [PATCH 28/41] Fix a bug that was causing external redirects to fail in actions The session id will be present on action request, and these are data requests --- .changeset/brave-points-cross.md | 5 +++++ .../src/server/authenticate/admin/helpers/redirect.ts | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 .changeset/brave-points-cross.md diff --git a/.changeset/brave-points-cross.md b/.changeset/brave-points-cross.md new file mode 100644 index 0000000000..9050b38c6f --- /dev/null +++ b/.changeset/brave-points-cross.md @@ -0,0 +1,5 @@ +--- +'@shopify/shopify-app-remix': patch +--- + +Fixes a bug that was causing external redirects to fail in remix actions diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/redirect.ts b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/redirect.ts index 2de7cb8aa9..49d678215b 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/helpers/redirect.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/helpers/redirect.ts @@ -66,15 +66,11 @@ function isBounceRequest(request: Request) { } function isDataRequest(request: Request) { - const {searchParams} = new URL(request.url); - const isGet = request.method === 'GET'; const sessionTokenHeader = Boolean(getSessionTokenHeader(request)); - const sessionTokenSearchParam = searchParams.has('id_token'); return ( sessionTokenHeader && - !sessionTokenSearchParam && !isBounceRequest(request) && (!isEmbeddedRequest(request) || !isGet) ); From 42013e8f2f390594c8854955aac3f2cbe0870ebf Mon Sep 17 00:00:00 2001 From: Zoey Lan Date: Wed, 17 Jan 2024 14:58:29 -0700 Subject: [PATCH 29/41] Use new shopify-api util to get admin url --- .changeset/dirty-starfishes-accept.md | 5 +++++ .../shopify-app-remix/src/server/authenticate/login/login.ts | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/dirty-starfishes-accept.md diff --git a/.changeset/dirty-starfishes-accept.md b/.changeset/dirty-starfishes-accept.md new file mode 100644 index 0000000000..c7b5848bd5 --- /dev/null +++ b/.changeset/dirty-starfishes-accept.md @@ -0,0 +1,5 @@ +--- +'@shopify/shopify-app-remix': patch +--- + +Minor refactor in login.ts to use new URL util method from shopify-api-js diff --git a/packages/shopify-app-remix/src/server/authenticate/login/login.ts b/packages/shopify-app-remix/src/server/authenticate/login/login.ts index 0b1e1355e6..50fb88e059 100644 --- a/packages/shopify-app-remix/src/server/authenticate/login/login.ts +++ b/packages/shopify-app-remix/src/server/authenticate/login/login.ts @@ -37,8 +37,8 @@ export function loginFactory(params: BasicParams) { const authPath = `${config.appUrl}${config.auth.path}?shop=${sanitizedShop}`; - const storeName = sanitizedShop.split('.')[0]; - const installPath = `https://admin.shopify.com/store/${storeName}/oauth/install?client_id=${config.apiKey}`; + const adminPath = api.utils.legacyUrlToShopAdminUrl(sanitizedShop); + const installPath = `https://${adminPath}/oauth/install?client_id=${config.apiKey}`; const shouldInstall = config.isEmbeddedApp && config.future.unstable_newEmbeddedAuthStrategy; From c5f62602eb4d4585647c2293ada7be29e9197b32 Mon Sep 17 00:00:00 2001 From: Rachel Carvalho Date: Thu, 18 Jan 2024 11:52:25 -0300 Subject: [PATCH 30/41] bump shopify-api package --- packages/shopify-app-express/package.json | 2 +- packages/shopify-app-remix/package.json | 2 +- .../package.json | 4 +-- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 4 +-- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- yarn.lock | 31 +++++++++++++++---- 13 files changed, 39 insertions(+), 20 deletions(-) diff --git a/packages/shopify-app-express/package.json b/packages/shopify-app-express/package.json index 08de2a6551..1119c8c68c 100644 --- a/packages/shopify-app-express/package.json +++ b/packages/shopify-app-express/package.json @@ -30,7 +30,7 @@ "Storefront API" ], "dependencies": { - "@shopify/shopify-api": "^9.0.1", + "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3", "@shopify/shopify-app-session-storage-memory": "^2.0.3", "cookie-parser": "^1.4.6", diff --git a/packages/shopify-app-remix/package.json b/packages/shopify-app-remix/package.json index dc9c2488cc..f856883609 100644 --- a/packages/shopify-app-remix/package.json +++ b/packages/shopify-app-remix/package.json @@ -69,7 +69,7 @@ "dependencies": { "@remix-run/server-runtime": "^2.0.0", "@shopify/admin-api-client": "^0.2.1", - "@shopify/shopify-api": "^9.0.1", + "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3", "@shopify/storefront-api-client": "^0.2.1", "isbot": "^3.7.1", diff --git a/packages/shopify-app-session-storage-dynamodb/package.json b/packages/shopify-app-session-storage-dynamodb/package.json index 87fb300289..cf26504940 100644 --- a/packages/shopify-app-session-storage-dynamodb/package.json +++ b/packages/shopify-app-session-storage-dynamodb/package.json @@ -37,13 +37,13 @@ "tslib": "^2.6.2" }, "peerDependencies": { - "@shopify/shopify-api": "^9.0.1", + "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3" }, "devDependencies": { "@shopify/eslint-plugin": "^42.1.0", "@shopify/prettier-config": "^1.1.2", - "@shopify/shopify-api": "^9.0.1", + "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3", "@shopify/shopify-app-session-storage-test-utils": "^1.0.3", "eslint": "^8.55.0", diff --git a/packages/shopify-app-session-storage-kv/package.json b/packages/shopify-app-session-storage-kv/package.json index 129f86fe7a..3659a7f772 100644 --- a/packages/shopify-app-session-storage-kv/package.json +++ b/packages/shopify-app-session-storage-kv/package.json @@ -36,7 +36,7 @@ "tslib": "^2.6.2" }, "peerDependencies": { - "@shopify/shopify-api": "^9.0.1", + "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3" }, "devDependencies": { diff --git a/packages/shopify-app-session-storage-memory/package.json b/packages/shopify-app-session-storage-memory/package.json index d568d00b03..b56fb77691 100644 --- a/packages/shopify-app-session-storage-memory/package.json +++ b/packages/shopify-app-session-storage-memory/package.json @@ -33,7 +33,7 @@ "tslib": "^2.6.2" }, "peerDependencies": { - "@shopify/shopify-api": "^9.0.1", + "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3" }, "devDependencies": { diff --git a/packages/shopify-app-session-storage-mongodb/package.json b/packages/shopify-app-session-storage-mongodb/package.json index 8f9b7ff615..ffa0d03522 100644 --- a/packages/shopify-app-session-storage-mongodb/package.json +++ b/packages/shopify-app-session-storage-mongodb/package.json @@ -35,7 +35,7 @@ "tslib": "^2.6.2" }, "peerDependencies": { - "@shopify/shopify-api": "^9.0.1", + "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3" }, "devDependencies": { diff --git a/packages/shopify-app-session-storage-mysql/package.json b/packages/shopify-app-session-storage-mysql/package.json index 6510f8a373..a9c55df269 100644 --- a/packages/shopify-app-session-storage-mysql/package.json +++ b/packages/shopify-app-session-storage-mysql/package.json @@ -36,7 +36,7 @@ "tslib": "^2.6.2" }, "peerDependencies": { - "@shopify/shopify-api": "^9.0.1", + "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3" }, "devDependencies": { diff --git a/packages/shopify-app-session-storage-postgresql/package.json b/packages/shopify-app-session-storage-postgresql/package.json index 6aa8836e64..75246c90d4 100644 --- a/packages/shopify-app-session-storage-postgresql/package.json +++ b/packages/shopify-app-session-storage-postgresql/package.json @@ -37,7 +37,7 @@ "tslib": "^2.6.2" }, "peerDependencies": { - "@shopify/shopify-api": "^9.0.1", + "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3" }, "devDependencies": { diff --git a/packages/shopify-app-session-storage-prisma/package.json b/packages/shopify-app-session-storage-prisma/package.json index 9e23a0365b..ba1c1fcf3a 100644 --- a/packages/shopify-app-session-storage-prisma/package.json +++ b/packages/shopify-app-session-storage-prisma/package.json @@ -35,13 +35,13 @@ }, "peerDependencies": { "@prisma/client": "^4.13.0", - "@shopify/shopify-api": "^9.0.1", + "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3", "prisma": "^4.13.0" }, "devDependencies": { "@prisma/client": "^4.13.0", - "@shopify/shopify-api": "^9.0.1", + "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3", "prisma": "^4.13.0", "@shopify/eslint-plugin": "^42.1.0", diff --git a/packages/shopify-app-session-storage-redis/package.json b/packages/shopify-app-session-storage-redis/package.json index 8c322e5e60..85b2614bab 100644 --- a/packages/shopify-app-session-storage-redis/package.json +++ b/packages/shopify-app-session-storage-redis/package.json @@ -36,7 +36,7 @@ "tslib": "^2.6.2" }, "peerDependencies": { - "@shopify/shopify-api": "^9.0.1", + "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3" }, "devDependencies": { diff --git a/packages/shopify-app-session-storage-sqlite/package.json b/packages/shopify-app-session-storage-sqlite/package.json index 9b29613350..5e60c55298 100644 --- a/packages/shopify-app-session-storage-sqlite/package.json +++ b/packages/shopify-app-session-storage-sqlite/package.json @@ -36,7 +36,7 @@ "tslib": "^2.6.2" }, "peerDependencies": { - "@shopify/shopify-api": "^9.0.1", + "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3" }, "devDependencies": { diff --git a/packages/shopify-app-session-storage-test-utils/package.json b/packages/shopify-app-session-storage-test-utils/package.json index e90cd2d1f0..8d6b4f147d 100644 --- a/packages/shopify-app-session-storage-test-utils/package.json +++ b/packages/shopify-app-session-storage-test-utils/package.json @@ -38,7 +38,7 @@ "tslib": "^2.6.2" }, "peerDependencies": { - "@shopify/shopify-api": "^9.0.1", + "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index b4483d0af0..84eee1fc0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2787,6 +2787,13 @@ dependencies: "@shopify/graphql-client" "^0.9.1" +"@shopify/admin-api-client@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@shopify/admin-api-client/-/admin-api-client-0.2.2.tgz#c288b4af8834bc7ab6c7c1ad26e7feddfa648ec6" + integrity sha512-7x79Ras5gbf7U3oVBjFIPF70WUUit3kLF93IMhtTaC9OJ/b9NIeW1w4/+RSJq3FM/7MGQGUkyrrnuNI3nl3Eig== + dependencies: + "@shopify/graphql-client" "^0.9.2" + "@shopify/babel-preset@^24.1.4": version "24.1.5" resolved "https://registry.yarnpkg.com/@shopify/babel-preset/-/babel-preset-24.1.5.tgz#76cfef62bb8a4d9769559f3f2505148a55d13488" @@ -2853,6 +2860,11 @@ resolved "https://registry.yarnpkg.com/@shopify/graphql-client/-/graphql-client-0.9.1.tgz#0c37a4143c0dd26837e82b8fd13e1b64fabf90dc" integrity sha512-yx6PIHY8u4O0ijUQccmIpjoyM1JSbe5RkrSyXSbjhsS+nla0WAJDIMw4R01mjx3dFnVIcGONhRX/QrUq97uWig== +"@shopify/graphql-client@^0.9.2": + version "0.9.2" + resolved "https://registry.yarnpkg.com/@shopify/graphql-client/-/graphql-client-0.9.2.tgz#2c37ba20bbc1713c78b1b241af99405cd92ab428" + integrity sha512-Xk7bPA3UsLXFHV6F39t5g8dTVxqPagwJoR+MLYUvCuhlWpDeZW3f/OcgShXPPben5NcnOVV38kj5GVlkvrWqiQ== + "@shopify/loom-cli@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@shopify/loom-cli/-/loom-cli-1.1.0.tgz#e92f423a26f00c33b7c247a65667a8ff9bd1e8f8" @@ -2990,14 +3002,14 @@ jest-matcher-utils "^26.6.2" react-reconciler "^0.28.0" -"@shopify/shopify-api@^9.0.1": - version "9.0.1" - resolved "https://registry.yarnpkg.com/@shopify/shopify-api/-/shopify-api-9.0.1.tgz#7f236d94d902b3671c6a11f11f3561f98ddc7422" - integrity sha512-ToqT5zt/YA+VbFcLAoEVr6grs6v+Y9io5kuULy9Zhm5RClFEhkpRLM4U+vrrV5XjWYC6pcxs00WjpSi0onryrw== +"@shopify/shopify-api@^9.0.2": + version "9.0.2" + resolved "https://registry.yarnpkg.com/@shopify/shopify-api/-/shopify-api-9.0.2.tgz#01a321fc6bcf3450572751e6c1f8c1e4ba8ab78a" + integrity sha512-ZOnzDein63JMatQAYshg2RyBXcseBUTP8stbrwG/FwDhWqNafxMC2O8AcoIGoaNc1SoRYbWpxCbwkeO/CrW7Vg== dependencies: - "@shopify/admin-api-client" "^0.2.1" + "@shopify/admin-api-client" "^0.2.2" "@shopify/network" "^3.2.1" - "@shopify/storefront-api-client" "^0.2.1" + "@shopify/storefront-api-client" "^0.2.2" compare-versions "^5.0.3" isbot "^3.6.10" jose "^4.9.1" @@ -3012,6 +3024,13 @@ dependencies: "@shopify/graphql-client" "^0.9.1" +"@shopify/storefront-api-client@^0.2.2": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@shopify/storefront-api-client/-/storefront-api-client-0.2.2.tgz#79ddddd70838617e7a1a509f2d58ce148457e293" + integrity sha512-sPpbXEGYbCRQ1jvwdEMtrutUTLmwtrZ30nHBLuKLIgtezFmhuvF5/FD/hgPQCxWxEukZt8518gAVgE6p+9D8ig== + dependencies: + "@shopify/graphql-client" "^0.9.2" + "@shopify/typescript-configs@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@shopify/typescript-configs/-/typescript-configs-5.1.0.tgz#f6e8fdd3291bf0a406578b2c6eb21f8c542d3c0a" From 0da2fb97df6c7e3bb57263d4df3108035ea81f91 Mon Sep 17 00:00:00 2001 From: Rachel Carvalho Date: Thu, 18 Jan 2024 11:53:36 -0300 Subject: [PATCH 31/41] Log throttling errors on webhook registration again --- .../webhooks/__tests__/mock-responses.ts | 11 +++ .../webhooks/__tests__/register.test.ts | 99 ++++++++++++++++++- .../server/authenticate/webhooks/register.ts | 57 +++++++---- .../shopify-app-remix/src/server/types.ts | 2 +- 4 files changed, 148 insertions(+), 21 deletions(-) diff --git a/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/mock-responses.ts b/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/mock-responses.ts index 5377ca670a..fd0533599b 100644 --- a/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/mock-responses.ts +++ b/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/mock-responses.ts @@ -32,3 +32,14 @@ export const HTTP_WEBHOOK_CREATE_ERROR_RESPONSE = { }, }, }; + +export const HTTP_WEBHOOK_THROTTLING_ERROR_RESPONSE = { + errors: [ + { + message: 'Throttled', + extensions: { + code: 'THROTTLED', + }, + }, + ], +}; diff --git a/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/register.test.ts b/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/register.test.ts index b3dfd241ad..c8cec6fdc4 100644 --- a/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/register.test.ts +++ b/packages/shopify-app-remix/src/server/authenticate/webhooks/__tests__/register.test.ts @@ -1,4 +1,4 @@ -import {DeliveryMethod, Session} from '@shopify/shopify-api'; +import {DeliveryMethod, GraphqlQueryError, Session} from '@shopify/shopify-api'; import {shopifyApp} from '../../..'; import { @@ -110,4 +110,101 @@ describe('Webhook registration', () => { NOT_A_VALID_TOPIC: [expect.objectContaining({success: false})], }); }); + + // eslint-disable-next-line no-warning-comments + // TODO: Remove tests once we have a better solution to parallel afterAuth calls + it('logs throttling errors', async () => { + // GIVEN + const shopify = shopifyApp( + testConfig({ + webhooks: { + NOT_A_VALID_TOPIC: { + deliveryMethod: DeliveryMethod.Http, + callbackUrl: '/webhooks', + }, + }, + }), + ); + const session = new Session({ + id: `offline_${TEST_SHOP}`, + shop: TEST_SHOP, + isOnline: false, + state: 'test', + accessToken: 'totally_real_token', + }); + + await mockExternalRequests( + { + request: new Request(GRAPHQL_URL, { + method: 'POST', + body: 'webhookSubscriptions', + }), + response: new Response( + JSON.stringify(mockResponses.EMPTY_WEBHOOK_RESPONSE), + ), + }, + { + request: new Request(GRAPHQL_URL, { + method: 'POST', + body: 'webhookSubscriptionCreate', + }), + response: new Response( + JSON.stringify(mockResponses.HTTP_WEBHOOK_THROTTLING_ERROR_RESPONSE), + ), + }, + ); + + // WHEN + const results = await shopify.registerWebhooks({session}); + + // THEN + expect(results).toBeUndefined(); + }); + + it('throws other errors', async () => { + // GIVEN + const shopify = shopifyApp( + testConfig({ + webhooks: { + NOT_A_VALID_TOPIC: { + deliveryMethod: DeliveryMethod.Http, + callbackUrl: '/webhooks', + }, + }, + }), + ); + const session = new Session({ + id: `offline_${TEST_SHOP}`, + shop: TEST_SHOP, + isOnline: false, + state: 'test', + accessToken: 'totally_real_token', + }); + + await mockExternalRequests( + { + request: new Request(GRAPHQL_URL, { + method: 'POST', + body: 'webhookSubscriptions', + }), + response: new Response( + JSON.stringify(mockResponses.EMPTY_WEBHOOK_RESPONSE), + ), + }, + { + request: new Request(GRAPHQL_URL, { + method: 'POST', + body: 'webhookSubscriptionCreate', + }), + response: new Response( + JSON.stringify({errors: [{extensions: {code: 'FAILED_REQUEST'}}]}), + ), + }, + ); + + // THEN + expect(shopify.registerWebhooks({session})).rejects.toThrowError( + GraphqlQueryError, + ); + }); }); diff --git a/packages/shopify-app-remix/src/server/authenticate/webhooks/register.ts b/packages/shopify-app-remix/src/server/authenticate/webhooks/register.ts index 0acc99c6a7..ec69392a48 100644 --- a/packages/shopify-app-remix/src/server/authenticate/webhooks/register.ts +++ b/packages/shopify-app-remix/src/server/authenticate/webhooks/register.ts @@ -4,26 +4,45 @@ import type {RegisterWebhooksOptions} from './types'; export function registerWebhooksFactory({api, logger}: BasicParams) { return async function registerWebhooks({session}: RegisterWebhooksOptions) { - return api.webhooks.register({session}).then((response) => { - Object.entries(response).forEach(([topic, topicResults]) => { - topicResults.forEach(({success, ...rest}) => { - if (success) { - logger.debug('Registered webhook', { - topic, - shop: session.shop, - operation: rest.operation, - }); - } else { - logger.error('Failed to register webhook', { - topic, - shop: session.shop, - result: JSON.stringify(rest.result), - }); - } + return api.webhooks + .register({session}) + .then((response) => { + Object.entries(response).forEach(([topic, topicResults]) => { + topicResults.forEach(({success, ...rest}) => { + if (success) { + logger.debug('Registered webhook', { + topic, + shop: session.shop, + operation: rest.operation, + }); + } else { + logger.error('Failed to register webhook', { + topic, + shop: session.shop, + result: JSON.stringify(rest.result), + }); + } + }); }); - }); - return response; - }); + return response; + }) + .catch((error) => { + const graphQLErrors: {extensions: {code?: string}}[] = + error.body?.errors?.graphQLErrors || []; + + const throttled = graphQLErrors.find( + ({extensions: {code}}) => code === 'THROTTLED', + ); + + if (throttled) { + logger.error('Failed to register webhooks', { + shop: session.shop, + error: JSON.stringify(error), + }); + } else { + throw error; + } + }); }; } diff --git a/packages/shopify-app-remix/src/server/types.ts b/packages/shopify-app-remix/src/server/types.ts index 9d942981f5..e3cfaa55ac 100644 --- a/packages/shopify-app-remix/src/server/types.ts +++ b/packages/shopify-app-remix/src/server/types.ts @@ -66,7 +66,7 @@ interface JSONArray extends Array {} type RegisterWebhooks = ( options: RegisterWebhooksOptions, -) => Promise; +) => Promise; export enum LoginErrorType { MissingShop = 'MISSING_SHOP', From 6b0327a0a5758a5a032ee5a02e3a2d379f30c3d6 Mon Sep 17 00:00:00 2001 From: Zoey Lan Date: Thu, 18 Jan 2024 10:27:53 -0700 Subject: [PATCH 32/41] bump shopify-api version for shopify-app-session-storage --- packages/shopify-app-session-storage/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/shopify-app-session-storage/package.json b/packages/shopify-app-session-storage/package.json index f3e01f870a..c6a1ed5070 100644 --- a/packages/shopify-app-session-storage/package.json +++ b/packages/shopify-app-session-storage/package.json @@ -34,7 +34,7 @@ "tslib": "^2.6.2" }, "peerDependencies": { - "@shopify/shopify-api": "^9.0.1" + "@shopify/shopify-api": "^9.0.2" }, "devDependencies": { "@shopify/eslint-plugin": "^42.1.0", From b998c30c6f6aa8c4f15146f3617409a5c834cc39 Mon Sep 17 00:00:00 2001 From: Zoey Lan Date: Thu, 18 Jan 2024 10:30:52 -0700 Subject: [PATCH 33/41] Add changesets --- .changeset/famous-knives-fix.md | 17 +++++++++++++++++ .changeset/smart-windows-smash.md | 5 +++++ 2 files changed, 22 insertions(+) create mode 100644 .changeset/famous-knives-fix.md create mode 100644 .changeset/smart-windows-smash.md diff --git a/.changeset/famous-knives-fix.md b/.changeset/famous-knives-fix.md new file mode 100644 index 0000000000..179ac9dd3e --- /dev/null +++ b/.changeset/famous-knives-fix.md @@ -0,0 +1,17 @@ +--- +'@shopify/shopify-app-session-storage-postgresql': patch +'@shopify/shopify-app-session-storage-test-utils': patch +'@shopify/shopify-app-session-storage-dynamodb': patch +'@shopify/shopify-app-session-storage-mongodb': patch +'@shopify/shopify-app-session-storage-memory': patch +'@shopify/shopify-app-session-storage-prisma': patch +'@shopify/shopify-app-session-storage-sqlite': patch +'@shopify/shopify-app-session-storage-mysql': patch +'@shopify/shopify-app-session-storage-redis': patch +'@shopify/shopify-app-session-storage-kv': patch +'@shopify/shopify-app-session-storage': patch +'@shopify/shopify-app-express': patch +'@shopify/shopify-app-remix': patch +--- + +Bump shopify-api version from 9.0.1 to 9.0.2 diff --git a/.changeset/smart-windows-smash.md b/.changeset/smart-windows-smash.md new file mode 100644 index 0000000000..a594b6f341 --- /dev/null +++ b/.changeset/smart-windows-smash.md @@ -0,0 +1,5 @@ +--- +'@shopify/shopify-app-remix': patch +--- + +Handle webhook registration throttling error From f4451647728515f90ff0f5a55e5cba7010677842 Mon Sep 17 00:00:00 2001 From: Zoey Lan Date: Wed, 17 Jan 2024 11:27:02 -0700 Subject: [PATCH 34/41] Use body field from GraphqlQueryError --- .changeset/wild-bottles-explode.md | 5 +++++ .../server/authenticate/admin/strategies/auth-code-flow.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/wild-bottles-explode.md diff --git a/.changeset/wild-bottles-explode.md b/.changeset/wild-bottles-explode.md new file mode 100644 index 0000000000..522fd6509e --- /dev/null +++ b/.changeset/wild-bottles-explode.md @@ -0,0 +1,5 @@ +--- +'@shopify/shopify-app-remix': patch +--- + +Use 'body' field from GraphqlQueryError when logging session validation error diff --git a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts index 69a6dba4cc..bd4b786ab0 100644 --- a/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts +++ b/packages/shopify-app-remix/src/server/authenticate/admin/strategies/auth-code-flow.ts @@ -306,7 +306,7 @@ export class AuthCodeFlowStrategy< } else if (error instanceof GraphqlQueryError) { const context: {[key: string]: string} = {shop}; if (error.response) { - context.response = JSON.stringify(error.response); + context.response = JSON.stringify(error.body); } logger.error( From 6dffcb3e3d91913b584cba5a40e533750e9e402f Mon Sep 17 00:00:00 2001 From: Petra Donka Date: Sat, 20 Jan 2024 15:47:13 +0100 Subject: [PATCH 35/41] bump Prisma from 4.13.0 to 5.8.1 --- .../package.json | 8 +-- yarn.lock | 66 +++++++++++++------ 2 files changed, 49 insertions(+), 25 deletions(-) diff --git a/packages/shopify-app-session-storage-prisma/package.json b/packages/shopify-app-session-storage-prisma/package.json index ba1c1fcf3a..2303450d5b 100644 --- a/packages/shopify-app-session-storage-prisma/package.json +++ b/packages/shopify-app-session-storage-prisma/package.json @@ -34,16 +34,16 @@ "tslib": "^2.6.2" }, "peerDependencies": { - "@prisma/client": "^4.13.0", + "@prisma/client": "^5.8.1", "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3", - "prisma": "^4.13.0" + "prisma": "^5.8.1" }, "devDependencies": { - "@prisma/client": "^4.13.0", + "@prisma/client": "^5.8.1", "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3", - "prisma": "^4.13.0", + "prisma": "^5.8.1", "@shopify/eslint-plugin": "^42.1.0", "@shopify/prettier-config": "^1.1.2", "@shopify/shopify-app-session-storage-test-utils": "^1.0.3", diff --git a/yarn.lock b/yarn.lock index 84eee1fc0f..cfd6ce16bb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2660,22 +2660,46 @@ resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@prisma/client@^4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@prisma/client/-/client-4.13.0.tgz#271d2b9756503ea17bbdb459c7995536cf2a6191" - integrity sha512-YaiiICcRB2hatxsbnfB66uWXjcRw3jsZdlAVxmx0cFcTc/Ad/sKdHCcWSnqyDX47vAewkjRFwiLwrOUjswVvmA== - dependencies: - "@prisma/engines-version" "4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a" - -"@prisma/engines-version@4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a": - version "4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a" - resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-4.13.0-50.1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a.tgz#ae338908d11685dee50e7683502d75442b955bf9" - integrity sha512-fsQlbkhPJf08JOzKoyoD9atdUijuGBekwoOPZC3YOygXEml1MTtgXVpnUNchQlRSY82OQ6pSGQ9PxUe4arcSLQ== - -"@prisma/engines@4.13.0": - version "4.13.0" - resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-4.13.0.tgz#582a6b90b6efeb0f465984f1fe0e72a4afaaa5ae" - integrity sha512-HrniowHRZXHuGT9XRgoXEaP2gJLXM5RMoItaY2PkjvuZ+iHc0Zjbm/302MB8YsPdWozAPHHn+jpFEcEn71OgPw== +"@prisma/client@^5.8.1": + version "5.8.1" + resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.8.1.tgz#7815ec51c0ca2a6de219c02e7846701ae3baf240" + integrity sha512-xQtMPfbIwLlbm0VVIVQY2yqQVOxPwRQhvIp7Z3m2900g1bu/zRHKhYZJQWELqmjl6d8YwBy0K2NvMqh47v1ubw== + +"@prisma/debug@5.8.1": + version "5.8.1" + resolved "https://registry.yarnpkg.com/@prisma/debug/-/debug-5.8.1.tgz#704daa36919b0fc4d227260ecebfa1c94b155b07" + integrity sha512-tjuw7eA0Us3T42jx9AmAgL58rzwzpFGYc3R7Y4Ip75EBYrKMBA1YihuWMcBC92ILmjlQ/u3p8VxcIE0hr+fZfg== + +"@prisma/engines-version@5.8.1-1.78caf6feeaed953168c64e15a249c3e9a033ebe2": + version "5.8.1-1.78caf6feeaed953168c64e15a249c3e9a033ebe2" + resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.8.1-1.78caf6feeaed953168c64e15a249c3e9a033ebe2.tgz#f600a45afc4cf0c0356b6ed90add6050fa3f3239" + integrity sha512-f5C3JM3l9yhGr3cr4FMqWloFaSCpNpMi58Om22rjD2DOz3owci2mFdFXMgnAGazFPKrCbbEhcxdsRfspEYRoFQ== + +"@prisma/engines@5.8.1": + version "5.8.1" + resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.8.1.tgz#b850751f5bf7d5e570b9fe16cefdc2b1fd2c02c3" + integrity sha512-TJgYLRrZr56uhqcXO4GmP5be+zjCIHtLDK20Cnfg+o9d905hsN065QOL+3Z0zQAy6YD31Ol4u2kzSfRmbJv/uA== + dependencies: + "@prisma/debug" "5.8.1" + "@prisma/engines-version" "5.8.1-1.78caf6feeaed953168c64e15a249c3e9a033ebe2" + "@prisma/fetch-engine" "5.8.1" + "@prisma/get-platform" "5.8.1" + +"@prisma/fetch-engine@5.8.1": + version "5.8.1" + resolved "https://registry.yarnpkg.com/@prisma/fetch-engine/-/fetch-engine-5.8.1.tgz#38bb92f1fbd3669340a3cc49fce403ab4df671dd" + integrity sha512-+bgjjoSFa6uYEbAPlklfoVSStOEfcpheOjoBoNsNNSQdSzcwE2nM4Q0prun0+P8/0sCHo18JZ9xqa8gObvgOUw== + dependencies: + "@prisma/debug" "5.8.1" + "@prisma/engines-version" "5.8.1-1.78caf6feeaed953168c64e15a249c3e9a033ebe2" + "@prisma/get-platform" "5.8.1" + +"@prisma/get-platform@5.8.1": + version "5.8.1" + resolved "https://registry.yarnpkg.com/@prisma/get-platform/-/get-platform-5.8.1.tgz#8cd450b65a52a5a6ed5b2f52457136a492c0f251" + integrity sha512-wnA+6HTFcY+tkykMokix9GiAkaauPC5W/gg0O5JB0J8tCTNWrqpnQ7AsaGRfkYUbeOIioh6woDjQrGTTRf1Zag== + dependencies: + "@prisma/debug" "5.8.1" "@redis/bloom@1.2.0": version "1.2.0" @@ -9001,12 +9025,12 @@ pretty-format@^29.0.0, pretty-format@^29.5.0: ansi-styles "^5.0.0" react-is "^18.0.0" -prisma@^4.13.0: - version "4.13.0" - resolved "https://registry.yarnpkg.com/prisma/-/prisma-4.13.0.tgz#0b83f40acf50cd47d7463a135c4e9b275713e602" - integrity sha512-L9mqjnSmvWIRCYJ9mQkwCtj4+JDYYTdhoyo8hlsHNDXaZLh/b4hR0IoKIBbTKxZuyHQzLopb/+0Rvb69uGV7uA== +prisma@^5.8.1: + version "5.8.1" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.8.1.tgz#1f101793a8831c0719dfbed5f85a96ea4888c9d3" + integrity sha512-N6CpjzECnUHZ5beeYpDzkt2rYpEdAeqXX2dweu6BoQaeYkNZrC/WJHM+5MO/uidFHTak8QhkPKBWck1o/4MD4A== dependencies: - "@prisma/engines" "4.13.0" + "@prisma/engines" "5.8.1" promise-inflight@^1.0.1: version "1.0.1" From ea88df2f9210c02df8becde12304c8054f90f6f6 Mon Sep 17 00:00:00 2001 From: Petra Donka Date: Sat, 20 Jan 2024 15:59:08 +0100 Subject: [PATCH 36/41] create a draft changelog entry --- .changeset/ninety-rivers-worry.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/ninety-rivers-worry.md diff --git a/.changeset/ninety-rivers-worry.md b/.changeset/ninety-rivers-worry.md new file mode 100644 index 0000000000..b14cebf13e --- /dev/null +++ b/.changeset/ninety-rivers-worry.md @@ -0,0 +1,5 @@ +--- +'@shopify/shopify-app-session-storage-prisma': patch +--- + +Updated the dependency on `prisma` From 8ac60206a567c2c6736e8b96e04058d44f0650fe Mon Sep 17 00:00:00 2001 From: Petra Donka Date: Mon, 22 Jan 2024 21:46:03 +0100 Subject: [PATCH 37/41] updated changeset --- .changeset/ninety-rivers-worry.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/ninety-rivers-worry.md b/.changeset/ninety-rivers-worry.md index b14cebf13e..a45635387c 100644 --- a/.changeset/ninety-rivers-worry.md +++ b/.changeset/ninety-rivers-worry.md @@ -1,5 +1,5 @@ --- -'@shopify/shopify-app-session-storage-prisma': patch +'@shopify/shopify-app-session-storage-prisma': major --- Updated the dependency on `prisma` From d9a7c8c3facb0c8bcb782c250234841c43370ea0 Mon Sep 17 00:00:00 2001 From: Paulo Margarido <64600052+paulomarg@users.noreply.github.com> Date: Wed, 24 Jan 2024 08:46:43 -0500 Subject: [PATCH 38/41] Expand on the Prisma 5 changelog notice --- .changeset/ninety-rivers-worry.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/ninety-rivers-worry.md b/.changeset/ninety-rivers-worry.md index a45635387c..3ad7602ff9 100644 --- a/.changeset/ninety-rivers-worry.md +++ b/.changeset/ninety-rivers-worry.md @@ -2,4 +2,4 @@ '@shopify/shopify-app-session-storage-prisma': major --- -Updated the dependency on `prisma` +Updated the dependency on `prisma` to v5+. This package itself has no breaking changes, but you'll need to update your app's dependency on Prisma as well as this package. From d8f843657e6fb9419843f89ebc44c8e6ad12cf71 Mon Sep 17 00:00:00 2001 From: Paulo Margarido <64600052+paulomarg@users.noreply.github.com> Date: Wed, 24 Jan 2024 10:38:29 -0500 Subject: [PATCH 39/41] Replace query() call with request() --- .changeset/polite-birds-unite.md | 5 +++++ .../src/middlewares/has-valid-access-token.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/polite-birds-unite.md diff --git a/.changeset/polite-birds-unite.md b/.changeset/polite-birds-unite.md new file mode 100644 index 0000000000..abe6780eff --- /dev/null +++ b/.changeset/polite-birds-unite.md @@ -0,0 +1,5 @@ +--- +'@shopify/shopify-app-express': patch +--- + +Replaced query() internal call in the GraphQL client with request() diff --git a/packages/shopify-app-express/src/middlewares/has-valid-access-token.ts b/packages/shopify-app-express/src/middlewares/has-valid-access-token.ts index 795e71f6de..a033cf3500 100644 --- a/packages/shopify-app-express/src/middlewares/has-valid-access-token.ts +++ b/packages/shopify-app-express/src/middlewares/has-valid-access-token.ts @@ -12,7 +12,7 @@ export async function hasValidAccessToken( ): Promise { try { const client = new api.clients.Graphql({session}); - await client.query({data: TEST_GRAPHQL_QUERY}); + await client.request(TEST_GRAPHQL_QUERY); return true; } catch (error) { if (error instanceof HttpResponseError && error.response.code === 401) { From b4eeb24233e3f7b97a517eb1a2fd659fcac6f690 Mon Sep 17 00:00:00 2001 From: Paulo Margarido <64600052+paulomarg@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:38:33 -0500 Subject: [PATCH 40/41] Fix dev dependencies across all projects --- .changeset/dry-meals-hear.md | 17 +++++++++++++ package.json | 6 ++++- packages/shopify-app-express/package.json | 11 ++------- packages/shopify-app-remix/package.json | 8 ++----- .../package.json | 13 ++-------- .../package.json | 11 ++------- .../package.json | 13 ++-------- .../package.json | 12 ++-------- .../package.json | 12 ++-------- .../package.json | 12 ++-------- .../package.json | 14 ++--------- .../package.json | 12 ++-------- .../package.json | 12 ++-------- .../package.json | 19 ++------------- .../shopify-app-session-storage/package.json | 14 ++--------- yarn.lock | 24 +++++++++---------- 16 files changed, 60 insertions(+), 150 deletions(-) create mode 100644 .changeset/dry-meals-hear.md diff --git a/.changeset/dry-meals-hear.md b/.changeset/dry-meals-hear.md new file mode 100644 index 0000000000..1790d7408c --- /dev/null +++ b/.changeset/dry-meals-hear.md @@ -0,0 +1,17 @@ +--- +'@shopify/shopify-app-session-storage-postgresql': patch +'@shopify/shopify-app-session-storage-test-utils': patch +'@shopify/shopify-app-session-storage-dynamodb': patch +'@shopify/shopify-app-session-storage-mongodb': patch +'@shopify/shopify-app-session-storage-memory': patch +'@shopify/shopify-app-session-storage-prisma': patch +'@shopify/shopify-app-session-storage-sqlite': patch +'@shopify/shopify-app-session-storage-mysql': patch +'@shopify/shopify-app-session-storage-redis': patch +'@shopify/shopify-app-session-storage-kv': patch +'@shopify/shopify-app-session-storage': patch +'@shopify/shopify-app-express': patch +'@shopify/shopify-app-remix': patch +--- + +Improved and simplified package.json dependencies diff --git a/package.json b/package.json index b70701b160..d85c155095 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ }, "devDependencies": { "@changesets/cli": "^2.26.1", - "@jest/types": "^29.6.1", + "@jest/types": "^29.6.3", "@shopify/eslint-plugin": "^42.1.0", "@shopify/loom": "^1.0.2", "@shopify/loom-cli": "^1.1.0", @@ -22,12 +22,16 @@ "@shopify/loom-plugin-prettier": "^2.0.1", "@shopify/prettier-config": "^1.1.2", "@shopify/typescript-configs": "^5.1.0", + "@types/jest": "^29.5.1", "eslint": "^8.55.0", + "eslint-plugin-prettier": "^4.2.1", "jest": "^29.1.0", "jest-fetch-mock": "^3.0.3", "jest-runner-eslint": "^2.0.0", + "prettier": "^2.8.8", "rimraf": "^5.0.0", "ts-jest": "^29.1.0", + "tslib": "^2.6.2", "typescript": "^4.9.5" }, "dependencies": {}, diff --git a/packages/shopify-app-express/package.json b/packages/shopify-app-express/package.json index 1119c8c68c..ccd8159dda 100644 --- a/packages/shopify-app-express/package.json +++ b/packages/shopify-app-express/package.json @@ -35,22 +35,15 @@ "@shopify/shopify-app-session-storage-memory": "^2.0.3", "cookie-parser": "^1.4.6", "express": "^4.18.1", - "semver": "^7.5.4", - "tslib": "^2.6.2" + "semver": "^7.5.4" }, "devDependencies": { - "@shopify/eslint-plugin": "^42.1.0", - "@shopify/prettier-config": "^1.1.2", "@types/compression": "^1.7.2", "@types/cookie-parser": "^1.4.3", "@types/express": "^4.17.16", "@types/jsonwebtoken": "^9.0.5", - "eslint": "^8.55.0", - "eslint-plugin-prettier": "^4.2.1", "jsonwebtoken": "^9.0.2", - "prettier": "^2.8.8", - "supertest": "^6.3.3", - "typescript": "^4.9.5" + "supertest": "^6.3.3" }, "files": [ "build/*", diff --git a/packages/shopify-app-remix/package.json b/packages/shopify-app-remix/package.json index f856883609..2873790713 100644 --- a/packages/shopify-app-remix/package.json +++ b/packages/shopify-app-remix/package.json @@ -55,16 +55,13 @@ "@shopify/polaris": "^11.8.0", "@shopify/react-testing": "^5.1.3", "@shopify/shopify-app-session-storage-memory": "^2.0.3", - "@types/jest": "^29.5.1", "@types/jsonwebtoken": "^9.0.5", "@types/react": "^18.2.18", "@types/semver": "^7.5.6", - "jest": "^29.5.0", "jest-fetch-mock": "^3.0.3", "jsonwebtoken": "^9.0.2", "react": "^18.2.0", - "react-dom": "^18.2.0", - "ts-jest": "^29.1.0" + "react-dom": "^18.2.0" }, "dependencies": { "@remix-run/server-runtime": "^2.0.0", @@ -73,8 +70,7 @@ "@shopify/shopify-app-session-storage": "^2.0.3", "@shopify/storefront-api-client": "^0.2.1", "isbot": "^3.7.1", - "semver": "^7.5.4", - "tslib": "^2.6.2" + "semver": "^7.5.4" }, "peerDependencies": { "@remix-run/react": "*", diff --git a/packages/shopify-app-session-storage-dynamodb/package.json b/packages/shopify-app-session-storage-dynamodb/package.json index cf26504940..76bfb34ea6 100644 --- a/packages/shopify-app-session-storage-dynamodb/package.json +++ b/packages/shopify-app-session-storage-dynamodb/package.json @@ -33,23 +33,14 @@ ], "dependencies": { "@aws-sdk/client-dynamodb": "^3.470.0", - "@aws-sdk/util-dynamodb": "^3.332.0", - "tslib": "^2.6.2" + "@aws-sdk/util-dynamodb": "^3.332.0" }, "peerDependencies": { "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3" }, "devDependencies": { - "@shopify/eslint-plugin": "^42.1.0", - "@shopify/prettier-config": "^1.1.2", - "@shopify/shopify-api": "^9.0.2", - "@shopify/shopify-app-session-storage": "^2.0.3", - "@shopify/shopify-app-session-storage-test-utils": "^1.0.3", - "eslint": "^8.55.0", - "eslint-plugin-prettier": "^4.2.1", - "prettier": "^2.8.8", - "typescript": "^4.9.5" + "@shopify/shopify-app-session-storage-test-utils": "^1.0.3" }, "files": [ "build/*", diff --git a/packages/shopify-app-session-storage-kv/package.json b/packages/shopify-app-session-storage-kv/package.json index 3659a7f772..fe2e8ddef7 100644 --- a/packages/shopify-app-session-storage-kv/package.json +++ b/packages/shopify-app-session-storage-kv/package.json @@ -32,8 +32,7 @@ "CloudFlare" ], "dependencies": { - "semver": "^7.5.4", - "tslib": "^2.6.2" + "semver": "^7.5.4" }, "peerDependencies": { "@shopify/shopify-api": "^9.0.2", @@ -41,14 +40,8 @@ }, "devDependencies": { "@cloudflare/workers-types": "^4.20230511.0", - "@shopify/eslint-plugin": "^42.1.0", - "@shopify/prettier-config": "^1.1.2", "@shopify/shopify-app-session-storage-test-utils": "^1.0.3", - "eslint": "^8.55.0", - "eslint-plugin-prettier": "^4.2.1", - "miniflare": "^2.14.0", - "prettier": "^2.8.8", - "typescript": "^4.9.5" + "miniflare": "^2.14.0" }, "files": [ "build/*", diff --git a/packages/shopify-app-session-storage-memory/package.json b/packages/shopify-app-session-storage-memory/package.json index b56fb77691..7c3ecb5b67 100644 --- a/packages/shopify-app-session-storage-memory/package.json +++ b/packages/shopify-app-session-storage-memory/package.json @@ -29,22 +29,13 @@ "Storefront API", "session storage" ], - "dependencies": { - "tslib": "^2.6.2" - }, + "dependencies": {}, "peerDependencies": { "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3" }, "devDependencies": { - "@shopify/eslint-plugin": "^42.1.0", - "@shopify/prettier-config": "^1.1.2", - "@shopify/shopify-app-session-storage-test-utils": "^1.0.3", - "@types/jest": "^29.5.1", - "eslint": "^8.55.0", - "eslint-plugin-prettier": "^4.2.1", - "prettier": "^2.8.8", - "typescript": "4.9.5" + "@shopify/shopify-app-session-storage-test-utils": "^1.0.3" }, "files": [ "build/*", diff --git a/packages/shopify-app-session-storage-mongodb/package.json b/packages/shopify-app-session-storage-mongodb/package.json index ffa0d03522..8ddf70626d 100644 --- a/packages/shopify-app-session-storage-mongodb/package.json +++ b/packages/shopify-app-session-storage-mongodb/package.json @@ -31,22 +31,14 @@ "session storage" ], "dependencies": { - "mongodb": "^6.3.0", - "tslib": "^2.6.2" + "mongodb": "^6.3.0" }, "peerDependencies": { "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3" }, "devDependencies": { - "@shopify/eslint-plugin": "^42.1.0", - "@shopify/prettier-config": "^1.1.2", - "@shopify/shopify-app-session-storage-test-utils": "^1.0.3", - "@types/jest": "^29.5.1", - "eslint": "^8.55.0", - "eslint-plugin-prettier": "^4.2.1", - "prettier": "^2.8.8", - "typescript": "4.9.5" + "@shopify/shopify-app-session-storage-test-utils": "^1.0.3" }, "files": [ "build/*", diff --git a/packages/shopify-app-session-storage-mysql/package.json b/packages/shopify-app-session-storage-mysql/package.json index a9c55df269..eb2e51d7a1 100644 --- a/packages/shopify-app-session-storage-mysql/package.json +++ b/packages/shopify-app-session-storage-mysql/package.json @@ -32,22 +32,14 @@ "session storage" ], "dependencies": { - "mysql2": "^3.3.1", - "tslib": "^2.6.2" + "mysql2": "^3.3.1" }, "peerDependencies": { "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3" }, "devDependencies": { - "@shopify/eslint-plugin": "^42.1.0", - "@shopify/prettier-config": "^1.1.2", - "@shopify/shopify-app-session-storage-test-utils": "^1.0.3", - "@types/jest": "^29.5.1", - "eslint": "^8.55.0", - "eslint-plugin-prettier": "^4.2.1", - "prettier": "^2.8.8", - "typescript": "4.9.5" + "@shopify/shopify-app-session-storage-test-utils": "^1.0.3" }, "files": [ "build/*", diff --git a/packages/shopify-app-session-storage-postgresql/package.json b/packages/shopify-app-session-storage-postgresql/package.json index 75246c90d4..2201625957 100644 --- a/packages/shopify-app-session-storage-postgresql/package.json +++ b/packages/shopify-app-session-storage-postgresql/package.json @@ -33,23 +33,15 @@ ], "dependencies": { "pg": "^8.11.0", - "pg-connection-string": "^2.6.2", - "tslib": "^2.6.2" + "pg-connection-string": "^2.6.2" }, "peerDependencies": { "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3" }, "devDependencies": { - "@shopify/eslint-plugin": "^42.1.0", - "@shopify/prettier-config": "^1.1.2", "@shopify/shopify-app-session-storage-test-utils": "^1.0.3", - "@types/jest": "^29.5.1", - "@types/pg": "^8.6.6", - "eslint": "^8.55.0", - "eslint-plugin-prettier": "^4.2.1", - "prettier": "^2.8.8", - "typescript": "4.9.5" + "@types/pg": "^8.6.6" }, "files": [ "build/*", diff --git a/packages/shopify-app-session-storage-prisma/package.json b/packages/shopify-app-session-storage-prisma/package.json index 2303450d5b..61e317771f 100644 --- a/packages/shopify-app-session-storage-prisma/package.json +++ b/packages/shopify-app-session-storage-prisma/package.json @@ -30,9 +30,7 @@ "session storage", "Prisma" ], - "dependencies": { - "tslib": "^2.6.2" - }, + "dependencies": {}, "peerDependencies": { "@prisma/client": "^5.8.1", "@shopify/shopify-api": "^9.0.2", @@ -41,16 +39,8 @@ }, "devDependencies": { "@prisma/client": "^5.8.1", - "@shopify/shopify-api": "^9.0.2", - "@shopify/shopify-app-session-storage": "^2.0.3", - "prisma": "^5.8.1", - "@shopify/eslint-plugin": "^42.1.0", - "@shopify/prettier-config": "^1.1.2", "@shopify/shopify-app-session-storage-test-utils": "^1.0.3", - "eslint": "^8.55.0", - "eslint-plugin-prettier": "^4.2.1", - "prettier": "^2.8.7", - "typescript": "4.9.5" + "prisma": "^5.8.1" }, "files": [ "build/*", diff --git a/packages/shopify-app-session-storage-redis/package.json b/packages/shopify-app-session-storage-redis/package.json index 85b2614bab..d5c30d0807 100644 --- a/packages/shopify-app-session-storage-redis/package.json +++ b/packages/shopify-app-session-storage-redis/package.json @@ -32,22 +32,14 @@ "Redis" ], "dependencies": { - "redis": "^4.6.11", - "tslib": "^2.6.2" + "redis": "^4.6.11" }, "peerDependencies": { "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3" }, "devDependencies": { - "@shopify/eslint-plugin": "^42.1.0", - "@shopify/prettier-config": "^1.1.2", - "@shopify/shopify-app-session-storage-test-utils": "^1.0.3", - "@types/jest": "^29.5.1", - "eslint": "^8.55.0", - "eslint-plugin-prettier": "^4.2.1", - "prettier": "^2.8.8", - "typescript": "4.9.5" + "@shopify/shopify-app-session-storage-test-utils": "^1.0.3" }, "files": [ "build/*", diff --git a/packages/shopify-app-session-storage-sqlite/package.json b/packages/shopify-app-session-storage-sqlite/package.json index 5e60c55298..84976a9c4a 100644 --- a/packages/shopify-app-session-storage-sqlite/package.json +++ b/packages/shopify-app-session-storage-sqlite/package.json @@ -32,23 +32,15 @@ "sqlite" ], "dependencies": { - "sqlite3": "^5.1.6", - "tslib": "^2.6.2" + "sqlite3": "^5.1.6" }, "peerDependencies": { "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3" }, "devDependencies": { - "@shopify/eslint-plugin": "^42.1.0", - "@shopify/prettier-config": "^1.1.2", "@shopify/shopify-app-session-storage-test-utils": "^1.0.3", - "@types/jest": "^29.5.1", - "@types/sqlite3": "^3.1.8", - "eslint": "^8.55.0", - "eslint-plugin-prettier": "^4.2.1", - "prettier": "^2.8.8", - "typescript": "4.9.5" + "@types/sqlite3": "^3.1.8" }, "files": [ "build/*", diff --git a/packages/shopify-app-session-storage-test-utils/package.json b/packages/shopify-app-session-storage-test-utils/package.json index 8d6b4f147d..a67d2fe7f1 100644 --- a/packages/shopify-app-session-storage-test-utils/package.json +++ b/packages/shopify-app-session-storage-test-utils/package.json @@ -33,27 +33,12 @@ "session storage", "test utilities" ], - "dependencies": { - "jest-fetch-mock": "^3.0.3", - "tslib": "^2.6.2" - }, + "dependencies": {}, "peerDependencies": { "@shopify/shopify-api": "^9.0.2", "@shopify/shopify-app-session-storage": "^2.0.3" }, - "devDependencies": { - "@shopify/eslint-plugin": "^42.1.0", - "@shopify/prettier-config": "^1.1.2", - "@types/jest": "^29.5.1", - "eslint": "^8.55.0", - "eslint-plugin-prettier": "^4.2.1", - "jest": "^29.5.0", - "jest-fetch-mock": "^3.0.3", - "jest-runner-eslint": "^2.0.0", - "prettier": "^2.8.8", - "ts-jest": "^29.1.0", - "typescript": "^4.9.5" - }, + "devDependencies": {}, "files": [ "build/*", "!bundle/*", diff --git a/packages/shopify-app-session-storage/package.json b/packages/shopify-app-session-storage/package.json index c6a1ed5070..2c66a731a2 100644 --- a/packages/shopify-app-session-storage/package.json +++ b/packages/shopify-app-session-storage/package.json @@ -30,21 +30,11 @@ "Storefront API", "session storage" ], - "dependencies": { - "tslib": "^2.6.2" - }, + "dependencies": {}, "peerDependencies": { "@shopify/shopify-api": "^9.0.2" }, - "devDependencies": { - "@shopify/eslint-plugin": "^42.1.0", - "@shopify/prettier-config": "^1.1.2", - "@types/jest": "^29.5.1", - "eslint": "^8.55.0", - "eslint-plugin-prettier": "^4.2.1", - "prettier": "^2.8.8", - "typescript": "^4.9.5" - }, + "devDependencies": {}, "files": [ "build/*", "!bundle/*", diff --git a/yarn.lock b/yarn.lock index cfd6ce16bb..ff94537645 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2189,10 +2189,10 @@ dependencies: "@sinclair/typebox" "^0.25.16" -"@jest/schemas@^29.6.0": - version "29.6.0" - resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.0.tgz#0f4cb2c8e3dca80c135507ba5635a4fd755b0040" - integrity sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ== +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== dependencies: "@sinclair/typebox" "^0.27.8" @@ -2352,12 +2352,12 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" -"@jest/types@^29.6.1": - version "29.6.1" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.1.tgz#ae79080278acff0a6af5eb49d063385aaa897bf2" - integrity sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw== +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== dependencies: - "@jest/schemas" "^29.6.0" + "@jest/schemas" "^29.6.3" "@types/istanbul-lib-coverage" "^2.0.0" "@types/istanbul-reports" "^3.0.0" "@types/node" "*" @@ -7672,7 +7672,7 @@ jest@^27.2.4: import-local "^3.0.2" jest-cli "^27.5.1" -jest@^29.1.0, jest@^29.5.0: +jest@^29.1.0: version "29.5.0" resolved "https://registry.yarnpkg.com/jest/-/jest-29.5.0.tgz#f75157622f5ce7ad53028f2f8888ab53e1f1f24e" integrity sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ== @@ -8982,7 +8982,7 @@ prettier-linter-helpers@^1.0.0: dependencies: fast-diff "^1.1.2" -prettier@^2.7.1, prettier@^2.8.7, prettier@^2.8.8: +prettier@^2.7.1, prettier@^2.8.8: version "2.8.8" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== @@ -10240,7 +10240,7 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@4.9.5, typescript@^4.3.5, typescript@^4.9.5: +typescript@^4.3.5, typescript@^4.9.5: version "4.9.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== From b7fccbcc51878e31dfe9899954961266a4126065 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 Jan 2024 20:23:45 +0000 Subject: [PATCH 41/41] Bump @changesets/cli from 2.26.1 to 2.27.1 Bumps [@changesets/cli](https://github.com/changesets/changesets) from 2.26.1 to 2.27.1. - [Release notes](https://github.com/changesets/changesets/releases) - [Changelog](https://github.com/changesets/changesets/blob/main/docs/modifying-changelog-format.md) - [Commits](https://github.com/changesets/changesets/compare/@changesets/cli@2.26.1...@changesets/cli@2.27.1) --- updated-dependencies: - dependency-name: "@changesets/cli" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 265 +++++++++++++++++++++++---------------------------- 2 files changed, 120 insertions(+), 147 deletions(-) diff --git a/package.json b/package.json index d85c155095..fc2ef0184b 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "clean": "rimraf ./packages/*/build .loom" }, "devDependencies": { - "@changesets/cli": "^2.26.1", + "@changesets/cli": "^2.27.1", "@jest/types": "^29.6.3", "@shopify/eslint-plugin": "^42.1.0", "@shopify/loom": "^1.0.2", diff --git a/yarn.lock b/yarn.lock index ff94537645..5985cbcff1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1567,14 +1567,7 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.16.3", "@babel/runtime@^7.20.1", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4": - version "7.21.5" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200" - integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q== - dependencies: - regenerator-runtime "^0.13.11" - -"@babel/runtime@^7.8.7": +"@babel/runtime@^7.16.3", "@babel/runtime@^7.20.1", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": version "7.22.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ== @@ -1638,16 +1631,16 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@changesets/apply-release-plan@^6.1.3": - version "6.1.3" - resolved "https://registry.yarnpkg.com/@changesets/apply-release-plan/-/apply-release-plan-6.1.3.tgz#3bcc0bd57ba00d50d20df7d0141f1a9b2134eaf7" - integrity sha512-ECDNeoc3nfeAe1jqJb5aFQX7CqzQhD2klXRez2JDb/aVpGUbX673HgKrnrgJRuQR/9f2TtLoYIzrGB9qwD77mg== +"@changesets/apply-release-plan@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@changesets/apply-release-plan/-/apply-release-plan-7.0.0.tgz#ce3c3dfc5720550a5d592b54ad2f411f816ec5ff" + integrity sha512-vfi69JR416qC9hWmFGSxj7N6wA5J222XNBmezSVATPWDVPIF7gkd4d8CpbEbXmRWbVrkoli3oerGS6dcL/BGsQ== dependencies: "@babel/runtime" "^7.20.1" - "@changesets/config" "^2.3.0" - "@changesets/get-version-range-type" "^0.3.2" - "@changesets/git" "^2.0.0" - "@changesets/types" "^5.2.1" + "@changesets/config" "^3.0.0" + "@changesets/get-version-range-type" "^0.4.0" + "@changesets/git" "^3.0.0" + "@changesets/types" "^6.0.0" "@manypkg/get-packages" "^1.1.3" detect-indent "^6.0.0" fs-extra "^7.0.1" @@ -1655,164 +1648,163 @@ outdent "^0.5.0" prettier "^2.7.1" resolve-from "^5.0.0" - semver "^5.4.1" + semver "^7.5.3" -"@changesets/assemble-release-plan@^5.2.3": - version "5.2.3" - resolved "https://registry.yarnpkg.com/@changesets/assemble-release-plan/-/assemble-release-plan-5.2.3.tgz#5ce6191c6e193d40b566a7b0e01690cfb106f4db" - integrity sha512-g7EVZCmnWz3zMBAdrcKhid4hkHT+Ft1n0mLussFMcB1dE2zCuwcvGoy9ec3yOgPGF4hoMtgHaMIk3T3TBdvU9g== +"@changesets/assemble-release-plan@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@changesets/assemble-release-plan/-/assemble-release-plan-6.0.0.tgz#c69969b4bef7c32a8544b6941d1053260ca47e05" + integrity sha512-4QG7NuisAjisbW4hkLCmGW2lRYdPrKzro+fCtZaILX+3zdUELSvYjpL4GTv0E4aM9Mef3PuIQp89VmHJ4y2bfw== dependencies: "@babel/runtime" "^7.20.1" - "@changesets/errors" "^0.1.4" - "@changesets/get-dependents-graph" "^1.3.5" - "@changesets/types" "^5.2.1" + "@changesets/errors" "^0.2.0" + "@changesets/get-dependents-graph" "^2.0.0" + "@changesets/types" "^6.0.0" "@manypkg/get-packages" "^1.1.3" - semver "^5.4.1" + semver "^7.5.3" -"@changesets/changelog-git@^0.1.14": - version "0.1.14" - resolved "https://registry.yarnpkg.com/@changesets/changelog-git/-/changelog-git-0.1.14.tgz#852caa7727dcf91497c131d05bc2cd6248532ada" - integrity sha512-+vRfnKtXVWsDDxGctOfzJsPhaCdXRYoe+KyWYoq5X/GqoISREiat0l3L8B0a453B2B4dfHGcZaGyowHbp9BSaA== +"@changesets/changelog-git@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@changesets/changelog-git/-/changelog-git-0.2.0.tgz#1f3de11becafff5a38ebe295038a602403c93a86" + integrity sha512-bHOx97iFI4OClIT35Lok3sJAwM31VbUM++gnMBV16fdbtBhgYu4dxsphBF/0AZZsyAHMrnM0yFcj5gZM1py6uQ== dependencies: - "@changesets/types" "^5.2.1" + "@changesets/types" "^6.0.0" -"@changesets/cli@^2.26.1": - version "2.26.1" - resolved "https://registry.yarnpkg.com/@changesets/cli/-/cli-2.26.1.tgz#2d10858d7d32314a524e383111c96d831eb0402f" - integrity sha512-XnTa+b51vt057fyAudvDKGB0Sh72xutQZNAdXkCqPBKO2zvs2yYZx5hFZj1u9cbtpwM6Sxtcr02/FQJfZOzemQ== +"@changesets/cli@^2.27.1": + version "2.27.1" + resolved "https://registry.yarnpkg.com/@changesets/cli/-/cli-2.27.1.tgz#abce480fd30b9abbe2cfcf07d5d668c364ce2804" + integrity sha512-iJ91xlvRnnrJnELTp4eJJEOPjgpF3NOh4qeQehM6Ugiz9gJPRZ2t+TsXun6E3AMN4hScZKjqVXl0TX+C7AB3ZQ== dependencies: "@babel/runtime" "^7.20.1" - "@changesets/apply-release-plan" "^6.1.3" - "@changesets/assemble-release-plan" "^5.2.3" - "@changesets/changelog-git" "^0.1.14" - "@changesets/config" "^2.3.0" - "@changesets/errors" "^0.1.4" - "@changesets/get-dependents-graph" "^1.3.5" - "@changesets/get-release-plan" "^3.0.16" - "@changesets/git" "^2.0.0" - "@changesets/logger" "^0.0.5" - "@changesets/pre" "^1.0.14" - "@changesets/read" "^0.5.9" - "@changesets/types" "^5.2.1" - "@changesets/write" "^0.2.3" + "@changesets/apply-release-plan" "^7.0.0" + "@changesets/assemble-release-plan" "^6.0.0" + "@changesets/changelog-git" "^0.2.0" + "@changesets/config" "^3.0.0" + "@changesets/errors" "^0.2.0" + "@changesets/get-dependents-graph" "^2.0.0" + "@changesets/get-release-plan" "^4.0.0" + "@changesets/git" "^3.0.0" + "@changesets/logger" "^0.1.0" + "@changesets/pre" "^2.0.0" + "@changesets/read" "^0.6.0" + "@changesets/types" "^6.0.0" + "@changesets/write" "^0.3.0" "@manypkg/get-packages" "^1.1.3" - "@types/is-ci" "^3.0.0" - "@types/semver" "^6.0.0" + "@types/semver" "^7.5.0" ansi-colors "^4.1.3" chalk "^2.1.0" + ci-info "^3.7.0" enquirer "^2.3.0" external-editor "^3.1.0" fs-extra "^7.0.1" human-id "^1.0.2" - is-ci "^3.0.1" meow "^6.0.0" outdent "^0.5.0" p-limit "^2.2.0" preferred-pm "^3.0.0" resolve-from "^5.0.0" - semver "^5.4.1" + semver "^7.5.3" spawndamnit "^2.0.0" term-size "^2.1.0" tty-table "^4.1.5" -"@changesets/config@^2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@changesets/config/-/config-2.3.0.tgz#bff074d6492fa772cee139f9a04efa4cd56445bb" - integrity sha512-EgP/px6mhCx8QeaMAvWtRrgyxW08k/Bx2tpGT+M84jEdX37v3VKfh4Cz1BkwrYKuMV2HZKeHOh8sHvja/HcXfQ== +"@changesets/config@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@changesets/config/-/config-3.0.0.tgz#a1a1cafc77134b117b4a9266459c84fdd360a6be" + integrity sha512-o/rwLNnAo/+j9Yvw9mkBQOZySDYyOr/q+wptRLcAVGlU6djOeP9v1nlalbL9MFsobuBVQbZCTp+dIzdq+CLQUA== dependencies: - "@changesets/errors" "^0.1.4" - "@changesets/get-dependents-graph" "^1.3.5" - "@changesets/logger" "^0.0.5" - "@changesets/types" "^5.2.1" + "@changesets/errors" "^0.2.0" + "@changesets/get-dependents-graph" "^2.0.0" + "@changesets/logger" "^0.1.0" + "@changesets/types" "^6.0.0" "@manypkg/get-packages" "^1.1.3" fs-extra "^7.0.1" micromatch "^4.0.2" -"@changesets/errors@^0.1.4": - version "0.1.4" - resolved "https://registry.yarnpkg.com/@changesets/errors/-/errors-0.1.4.tgz#f79851746c43679a66b383fdff4c012f480f480d" - integrity sha512-HAcqPF7snsUJ/QzkWoKfRfXushHTu+K5KZLJWPb34s4eCZShIf8BFO3fwq6KU8+G7L5KdtN2BzQAXOSXEyiY9Q== +"@changesets/errors@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@changesets/errors/-/errors-0.2.0.tgz#3c545e802b0f053389cadcf0ed54e5636ff9026a" + integrity sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow== dependencies: extendable-error "^0.1.5" -"@changesets/get-dependents-graph@^1.3.5": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@changesets/get-dependents-graph/-/get-dependents-graph-1.3.5.tgz#f94c6672d2f9a87aa35512eea74550585ba41c21" - integrity sha512-w1eEvnWlbVDIY8mWXqWuYE9oKhvIaBhzqzo4ITSJY9hgoqQ3RoBqwlcAzg11qHxv/b8ReDWnMrpjpKrW6m1ZTA== +"@changesets/get-dependents-graph@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@changesets/get-dependents-graph/-/get-dependents-graph-2.0.0.tgz#97f0cc9fbec436e0d6ab95a6a59c08acf21ac714" + integrity sha512-cafUXponivK4vBgZ3yLu944mTvam06XEn2IZGjjKc0antpenkYANXiiE6GExV/yKdsCnE8dXVZ25yGqLYZmScA== dependencies: - "@changesets/types" "^5.2.1" + "@changesets/types" "^6.0.0" "@manypkg/get-packages" "^1.1.3" chalk "^2.1.0" fs-extra "^7.0.1" - semver "^5.4.1" + semver "^7.5.3" -"@changesets/get-release-plan@^3.0.16": - version "3.0.16" - resolved "https://registry.yarnpkg.com/@changesets/get-release-plan/-/get-release-plan-3.0.16.tgz#5d9cfc4ffda02c496ef0fde407210de8e3a0fb19" - integrity sha512-OpP9QILpBp1bY2YNIKFzwigKh7Qe9KizRsZomzLe6pK8IUo8onkAAVUD8+JRKSr8R7d4+JRuQrfSSNlEwKyPYg== +"@changesets/get-release-plan@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@changesets/get-release-plan/-/get-release-plan-4.0.0.tgz#8cb057da90a08796a335dfd18073234d33902069" + integrity sha512-9L9xCUeD/Tb6L/oKmpm8nyzsOzhdNBBbt/ZNcjynbHC07WW4E1eX8NMGC5g5SbM5z/V+MOrYsJ4lRW41GCbg3w== dependencies: "@babel/runtime" "^7.20.1" - "@changesets/assemble-release-plan" "^5.2.3" - "@changesets/config" "^2.3.0" - "@changesets/pre" "^1.0.14" - "@changesets/read" "^0.5.9" - "@changesets/types" "^5.2.1" + "@changesets/assemble-release-plan" "^6.0.0" + "@changesets/config" "^3.0.0" + "@changesets/pre" "^2.0.0" + "@changesets/read" "^0.6.0" + "@changesets/types" "^6.0.0" "@manypkg/get-packages" "^1.1.3" -"@changesets/get-version-range-type@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@changesets/get-version-range-type/-/get-version-range-type-0.3.2.tgz#8131a99035edd11aa7a44c341cbb05e668618c67" - integrity sha512-SVqwYs5pULYjYT4op21F2pVbcrca4qA/bAA3FmFXKMN7Y+HcO8sbZUTx3TAy2VXulP2FACd1aC7f2nTuqSPbqg== +"@changesets/get-version-range-type@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@changesets/get-version-range-type/-/get-version-range-type-0.4.0.tgz#429a90410eefef4368502c41c63413e291740bf5" + integrity sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ== -"@changesets/git@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@changesets/git/-/git-2.0.0.tgz#8de57649baf13a86eb669a25fa51bcad5cea517f" - integrity sha512-enUVEWbiqUTxqSnmesyJGWfzd51PY4H7mH9yUw0hPVpZBJ6tQZFMU3F3mT/t9OJ/GjyiM4770i+sehAn6ymx6A== +"@changesets/git@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@changesets/git/-/git-3.0.0.tgz#e71d003752a97bc27988db6d410e0038a4a88055" + integrity sha512-vvhnZDHe2eiBNRFHEgMiGd2CT+164dfYyrJDhwwxTVD/OW0FUD6G7+4DIx1dNwkwjHyzisxGAU96q0sVNBns0w== dependencies: "@babel/runtime" "^7.20.1" - "@changesets/errors" "^0.1.4" - "@changesets/types" "^5.2.1" + "@changesets/errors" "^0.2.0" + "@changesets/types" "^6.0.0" "@manypkg/get-packages" "^1.1.3" is-subdir "^1.1.1" micromatch "^4.0.2" spawndamnit "^2.0.0" -"@changesets/logger@^0.0.5": - version "0.0.5" - resolved "https://registry.yarnpkg.com/@changesets/logger/-/logger-0.0.5.tgz#68305dd5a643e336be16a2369cb17cdd8ed37d4c" - integrity sha512-gJyZHomu8nASHpaANzc6bkQMO9gU/ib20lqew1rVx753FOxffnCrJlGIeQVxNWCqM+o6OOleCo/ivL8UAO5iFw== +"@changesets/logger@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@changesets/logger/-/logger-0.1.0.tgz#2d2a58536c5beeeaef52ab464931d99fcf24f17b" + integrity sha512-pBrJm4CQm9VqFVwWnSqKEfsS2ESnwqwH+xR7jETxIErZcfd1u2zBSqrHbRHR7xjhSgep9x2PSKFKY//FAshA3g== dependencies: chalk "^2.1.0" -"@changesets/parse@^0.3.16": - version "0.3.16" - resolved "https://registry.yarnpkg.com/@changesets/parse/-/parse-0.3.16.tgz#f8337b70aeb476dc81745ab3294022909bc4a84a" - integrity sha512-127JKNd167ayAuBjUggZBkmDS5fIKsthnr9jr6bdnuUljroiERW7FBTDNnNVyJ4l69PzR57pk6mXQdtJyBCJKg== +"@changesets/parse@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@changesets/parse/-/parse-0.4.0.tgz#5cabbd9844b3b213cb83f5edb5768454c70dd2b4" + integrity sha512-TS/9KG2CdGXS27S+QxbZXgr8uPsP4yNJYb4BC2/NeFUj80Rni3TeD2qwWmabymxmrLo7JEsytXH1FbpKTbvivw== dependencies: - "@changesets/types" "^5.2.1" + "@changesets/types" "^6.0.0" js-yaml "^3.13.1" -"@changesets/pre@^1.0.14": - version "1.0.14" - resolved "https://registry.yarnpkg.com/@changesets/pre/-/pre-1.0.14.tgz#9df73999a4d15804da7381358d77bb37b00ddf0f" - integrity sha512-dTsHmxQWEQekHYHbg+M1mDVYFvegDh9j/kySNuDKdylwfMEevTeDouR7IfHNyVodxZXu17sXoJuf2D0vi55FHQ== +"@changesets/pre@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@changesets/pre/-/pre-2.0.0.tgz#ad3edf3d6ac287991d7ef5e26cf280d03c9e3764" + integrity sha512-HLTNYX/A4jZxc+Sq8D1AMBsv+1qD6rmmJtjsCJa/9MSRybdxh0mjbTvE6JYZQ/ZiQ0mMlDOlGPXTm9KLTU3jyw== dependencies: "@babel/runtime" "^7.20.1" - "@changesets/errors" "^0.1.4" - "@changesets/types" "^5.2.1" + "@changesets/errors" "^0.2.0" + "@changesets/types" "^6.0.0" "@manypkg/get-packages" "^1.1.3" fs-extra "^7.0.1" -"@changesets/read@^0.5.9": - version "0.5.9" - resolved "https://registry.yarnpkg.com/@changesets/read/-/read-0.5.9.tgz#a1b63a82b8e9409738d7a0f9cc39b6d7c28cbab0" - integrity sha512-T8BJ6JS6j1gfO1HFq50kU3qawYxa4NTbI/ASNVVCBTsKquy2HYwM9r7ZnzkiMe8IEObAJtUVGSrePCOxAK2haQ== +"@changesets/read@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@changesets/read/-/read-0.6.0.tgz#27e13b58d0b0eb3b0a5cba48a3f4f71f05ef4610" + integrity sha512-ZypqX8+/im1Fm98K4YcZtmLKgjs1kDQ5zHpc2U1qdtNBmZZfo/IBiG162RoP0CUF05tvp2y4IspH11PLnPxuuw== dependencies: "@babel/runtime" "^7.20.1" - "@changesets/git" "^2.0.0" - "@changesets/logger" "^0.0.5" - "@changesets/parse" "^0.3.16" - "@changesets/types" "^5.2.1" + "@changesets/git" "^3.0.0" + "@changesets/logger" "^0.1.0" + "@changesets/parse" "^0.4.0" + "@changesets/types" "^6.0.0" chalk "^2.1.0" fs-extra "^7.0.1" p-filter "^2.1.0" @@ -1822,18 +1814,18 @@ resolved "https://registry.yarnpkg.com/@changesets/types/-/types-4.1.0.tgz#fb8f7ca2324fd54954824e864f9a61a82cb78fe0" integrity sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw== -"@changesets/types@^5.2.1": - version "5.2.1" - resolved "https://registry.yarnpkg.com/@changesets/types/-/types-5.2.1.tgz#a228c48004aa8a93bce4be2d1d31527ef3bf21f6" - integrity sha512-myLfHbVOqaq9UtUKqR/nZA/OY7xFjQMdfgfqeZIBK4d0hA6pgxArvdv8M+6NUzzBsjWLOtvApv8YHr4qM+Kpfg== +"@changesets/types@^6.0.0": + version "6.0.0" + resolved "https://registry.yarnpkg.com/@changesets/types/-/types-6.0.0.tgz#e46abda9890610dd1fbe1617730173d2267544bd" + integrity sha512-b1UkfNulgKoWfqyHtzKS5fOZYSJO+77adgL7DLRDr+/7jhChN+QcHnbjiQVOz/U+Ts3PGNySq7diAItzDgugfQ== -"@changesets/write@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@changesets/write/-/write-0.2.3.tgz#baf6be8ada2a67b9aba608e251bfea4fdc40bc63" - integrity sha512-Dbamr7AIMvslKnNYsLFafaVORx4H0pvCA2MHqgtNCySMe1blImEyAEOzDmcgKAkgz4+uwoLz7demIrX+JBr/Xw== +"@changesets/write@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@changesets/write/-/write-0.3.0.tgz#c6c5bc390cce4031da20eab8a4ca2d71453a1985" + integrity sha512-slGLb21fxZVUYbyea+94uFiD6ntQW0M2hIKNznFizDhZPDgn2c/fv1UzzlW43RVzh1BEDuIqW6hzlJ1OflNmcw== dependencies: "@babel/runtime" "^7.20.1" - "@changesets/types" "^5.2.1" + "@changesets/types" "^6.0.0" fs-extra "^7.0.1" human-id "^1.0.2" prettier "^2.7.1" @@ -3609,13 +3601,6 @@ dependencies: "@types/node" "*" -"@types/is-ci@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/is-ci/-/is-ci-3.0.0.tgz#7e8910af6857601315592436f030aaa3ed9783c3" - integrity sha512-Q0Op0hdWbYd1iahB+IFNQcWXFq4O0Q5MwQP7uN0souuQ4rPg1vEYcnIOfr1gY+M+6rc8FGoRaBO1mOOvL29sEQ== - dependencies: - ci-info "^3.1.0" - "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" @@ -3790,12 +3775,7 @@ resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== -"@types/semver@^6.0.0": - version "6.2.3" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-6.2.3.tgz#5798ecf1bec94eaa64db39ee52808ec0693315aa" - integrity sha512-KQf+QAMWKMrtBMsB8/24w53tEsxllMj6TuA80TT/5igJalLI/zm0L3oXRbIAl4Ohfc85gyHX/jhMwsVkmhLU4A== - -"@types/semver@^7.3.12", "@types/semver@^7.5.6": +"@types/semver@^7.3.12", "@types/semver@^7.5.0", "@types/semver@^7.5.6": version "7.5.6" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.6.tgz#c65b2bfce1bec346582c07724e3f8c1017a20339" integrity sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A== @@ -4654,10 +4634,10 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== -ci-info@^3.1.0, ci-info@^3.2.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91" - integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw== +ci-info@^3.2.0, ci-info@^3.7.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" + integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== cjs-module-lexer@^1.0.0: version "1.2.2" @@ -6533,13 +6513,6 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-ci@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" - integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ== - dependencies: - ci-info "^3.2.0" - is-core-module@^2.11.0, is-core-module@^2.9.0: version "2.12.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.0.tgz#36ad62f6f73c8253fd6472517a12483cf03e7ec4" @@ -9492,12 +9465,12 @@ semiver@^1.1.0: resolved "https://registry.yarnpkg.com/semiver/-/semiver-1.1.0.tgz#9c97fb02c21c7ce4fcf1b73e2c7a24324bdddd5f" integrity sha512-QNI2ChmuioGC1/xjyYwyZYADILWyW6AmS1UH6gDj/SFUUUS4MBAWs/7mxnkRPc/F4iHezDP+O8t0dO8WHiEOdg== -"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@7.x, semver@^7.0.0, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.4: +semver@7.x, semver@^7.0.0, semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==