Giter VIP home page Giter VIP logo

shopify-api-js's Introduction

This repo has been moved

Important

This repo has been merged with Shopify/shopify-app-js. All of the packages contained within this repo were copied over and will be maintained / released from there.

If you have an issue or feature request, please create it in the new repository.

@shopify/shopify-api-js

This mono-repo is a collection of Shopify's JavaScript API client libraries and utilities.

A library supporting Shopify apps to access Shopify's APIs, by making it easier to perform the following actions:

  • Creating online or offline access tokens for the Admin API via OAuth
  • Making requests to the Admin API (REST or GraphQL) and Storefront API (GraphQL).
  • Register/process webhooks

For use on the server.

A library to interact with Shopify's GraphQL Storefront API. For use on the client or server.

A library to interact with Shopify's GraphQL and REST Admin APIs. For use on the server.

A client to interact with any of Shopify's GraphQL APIs.

Enables JavaScript / TypeScript apps to use a #graphql tag to parse queries with graphql-codegen.

shopify-api-js's People

Contributors

andyw8 avatar batuhannarci avatar byrichardpowell avatar carmelal avatar cjauvin avatar cquemin avatar dependabot[bot] avatar derom avatar gfscott avatar github-actions[bot] avatar iliashad avatar jakxz avatar lizkenyon avatar matteodepalo avatar melissaluu avatar mkevinosullivan avatar mllemango avatar ocastx avatar paulinakhew avatar paulomarg avatar pepicrft avatar rdunk avatar rezaansyed avatar scottdixon avatar sle-c avatar surma avatar tbrenev avatar thecodepixi avatar themarkappleby 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-api-js's Issues

Amazon AWS API Gateway support

Overview

I am working on using a AWS lambda function (which is behind a API gateway route). I created a mocked IncomingMessage and ServerResponse object using node-mocks-http. I am using Serverless framework.

Can we add support for AWS API gateway, AWS Lambda, and Serverless framework?

Type

  • New feature

Motivation

My code

 export const getAuth = async (event: any): Promise<any> => {
  try {
    Shopify.Context.initialize({
        API_KEY: String(process.env.SHOPIFY_API_KEY),
        API_SECRET_KEY: String(process.env.SHOPIFY_API_SECRET),
        SCOPES: String(process.env.SHOPIFY_API_SCOPES).split(","),
        HOST_NAME: String(process.env.SHOPIFY_APP_URL).replace(/https:\/\//, ""),
        API_VERSION: ApiVersion.October20,
        IS_EMBEDDED_APP: true,
        SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
    });


    const request:any = httpMocks.createRequest({
      method: event.httpMethd,
      url: event.resource,
      params: event.pathParameters,
    });
    console.log(request);

    const response = httpMocks.createResponse();

    try {
      const authRoute = await Shopify.Auth.beginAuth(request, response, SHOP, '/auth/callback');

      return reply({ 'Location': authRoute }, httpStatuses.MOVED_TEMPORARILY);
    }
    catch (e) {
      console.log(e);
      return replyError(e);
    }

    
    
  } catch (e) {
    return replyError(e);
  }
};

export function handler(event, context):any {
  const collectionHandlers = {
    POST: null,
    GET: getAuth,
  };
  const itemHandlers = {
    PUT: null,
    DELETE: null,
  };

  return router(collectionHandlers, itemHandlers, { event, context });
}

I am getting the following response

TypeError: Cannot set property 'set-cookie' of undefined
    at EventEmitter.setHeader (_http_outgoing.js:556:31)
    at Cookies.set (/Users/umair/Desktop/code/shopify/category_management/backend/node_modules/cookies/index.js:116:13)
    at Object.<anonymous> (/Users/umair/Desktop/code/shopify/category_management/backend/node_modules/@shopify/shopify-api/dist/auth/oauth/oauth.js:55:33)
    at step (/Users/umair/Desktop/code/shopify/category_management/backend/node_modules/@shopify/shopify-api/node_modules/tslib/tslib.js:143:27)
    at Object.next (/Users/umair/Desktop/code/shopify/category_management/backend/node_modules/@shopify/shopify-api/node_modules/tslib/tslib.js:124:57)
    at fulfilled (/Users/umair/Desktop/code/shopify/category_management/backend/node_modules/@shopify/shopify-api/node_modules/tslib/tslib.js:114:62)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
2021-03-31 19:05:10 [error]: TypeError: Cannot set property 'set-cookie' of undefined 

Checklist

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

Res redirect runs into Auth0 cors issues.

Issue summary

This may not be an issue and may be a gap in my knowledge of how Shopify is implementing Auth0 and how that works with this library, so I apologize in advance if these issues are unrelated.

I am creating a non-embedded Shopify app. This app should allow users to authenticate without downloading the app from the Shopify store, but instead visiting a route. Like the library docs specified, I have a /login route that uses the Shopify context that was setup to create a authRoute and upon trying to redirect using the response from the route handler I get a cors error from Auth0. Is there a different way to initiate authentication for a non-embedded app without using the shopify store but a custom route from the app?

Auth0 recommends passing this authRoute to the UI and doing a window.location change, this does not include the cookies needed for authentication in the validateAuthCallback function so this workaround wouldn't work here.

Expected behavior

What do you think should happen?

I should be able to allow the user to authenticate using the /login route. The user specifies their shop name -> {shopname}.myshopify.com and we use this to authenticate them using the documented ways in this library's documentation without receiving cors issues from Auth0.

Actual behavior

What actually happens?

Auth0 throws cors errors when the authentication process is initiated from my app (ngrok link)

Tip: include an error message (in a <details></details> tag) if your issue is related to an error

Steps to reproduce the problem

  1. Setup a non-embedded app using the steps provided in this library's documentation.
  2. Setup a login route that takes a shopname input. Use this input to create the authroute using the begin auth function. Once the auth route is created, try to redirect to that route with the response.

Reduced test case

The best way to get your bug fixed is to provide a reduced test case.


Checklist

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

Api version 2021-04

Issue summary

Add 2021-04 in the versions enum in base_types.ts

Expected behavior

2021-04 entry is added

Reduced test case

Ability to make GQL call taretting the 2021-04 version


Checklist

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

Koa-Router: request 'on' is not a function

I am using koa-router for my api routes and I can't get the Webhook.process to work. It says that 'response.on' is not a function. What do I need to do to get this to work?

Register webhook:

    const handleWebhookRequest = async (topic, shop, body) => {
        console.log(this + '.hWhR() topic: ' + topic);
        console.log(this + '.hWhR() shop: ' + shop);
        console.log(this + '.hWhR() body: ' + body);
        appUninstall(shop);
    }

    const resp = await Shopify.Webhooks.Registry.register({
        path: '/api/admin/webhooks/app-uninstalled',
        topic: 'APP_UNINSTALLED',
        accessToken: token,
        shop: shop,
        webhookHandler: handleWebhookRequest
    });
    console.log('resp:');
    console.dir(resp);

Api router callback:

const Router = require('koa-router');
const koaBody = require('koa-body');

const router = new Router({ prefix: routePrefix });

router.post('/app-uninstalled', koaBody(), async (ctx, next) => {
    try {
        await Shopify.Webhooks.Registry.process(ctx.request, ctx.response);
    } catch (error) {
        console.log(error);
    }
});

Documentation improvements

Overview/summary

Now that the library has been released, we noticed a couple of pain points that we can help remedy via documentation. This PR aggregates them so we can address them at once.

  1. Document the methods and parameters for the REST and GraphQL clients (using the same table format from the withSession docs).
  2. Add a concrete production-valid example of how to use CustomSessionStorage in the session notes. We could show how to implement the callbacks using Redis.
    2.1. We may want to consider adding a few tips (and what-not-to-dos!) on using this feature to help developers choose their preferred method.

Checklist

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

Redis session on shopify CLI

Overview/summary

Hello.
I have been trying for several days to use a custom session storage with the shopify CLI (Tried MongoDB, Redis, etc) without any success.
There's any repository with a proper custom session storage with shopify CLI that I can use as a reference?
Also anyone prefers shopify CLI over a scratch implementation? I've seen that shopify CLI adds various thing that could be a pain to debug in the future.

Allow mutations with GraphQLClient

Overview

Allow mutations with GraphQLClient and not just queries.

...

Type

  • New feature
  • Changes to existing features

Motivation

What inspired this feature request? What problems were you facing?
Wanting to make mutations using the shopify GraphQLClient
...

Area

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

Checklist

  • [X ] I have described this feature request in a way that is actionable (if possible)

error handling issues in this lib

hello,
this lib does not handle errors or allows the user to handle them by himself.

for instance

  1. RestClient does not load a valid json string? throws an error.
    use case: if you try to load an asset that does not exist api will return a 404 status with no response body. which will break the .get() method. no way to read the actual response or header as far as i know

  2. webhooks "register" method, the code assumes the response will for sure have a checkBody.data.webhookSubscriptions when it doesn't it breaks of course with no way to debug.

More description about how to store webhooks

Overview/summary

from shopify-node-api/docs/usage/webhooks.md

Note: The webhooks you register with Shopify are saved in the Shopify platform, but your handlers need to be reloaded whenever your server restarts. It is recommended to store your Webhooks in a persistent manner (for example, in a database) so that you can reload previously registered webhooks when your app restarts.

So what is it that needs to be stored? the webhook object that's returned here?

  const response = await Shopify.Webhooks.Registry.register({
    shop: config.shop,
    accessToken: config.accessToken,
    path: "/webhooks",
    topic: 'APP_UNINSTALLED',
    webhookHandler: async (topic, shop, body) =>{
      appReducer(shop, {action: 'APP_UNINSTALLED'})
    },
  });

  1. In digging i found response.result.data.webhookSubscriptionCreate.webhookSubscription.id
    it will return something like gid://shopify/WebhookSubscription/12345678901234

What do we do with this result?

  1. So we need to loop through all active shops and resend all webhook registrations every time the server starts?

Can you help update the documentation with a little more info about this?

...

Motivation

What inspired this enhancement?

learning more about webhooks

...

Area

  • [webhooks] Add any relevant Area: <area> labels to this issue
  • [documentation]

Checklist

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

Markdown link checker causing CI to fail

Issue summary

Tool used to check that all links in Markdown files are valid gets rate-limited when checking changelog PR links. Causes CI to fail because some links return 429 Too Many Requests.

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

Issue summary

Getting error with message: Cannot complete OAuth process. Could not find an OAuth cookie for shop url:

Expected behavior

validateAuthCallback shouldn't throw an error

Actual behavior

/auth/callback route throws an error
This happens while validating auth callback (route: /auth/callback)
I used the code from the docs (README.md)

Steps to reproduce the problem

  1. Initialize Shopify Context
Shopify.Context.initialize({
    API_KEY: settings.SHOPIFY_API_KEY,
    API_SECRET_KEY: settings.SHOPIFY_API_SECRET,
    SCOPES: settings.SHOPIFY_API_SCOPES.split(','),
    HOST_NAME: settings.HOST.replace(/https:\/\//, ''),
    API_VERSION: ApiVersion.October20,
    IS_EMBEDDED_APP: true,
    SESSION_STORAGE: new Shopify.Session.MemorySessionStorage()
  })
  1. Add auth route in my case is /login
router
    .get('/login', async (req: Request, res: Response) => {
      const { shop } = req.query

      if (typeof shop !== 'string' || !Shopify.Utils.validateShop(shop)) {
        res.status(400).send('Invalid shop parameter')
        return
      }

      try {
        const authRoute = await Shopify.Auth.beginAuth(
          req,
          res,
          shop,
          '/auth/callback',
          true
        )

        return res.redirect(authRoute)
      } catch (error) {
        log.error(error)
        res.status(500).send('Unexpected error occured')
      }
    })
  1. Add auth callback route in my case /auth/callback
router
    .get('/auth/callback', async (req: Request, res: Response) => {
      try {
        await Shopify.Auth.validateAuthCallback(
          req,
          res,
          (req.query as unknown) as AuthQuery
        )

        return res.redirect('/')
      } catch (error) {
        log.error(error)
        res.status(500).send('Unexpected error occured')
      }
    })
  1. Open the app with the route /auth?shop=your.shop.url

Checklist

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

How to use Shopify.Utils.graphqlProxy with NextJS (API-Routes)

I want to use Shopify.Utils.graphqlProxy with NextJS (API-Routes) without koa.

like this:
pages/api/graphql.ts

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
  await Shopify.Utils.graphqlProxy(req, res);
};

But these two events do not fire.
https://github.com/Shopify/shopify-node-api/blob/bc70452e05de5f9209cdf21e0bf22edde3cce47f/src/utils/graphql_proxy.ts#L22
https://github.com/Shopify/shopify-node-api/blob/bc70452e05de5f9209cdf21e0bf22edde3cce47f/src/utils/graphql_proxy.ts#L26

I have confirmed that I can proxy with the following code.

const handler = async (req: NextApiRequest, res: NextApiResponse) => {
  const session = await Shopify.Utils.loadCurrentSession(req, res);
  if (!session) {
    throw new Error("Cannot proxy query. No session found.");
  } else if (!session.accessToken) {
    throw new Error("Cannot proxy query. Session not authenticated.");
  }

  const shopName: string = session.shop;
  const token: string = session.accessToken;
  const options = {
    data: req.body,
  };
  const client = new Shopify.Clients.Graphql(shopName, token);
  const response = await client.query(options);
  res.status(200).json(response.body);
};

Could you give me some advice about this?

Can't create webhook for SUBSCRIPTION_CONTRACTS_CREATE topic

Issue summary

I'm building a custom shopify app, where I register a webhook for SUBSCRIPTION_CONTRACTS_CREATE and SUBSCRIPTION_CONTRACTS_UPDATE topic.

Expected behavior

I expect the webhook to be registered successfully, like when I register PRODUCTS_CREATEtopic.

Actual behavior

The webhook isn't registered, and I get this traceback:

(node:53083) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'webhookSubscriptions' of null
    at Object.<anonymous> (/Users/sophie/Projects/shopify-app/node_modules/@shopify/shopify-api/dist/webhooks/registry.js:92:44)
    at step (/Users/sophie/Projects/shopify-app/node_modules/@shopify/shopify-api/node_modules/tslib/tslib.js:143:27)
    at Object.next (/Users/sophie/Projects/shopify-app/node_modules/@shopify/shopify-api/node_modules/tslib/tslib.js:124:57)
    at fulfilled (/Users/sophie/Projects/shopify-app/node_modules/@shopify/shopify-api/node_modules/tslib/tslib.js:114:62)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)

Steps to reproduce the problem

  1. use shopify-api version 1.2.1
  2. Add this code to your custom/public shopify app afterAuth:
            Shopify.Webhooks.Registry.register({
                shop,
                accessToken,
                path: '/webhooks',
                topic: 'SUBSCRIPTION_CONTRACTS_CREATE',
                apiVersion: ApiVersion.April21,  // I also tried '2021-07' and ApiVersion.January21
                webhookHandler: (_topic, shop, _body) => { console.log('Subscription Contract was created!'); }
            });

Handling app proxy from Shopify

I am wondering if there is a way to check if the user (store) is authenticated when the request comes from the Shopify proxy? How do I get a session for a store (offline/online) when a user (store) has sent a request to the app's server using a script that is injected with a script tag? Or are there any other ways to check if the request sent with exactly Shopify app proxy? The current problem is that you can fetch store data from the app using any shop parameter in the request and all requests coming to the path /proxy/* bypass authentication check

Embedded navigation - Enable cookies / auth loop

Issue summary

I have a fresh embedded app created via the CLI and when I use embedded navigation it doesn't load a page, it instead redirects to an enable cookies url, then redirects to auth and loads the initial default view again once complete.

Expected behavior

What do you think should happen?

I would expect that when clicking a navigation item it would load a page from the Next.js app.

Actual behavior

What actually happens?

I created a brand new Shopify app using the CLI. I duplicated pages/index.js to pages/sample.js. I then went to App Setup > Embedded app > Configure (Navigation) inbox the partner dashboard. I added two items, Dashboard > / and Sample > /sample.

The navigation appears as expected, when I click on the "Sample" link I would expect it to load the sample page from Next.js. However it seems to redirect to an enable cookies url, then redirects to auth and loads the initial default view again once complete.

Steps to reproduce the problem

  1. Create a new app via the CLI
  2. Add a new page to Next.js
  3. Add navigation in the partner dashboard and then try and open link to new page

Checklist

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

Invalid config host must be provided page freeze with App Bridge

Issue summary

When you have an invalid or missing host in App Bridge's react provider, it casues an infinite loop in JavaScript which ends up crashing the tab.

The console gets spammed with the same message:

Invalid config host must be provided page freeze

Expected behavior

It should throw the error once or provide a way to handle the error and no infinite JavaScript loop should occur.

Actual behavior

An infinite loop in JavaScript occurs causing the tab to crash.

Steps to reproduce the problem

  1. Create an app with App Brdige 2.0
  2. Use the Provider from "@shopify/app-bridge-react".
  3. Put an incorrect host in the provider's config.

Reduced test case

Checklist

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

Http Client depends on obsolete package 'fs'

Hello,

I get this error when trying to include the @shopify/shopify-api package in my project:

./node_modules/@shopify/shopify-api/dist/clients/http_client/http_client.js:7:0
Module not found: Can't resolve 'fs'

Checking that file, it seems that indeed that script is trying to use 'fs', on line 7:

var fs_1 = tslib_1.__importDefault(require("fs"));

That package is not available anymore on the NPM repository: https://www.npmjs.com/package/fs

No instructions on how to make post request to rest api

No documentation provided on how to use shopify-api properly

I tried to figure out how to make post requests to the sopify admin rest api but I can't figure it out. That's what I expected the request to look like:

const client = new Shopify.Clients.Rest(shop, accessToken);

        await client.post({
          path: 'script_tags',
          data: {
            script_tag: {event: "onload", src: "url"}
          }
        }).catch(console.log)

This request resolves in an Error:
TypeError [ERR_INVALID_ARG_TYPE]: The "string" argument must be of type string or an instance of Buffer or ArrayBuffer. Received null

There is no documentation provided on what format the data parameter should have.

Shopify.Utils.loadCurrentSession does not always call the loadCallback and returns undefined, Oauth loop

Issue summary

On the server side, when calling await Shopify.Utils.loadCurrentSession(ctx.req, ctx.res), the function is supposed to call the loadCallback for the Shopify.Session.CustomSessionStorage. However, it does not always call this function when invoked, and immediately returns undefined, which causes the app to re-auth and get stuck in an infinite Oauth loop.

Expected behavior

await Shopify.Utils.loadCurrentSession(ctx.req, ctx.res) should always call the loadCallback for the Shopify.Session.CustomSessionStorage. Its value should be undefined only if loadCallback returns undefined, but should otherwise contain a Session object.

Actual behavior

During auth and app loading, Shopify.Utils.loadCurrentSession calls the loadCallback function. However, after auth when handleRequest is called, and the Shopify.Utils.loadCurrentSession is called again, it immediately (less than 1ms) returns undefined and loadCallback is NOT run.

Example code from server.js

Shopify.Context.initialize({
  API_KEY: API_KEY,
  API_SECRET_KEY: SHOPIFY_API_SECRET,
  SCOPES: SHOPIFY_API_SCOPES.split(","),
  HOST_NAME: SHOPIFY_APP_URL.replace(/https:\/\//, ""),
  API_VERSION: ApiVersion.April21,
  IS_EMBEDDED_APP: true,
  SESSION_STORAGE: new Shopify.Session.CustomSessionStorage(
    storeCallback,
    loadCallback,
    deleteCallback,
  ),
});

async function loadCallback(id) {
  //This is NOT called after handleRequest()
  //Load session from db
  session = await getFromDb();
  if(session) {
    return session;
  } else {
    return undefined;
  }
});

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

    const currentSession = await Shopify.Utils.loadCurrentSession(ctx.req, ctx.res);
    if(!currentSession) {
      ctx.redirect(`/auth?shop=${shop}`);
    }  else {
      await handleRequest(ctx);
    }
  });

App Oauth flow

Shopify.Utils.loadCurrentSession: undefined
Redirect to /auth
storeCallback: Store new temp session #1 (random id, with partial data)
Shopify.Utils.loadCurrentSession (loadCallback runs): Load temp session #1
storeCallback: Store shop session (with myshopify url in id)
storeCallback: Store temp session #1 again (with full data)
Shopify.Utils.loadCurrentSession (loadCallback runs): Load temp session #1 again
Redirect to /
Shopify.Utils.loadCurrentSession (loadCallback runs): Load temp session #1 again
Load app (handleRequest())
Shopify.Utils.loadCurrentSession (**loadCallback does NOT run**): Attempt to load current online session (but immediately returns undefined)
Redirect to /auth
Process repeats until app throws error: "The app couldnโ€™t be loaded This app canโ€™t load due to an issue with browser cookies."

shopifyAuth throws 500 error if more than 1 minute between GET / and GET /auth/callback

Issue summary

When a user is on the "You are about to install #APP_NAME" page and they wait for more than 1 minute to click "Install app" a 500 error is thrown.

Url
GET /auth/callback?code=4e9c4cb180763a21ab0fbf243c17f5ba&hmac=751bbcaa7f5a4013575d617af11f23eb1e570c8c1f82d36a12405895404bda38&host=cXVpdmVyLWltbWVkaWF0ZS1kZWxpdmVyaWVzLm15c2hvcGlmeS5jb20vYWRtaW4&shop=quiver-immediate-deliveries.myshopify.com&state=284790218494132&timestamp=1623885746

Error

InternalServerError: Cannot complete OAuth process. Could not find an OAuth cookie for shop url: quiver-immediate-deliveries.myshopify.com
      at Object.throw (/app/node_modules/koa/lib/context.js:97:11)
      at /app/node_modules/@shopify/koa-shopify-auth/dist/src/auth/index.js:100:42
      at step (/app/node_modules/tslib/tslib.js:141:27)
      at Object.throw (/app/node_modules/tslib/tslib.js:122:57)
      at rejected (/app/node_modules/tslib/tslib.js:113:69)
      at runMicrotasks (<anonymous>)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)

This bug is difficult to recreate as installing the app on a test store, you typically don't go through the App store. This means the "You are about to install #APP_NAME" is skipped as it "Install app" is auto-clicked/auto-selected.

To understand this error better, I added CustomSessionStorage class with the relevant functions. The loadCallback function is never called if the time between auth is longer than 1 minute even though there is a session in the storage.

This issue occurs when using the Shopify.Session.MemorySessionStorage and Shopify.Session.CustomSessionStorage.

My relevant dependencies are:

"@shopify/app-bridge": "^2.0.3",
"@shopify/app-bridge-react": "^2.0.3",
"@shopify/app-bridge-utils": "^2.0.3",
"@shopify/koa-shopify-auth": "^4.1.3",
"koa": "^2.13.1",
"koa-bodyparser": "^4.3.0",
"koa-ignore": "^1.0.1",
"koa-logger": "^3.2.1",
"koa-router": "^8.0.8",
"shopify-api-node": "^3.6.12",

Expected behavior

Users should be able to sit for more 1 minute idle between auth and auth callback
OR
I should be able to handle this error and restart the auth process rather than just throwing 500 error.

Steps to reproduce the problem

I have been recreating the issue via shopify.

  1. Open a store
  2. Click on apps
  3. Click on shop for apps
  4. Search for quiver app or any app that uses the new oauth flow
  5. Click add app
  6. Wait for more than 1 minute before clicking install app

passing query parameter to client get request

How to pass query parameter to the client.get()?

I have tried passing the query key as shown below (and some other variations with .json and ?) but I get the whole assets array. I am not sure if I am doing it incorrectly or it's a bug.

client.get({
    path: `themes/${mainTheme.id}/assets`,
    query: "asset[key]=templates/index.liquid",
  })

However, I am able to get the desired result using axios.

const config = {
  baseURL: `https://${session.shop}`,
  headers: {
    "X-Shopify-Access-Token": session.accessToken,
    "X-Shopify-Shop-Domain": session.shop,
  },
};
const result = await axios.get(
  `/admin/api/2021-04/themes/${mainTheme.id}/assets.json?asset[key]=templates/index.liquid`,
  config
);

Please update the docs with more examples.

Example not present TS implementation however shopify-node-api written in TS. Tutorial also...

https://github.com/Shopify/shopify-node-api
written in ts
https://github.com/Shopify/shopify-demo-app-node-react
in JS
they recomend not use Memory storage for sessions for production
but not suppy docs or examples how to implement it in pure JS
but only TS example with redis
after i try to implement my own storage callbacks in pure JS i get
SessionStorageError: Expected return to be instanceof Session, but received instanceof Object

Custom route sessions expire after few seconds

Issue summary

I had created a sample app using the shopify cli tool (shopify create) and I wanted to add additional routes to the existing app. I have created a route called /api/orders which returns a list of all the orders.

server.js

router.post(
    "/graphql",
    verifyRequest({ returnHeader: true }),
    async (ctx, next) => {
      await Shopify.Utils.graphqlProxy(ctx.req, ctx.res);
    }
  );

router.get("/api/orders", verifyRequest(), async (ctx) => {
    console.log(ACTIVE_SHOPIFY_SHOPS);
    const session = await Shopify.Utils.loadCurrentSession(ctx.req, ctx.res);
    const client = new Shopify.Clients.Graphql(
      session.shop,
      session.accessToken
    );
    // console.log("Loaded token: ", session.accessToken)
    // console.log("cookies: ", ctx.cookies.get('accessToken'))
    ctx.body = await api_orders(client, ctx.params, ctx.query);  // returns JSON string  });

After starting the server (shopify serve), I go ahead and authenticate myself by opening the auth URL (https://<ngrock_url>/auth?shop=shopname.myshopify.com) in my browser.

Once I'm done with that I can get the orders by opening the defined route in my browser (https://<ngrock_url>/api/orders). However when I open the url again after a few seconds, it redirects me to a different unrecognized url (https://admin/oauth/authorize?client_id=e74e84f43b273631f1asdfccbd1d6607f2bc&scope=read_orders&redirect_uri=https%3A%2F%2F3960a5286b424.ngrok.io%2Fauth%2Fcallback&state=233468616505697&grant_options%5B%5D=per-user)

Expected behavior

https://<ngrock_url>/api/orders url continues to return a list of orders even after some time has passed.

Actual behavior

Opening the url after some time redirects to an unknown page or returns TypeError: Cannot read property 'map' of undefined.

TypeError: Cannot read property 'map' of undefined
       at new AuthScopes (/home/gts/Projects/personal/shopify/sample_app/node_modules/@shopify/shopify-api/dist/auth/scopes/index.js:14:35)
       at AuthScopes.equals (/home/gts/Projects/personal/shopify/sample_app/node_modules/@shopify/shopify-api/dist/auth/scopes/index.js:38:21)
       at /home/gts/Projects/personal/shopify/sample_app/node_modules/@shopify/koa-shopify-auth/dist/src/verify-request/verify-token.js:23:79
       at step (/home/gts/Projects/personal/shopify/sample_app/node_modules/tslib/tslib.js:133:27)
       at Object.next (/home/gts/Projects/personal/shopify/sample_app/node_modules/tslib/tslib.js:114:57)
       at fulfilled (/home/gts/Projects/personal/shopify/sample_app/node_modules/tslib/tslib.js:104:62)
       at processTicksAndRejections (internal/process/task_queues.js:97:5)

Steps to reproduce the problem

  1. Create node app using shopify cli (shopify create node)
  2. Add /api/orders route in server.js and add read_orders scope to .env
  3. Open the defined route in browser after authenticating your app.
  4. Open the same url again after a minute.

Checklist

  • Tried with offline tokens which returns the second error: TypeError: Cannot read property 'map' of undefined in auth/scopes

Add Offline Mode to graphqlProxy

Overview/summary

Add a feature to Shopify.Utils.graphqlProxy(ctx.req, ctx.res) to handle case where the shop is authenticated under accessMode=offline
...

Motivation

What inspired this enhancement?
Under the codebase of graphql_proxy.ts, the function called loadCurrentSession to load the current session. However, it didn't list out the third argument, which is a boolean type of whether the session is online or offline. And the default value is online.
Thus, no matter what type of accessMode the developers wanted, this function would only call loadCurrentSession with the online option, which would cause an error either not found the session or unauthorized if they used offline accessMode.
Currently, it works fine with the online mode, but it would be better to add an extra option for developers to choose the online/offline in order to use graphqlProxy easily.
...

Area

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

Checklist

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

Are App Bridge 2.0 apps supported?

Overview

Hi everyone!
I just started working on an embedded app with NodeJS and the documentation suggests I should use this package. The docs also say I must use App Bridge 2.0 on the frontend to generate session tokens that will be sent to the backend API.
Is this package updated to work with this new architecture? Checking the code, I see that it sets cookies to store the user session. Can I use this or should I implement my own logic until this package is updated?

Thank you very much

Session scope check fails when scope is undefined

Issue summary

This fails when session is present with scope undefined.

``
if (session) {
const scopesChanged = !Shopify.Context.SCOPES.equals(session.scope);

  if (!scopesChanged && session.accessToken && (!session.expires || session.expires >= new Date())) {
    ctx.cookies.set(TOP_LEVEL_OAUTH_COOKIE_NAME);
    await next();
    return;
  }
}

``

Expected behavior

Should invalidate session and initiate oauth.

Actual behavior

What actually happens?

The failure happens downstream in shopify api,

TypeError: Cannot read property 'map' of undefined at new AuthScopes (/node_modules/@shopify/shopify-api/dist/auth/scopes/index.js:14:35)

Steps to reproduce the problem

I'm unable to reproduce this reliably, but can manually remove scope from session to test.

Shopify.Webhooks.Registry.process doesn't terminate

Issue summary

Write a short description of the issue here โ†“

I am setting up an app-uninstalled webhook as in this tutorial, but it seems the webhook handler hangs at

      await Shopify.Webhooks.Registry.process(ctx.req, ctx.res);

The full handler code is here

  router.post("/webhooks", async (ctx) => {
    // eslint-disable-next-line no-console
    console.log(`Webhook request received`);

    try {
      // NEXT LINE HANGS FOREVER
      await Shopify.Webhooks.Registry.process(ctx.req, ctx.res);
    } catch (err) {
      console.error(err);
      err.status = err.statusCode || err.status || 500;
      throw err;
    }

    // eslint-disable-next-line no-console
    console.log(`Webhook processed with status code 200`);
  });

And the webhook handler is registered like this

  server.use(
    createShopifyAuth({
      async afterAuth(ctx) {
        const { shop, scope, accessToken } = ctx.state.shopify;
        await firestore.saveShop(shop, scope, accessToken);

        const registration = await Shopify.Webhooks.Registry.register({
          shop,
          accessToken,
          path: "/webhooks",
          topic: "APP_UNINSTALLED",
          webhookHandler: async (_topic, shop, _body) => {
            // eslint-disable-next-line no-console
            console.info(`App uninstalled from ${shop}`);
            await firestore.deleteShop(shop);
          },
        });

        if (registration.success) {
          // eslint-disable-next-line no-console
          console.info("Successfully registered APP_UNINSTALLED webhook.");
        } else {
          // eslint-disable-next-line no-console
          console.info(
            "Failed to register APP_UNINSTALLED webhook",
            registration.result
          );
        }

        ctx.redirect(
          `https://${CWA_HOST}/shopify&shop=${shop}&accessToken=${accessToken}`
        );
      },
    })
  );

Any ideas Shopify.Webhooks.Registry.process simply hangs without throwing an error?

Expected behavior

What do you think should happen?

Actual behavior

What actually happens?

Tip: include an error message (in a <details></details> tag) if your issue is related to an error

Steps to reproduce the problem

Reduced test case

The best way to get your bug fixed is to provide a reduced test case.


Checklist

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

How do I customize the session?

The package gives us CustomSessionStorage and requires to implement 3 callbacks which are storeCallback, loadCallback, deleteCallback. The storeCallback method requires Session argument when using Typescript. How do I store my own session data? For example, let's say I want to store the online and offline access token at the same time, but it looks like the current implementation only allows one token. And even if I can extend Session with my own class, I'm forced to store shop, state and scope. I'm not sure what the state property is for. Is there any way I can further customize it?

class Session {
    readonly id: string;
    static cloneSession(session: Session, newId: string): Session;
    shop: string;
    state: string;
    scope: string;
    expires?: Date;
    isOnline?: boolean;
    accessToken?: string;
    onlineAccessInfo?: OnlineAccessInfo;
    constructor(id: string);
}

Add option for adjusts the request rate so that the rate limit is not reached

Overview

Can we add option for adjusts the request rate so that the rate limit is not reached?

Type

  • New feature

Motivation

Currently, all developers who use the Shopify api need to take care of the rate limit on their own.
I thought it would be easier for many developers to use this library if it was implemented as an option in this library.

Checklist

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

Rest client. Get request with query fails if the limit is not specified

Issue summary

With this kind of request

client.get({
  path: 'redirects',
  query: {
	  "path": 'some_path',
  },
});

I get error, because limit is undefined

Expected behavior

Successful request to the api

Actual behavior

I get error UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'toString' of undefined
which comes from this row https://github.com/Shopify/shopify-node-api/blob/7c9201af556c6f82deb936215f5099d08e35f727/src/clients/rest/rest_client.ts#L35


Checklist

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

Pass isOnline flag in docs for NodeJS

Overview/summary

The docs to begin OAuth don't include the isOnline argument in the NodeJS code snippet. It is set in the Express example though and basically forces the Cookie to immediately expire when not set - i.e. breaking the OAuth flow when following the vanilla NodeJS docs. I would suggest using online auth in the NodeJS code as well.

Screen Shot 2021-06-13 at 12 36 37

Motivation

I spent too much time reverse engineering why the vanilla NodeJS code wasn't working. This small adjustment in your docs could save others the time to figure it out the hard way.

...

Area

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

Checklist

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

How do we load the session to make requests to GraphQL/REST?

I'm trying to load the session via Shopify.Utils.loadCurrentSession(req, res, true) but after reading the code I noticed it first checks if the request is valid with validateCallback which then checks for the presence of a cookie called shopify_app_session.

When I make a request to the server via the front-end the cookie is not sent because cookies are not allowed in Shopify Apps, thus cookies are empty and fails to load the session object. Of course, if I set a shopify_app_session cookie when the app is first loaded it works as expected, but it will be rejected due to the usage of cookies.

How do we solve this?

What I'm doing right now is returning the session object from the validateCallback callback and then store this somehow e.g. accessToken and session ID in DB and shopOrigin in the client (sessionStorage, etc) for easy access. Is that the correct approach?

missing cookies in callback request

I'm trying to make an embedded app and use chrome browser.
validateAuthCallback-function throws ShopifyErrors.SessionNotFound.

/api/callback.ts

await Shopify.Auth.validateAuthCallback(request, response, query as AuthQuery);

SessionNotFound [Error]: Cannot complete OAuth process. No session found for the specified shop url: myshop.myshopify.com

sameSite: 'lax', missing cookies in callback request .

    cookies.set(ShopifyOAuth.SESSION_COOKIE_NAME, session.id, {
      signed: true,
      expires: new Date(Date.now() + 60000),
      sameSite: 'lax',
      secure: true,
    });

I've temporarily changed sameSite:'none', and it works, but I don't know how to fix it correctly.

Api Versions are out of date

Issue summary

Using import Shopify, {ApiVersion} from '@shopify/shopify-api';
When I print out the available ApiVersions, I get an old list:

ApiVersions: {
  July19: '2019-07',
  October19: '2019-10',
  January20: '2020-01',
  April20: '2020-04',
  July20: '2020-07',
  October20: '2020-10',
  Unstable: 'unstable',
  Unversioned: 'unversioned'
}

Seems that these are cached values. How do I get the latest API versions to load?

callbackUrl Address is invalid on webhook registration

Hello,

I'm using this guide to setup webhooks in my Shopify app.

Specifically I have the following code:

         ACTIVE_SHOPIFY_SHOPS[shop] = scope;
         const registration = await Shopify.Webhooks.Registry.register({
          shop,
          accessToken,
          path: '/webhooks',
          topic: 'APP_UNINSTALLED',
          apiVersion: ApiVersion.October20,
          webhookHandler: (_topic, shop, _body) => {
            console.log('App uninstalled');
            delete ACTIVE_SHOPIFY_SHOPS[shop];
          },
        });

        if (registration.success) {
          console.log('Successfully registered webhook!');
        } else {
          console.log('Failed to register webhook', registration.result.data.webhookSubscriptionCreate.userErrors);
        }

However the webhook fails to register each time giving me the error:

Failed to register webhook [
  {
    field: [ 'webhookSubscription', 'callbackUrl' ],
    message: 'Address is invalid'
  }
]

Any ideas - I can't see anywhere to specify the callback / where is it getting it from to call it invalid?

Thanks.

TypeError: Cannot read property 'initialize' of undefined

Issue summary

Hi shopify-node-api team,

Thanks for making this node API.

I am trying to build an embedded app with Vue/Koa.js, I just start by following the tutorial of @shopify/koa-shopify-auth. However, when I tried to run the example app via command node server.js,
it return an error said:

Shopify.Context.initialize({
                ^
TypeError: Cannot read property 'initialize' of undefined

It confused me, so I came here following the getting start tutorial of @shopify/shopify-node-api, and carefully checked every input arguments(API_KEY, API_SECRET_KEY, SCOPES, HOST, all these value are correct).

Unfortunately, I got just the same error.

Expected behavior

I expected the example app can successfully executed, or at least the function Shopify.Context.initialize could work.

Actual behavior

when I tried to run the example app via command node server.js,
it return an error said:

Shopify.Context.initialize({
                ^
TypeError: Cannot read property 'initialize' of undefined

Even I checked the tutorial at here, it still end with same the error.

Steps to reproduce the problem

  1. Setup the files
  2. type node server.js

Reduced test case

Following is my code(sever.js and package.json):

server.js

import 'isomorphic-fetch';
import dotenv from 'dotenv';
import Koa from 'koa';
import session from 'koa-session'; 
import shopifyAuth, {default as verifyRequest} from '@shopify/koa-shopify-auth';
import Shopify, {ApiVersion} from '@shopify/shopify-api';

dotenv.config();
const port = parseInt(process.env.port, 10) || 3000;
const {SHOPIFY_API_SECRET, SHOPIFY_API_KEY} = process.env;

//initialize the library
Shopify.Context.initialize({
    API_KEY: SHOPIFY_API_KEY,
    API_SECRET_KEY: SHOPIFY_API_SECRET,
    SCOPES: [process.env.SHOPIFY_APP_SCOPES],
    HOST_NAME: process.env.SHOPIFY_APP_URL.replace(/^https:\/\//, ''),
    IS_EMBEDDED_APP: true,
    API_VERSION: ApiVersion.October20,
    SESSION_STORAGE: new Shopify.Session.MemorySessionStorage()
  });

const server = new Koa();
server.keys = [SHOPIFY_API_SECRET];
server.use(session(server))

.use(
    shopifyAuth({
        afterAuth(ctx){
            const {shop, accessToken} = ctx.session;
            console.log('First Trial Success!', accessToken);
            ctx.redirect('/');
        },
    }),
)

.use(verifyRequest())

.use(ctx => {
    ctx.body = '๐Ÿ˜Ž';
});

server.listen(port, ()=>{
    console.log('> Ready on http://localhost:${port}');
});

package.json

{
  "name": "koala",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Dan Hsu",
  "license": "ISC",
  "dependencies": {
    "@shopify/app-bridge-utils": "^1.29.0",
    "@shopify/koa-shopify-auth": "^4.0.2",
    "@shopify/shopify-api": "^1.1.0",
    "@types/koa": "^2.13.1",
    "dotenv": "^8.2.0",
    "isomorphic-fetch": "^3.0.0",
    "koa": "^2.13.1",
    "koa-router": "^8.0.8",
    "koa-session": "^6.1.0"
  }
}

Checklist

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

Typescript types of REST Shopify Admin Objects

Overview

It would be great to have types of each of the REST Admin API Shopify objects (e.g. Orders, Products, etc...) defined in this library (or maybe in a separate dedicated library of types) so that partners can have a type safe way of handling Shopify REST API responses.

This library doesn't have these types and other community based libraries that have defined types are unreliable and oftentimes out of date.

Type

  • New feature
  • [] Changes to existing features

Motivation

What inspired this feature request? What problems were you facing?

I'd like to have types of Shopify REST objects so that I can have reliable types and make more reliable Shopify apps.


Checklist

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

new Shopify.Clients.Graphql is not working, "Domain .. is not valid", applies token as domain

Issue summary

Write a short description of the issue here โ†“
new Shopify.Clients.Graphql(shop, accessToken); is not working.

Expected behavior

The code I am calling.

addScriptTag = () => {
const shop = "myshop.myshopify.com;
const accessToken = "shpat_xxx"
console.log("beforeClient", shop, accessToken)
const client = new Shopify.Clients.Graphql(shop, accessToken);
//error occurs when calling "Shopify.Clients.Graphql(shop, accessToken);"
// ...
}
// (I have added logs to the graphQL_client.js code)

function GraphqlClient(domain, token) {
// ...
console.log("graphql_client.js", domain, token)
// ...
this.client = new http_client_1.HttpClient(this.domain);

When I call addScriptTag(shop, accessToken), I receive the following logs:

beforeClient bobbetest.myshopify.com shpat_xxx
graphql_client.js shpat_xxx myshop.myshopify.com
InvalidShopError: Domain shpat_xxx is not valid. //this error is thrown in http_client.js since the access token is no valid url
...
graphql_client.js myshop.myshopify.com shpat_xxx

What do you think should happen?
Since my code is almost equal to the Instruction guide, and equal to the Shopify Node.js and React tutorial, I expected it to work.

Actual behavior

What actually happens?
So as you can see, the GraphQl-client gets called twice. One time with the domain and accessToken parameter switched up, which causes the program to break. (see the two logs "graphQL_client.js ...") I think this is a bug not caused by my programming, since the log "beforeClient myshop.myshopify.com shpat_xxx" is only logged once.
I do not know how I should proceed to fix this bug.

the complete error message:

InvalidShopError: Domain shpat_xxx is not valid
โ”ƒ     at InvalidShopError.ShopifyError [as constructor] (.../node_modules/@shopify/shopify-api/dist/error.js:13:28)
โ”ƒ     at new InvalidShopError (.../webstory/node_modules/@shopify/shopify-api/dist/error.js:31:42)
โ”ƒ     at new HttpClient (.../node_modules/@shopify/shopify-api/dist/clients/http_client/http_client.js:25:19)
โ”ƒ     at new GraphqlClient (.../node_modules/@shopify/shopify-api/dist/clients/graphql/graphql_client.js:21:23)
โ”ƒ     at _callee$ (.../server/handlers/mutations/addScriptTag.js:26:18)
โ”ƒ     at tryCatch (.../node_modules/@babel/polyfill/node_modules/regenerator-runtime/runtime.js:63:40)
โ”ƒ     at Generator.invoke [as _invoke] (.../node_modules/@babel/polyfill/node_modules/regenerator-runtime/runtime.js:293:22)
โ”ƒ     at Generator.next (.../node_modules/@babel/polyfill/node_modules/regenerator-runtime/runtime.js:118:21)
โ”ƒ     at asyncGeneratorStep (.../server/handlers/mutations/addScriptTag.js:8:103)
โ”ƒ     at _next (.../server/handlers/mutations/addScriptTag.js:10:194)
โ”ƒ     at .../server/handlers/mutations/addScriptTag.js:10:364
โ”ƒ     at new Promise (<anonymous>)
โ”ƒ     at .../server/handlers/mutations/addScriptTag.js:10:97
โ”ƒ     at addScriptTag (.../server/handlers/mutations/addScriptTag.js:6:26)
โ”ƒ     at _callee2$ (.../server/server.js:51:9)
โ”ƒ     at tryCatch (.../node_modules/@babel/polyfill/node_modules/regenerator-runtime/runtime.js:63:40)

Typescript compulsory?

By choice we are not using Typescript. Is it compulsory to use this Module?

Follow in the documentation this does not work

const Shopify 				= require('@shopify/shopify-api');
const {API_KEY, API_SECRET_KEY, SCOPES, SHOP, HOST, PORT, APIVERSION, ISONLINE} = process.env;

Shopify.Context.initialize({
	API_KEY,
	API_SECRET_KEY,
	SCOPES: [SCOPES],
	HOST_NAME: HOST,
	IS_EMBEDDED_APP: true,
	API_VERSION: Shopify.ApiVersion[APIVERSION]
});

while this does

const Shopify 				= require('@shopify/shopify-api');
const {API_KEY, API_SECRET_KEY, SCOPES, SHOP, HOST, PORT, APIVERSION, ISONLINE} = process.env;
Shopify.default.Context.initialize({
	API_KEY,
	API_SECRET_KEY,
	SCOPES: [SCOPES],
	HOST_NAME: HOST,
	IS_EMBEDDED_APP: true,
	API_VERSION: Shopify.ApiVersion[APIVERSION]
});

Note the usage of Shopify.default. The same append for all methods, if default is not there node return this type of error: TypeError: Shopify.initialize is not a function

Is this because we are not using typescript and await syntax? How can we work with this module in plan JS?

Passing custom ARN argument when registering EventBridge webhooks

Maybe more of a question as I'm assuming I may have misinterpreted the docs.

When registering EventBridge webhooks, the arn argument looks like it's built as a URL in the following format:

https://${Context.HOST_NAME}${path}

This tutorial suggests using the Partner event source ARN which as far as I can tell, for Shopify events, follow the format:

arn:aws:events:region::event-source/aws.partner/shopify.com/id/name

Is there a way to set the ARN argument manually, or am I missing something here? Thanks.

graphqlProxy does not work in offline accessMode

Issue summary

when accessMode is offline graphql_proxy doesn't work.

Expected behavior

you need to pass a parameter to the graphqlProxy to let it know the accessMode.
https://github.com/Shopify/shopify-node-api/blob/766317d6419d0f8b1600e917f63de21f91f65106/src/utils/graphql_proxy.ts#L9

error details

As the accessMode is not being passed to the graphql_proxy, it tries to get the online session as it does not exist and throws a throw below.

https://github.com/Shopify/shopify-node-api/blob/766317d6419d0f8b1600e917f63de21f91f65106/src/utils/graphql_proxy.ts#L11

  Error: Cannot proxy query. No session found.
      at SessionNotFound.ShopifyError [as constructor] (/home/ubuntu/src//node_modules/@shopify/shopify-api/dist/error.js:13:28)
      at new SessionNotFound (/home/ubuntu/src//node_modules/@shopify/shopify-api/dist/error.js:140:42)
      at Object.<anonymous> (/home/ubuntu/src//node_modules/@shopify/shopify-api/dist/utils/graphql_proxy.js:17:31)
      at step (/home/ubuntu/src//node_modules/@shopify/shopify-api/node_modules/tslib/tslib.js:143:27)
      at Object.next (/home/ubuntu/src//node_modules/@shopify/shopify-api/node_modules/tslib/tslib.js:124:57)
      at fulfilled (/home/ubuntu/src//node_modules/@shopify/shopify-api/node_modules/tslib/tslib.js:114:62)

Error: Context has not been properly initialized. Please call the .initialize() method to setup your app context object

Issue summary

Right after migrate from old api to "Shopify.context" based api i started getting this error during attempt install embedded app

Immediately after changes have been made to the server.js, based on tutorial, i can't install app by /auth?shop=${shop} url, due to Error: Context has not been properly initialized. Please call the .initialize() method to setup your app context object

Actual behavior

.env is completed by all data in accordance with docs and nice logs before start auth process

createShopifyAuth({
      async afterAuth(ctx) {

Also Shopify.context logs ok during server starts. But /auth?shop=${shop} request crashes with above error.

package.json:

"dependencies": {
    "@apollo/client": "^3.3.9",
    "@google-cloud/storage": "^5.8.0",
    "@shopify/app-bridge-react": "^1.29.0",
    "@shopify/app-bridge-utils": "^1.29.0",
    "@shopify/koa-shopify-auth": "^4.0.3",
    "@shopify/polaris": "^6.0.1",
    "@shopify/polaris-icons": "^4.5.0",
    "@shopify/shopify-api": "^1.2.0",
    "@zeit/next-css": "^1.0.1",
    "dotenv": "^8.2.0",
    "graphql": "^15.5.0",
    "history": "^5.0.0",
    "isomorphic-fetch": "^3.0.0",
    "koa": "^2.13.1",
    "koa-router": "^10.0.0",
    "lodash": "^4.17.20",
    "next": "^10.0.6",
    "nodemon": "^2.0.7",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "request": "^2.88.2",
    "store-js": "^2.0.4",
    "uuid": "^8.3.2"
  },

Can you advise something?

InvalidJwtError: Failed to parse session token when using Shopify.Utils.loadCurrentSession

Issue summary

Hi there,

Thanks for making this node API!

I've been making a test development app to learn more about how cookieless sessions should work in Shopify apps.

However, Shopify.Utils.loadCurrentSession is suddenly failing to parse session tokens provided by making a request to API routes using authenticatedFetch from @shopify/app-bridge-utils. This was working late last week, but when I loaded my development app today, this suddenly started failing with no changes on my part.

InvalidJwtError: Failed to parse session token 'base64 string here': jwt not active    at InvalidJwtError.ShopifyError [as constructor] (\node_modules\@shopify\shopify-api\dist\error.js:13:28)

Expected behavior

authenticatedFetch should request the data from my test route and attach the Authorization header, which is then parsed within the Shopify object on the back end and return a session, which I can check the presence of, and complete the API request with dummy data.

Actual behavior

The session now fails to load in the /test route, due to a failure to parse the provided token that the authenticatedFetch is attaching to the request. The base 64 string matches in the backend error logs and on the front end network request, though:

Frontend request

Authorization | Bearer   eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczpcL1wvcHJvZHVjdC1vcHRpb25zLXRlc3QxNS5teXNob3BpZnkuY29tXC9hZG1pbiIsImRlc3QiOiJodHRwczpcL1wvcHJvZHVjdC1vcHRpb25zLXRlc3QxNS5teXNob3BpZnkuY29tIiwiYXVkIjoiODUxOWFmNzMzZTRhNThlNDBlNDlkZjhmMWVkM2U4YTAiLCJzdWIiOiIzNjA0MzQ4OTMyOCIsImV4cCI6MTYxNTgzMTAwMSwibmJmIjoxNjE1ODMwOTQxLCJpYXQiOjE2MTU4MzA5NDEsImp0aSI6IjVkZjRmODgzLTY5YWMtNGY3ZC05ZjhkLWNlYzQ2YWIwYjg3OSIsInNpZCI6ImI3YTM5ODg1MjRhNzI0NDgyZjYyMWNjODg2ZDI1M2ExOGQ5NjcyMmZkMjhmNzY2ZGZhNTEwMzQxM2FhZmVjNDUifQ.O7E8xBPyzskfomLuGwz_0BjnpY6Az6vqk9mDjHXbJTQ

server error

InvalidJwtError: Failed to parse session token 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczpcL1wvcHJvZHVjdC1vcHRpb25zLXRlc3QxNS5teXNob3BpZnkuY29tXC9hZG1pbiIsImRlc3QiOiJodHRwczpcL1wvcHJvZHVjdC1vcHRpb25zLXRlc3QxNS5teXNob3BpZnkuY29tIiwiYXVkIjoiODUxOWFmNzMzZTRhNThlNDBlNDlkZjhmMWVkM2U4YTAiLCJzdWIiOiIzNjA0MzQ4OTMyOCIsImV4cCI6MTYxNTgzMTAwMSwibmJmIjoxNjE1ODMwOTQxLCJpYXQiOjE2MTU4MzA5NDEsImp0aSI6IjVkZjRmODgzLTY5YWMtNGY3ZC05ZjhkLWNlYzQ2YWIwYjg3OSIsInNpZCI6ImI3YTM5ODg1MjRhNzI0NDgyZjYyMWNjODg2ZDI1M2ExOGQ5NjcyMmZkMjhmNzY2ZGZhNTEwMzQxM2FhZmVjNDUifQ.O7E8xBPyzskfomLuGwz_0BjnpY6Az6vqk9mDjHXbJTQ': jwt not active    at InvalidJwtError.ShopifyError [as constructor]

What actually happens?

InvalidJwtError: Failed to parse session token 'base64 string here: jwt not active    at InvalidJwtError.ShopifyError [as constructor] (\node_modules\@shopify\shopify-api\dist\error.js:13:28)
    at new InvalidJwtError (\node_modules\@shopify\shopify-api\dist\error.js:39:42)
    at Object.decodeSessionToken (\node_modules\@shopify\shopify-api\dist\utils\decode-session-token.js:20:15)
    at Object.getCurrentSessionId (\node_modules\@shopify\shopify-api\dist\auth\oauth\oauth.js:218:64)
    at Object.<anonymous> (\node_modules\@shopify\shopify-api\dist\utils\load-current-session.js:19:46)
    at step (\node_modules\@shopify\shopify-api\node_modules\tslib\tslib.js:143:27)
    at Object.next (\node_modules\@shopify\shopify-api\node_modules\tslib\tslib.js:124:57)
    at \node_modules\@shopify\shopify-api\node_modules\tslib\tslib.js:117:75
    at new Promise (<anonymous>)
    at Object.__awaiter (\node_modules\@shopify\shopify-api\node_modules\tslib\tslib.js:113:16)
    at Object.loadCurrentSession (\node_modules\@shopify\shopify-api\dist\utils\load-current-session.js:15:20)

Steps to reproduce the problem

  1. Install Development app into dev store and authenticate
  2. When index.js loads, getTestData will make the request to the api route specified in server.js
  3. Session token will fail parsing from inside Shopify.Utils.loadCurrentSession. This was working on Friday with the below reduced test case, but now it's not.

Reduced test case

server.js

require("isomorphic-fetch");
const dotenv = require("dotenv");
dotenv.config();
const Koa = require("koa");
const next = require("next");
const { default: createShopifyAuth } = require("@shopify/koa-shopify-auth");
const { verifyRequest } = require("@shopify/koa-shopify-auth");
const { default: Shopify, ApiVersion } = require("@shopify/shopify-api");
const Router = require("@koa/router");

const { SHOPIFY_APP_SECRET, SHOPIFY_APP_KEY } = process.env;

Shopify.Context.initialize({
  API_KEY: process.env.SHOPIFY_APP_KEY,
  API_SECRET_KEY: process.env.SHOPIFY_APP_SECRET,
  SCOPES: ["read_products", "write_products"],
  HOST_NAME: process.env.BASE_URL.replace(/https:\/\//, ""),
  API_VERSION: ApiVersion.January21,
  IS_EMBEDDED_APP: true,
  SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
});

const ACTIVE_SHOPIFY_SHOPS = {};

app.prepare().then(() => {
  const server = new Koa();
  const router = new Router();

  server.keys = [Shopify.Context.API_SECRET_KEY];

  server.use(
    createShopifyAuth({
      accessMode: "offline",
      async afterAuth(ctx) {
        const { shop, scope, accessToken } = ctx.state.shopify;
        console.log({ shopify: ctx.state.shopify });
        ACTIVE_SHOPIFY_SHOPS[shop] = scope;
        console.log({ ACTIVE_SHOPIFY_SHOPS });

        ctx.redirect(`/?shop=${shop}`);     
      },
    })
   
  );

  const handleRequest = async (ctx) => {
    await handle(ctx.req, ctx.res);
    ctx.respond = false;
    ctx.res.statusCode = 200;
  };

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

    if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
      ctx.redirect(`/auth?shop=${shop}`);
    } else {
      await handleRequest(ctx);
    }
  });
  
  router.get("/test", async (ctx) => {
    console.log("before load session");
    const session = await Shopify.Utils.loadCurrentSession(
      ctx.request,
      ctx.response,
      false
    );
    console.log({ session });
  
    return (ctx.body = { it: "worked" });
  });


  router.get("(/_next/static/.*)", handleRequest);
  router.get("/_next/webpack-hmr", handleRequest);
  router.get("/__nextjs_original-stack-frame", handleRequest);
  router.get("(.*)", verifyRequest(), handleRequest);

  server.use(router.allowedMethods());
  server.use(router.routes());

  server.listen(port, () => {
    console.log(`> Ready on http://localhost:${port}`);
  });
});

index.js - Main Page of React App

import { authenticatedFetch } from "@shopify/app-bridge-utils";

  async componentDidMount() {
    this.getTestData();
  }

  getTestData = async () => {
    const app = this.context;
    const response = await authenticatedFetch(app)(
      `${APP_URL}/test`
    ).then(
      (result) => {
        console.log({ result });
      },
      (error) => {
        this.setState({ error });
      }
    );
  };

Checklist

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

Expand GraphqlParams allowed record types

Overview

I saw that a PR is currently in progress for updating documentation related to the GraphQL client, so thought I'd flag here.

The GraphqlParams type defines an optional query property with type Record<string, string | number>. In other words, query parameters must be an object with values of either string or number.

Some mutations require passing an object as a parameter value. For example eventBridgeWebhookSubscriptionCreate requires a webhookSubscription parameter of type EventBridgeWebhookSubscriptionInput. Attempting to pass a value like that results in type errors.

I'm not completely familiar with all queries/mutations offered by the Admin API GraphQL endpoint and their required parameters, so I'm not sure what this type should be.

Type

  • Changes to existing features

Motivation

I'm attempting to use the GraphQL client provided by this library to create EventBridge subscriptions, but I'm seeing type errors. Apologies if I've misunderstood how the GraphQL client works, I understand documentation is still in progress. Thanks!

Checklist

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

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.