我正在尝试创建一个新会话以将该会话存储在数据库 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}`);
}
};
}
您应该使用 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;
}
}