App reviews, troubleshooting, and recommendations
My app initially has limited access scopes. When I update the access scopes in the shopify.app.toml file and `.env` file and redeploy, the merchant sees the approval screen when they open my app. When they click on "update," the new access scopes are granted, but the createSession method in CustomSessionStorage is not invoked, so the access scopes for the merchant are not updated in the database. Additionally, the afterAuth hook is only triggered during app installation. Is there any hook, method, or API that is invoked when a merchant's access scopes are updated? This is crucial for me as I store access scopes in the database and need to run setup code or enable features based on the granted access scopes.
const shopify = shopifyApp({
apiKey: process.env.SHOPIFY_API_KEY,
apiSecretKey: process.env.SHOPIFY_API_SECRET || "",
apiVersion: ApiVersion.April24,
scopes: process.env.SCOPES?.split(","),
appUrl: process.env.SHOPIFY_APP_URL || "",
authPathPrefix: "/auth",
sessionStorage: new CustomSessionStorage(),
distribution: AppDistribution.AppStore,
restResources,
future: {
unstable_newEmbeddedAuthStrategy: true,
},
...(process.env.SHOP_CUSTOM_DOMAIN
? { customShopDomains: [process.env.SHOP_CUSTOM_DOMAIN] }
: {}),
hooks: {
afterAuth(ctx) {
console.log("afterAuth", ctx);
},
},
isEmbeddedApp: true,
});
Ideally, the afterAuth name should be something `afterInstall` as it is very misleading because it is only invoked during app installation.
Here is my custom session storage class:
import type { SessionStorage } from "@shopify/shopify-app-session-storage";
import prisma from "./db.server";
import { Session } from "@shopify/shopify-api";
export default class CustomSessionStorage implements SessionStorage {
async deleteSession(id: string): Promise<boolean> {
await prisma.session.update({
where: { id: id },
data: { isDeleted: true },
});
return true;
}
async deleteSessions(ids: string[]): Promise<boolean> {
await prisma.session.updateMany({
where: { id: { in: ids } },
data: { isDeleted: true },
});
return true;
}
async findSessionsByShop(shop: string): Promise<Session[]> {
const sessions = await prisma.session.findMany({
where: { shop, isDeleted: false },
});
return sessions.map(
(session) =>
new Session({
id: session.id,
accessToken: session.accessToken,
state: session.state,
shop: session.shop,
isOnline: true,
}),
);
}
async loadSession(id: string): Promise<Session | undefined> {
const session = await prisma.session.findUnique({
where: { id, isDeleted: false },
});
if (!session) return undefined;
console.log(`Session loaded: ${session.id}`);
return new Session({
id: session.id,
accessToken: session.accessToken,
state: session.state,
shop: session.shop,
isOnline: true,
scope: session.scope ? session.scope : undefined,
});
}
async storeSession(session: Session): Promise<boolean> {
console.log(`Storing session: ${session.id}`, session);
await prisma.session.upsert({
where: { id: session.id },
update: {
accessToken: session.accessToken,
scope: session.scope,
isDeleted: false,
state: session.state,
},
create: {
id: session.id,
accessToken: session.accessToken!,
scope: session.scope,
isDeleted: false,
state: session.state,
shop: session.shop,
installedAt: new Date(),
},
});
console.log(`Session stored: ${session.id}`);
return true;
}
}
Here is my scopes
SCOPES=read_metaobject_definitions,read_metaobjects,read_products,unauthenticated_read_metaobjects,unauthenticated_read_product_listings,unauthenticated_read_product_tags,write_metaobject_definitions,write_metaobjects
Part of .toml file
[access_scopes]
# Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes
scopes = "read_metaobject_definitions,read_metaobjects,read_products,unauthenticated_read_metaobjects,unauthenticated_read_product_listings,unauthenticated_read_product_tags,write_metaobject_definitions,write_metaobjects"
Update
I'm not sure if this is the correct approach, but here’s my current solution:
I’ve added a new column named isScopeUpdated to the Session table. Whenever I deploy the app with updated scopes, I set isScopeUpdated to false for all stores. When fetching the session in loadSession, I only fetch sessions where isScopeUpdated is true. This forces Shopify to re-trigger token generation and fire the afterAuth hook.
Updated CustomSessionStorage
import type { SessionStorage } from "@shopify/shopify-app-session-storage";
import prisma from "./db.server";
import { Session } from "@shopify/shopify-api";
export default class CustomSessionStorage implements SessionStorage {
async deleteSession(id: string): Promise<boolean> {
await prisma.session.update({
where: { id: id },
data: { isDeleted: true },
});
return true;
}
async deleteSessions(ids: string[]): Promise<boolean> {
await prisma.session.updateMany({
where: { id: { in: ids } },
data: { isDeleted: true },
});
return true;
}
async findSessionsByShop(shop: string): Promise<Session[]> {
const sessions = await prisma.session.findMany({
where: { shop, isDeleted: false, isScopeUpdated: true },
});
return sessions.map(
(session) =>
new Session({
id: session.id,
accessToken: session.accessToken,
state: session.state,
shop: session.shop,
isOnline: true,
}),
);
}
async loadSession(id: string): Promise<Session | undefined> {
const session = await prisma.session.findUnique({
where: { id, isDeleted: false, isScopeUpdated: true },
});
if (!session) return undefined;
console.log(`Session loaded: ${session.id}`);
return new Session({
id: session.id,
accessToken: session.accessToken,
state: session.state,
shop: session.shop,
isOnline: true,
scope: session.scope ? session.scope : undefined,
});
}
async storeSession(session: Session): Promise<boolean> {
console.log(`Storing session: ${session.id}`, session);
await prisma.session.upsert({
where: { id: session.id },
update: {
accessToken: session.accessToken,
scope: session.scope,
isDeleted: false,
isScopeUpdated: true,
state: session.state,
},
create: {
id: session.id,
accessToken: session.accessToken!,
scope: session.scope,
isDeleted: false,
state: session.state,
shop: session.shop,
installedAt: new Date(),
isScopeUpdated: true,
},
});
console.log(`Session stored: ${session.id}`);
return true;
}
}
Hey Community! As the holiday season unfolds, we want to extend heartfelt thanks to a...
By JasonH Dec 6, 2024Dropshipping, a high-growth, $226 billion-dollar industry, remains a highly dynamic bus...
By JasonH Nov 27, 2024Hey Community! It’s time to share some appreciation and celebrate what we have accomplis...
By JasonH Nov 14, 2024