@zonos/elements 1.3.45 → 1.3.47
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/dist/cjs/{cart-89b4f62d.js → cart-85870c2e.js} +187 -62
- package/dist/cjs/{sendErrorLog-a966a8c1.js → createCustomEvent-aacc2833.js} +2 -22
- package/dist/cjs/index.cjs.js +13 -8
- package/dist/cjs/{paypal-1a1882c4.js → paypal-e4428dbf.js} +3 -3
- package/dist/cjs/zonos-address_8.cjs.entry.js +1 -1
- package/dist/cjs/zonos-cart-items_12.cjs.entry.js +25 -19
- package/dist/cjs/zonos-checkout.cjs.entry.js +10 -10
- package/dist/cjs/zonos-controller.cjs.entry.js +6 -6
- package/dist/cjs/zonos-country-flag.cjs.entry.js +1 -1
- package/dist/cjs/zonos-country-select_4.cjs.entry.js +1 -1
- package/dist/cjs/zonos-hello-dialog.cjs.entry.js +1 -1
- package/dist/cjs/zonos-hello.cjs.entry.js +2 -3
- package/dist/cjs/zonos-language-select_2.cjs.entry.js +1 -1
- package/dist/cjs/zonos-notification_3.cjs.entry.js +2 -2
- package/dist/collection/components/checkout/zonos-checkout/zonos-checkout-finish/zonos-checkout-finish.js +14 -8
- package/dist/collection/components/main/zonos-controller.js +4 -3
- package/dist/collection/components/store/checkout/stripe.js +4 -7
- package/dist/collection/components/store/hello/localization.js +1 -1
- package/dist/collection/components/utils/api/calculateLandedCost.js +1 -2
- package/dist/collection/components/utils/api/checkItemRestrictions.js +1 -2
- package/dist/collection/components/utils/api/getCountries.js +1 -1
- package/dist/collection/components/utils/api/getCountryLandedCost.js +1 -1
- package/dist/collection/components/utils/api/getExchangeRate.js +6 -2
- package/dist/collection/components/utils/api/getInclusivePricing.js +1 -2
- package/dist/collection/components/utils/api/getLocaleMessages.js +7 -2
- package/dist/collection/components/utils/api/getLocalization.js +3 -6
- package/dist/collection/components/utils/api/getShippingZones.js +1 -2
- package/dist/collection/components/utils/api/getZonosSettings.js +1 -2
- package/dist/collection/components/utils/api/paypalCaptureOrder.js +1 -1
- package/dist/collection/components/utils/api/paypalCreateOrder.js +1 -1
- package/dist/collection/components/utils/api/trackConversionEvent.js +1 -2
- package/dist/collection/components/utils/api/triggerBuildLandedCost.js +3 -2
- package/dist/collection/components/utils/getNextFallbackApi.js +18 -0
- package/dist/collection/components/utils/handleFetch.js +84 -20
- package/dist/collection/components/utils/logging/sendErrorLog.js +33 -6
- package/dist/collection/components/utils/logging/sendLoadScriptLog.js +1 -1
- package/dist/collection/environment.js +15 -78
- package/dist/collection/scripts/_getZonosQaUrl.js +6 -2
- package/dist/collection/scripts/_zonosBase.js +1 -0
- package/dist/collection/utils/formattedCloudflareApiUrl.js +43 -0
- package/dist/collection/utils/formattedUrl.js +80 -0
- package/dist/collection/utils/generateCloudflareDeployUrl.js +21 -0
- package/dist/esm/{cart-9f40efd7.js → cart-3aae6236.js} +188 -64
- package/dist/esm/{sendErrorLog-aea606c0.js → createCustomEvent-ccb20b48.js} +3 -22
- package/dist/esm/index.js +13 -8
- package/dist/esm/{paypal-f4bb6ce7.js → paypal-b1589e7b.js} +3 -3
- package/dist/esm/zonos-address_8.entry.js +1 -1
- package/dist/esm/zonos-cart-items_12.entry.js +17 -11
- package/dist/esm/zonos-checkout.entry.js +3 -3
- package/dist/esm/zonos-controller.entry.js +6 -6
- package/dist/esm/zonos-country-flag.entry.js +1 -1
- package/dist/esm/zonos-country-select_4.entry.js +1 -1
- package/dist/esm/zonos-hello-dialog.entry.js +1 -1
- package/dist/esm/zonos-hello.entry.js +2 -3
- package/dist/esm/zonos-language-select_2.entry.js +1 -1
- package/dist/esm/zonos-notification_3.entry.js +2 -2
- package/dist/esm-es5/{cart-9f40efd7.js → cart-3aae6236.js} +1 -1
- package/dist/esm-es5/createCustomEvent-ccb20b48.js +1 -0
- package/dist/esm-es5/index.js +1 -1
- package/dist/esm-es5/paypal-b1589e7b.js +17 -0
- package/dist/esm-es5/zonos-address_8.entry.js +1 -1
- package/dist/esm-es5/zonos-cart-items_12.entry.js +1 -1
- package/dist/esm-es5/zonos-checkout.entry.js +1 -1
- package/dist/esm-es5/zonos-controller.entry.js +1 -1
- package/dist/esm-es5/zonos-country-flag.entry.js +1 -1
- package/dist/esm-es5/zonos-country-select_4.entry.js +1 -1
- package/dist/esm-es5/zonos-hello-dialog.entry.js +1 -1
- package/dist/esm-es5/zonos-hello.entry.js +1 -1
- package/dist/esm-es5/zonos-language-select_2.entry.js +1 -1
- package/dist/esm-es5/zonos-notification_3.entry.js +1 -1
- package/dist/scripts/index.d.ts +11 -0
- package/dist/scripts/index.js +1 -1
- package/dist/scripts/loadZonos.js +1 -1
- package/dist/scripts/zonos.js +1 -1
- package/dist/types/components/utils/getNextFallbackApi.d.ts +10 -0
- package/dist/types/components/utils/handleFetch.d.ts +10 -1
- package/dist/types/components/utils/logging/sendErrorLog.d.ts +7 -1
- package/dist/types/environment.d.ts +8 -9
- package/dist/types/scripts/_getNextFallbackUrl.d.ts +1 -1
- package/dist/types/scripts/_zonosBase.d.ts +5 -0
- package/dist/types/utils/formattedCloudflareApiUrl.d.ts +20 -0
- package/dist/types/utils/formattedUrl.d.ts +9 -0
- package/dist/types/utils/generateCloudflareDeployUrl.d.ts +10 -0
- package/dist/zonos-elements/assets/RemoveIcon.svg +1 -1
- package/dist/zonos-elements/index.esm.js +1 -1
- package/dist/zonos-elements/{p-7b8099ed.js → p-08a43211.js} +1 -1
- package/dist/zonos-elements/p-1738fe3f.entry.js +1 -0
- package/dist/zonos-elements/p-284520ae.js +17 -0
- package/dist/zonos-elements/{p-578f42c1.entry.js → p-3225705b.entry.js} +1 -1
- package/dist/zonos-elements/{p-8630168f.system.entry.js → p-3403a81a.system.entry.js} +1 -1
- package/dist/zonos-elements/p-3fb9e320.system.js +17 -0
- package/dist/zonos-elements/{p-acf9a97e.system.entry.js → p-3fd66f5b.system.entry.js} +1 -1
- package/dist/zonos-elements/{p-8bf1af28.entry.js → p-57c67bbf.entry.js} +1 -1
- package/dist/zonos-elements/{p-4204ccf9.system.entry.js → p-6d613cee.system.entry.js} +1 -1
- package/dist/zonos-elements/p-7d0663ab.system.entry.js +1 -0
- package/dist/zonos-elements/{p-7bd28076.system.entry.js → p-8949d4a2.system.entry.js} +1 -1
- package/dist/zonos-elements/p-8b02985f.entry.js +1 -0
- package/dist/zonos-elements/p-90935326.system.entry.js +1 -0
- package/dist/zonos-elements/{p-c7952400.entry.js → p-9c9e8288.entry.js} +1 -1
- package/dist/zonos-elements/{p-43d3cafd.system.js → p-a576cbb8.system.js} +1 -1
- package/dist/zonos-elements/p-aa0127b3.system.js +1 -0
- package/dist/zonos-elements/{p-d014791e.entry.js → p-b9815df4.entry.js} +1 -1
- package/dist/zonos-elements/{p-ca0a5c22.system.entry.js → p-c035c64f.system.entry.js} +1 -1
- package/dist/zonos-elements/p-c3ca9e48.system.entry.js +1 -0
- package/dist/zonos-elements/p-c78dd680.system.js +1 -1
- package/dist/zonos-elements/{p-5845029a.system.entry.js → p-d5741e4e.system.entry.js} +1 -1
- package/dist/zonos-elements/{p-8618e185.entry.js → p-d5b21512.entry.js} +1 -1
- package/dist/zonos-elements/{p-37c9fdd2.system.entry.js → p-e4033aa7.system.entry.js} +1 -1
- package/dist/zonos-elements/{p-567b457b.entry.js → p-ea58a4a6.entry.js} +1 -1
- package/dist/zonos-elements/{p-f9754e48.entry.js → p-ed496e98.entry.js} +1 -1
- package/dist/zonos-elements/p-eefeab02.js +1 -0
- package/dist/zonos-elements/p-f25559fd.entry.js +1 -0
- package/dist/zonos-elements/p-f9b50965.system.js +1 -0
- package/dist/zonos-elements/zonos-elements.esm.js +1 -1
- package/package.json +1 -1
- package/dist/esm-es5/paypal-f4bb6ce7.js +0 -17
- package/dist/esm-es5/sendErrorLog-aea606c0.js +0 -1
- package/dist/zonos-elements/p-0bfe455c.system.entry.js +0 -1
- package/dist/zonos-elements/p-13618b27.system.entry.js +0 -1
- package/dist/zonos-elements/p-29f4cce5.system.js +0 -17
- package/dist/zonos-elements/p-2d01b8e6.system.entry.js +0 -1
- package/dist/zonos-elements/p-3490d40f.js +0 -17
- package/dist/zonos-elements/p-5b9f2535.system.js +0 -1
- package/dist/zonos-elements/p-6f759c76.system.js +0 -1
- package/dist/zonos-elements/p-757166b8.entry.js +0 -1
- package/dist/zonos-elements/p-76291114.entry.js +0 -1
- package/dist/zonos-elements/p-9aef3a35.entry.js +0 -1
- package/dist/zonos-elements/p-d99e58f2.js +0 -1
|
@@ -14,7 +14,6 @@ export const trackConversionEvent = async (eventName, metadata) => {
|
|
|
14
14
|
!window.Zonos.zonosConversionTest) {
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
|
-
const url = `${process.env.NEXT_PUBLIC_ZONOS_ROUTE_URL}/api/zonos-elements/track-conversion-event`;
|
|
18
17
|
const countryCode = localizationStore.selectedCountryCode;
|
|
19
18
|
const organizationId = zonosStore.organizationId;
|
|
20
19
|
const checkoutSessionId = cartStore.checkoutSessionId;
|
|
@@ -29,7 +28,7 @@ export const trackConversionEvent = async (eventName, metadata) => {
|
|
|
29
28
|
metadata,
|
|
30
29
|
organizationId,
|
|
31
30
|
};
|
|
32
|
-
await handleFetch(
|
|
31
|
+
await handleFetch('/api/zonos-elements/track-conversion-event', {
|
|
33
32
|
body,
|
|
34
33
|
method: 'POST',
|
|
35
34
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { prepCalculateLandedCost } from "./prepCalculateLandedCost";
|
|
2
|
+
import { MAIN_BASE_API } from "../getNextFallbackApi";
|
|
2
3
|
import { handleFetch } from "../handleFetch";
|
|
3
4
|
export const triggerBuildLandedCost = async (buildLandedCost) => {
|
|
4
5
|
var _a, _b, _c, _d;
|
|
@@ -9,13 +10,13 @@ export const triggerBuildLandedCost = async (buildLandedCost) => {
|
|
|
9
10
|
checkoutSessionId: data.checkoutSessionId,
|
|
10
11
|
contact: data.contact,
|
|
11
12
|
shippingAddress: data.shippingAddress,
|
|
12
|
-
zonosApiRoute:
|
|
13
|
+
zonosApiRoute: MAIN_BASE_API,
|
|
13
14
|
});
|
|
14
15
|
if (!landedCost || ((_a = landedCost.errors) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
15
16
|
throw new Error(`Failed to build landed cost: ${((_c = (_b = landedCost === null || landedCost === void 0 ? void 0 : landedCost.errors) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.message) || 'Unknown error'}`);
|
|
16
17
|
}
|
|
17
18
|
// Validate the landed cost object to make sure the returned value from buildLandedCost has a valid schema
|
|
18
|
-
const { errors, json } = await handleFetch(
|
|
19
|
+
const { errors, json } = await handleFetch('/api/zonos-elements/validate-landed-cost', {
|
|
19
20
|
body: landedCost,
|
|
20
21
|
headers: {
|
|
21
22
|
'Content-Type': 'application/json',
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const fallbackApiType = ['vercel', 'cloudflare'];
|
|
2
|
+
export const MAIN_BASE_API_TYPE = fallbackApiType[0];
|
|
3
|
+
export const fallbackApiList = {
|
|
4
|
+
cloudflare: process.env.NEXT_PUBLIC_ZONOS_CLOUDFLARE_URL,
|
|
5
|
+
vercel: process.env.NEXT_PUBLIC_ZONOS_ROUTE_URL,
|
|
6
|
+
};
|
|
7
|
+
export const MAIN_BASE_API = fallbackApiList[MAIN_BASE_API_TYPE];
|
|
8
|
+
/**
|
|
9
|
+
* Next available fallback api type
|
|
10
|
+
*/
|
|
11
|
+
export const getNextFallbackApiType = (currentApiType = MAIN_BASE_API_TYPE) => {
|
|
12
|
+
const currentIndex = fallbackApiType.indexOf(currentApiType);
|
|
13
|
+
const nextFallbackApiType = fallbackApiType[currentIndex + 1];
|
|
14
|
+
if (!nextFallbackApiType) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return nextFallbackApiType;
|
|
18
|
+
};
|
|
@@ -10,6 +10,10 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
10
10
|
}
|
|
11
11
|
return t;
|
|
12
12
|
};
|
|
13
|
+
import { fallbackApiList, getNextFallbackApiType, MAIN_BASE_API_TYPE, } from "./getNextFallbackApi";
|
|
14
|
+
import { jsonParse } from "./jsonParse";
|
|
15
|
+
import { log } from "./logging/log";
|
|
16
|
+
import { sendErrorLog } from "./logging/sendErrorLog";
|
|
13
17
|
const getResponseBody = async ({ response, shouldLog = true, shouldThrow = false, }) => {
|
|
14
18
|
try {
|
|
15
19
|
const json = await response.json();
|
|
@@ -26,14 +30,36 @@ const getResponseBody = async ({ response, shouldLog = true, shouldThrow = false
|
|
|
26
30
|
return null;
|
|
27
31
|
}
|
|
28
32
|
};
|
|
29
|
-
const
|
|
33
|
+
const encodeString = (text) => {
|
|
34
|
+
try {
|
|
35
|
+
// encode the string, remove the "=" at the end of the encoded string
|
|
36
|
+
const encodedString = btoa(text).replace(/=/g, '');
|
|
37
|
+
return encodedString;
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
// return the reverse string if the encoding failed
|
|
41
|
+
return text
|
|
42
|
+
.replace(/[^A-Za-z0-9]/g, '')
|
|
43
|
+
.split('')
|
|
44
|
+
.reverse()
|
|
45
|
+
.join('');
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
const handleRequest = async ({ basePathType, options, path, }) => {
|
|
30
49
|
let response;
|
|
50
|
+
// fallback to the main base API if the passed in base path is not available
|
|
51
|
+
const baseUrl = fallbackApiList[basePathType] || MAIN_BASE_API_TYPE;
|
|
52
|
+
const url = baseUrl + path;
|
|
53
|
+
// adding random encoded token to the url so that the request will be cached differently for each store
|
|
54
|
+
const modifiedUrl = new URL(url);
|
|
55
|
+
modifiedUrl.searchParams.set('zToken', encodeString(window.location.hostname));
|
|
31
56
|
try {
|
|
32
|
-
response = await fetch(
|
|
57
|
+
response = await fetch(modifiedUrl.toString(), options);
|
|
33
58
|
}
|
|
34
59
|
catch (e) {
|
|
35
60
|
if (e instanceof Error) {
|
|
36
61
|
return {
|
|
62
|
+
baseUrl,
|
|
37
63
|
errors: [{ message: e.message, type: 'unknown' }],
|
|
38
64
|
json: null,
|
|
39
65
|
response: null,
|
|
@@ -42,6 +68,7 @@ const handleRequest = async (url, options) => {
|
|
|
42
68
|
}
|
|
43
69
|
const message = 'Fail to fetch';
|
|
44
70
|
return {
|
|
71
|
+
baseUrl,
|
|
45
72
|
errors: [{ message, type: 'unknown' }],
|
|
46
73
|
json: null,
|
|
47
74
|
response: null,
|
|
@@ -57,6 +84,7 @@ const handleRequest = async (url, options) => {
|
|
|
57
84
|
if (response.ok) {
|
|
58
85
|
const data = await getResponseBody({ response });
|
|
59
86
|
return {
|
|
87
|
+
baseUrl,
|
|
60
88
|
errors: [],
|
|
61
89
|
json: data,
|
|
62
90
|
response,
|
|
@@ -71,6 +99,7 @@ const handleRequest = async (url, options) => {
|
|
|
71
99
|
});
|
|
72
100
|
const message = JSON.stringify(data);
|
|
73
101
|
return {
|
|
102
|
+
baseUrl,
|
|
74
103
|
errors: [{ message, type: status }],
|
|
75
104
|
json: null,
|
|
76
105
|
response,
|
|
@@ -80,6 +109,7 @@ const handleRequest = async (url, options) => {
|
|
|
80
109
|
catch (e) {
|
|
81
110
|
const message = 'Unknown error';
|
|
82
111
|
return {
|
|
112
|
+
baseUrl,
|
|
83
113
|
errors: [{ message: `${status}: ${message}`, type: status }],
|
|
84
114
|
json: null,
|
|
85
115
|
response: null,
|
|
@@ -87,31 +117,65 @@ const handleRequest = async (url, options) => {
|
|
|
87
117
|
};
|
|
88
118
|
}
|
|
89
119
|
};
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
120
|
+
const handleRequestWithFallback = async ({ basePathType, options, path, }) => {
|
|
121
|
+
var _a, _b;
|
|
122
|
+
const _c = await handleRequest({
|
|
123
|
+
basePathType,
|
|
124
|
+
options,
|
|
125
|
+
path,
|
|
126
|
+
}), { errors, response } = _c, rest = __rest(_c, ["errors", "response"]);
|
|
127
|
+
const nextFallbackApiType = getNextFallbackApiType(basePathType);
|
|
128
|
+
const unknownError = errors.find(({ type }) => type === 'unknown');
|
|
129
|
+
const isUnknownError = !!unknownError || (response === null || response === void 0 ? void 0 : response.status) === 404;
|
|
130
|
+
const shouldCallFallback =
|
|
131
|
+
// If next fallback is available, and the error is unknown (fail to fetch) or 404
|
|
132
|
+
nextFallbackApiType && isUnknownError;
|
|
133
|
+
// Should log the error if it's unknown error
|
|
134
|
+
if (isUnknownError) {
|
|
135
|
+
void sendErrorLog({
|
|
136
|
+
endpoint: fallbackApiList[basePathType] + path,
|
|
137
|
+
errors: errors.map(({ message }) => message),
|
|
138
|
+
metadata: {
|
|
139
|
+
currentBasePath: basePathType,
|
|
140
|
+
nextFallbackApiType,
|
|
141
|
+
requestHeaders: options === null || options === void 0 ? void 0 : options.headers,
|
|
142
|
+
requestPayload: jsonParse((_a = options === null || options === void 0 ? void 0 : options.body) === null || _a === void 0 ? void 0 : _a.toString()) || ((_b = options === null || options === void 0 ? void 0 : options.body) === null || _b === void 0 ? void 0 : _b.toString()),
|
|
143
|
+
status: response === null || response === void 0 ? void 0 : response.status,
|
|
144
|
+
statusText: response === null || response === void 0 ? void 0 : response.statusText,
|
|
145
|
+
},
|
|
146
|
+
// When the error is unknown, error reporting should be enabled for period of time after release, so we can monitor the error. We should always report error for the last fallback API fails
|
|
147
|
+
shouldPostReleaseErrorReporting: !!nextFallbackApiType,
|
|
148
|
+
});
|
|
95
149
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
150
|
+
if (shouldCallFallback) {
|
|
151
|
+
log(`=== "${path}" failed to fetch, trying next fallback ${nextFallbackApiType}`);
|
|
152
|
+
return handleRequestWithFallback({
|
|
153
|
+
basePathType: nextFallbackApiType,
|
|
154
|
+
options,
|
|
155
|
+
path,
|
|
156
|
+
});
|
|
103
157
|
}
|
|
104
|
-
|
|
105
|
-
|
|
158
|
+
// No more fallbacks, just return the result
|
|
159
|
+
return Object.assign({ errors, response }, rest);
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* Fetch request and will request to fallback API when the first one fails
|
|
163
|
+
* @param path Path name of the request (e.g. /api/something/com).
|
|
164
|
+
* @param _opts fetch options
|
|
165
|
+
*/
|
|
166
|
+
export const handleFetch = async (path, _opts) => {
|
|
106
167
|
const opts = __rest(_opts || {}, []);
|
|
107
168
|
const body = opts.body ? JSON.stringify(opts.body) : undefined;
|
|
108
169
|
const method = opts.method || 'GET';
|
|
109
|
-
// adding random encoded token to the url so that the request will be cached differently for each store
|
|
110
|
-
const modifiedUrl = new URL(url);
|
|
111
|
-
modifiedUrl.searchParams.set('zToken', encodeString(window.location.hostname));
|
|
112
170
|
// headers content type
|
|
113
171
|
const headers = Object.assign({ 'Content-Type': 'application/json', 'Zonos-Is-BigCommerce': window.Zonos.isBigCommerce.toString(), 'Zonos-Referer': window.location.hostname, 'Zonos-Version': window.Zonos.version, credentialtoken: window.Zonos.zonosApiKey || 'Empty credential token from zonos-elements' }, opts.headers);
|
|
114
172
|
const options = Object.assign(Object.assign({}, opts), { body: method === 'GET' ? undefined : body, headers,
|
|
115
173
|
method });
|
|
116
|
-
|
|
174
|
+
// This is for testing fallback API, set fallback API type to start with
|
|
175
|
+
const useFallbackApi = new URLSearchParams(window.location.search).get('useFallbackApi');
|
|
176
|
+
return handleRequestWithFallback({
|
|
177
|
+
basePathType: useFallbackApi || MAIN_BASE_API_TYPE,
|
|
178
|
+
options,
|
|
179
|
+
path,
|
|
180
|
+
});
|
|
117
181
|
};
|
|
@@ -1,5 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { fallbackApiList, getNextFallbackApiType, MAIN_BASE_API, } from "../getNextFallbackApi";
|
|
2
|
+
/**
|
|
3
|
+
* This is the time in milliseconds after release time that we will start sending error logs to the new API
|
|
4
|
+
* @default 1 hour
|
|
5
|
+
*/
|
|
6
|
+
const postReleaseReportingDuration = 1 * 3600 * 1000; // 1 hour
|
|
7
|
+
export const sendErrorLog = async ({ endpoint, errors, metadata, shouldPostReleaseErrorReporting, }) => {
|
|
8
|
+
if (shouldPostReleaseErrorReporting) {
|
|
9
|
+
// We only want to report errors that happened if current time is less than 2 hours after release time
|
|
10
|
+
const releaseTime = Number(window.Zonos.releaseDate);
|
|
11
|
+
const currentTime = new Date().getTime();
|
|
12
|
+
if (currentTime - releaseTime > postReleaseReportingDuration) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
3
16
|
const userAgentData = 'userAgentData' in window.navigator
|
|
4
17
|
? // userAgentData is not yet supported in all browsers
|
|
5
18
|
window.navigator.userAgentData
|
|
@@ -11,8 +24,22 @@ export const sendErrorLog = async ({ endpoint, errors, metadata }) => {
|
|
|
11
24
|
? userAgentData
|
|
12
25
|
: window.navigator.userAgent }, metadata),
|
|
13
26
|
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
27
|
+
try {
|
|
28
|
+
// use native fetch because in handleFetch also calls sendErrorLog and it will possibly cause circular dependencies issue
|
|
29
|
+
void fetch(`${MAIN_BASE_API}/api/zonos-elements/log-error`, {
|
|
30
|
+
body: JSON.stringify(errorObject),
|
|
31
|
+
method: 'POST',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (_err) {
|
|
35
|
+
const nextFallbackApi = getNextFallbackApiType();
|
|
36
|
+
if (!nextFallbackApi) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// request to the fallback API if the first one fails
|
|
40
|
+
void fetch(`${fallbackApiList[nextFallbackApi]}/api/zonos-elements/log-error`, {
|
|
41
|
+
body: JSON.stringify(errorObject),
|
|
42
|
+
method: 'POST',
|
|
43
|
+
});
|
|
44
|
+
}
|
|
18
45
|
};
|
|
@@ -14,7 +14,7 @@ export const sendLoadScriptLog = async (params) => {
|
|
|
14
14
|
const scriptLoadLogBody = Object.assign(Object.assign({ href: window.location.href }, params), { metadata: Object.assign({ connectionDownlink: baseConnection === null || baseConnection === void 0 ? void 0 : baseConnection.downlink, connectionEffectiveType: baseConnection === null || baseConnection === void 0 ? void 0 : baseConnection.effectiveType, connectionRtt: baseConnection === null || baseConnection === void 0 ? void 0 : baseConnection.rtt, connectionSaveData: baseConnection === null || baseConnection === void 0 ? void 0 : baseConnection.saveData, userAgent: userAgentData && userAgentData.brands.length > 0
|
|
15
15
|
? userAgentData
|
|
16
16
|
: window.navigator.userAgent }, params.metadata) });
|
|
17
|
-
void handleFetch(
|
|
17
|
+
void handleFetch('/api/zonos-elements/log-script-load', {
|
|
18
18
|
body: scriptLoadLogBody,
|
|
19
19
|
method: 'POST',
|
|
20
20
|
});
|
|
@@ -5,7 +5,10 @@ import { config } from "dotenv";
|
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
// rollup and ts somehow can't compile this with absolute path
|
|
7
7
|
// eslint-disable-next-line no-relative-import-paths/no-relative-import-paths
|
|
8
|
-
import {
|
|
8
|
+
import { formattedCloudflareApiUrl } from "./utils/formattedCloudflareApiUrl";
|
|
9
|
+
// rollup and ts somehow can't compile this with absolute path
|
|
10
|
+
// eslint-disable-next-line no-relative-import-paths/no-relative-import-paths
|
|
11
|
+
import { formattedUrl } from "./utils/formattedUrl";
|
|
9
12
|
export const isClientSide = typeof window !== 'undefined';
|
|
10
13
|
export const inProdEnvironment = process.env.NODE_ENV === 'production';
|
|
11
14
|
export const inTestEnvironment = process.env.NODE_ENV === 'test';
|
|
@@ -26,83 +29,6 @@ const UNPKG_ZONOS_URL = `https://unpkg.com/@zonos/elements@${packageVersion}`;
|
|
|
26
29
|
*/
|
|
27
30
|
const CDNJS_ZONOS_URL = `https://cdnjs.cloudflare.com/ajax/libs/zonos-elements/${packageVersion}`;
|
|
28
31
|
const currentBranch = process.env.VERCEL_GIT_COMMIT_REF || process.env.BRANCH_NAME;
|
|
29
|
-
export const formattedUrl = ({ branchName, inCI: _inCI, inVercel: _inVercel, isVercelPreview: _isVercelPreview, packageVersion: _packageVersion, type = 'full', url, }) => {
|
|
30
|
-
if (!url) {
|
|
31
|
-
// return origin value so t3 can throw error when it's empty
|
|
32
|
-
return url;
|
|
33
|
-
}
|
|
34
|
-
const trimmedUrl = (_url) => _url === null || _url === void 0 ? void 0 : _url.replace('https://', '');
|
|
35
|
-
const domainOnlyUrl = trimmedUrl(url);
|
|
36
|
-
if (!_inCI && !_inVercel) {
|
|
37
|
-
// local host environment, no need to format url
|
|
38
|
-
return `https://${domainOnlyUrl}`;
|
|
39
|
-
}
|
|
40
|
-
const isProdBranch = branchName && ['main'].includes(branchName);
|
|
41
|
-
const isServerless =
|
|
42
|
-
// url: route.js.zonos.com
|
|
43
|
-
domainOnlyUrl.includes('route.js') ||
|
|
44
|
-
// url: zonos-elements-serverless-git-something
|
|
45
|
-
domainOnlyUrl.includes('serverless-git');
|
|
46
|
-
const isTest =
|
|
47
|
-
// url: test.js.zonos.com
|
|
48
|
-
domainOnlyUrl.includes('test.') ||
|
|
49
|
-
// url: zonos-elements-test-git-something
|
|
50
|
-
domainOnlyUrl.includes('test-git') ||
|
|
51
|
-
// url: something.preview.js.zonos.com (this is only applicable on preview deployment of `zonos-elements` and `zonos-elements-test`)
|
|
52
|
-
domainOnlyUrl.includes('preview.js.zonos.com');
|
|
53
|
-
if (isTest && isProdBranch) {
|
|
54
|
-
return `https://${domainOnlyUrl}`;
|
|
55
|
-
}
|
|
56
|
-
// always use vercel url if in vercel env, non-production branch (not qa or main branch) or the domain using is a test url
|
|
57
|
-
if ((_isVercelPreview && !isProdBranch) ||
|
|
58
|
-
isTest ||
|
|
59
|
-
(_inVercel && type === 'vercel-git')) {
|
|
60
|
-
// use vercel branch_url instead of the production url
|
|
61
|
-
if (!branchName) {
|
|
62
|
-
throw new Error('Fail to get vercel commit ref or branch name');
|
|
63
|
-
}
|
|
64
|
-
// get project name based on the `${url}`
|
|
65
|
-
const isStorybook =
|
|
66
|
-
// url: storybook.js.zonos.com
|
|
67
|
-
domainOnlyUrl.includes('storybook.js') ||
|
|
68
|
-
// url: zonos-elements-storybook-git-something
|
|
69
|
-
domainOnlyUrl.includes('storybook-git');
|
|
70
|
-
const projectName = isStorybook
|
|
71
|
-
? 'zonos-elements-storybook'
|
|
72
|
-
: isServerless
|
|
73
|
-
? 'zonos-elements-serverless'
|
|
74
|
-
: 'zonos-elements';
|
|
75
|
-
// add -test suffix for test environment
|
|
76
|
-
const fullProjectName = isTest ? `${projectName}-test` : projectName;
|
|
77
|
-
// generate generated deploy url based on the branch name
|
|
78
|
-
const vercelGitBranchSuffix = generateVercelDeployUrl({
|
|
79
|
-
branchName,
|
|
80
|
-
projectName: fullProjectName,
|
|
81
|
-
});
|
|
82
|
-
return `https://${vercelGitBranchSuffix}`;
|
|
83
|
-
}
|
|
84
|
-
// only require package version in production and in CI, so that we still can use dev url (no version prefix).
|
|
85
|
-
if (!_packageVersion && (isProdBranch || _inCI)) {
|
|
86
|
-
throw new Error('package version is required');
|
|
87
|
-
}
|
|
88
|
-
const version = type === 'major' ? `${_packageVersion === null || _packageVersion === void 0 ? void 0 : _packageVersion.split('.')[0]}` : _packageVersion;
|
|
89
|
-
const isVersionedUrl = domainOnlyUrl === null || domainOnlyUrl === void 0 ? void 0 : domainOnlyUrl.startsWith('v');
|
|
90
|
-
const shouldAddVersion = (isProdBranch || _inCI) && !isVersionedUrl;
|
|
91
|
-
/**
|
|
92
|
-
* Trimming the `qa` from the domain url only when we add version prefix
|
|
93
|
-
* @reason We've now introduced alpha versioning for the QA branch deployment.
|
|
94
|
-
* However, we've generated the domain as `v1.2.4-alpha.1.js.zonos.com`, while the
|
|
95
|
-
* current CDN URL env uses `qa.js.zonos.com` on qa branch. This discrepancy causes
|
|
96
|
-
* this function to return an invalid alpha domain (v1.2.4-alpha.1.qa.js.zonos.com).
|
|
97
|
-
*/
|
|
98
|
-
const trimmedQaUrl = shouldAddVersion
|
|
99
|
-
? domainOnlyUrl === null || domainOnlyUrl === void 0 ? void 0 : domainOnlyUrl.replace('qa.js.zonos.com', 'js.zonos.com')
|
|
100
|
-
: domainOnlyUrl;
|
|
101
|
-
// only set version for the url once
|
|
102
|
-
return shouldAddVersion
|
|
103
|
-
? `https://v${version}.${trimmedQaUrl}`
|
|
104
|
-
: `https://${trimmedQaUrl}`;
|
|
105
|
-
};
|
|
106
32
|
if (typeof window === 'undefined') {
|
|
107
33
|
// storybook can't access to any "node" modules
|
|
108
34
|
config();
|
|
@@ -124,9 +50,11 @@ else {
|
|
|
124
50
|
}
|
|
125
51
|
const envClient = {
|
|
126
52
|
NEXT_PUBLIC_FLAG_URL: z.string(),
|
|
53
|
+
NEXT_PUBLIC_RELEASE_DATE: z.string(),
|
|
127
54
|
NEXT_PUBLIC_ZONOS_API_KEY: z.string(),
|
|
128
55
|
NEXT_PUBLIC_ZONOS_CDNJS_CDN_URL: z.string(),
|
|
129
56
|
NEXT_PUBLIC_ZONOS_CDN_URL: z.string(),
|
|
57
|
+
NEXT_PUBLIC_ZONOS_CLOUDFLARE_URL: z.string(),
|
|
130
58
|
NEXT_PUBLIC_ZONOS_JS_DELIVR_CDN_URL: z.string(),
|
|
131
59
|
NEXT_PUBLIC_ZONOS_PACKAGE_VERSION: z.string(),
|
|
132
60
|
NEXT_PUBLIC_ZONOS_ROUTE_URL: z.string(),
|
|
@@ -149,6 +77,7 @@ export const env = createEnv({
|
|
|
149
77
|
// eslint-disable-next-line sort-keys/sort-keys-fix
|
|
150
78
|
runtimeEnvStrict: {
|
|
151
79
|
NEXT_PUBLIC_FLAG_URL: process.env.NEXT_PUBLIC_FLAG_URL,
|
|
80
|
+
NEXT_PUBLIC_RELEASE_DATE: new Date().getTime().toString(),
|
|
152
81
|
NEXT_PUBLIC_ZONOS_API_KEY: process.env.NEXT_PUBLIC_ZONOS_API_KEY,
|
|
153
82
|
NEXT_PUBLIC_ZONOS_CDNJS_CDN_URL: CDNJS_ZONOS_URL,
|
|
154
83
|
/**
|
|
@@ -164,6 +93,14 @@ export const env = createEnv({
|
|
|
164
93
|
type: 'full',
|
|
165
94
|
url: process.env.NEXT_PUBLIC_ZONOS_CDN_URL,
|
|
166
95
|
}),
|
|
96
|
+
NEXT_PUBLIC_ZONOS_CLOUDFLARE_URL: formattedCloudflareApiUrl({
|
|
97
|
+
branchName: currentBranch,
|
|
98
|
+
inCI,
|
|
99
|
+
inVercel,
|
|
100
|
+
isVercelPreview,
|
|
101
|
+
projectName: 'zonos-elements',
|
|
102
|
+
url: process.env.NEXT_PUBLIC_ZONOS_CLOUDFLARE_URL,
|
|
103
|
+
}),
|
|
167
104
|
NEXT_PUBLIC_ZONOS_JS_DELIVR_CDN_URL: JS_DELIVR_ZONOS_URL,
|
|
168
105
|
NEXT_PUBLIC_ZONOS_PACKAGE_VERSION: packageVersion,
|
|
169
106
|
/**
|
|
@@ -16,11 +16,15 @@ export const getZonosQaUrl = () => {
|
|
|
16
16
|
const qaUrl = extractZonosQaUrl(window.location.href);
|
|
17
17
|
const cdnUrl = process.env.NEXT_PUBLIC_ZONOS_CDN_URL;
|
|
18
18
|
// when the qaUrl is not set and the built URL is from the test domain, use the test domain for testing
|
|
19
|
-
if (!qaUrl &&
|
|
19
|
+
if (!qaUrl &&
|
|
20
|
+
(cdnUrl.includes('test.') ||
|
|
21
|
+
// Vercel git domain
|
|
22
|
+
cdnUrl.includes('test-'))) {
|
|
20
23
|
return cdnUrl;
|
|
21
24
|
}
|
|
22
25
|
// when local demo app is running, it needs to use the local dev script unless the qaUrl is set
|
|
23
|
-
if (!qaUrl &&
|
|
26
|
+
if (!qaUrl &&
|
|
27
|
+
['dev.demo.elements.zonos.com'].includes(window.location.hostname)) {
|
|
24
28
|
return 'https://dev.elements.zonos.com';
|
|
25
29
|
}
|
|
26
30
|
if (!qaUrl) {
|
|
@@ -40,6 +40,7 @@ Zonos.zonosConversionTest = typeof window !== 'undefined' &&
|
|
|
40
40
|
!!window.location.search.match(/\?.*(&*)zonosConversionTest/);
|
|
41
41
|
Zonos._paypalMockResponse = null;
|
|
42
42
|
Zonos.isBigCommerce = false;
|
|
43
|
+
Zonos.releaseDate = process.env.NEXT_PUBLIC_RELEASE_DATE;
|
|
43
44
|
/**
|
|
44
45
|
* By default, the package will load from npm
|
|
45
46
|
*/
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// rollup and ts somehow can't compile this with absolute path
|
|
2
|
+
// eslint-disable-next-line no-relative-import-paths/no-relative-import-paths
|
|
3
|
+
import { generateCloudflareDeployUrl } from "./generateCloudflareDeployUrl";
|
|
4
|
+
/**
|
|
5
|
+
* This is to generate cloudflare url based on the environment. This won't
|
|
6
|
+
* - Get the cloudflare alias to build if the environment is Preview
|
|
7
|
+
* - Get the cloudflare production url (not preview alias) if it's production
|
|
8
|
+
* @note
|
|
9
|
+
* This is mainly used for get correct Cloudflare API to be used in scripts
|
|
10
|
+
* @note
|
|
11
|
+
* "url" is the API url in Cloudflare
|
|
12
|
+
* @returns
|
|
13
|
+
* - `https://<branch_name>.<project_name>.pages.dev` if is Preview
|
|
14
|
+
* - `https://route.elements.zonos.com` if is Production
|
|
15
|
+
*/
|
|
16
|
+
export const formattedCloudflareApiUrl = ({ branchName, inCI: _inCI, inVercel: _inVercel, isVercelPreview: _isVercelPreview, projectName, url, }) => {
|
|
17
|
+
if (!url) {
|
|
18
|
+
// return origin value so t3 can throw error when it's empty
|
|
19
|
+
return url;
|
|
20
|
+
}
|
|
21
|
+
const trimmedUrl = (_url) => _url === null || _url === void 0 ? void 0 : _url.replace('https://', '');
|
|
22
|
+
const domainOnlyUrl = trimmedUrl(url);
|
|
23
|
+
if (!_inCI && !_inVercel) {
|
|
24
|
+
// local host environment, no need to format url
|
|
25
|
+
return `https://${domainOnlyUrl}`;
|
|
26
|
+
}
|
|
27
|
+
const isProdBranch = branchName && ['main'].includes(branchName);
|
|
28
|
+
// always use Cloudflare preview alias if in not production environment
|
|
29
|
+
if (_isVercelPreview && !isProdBranch) {
|
|
30
|
+
// use Cloudflare branch_url instead of the production url
|
|
31
|
+
if (!branchName) {
|
|
32
|
+
throw new Error('Fail to get Cloudflare commit ref or branch name');
|
|
33
|
+
}
|
|
34
|
+
// generate generated deploy url based on the branch name
|
|
35
|
+
const cloudflareUrl = generateCloudflareDeployUrl({
|
|
36
|
+
branchName,
|
|
37
|
+
projectName,
|
|
38
|
+
});
|
|
39
|
+
return `https://${cloudflareUrl}`;
|
|
40
|
+
}
|
|
41
|
+
// use cloudflare production url
|
|
42
|
+
return `https://${domainOnlyUrl}`;
|
|
43
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// rollup and ts somehow can't compile this with absolute path
|
|
2
|
+
// eslint-disable-next-line no-relative-import-paths/no-relative-import-paths
|
|
3
|
+
import { generateVercelDeployUrl } from "./generateVercelDeployUrl";
|
|
4
|
+
export const formattedUrl = ({ branchName, inCI: _inCI, inVercel: _inVercel, isVercelPreview: _isVercelPreview, packageVersion: _packageVersion, type = 'full', url, }) => {
|
|
5
|
+
if (!url) {
|
|
6
|
+
// return origin value so t3 can throw error when it's empty
|
|
7
|
+
return url;
|
|
8
|
+
}
|
|
9
|
+
const trimmedUrl = (_url) => _url === null || _url === void 0 ? void 0 : _url.replace('https://', '');
|
|
10
|
+
const domainOnlyUrl = trimmedUrl(url);
|
|
11
|
+
if (!_inCI && !_inVercel) {
|
|
12
|
+
// local host environment, no need to format url
|
|
13
|
+
return `https://${domainOnlyUrl}`;
|
|
14
|
+
}
|
|
15
|
+
const isProdBranch = branchName && ['main'].includes(branchName);
|
|
16
|
+
const isServerless =
|
|
17
|
+
// url: route.js.zonos.com
|
|
18
|
+
domainOnlyUrl.includes('route.js') ||
|
|
19
|
+
// url: zonos-elements-serverless-git-something
|
|
20
|
+
domainOnlyUrl.includes('serverless-git');
|
|
21
|
+
const isTest =
|
|
22
|
+
// url: test.js.zonos.com
|
|
23
|
+
domainOnlyUrl.includes('test.') ||
|
|
24
|
+
// url: zonos-elements-test-git-something
|
|
25
|
+
domainOnlyUrl.includes('test-git') ||
|
|
26
|
+
// url: something.preview.js.zonos.com (this is only applicable on preview deployment of `zonos-elements` and `zonos-elements-test`)
|
|
27
|
+
domainOnlyUrl.includes('preview.js.zonos.com');
|
|
28
|
+
if (isTest && isProdBranch) {
|
|
29
|
+
return `https://${domainOnlyUrl}`;
|
|
30
|
+
}
|
|
31
|
+
// always use vercel url if in vercel env, non-production branch (not qa or main branch) or the domain using is a test url
|
|
32
|
+
if ((_isVercelPreview && !isProdBranch) ||
|
|
33
|
+
isTest ||
|
|
34
|
+
(_inVercel && type === 'vercel-git')) {
|
|
35
|
+
// use vercel branch_url instead of the production url
|
|
36
|
+
if (!branchName) {
|
|
37
|
+
throw new Error('Fail to get vercel commit ref or branch name');
|
|
38
|
+
}
|
|
39
|
+
// get project name based on the `${url}`
|
|
40
|
+
const isStorybook =
|
|
41
|
+
// url: storybook.js.zonos.com
|
|
42
|
+
domainOnlyUrl.includes('storybook.js') ||
|
|
43
|
+
// url: zonos-elements-storybook-git-something
|
|
44
|
+
domainOnlyUrl.includes('storybook-git');
|
|
45
|
+
const projectName = isStorybook
|
|
46
|
+
? 'zonos-elements-storybook'
|
|
47
|
+
: isServerless
|
|
48
|
+
? 'zonos-elements-serverless'
|
|
49
|
+
: 'zonos-elements';
|
|
50
|
+
// add -test suffix for test environment
|
|
51
|
+
const fullProjectName = isTest ? `${projectName}-test` : projectName;
|
|
52
|
+
// generate generated deploy url based on the branch name
|
|
53
|
+
const vercelGitBranchSuffix = generateVercelDeployUrl({
|
|
54
|
+
branchName,
|
|
55
|
+
projectName: fullProjectName,
|
|
56
|
+
});
|
|
57
|
+
return `https://${vercelGitBranchSuffix}`;
|
|
58
|
+
}
|
|
59
|
+
// only require package version in production and in CI, so that we still can use dev url (no version prefix).
|
|
60
|
+
if (!_packageVersion && (isProdBranch || _inCI)) {
|
|
61
|
+
throw new Error('package version is required');
|
|
62
|
+
}
|
|
63
|
+
const version = type === 'major' ? `${_packageVersion === null || _packageVersion === void 0 ? void 0 : _packageVersion.split('.')[0]}` : _packageVersion;
|
|
64
|
+
const isVersionedUrl = domainOnlyUrl === null || domainOnlyUrl === void 0 ? void 0 : domainOnlyUrl.startsWith('v');
|
|
65
|
+
const shouldAddVersion = (isProdBranch || _inCI) && !isVersionedUrl;
|
|
66
|
+
/**
|
|
67
|
+
* Trimming the `qa` from the domain url only when we add version prefix
|
|
68
|
+
* @reason We've now introduced alpha versioning for the QA branch deployment.
|
|
69
|
+
* However, we've generated the domain as `v1.2.4-alpha.1.js.zonos.com`, while the
|
|
70
|
+
* current CDN URL env uses `qa.js.zonos.com` on qa branch. This discrepancy causes
|
|
71
|
+
* this function to return an invalid alpha domain (v1.2.4-alpha.1.qa.js.zonos.com).
|
|
72
|
+
*/
|
|
73
|
+
const trimmedQaUrl = shouldAddVersion
|
|
74
|
+
? domainOnlyUrl === null || domainOnlyUrl === void 0 ? void 0 : domainOnlyUrl.replace('qa.js.zonos.com', 'js.zonos.com')
|
|
75
|
+
: domainOnlyUrl;
|
|
76
|
+
// only set version for the url once
|
|
77
|
+
return shouldAddVersion
|
|
78
|
+
? `https://v${version}.${trimmedQaUrl}`
|
|
79
|
+
: `https://${trimmedQaUrl}`;
|
|
80
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a Cloudflare deploy URL based on the branch name and project name
|
|
3
|
+
* @note not including https://
|
|
4
|
+
* @return {url} string
|
|
5
|
+
* @urlExample build-cloudflare-2.zonos-elements.page.dev
|
|
6
|
+
*/
|
|
7
|
+
export const generateCloudflareDeployUrl = ({ branchName, projectName, }) => {
|
|
8
|
+
const normalizedProjectName = projectName.toLowerCase().replace(/\s+/g, '-');
|
|
9
|
+
// remove dots and replace non-alphanumeric characters with dashes
|
|
10
|
+
const normalizedBranchName = branchName
|
|
11
|
+
.toLowerCase()
|
|
12
|
+
.replace(/\./g, '-') // Replace periods with hyphens
|
|
13
|
+
.replace(/\//g, '-') // Replace slashes with hyphens
|
|
14
|
+
.replace(/[^a-z0-9-_]/g, '') // Remove any characters that aren't alphanumeric, hyphens, or underscores
|
|
15
|
+
.replace(/-+/g, '-') // Collapse multiple hyphens into a single hyphen
|
|
16
|
+
.replace(/^-+|-+$/g, ''); // Trim hyphens from start and end
|
|
17
|
+
if (!normalizedProjectName || !normalizedBranchName) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return `${normalizedBranchName}.${normalizedProjectName}.pages.dev`;
|
|
21
|
+
};
|