@shopify/shopify-app-session-storage-prisma
Version:
Shopify App Session Storage for Prisma
204 lines (200 loc) • 7.37 kB
JavaScript
'use strict';
var shopifyApi = require('@shopify/shopify-api');
var client = require('@prisma/client');
const UNIQUE_KEY_CONSTRAINT_ERROR_CODE = 'P2002';
class PrismaSessionStorage {
prisma;
ready;
tableName = 'session';
connectionRetries = 2;
connectionRetryIntervalMs = 5000;
constructor(prisma, { tableName, connectionRetries, connectionRetryIntervalMs, } = {}) {
this.prisma = prisma;
if (tableName) {
this.tableName = tableName;
}
if (connectionRetries !== undefined) {
this.connectionRetries = connectionRetries;
}
if (connectionRetryIntervalMs !== undefined) {
this.connectionRetryIntervalMs = connectionRetryIntervalMs;
}
if (this.getSessionTable() === undefined) {
throw new Error(`PrismaClient does not have a ${this.tableName} table`);
}
this.ready = this.pollForTable()
.then(() => true)
.catch((cause) => {
throw new MissingSessionTableError(`Prisma ${this.tableName} table does not exist. This could happen for a few reasons, see https://github.com/Shopify/shopify-app-js/tree/main/packages/apps/session-storage/shopify-app-session-storage-prisma#troubleshooting for more information`, cause);
});
}
async storeSession(session) {
await this.ensureReady();
const data = this.sessionToRow(session);
try {
await this.getSessionTable().upsert({
where: { id: session.id },
update: data,
create: data,
});
}
catch (error) {
if (error instanceof client.Prisma.PrismaClientKnownRequestError &&
error.code === UNIQUE_KEY_CONSTRAINT_ERROR_CODE) {
console.log('Caught PrismaClientKnownRequestError P2002 - Unique Key Key Constraint, retrying upsert.');
await this.getSessionTable().upsert({
where: { id: session.id },
update: data,
create: data,
});
return true;
}
throw error;
}
return true;
}
async loadSession(id) {
await this.ensureReady();
const row = await this.getSessionTable().findUnique({
where: { id },
});
if (!row) {
return undefined;
}
return this.rowToSession(row);
}
async deleteSession(id) {
await this.ensureReady();
try {
await this.getSessionTable().delete({ where: { id } });
}
catch {
return true;
}
return true;
}
async deleteSessions(ids) {
await this.ensureReady();
await this.getSessionTable().deleteMany({ where: { id: { in: ids } } });
return true;
}
async findSessionsByShop(shop) {
await this.ensureReady();
const sessions = await this.getSessionTable().findMany({
where: { shop },
take: 25,
orderBy: [{ expires: 'desc' }],
});
return sessions.map((session) => this.rowToSession(session));
}
async isReady() {
try {
await this.pollForTable();
this.ready = Promise.resolve(true);
}
catch (_error) {
this.ready = Promise.resolve(false);
}
return this.ready;
}
async ensureReady() {
if (!(await this.ready))
throw new MissingSessionStorageError('Prisma session storage is not ready. Use the `isReady` method to poll for the table.');
}
async pollForTable() {
for (let i = 0; i < this.connectionRetries; i++) {
try {
await this.getSessionTable().count();
return;
}
catch (error) {
console.log(`Error obtaining session table: ${error}`);
}
await sleep(this.connectionRetryIntervalMs);
}
throw Error(`The table \`${this.tableName}\` does not exist in the current database.`);
}
sessionToRow(session) {
const sessionParams = session.toObject();
return {
id: session.id,
shop: session.shop,
state: session.state,
isOnline: session.isOnline,
scope: session.scope || null,
expires: session.expires || null,
accessToken: session.accessToken || '',
userId: sessionParams.onlineAccessInfo?.associated_user
.id || null,
firstName: sessionParams.onlineAccessInfo?.associated_user.first_name || null,
lastName: sessionParams.onlineAccessInfo?.associated_user.last_name || null,
email: sessionParams.onlineAccessInfo?.associated_user.email || null,
accountOwner: sessionParams.onlineAccessInfo?.associated_user.account_owner || false,
locale: sessionParams.onlineAccessInfo?.associated_user.locale || null,
collaborator: sessionParams.onlineAccessInfo?.associated_user.collaborator || false,
emailVerified: sessionParams.onlineAccessInfo?.associated_user.email_verified || false,
refreshToken: sessionParams.refreshToken || null,
refreshTokenExpires: sessionParams.refreshTokenExpires || null,
};
}
rowToSession(row) {
const sessionParams = {
id: row.id,
shop: row.shop,
state: row.state,
isOnline: row.isOnline,
userId: String(row.userId),
firstName: String(row.firstName),
lastName: String(row.lastName),
email: String(row.email),
locale: String(row.locale),
};
if (row.accountOwner !== null) {
sessionParams.accountOwner = row.accountOwner;
}
if (row.collaborator !== null) {
sessionParams.collaborator = row.collaborator;
}
if (row.emailVerified !== null) {
sessionParams.emailVerified = row.emailVerified;
}
if (row.expires) {
sessionParams.expires = row.expires.getTime();
}
if (row.scope) {
sessionParams.scope = row.scope;
}
if (row.accessToken) {
sessionParams.accessToken = row.accessToken;
}
if (row.refreshToken) {
sessionParams.refreshToken = row.refreshToken;
}
if (row.refreshTokenExpires) {
sessionParams.refreshTokenExpires = row.refreshTokenExpires.getTime();
}
return shopifyApi.Session.fromPropertyArray(Object.entries(sessionParams), true);
}
getSessionTable() {
return this.prisma[this.tableName];
}
}
class MissingSessionTableError extends Error {
cause;
constructor(message, cause) {
super(message);
this.cause = cause;
}
}
class MissingSessionStorageError extends Error {
constructor(message) {
super(message);
}
}
async function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
exports.MissingSessionStorageError = MissingSessionStorageError;
exports.MissingSessionTableError = MissingSessionTableError;
exports.PrismaSessionStorage = PrismaSessionStorage;
//# sourceMappingURL=prisma.js.map