shopify-app-template-node icon indicating copy to clipboard operation
shopify-app-template-node copied to clipboard

Example code for CustomSessionStorage of redis

Open NoharaMasato opened this issue 4 years ago • 3 comments
trafficstars

Issue summary

I want example code which store session into redis.

I add the following code to this app. I refered to this page, but the code is ts.

// server.js
const sessionStorage = new RedisStore();

Shopify.Context.initialize({
  API_KEY: process.env.SHOPIFY_API_KEY,
  API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
  SCOPES: process.env.SCOPES.split(","),
  HOST_NAME: process.env.HOST.replace(/https:\/\//, ""),
  API_VERSION: ApiVersion.October20,
  IS_EMBEDDED_APP: true,
  // This should be replaced with your preferred storage strategy
  // SESSION_STORAGE: new Shopify.Session.MemorySessionStorage(),
  SESSION_STORAGE: new Shopify.Session.CustomSessionStorage(
    sessionStorage.storeCallback,
    sessionStorage.loadCallback,
    sessionStorage.deleteCallback,
  ),
});
// redis-store.js
// Import the Session type from the library, along with the Node redis package, and `promisify` from Node
// import {Session} from '@shopify/shopify-api/dist/auth/session';
import redis from 'redis';
import {promisify} from 'util';

class RedisStore {
  // private client: redis.RedisClient;
  // private getAsync;
  // private setAsync;
  // private delAsync;
  constructor() {
    // Create a new redis client
    this.client = redis.createClient();
    // Use Node's `promisify` to have redis return a promise from the client methods
    this.getAsync = promisify(this.client.get).bind(this.client);
    this.setAsync = promisify(this.client.set).bind(this.client);
    this.delAsync = promisify(this.client.del).bind(this.client);
  }

  /*
    The storeCallback takes in the Session, and sets a stringified version of it on the redis store
    This callback is used for BOTH saving new Sessions and updating existing Sessions.
    If the session can be stored, return true
    Otherwise, return false
  */
  async storeCallback(session) {
    try {
      // Inside our try, we use the `setAsync` method to save our session.
      // This method returns a boolean (true is successful, false if not)
      console.log(this.setAsync)
      return await this.setAsync(session.id, JSON.stringify(session))
    } catch (err) {
      // throw errors, and handle them gracefully in your application
      throw new Error(err)
    }
  };

  /*
    The loadCallback takes in the id, and uses the getAsync method to access the session data
     If a stored session exists, it's parsed and returned
     Otherwise, return undefined
  */
  async loadCallback(id) {
    try {
      // Inside our try, we use `getAsync` to access the method by id
      // If we receive data back, we parse and return it
      // If not, we return `undefined`
      let reply = await this.getAsync(id);
      if (reply) {
        return JSON.parse(reply);
      } else {
        return undefined
      }
    } catch (err) {
      throw new Error(err)
    }
  };

  /*
    The deleteCallback takes in the id, and uses the redis `del` method to delete it from the store
    If the session can be deleted, return true
    Otherwise, return false
  */
  async deleteCallback (id) {
    try {
      // Inside our try, we use the `delAsync` method to delete our session.
      // This method returns a boolean (true is successful, false if not)
      return await this.delAsync(id)
    } catch (err) {
      throw new Error(err)
    }
  };
}

// Export the class
export default RedisStore;

My code causes following error.

  Error: CustomSessionStorage failed to store a session. Error Details: Error: TypeError: this.setAsync is not a function
      at SessionStorageError.ShopifyError [as constructor] (/home/nohara/work/orion-web/node_modules/@shopify/shopify-api/dist/error.js:13:28)
      at new SessionStorageError (/home/nohara/work/orion-web/node_modules/@shopify/shopify-api/dist/error.js:172:42)
      at CustomSessionStorage.<anonymous> (/home/nohara/work/orion-web/node_modules/@shopify/shopify-api/dist/auth/session/storage/custom.js:27:31)
      at step (/home/nohara/work/orion-web/node_modules/@shopify/shopify-api/node_modules/tslib/tslib.js:143:27)
      at Object.throw (/home/nohara/work/orion-web/node_modules/@shopify/shopify-api/node_modules/tslib/tslib.js:124:57)
      at rejected (/home/nohara/work/orion-web/node_modules/@shopify/shopify-api/node_modules/tslib/tslib.js:115:69)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)

NoharaMasato avatar Jul 09 '21 06:07 NoharaMasato

You can find a good example here:

https://github.com/t-kelly/nextjs-shopify-app/blob/main/lib/redis.js https://github.com/t-kelly/nextjs-shopify-app/blob/main/lib/shopify.js#L12

dan-gamble avatar Jul 09 '21 08:07 dan-gamble

Thanks for reporting this, we'll look into what might be going wrong here. In the meantime, Thomas' class is definitely a good alternative, thanks @dan-gamble!

paulomarg avatar Jul 13 '21 13:07 paulomarg

@NoharaMasato I think you need to bind your methods:

SESSION_STORAGE: new Shopify.Session.CustomSessionStorage(
    sessionStorage.storeCallback.bind(sessionStorage),
    sessionStorage.loadCallback.bind(sessionStorage),
    sessionStorage.deleteCallback.bind(sessionStorage),
  ),

Or alternatively sack off OO as it's just creating confusion:

const createRedisStore = (config) => {
    // Create a new redis client
    const client = redis.createClient(config);
    // Use Node's `promisify` to have redis return a promise from the client methods
    const getAsync = promisify(this.client.get).bind(this.client);
    const setAsync = promisify(this.client.set).bind(this.client);
    const delAsync = promisify(this.client.del).bind(this.client);
    const storeCallback = () => { ... }
    const loadCallback = () => { ... }
    const deleteCallback = () => { ... }
    return { storeCallback, loadCallback, deleteCallback };
}

const redisStore = createRedisStore({});

// ...

SESSION_STORAGE: new Shopify.Session.CustomSessionStorage(
    redisStore.storeCallback,
    redisStore.loadCallback,
    redisStore.deleteCallback,
  ),

richardscarrott avatar Jul 21 '22 16:07 richardscarrott

This issue is stale because it has been open for 60 days with no activity. It will be closed if no further action occurs in 14 days.

github-actions[bot] avatar Oct 07 '22 02:10 github-actions[bot]

We are closing this issue because it has been inactive for a few months. This probably means that it is not reproducible or it has been fixed in a newer version. If it’s an enhancement and hasn’t been taken on since it was submitted, then it seems other issues have taken priority.

If you still encounter this issue with the latest stable version, please reopen using the issue template. You can also contribute directly by submitting a pull request– see the CONTRIBUTING.md file for guidelines

Thank you!

github-actions[bot] avatar Oct 22 '22 02:10 github-actions[bot]