@blotoutio/providers-blotout-wallet-sdk 0.64.0 → 0.65.1

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/index.cjs.js CHANGED
@@ -546,26 +546,29 @@ let BlotoutWallet = class BlotoutWallet extends s {
546
546
  this.dispatchEvent(new CustomEvent('blotout-wallet-email-saved', { bubbles: true }));
547
547
  window.edgetag('tag', 'CartRecovery_KeepCartEmailSaved', {}, {}, { destination: this.edgeURL });
548
548
  }
549
- await this.storeApi.addItems(this.lastExpiredCart.items);
549
+ this.restoreResponse = await this.storeApi.addItems(this.lastExpiredCart.items);
550
550
  const expiredCartId = this.lastExpiredCart.cartId;
551
551
  // We attempt to mark the cart as restored, but if the request fails,
552
552
  // we log the error in the console and let the user continue. Since the
553
553
  // problem is probably in the `/cart/restore` endpoint, further attempts
554
554
  // would not resolve the problem, which would just increase the number
555
555
  // of failed calls and not solve the problem.
556
- const response = await fetch(this.getUrl(`/cart/restore/${expiredCartId}`), {
556
+ await fetch(this.getUrl(`/cart/restore/${expiredCartId}`), {
557
557
  method: 'POST',
558
558
  headers: this.getHeaders(),
559
- });
560
- if (!response.ok) {
561
- throw new Error(`Could not update status in DB - ${response.status}: ${response.text}\n\n${await response.text()}`);
562
- }
559
+ })
560
+ .then(async (response) => {
561
+ if (!response.ok) {
562
+ throw new Error(`Could not update status in DB - ${response.status}: ${response.text}\n\n${await response.text()}`);
563
+ }
564
+ })
565
+ .catch(logger.error);
563
566
  this.lastExpiredCart = undefined;
564
567
  this.dispatchEvent(new CustomEvent('blotout-wallet-cart-restored', {
565
568
  bubbles: true,
566
569
  }));
567
570
  // Send the request as beacon as there could be a immediate redirect in the next step
568
- window.edgetag('tag', 'CartRecovery_CartRestored', { isSilent: !!this.silentRestore }, {}, { method: 'beacon' });
571
+ window.edgetag('tag', 'CartRecovery_CartRestored', { isSilent: !!this.silentRestore }, {}, { method: 'beacon', destination: this.edgeURL });
569
572
  };
570
573
  this.onSubmit = async (ev) => {
571
574
  ev.preventDefault();
@@ -578,14 +581,28 @@ let BlotoutWallet = class BlotoutWallet extends s {
578
581
  if (this.state == 'restored') {
579
582
  this.hideModal('restore');
580
583
  }
581
- // Redirect to custom path
582
- if (this.restoreRedirect) {
583
- try {
584
- const redirect = new URL(this.restoreRedirect, window.location.origin);
585
- window.location.href = redirect.href;
586
- }
587
- catch (e) {
588
- console.error('Invalid redirect URL', e);
584
+ // handle afterRestore action
585
+ if (this.afterRestore) {
586
+ switch (this.afterRestore.action) {
587
+ case 'refresh': {
588
+ window.location.reload();
589
+ break;
590
+ }
591
+ case 'redirect': {
592
+ try {
593
+ window.location.href = new URL(this.afterRestore.url, window.location.href).toString();
594
+ }
595
+ catch (e) {
596
+ console.error('Invalid redirect URL', e);
597
+ }
598
+ break;
599
+ }
600
+ case 'checkout': {
601
+ const url = this.storeApi.getCheckoutURL();
602
+ if (url) {
603
+ window.location.href = url.toString();
604
+ }
605
+ }
589
606
  }
590
607
  }
591
608
  }
@@ -654,6 +671,9 @@ let BlotoutWallet = class BlotoutWallet extends s {
654
671
  return x `
655
672
  ${cartTick({ class: 'icon' })}
656
673
  <div>Cart restored successfully!</div>
674
+ ${this.restoreResponse
675
+ ? x `<p>Some items could not be added: ${this.restoreResponse}</p>`
676
+ : T}
657
677
  `;
658
678
  };
659
679
  this.failedContent = () => {
@@ -767,6 +787,7 @@ let BlotoutWallet = class BlotoutWallet extends s {
767
787
  .catch(logger.error)
768
788
  .finally(() => { var _a; return (_a = this.dialog) === null || _a === void 0 ? void 0 : _a.close(action); });
769
789
  this.dispatchEvent(new CustomEvent('blotout-wallet-hidden', { bubbles: true }));
790
+ this.restoreResponse = undefined;
770
791
  }
771
792
  getHeaders(json = false) {
772
793
  const headers = new Headers({
@@ -980,6 +1001,10 @@ __decorate([
980
1001
  r(),
981
1002
  __metadata("design:type", String)
982
1003
  ], BlotoutWallet.prototype, "state", void 0);
1004
+ __decorate([
1005
+ r(),
1006
+ __metadata("design:type", Object)
1007
+ ], BlotoutWallet.prototype, "restoreResponse", void 0);
983
1008
  BlotoutWallet = __decorate([
984
1009
  t$1('blotout-wallet')
985
1010
  ], BlotoutWallet);
@@ -1044,8 +1069,8 @@ const init = (params) => {
1044
1069
  element.theme = theme;
1045
1070
  element.edgeURL = params.baseUrl;
1046
1071
  element.userId = params.userId;
1047
- element.restoreRedirect = (_e = params.manifest.variables) === null || _e === void 0 ? void 0 : _e['restoreRedirect'];
1048
- element.silentRestore = ((_f = params.manifest.variables) === null || _f === void 0 ? void 0 : _f['silentRestore']) === '1';
1072
+ element.afterRestore = (_e = params.manifest.variables) === null || _e === void 0 ? void 0 : _e.afterRestore;
1073
+ element.silentRestore = (_f = params.manifest.variables) === null || _f === void 0 ? void 0 : _f.silentRestore;
1049
1074
  document.body.append(element);
1050
1075
  }
1051
1076
  };
package/index.js CHANGED
@@ -547,26 +547,29 @@ var ProvidersBlotoutWalletSdk = (function () {
547
547
  this.dispatchEvent(new CustomEvent('blotout-wallet-email-saved', { bubbles: true }));
548
548
  window.edgetag('tag', 'CartRecovery_KeepCartEmailSaved', {}, {}, { destination: this.edgeURL });
549
549
  }
550
- await this.storeApi.addItems(this.lastExpiredCart.items);
550
+ this.restoreResponse = await this.storeApi.addItems(this.lastExpiredCart.items);
551
551
  const expiredCartId = this.lastExpiredCart.cartId;
552
552
  // We attempt to mark the cart as restored, but if the request fails,
553
553
  // we log the error in the console and let the user continue. Since the
554
554
  // problem is probably in the `/cart/restore` endpoint, further attempts
555
555
  // would not resolve the problem, which would just increase the number
556
556
  // of failed calls and not solve the problem.
557
- const response = await fetch(this.getUrl(`/cart/restore/${expiredCartId}`), {
557
+ await fetch(this.getUrl(`/cart/restore/${expiredCartId}`), {
558
558
  method: 'POST',
559
559
  headers: this.getHeaders(),
560
- });
561
- if (!response.ok) {
562
- throw new Error(`Could not update status in DB - ${response.status}: ${response.text}\n\n${await response.text()}`);
563
- }
560
+ })
561
+ .then(async (response) => {
562
+ if (!response.ok) {
563
+ throw new Error(`Could not update status in DB - ${response.status}: ${response.text}\n\n${await response.text()}`);
564
+ }
565
+ })
566
+ .catch(logger.error);
564
567
  this.lastExpiredCart = undefined;
565
568
  this.dispatchEvent(new CustomEvent('blotout-wallet-cart-restored', {
566
569
  bubbles: true,
567
570
  }));
568
571
  // Send the request as beacon as there could be a immediate redirect in the next step
569
- window.edgetag('tag', 'CartRecovery_CartRestored', { isSilent: !!this.silentRestore }, {}, { method: 'beacon' });
572
+ window.edgetag('tag', 'CartRecovery_CartRestored', { isSilent: !!this.silentRestore }, {}, { method: 'beacon', destination: this.edgeURL });
570
573
  };
571
574
  this.onSubmit = async (ev) => {
572
575
  ev.preventDefault();
@@ -579,14 +582,28 @@ var ProvidersBlotoutWalletSdk = (function () {
579
582
  if (this.state == 'restored') {
580
583
  this.hideModal('restore');
581
584
  }
582
- // Redirect to custom path
583
- if (this.restoreRedirect) {
584
- try {
585
- const redirect = new URL(this.restoreRedirect, window.location.origin);
586
- window.location.href = redirect.href;
587
- }
588
- catch (e) {
589
- console.error('Invalid redirect URL', e);
585
+ // handle afterRestore action
586
+ if (this.afterRestore) {
587
+ switch (this.afterRestore.action) {
588
+ case 'refresh': {
589
+ window.location.reload();
590
+ break;
591
+ }
592
+ case 'redirect': {
593
+ try {
594
+ window.location.href = new URL(this.afterRestore.url, window.location.href).toString();
595
+ }
596
+ catch (e) {
597
+ console.error('Invalid redirect URL', e);
598
+ }
599
+ break;
600
+ }
601
+ case 'checkout': {
602
+ const url = this.storeApi.getCheckoutURL();
603
+ if (url) {
604
+ window.location.href = url.toString();
605
+ }
606
+ }
590
607
  }
591
608
  }
592
609
  }
@@ -655,6 +672,9 @@ var ProvidersBlotoutWalletSdk = (function () {
655
672
  return x `
656
673
  ${cartTick({ class: 'icon' })}
657
674
  <div>Cart restored successfully!</div>
675
+ ${this.restoreResponse
676
+ ? x `<p>Some items could not be added: ${this.restoreResponse}</p>`
677
+ : T}
658
678
  `;
659
679
  };
660
680
  this.failedContent = () => {
@@ -768,6 +788,7 @@ var ProvidersBlotoutWalletSdk = (function () {
768
788
  .catch(logger.error)
769
789
  .finally(() => { var _a; return (_a = this.dialog) === null || _a === void 0 ? void 0 : _a.close(action); });
770
790
  this.dispatchEvent(new CustomEvent('blotout-wallet-hidden', { bubbles: true }));
791
+ this.restoreResponse = undefined;
771
792
  }
772
793
  getHeaders(json = false) {
773
794
  const headers = new Headers({
@@ -981,6 +1002,10 @@ var ProvidersBlotoutWalletSdk = (function () {
981
1002
  r(),
982
1003
  __metadata("design:type", String)
983
1004
  ], BlotoutWallet.prototype, "state", void 0);
1005
+ __decorate([
1006
+ r(),
1007
+ __metadata("design:type", Object)
1008
+ ], BlotoutWallet.prototype, "restoreResponse", void 0);
984
1009
  BlotoutWallet = __decorate([
985
1010
  t$1('blotout-wallet')
986
1011
  ], BlotoutWallet);
@@ -1045,8 +1070,8 @@ var ProvidersBlotoutWalletSdk = (function () {
1045
1070
  element.theme = theme;
1046
1071
  element.edgeURL = params.baseUrl;
1047
1072
  element.userId = params.userId;
1048
- element.restoreRedirect = (_e = params.manifest.variables) === null || _e === void 0 ? void 0 : _e['restoreRedirect'];
1049
- element.silentRestore = ((_f = params.manifest.variables) === null || _f === void 0 ? void 0 : _f['silentRestore']) === '1';
1073
+ element.afterRestore = (_e = params.manifest.variables) === null || _e === void 0 ? void 0 : _e.afterRestore;
1074
+ element.silentRestore = (_f = params.manifest.variables) === null || _f === void 0 ? void 0 : _f.silentRestore;
1050
1075
  document.body.append(element);
1051
1076
  }
1052
1077
  };
package/index.mjs CHANGED
@@ -544,26 +544,29 @@ let BlotoutWallet = class BlotoutWallet extends s {
544
544
  this.dispatchEvent(new CustomEvent('blotout-wallet-email-saved', { bubbles: true }));
545
545
  window.edgetag('tag', 'CartRecovery_KeepCartEmailSaved', {}, {}, { destination: this.edgeURL });
546
546
  }
547
- await this.storeApi.addItems(this.lastExpiredCart.items);
547
+ this.restoreResponse = await this.storeApi.addItems(this.lastExpiredCart.items);
548
548
  const expiredCartId = this.lastExpiredCart.cartId;
549
549
  // We attempt to mark the cart as restored, but if the request fails,
550
550
  // we log the error in the console and let the user continue. Since the
551
551
  // problem is probably in the `/cart/restore` endpoint, further attempts
552
552
  // would not resolve the problem, which would just increase the number
553
553
  // of failed calls and not solve the problem.
554
- const response = await fetch(this.getUrl(`/cart/restore/${expiredCartId}`), {
554
+ await fetch(this.getUrl(`/cart/restore/${expiredCartId}`), {
555
555
  method: 'POST',
556
556
  headers: this.getHeaders(),
557
- });
558
- if (!response.ok) {
559
- throw new Error(`Could not update status in DB - ${response.status}: ${response.text}\n\n${await response.text()}`);
560
- }
557
+ })
558
+ .then(async (response) => {
559
+ if (!response.ok) {
560
+ throw new Error(`Could not update status in DB - ${response.status}: ${response.text}\n\n${await response.text()}`);
561
+ }
562
+ })
563
+ .catch(logger.error);
561
564
  this.lastExpiredCart = undefined;
562
565
  this.dispatchEvent(new CustomEvent('blotout-wallet-cart-restored', {
563
566
  bubbles: true,
564
567
  }));
565
568
  // Send the request as beacon as there could be a immediate redirect in the next step
566
- window.edgetag('tag', 'CartRecovery_CartRestored', { isSilent: !!this.silentRestore }, {}, { method: 'beacon' });
569
+ window.edgetag('tag', 'CartRecovery_CartRestored', { isSilent: !!this.silentRestore }, {}, { method: 'beacon', destination: this.edgeURL });
567
570
  };
568
571
  this.onSubmit = async (ev) => {
569
572
  ev.preventDefault();
@@ -576,14 +579,28 @@ let BlotoutWallet = class BlotoutWallet extends s {
576
579
  if (this.state == 'restored') {
577
580
  this.hideModal('restore');
578
581
  }
579
- // Redirect to custom path
580
- if (this.restoreRedirect) {
581
- try {
582
- const redirect = new URL(this.restoreRedirect, window.location.origin);
583
- window.location.href = redirect.href;
584
- }
585
- catch (e) {
586
- console.error('Invalid redirect URL', e);
582
+ // handle afterRestore action
583
+ if (this.afterRestore) {
584
+ switch (this.afterRestore.action) {
585
+ case 'refresh': {
586
+ window.location.reload();
587
+ break;
588
+ }
589
+ case 'redirect': {
590
+ try {
591
+ window.location.href = new URL(this.afterRestore.url, window.location.href).toString();
592
+ }
593
+ catch (e) {
594
+ console.error('Invalid redirect URL', e);
595
+ }
596
+ break;
597
+ }
598
+ case 'checkout': {
599
+ const url = this.storeApi.getCheckoutURL();
600
+ if (url) {
601
+ window.location.href = url.toString();
602
+ }
603
+ }
587
604
  }
588
605
  }
589
606
  }
@@ -652,6 +669,9 @@ let BlotoutWallet = class BlotoutWallet extends s {
652
669
  return x `
653
670
  ${cartTick({ class: 'icon' })}
654
671
  <div>Cart restored successfully!</div>
672
+ ${this.restoreResponse
673
+ ? x `<p>Some items could not be added: ${this.restoreResponse}</p>`
674
+ : T}
655
675
  `;
656
676
  };
657
677
  this.failedContent = () => {
@@ -765,6 +785,7 @@ let BlotoutWallet = class BlotoutWallet extends s {
765
785
  .catch(logger.error)
766
786
  .finally(() => { var _a; return (_a = this.dialog) === null || _a === void 0 ? void 0 : _a.close(action); });
767
787
  this.dispatchEvent(new CustomEvent('blotout-wallet-hidden', { bubbles: true }));
788
+ this.restoreResponse = undefined;
768
789
  }
769
790
  getHeaders(json = false) {
770
791
  const headers = new Headers({
@@ -978,6 +999,10 @@ __decorate([
978
999
  r(),
979
1000
  __metadata("design:type", String)
980
1001
  ], BlotoutWallet.prototype, "state", void 0);
1002
+ __decorate([
1003
+ r(),
1004
+ __metadata("design:type", Object)
1005
+ ], BlotoutWallet.prototype, "restoreResponse", void 0);
981
1006
  BlotoutWallet = __decorate([
982
1007
  t$1('blotout-wallet')
983
1008
  ], BlotoutWallet);
@@ -1042,8 +1067,8 @@ const init = (params) => {
1042
1067
  element.theme = theme;
1043
1068
  element.edgeURL = params.baseUrl;
1044
1069
  element.userId = params.userId;
1045
- element.restoreRedirect = (_e = params.manifest.variables) === null || _e === void 0 ? void 0 : _e['restoreRedirect'];
1046
- element.silentRestore = ((_f = params.manifest.variables) === null || _f === void 0 ? void 0 : _f['silentRestore']) === '1';
1070
+ element.afterRestore = (_e = params.manifest.variables) === null || _e === void 0 ? void 0 : _e.afterRestore;
1071
+ element.silentRestore = (_f = params.manifest.variables) === null || _f === void 0 ? void 0 : _f.silentRestore;
1047
1072
  document.body.append(element);
1048
1073
  }
1049
1074
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blotoutio/providers-blotout-wallet-sdk",
3
- "version": "0.64.0",
3
+ "version": "0.65.1",
4
4
  "description": "Blotout Wallet SDK for EdgeTag",
5
5
  "author": "Blotout",
6
6
  "license": "MIT",
@@ -34,6 +34,8 @@ new Set([
34
34
  ...expand('911,921,922,923,924,926,927,932,933,935,942,944,946,950,953,955,957-958,960-969,974,975,976,977,981-982,987,988,990-999'),
35
35
  ]);
36
36
 
37
+ const delay = (n, resolvedValue) => new Promise((resolve) => setTimeout(() => resolve(resolvedValue), n));
38
+
37
39
  const cartTokenCookie = 'cart';
38
40
  const cartTokenTwoCookie = 'cart2';
39
41
 
@@ -78,22 +80,72 @@ const parseCookies = (cookie) => {
78
80
  };
79
81
 
80
82
  // eslint-disable-next-line @nx/enforce-module-boundaries
83
+ const MAX_RETRY_COUNT = 3;
84
+ const RETRY_DELAY = 500;
85
+ const ERROR_PATTERNS = {
86
+ cannotAddMore: /^You can't add more (.*?) to the cart\.$/,
87
+ soldOut: /^The product '?(.*?)'? is already sold out\.$/,
88
+ };
89
+ const getItemNameFromDescription = (description) => {
90
+ let matches = description.match(ERROR_PATTERNS.cannotAddMore);
91
+ if (matches === null || matches === void 0 ? void 0 : matches.length)
92
+ return matches[1];
93
+ matches = description.match(ERROR_PATTERNS.soldOut);
94
+ if (matches === null || matches === void 0 ? void 0 : matches.length)
95
+ return matches[1];
96
+ return null;
97
+ };
98
+ const addItemsToCart = (fetchOverride, itemsToAdd) => fetchOverride(`${window.Shopify.routes.root}cart/add.js`, {
99
+ method: 'POST',
100
+ body: JSON.stringify({
101
+ items: itemsToAdd.map((item) => ({
102
+ id: item.variantId || item.productId,
103
+ quantity: item.quantity,
104
+ })),
105
+ }),
106
+ headers: { 'Content-type': 'application/json' },
107
+ });
81
108
  const createShopApi = (fetchOverride = window.fetch) => ({
82
- addItems(items) {
83
- return fetchOverride(`${window.Shopify.routes.root}cart/add.js`, {
84
- method: 'POST',
85
- body: JSON.stringify({
86
- items: items.map((item) => ({
87
- id: item.variantId || item.productId,
88
- quantity: item.quantity,
89
- })),
90
- }),
91
- headers: { 'Content-type': 'application/json' },
92
- }).then(async (response) => {
93
- if (!response.ok) {
94
- throw new Error('Could not add items', { cause: await response.text() });
109
+ async addItems(items) {
110
+ var _a;
111
+ let filteredItems = items;
112
+ const unavailableItems = [];
113
+ for (let retries = 0; retries < MAX_RETRY_COUNT; retries++) {
114
+ const response = await addItemsToCart(fetchOverride, filteredItems);
115
+ if (response.ok) {
116
+ break;
95
117
  }
96
- });
118
+ if (response.status == 422) {
119
+ const data = (await response.json());
120
+ const rejectedItemName = (_a = getItemNameFromDescription(data.description)) === null || _a === void 0 ? void 0 : _a.toLowerCase();
121
+ if (!rejectedItemName) {
122
+ throw new Error(`Could not add items`, { cause: data });
123
+ }
124
+ else {
125
+ const oldLength = filteredItems.length;
126
+ filteredItems = filteredItems.filter((item) => {
127
+ if (item.name.toLowerCase().includes(rejectedItemName)) {
128
+ unavailableItems.push(item.name);
129
+ return false;
130
+ }
131
+ return true;
132
+ });
133
+ if (filteredItems.length == 0) {
134
+ break;
135
+ }
136
+ else if (oldLength == filteredItems.length) {
137
+ throw new Error(`Could not add items`, { cause: data });
138
+ }
139
+ }
140
+ await delay(RETRY_DELAY);
141
+ }
142
+ else if (!response.ok) {
143
+ throw new Error(`Could not add items`, { cause: await response.text() });
144
+ }
145
+ }
146
+ return unavailableItems.length > 0
147
+ ? `Some items could not be added: ${unavailableItems.join(', ')}`
148
+ : undefined;
97
149
  },
98
150
  getCartToken() {
99
151
  let cartToken = getCookieValue(cartTokenCookie);
@@ -102,6 +154,11 @@ const createShopApi = (fetchOverride = window.fetch) => ({
102
154
  }
103
155
  return cartToken || null;
104
156
  },
157
+ getCheckoutURL() {
158
+ const token = this.getCartToken();
159
+ return token
160
+ ? new URL(`/checkouts/cn/${token}`, window.location.origin)
161
+ : null;
162
+ },
105
163
  });
106
164
  window[registryKey].storeAPIFactory = createShopApi;
107
- window[registryKey].platform = 'SHOPIFY';
@@ -35,6 +35,8 @@
35
35
  ...expand('911,921,922,923,924,926,927,932,933,935,942,944,946,950,953,955,957-958,960-969,974,975,976,977,981-982,987,988,990-999'),
36
36
  ]);
37
37
 
38
+ const delay = (n, resolvedValue) => new Promise((resolve) => setTimeout(() => resolve(resolvedValue), n));
39
+
38
40
  const cartTokenCookie = 'cart';
39
41
  const cartTokenTwoCookie = 'cart2';
40
42
 
@@ -79,22 +81,72 @@
79
81
  };
80
82
 
81
83
  // eslint-disable-next-line @nx/enforce-module-boundaries
84
+ const MAX_RETRY_COUNT = 3;
85
+ const RETRY_DELAY = 500;
86
+ const ERROR_PATTERNS = {
87
+ cannotAddMore: /^You can't add more (.*?) to the cart\.$/,
88
+ soldOut: /^The product '?(.*?)'? is already sold out\.$/,
89
+ };
90
+ const getItemNameFromDescription = (description) => {
91
+ let matches = description.match(ERROR_PATTERNS.cannotAddMore);
92
+ if (matches === null || matches === void 0 ? void 0 : matches.length)
93
+ return matches[1];
94
+ matches = description.match(ERROR_PATTERNS.soldOut);
95
+ if (matches === null || matches === void 0 ? void 0 : matches.length)
96
+ return matches[1];
97
+ return null;
98
+ };
99
+ const addItemsToCart = (fetchOverride, itemsToAdd) => fetchOverride(`${window.Shopify.routes.root}cart/add.js`, {
100
+ method: 'POST',
101
+ body: JSON.stringify({
102
+ items: itemsToAdd.map((item) => ({
103
+ id: item.variantId || item.productId,
104
+ quantity: item.quantity,
105
+ })),
106
+ }),
107
+ headers: { 'Content-type': 'application/json' },
108
+ });
82
109
  const createShopApi = (fetchOverride = window.fetch) => ({
83
- addItems(items) {
84
- return fetchOverride(`${window.Shopify.routes.root}cart/add.js`, {
85
- method: 'POST',
86
- body: JSON.stringify({
87
- items: items.map((item) => ({
88
- id: item.variantId || item.productId,
89
- quantity: item.quantity,
90
- })),
91
- }),
92
- headers: { 'Content-type': 'application/json' },
93
- }).then(async (response) => {
94
- if (!response.ok) {
95
- throw new Error('Could not add items', { cause: await response.text() });
110
+ async addItems(items) {
111
+ var _a;
112
+ let filteredItems = items;
113
+ const unavailableItems = [];
114
+ for (let retries = 0; retries < MAX_RETRY_COUNT; retries++) {
115
+ const response = await addItemsToCart(fetchOverride, filteredItems);
116
+ if (response.ok) {
117
+ break;
96
118
  }
97
- });
119
+ if (response.status == 422) {
120
+ const data = (await response.json());
121
+ const rejectedItemName = (_a = getItemNameFromDescription(data.description)) === null || _a === void 0 ? void 0 : _a.toLowerCase();
122
+ if (!rejectedItemName) {
123
+ throw new Error(`Could not add items`, { cause: data });
124
+ }
125
+ else {
126
+ const oldLength = filteredItems.length;
127
+ filteredItems = filteredItems.filter((item) => {
128
+ if (item.name.toLowerCase().includes(rejectedItemName)) {
129
+ unavailableItems.push(item.name);
130
+ return false;
131
+ }
132
+ return true;
133
+ });
134
+ if (filteredItems.length == 0) {
135
+ break;
136
+ }
137
+ else if (oldLength == filteredItems.length) {
138
+ throw new Error(`Could not add items`, { cause: data });
139
+ }
140
+ }
141
+ await delay(RETRY_DELAY);
142
+ }
143
+ else if (!response.ok) {
144
+ throw new Error(`Could not add items`, { cause: await response.text() });
145
+ }
146
+ }
147
+ return unavailableItems.length > 0
148
+ ? `Some items could not be added: ${unavailableItems.join(', ')}`
149
+ : undefined;
98
150
  },
99
151
  getCartToken() {
100
152
  let cartToken = getCookieValue(cartTokenCookie);
@@ -103,8 +155,13 @@
103
155
  }
104
156
  return cartToken || null;
105
157
  },
158
+ getCheckoutURL() {
159
+ const token = this.getCartToken();
160
+ return token
161
+ ? new URL(`/checkouts/cn/${token}`, window.location.origin)
162
+ : null;
163
+ },
106
164
  });
107
165
  window[registryKey].storeAPIFactory = createShopApi;
108
- window[registryKey].platform = 'SHOPIFY';
109
166
 
110
167
  })();
@@ -32,6 +32,8 @@ new Set([
32
32
  ...expand('911,921,922,923,924,926,927,932,933,935,942,944,946,950,953,955,957-958,960-969,974,975,976,977,981-982,987,988,990-999'),
33
33
  ]);
34
34
 
35
+ const delay = (n, resolvedValue) => new Promise((resolve) => setTimeout(() => resolve(resolvedValue), n));
36
+
35
37
  const cartTokenCookie = 'cart';
36
38
  const cartTokenTwoCookie = 'cart2';
37
39
 
@@ -76,22 +78,72 @@ const parseCookies = (cookie) => {
76
78
  };
77
79
 
78
80
  // eslint-disable-next-line @nx/enforce-module-boundaries
81
+ const MAX_RETRY_COUNT = 3;
82
+ const RETRY_DELAY = 500;
83
+ const ERROR_PATTERNS = {
84
+ cannotAddMore: /^You can't add more (.*?) to the cart\.$/,
85
+ soldOut: /^The product '?(.*?)'? is already sold out\.$/,
86
+ };
87
+ const getItemNameFromDescription = (description) => {
88
+ let matches = description.match(ERROR_PATTERNS.cannotAddMore);
89
+ if (matches === null || matches === void 0 ? void 0 : matches.length)
90
+ return matches[1];
91
+ matches = description.match(ERROR_PATTERNS.soldOut);
92
+ if (matches === null || matches === void 0 ? void 0 : matches.length)
93
+ return matches[1];
94
+ return null;
95
+ };
96
+ const addItemsToCart = (fetchOverride, itemsToAdd) => fetchOverride(`${window.Shopify.routes.root}cart/add.js`, {
97
+ method: 'POST',
98
+ body: JSON.stringify({
99
+ items: itemsToAdd.map((item) => ({
100
+ id: item.variantId || item.productId,
101
+ quantity: item.quantity,
102
+ })),
103
+ }),
104
+ headers: { 'Content-type': 'application/json' },
105
+ });
79
106
  const createShopApi = (fetchOverride = window.fetch) => ({
80
- addItems(items) {
81
- return fetchOverride(`${window.Shopify.routes.root}cart/add.js`, {
82
- method: 'POST',
83
- body: JSON.stringify({
84
- items: items.map((item) => ({
85
- id: item.variantId || item.productId,
86
- quantity: item.quantity,
87
- })),
88
- }),
89
- headers: { 'Content-type': 'application/json' },
90
- }).then(async (response) => {
91
- if (!response.ok) {
92
- throw new Error('Could not add items', { cause: await response.text() });
107
+ async addItems(items) {
108
+ var _a;
109
+ let filteredItems = items;
110
+ const unavailableItems = [];
111
+ for (let retries = 0; retries < MAX_RETRY_COUNT; retries++) {
112
+ const response = await addItemsToCart(fetchOverride, filteredItems);
113
+ if (response.ok) {
114
+ break;
93
115
  }
94
- });
116
+ if (response.status == 422) {
117
+ const data = (await response.json());
118
+ const rejectedItemName = (_a = getItemNameFromDescription(data.description)) === null || _a === void 0 ? void 0 : _a.toLowerCase();
119
+ if (!rejectedItemName) {
120
+ throw new Error(`Could not add items`, { cause: data });
121
+ }
122
+ else {
123
+ const oldLength = filteredItems.length;
124
+ filteredItems = filteredItems.filter((item) => {
125
+ if (item.name.toLowerCase().includes(rejectedItemName)) {
126
+ unavailableItems.push(item.name);
127
+ return false;
128
+ }
129
+ return true;
130
+ });
131
+ if (filteredItems.length == 0) {
132
+ break;
133
+ }
134
+ else if (oldLength == filteredItems.length) {
135
+ throw new Error(`Could not add items`, { cause: data });
136
+ }
137
+ }
138
+ await delay(RETRY_DELAY);
139
+ }
140
+ else if (!response.ok) {
141
+ throw new Error(`Could not add items`, { cause: await response.text() });
142
+ }
143
+ }
144
+ return unavailableItems.length > 0
145
+ ? `Some items could not be added: ${unavailableItems.join(', ')}`
146
+ : undefined;
95
147
  },
96
148
  getCartToken() {
97
149
  let cartToken = getCookieValue(cartTokenCookie);
@@ -100,6 +152,11 @@ const createShopApi = (fetchOverride = window.fetch) => ({
100
152
  }
101
153
  return cartToken || null;
102
154
  },
155
+ getCheckoutURL() {
156
+ const token = this.getCartToken();
157
+ return token
158
+ ? new URL(`/checkouts/cn/${token}`, window.location.origin)
159
+ : null;
160
+ },
103
161
  });
104
162
  window[registryKey].storeAPIFactory = createShopApi;
105
- window[registryKey].platform = 'SHOPIFY';