这对我不起作用 SESSION_STORAGE:

问题描述 投票:0回答:1

我正在尝试创建一个新会话以将该会话存储在数据库 Postgres 中。我正在尝试使用此代码,但没有任何效果,这令人沮丧。

我几乎确定我需要发送第二个参数,但我不知道它会是什么!

我的代码:

  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.April22,
  IS_EMBEDDED_APP: true,
  // This should be replaced with your preferred storage strategy
  SESSION_STORAGE: new Shopify.Session.PostgreSQLSessionStorage(
    new URL(process.env.DATABASE_URL)
  ),
});

错误:

┃ file:///Users/blablabla/Documents/Apps/cli_shopify_mayo2022/cli-19may/server/middleware/verify-request.js:25
┃     if (session?.isActive()) {
┃                  ^
┃ 
┃ TypeError: session?.isActive is not a function
┃     at file:///Users/diegotorres/Documents/Apps/cli_shopify_mayo2022/cli-19may/server/middleware/verify-request.js:25:18
┃     at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
┃ 
┃ Node.js v18.0.0
┃ [nodemon] app crashed - waiting for file changes before starting...

postgres 文件

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PostgreSQLSessionStorage = void 0;
var tslib_1 = require("tslib");
var pg_1 = tslib_1.__importDefault(require("pg"));
var session_utils_1 = require("../session-utils");
var defaultPostgreSQLSessionStorageOptions = {
    sessionTableName: 'shopify_sessions',
    port: 3211,
};
var PostgreSQLSessionStorage = /** @class */ (function () {
    function PostgreSQLSessionStorage(dbUrl, opts) {
        if (opts === void 0) { opts = {}; }
        this.dbUrl = dbUrl;
        if (typeof this.dbUrl === 'string') {
            this.dbUrl = new URL(this.dbUrl);
        }
        this.options = tslib_1.__assign(tslib_1.__assign({}, defaultPostgreSQLSessionStorageOptions), opts);
        this.ready = this.init();
    }
    PostgreSQLSessionStorage.withCredentials = function (host, dbName, username, password, opts) {
        return new PostgreSQLSessionStorage(new URL("postgres://".concat(encodeURIComponent(username), ":").concat(encodeURIComponent(password), "@").concat(host, "/").concat(encodeURIComponent(dbName))), opts);
    };
    PostgreSQLSessionStorage.prototype.storeSession = function (session) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var entries, query;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, this.ready];
                    case 1:
                        _a.sent();
                        entries = (0, session_utils_1.sessionEntries)(session);
                        query = "\n      INSERT INTO ".concat(this.options.sessionTableName, "\n      (").concat(entries.map(function (_a) {
                            var _b = tslib_1.__read(_a, 1), key = _b[0];
                            return key;
                        }).join(', '), ")\n      VALUES (").concat(entries.map(function (_, i) { return "$".concat(i + 1); }).join(', '), ")\n      ON CONFLICT (id) DO UPDATE SET ").concat(entries
                            .map(function (_a) {
                            var _b = tslib_1.__read(_a, 1), key = _b[0];
                            return "".concat(key, " = Excluded.").concat(key);
                        })
                            .join(', '), ";\n    ");
                        return [4 /*yield*/, this.query(query, entries.map(function (_a) {
                                var _b = tslib_1.__read(_a, 2), _key = _b[0], value = _b[1];
                                return value;
                            }))];
                    case 2:
                        _a.sent();
                        return [2 /*return*/, true];
                }
            });
        });
    };
    PostgreSQLSessionStorage.prototype.loadSession = function (id) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var query, rows, rawResult;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, this.ready];
                    case 1:
                        _a.sent();
                        query = "\n      SELECT * FROM ".concat(this.options.sessionTableName, "\n      WHERE id = $1;\n    ");
                        return [4 /*yield*/, this.query(query, [id])];
                    case 2:
                        rows = _a.sent();
                        if (!Array.isArray(rows) || (rows === null || rows === void 0 ? void 0 : rows.length) !== 1)
                            return [2 /*return*/, undefined];
                        rawResult = rows[0];
                        return [2 /*return*/, (0, session_utils_1.sessionFromEntries)(Object.entries(rawResult))];
                }
            });
        });
    };
    PostgreSQLSessionStorage.prototype.deleteSession = function (id) {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var query;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, this.ready];
                    case 1:
                        _a.sent();
                        query = "\n      DELETE FROM ".concat(this.options.sessionTableName, "\n      WHERE id = $1;\n    ");
                        return [4 /*yield*/, this.query(query, [id])];
                    case 2:
                        _a.sent();
                        return [2 /*return*/, true];
                }
            });
        });
    };
    PostgreSQLSessionStorage.prototype.disconnect = function () {
        return this.client.end();
    };
    PostgreSQLSessionStorage.prototype.init = function () {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0:
                        this.client = new pg_1.default.Client({ connectionString: this.dbUrl.toString() });
                        return [4 /*yield*/, this.connectClient()];
                    case 1:
                        _a.sent();
                        return [4 /*yield*/, this.createTable()];
                    case 2:
                        _a.sent();
                        return [2 /*return*/];
                }
            });
        });
    };
    PostgreSQLSessionStorage.prototype.connectClient = function () {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, this.client.connect()];
                    case 1:
                        _a.sent();
                        return [2 /*return*/];
                }
            });
        });
    };
    PostgreSQLSessionStorage.prototype.hasSessionTable = function () {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var query, _a, rows;
            return tslib_1.__generator(this, function (_b) {
                switch (_b.label) {
                    case 0:
                        query = "\n      SELECT * FROM pg_catalog.pg_tables WHERE tablename = $1\n    ";
                        return [4 /*yield*/, this.query(query, [this.options.sessionTableName])];
                    case 1:
                        _a = tslib_1.__read.apply(void 0, [_b.sent(), 1]), rows = _a[0];
                        return [2 /*return*/, Array.isArray(rows) && rows.length === 1];
                }
            });
        });
    };
    PostgreSQLSessionStorage.prototype.createTable = function () {
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var hasSessionTable, query;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, this.hasSessionTable()];
                    case 1:
                        hasSessionTable = _a.sent();
                        if (!!hasSessionTable) return [3 /*break*/, 3];
                        query = "\n        CREATE TABLE ".concat(this.options.sessionTableName, " (\n          id varchar(255) NOT NULL PRIMARY KEY,\n          shop varchar(255) NOT NULL,\n          state varchar(255) NOT NULL,\n          isOnline boolean NOT NULL,\n          scope varchar(255),\n          accessToken varchar(255)\n        )\n      ");
                        return [4 /*yield*/, this.query(query)];
                    case 2:
                        _a.sent();
                        _a.label = 3;
                    case 3: return [2 /*return*/];
                }
            });
        });
    };
    PostgreSQLSessionStorage.prototype.query = function (sql, params) {
        if (params === void 0) { params = []; }
        return tslib_1.__awaiter(this, void 0, void 0, function () {
            var result;
            return tslib_1.__generator(this, function (_a) {
                switch (_a.label) {
                    case 0: return [4 /*yield*/, this.client.query(sql, params)];
                    case 1:
                        result = _a.sent();
                        return [2 /*return*/, result.rows];
                }
            });
        });
    };
    return PostgreSQLSessionStorage;
}());
exports.PostgreSQLSessionStorage = PostgreSQLSessionStorage;

所以,调查了一下我发现了以下文件,这是应用程序崩溃的地方

import { Shopify } from "@shopify/shopify-api";

const TEST_GRAPHQL_QUERY = `
{
  shop {
    name
  }
}`;

export default function verifyRequest(app, { returnHeader = true } = {}) {
  return async (req, res, next) => {
    const session = await Shopify.Utils.loadCurrentSession(
      req,
      res,
      app.get("use-online-tokens")
    );

    let shop = req.query.shop;

    if (session && shop && session.shop !== shop) {
      // The current request is for a different shop. Redirect gracefully.
      return res.redirect(`/auth?shop=${shop}`);
    }

    if (session?.isActive()) {
      try {
        // make a request to make sure oauth has succeeded, retry otherwise
        const client = new Shopify.Clients.Graphql(
          session.shop,
          session.accessToken
        );
        await client.query({ data: TEST_GRAPHQL_QUERY });
        return next();
      } catch (e) {
        if (
          e instanceof Shopify.Errors.HttpResponseError &&
          e.response.code === 401
        ) {
          // We only want to catch 401s here, anything else should bubble up
        } else {
          throw e;
        }
      }
    }

    if (returnHeader) {
      if (!shop) {
        if (session) {
          shop = session.shop;
        } else if (Shopify.Context.IS_EMBEDDED_APP) {
          const authHeader = req.headers.authorization;
          const matches = authHeader?.match(/Bearer (.*)/);
          if (matches) {
            const payload = Shopify.Utils.decodeSessionToken(matches[1]);
            shop = payload.dest.replace("https://", "");
          }
        }
      }

      if (!shop || shop === "") {
        return res
          .status(400)
          .send(
            `Could not find a shop to authenticate with. Make sure you are making your XHR request with App Bridge's authenticatedFetch method.`
          );
      }

      res.status(403);
      res.header("X-Shopify-API-Request-Failure-Reauthorize", "1");
      res.header(
        "X-Shopify-API-Request-Failure-Reauthorize-Url",
        `/auth?shop=${shop}`
      );
      res.end();
    } else {
      res.redirect(`/auth?shop=${shop}`);
    }
  };
}
postgresql shopify shopify-app session-storage shopify-api-node
1个回答
0
投票

您应该使用 Session 中的cloneSession。

这是我的工作项目和 postgresql 会话的自定义实现的完整代码。

// tslint:disable-next-line:no-submodule-imports
import { Session } from '@shopify/shopify-api/dist/auth/session/index.js';
import pg from 'pg';

// tslint:disable-next-line:interface-name
interface SessionInterface {
  readonly id: string;
  shop: string;
  state: string;
  isOnline: boolean;
  scope?: string;
  expires?: Date;
  accessToken?: string;
  onlineAccessInfo?: any;
  isActive(): boolean;
}

// tslint:disable-next-line:interface-name
interface SessionStorage {
  /**
   * Creates or updates the given session in storage.
   *
   * @param session Session to store
   */
  storeSession(session: SessionInterface): Promise<boolean>;

  /**
   * Loads a session from storage.
   *
   * @param id Id of the session to load
   */
  loadSession(id: string): Promise<SessionInterface | undefined>;

  /**
   * Deletes a session from storage.
   *
   * @param id Id of the session to delete
   */
  deleteSession(id: string): Promise<boolean>;
}

function fromEntries<T>(entries: Array<[keyof T, T[keyof T]]>): T {
  return entries.reduce(
    (acc, [key, value]) => ({ ...acc, [key]: value }),
    <T>{}
  );
}

function sessionFromEntries(
  entries: Array<[string, string | number]>,
): SessionInterface {
  const obj = fromEntries(
    entries
    // tslint:disable-next-line:variable-name
      .filter(([_key, value]) => value !== null)
      .map(([key, value]) => {
        switch (key.toLowerCase()) {
          case 'isonline':
            return ['isOnline', value];
          case 'accesstoken':
            return ['accessToken', value];
          default:
            return [key.toLowerCase(), value];
        }
      }),
  ) as any;

  if (typeof obj.isOnline === 'string') {
    obj.isOnline = obj.isOnline.toString().toLowerCase() === 'true';
  } else if (typeof obj.isOnline === 'number') {
    obj.isOnline = Boolean(obj.isOnline);
  }

  if (obj.scope) { obj.scope = obj.scope.toString(); }

  return obj;
}

const includedKeys = [
  'id',
  'shop',
  'state',
  'isOnline',
  'scope',
  'accessToken',
];
function sessionEntries(
  session: SessionInterface,
): Array<[string, string | number]> {
  return Object.entries(session).filter(([key]) => includedKeys.includes(key));
}

// tslint:disable-next-line:interface-name
interface PostgreSQLSessionStorageOptions {
  sessionTableName: string;
  port: number;
}
const defaultPostgreSQLSessionStorageOptions: PostgreSQLSessionStorageOptions =
  {
    sessionTableName: 'shopify_sessions',
    port: 3211,
  };

export class PostgreSQLSessionStorage implements SessionStorage {
  public static withCredentials(
    host: string,
    dbName: string,
    username: string,
    password: string,
    opts: Partial<PostgreSQLSessionStorageOptions>,
  ) {
    return new PostgreSQLSessionStorage(
      new URL(
        `postgres://${encodeURIComponent(username)}:${encodeURIComponent(
          password,
        )}@${host}/${encodeURIComponent(dbName)}`,
      ),
      opts,
    );
  }

  public readonly ready: Promise<void>;
  private options: PostgreSQLSessionStorageOptions;
  private client: pg.Client;

  constructor(
    private dbUrl: URL,
    opts: Partial<PostgreSQLSessionStorageOptions> = {},
  ) {
    if (typeof this.dbUrl === 'string') {
      this.dbUrl = new URL(this.dbUrl);
    }
    this.options = {...defaultPostgreSQLSessionStorageOptions, ...opts};
    this.ready = this.init();
  }

  public async storeSession(session: SessionInterface): Promise<boolean> {
    await this.ready;

    const entries = sessionEntries(session);
    const query = `
      INSERT INTO ${this.options.sessionTableName}
      (${entries.map(([key]) => key).join(', ')})
      VALUES (${entries.map((_, i) => `$${i + 1}`).join(', ')})
      ON CONFLICT (id) DO UPDATE SET ${entries
      .map(([key]) => `${key} = Excluded.${key}`)
      .join(', ')};
    `;
    await this.query(
      query,
      // tslint:disable-next-line:variable-name
      entries.map(([_key, value]) => value),
    );
    return true;
  }

  public async loadSession(id: string): Promise<Session | undefined> {
    await this.ready;
    const query = `
      SELECT * FROM ${this.options.sessionTableName}
      WHERE id = $1;
    `;
    const rows = await this.query(query, [id]);
    if (!Array.isArray(rows) || rows?.length !== 1) { return undefined; }
    const rawResult = rows[0] as any;
    const sessionData = sessionFromEntries(Object.entries(rawResult));
    return Session.cloneSession(sessionData, sessionData.id);
  }

  public async deleteSession(id: string): Promise<boolean> {
    await this.ready;
    const query = `
      DELETE FROM ${this.options.sessionTableName}
      WHERE id = $1;
    `;
    await this.query(query, [id]);
    return true;
  }

  public disconnect(): Promise<void> {
    return this.client.end();
  }

  private async init() {
    this.client = new pg.Client({connectionString: this.dbUrl.toString(), ssl: true});
    await this.connectClient();
    await this.createTable();
  }

  private async connectClient(): Promise<void> {
    await this.client.connect();
  }

  private async hasSessionTable(): Promise<boolean> {
    const query = `
      SELECT * FROM pg_catalog.pg_tables WHERE tablename = $1
    `;
    const [rows] = await this.query(query, [this.options.sessionTableName]);
    return Array.isArray(rows) && rows.length === 1;
  }

  private async createTable() {
    const hasSessionTable = await this.hasSessionTable();
    if (!hasSessionTable) {
      const query = `
        CREATE TABLE IF NOT EXISTS ${this.options.sessionTableName} (
          id varchar(255) NOT NULL PRIMARY KEY,
          shop varchar(255) NOT NULL,
          state varchar(255) NOT NULL,
          isOnline boolean NOT NULL,
          scope varchar(255),
          accessToken varchar(255)
        )
      `;
      await this.query(query);
    }
  }

  private async query(sql: string, params: any[] = []): Promise<any> {
    const result = await this.client.query(sql, params);
    return result.rows;
  }
}
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.