@agnostack/next-shopify 2.0.14-alpha.7 → 2.0.14-alpha.9

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [2.0.14-alpha.9](https://github.com/agnostack/next-shopify/compare/v2.0.14-alpha.8...v2.0.14-alpha.9) (2024-10-06)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * protect route mods ([dadc019](https://github.com/agnostack/next-shopify/commit/dadc01900f7e449bf6dc11acc30415fd920ecd39))
7
+
8
+ ## [2.0.14-alpha.8](https://github.com/agnostack/next-shopify/compare/v2.0.14-alpha.7...v2.0.14-alpha.8) (2024-10-06)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * protect route mods ([b01c4b3](https://github.com/agnostack/next-shopify/commit/b01c4b3327db6aee58fd1c49882aa493cdb4d823))
14
+
1
15
  ## [2.0.14-alpha.7](https://github.com/agnostack/next-shopify/compare/v2.0.14-alpha.6...v2.0.14-alpha.7) (2024-10-06)
2
16
 
3
17
 
@@ -1 +1 @@
1
- {"version":3,"file":"protect.d.ts","sourceRoot":"","sources":["../../../../src/lib/handlers/ssr/protect.js"],"names":[],"mappings":"AAmBO;;;;;;;;;;;;;;GAsHN"}
1
+ {"version":3,"file":"protect.d.ts","sourceRoot":"","sources":["../../../../src/lib/handlers/ssr/protect.js"],"names":[],"mappings":"AAqBO;;;;;;;;;;;;;;GAqJN"}
@@ -17,12 +17,13 @@ const chalk_1 = __importDefault(require("chalk"));
17
17
  const utils_1 = require("../../utils");
18
18
  const shared_1 = require("../../../shared");
19
19
  const protectRoute = (serverRuntimeConfig, props) => {
20
- const { PAGE_PATHS } = serverRuntimeConfig; // API_PATHS
20
+ const { PAGE_PATHS, API_PATHS } = serverRuntimeConfig; // PAYMENT_CONFIG
21
+ const prepareWebhookHandlers = (0, utils_1.getPrepareWebhookHandlers)(serverRuntimeConfig);
21
22
  return ({ req, res, query, locale, } = {}, data) => __awaiter(void 0, void 0, void 0, function* () {
22
- var _a, _b, _c, _d;
23
+ var _a, _b, _c, _d, _e, _f;
23
24
  const shop = (_a = query === null || query === void 0 ? void 0 : query.shop) !== null && _a !== void 0 ? _a : (_b = req.query) === null || _b === void 0 ? void 0 : _b.shop;
24
- console.info(`>>> ~ protectRoute`, { shop, data });
25
- // const embedded = query?.embedded ?? req.query?.embedded
25
+ const embedded = (_c = query === null || query === void 0 ? void 0 : query.embedded) !== null && _c !== void 0 ? _c : (_d = req.query) === null || _d === void 0 ? void 0 : _d.embedded;
26
+ const idToken = (_e = query === null || query === void 0 ? void 0 : query.id_token) !== null && _e !== void 0 ? _e : (_f = req.query) === null || _f === void 0 ? void 0 : _f.id_token;
26
27
  if ((0, shared_1.stringEmpty)(shop)) {
27
28
  return {
28
29
  redirect: {
@@ -39,76 +40,86 @@ const protectRoute = (serverRuntimeConfig, props) => {
39
40
  props: (0, shared_1.cleanObject)(Object.assign(Object.assign({ locale }, props), data), false, shared_1.stringEmptyOnly),
40
41
  };
41
42
  }
42
- // findSessionIds, loadSession,
43
- const { shopify, handleTokenExchange } = yield (0, utils_1.getShopifyHelpers)(serverRuntimeConfig, { shop });
43
+ const { shopify, loadAppData, loadSession, findSessionIds, handleTokenExchange, encryptStoreSession, } = yield (0, utils_1.getShopifyHelpers)(serverRuntimeConfig, { shop });
44
44
  if (shopify === null || shopify === void 0 ? void 0 : shopify.config) {
45
45
  res.setHeader('Content-Security-Policy', (0, utils_1.getSecurityHeader)(shop, shopify.config));
46
46
  }
47
- const shopContext = (_c = data === null || data === void 0 ? void 0 : data.query) === null || _c === void 0 ? void 0 : _c.shop;
48
- console.info(`>>> ~ shopContext`, shopContext);
49
- const tokenContext = (_d = data === null || data === void 0 ? void 0 : data.query) === null || _d === void 0 ? void 0 : _d.id_token;
50
- console.info(`>>> ~ tokenContext`, tokenContext);
51
47
  // NOTE initial load
52
- if ((0, shared_1.stringNotEmpty)(shopContext) && (0, shared_1.stringNotEmpty)(tokenContext)) {
53
- const { session: offlineSession } = yield handleTokenExchange({ idToken: tokenContext });
48
+ if ((embedded === 1) && (0, shared_1.stringNotEmpty)(idToken)) {
49
+ const { session: _offlineSession } = yield handleTokenExchange({ idToken });
50
+ const offlineSession = _offlineSession.toObject();
54
51
  console.info(`>>> ~ offlineSession`, offlineSession);
55
- const { session: onlineSession } = yield handleTokenExchange({ idToken: tokenContext, isOnline: true });
56
- console.info(`>>> ~ onlineSession`, onlineSession);
57
- }
58
- /*
59
- const existingSessionIds = await findSessionIds(shop)
60
- if (arrayEmpty(existingSessionIds)) {
61
- // TODO: explore objectToQuerystring
62
- const queryParams = new URLSearchParams({ ...query, shop }).toString()
63
-
64
- return {
65
- redirect: {
66
- destination: `${API_PATHS[API_ROUTE_NAMES.AUTH_ONLINE]}?${queryParams}`,
67
- permanent: false,
68
- },
69
- }
52
+ // const { session: onlineSession } = await handleTokenExchange({ idToken, isOnline: true })
53
+ // console.info(`>>> ~ onlineSession`, onlineSession)
54
+ yield encryptStoreSession(offlineSession);
55
+ let appId;
56
+ try {
57
+ const { appInfo } = yield loadAppData();
58
+ appId = (0, shared_1.normalizeShopifyId)(appInfo === null || appInfo === void 0 ? void 0 : appInfo.id).id;
59
+ // eslint-disable-next-line no-empty
60
+ }
61
+ catch (_ignore) {
62
+ console.info('Ignoring error loading appData', _ignore);
63
+ }
64
+ // Register webhooks
65
+ const webhookHandlers = prepareWebhookHandlers({ appId });
66
+ shopify.webhooks.addHandlers(webhookHandlers);
67
+ // NOTE: webhooks cannot use an online session to make API calls to Shopify (but not all webooks may be doing that for all apps)
68
+ yield shopify.webhooks.register({ session: offlineSession });
69
+ // if (PAYMENT_CONFIG?.required) {
70
+ // // NOTE: handleEnsureBilling handles calling encryptStoreSession for offline session when shouldCheckBilling
71
+ // const confirmationUrl = await handleEnsureBilling(session, reAuth)
72
+ // if (stringNotEmpty(confirmationUrl)) {
73
+ // redirectUrl = confirmationUrl
74
+ // }
75
+ // }
70
76
  }
71
-
72
- if (embedded !== '1') {
73
- try {
74
- // NOTE: this does not work if called INSIDE of embedded
75
- await loadSession({ req, res })
76
-
77
- const destination = await shopify.auth.getEmbeddedAppUrl({
78
- rawRequest: req,
79
- rawResponse: res,
80
- })
81
-
82
- return {
83
- redirect: {
84
- destination,
85
- permanent: false,
86
- },
77
+ else {
78
+ const existingSessionIds = yield findSessionIds(shop);
79
+ if ((0, shared_1.arrayEmpty)(existingSessionIds)) {
80
+ // TODO: explore objectToQuerystring
81
+ const queryParams = new URLSearchParams(Object.assign(Object.assign({}, query), { shop })).toString();
82
+ return {
83
+ redirect: {
84
+ destination: `${API_PATHS[shared_1.API_ROUTE_NAMES.AUTH_ONLINE]}?${queryParams}`,
85
+ permanent: false,
86
+ },
87
+ };
87
88
  }
88
- } catch (error) {
89
- if (error instanceof ShopifyAuthenticationError) {
90
- // TODO: explore objectToQuerystring
91
- const queryParams = new URLSearchParams({
92
- ...query,
93
- shop,
94
- reAuth: (error instanceof ShopifySessionAuthenticationError),
95
- }).toString()
96
-
97
- return {
98
- redirect: {
99
- destination: `${API_PATHS[API_ROUTE_NAMES.AUTH_ONLINE]}?${queryParams}`,
100
- permanent: false,
101
- },
102
- }
89
+ if (embedded !== '1') {
90
+ try {
91
+ // NOTE: this does not work if called INSIDE of embedded
92
+ yield loadSession({ req, res });
93
+ const destination = yield shopify.auth.getEmbeddedAppUrl({
94
+ rawRequest: req,
95
+ rawResponse: res,
96
+ });
97
+ return {
98
+ redirect: {
99
+ destination,
100
+ permanent: false,
101
+ },
102
+ };
103
+ }
104
+ catch (error) {
105
+ if (error instanceof shared_1.ShopifyAuthenticationError) {
106
+ // TODO: explore objectToQuerystring
107
+ const queryParams = new URLSearchParams(Object.assign(Object.assign({}, query), { shop, reAuth: (error instanceof shared_1.ShopifySessionAuthenticationError) })).toString();
108
+ return {
109
+ redirect: {
110
+ destination: `${API_PATHS[shared_1.API_ROUTE_NAMES.AUTH_ONLINE]}?${queryParams}`,
111
+ permanent: false,
112
+ },
113
+ };
114
+ }
115
+ console.error('Error protecting route.', error);
116
+ throw error;
117
+ }
103
118
  }
104
- console.error('Error protecting route.', error)
105
-
106
- throw error
107
- }
108
119
  }
109
- */
120
+ const foo = (0, shared_1.cleanObject)(Object.assign(Object.assign({ locale }, props), data), false, shared_1.stringEmptyOnly);
110
121
  return {
111
- props: (0, shared_1.cleanObject)(Object.assign({ locale }, props), false, shared_1.stringEmptyOnly),
122
+ props: foo,
112
123
  };
113
124
  });
114
125
  };
@@ -1 +1 @@
1
- {"version":3,"file":"protect.js","sourceRoot":"","sources":["../../../../src/lib/handlers/ssr/protect.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"protect.js","sourceRoot":"","sources":["../../../../src/lib/handlers/ssr/protect.js"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agnostack/next-shopify",
3
- "version": "2.0.14-alpha.7",
3
+ "version": "2.0.14-alpha.9",
4
4
  "author": "agnoStack Dev <developers@agnostack.com> (https://agnostack.com)",
5
5
  "owner": "agnoStack",
6
6
  "description": "Please contact agnoStack via info@agnostack.com for any questions",