Git Product home page Git Product logo

shopify-app-js's Introduction

Shopify API and app tools for JavaScript

This repository contains packages you can use to interact with Shopify's APIs. You can use these packages to create clients for those APIs directly, or to create apps using TypeScript / JavaScript.

It is organized as a monorepo, which includes multiple packages that can be used together.

Packages

The packages in this repository can be used to extend Shopify in different ways:

These packages make it easy to interact with Shopify's APIs if you have the required access tokens.

Package Latest version Description
@shopify/admin-api-client Latest badge Client for the GraphQL and REST Admin APIs.
@shopify/storefront-api-client Latest badge Client for the GraphQL Storefront API.
@shopify/graphql-client Latest badge Generic GraphQL API client.
@shopify/api-codegen-preset Latest badge Codegen preset for Shopify APIs. Automatically integrates with the clients above.

These packages make it easy to create Shopify apps with TS / JS using different tech stacks.

Package Latest version Description
@shopify/shopify-api Latest badge Framework and runtime agnostic library for Shopify OAuth, APIs, webhooks, and more.
@shopify/shopify-app-remix Latest badge Implementation of @shopify/shopify-api to make it easy to create apps using Remix.
@shopify/shopify-app-express Latest badge Implementation of @shopify/shopify-api to make it easy to create apps using Express.

These packages provide database-specific implementations to manage @shopify/shopify-api sessions.

Package Latest version Description
@shopify/shopify-app-session-storage Latest badge Provides the interfaces used by the app middleware packages to write custom packages.
@shopify/shopify-app-session-storage-drizzle Latest badge Drizzle implementation of the session storage interface.
@shopify/shopify-app-session-storage-dynamodb Latest badge DynamoDB implementation of the session storage interface.
@shopify/shopify-app-session-storage-kv Latest badge Cloudflare KV implementation of the session storage interface.
@shopify/shopify-app-session-storage-memory Latest badge Memory implementation of the session storage interface.
@shopify/shopify-app-session-storage-mongodb Latest badge MongoDB implementation of the session storage interface.
@shopify/shopify-app-session-storage-mysql Latest badge Mysql implementation of the session storage interface.
@shopify/shopify-app-session-storage-postgresql Latest badge PostgreSQL implementation of the session storage interface.
@shopify/shopify-app-session-storage-prisma Latest badge Prisma implementation of the session storage interface.
@shopify/shopify-app-session-storage-redis Latest badge Redis implementation of the session storage interface.
@shopify/shopify-app-session-storage-sqlite Latest badge SQLite implementation of the session storage interface.

shopify-app-js's People

Contributors

andyw8 avatar byrichardpowell avatar carmelal avatar cjauvin avatar cquemin avatar dependabot[bot] avatar derom avatar doitalldev avatar github-actions[bot] avatar jakxz avatar joelvh avatar lizkenyon avatar matteodepalo avatar melissaluu avatar mkevinosullivan avatar mllemango avatar paulinakhew avatar paulomarg avatar paulspringett avatar pepicrft avatar rachel-carvalho avatar rdunk avatar rezaansyed avatar scottdixon avatar sle-c avatar surma avatar tbrenev avatar thecodepixi avatar zirkelc avatar zzooeeyy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

shopify-app-js's Issues

Error: Cannot complete OAuth process. Could not find an OAuth cookie for shop url: xxx

My code works perfectly when using an ngrok tunnel and the shopify app gets installed as expected with all the webhooks working.

However when I deploy my app on a server I get the error Cannot complete OAuth process. Could not find an OAuth cookie for shop url: xxx.

After days of debugging I found out in case of server, I am getting shopify_app_state cookie 'undefined', while in case of ngrok tunnelling of a local server, it gives me a cookie.

I tried adding shopify_app_state manually with sameSite "none" options. However none of them worked.

I had to get both offline and online auth to get a user's access_token and the user's corresponding email. The whole thing works perfectly offline.

Thanks for reading. Any help will be appreciated.

`app.get(
shopify.config.auth.path,

async function (req: any, res: any, next: any) {
try {
let options = {
secure: true,
httpOnly: false,
sameSite: "none" as CookieOptions["sameSite"],
path: "/",
};

  // const state = req.query.state;
  const state = shopify.api.auth.nonce();

  const authHandler = shopify.auth.begin();
  res.cookie("shopify_app_state", state, options);
  res.cookie("sameSite", "None");
  res.cookie("secure", "true");
  res.cookie("SameSite", "None");
  res.cookie("Secure", "true");
  console.log(req.cookies);
  authHandler(req, res, next);
} catch (error) {
  console.log(error);
}

}
);
app.get(
shopify.config.auth.callbackPath,
async function (req: any, res: any, next: any) {
const callbackHandler = shopify.auth.callback();
console.log("======cb======");
let options = {
secure: true,
httpOnly: false,
sameSite: "none" as CookieOptions["sameSite"],
path: "/",
};

const state = shopify.api.auth.nonce();

// Set cookie
res.cookie("shopify_app_state", state, options);

console.log(req.cookies);
callbackHandler(req, res, next);

},
// shopify.redirectToShopifyOrAppRoot()
async function (req: any, res: any) {
res.redirect(${shopifyOnline.config.auth.path}?shop=${req.query.shop});
}
);

app.get(
shopifyOnline.config.auth.path,

async function (req: any, res: any, next: any) {
try {
let options = {
secure: true,
httpOnly: false,
sameSite: "none" as CookieOptions["sameSite"],
path: "/",
};

  // const state = req.query.state;
  const state = shopifyOnline.api.auth.nonce();

  const authHandler = shopifyOnline.auth.begin();
  res.cookie("shopify_app_state", state, options);
  res.cookie("sameSite", "None");
  res.cookie("secure", "true");
  res.cookie("SameSite", "None");
  res.cookie("Secure", "true");
  console.log(req.cookies);
  authHandler(req, res, next);
} catch (error) {
  console.log(error);
}

}
);
app.get(
shopifyOnline.config.auth.callbackPath,
async function (req: any, res: any, next: any) {
const callbackHandler = shopifyOnline.auth.callback();
console.log("======cb======");
let options = {
secure: true,
httpOnly: false,
sameSite: "none" as CookieOptions["sameSite"],
path: "/",
};

const state = shopifyOnline.api.auth.nonce();

// Set cookie
res.cookie("shopify_app_state", state, options);

console.log(req.cookies);
callbackHandler(req, res, next);

},
shopifyOnline.redirectToShopifyOrAppRoot()
);`

Opt out of automatic webhook registration (slow if many handlers)

This library delegates registering webhooks to https://github.com/Shopify/shopify-api-js where webhooks are registered sequentially in a loop. This makes it a quite slow and blocking process if registering a lot of webhooks during which the customer has to wait for a server reply.

The obvious solution is to put this work into a background worker and turn off the automatic registration in this library. However, this is currently not possible.

Auth Callback: GraphQL query returned errors

Issue summary

Reporting here, I think the issue is related to @shopify/shopify-app-express.

After a user approves the token scopes and installs the app, the callback URL presents the error GraphQL query returned errors.

I am using the default shopifyApp config

const shopify = shopifyApp({
  api: {
    apiVersion: LATEST_API_VERSION,
    restResources,
    billing: billingConfig, // or replace with billingConfig above to enable example billing
  },
  auth: {
    path: "/api/auth",
    callbackPath: "/api/auth/callback",
  },
  webhooks: {
    path: "/api/webhooks",
  },
  // This should be replaced with your preferred storage strategy
  sessionStorage: new SQLiteSessionStorage(DB_PATH),
});
  • @shopify/shopify-app-express version: 1.1.0
  • Node version: 18.14.2
  • Operating system: MacOS
2023-04-30 12:19:55 โ”‚ backend  โ”‚ [shopify-app/INFO] Handling request to complete OAuth process
2023-04-30 12:19:55 โ”‚ backend  โ”‚ [shopify-api/INFO] Completing OAuth | {shop: X.myshopify.com}
2023-04-30 12:19:56 โ”‚ backend  โ”‚ [shopify-app/INFO] Running validateAuthenticatedSession
2023-04-30 12:19:56 โ”‚ backend  โ”‚ [shopify-api/INFO] Creating new session | {shop:X.myshopify.com, isOnline: false}
2023-04-30 12:19:56 โ”‚ backend  โ”‚ [shopify-api/INFO] Registering webhooks | {shop: X.myshopify.com}
2023-04-30 12:19:56 โ”‚ backend  โ”‚ [shopify-app/INFO] Request session has a valid access token | {shop: X.myshopify.com}
2023-04-30 12:19:56 โ”‚ backend  โ”‚ In getLocations
2023-04-30 12:19:56 โ”‚ backend  โ”‚ In getInventoryLevelsForLocations
2023-04-30 12:19:56 โ”‚ backend  โ”‚ In getInventoryItems
2023-04-30 12:19:56 โ”‚ backend  โ”‚ [shopify-app/ERROR] Failed to complete OAuth with error: Error: GraphQL query returned errors

Expected behavior

callbackPath /api/auth/callback, should redirect user to homepage.

Actual behavior

My page displays the error GraphQL query returned errors. I must redirect to admin homepage before I can see the app.

Steps to reproduce the problem

  1. Install shopify app
  2. Watch redirect
  3. See error

shopify-app-template-node/issues/1261

PostgreSQLSessionStorage throw error: relation "shopify_sessions" already exists

Issue summary

Initialize context with PostgreSQLSessionStorage throws error: relation "shopify_sessions" already exists.

Expected behavior

No error.

Actual behavior

2022-12-22 18:57:30 | backend  | error: relation "shopify_sessions" already exists
2022-12-22 18:57:30 | backend  |     at Parser.parseErrorMessage (.../node_modules/pg-protocol/dist/parser.js:287:98)
2022-12-22 18:57:30 | backend  |     at Parser.handlePacket (.../node_modules/pg-protocol/dist/parser.js:126:29)
2022-12-22 18:57:30 | backend  |     at Parser.parse (.../node_modules/pg-protocol/dist/parser.js:39:38)
2022-12-22 18:57:30 | backend  |     at Socket.<anonymous> (/Users/web/node_modules/pg-protocol/dist/index.js:11:42)
2022-12-22 18:57:30 | backend  |     at Socket.emit (node:events:513:28)
2022-12-22 18:57:30 | backend  |     at addChunk (node:internal/streams/readable:324:12)
2022-12-22 18:57:30 | backend  |     at readableAddChunk (node:internal/streams/readable:297:9)
2022-12-22 18:57:30 | backend  |     at Readable.push (node:internal/streams/readable:234:10)
2022-12-22 18:57:30 | backend  |     at TCP.onStreamRead (node:internal/stream_base_commons:190:23) {
2022-12-22 18:57:30 | backend  |   length: 110,

Steps to reproduce the problem

using

"@shopify/shopify-app-session-storage-postgresql": "^1.0.1",

init with

  sessionStorage: new PostgreSQLSessionStorage(
      `postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}`)

on the second run. it throws errors

Document what's stored for `onlineAccessInfo` in the session storages

Issue summary

If developers had custom session storage that stored all fields from a session in earlier iterations of the library (e.g., prior to v4), using the DB adapters may cause issues since they only store the associated_user.id value.

To do

Document why only id is stored (PII concerns) and highlight a warning to developers in the implementing custom storage guide of the risks of storing PII data and the obligations around it.

Webhook for discount update/delete

Hi there,

I would be really useful to have webhooks for any updates/delete of discounts in order to keep up to date with changes on Shopify side.

Thanks!

ensureInstalledOnShop crashes on invalid request

Issue summary

I setup a shopify app which is essentially the same as the example on this repo.
I can install an app, auth flow works as intended.
If I try to make a request from my browser (or postman) to localhost:3000/?shop=.myshopify.com, the middleware ensureInstalledOnShop crashes.

  • @shopify/shopify-app-* package and version: 1.0.0
  • Node version: 18.12.1
  • Operating system: macOS

Output:

[shopify-app/INFO] Running ensureInstalledOnShop
/node_modules/@shopify/shopify-api/lib/error.js:13
        var _this = _super.apply(this, tslib_1.__spreadArray([], tslib_1.__read(args), false)) || this;
InvalidRequestError: Request does not contain a host query parameter
    at InvalidRequestError.ShopifyError [as constructor] 

Expected behavior

App should not crash if an invalid request is made.
Middleware should redirect send a 400 response.

Actual behavior

App crashes if ensureInstalledOnShop middleware does not get some parameters.

Steps to reproduce the problem

  1. Used the example which is on the repo homepage
  2. Installed app on my development store
  3. Made localhost:3000/?shop=.myshopify.com request from browser

Webhook handlers are not in correct order

Issue summary

  • @shopify/shopify-app-express: v1.1.0
  • Node version: v18.9.1
  • Operating system:

Expected behavior

Call multiple APP_UNINSTALLED callbacks sequentially.

Actual behavior

The APP_UNINSTALLED callback added by shop-app-express is prior to mine.

Steps to reproduce the problem

Add one APP_UNINSTALLED callback via processWebhooks({ webhookHandlers: ... )

Possible solution

Await till my custom handlers added before adding default APP_UNINSTALLED handler

shopify-app-js/packages/shopify-app-express/src/webhooks/index.ts
image

Webhook handling seems broken

You set the express.text({type: '*/*'}), in https://github.com/Shopify/shopify-app-js/blob/main/packages/shopify-app-express/src/webhooks/index.ts#L24

However, won't most people have app.use(express.json()) in their server.ts which already parses the body as JSON? Setting express.text() after it doesn't do much if the body is already parsed (and it probably is since webhooks come in with 'content-type': 'application/json')

This leads to the following in all webhooks:

[shopify-app/ERROR] Failed to process webhook: Error: No body was received when processing webhook

Please show affected resource in GQL deprecation error

Overview/summary

Many times during development I may get deprecation notices like the one below. I'm not sure if the message is just informing me of a deprecation in the abstract or if something in my code is on the chopping block for deprecation.

In Apps > YOUR_APP > API Health the shopify admin gives me a list of exactly which properties are to be deprecated, but this is not useful for custom apps or during development.

Please include some way of getting the GQL properties which need to be updated to be compliant with the latest version of the GQL API

image

DynamoDB session store

We are planning to use DynamoDB for our application. Just creating this issue here in case others also have a need for it?

We could contribute a Dynamo DB session store package if you decide to accept community packages.

Need AWS Lambda (API Gateway Proxy Event) adapter

Overview

When implementing ShopifyJS in AWS Lambda Node runtimes, the existing node adapter is problematic because of native IncomingMessage and ServerResponse.

interface NodeAdapterArgs extends AdapterArgs {
  rawRequest: IncomingMessage;
  rawResponse: ServerResponse;
}

Having a separate adapter for AWS API Gateway Proxy event for AWS Lambda which maps the NormalizedRequest and NormalizedResponse to APIGatewayProxyEventV2 and APIGatewayProxyStructuredResultV2.

interface LambdaAPIGatewayArgs extends AdapterArgs {
  rawRequest: APIGatewayProxyEventV2;
  rawResponse: APIGatewayProxyStructuredResultV2;
}

Here, considering only the AWS API Gateway as the lambda trigger, since typically the auth endpoints are available as REST only such as /auth etc.

GraphQL filter orders by tag returns no results

Issue summary

When i make a GraphQL query to retrieve orders with a query filter on tag, it returns no results. Using the same query in the Shopify GraphQL app in the store returns results. Removing the query:"tag:Ship" from query results in orders being returned.

My code was adapted from the QR Code example app - https://shopify.dev/docs/apps/getting-started/build-app-example
My code adds a new endpoint middleware to express and makes a simple graphql query -

import express from "express";
import shopify from "../shopify.js";

const ORDERS_QUERY = `
query {
  orders(first:100, query:"tag:Ship") {
    edges {
      node {
        id
        name
        tags
      }
    }
  }
}
  `;

export default function applyOrdersApiEndpoints(app) {
  app.use(express.json());

  app.get("/api/weekorders", async (req, res) => {

    const client = new shopify.api.clients.Graphql({
      session: res.locals.shopify.session,
    });

    const orders = await client.query({
      data: {
        query: ORDERS_QUERY
      },
    });

    res.send(orders.body.data);
  });
}

app setup in shopify.js (unchanged from QR Code example app):

import { LATEST_API_VERSION } from "@shopify/shopify-api";
import { shopifyApp } from "@shopify/shopify-app-express";
import { LogSeverity } from "@shopify/shopify-api"
import { SQLiteSessionStorage } from "@shopify/shopify-app-session-storage-sqlite";
import { restResources } from "@shopify/shopify-api/rest/admin/2023-01";
import sqlite3 from "sqlite3";
import { join } from "path";
import { QRCodesDB } from "./qr-codes-db.js";

const database = new sqlite3.Database(join(process.cwd(), "database.sqlite"));
// Initialize SQLite DB
QRCodesDB.db = database;
QRCodesDB.init();
const shopify = shopifyApp({
  api: {
    apiVersion: LATEST_API_VERSION,
    restResources,
    billing: undefined, // or replace with billingConfig above to enable example billing
    logger: {
      level: LogSeverity.Debug,
      httpRequests: true, // if the error seems to be related to requests
    }
  },
  auth: {
    path: "/api/auth",
    callbackPath: "/api/auth/callback",
  },
  webhooks: {
    path: "/api/webhooks",
  },
  sessionStorage: new SQLiteSessionStorage(database),
});

export default shopify;
  • @shopify/shopify-app-* package and version:
"@shopify/shopify-app-express": "^1.1.0",
"@shopify/shopify-app-session-storage-sqlite": "^1.0.0",
  • Node version: v18.15.0
  • Operating system: Ubuntu 5.10.16.3-microsoft-standard-WSL2
023-03-17 23:28:31 | backend  | [shopify-api/INFO] version 6.2.0, environment Node v18.15.0
2023-03-17 23:28:48 | backend  | [shopify-app/INFO] Running ensureInstalledOnShop
2023-03-17 23:28:48 | backend  | [shopify-app/DEBUG] Checking if shop has installed the app | {shop: anna-cake-couture.myshopify.com}
2023-03-17 23:28:48 | backend  | [shopify-app/INFO] App is installed and ready to load | {shop: anna-cake-couture.myshopify.com}
2023-03-17 23:28:52 | backend  | [shopify-app/INFO] Running validateAuthenticatedSession
2023-03-17 23:28:52 | backend  | [shopify-api/DEBUG] App is embedded, looking for session id in JWT payload | {isOnline: false}
2023-03-17 23:28:52 | backend  | [shopify-api/DEBUG] Found valid JWT payload | {shop: anna-cake-couture.myshopify.com, isOnline: false}
2023-03-17 23:28:52 | backend  | [shopify-app/DEBUG] Request session found and loaded | {shop: anna-cake-couture.myshopify.com}
2023-03-17 23:28:52 | backend  | [shopify-app/DEBUG] Request session exists and is active | {shop: anna-cake-couture.myshopify.com}
2023-03-17 23:28:52 | backend  | [shopify-api/DEBUG] Making HTTP request  -  POST https://anna-cake-couture.myshopify.com/admin/api/2023-01/graphql.json  -  Headers:      
                                 {"X-Shopify-Access-Token":["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"],"User-Agent":["Shopify Express Library v1.2.2 | Shopify API Library v6.2.0 | Node 
                                 v18.15.0"],"Content-Type":["application/graphql"],"Content-Length":["26"]}  -  Body: "\n{\n  shop {\n    name\n  }\n}"
2023-03-17 23:28:53 | backend  | [shopify-api/DEBUG] Completed HTTP request, received 200 OK
2023-03-17 23:28:53 | backend  | [shopify-app/INFO] Request session has a valid access token | {shop: anna-cake-couture.myshopify.com}
2023-03-17 23:28:53 | backend  | [shopify-api/DEBUG] Making HTTP request  -  POST https://anna-cake-couture.myshopify.com/admin/api/2023-01/graphql.json  -  Headers:      
                                 {"X-Shopify-Access-Token":["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"],"User-Agent":["Shopify Express Library v1.2.2 | Shopify API Library v6.2.0 | Node 
                                 v18.15.0"],"Content-Type":["application/json"],"Content-Length":["159"]}  -  Body: "{\"query\":\"\\nquery {\\n  orders(first:100, query:\\\"tag:Ship\\\") {\\n    edges 
                                 {\\n      node {\\n        id\\n        name\\n        tags\\n      }\\n    }\\n  }\\n}\\n  \"}"
2023-03-17 23:28:53 | backend  | [shopify-api/DEBUG] Completed HTTP request, received 200 OK

Expected behavior

The GraphQL query should return orders tagged with 'Ship'

Actual behavior

No results are returned

Steps to reproduce the problem

Run the query above using the GraphQL client as shown above

Enhancement Express middleware default options on processWebhooks

Overview/summary

ProcessWebhooks method on @shopify/shopify-app-express does not allow users to change the express middleware default options, such as limit parameters. This can be limiting for some use cases Eg. big webhook payload, and I think it would be beneficial to allow users to customize it without to rewrite the whole function.

export function processWebhooks({
  api,
  config,
}: ApiAndConfigParams): ProcessWebhooksMiddleware {
  return function ({webhookHandlers}: ProcessWebhooksMiddlewareParams) {
    mountWebhooks(api, config, webhookHandlers);

    return [
      express.text({type: '*/*'}),
      async (req: Request, res: Response) => {
        await process({
          req,
          res,
          api,
          config,
        });
      },
    ];
  };
}

Eg. i would like to change

 express.text({ type: '*/*', limit: '1MB' }),

`ensureInstalledOnShop` did not receive a shop query argument

Issue summary

During moderation in the Appstore, I have already received a reject several times. There are errors in the moderator's account when changing the subscription plan. I see in the production logs ensureInstalledOnShop did not receive a shop query argument. I can't figure out why this is happening. There is no such error on my dev/prod accounts, including test staff accounts.

How can more data be collected when this happens? I thought the issue would be solved after closing #94, but these errors are again...

Packages:

Logs:

[shopify-app/ERROR] ensureInstalledOnShop did not receive a shop query argument | {shop: undefined}
[shopify-app/INFO] Running ensureInstalledOnShop
[shopify-app/ERROR] ensureInstalledOnShop did not receive a shop query argument | {shop: undefined}
[shopify-app/ERROR] ensureInstalledOnShop did not receive a shop query argument | {shop: undefined}
[shopify-app/INFO] Running ensureInstalledOnShop
....

Shopify.ts

export const shopify = shopifyApp({
    api: {
        apiKey: process.env.SHOPIFY_API_KEY!,
        apiSecretKey: process.env.SHOPIFY_API_SECRET!,
        scopes: process.env.SCOPES!.split(','),
        hostName: process.env.HOST!.replace(/https?:\/\//, ''),
        hostScheme: 'https',
        apiVersion: LATEST_API_VERSION,
        isEmbeddedApp: true,
        logger: {
            level: LogSeverity.Debug,
        },
        ...(process.env.SHOP_CUSTOM_DOMAIN && {
            customShopDomains: [process.env.SHOP_CUSTOM_DOMAIN],
        }),
        billing: {
            [BillingPlan.Pro]: {
                interval: BillingInterval.Every30Days,
                amount: BillingPlans.Pro.price,
                currencyCode: 'USD',
                replacementBehavior: BillingReplacementBehavior.ApplyImmediately,
                trialDays: 3,
            },
        },
    },
    webhooks: {
        path: '/api/webhooks',
    },
    auth: {
        path: '/api/auth',
        callbackPath: '/api/auth/callback',
    },
    sessionStorage: new PostgreSQLSessionStorage(new URL(process.env.POSTGRES_URL!), {
        sessionTableName: 'shopify_sessions',
    }),
    useOnlineTokens: true,
});

App.ts

export async function createServer(isProd = process.env.NODE_ENV === 'production') {
    const app = express();

    // CSP x-frame header
    app.use(async (req, res, next) => {
        const shop = shopify.api.utils.sanitizeShop(req.query.shop as string);

        res.setHeader(
            'Content-Security-Policy',
            `frame-ancestors https://admin.shopify.com ${shop ? 'https://' + encodeURIComponent(shop) : ''}`,
        );
        next();
    });

    app.get(shopify.config.auth.path, shopify.auth.begin());
    app.get(
        shopify.config.auth.callbackPath,
        shopify.auth.callback(),
        async (req, res, next) => {
            const session = res.locals.shopify.session;
            await saveShopData(session);
            initProductWidget(session.shop);
            next();
        },
        shopify.redirectToShopifyOrAppRoot(),
    );

    app.post(shopify.config.webhooks.path, shopify.processWebhooks({ webhookHandlers }));

    // All endpoints after this point will require an active session
    app.use('/api/*', shopify.validateAuthenticatedSession());

    // All endpoints after this point will have access to a request.body
    // attribute, as a result of the express.json() middleware
    app.use(express.json());
    app.use(urlencoded({ extended: true }));

    app.post('/api/graphql', async (req: Request, res) => {
        try {
            await shopify.api.clients
                .graphqlProxy({
                    session: res.locals.shopify.session as Session,
                    rawBody: req.body,
                })
                .then(data => res.send(data.body))
                .catch(e => res.status(400).send({ errors: e?.response?.errors }));
        } catch (e) {
            console.log(e);
        }
    });

    // controllers...
    app.use('/api/products', ProductsController);
    app.use('/api/files', FilesController);
    app.use('/api/widgets', WidgetsController);

    if (isProd) {
        const compression = await import('compression').then(({ default: fn }) => fn);
        const serveStatic = await import('serve-static').then(({ default: fn }) => fn);
        app.use(compression());
        app.use(serveStatic(PROD_INDEX_PATH, { index: false }));
    }

    app.use('/*', shopify.ensureInstalledOnShop(), async (req, res, next) => {
        const htmlFile = join(isProd ? PROD_INDEX_PATH : DEV_INDEX_PATH, 'index.html');

        return res.status(200).set('Content-Type', 'text/html').send(readFileSync(htmlFile));
    });

    return { app };
}

sameSite: Lax is failing to set the cookie

Issue summary

Because sameSite: lax, the cookies are not being set by the oauth being call.

Expected behavior

It should set the cookies and authenticate the embedded app.

We manually changed these lines in oauth.js into sameSite: "none" and it works, but being a change done in node_modules it will not work long term.

Is there any way to set the sameSite policy or another way to solve this issue?

MySQL session storage v1.1.3 throws TypeError

Issue summary

I updated the @shopify/shopify-app-session-storage-mysql package from 1.0.2 to 1.1.3.
Here is the relevant snippet from web/shopify.js

const DB_PATH = process.env.DB_PATH // 'mysql://[email protected]/dname'
const shopify = shopifyApp({
	sessionStorage: new MySQLSessionStorage(DB_PATH),
	...
});

It works fine with version 1.0.2 but on 1.13 it throws the following exception:

[shopify-api/INFO] version 7.0.0, environment Node v18.11.0
/tamir/app/web/node_modules/@shopify/shopify-app-session-storage-mysql/build/cjs/mysql-connection.js:71
    return decodeURIComponent(this.dbUrl.pathname.slice(1));
                                                  ^
TypeError: Cannot read properties of undefined (reading 'slice')
    at MySqlConnection.getDatabase 
                                 (/tamir/app/web/node_modules/@shopify/shopify-app-session-storage-mysql/build/cjs/mysql-connection.js:71:51)
    at MySqlConnection.init 
                                 (/tamir/app/web/node_modules/@shopify/shopify-app-session-storage-mysql/build/cjs/mysql-connection.js:95:22)
    at new MySqlConnection 
                                 (/tamir/app/web/node_modules/@shopify/shopify-app-session-storage-mysql/build/cjs/mysql-connection.js:20:23)
    at MySQLSessionStorage.init 
                                 (/tamir/app/web/node_modules/@shopify/shopify-app-session-storage-mysql/build/cjs/mysql.js:95:23)
    at new MySQLSessionStorage 
                                 (/tamir/app/web/node_modules/@shopify/shopify-app-session-storage-mysql/build/cjs/mysql.js:32:30)
    at file:///tamir/app/web/shopify.js:38:18
    at ModuleJob.run (node:internal/modules/esm/module_job:193:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:530:24)
    at async loadESM (node:internal/process/esm_loader:91:5)
Node.js v18.11.0
[nodemon] app crashed - waiting for file changes before starting...
  • @shopify/shopify-app-* package and version: 7.0.0
  • Node version: 18.11.0
  • Operating system: MacOS

Expected behavior

The package should successfully connect to the database

Actual behavior

The package throws a TypeError

Steps to reproduce the problem

  1. Create an app and install the MySQL adapter v1.1.3
  2. Try to connect to your local database with a connection string

MySQL Session Storage: Too many connections -> Connection via Pool "MySQL2"

Overview/summary

In principle, session storage via Mysql works cleanly and well.
However, with a large number of requests, the database complains that there are too many connections. Which leads to an error in the long run.

Would it be possible to control the connection to the database via Mysql2 pool?
And here with the pool also the connection limit deposited?
mysql.createPool(connectionOptions)
In principle, the Mysql2 already offers everything necessary for this.

const shopify = shopifyApp({
    api: apiKey: process.env.SHOPIFY_API_KEY,
    apiSecretKey: process.env.SHOPIFY_API_SECRET,
    apiVersion: process.env.API_VERSION,
    restResources,
    billing: billingConfigSubscriptionPayment, // or replace with billingConfig above to enable example billing
    isEmbeddedApp: true,
    logger: {
        level: LogSeverity.Error,
    },
    auth: {
        path: "/api/auth",
        callbackPath: "/api/auth/callback",
    },
    webhooks: {
        path: "/api/webhooks",
    },
    useOnlineTokens: false, // declare offline session token
    sessionStorage: MySQLSessionStorage.withCredentials(
        `${process.env.MYSQL_HOST}`,
        `${process.env.MYSQL_DATABASE}`,
        `${process.env.MYSQL_USER}`,
        `${process.env.MYSQL_PASSWORD}`
    ),
});

Tasks

No tasks being tracked yet.

shop is getting undefined when redirecting to /api/auth?shop=undefined

Issue summary

when session verification fails then it redirects to /api/auth and passes shop in query parameters but here shop is undefined
/api/auth?shop=undefined

it doesn't happen on development server but it happens on prod server
followed official doc for fly.io deployment but this error exist

  • @shopify/shopify-app-* package and version:
  • Node version: v18.12.1
  • Operating system: don't know (fly.io server)

Expected behavior

it should redirect to /api/auth?shop=mystore.myshopify.com

Actual behavior

/api/auth?shop=undefined

Steps to reproduce the problem

  1. clone this repo https://github.com/yashsony/deploymenttesting
  2. and deploy it on fly.io through cli link

Duplicate entry 'migrateScopeFieldToVarchar1024'

Issue summary

In version 1.1.1 of @shopify/shopify-app-session-storage-mysql, there is an error stating: "Duplicate entry 'migrateScopeFieldToVarchar1024' for key 'shopify_sessions_migrations.PRIMARY'".

  • @shopify/shopify-api version: 6.2.0
  • Node version: 18.11.0
  • Operating system: MacOS
2023-02-26 08:43:17 | backend  | [shopify-api/INFO] version 6.2.0, environment Node v18.11.0
2023-02-26 08:43:18 | backend  | /Users/tamir/workspace-server/Custom bamba print/web/node_modules/mysql2/promise.js:93
2023-02-26 08:43:18 | backend  |     const localErr = new Error();
2023-02-26 08:43:18 | backend  |                      ^
2023-02-26 08:43:18 | backend  |
2023-02-26 08:43:18 | backend  | Error: Duplicate entry 'migrateScopeFieldToVarchar1024' for key 'shopify_sessions_migrations.PRIMARY'
2023-02-26 08:43:18 | backend  |     at PromiseConnection.query (/Users/tamir/workspace-server/Custom bamba 
                                 print/web/node_modules/mysql2/promise.js:93:22)
2023-02-26 08:43:18 | backend  |     at MySqlConnection.query (/Users/tamir/workspace-server/Custom bamba 
                                 print/web/node_modules/@shopify/shopify-app-session-storage-mysql/build/cjs/mysql-connection.js:21:28)
2023-02-26 08:43:18 | backend  |     at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
2023-02-26 08:43:18 | backend  |     at async MySqlSessionStorageMigrator.saveAppliedMigration (/Users/tamir/workspace-server/Custom 
                                 bamba print/web/node_modules/@shopify/shopify-app-session-storage/build/cjs/rdbms-session-storage-migrat
                                 or.js:31:5)
2023-02-26 08:43:18 | backend  |     at async MySqlSessionStorageMigrator.applyMigrations (/Users/tamir/workspace-server/Custom bamba 
                                 print/web/node_modules/@shopify/shopify-app-session-storage/build/cjs/abstract-migration-engine.js:28:9)
                                  {
2023-02-26 08:43:18 | backend  |   code: 'ER_DUP_ENTRY',
2023-02-26 08:43:18 | backend  |   errno: 1062,
2023-02-26 08:43:18 | backend  |   sql: '\n' +
2023-02-26 08:43:18 | backend  |     '          INSERT INTO shopify_sessions_migrations (migration_name)\n' +
2023-02-26 08:43:18 | backend  |     "          VALUES('migrateScopeFieldToVarchar1024');\n" +
2023-02-26 08:43:18 | backend  |     '        ',
2023-02-26 08:43:18 | backend  |   sqlState: '23000',
2023-02-26 08:43:18 | backend  |   sqlMessage: "Duplicate entry 'migrateScopeFieldToVarchar1024' for key 
                                 'shopify_sessions_migrations.PRIMARY'"
2023-02-26 08:43:18 | backend  | }
2023-02-26 08:43:18 | backend  |
2023-02-26 08:43:18 | backend  | Node.js v18.11.0
2023-02-26 08:43:18 | backend  | [nodemon] app crashed - waiting for file changes before starting...

Expected behavior

There should be no error thrown if the migration already exists.

Actual behavior

The "Duplicate entry" error is thrown.
Version 1.0.2 has no errors.

Steps to reproduce the problem

  1. Create a new shopify/cli node app template
  2. Install and connect the shopify-app-session-storage-mysql adapter
  3. Run your dev server once and install the app on your dev store
  4. Close the dev server and try to run it again

improve the document description

Overview

image please tell me how to locate the billing code? Sorry, I want to test the billing function, but I cannot find the **ensueBilling** API in the latest version, which is exposed in v5. and I want to know where this API is currently located, and if further please explain why make such a significant version change? thank you

Duplicate entry 'migrateScopeFieldToVarchar1024' for key 'PRIMARY'"

ssion-storage/build/cjs/rdbms-session-storage-migrator.js:31:5)
2023-02-23 19:29:36 | backend | at async MySqlSessionStorageMigrator.applyMigrations
(/Users/metehankasap/Desktop/wishlist-app-newzi/web/node_modules/@shopify/shopify-app-session-storage/build/cjs/abstract-migration-engine.js:28:9) {
2023-02-23 19:29:36 | backend | code: 'ER_DUP_ENTRY',
2023-02-23 19:29:36 | backend | errno: 1062,
2023-02-23 19:29:36 | backend | sql: '\n' +
2023-02-23 19:29:36 | backend | ' INSERT INTO shopify_sessions_migrations (migration_name)\n' +
2023-02-23 19:29:36 | backend | " VALUES('migrateScopeFieldToVarchar1024');\n" +
2023-02-23 19:29:36 | backend | ' ',
2023-02-23 19:29:36 | backend | sqlState: '23000',
2023-02-23 19:29:36 | backend | sqlMessage: "Duplicate entry 'migrateScopeFieldToVarchar1024' for key 'PRIMARY'"
2023-02-23 19:29:36 | backend | }
2023-02-23 19:29:36 | backend |

TypeError: scopesArray.map is not a function

Issue summary

In this shopify.js file of the CLI 3 template, I tried using the MongoDBSessionStorage session storage but I was getting an error:

/Users//projects/buyonecolective-main/node_modules/@shopify/shopify-api/lib/auth/scopes/index.js:18
2023-01-12 17:52:09 | backend | .map(function (scope) { return scope.trim(); })
2023-01-12 17:52:09 | backend | ^
2023-01-12 17:52:09 | backend |
2023-01-12 17:52:09 | backend | TypeError: scopesArray.map is not a function
2023-01-12 17:52:09 | backend | at new AuthScopes (/Users/
/projects/-main/node_modules/@shopify/shopify-api/lib/auth/scopes/index.js:18:14)
2023-01-12 17:52:09 | backend | at Session.isActive (/Users/
/projects/-main/node_modules/@shopify/shopify-api/lib/session/session.js:80:77)
2023-01-12 17:52:09 | backend | at /Users/
/projects/*-main/web/node_modules/@shopify/shopify-app-express/build/cjs/middlewares/validate-authenticated-session.js:57:79
2023-01-12 17:52:09 | backend | at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
2023-01-12 17:52:09 | backend |
2023-01-12 17:52:09 | backend | Node.js v18.12.1
2023-01-12 17:52:09 | backend | [nodemon] app crashed - waiting for file changes before starting...
2023-01-12 17:52:09 | frontend | 18:52:09 [vite] http proxy error:

  • @shopify/shopify-app-* package and version:
  • Node version:
    18
  • Operating system:
    Mac OS

Expected behavior

shopify.validateAuthenticatedSession() should not break the app.

Actual behavior

App crashes when calling shopify.validateAuthenticatedSession()

Issue is most likely from the MongoDBSessionStorage class. I tweaked it a bit and it worked for me. The way it returns the Session in the loadSession method does not seem right.

PostgresSessionStorage crashes app due to idle connection

Issue summary

Using the PostgresSessionStorage crashes app due to idle connection several minutes after the app starts up.

[https://github.com/Shopify/shopify-app-js/blob/244464344495d41b9db5ff407f213a7de7fe7eb0/packages/shopify-app-session-storage-postgresql/src/postgresql.ts](PostgreSQL Session Storage code)

I'm using shopify-api-js 4.2.0 in my app which has very similar code for the PostgresSessionStorage. To get around this problem I copied the code into my project and modified it so that PostgresSessionStorage uses a pg.Pool instead of pg.client instance variable. Every time we want to make a query I request a client from the pool and the release the client when the query has completed. After this change, my app is no longer crashing several minutes after startup.

Expected behavior

Using PostgresSessionStorage shouldn't crash the app.

Actual behavior

The app will crash with the error message "connection terminated unexpectedly" a couple minutes after app startup when using PostgresSessionStorage.

Steps to reproduce the problem

  1. Launch a Shopify app using PostgresSessionStorage.
  2. Wait a couple of minutes.

CSP Headers documentation

Please add documentation about usage shopify.cspHeaders() middleware. This may be relevant upon migration to a new Admin URL (admin.shopify.com...).

Webhook URL doesn't get updated automatically when Ngrok URL changes

Issue summary

During development, the Ngrok URL might change from time to time.

We mostly handle it gracefully - In the CLI, developers can choose to have the URL updated automatically, and the CLI takes care of updating the allowed URLs in Partners. In other words, things just work ๐ŸŽ‰

There is one exception - webhooks.
When the Ngrok changes, we don't update the webhook URL, which results in webhook delivery failure.

Once a developer figures out the source of the problem, they can hack their way around to update the webhook URL (the easiest way is to call shopify.api.webhooks.register({session: res.locals.shopify.session}) in the context of a response).

There's also a post on the Community Forum describing this problem.

Expected behavior

Things should just work ๐ŸŽ‰
If the Ngrok URL changes, the webhook URL should be updated transparently under the hood.

Actual behavior

Webhook delivery fails start failing as soon as the Ngrok URL changes.

Steps to reproduce the problem

I'm not sure how to trigger a Ngrok URL change, but if you wait for long enough, it will happen on its own.

How to respond back to Shopify webhook notifications

I'm trying to follow the steps on the documentation and examples provided. One thing is escaping me, and I can't find it anywhere. I know that you need to respond back to Shopify with a success code for the hook call to be considered delivered. However in the examples from the Shopify CLI boilerplate I don't see how I can access the res method from Express to end the callback function with either a success or failure code.

I'm likely missing something here, any chance I could get some pointers?

leaky bucket rate limiting does not keep order of calls

Overview/summary

Shopify API rate limiting is based on leaky bucket. However library implementation relies on very naive โ€˜retryโ€™ strategy without caring about leaky bucket and requests queuing so in edge cases it can end up with serious problem, even requests that will never be called. Two cases described below :
Here is most important part of https://github.com/Shopify/shopify-node-api/blob/main/src/clients/http_client/http_client.ts#L128-L141

  • RestAPI
    As leaky bucket is built based on number of request this is almost ok implementation but only โ€˜almostโ€™. As there is no queuing just โ€˜setTimeoutโ€™ it is still possible that if request A fails and is waiting till for retry there is another request B that will be made just before retry of A and therefore retry of B will fail again, in edge case it can be never called. Solution is simple : there should be queue of requests, when request fails because of rate limit then it is added at the end of queue of waiting requests, and library then makes request one by one from queue.

  • GraphQL
    Actual implementation ignores leaky bucket mechanism and, if i didn't miss anything just retries after one second. Solution would be as above queue but this time bases on the throttle status returned by graphQL API.

Motivation

What inspired this enhancement?

...

Area

  • Add any relevant Area: <area> labels to this issue

Checklist

  • I have described this enhancement in a way that is actionable (if possible)

scope column with 255 chars is not enough

When having a few unauthenticated_* scopes the 255 limit is not enough. and cause the app install to fail

  • @shopify/shopify-app-* package and version: 1.0.1

Expected behavior

no crash

Actual behavior

crash when trying to save the scope string into the DB

Steps to reproduce the problem

  1. add a few scopes unauthenticated_ to the App
  2. install the app
  3. crash when trying to save the scope string into the DB

MySQL Storage: Password with special character will always access denied

Issue summary

I would like to raise the issue regarding password input, currently when we use complex password like sql123#@~ some of the characters will not properly handled or decoded during the login process of the mysql2 package (code reference to that package there's no trace of decodeURIComponent for the password field) and it will always result to access denied.

We do the encodeURIComponent for the password on this line of code in src/mysql.ts#L33. Right now, my workaround is to provide a password without special characters but I know this is not the best idea for production use.

That being said, I would like to request if we can have an option for the mysql connection credential to provide it via object instead of URL string only, just like the main package mysql2 provided option for mysql.createConnection({...})

  • @shopify/[email protected] package and version:
  • Node version: v16.19.1
  • Operating system: MacOS M1
MySQL error: Access denied for [email protected]

Steps to reproduce the problem

  1. Use password content with character # or any special character. (For my case I used #)
  2. Try to connect using any of the provided steps by MySQLSessionStorage class, by class argument url string or using withCredentials

Improve RedisSessionStorage for Heroku-hosted apps

Necessary Redis 6 + Heroku workaround isn't supported, results in app crashing on disconnect

For Redis 6 with Heroku apps, if certain event handlers aren't defined before connecting, the app will crash on disconnect. Heroku Redis instances disconnect/reconnect every 5 minutes so this results in a very unreliable server.

Here is a sample error log for this issue

Error: Socket closed unexpectedly
  File "/app/node_modules/@redis/client/dist/lib/client/socket.js", line 195, in TLSSocket.<anonymous>
    '{snip} lassPrivateFieldGet(this, _RedisSocket_instances, "m", _RedisSocket_onSocketError).call(this, new errors_1.SocketClosedUnexpectedlyError());
  File "node:events", line 628, in Object.onceWrapper
  File "node:events", line 525, in TLSSocket.emit
  File "node:domain", line 489, in TLSSocket.emit
  File "node:net", line 322, in <anonymous>

To prevent this behavior the known solution is to simply define these event handlers, even if no action is taken in the callback

- 'error'
- 'connect'
- 'reconnecting'
- 'ready'

Here is an issue on the node-redis repo discussing implementation details.

I updated and built the package locally to fix an app that is running in production but would be great it there was a built in error handler on the class that we could pass a callback to. Happy to submit PR so we can use the public package going forward if you think this is worth implementing.

PostgreSQL session storage intermittent fail

Issue summary

Shopify App running on Fly.io restarts due to PostgreSQL connection issue. There is no clear error log (attached below). This issue is happening randomly in all the environments I have.

I configured LogSeverity.Debug and httpRequests: true but log trace is the same. Is there any other way to catch the error?

  • @shopify/shopify-app-express : 1.1.0
  • @shopify/shopify-app-session-storage-postgresql : 1.1.0
  • Node version: 18.15.0
  • Operating system: Alpine Linux v3.17.2 running on docker (node:18-alpine)
2023-03-21T09:59:54.353 app[f7856329] mad [info] node:events:491

2023-03-21T09:59:54.353 app[f7856329] mad [info] throw er; // Unhandled 'error' event

2023-03-21T09:59:54.353 app[f7856329] mad [info] ^

2023-03-21T09:59:54.353 app[f7856329] mad [info] Error: Connection terminated unexpectedly

2023-03-21T09:59:54.353 app[f7856329] mad [info] at Connection.<anonymous> (/app/node_modules/pg/lib/client.js:131:73)

2023-03-21T09:59:54.353 app[f7856329] mad [info] at Object.onceWrapper (node:events:627:28)

2023-03-21T09:59:54.353 app[f7856329] mad [info] at Connection.emit (node:events:513:28)

2023-03-21T09:59:54.353 app[f7856329] mad [info] at Socket.<anonymous> (/app/node_modules/pg/lib/connection.js:112:12)

2023-03-21T09:59:54.353 app[f7856329] mad [info] at Socket.emit (node:events:525:35)

2023-03-21T09:59:54.353 app[f7856329] mad [info] at endReadableNT (node:internal/streams/readable:1359:12)

2023-03-21T09:59:54.353 app[f7856329] mad [info] at process.processTicksAndRejections (node:internal/process/task_queues:82:21)

2023-03-21T09:59:54.353 app[f7856329] mad [info] Emitted 'error' event on Client instance at:

2023-03-21T09:59:54.353 app[f7856329] mad [info] at Client._handleErrorEvent (/app/node_modules/pg/lib/client.js:330:10)

2023-03-21T09:59:54.353 app[f7856329] mad [info] at Connection.<anonymous> (/app/node_modules/pg/lib/client.js:148:16)

2023-03-21T09:59:54.354 app[f7856329] mad [info] at Object.onceWrapper (node:events:627:28)

2023-03-21T09:59:54.354 app[f7856329] mad [info] [... lines matching original stack trace ...]

2023-03-21T09:59:54.354 app[f7856329] mad [info] at process.processTicksAndRejections (node:internal/process/task_queues:82:21)

Expected behavior

The application should not close de connection unexpectedly

Actual behavior

DB connection is closed unexpectedly

Steps to reproduce the problem

  1. Configure PostgreSQL as session storage DB
export const shopify = shopifyApp({
    api: {
        apiVersion: LATEST_API_VERSION,
        restResources,
        billing: undefined,
        logger: {
            level: LogSeverity.Debug,
            httpRequests: true,
        },
    },
    auth: {
        path: '/api/auth',
        callbackPath: '/api/auth/callback',
    },
    webhooks: {
        path: '/api/webhooks',
    },
    sessionStorage: new PostgreSQLSessionStorage(DB_STRING),
})
  1. Run the app as a Fly.io app
  2. Wait for the error to appear

Webhook configuration isn't validating correctly

Issue summary

After configuring a webhook using shopify-app-express package (as it's configured by default in Shopify Nodejs template), when firing an event, the webhook cannot be validated

-->

  • @shopify/shopify-app-* package and version: @shopify/[email protected]
  • Node version: v18.12.1
  • Operating system: Ubuntu 22.04.1 LTS running on Windows 11 WSL2
[shopify-api/INFO] Processing webhook request | {apiVersion: 2023-01, domain: supernormal-dev.myshopify.com, topic: checkouts/update, webhookId: 
                                 a7762489-908c-4547-85a8-b8c098bfa5a7}
2023-01-24 22:05:16 | backend  | [shopify-app/ERROR] Failed to process webhook: Error: Could not validate request for topic checkouts/update

Expected behavior

When firing an event related with the webhook configured, I should receive the payload

Actual behavior

Getting a could not validate request error

Steps to reproduce the problem

  1. Configure an express post call for webhooks

app.post( shopify.config.webhooks.path, shopify.processWebhooks({ webhookHandlers: webhookHandlerIndex }) );

  1. Configure the handler as documented by Shopify GDPR examples

`import { DeliveryMethod } from "@shopify/shopify-api";

export const webhookHandlerIndex = () => {

return ({
    CHECKOUTS_UPDATE: {
        deliveryMethod: DeliveryMethod.Http,
        callbackUrl: "/api/webhooks",
        callback: async (topic, shop, body, webhookId) => {
            const payload = JSON.parse(body);
            console.log(payload)
        }
    },
})

}
`

redirectToShopifyOrAppRoot() types

Overview/summary

Exptected Request, Response and NextFunction types in redirectToShopifyOrAppRoot() middleware interface in the package @shopify/shopify-app-express.

Q: How to handle InvalidJwtError, InvalidSessionError using shopify.validateAuthenticatedSession()?

Hi all,

"@shopify/app": "3.25.0",
"@shopify/cli": "3.25.0",
"@shopify/shopify-app-express": "^1.0.0",

Regarding last versions, would like to get into the idea of what was before now's wrapped into shopify, how do you guys handle InvalidJwtError on session?

// All endpoints after this point will require an active session
app.use("/api/*", shopify.validateAuthenticatedSession());

If the session is corrupted the server goes down, it means we've got to get back old methods of handling errors, instances of ShopifyErrors.

Any advice on how to do it?

Do I have to open the middleware function and super call this with params req, res? Is this really a solution to an integrated session handling verification?

ps. custom express error hadling doesn't work, it crashes on shopify.validateAuthenticatedSession()

Thanks,

image

Using separate schema in PostgreSQLSessionStorage

Overview/summary

I keep my tables in separate schemas for different projects. When I tried using PostgreSQLSessionStorage with a Database URL that specifies the schema, the schema was ignored, and the tables are created in the default schema public. I checked to see if schema could be specified as an option, but it seems thats not the case, please let me know if I missed something.

It would be a great enhancement to have the session storage recognize the schema. My Database URL looks like the following ...

postgres://user:pw@host:port/postgres?schema=my_new_schema

Pass `X-Shopify-API-Version` and `X-Shopify-Webhook-Id` to webhook handlers

Overview

X-Shopify-API-Version: `2022-10`
X-Shopify-Webhook-Id: `b54557e4-bdd9-4b37-8a5f-bf7d70bcd043`

For runtime validation / type-sanity, it would be nice if the webhooks could be verified to be the version that you expect. It would also be great for idempotency / logging / tracability to have the webhook ID.

Allow both `string` and `string[]` for cookies

From Shopify/shopify-api-js#517 (comment)

I was confused with the Cookie header parsing in the src/runtime/http/cookies. In the constructor it expects the Cookie header to be passed as a semi-colon separated string. However, the generic headers in src/runtime/http/types that is used in the NormalizedRequest interface allow for both string and string[]. This is relevant because, e.g., the src/auth/oauth/oauth.ts createCallback uses both the normalized requests and also the cookie class.
My intuition for the abstractConvertRequest function was to pass the cookies (i.e. state cookie + signature cookie) as a list of cookies, when in reality the cookie header needs to be a single string. (Passing the cookies as a list leads to the Cookie class to only respect the very first cookie in the list)

Redis session storage 1.1.1 shuts down Redis connection

Issue summary

Upgrading to @shopify/shopify-app-session-storage-redis 1.1.1 in my Heroku production environment (I use sqlite session storage in development) caused my Heroku instance to start timing out requests (30s), never being able to serve my app.

Heroku logs show an H12 error (just meaning request timeout). My error reporting tool (Rollbar) tells me it received Error Socket closed unexpectedly from Redis. I'm used to seeing that error log in Heroku logs when I tail them, but based on my research I've understood it to be innocuous and that Redis just re-establishes its connection periodically. But I've never seen the error come through my error reporting tool (as if it's being thrown elsewhere or is somehow being caught/handled differently).

After a fresh deploy, I am sometimes able to load the app once or twice (enough to make me walk away unfortunately). But eventually no requests can be served (presumably the Redis connection is stuck closed).

๐Ÿ’ก As I'm writing this, I actually wonder if the onError configuration option is being properly passed to the Redis client... that's the only explanation that's made sense so far (not having much insight into the session storage code). That would explain the closed connection, the error reporting difference, and intermittent behavior where it works for a short time but then stops working forever.

I've since reverted back to 1.0.2

Env

  • @shopify/shopify-api version: 6.2.0
  • Node version: 18
  • Operating system: Heroku/Linux
Error Socket closed unexpectedly
/app/node_modules/@redis/client/dist/lib/client/socket.js:181
.once('error', (err) => __classPrivateFieldGet(this, _RedisSocket_instances, "m", _RedisSocket_onSocketError).call(this, err))
.once('close', hadError => {
if (!hadError && __classPrivateFieldGet(this, _RedisSocket_isOpen, "f") && __classPrivateFieldGet(this, _RedisSocket_socket, "f") === socket) {
__classPrivateFieldGet(this, _RedisSocket_instances, "m", _RedisSocket_onSocketError).call(this, new errors_1.SocketClosedUnexpectedlyError());
}
})
.on('drain', () => {

Expected behavior

The Redis connection stays open or at least re-connects, letting my app be served.

Actual behavior

The Redis connection gets closed and ultimately blocks serving of my app.

Steps to reproduce the problem

  1. Set up a Shopify app and a Heroku dyno with Heroku Redis with the below session storage configuration
  2. Deploy to Heroku
const url = new URL(process.env.REDIS_TLS_URL) // or REDIS_URL if it's already TLS (just needs to start with `rediss:`)
const redisSessionStorage = new RedisSessionStorage(url, {
      url: url.toString(),
      socket: {
        tls: true,
        rejectUnauthorized: false,
      },
      onError(error) {
        console.error(error)
      },
    })

How can I use offline/online tokens in a single app?

Overview

See https://rsirokov.medium.com/shopify-app-online-and-offline-access-modes-8a8c5ecd928b for an overview of what I would like to do. Essentially, I want to be able to go through a single authentication flow from the user's perspective with a couple of extra redirects and get both offline and online tokens for that user. Depending on the route in my app / trigger for logic in my app (i.e. background jobs), I will use the appropriate user scoped online / process scoped offline token.

Is there a best practice for how you expect that to work with shopify-app-express? It seems like I could do it with two instances, but they would need to be at completely separate root routes (undesirable for me) to avoid having the middleware being called twice? I would really love a single instance that supported a boolean and could work both ways.

Here are the routes I currently use and would like to migrate up to this:

  • /shopify/auth/offline
  • /shopify/auth/offline/callback'
  • /shopify/auth/online
  • /shopify/auth/online/callback'

For Shopify App Node.js Template, Should I Use This Library or @shopify/shopify-api-js?

Overview/Summary

For context, I've been following the migration guide for @shopify/shopify-api from v5 to v6.
Right now, I'm developing Shopify App with Node.js Template. Generated with Shopify CLI.

There are lots of breaking changes, even the library delegates some of its functions to another library.
Such as session storage, session utils now only return session ID (not shop object any more), etc.

But I've followed this library guide and found that my previous use cases with @shopify/shopify-api v5 can be used as well in this library (@shopify/shopify-app).
So, should I uninstall my @shopify/shopify-api and use @shopify/shopify-app-express instead?

Does the purpose of @shopify/shopify-app-express is to replace @shopify/shopify-api for a Node.js-based Shopify App?

Please advice,
Thank you so much!

MySQLSessionStorage support for SSL/TLS: `server does not allow insecure connections, client must use SSL/TLS`

Issue summary

I am creating a Shopify app with a MySQL Planetscale (https://planetscale.com/) database. The connection URL I have for looks like:

DATABASE_URL='mysql://USERNAME:PASSWORD@HOST/TABLE?ssl={"rejectUnauthorized":true}'

I'm passing that to the session storage constructor as it suggests in the documentation:

...
sessionStorage: new MySQLSessionStorage(DATABASE_URL),
...

But when I start my server locally, it looks like the SSL option isn't getting picked up:

 Error: unknown error: Code: UNAVAILABLE
  server does not allow insecure connections, client must use SSL/TLS

I've tried a number of different query parameters suggested in the planetscale repo to bypass this issue with no luck:

?sslcert=/etc/ssl/cert.pem, ?ssl={}, sslaccept=strict,

Am I missing something on my side? I would expect that the ssl option would get picked up off the URL by MySQL2. I have verified that the URL is picked up correctly from my environment variables, and I have verified that I can connect using the same URL using their express.js example app: https://planetscale.com/docs/tutorials/connect-nodejs-app

  • @shopify/shopify-app-* package and version: "@shopify/shopify-app-express": "^2.0.0", "@shopify/app": "3.45.1"
  • Node version: v20.0.0
  • Operating system: macOS

Expected behavior

Database URL with ssl query parameter should be passed to the session storage constructor, a new session storage instance should be generated, and the app should make a connection to the DB.

Actual behavior

Shopify app is unable to connect due to misconfigured SSL/TLS

Thanks for any help! Appreciate all the work that has gone into these tools-- much improved experience from even a year ago.

MySql session Error

Issue summary

Can't add new command when connection is in closed state

  • @shopify/shopify-app-session-storage-mysql version 1.1.2:
  • Node version: 16.0.0
  • Operating system: Linux
 index > [shopify-app/INFO] Running ensureInstalledOnShop                                                                                                           
 index > Error: Can't add new command when connection is in closed state                                                                                            
 index >     at PromiseConnection.query (/var/www/ui-extension/web/node_modules/mysql2/promise.js:93:22)                                                            
 index >     at MySqlConnection.query (/var/www/ui-extension/web/node_modules/@shopify/shopify-app-session-storage-mysql/build/cjs/mysql-connection.js:21:28)       
 index >     at processTicksAndRejections (node:internal/process/task_queues:96:5)                                                                                  
 index >     at async MySQLSessionStorage.loadSession (/var/www/ui-extension/web/node_modules/@shopify/shopify-app-session-storage-mysql/build/cjs/mysql.js:54:20)  
 index >     at async /var/www/ui-extension/web/node_modules/@shopify/shopify-app-express/build/cjs/middlewares/ensure-installed-on-shop.js:32:23 {                 
 index >   code: undefined,                                                                                                                                         
 index >   errno: undefined,                                                                                                                                        
 index >   sql: undefined,                                                                                                                                          
 index >   sqlState: undefined,                                                                                                                                     
 index >   sqlMessage: undefined                                                                                                                                    
 index > }                                                                                                                                                          
 index > [shopify-app/INFO] Running ensureInstalledOnShop                                                                                                           
 index > [shopify-app/ERROR] ensureInstalledOnShop did not receive a shop query argument | {shop: undefined}                                                        
 index > [shopify-app/INFO] Running ensureInstalledOnShop                                                                                                           
 index > [shopify-app/ERROR] ensureInstalledOnShop did not receive a shop query argument | {shop: undefined}                                                        
                                                                                                                                                                    
                                                                                                                                                                    

Expected behavior

auto re-connect to database after disconnected

Actual behavior

did not re-connected after disconnected

Steps to reproduce the problem

1.run app using pm2
2.still idle for more than 5 hour ( don't make any request to database )

you should restart pm2 to resolve this Error

Tasks

No tasks being tracked yet.

I would like a proxy for the GraphQL Admin API

Overview

When calling the Admin API from the front end, it is necessary to prepare a dedicated handler for it. Here is an example of a handler created by npm init @shopify/app@latest

app.get("/api/products/count", async (_req, res) => {
  const countData = await shopify.api.rest.Product.count({
    session: res.locals.shopify.session,
  });
  res.status(200).send(countData);
});

app.get("/api/products/create", async (_req, res) => {
  let status = 200;
  let error = null;

  try {
    await productCreator(res.locals.shopify.session);
  } catch (e) {
    console.log(`Failed to process products/create: ${e.message}`);
    status = 500;
    error = e.message;
  }
  res.status(status).send({ success: status === 200, error });
});

This is inconvenient because it requires additional endpoints to access GraphQL. So I have added the following implementation and use it, but I would like to have such an implementation available officially.

app.post("/api/shopify/admin/graphql", async (req, res) => {
  const {
    query,
    variables,
  } = req.body;
  const client = new shopify.api.clients.Graphql({ session: res.locals.shopify.session });
  const response = await client.query({
    data: {
      query,
      variables,
    },
  });
  res.status(200)
    .send(response.body);
});

Body Parser PayloadTooLargeError causes Webhooks to fail

Issue summary

On our global error handler we are getting PayloadTooLargeError when handling some webhooks. The errors are out of our control because they are bubbling up from the Shopify App Express middleware from body-parser.

The body-parser middleware exposes the limit option to increase the allowed payload size, and the default is 100kb. As you can see from the error below, this webhook payload exceeded that size. The error is actually happening in raw-body which body-parser leverages: https://github.com/stream-utils/raw-body/blob/4203bba9eb3e989bf36fd7067e58725d55126cd1/index.js#L163 when it hits the limit. Size is checked after decompression.

In the Shopify Express repo, below is the line that probably needs to change, but currently there is no way for the app to configure it.

  • @shopify/shopify-app-* package and version: 3.23.0, @shopify/shopify-app-express: 1.1.0
  • Node version: 18.16.0
  • Operating system: Linux, Docker lts-alpine:3.17
PayloadTooLargeError: request entity too large
    at readStream (/app/node_modules/raw-body/index.js:156:17)
	at getRawBody (/app/node_modules/raw-body/index.js:109:12)
	at read (/app/node_modules/body-parser/lib/read.js:79:3)
	at textParser (/app/node_modules/body-parser/lib/types/text.js:86:5)
	at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)
	at next (/app/node_modules/express/lib/router/route.js:144:13)
	at app.post.shopify_1.shopify.processWebhooks.webhookHandlers (/app/backend/index.js:68:5)
	at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)
	at next (/app/node_modules/express/lib/router/route.js:144:13)
	at Route.dispatch (/app/node_modules/express/lib/router/route.js:114:3) {
	expected: 111657,
	length: 111657,
	limit: 102400,
    type: 'entity.too.large'

Expected behavior

  • A larger default probably makes sense for express.text({type: '*/*'}),, since some Shopify webhooks are coming in over this payload default limit.
  • The limit should be configurable via the middleware.

Actual behavior

  • An error occurs in the middleware, before the webhook handler is called, which offers no opportunity to handle the problem in code.

Steps to reproduce the problem

  1. Make a POST request to the webhooks endpoint with a valid payload which when decompressed is greater than 100kb.

ENV file issues after migrating to cli3

Hello,

After migrating our app from cli2 to cli3, we are able to run our app on local server. But after deployment, our environment variables returns undefined values. This never happened before the CLI migration

Does anyone have a guidance on how to set our environment variables after upgrading to cli3? It's been a whole week since we are debugging it but haven't seen any success. Would really appreciate help or tips here

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.