@uniforge/platform-shopify 0.1.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/auth/index.d.cts +246 -0
  2. package/dist/auth/index.d.ts +246 -0
  3. package/dist/auth/index.js +623 -0
  4. package/dist/auth/index.js.map +1 -0
  5. package/dist/auth/index.mjs +586 -0
  6. package/dist/auth/index.mjs.map +1 -0
  7. package/dist/billing/index.d.cts +58 -0
  8. package/dist/billing/index.d.ts +58 -0
  9. package/dist/billing/index.js +226 -0
  10. package/dist/billing/index.js.map +1 -0
  11. package/dist/billing/index.mjs +196 -0
  12. package/dist/billing/index.mjs.map +1 -0
  13. package/dist/graphql/index.d.cts +17 -0
  14. package/dist/graphql/index.d.ts +17 -0
  15. package/dist/graphql/index.js +67 -0
  16. package/dist/graphql/index.js.map +1 -0
  17. package/dist/graphql/index.mjs +40 -0
  18. package/dist/graphql/index.mjs.map +1 -0
  19. package/dist/index.d.cts +16 -0
  20. package/dist/index.d.ts +16 -0
  21. package/dist/index.js +37 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/index.mjs +11 -0
  24. package/dist/index.mjs.map +1 -0
  25. package/dist/multi-store/index.d.cts +28 -0
  26. package/dist/multi-store/index.d.ts +28 -0
  27. package/dist/multi-store/index.js +181 -0
  28. package/dist/multi-store/index.js.map +1 -0
  29. package/dist/multi-store/index.mjs +152 -0
  30. package/dist/multi-store/index.mjs.map +1 -0
  31. package/dist/performance/index.d.cts +22 -0
  32. package/dist/performance/index.d.ts +22 -0
  33. package/dist/performance/index.js +64 -0
  34. package/dist/performance/index.js.map +1 -0
  35. package/dist/performance/index.mjs +35 -0
  36. package/dist/performance/index.mjs.map +1 -0
  37. package/dist/platform/index.d.cts +16 -0
  38. package/dist/platform/index.d.ts +16 -0
  39. package/dist/platform/index.js +150 -0
  40. package/dist/platform/index.js.map +1 -0
  41. package/dist/platform/index.mjs +121 -0
  42. package/dist/platform/index.mjs.map +1 -0
  43. package/dist/rbac/index.d.cts +38 -0
  44. package/dist/rbac/index.d.ts +38 -0
  45. package/dist/rbac/index.js +56 -0
  46. package/dist/rbac/index.js.map +1 -0
  47. package/dist/rbac/index.mjs +29 -0
  48. package/dist/rbac/index.mjs.map +1 -0
  49. package/dist/security/index.d.cts +26 -0
  50. package/dist/security/index.d.ts +26 -0
  51. package/dist/security/index.js +102 -0
  52. package/dist/security/index.js.map +1 -0
  53. package/dist/security/index.mjs +69 -0
  54. package/dist/security/index.mjs.map +1 -0
  55. package/dist/webhooks/index.d.cts +36 -0
  56. package/dist/webhooks/index.d.ts +36 -0
  57. package/dist/webhooks/index.js +147 -0
  58. package/dist/webhooks/index.js.map +1 -0
  59. package/dist/webhooks/index.mjs +118 -0
  60. package/dist/webhooks/index.mjs.map +1 -0
  61. package/package.json +95 -0
@@ -0,0 +1,623 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/auth/index.ts
31
+ var auth_exports = {};
32
+ __export(auth_exports, {
33
+ ShopifySessionManager: () => ShopifySessionManager,
34
+ beginOAuth: () => beginOAuth,
35
+ createShopifyTokenExchangeFn: () => createShopifyTokenExchangeFn,
36
+ extractSessionToken: () => extractSessionToken,
37
+ extractShop: () => extractShop,
38
+ handleOAuthCallback: () => handleOAuthCallback,
39
+ performTokenExchange: () => performTokenExchange,
40
+ validateOAuthHmac: () => validateOAuthHmac
41
+ });
42
+ module.exports = __toCommonJS(auth_exports);
43
+
44
+ // src/auth/token-exchange.ts
45
+ var import_node_crypto = require("crypto");
46
+ var import_auth = require("@uniforge/platform-core/auth");
47
+ var import_auth2 = require("@uniforge/core/auth");
48
+ function extractSessionToken(request) {
49
+ const authHeader = request.headers["authorization"] ?? request.headers["Authorization"];
50
+ if (!authHeader || typeof authHeader !== "string") {
51
+ return null;
52
+ }
53
+ if (!authHeader.startsWith("Bearer ")) {
54
+ return null;
55
+ }
56
+ const token = authHeader.slice("Bearer ".length).trim();
57
+ return token.length > 0 ? token : null;
58
+ }
59
+ function extractShop(request) {
60
+ const shopFromQuery = request.query["shop"];
61
+ if (shopFromQuery && (0, import_auth.isValidShopDomain)(shopFromQuery)) {
62
+ return shopFromQuery;
63
+ }
64
+ const shopFromHeader = request.headers["x-shopify-shop-domain"];
65
+ if (shopFromHeader && typeof shopFromHeader === "string" && (0, import_auth.isValidShopDomain)(shopFromHeader)) {
66
+ return shopFromHeader;
67
+ }
68
+ return null;
69
+ }
70
+ async function createShopifyTokenExchangeFn(config) {
71
+ const { shopifyApi, LogSeverity } = await import("@shopify/shopify-api");
72
+ await import("@shopify/shopify-api/adapters/node");
73
+ const shopify = shopifyApi({
74
+ apiKey: config.apiKey,
75
+ apiSecretKey: config.apiSecretKey,
76
+ scopes: config.scopes,
77
+ hostName: config.hostName,
78
+ apiVersion: config.apiVersion,
79
+ isEmbeddedApp: config.isEmbeddedApp ?? true,
80
+ logger: { level: LogSeverity.Error }
81
+ });
82
+ return shopify.auth.tokenExchange;
83
+ }
84
+ async function performTokenExchange(input) {
85
+ const { config, sessionToken, shop, tokenType } = input;
86
+ if (!sessionToken) {
87
+ throw new Error("Session token is required for token exchange.");
88
+ }
89
+ if (!(0, import_auth.isValidShopDomain)(shop)) {
90
+ throw new Error(
91
+ `Invalid shop domain "${shop}". Expected format: example.myshopify.com`
92
+ );
93
+ }
94
+ const tokenExchangeFn = input.tokenExchangeFn ?? await createShopifyTokenExchangeFn(config);
95
+ const requestedTokenType = tokenType === "online" ? "urn:shopify:params:oauth:token-type:online-access-token" : "urn:shopify:params:oauth:token-type:offline-access-token";
96
+ try {
97
+ const { session: shopifySession } = await tokenExchangeFn({
98
+ sessionToken,
99
+ shop,
100
+ requestedTokenType
101
+ });
102
+ const now = /* @__PURE__ */ new Date();
103
+ const session = mapShopifySession(shopifySession, shop, now);
104
+ if (config.eventHandler) {
105
+ const event = (0, import_auth2.createAuthEvent)({
106
+ type: "token_exchange_success",
107
+ shopDomain: shop,
108
+ outcome: "success",
109
+ sessionId: session.id,
110
+ metadata: {
111
+ tokenType,
112
+ sessionId: session.id
113
+ }
114
+ });
115
+ await Promise.resolve(config.eventHandler.onAuthEvent(event));
116
+ }
117
+ return session;
118
+ } catch (error) {
119
+ if (config.eventHandler) {
120
+ const event = (0, import_auth2.createAuthEvent)({
121
+ type: "token_exchange_failure",
122
+ shopDomain: shop,
123
+ outcome: "failure",
124
+ metadata: {
125
+ tokenType,
126
+ error: error instanceof Error ? error.message : String(error)
127
+ }
128
+ });
129
+ await Promise.resolve(config.eventHandler.onAuthEvent(event));
130
+ }
131
+ throw error;
132
+ }
133
+ }
134
+ function mapShopifySession(shopifySession, shop, now) {
135
+ const session = {
136
+ id: shopifySession.id,
137
+ shop: shopifySession.shop || shop,
138
+ state: shopifySession.state || (0, import_node_crypto.randomUUID)(),
139
+ isOnline: shopifySession.isOnline,
140
+ scope: shopifySession.scope || "",
141
+ expires: shopifySession.expires ?? null,
142
+ createdAt: now,
143
+ updatedAt: now
144
+ };
145
+ if (shopifySession.accessToken) {
146
+ session.accessToken = shopifySession.accessToken;
147
+ }
148
+ if (shopifySession.refreshToken) {
149
+ session.refreshToken = shopifySession.refreshToken;
150
+ }
151
+ if (shopifySession.onlineAccessInfo) {
152
+ const info = shopifySession.onlineAccessInfo;
153
+ session.onlineAccessInfo = {
154
+ expiresIn: info.expires_in ?? info.expiresIn ?? 0,
155
+ associatedUserScope: info.associated_user_scope ?? info.associatedUserScope ?? "",
156
+ associatedUser: info.associated_user ? {
157
+ id: info.associated_user.id,
158
+ firstName: info.associated_user.first_name,
159
+ lastName: info.associated_user.last_name,
160
+ email: info.associated_user.email,
161
+ emailVerified: info.associated_user.email_verified,
162
+ accountOwner: info.associated_user.account_owner,
163
+ locale: info.associated_user.locale,
164
+ collaborator: info.associated_user.collaborator
165
+ } : info.associatedUser
166
+ };
167
+ }
168
+ return session;
169
+ }
170
+
171
+ // src/auth/session.ts
172
+ var import_auth3 = require("@uniforge/core/auth");
173
+ var ShopifySessionManager = class {
174
+ constructor(config, options) {
175
+ this.config = config;
176
+ const encryption = new import_auth3.TokenEncryptionServiceImpl(config.encryption);
177
+ this.encryptedStorage = new import_auth3.EncryptedSessionStorage(
178
+ config.sessionStorage,
179
+ encryption
180
+ );
181
+ if (options?.tokenExchangeFn) {
182
+ this.tokenExchangeFn = options.tokenExchangeFn;
183
+ }
184
+ if (options?.tokenRefreshFn) {
185
+ this.tokenRefreshFn = options.tokenRefreshFn;
186
+ }
187
+ }
188
+ encryptedStorage;
189
+ tokenExchangeFn;
190
+ tokenRefreshFn;
191
+ /**
192
+ * Get an existing valid session or create one via token exchange.
193
+ *
194
+ * For embedded apps, this:
195
+ * 1. Extracts the session token and shop from the request
196
+ * 2. Checks storage for an existing non-expired session
197
+ * 3. If no valid session, performs token exchange with Shopify
198
+ * 4. Stores the new session (encrypted) and returns it
199
+ */
200
+ async getOrCreateSession(request) {
201
+ const shop = extractShop(request);
202
+ if (!shop) {
203
+ throw new Error(
204
+ "Could not extract shop domain from request. Provide shop in query parameter or x-shopify-shop-domain header."
205
+ );
206
+ }
207
+ const sessionToken = extractSessionToken(request);
208
+ if (!sessionToken) {
209
+ throw new Error(
210
+ "Could not extract session token from Authorization header. Expected: Authorization: Bearer <token>"
211
+ );
212
+ }
213
+ const sessionId = `offline_${shop}`;
214
+ const existingSession = await this.encryptedStorage.loadSession(sessionId);
215
+ if (existingSession && !this.isExpired(existingSession)) {
216
+ return existingSession;
217
+ }
218
+ const exchangeInput = {
219
+ config: this.config,
220
+ sessionToken,
221
+ shop,
222
+ tokenType: "offline"
223
+ };
224
+ if (this.tokenExchangeFn) {
225
+ exchangeInput.tokenExchangeFn = this.tokenExchangeFn;
226
+ }
227
+ const session = await performTokenExchange(exchangeInput);
228
+ await this.encryptedStorage.storeSession(session);
229
+ return session;
230
+ }
231
+ /**
232
+ * Get an existing valid online session or create one via token exchange.
233
+ */
234
+ async getOrCreateOnlineSession(request) {
235
+ const shop = extractShop(request);
236
+ if (!shop) {
237
+ throw new Error(
238
+ "Could not extract shop domain from request."
239
+ );
240
+ }
241
+ const sessionToken = extractSessionToken(request);
242
+ if (!sessionToken) {
243
+ throw new Error(
244
+ "Could not extract session token from Authorization header."
245
+ );
246
+ }
247
+ const onlineExchangeInput = {
248
+ config: this.config,
249
+ sessionToken,
250
+ shop,
251
+ tokenType: "online"
252
+ };
253
+ if (this.tokenExchangeFn) {
254
+ onlineExchangeInput.tokenExchangeFn = this.tokenExchangeFn;
255
+ }
256
+ const session = await performTokenExchange(onlineExchangeInput);
257
+ await this.encryptedStorage.storeSession(session);
258
+ return session;
259
+ }
260
+ /**
261
+ * Refresh an expiring session token.
262
+ *
263
+ * Retries up to `config.tokenRefresh.maxRetries` on failure.
264
+ * On success, stores the refreshed session (encrypted) and emits
265
+ * a `token_refreshed` event. On final failure, emits
266
+ * `token_refresh_failed` and throws.
267
+ */
268
+ async refreshSessionToken(session) {
269
+ const maxRetries = this.config.tokenRefresh?.maxRetries ?? 3;
270
+ const refreshFn = this.tokenRefreshFn ?? await this.createDefaultRefreshFn();
271
+ let lastError;
272
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
273
+ try {
274
+ const refreshInput = { shop: session.shop };
275
+ if (session.accessToken) {
276
+ refreshInput.accessToken = session.accessToken;
277
+ }
278
+ if (session.refreshToken) {
279
+ refreshInput.refreshToken = session.refreshToken;
280
+ }
281
+ const { session: refreshedShopifySession } = await refreshFn({
282
+ session: refreshInput
283
+ });
284
+ const now = /* @__PURE__ */ new Date();
285
+ const refreshedSession = this.mapRefreshedSession(
286
+ refreshedShopifySession,
287
+ session,
288
+ now
289
+ );
290
+ await this.encryptedStorage.storeSession(refreshedSession);
291
+ if (this.config.eventHandler) {
292
+ const event = (0, import_auth3.createAuthEvent)({
293
+ type: "token_refreshed",
294
+ shopDomain: session.shop,
295
+ outcome: "success",
296
+ sessionId: session.id,
297
+ metadata: { attempt: String(attempt + 1) }
298
+ });
299
+ await Promise.resolve(
300
+ this.config.eventHandler.onAuthEvent(event)
301
+ );
302
+ }
303
+ return refreshedSession;
304
+ } catch (error) {
305
+ lastError = error instanceof Error ? error : new Error(String(error));
306
+ }
307
+ }
308
+ if (this.config.eventHandler) {
309
+ const event = (0, import_auth3.createAuthEvent)({
310
+ type: "token_refresh_failed",
311
+ shopDomain: session.shop,
312
+ outcome: "failure",
313
+ sessionId: session.id,
314
+ metadata: {
315
+ maxRetries: String(maxRetries),
316
+ error: lastError?.message ?? "Unknown error"
317
+ }
318
+ });
319
+ await Promise.resolve(this.config.eventHandler.onAuthEvent(event));
320
+ }
321
+ throw lastError ?? new Error("Token refresh failed after all retries.");
322
+ }
323
+ /**
324
+ * Delete all sessions for a shop (e.g., on app uninstall).
325
+ *
326
+ * Finds all sessions via `findSessionsByShop`, deletes them,
327
+ * and emits `session_deleted` events.
328
+ */
329
+ async cleanupShopSessions(shop) {
330
+ const sessions = await this.config.sessionStorage.findSessionsByShop(shop);
331
+ if (sessions.length === 0) {
332
+ return;
333
+ }
334
+ const sessionIds = sessions.map((s) => s.id);
335
+ await this.config.sessionStorage.deleteSessions(sessionIds);
336
+ if (this.config.eventHandler) {
337
+ for (const session of sessions) {
338
+ const event = (0, import_auth3.createAuthEvent)({
339
+ type: "session_deleted",
340
+ shopDomain: shop,
341
+ outcome: "success",
342
+ sessionId: session.id,
343
+ metadata: { reason: "shop_cleanup" }
344
+ });
345
+ await Promise.resolve(
346
+ this.config.eventHandler.onAuthEvent(event)
347
+ );
348
+ }
349
+ }
350
+ }
351
+ isExpired(session) {
352
+ if (!session.expires) {
353
+ return false;
354
+ }
355
+ return session.expires.getTime() <= Date.now();
356
+ }
357
+ mapRefreshedSession(refreshed, original, now) {
358
+ const session = {
359
+ id: refreshed.id || original.id,
360
+ shop: refreshed.shop || original.shop,
361
+ state: refreshed.state || original.state,
362
+ isOnline: refreshed.isOnline,
363
+ scope: refreshed.scope || original.scope,
364
+ expires: refreshed.expires ?? original.expires,
365
+ createdAt: original.createdAt,
366
+ updatedAt: now
367
+ };
368
+ if (refreshed.accessToken) {
369
+ session.accessToken = refreshed.accessToken;
370
+ }
371
+ if (refreshed.refreshToken) {
372
+ session.refreshToken = refreshed.refreshToken;
373
+ }
374
+ return session;
375
+ }
376
+ async createDefaultRefreshFn() {
377
+ const { shopifyApi, LogSeverity } = await import("@shopify/shopify-api");
378
+ await import("@shopify/shopify-api/adapters/node");
379
+ const shopify = shopifyApi({
380
+ apiKey: this.config.apiKey,
381
+ apiSecretKey: this.config.apiSecretKey,
382
+ scopes: this.config.scopes,
383
+ hostName: this.config.hostName,
384
+ apiVersion: this.config.apiVersion,
385
+ isEmbeddedApp: this.config.isEmbeddedApp ?? true,
386
+ logger: { level: LogSeverity.Error }
387
+ });
388
+ return async (params) => {
389
+ const result = await shopify.auth.refreshToken(params);
390
+ return result;
391
+ };
392
+ }
393
+ };
394
+
395
+ // src/auth/hmac.ts
396
+ var import_node_crypto2 = require("crypto");
397
+ function validateOAuthHmac(query, secret) {
398
+ const hmac = query["hmac"];
399
+ if (!hmac) {
400
+ return false;
401
+ }
402
+ const entries = Object.entries(query).filter(([key]) => key !== "hmac").sort(([a], [b]) => a.localeCompare(b));
403
+ const message = entries.map(([key, value]) => `${key}=${value}`).join("&");
404
+ const computed = (0, import_node_crypto2.createHmac)("sha256", secret).update(message).digest("hex");
405
+ if (computed.length !== hmac.length) {
406
+ return false;
407
+ }
408
+ return (0, import_node_crypto2.timingSafeEqual)(
409
+ Buffer.from(computed, "utf-8"),
410
+ Buffer.from(hmac, "utf-8")
411
+ );
412
+ }
413
+
414
+ // src/auth/oauth.ts
415
+ var import_node_crypto3 = require("crypto");
416
+ var import_auth4 = require("@uniforge/platform-core/auth");
417
+ var import_auth5 = require("@uniforge/core/auth");
418
+ var import_auth6 = require("@uniforge/core/auth");
419
+ async function beginOAuth(input) {
420
+ const { config, request, callbackPath, isOnline } = input;
421
+ const shop = request.query["shop"];
422
+ if (!shop || !(0, import_auth4.isValidShopDomain)(shop)) {
423
+ throw new Error(
424
+ "Could not extract valid shop domain from request. Provide shop in query parameter."
425
+ );
426
+ }
427
+ const beginFn = input.beginFn ?? await createShopifyOAuthBeginFn(config);
428
+ const { redirectUrl } = await beginFn({
429
+ shop,
430
+ callbackPath,
431
+ isOnline: isOnline ?? false
432
+ });
433
+ if (config.eventHandler) {
434
+ const event = (0, import_auth5.createAuthEvent)({
435
+ type: "oauth_begin",
436
+ shopDomain: shop,
437
+ outcome: "success",
438
+ metadata: { callbackPath }
439
+ });
440
+ await Promise.resolve(config.eventHandler.onAuthEvent(event));
441
+ }
442
+ return { redirectUrl };
443
+ }
444
+ async function handleOAuthCallback(input) {
445
+ const { config, request } = input;
446
+ const query = request.query;
447
+ const shop = query["shop"] ?? "";
448
+ if (query["error"]) {
449
+ const errorDesc = query["error_description"] ?? `Authorization ${query["error"]}`;
450
+ if (config.eventHandler) {
451
+ const event = (0, import_auth5.createAuthEvent)({
452
+ type: "oauth_callback_failure",
453
+ shopDomain: shop,
454
+ outcome: "failure",
455
+ metadata: {
456
+ error: query["error"],
457
+ errorDescription: errorDesc
458
+ }
459
+ });
460
+ await Promise.resolve(config.eventHandler.onAuthEvent(event));
461
+ }
462
+ return {
463
+ success: false,
464
+ error: {
465
+ code: "ACCESS_DENIED",
466
+ message: errorDesc,
467
+ statusCode: 403
468
+ }
469
+ };
470
+ }
471
+ if (!validateOAuthHmac(query, config.apiSecretKey)) {
472
+ if (config.eventHandler) {
473
+ const event = (0, import_auth5.createAuthEvent)({
474
+ type: "oauth_callback_failure",
475
+ shopDomain: shop,
476
+ outcome: "failure",
477
+ metadata: { error: "HMAC validation failed" }
478
+ });
479
+ await Promise.resolve(config.eventHandler.onAuthEvent(event));
480
+ }
481
+ return {
482
+ success: false,
483
+ error: {
484
+ code: "HMAC_VALIDATION_FAILED",
485
+ message: "HMAC validation failed on OAuth callback.",
486
+ statusCode: 403
487
+ }
488
+ };
489
+ }
490
+ try {
491
+ const callbackFn = input.callbackFn ?? await createShopifyOAuthCallbackFn(config);
492
+ const { session: shopifySession } = await callbackFn({ query });
493
+ const now = /* @__PURE__ */ new Date();
494
+ const session = mapOAuthSession(shopifySession, shop, now);
495
+ const grantedScopes = new Set(
496
+ (session.scope || "").split(",").map((s) => s.trim())
497
+ );
498
+ const requiredScopes = config.scopes;
499
+ const missingScopes = requiredScopes.filter(
500
+ (scope) => !grantedScopes.has(scope)
501
+ );
502
+ if (missingScopes.length > 0) {
503
+ if (config.eventHandler) {
504
+ const event = (0, import_auth5.createAuthEvent)({
505
+ type: "oauth_callback_failure",
506
+ shopDomain: shop,
507
+ outcome: "failure",
508
+ metadata: {
509
+ error: "scope_mismatch",
510
+ missingScopes: missingScopes.join(",")
511
+ }
512
+ });
513
+ await Promise.resolve(config.eventHandler.onAuthEvent(event));
514
+ }
515
+ return {
516
+ success: false,
517
+ error: {
518
+ code: "SCOPE_MISMATCH",
519
+ message: `Missing required scopes: ${missingScopes.join(", ")}. Granted: ${session.scope}`,
520
+ statusCode: 403
521
+ }
522
+ };
523
+ }
524
+ const encryption = new import_auth6.TokenEncryptionServiceImpl(config.encryption);
525
+ const encryptedStorage = new import_auth6.EncryptedSessionStorage(
526
+ config.sessionStorage,
527
+ encryption
528
+ );
529
+ await encryptedStorage.storeSession(session);
530
+ if (config.eventHandler) {
531
+ const event = (0, import_auth5.createAuthEvent)({
532
+ type: "oauth_callback_success",
533
+ shopDomain: shop,
534
+ outcome: "success",
535
+ sessionId: session.id,
536
+ metadata: { scope: session.scope }
537
+ });
538
+ await Promise.resolve(config.eventHandler.onAuthEvent(event));
539
+ }
540
+ return {
541
+ success: true,
542
+ session,
543
+ redirectUrl: `${config.hostName}/?shop=${shop}`
544
+ };
545
+ } catch (error) {
546
+ if (config.eventHandler) {
547
+ const event = (0, import_auth5.createAuthEvent)({
548
+ type: "oauth_callback_failure",
549
+ shopDomain: shop,
550
+ outcome: "failure",
551
+ metadata: {
552
+ error: error instanceof Error ? error.message : String(error)
553
+ }
554
+ });
555
+ await Promise.resolve(config.eventHandler.onAuthEvent(event));
556
+ }
557
+ return {
558
+ success: false,
559
+ error: {
560
+ code: "OAUTH_CALLBACK_FAILED",
561
+ message: error instanceof Error ? error.message : String(error),
562
+ statusCode: 500
563
+ }
564
+ };
565
+ }
566
+ }
567
+ async function createShopifyOAuthBeginFn(config) {
568
+ return async (params) => {
569
+ const nonce = (0, import_node_crypto3.randomUUID)();
570
+ const scopeString = config.scopes.join(",");
571
+ const redirectUri = `${config.hostName}${params.callbackPath}`;
572
+ const redirectUrl = `https://${params.shop}/admin/oauth/authorize?client_id=${config.apiKey}&scope=${encodeURIComponent(scopeString)}&redirect_uri=${encodeURIComponent(redirectUri)}&state=${nonce}`;
573
+ return { redirectUrl };
574
+ };
575
+ }
576
+ async function createShopifyOAuthCallbackFn(config) {
577
+ const { shopifyApi, LogSeverity } = await import("@shopify/shopify-api");
578
+ await import("@shopify/shopify-api/adapters/node");
579
+ const shopify = shopifyApi({
580
+ apiKey: config.apiKey,
581
+ apiSecretKey: config.apiSecretKey,
582
+ scopes: config.scopes,
583
+ hostName: config.hostName,
584
+ apiVersion: config.apiVersion,
585
+ isEmbeddedApp: config.isEmbeddedApp ?? false,
586
+ logger: { level: LogSeverity.Error }
587
+ });
588
+ return async (params) => {
589
+ const result = await shopify.auth.callback(params);
590
+ return result;
591
+ };
592
+ }
593
+ function mapOAuthSession(shopifySession, shop, now) {
594
+ const session = {
595
+ id: shopifySession.id,
596
+ shop: shopifySession.shop || shop,
597
+ state: shopifySession.state || (0, import_node_crypto3.randomUUID)(),
598
+ isOnline: shopifySession.isOnline,
599
+ scope: shopifySession.scope || "",
600
+ expires: shopifySession.expires ?? null,
601
+ createdAt: now,
602
+ updatedAt: now
603
+ };
604
+ if (shopifySession.accessToken) {
605
+ session.accessToken = shopifySession.accessToken;
606
+ }
607
+ if (shopifySession.refreshToken) {
608
+ session.refreshToken = shopifySession.refreshToken;
609
+ }
610
+ return session;
611
+ }
612
+ // Annotate the CommonJS export names for ESM import in node:
613
+ 0 && (module.exports = {
614
+ ShopifySessionManager,
615
+ beginOAuth,
616
+ createShopifyTokenExchangeFn,
617
+ extractSessionToken,
618
+ extractShop,
619
+ handleOAuthCallback,
620
+ performTokenExchange,
621
+ validateOAuthHmac
622
+ });
623
+ //# sourceMappingURL=index.js.map