oneentry 1.0.142 → 1.0.144
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/configure.js +2 -2
- package/dist/admins/adminsApi.d.ts +50 -0
- package/dist/admins/adminsApi.js +65 -0
- package/dist/admins/adminsInterfaces.d.ts +98 -0
- package/dist/admins/adminsInterfaces.js +2 -0
- package/dist/admins/adminsSchemas.d.ts +30 -0
- package/dist/admins/adminsSchemas.js +27 -0
- package/dist/attribute-sets/attributeSetsApi.d.ts +63 -0
- package/dist/attribute-sets/attributeSetsApi.js +98 -0
- package/dist/attribute-sets/attributeSetsInterfaces.d.ts +185 -0
- package/dist/attribute-sets/attributeSetsInterfaces.js +2 -0
- package/dist/attribute-sets/attributeSetsSchemas.d.ts +90 -0
- package/dist/attribute-sets/attributeSetsSchemas.js +74 -0
- package/dist/auth-provider/authProviderApi.d.ts +249 -0
- package/dist/auth-provider/authProviderApi.js +354 -0
- package/dist/auth-provider/authProviderSchemas.d.ts +131 -0
- package/dist/auth-provider/authProviderSchemas.js +82 -0
- package/dist/auth-provider/authProvidersInterfaces.d.ts +412 -0
- package/dist/auth-provider/authProvidersInterfaces.js +2 -0
- package/dist/base/asyncModules.d.ts +80 -0
- package/dist/base/asyncModules.js +448 -0
- package/dist/base/result.d.ts +39 -0
- package/dist/base/result.js +154 -0
- package/dist/base/stateModule.d.ts +41 -0
- package/dist/base/stateModule.js +128 -0
- package/dist/base/syncModules.d.ts +286 -0
- package/dist/base/syncModules.js +716 -0
- package/dist/base/utils.d.ts +197 -0
- package/dist/base/utils.js +2 -0
- package/dist/base/validation.d.ts +118 -0
- package/dist/base/validation.js +132 -0
- package/dist/blocks/blocksApi.d.ts +88 -0
- package/dist/blocks/blocksApi.js +207 -0
- package/dist/blocks/blocksInterfaces.d.ts +179 -0
- package/dist/blocks/blocksInterfaces.js +2 -0
- package/dist/blocks/blocksSchemas.d.ts +195 -0
- package/dist/blocks/blocksSchemas.js +43 -0
- package/dist/discounts/discountsApi.d.ts +76 -0
- package/dist/discounts/discountsApi.js +116 -0
- package/dist/discounts/discountsInterfaces.d.ts +217 -0
- package/dist/discounts/discountsInterfaces.js +2 -0
- package/dist/events/eventsApi.d.ts +60 -0
- package/dist/events/eventsApi.js +97 -0
- package/dist/events/eventsInterfaces.d.ts +87 -0
- package/dist/events/eventsInterfaces.js +2 -0
- package/dist/file-uploading/fileUploadingApi.d.ts +88 -0
- package/dist/file-uploading/fileUploadingApi.js +129 -0
- package/dist/file-uploading/fileUploadingInterfaces.d.ts +114 -0
- package/dist/file-uploading/fileUploadingInterfaces.js +2 -0
- package/dist/file-uploading/fileUploadingSchemas.d.ts +22 -0
- package/dist/file-uploading/fileUploadingSchemas.js +21 -0
- package/dist/forms/formsApi.d.ts +42 -0
- package/dist/forms/formsApi.js +57 -0
- package/dist/forms/formsInterfaces.d.ts +144 -0
- package/dist/forms/formsInterfaces.js +2 -0
- package/dist/forms/formsSchemas.d.ts +53 -0
- package/dist/forms/formsSchemas.js +34 -0
- package/dist/forms-data/formsDataApi.d.ts +106 -0
- package/dist/forms-data/formsDataApi.js +189 -0
- package/dist/forms-data/formsDataInterfaces.d.ts +522 -0
- package/dist/forms-data/formsDataInterfaces.js +2 -0
- package/dist/forms-data/formsDataSchemas.d.ts +115 -0
- package/dist/forms-data/formsDataSchemas.js +86 -0
- package/dist/general-types/generalTypesApi.d.ts +28 -0
- package/dist/general-types/generalTypesApi.js +38 -0
- package/dist/general-types/generalTypesInterfaces.d.ts +29 -0
- package/dist/general-types/generalTypesInterfaces.js +2 -0
- package/dist/general-types/generalTypesSchemas.d.ts +52 -0
- package/dist/general-types/generalTypesSchemas.js +36 -0
- package/dist/index.d.ts +124 -0
- package/dist/index.js +103 -0
- package/dist/integration-collections/integrationCollectionsApi.d.ts +163 -0
- package/dist/integration-collections/integrationCollectionsApi.js +220 -0
- package/dist/integration-collections/integrationCollectionsInterfaces.d.ts +313 -0
- package/dist/integration-collections/integrationCollectionsInterfaces.js +2 -0
- package/dist/integration-collections/integrationCollectionsSchemas.d.ts +80 -0
- package/dist/integration-collections/integrationCollectionsSchemas.js +61 -0
- package/dist/locales/localesApi.d.ts +27 -0
- package/dist/locales/localesApi.js +37 -0
- package/dist/locales/localesInterfaces.d.ts +40 -0
- package/dist/locales/localesInterfaces.js +2 -0
- package/dist/locales/localesSchemas.d.ts +32 -0
- package/dist/locales/localesSchemas.js +26 -0
- package/dist/menus/menusApi.d.ts +29 -0
- package/dist/menus/menusApi.js +39 -0
- package/dist/menus/menusInterfaces.d.ts +87 -0
- package/dist/menus/menusInterfaces.js +3 -0
- package/dist/menus/menusSchemas.d.ts +16 -0
- package/dist/menus/menusSchemas.js +28 -0
- package/dist/orders/ordersApi.d.ts +175 -0
- package/dist/orders/ordersApi.js +247 -0
- package/dist/orders/ordersInterfaces.d.ts +593 -0
- package/dist/orders/ordersInterfaces.js +2 -0
- package/dist/orders/ordersSchemas.d.ts +120 -0
- package/dist/orders/ordersSchemas.js +101 -0
- package/dist/pages/pagesApi.d.ts +151 -0
- package/dist/pages/pagesApi.js +390 -0
- package/dist/pages/pagesInterfaces.d.ts +286 -0
- package/dist/pages/pagesInterfaces.js +2 -0
- package/dist/pages/pagesSchemas.d.ts +85 -0
- package/dist/pages/pagesSchemas.js +46 -0
- package/dist/payments/paymentsApi.d.ts +82 -0
- package/dist/payments/paymentsApi.js +121 -0
- package/dist/payments/paymentsInterfaces.d.ts +200 -0
- package/dist/payments/paymentsInterfaces.js +2 -0
- package/dist/payments/paymentsSchemas.d.ts +100 -0
- package/dist/payments/paymentsSchemas.js +65 -0
- package/dist/product-statuses/productStatusesApi.d.ts +47 -0
- package/dist/product-statuses/productStatusesApi.js +70 -0
- package/dist/product-statuses/productStatusesInterfaces.d.ts +62 -0
- package/dist/product-statuses/productStatusesInterfaces.js +2 -0
- package/dist/product-statuses/productStatusesSchemas.d.ts +34 -0
- package/dist/product-statuses/productStatusesSchemas.js +30 -0
- package/dist/products/productsApi.d.ts +366 -0
- package/dist/products/productsApi.js +458 -0
- package/dist/products/productsInterfaces.d.ts +593 -0
- package/dist/products/productsInterfaces.js +2 -0
- package/dist/products/productsSchemas.d.ts +200 -0
- package/dist/products/productsSchemas.js +98 -0
- package/dist/sitemap/sitemapApi.d.ts +35 -0
- package/dist/sitemap/sitemapApi.js +45 -0
- package/dist/sitemap/sitemapInterfaces.d.ts +40 -0
- package/dist/sitemap/sitemapInterfaces.js +2 -0
- package/dist/system/systemApi.d.ts +43 -0
- package/dist/system/systemApi.js +56 -0
- package/dist/system/systemInterfaces.d.ts +29 -0
- package/dist/system/systemInterfaces.js +2 -0
- package/dist/templates/templatesApi.d.ts +49 -0
- package/dist/templates/templatesApi.js +75 -0
- package/dist/templates/templatesInterfaces.d.ts +67 -0
- package/dist/templates/templatesInterfaces.js +2 -0
- package/dist/templates/templatesSchemas.d.ts +48 -0
- package/dist/templates/templatesSchemas.js +31 -0
- package/dist/templates-preview/templatesPreviewApi.d.ts +38 -0
- package/dist/templates-preview/templatesPreviewApi.js +53 -0
- package/dist/templates-preview/templatesPreviewInterfaces.d.ts +119 -0
- package/dist/templates-preview/templatesPreviewInterfaces.js +2 -0
- package/dist/templates-preview/templatesPreviewSchemas.d.ts +83 -0
- package/dist/templates-preview/templatesPreviewSchemas.js +48 -0
- package/dist/users/usersApi.d.ts +143 -0
- package/dist/users/usersApi.js +171 -0
- package/dist/users/usersInterfaces.d.ts +231 -0
- package/dist/users/usersInterfaces.js +2 -0
- package/dist/users/usersSchemas.d.ts +38 -0
- package/dist/users/usersSchemas.js +28 -0
- package/dist/web-socket/wsApi.d.ts +25 -0
- package/dist/web-socket/wsApi.js +45 -0
- package/dist/web-socket/wsInterfaces.d.ts +16 -0
- package/dist/web-socket/wsInterfaces.js +2 -0
- package/package.json +2 -80
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
// Module-level device ID cache — survives class re-instantiation,
|
|
4
|
+
// but resets on page reload if localStorage is unavailable.
|
|
5
|
+
let _persistentDeviceId = null;
|
|
6
|
+
/**
|
|
7
|
+
* Returns a stable device ID for the browser environment.
|
|
8
|
+
*
|
|
9
|
+
* Algorithm:
|
|
10
|
+
* 1. If the module-level cache is already populated — return it (fastest path).
|
|
11
|
+
* 2. Otherwise try reading the ID from localStorage (key `oneentry_device_id`).
|
|
12
|
+
* This ensures the same browser gets the same ID across sessions and tabs.
|
|
13
|
+
* 3. If localStorage has nothing — generate a new ID from timestamp + random,
|
|
14
|
+
* persist it to localStorage and cache it in the module variable.
|
|
15
|
+
* 4. If localStorage is unavailable (SSR, private mode, iframe sandbox) —
|
|
16
|
+
* return null; the caller falls back to `_nodeDeviceId`.
|
|
17
|
+
* @returns {string | null} Device ID or null if localStorage is unavailable.
|
|
18
|
+
*/
|
|
19
|
+
function _getBrowserDeviceId() {
|
|
20
|
+
const win = typeof globalThis !== 'undefined' ? globalThis.window : undefined;
|
|
21
|
+
if (!(win === null || win === void 0 ? void 0 : win.localStorage))
|
|
22
|
+
return null;
|
|
23
|
+
if (_persistentDeviceId)
|
|
24
|
+
return _persistentDeviceId;
|
|
25
|
+
try {
|
|
26
|
+
const stored = win.localStorage.getItem('oneentry_device_id');
|
|
27
|
+
if (stored) {
|
|
28
|
+
_persistentDeviceId = stored;
|
|
29
|
+
return _persistentDeviceId;
|
|
30
|
+
}
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
32
|
+
}
|
|
33
|
+
catch (_e) {
|
|
34
|
+
// ignore
|
|
35
|
+
}
|
|
36
|
+
// Generate ID: base-36 timestamp + two random segments.
|
|
37
|
+
// base-36 is more compact than hex and contains no special characters.
|
|
38
|
+
const id = Date.now().toString(36) +
|
|
39
|
+
Math.random().toString(36).substring(2, 15) +
|
|
40
|
+
Math.random().toString(36).substring(2, 15);
|
|
41
|
+
_persistentDeviceId = id;
|
|
42
|
+
try {
|
|
43
|
+
win.localStorage.setItem('oneentry_device_id', id);
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
45
|
+
}
|
|
46
|
+
catch (_e) {
|
|
47
|
+
// ignore
|
|
48
|
+
}
|
|
49
|
+
return _persistentDeviceId;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Abstract class representing synchronization modules.
|
|
53
|
+
*/
|
|
54
|
+
class SyncModules {
|
|
55
|
+
/**
|
|
56
|
+
* Constructor to initialize state and URL.
|
|
57
|
+
* @param {StateModule} state - StateModule instance.
|
|
58
|
+
*/
|
|
59
|
+
constructor(state) {
|
|
60
|
+
/**
|
|
61
|
+
* Sorts attributes by their positions.
|
|
62
|
+
*
|
|
63
|
+
* The API returns attributes as an object `{ marker: AttrObject }`.
|
|
64
|
+
* Each attribute has a `position` field. The method rebuilds the object
|
|
65
|
+
* with keys sorted by ascending `position` so that the display order
|
|
66
|
+
* matches the order defined in the CMS.
|
|
67
|
+
* @param {any} data - The data containing attributes.
|
|
68
|
+
* @returns {any} Sorted attributes.
|
|
69
|
+
*/
|
|
70
|
+
this._sortAttributes = (data) => Object.fromEntries(Object.entries(data).sort(([, a], [, b]) => a.position - b.position));
|
|
71
|
+
this.state = state;
|
|
72
|
+
this._url = state.url;
|
|
73
|
+
this._nodeDeviceId =
|
|
74
|
+
Date.now().toString(36) +
|
|
75
|
+
Math.random().toString(36).substring(2, 15) +
|
|
76
|
+
Math.random().toString(36).substring(2, 15);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Constructs the full URL path by appending the given path to the base URL.
|
|
80
|
+
* @param {string} path - The path to append to the base URL.
|
|
81
|
+
* @returns {string} The full URL as a string.
|
|
82
|
+
*/
|
|
83
|
+
_getFullPath(path) {
|
|
84
|
+
return this._url + path;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Converts query parameters into a query string.
|
|
88
|
+
* @param {IProductsQuery | IUploadingQuery | any} query - The query object containing key-value pairs.
|
|
89
|
+
* @returns {string} A string representation of the query parameters.
|
|
90
|
+
*/
|
|
91
|
+
_queryParamsToString(query) {
|
|
92
|
+
// Skip null/undefined to avoid sending empty parameters.
|
|
93
|
+
// 0, false and '' are kept — they are semantically meaningful.
|
|
94
|
+
return Object.keys(query)
|
|
95
|
+
.filter((key) => query[key] !== null && query[key] !== undefined)
|
|
96
|
+
.map((key) => `${key}=${query[key]}`)
|
|
97
|
+
.join('&');
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Normalizes data based on language code.
|
|
101
|
+
*
|
|
102
|
+
* Recursively traverses the API response and unwraps localized fields.
|
|
103
|
+
* The API stores translations as objects like
|
|
104
|
+
* `{ "en_US": "Hello", "ru_RU": "Hello" }`. The method replaces such an object
|
|
105
|
+
* with the value for the requested `langCode` when that key is present.
|
|
106
|
+
*
|
|
107
|
+
* Traversal order:
|
|
108
|
+
* 1. Array → recursively normalize each element, then `_normalizeAttr`.
|
|
109
|
+
* 2. Object → for each key:
|
|
110
|
+
* - value is an array: recurse;
|
|
111
|
+
* - value is a non-object (primitive) or falsy: copy as-is;
|
|
112
|
+
* - object has key `langCode`: take only the needed translation;
|
|
113
|
+
* - otherwise: recurse deeper.
|
|
114
|
+
* 3. Primitive → return as-is.
|
|
115
|
+
* @param {any} data - The data to normalize.
|
|
116
|
+
* @param {string} langCode - The language code for normalization.
|
|
117
|
+
* @returns {any} Normalized data.
|
|
118
|
+
*/
|
|
119
|
+
_normalizeData(data, langCode = this.state.lang) {
|
|
120
|
+
if (Array.isArray(data)) {
|
|
121
|
+
return this._normalizeAttr(data.map((item) => this._normalizeData(item, langCode)));
|
|
122
|
+
}
|
|
123
|
+
else if (typeof data === 'object' && data) {
|
|
124
|
+
const normalizeData = {};
|
|
125
|
+
Object.keys(data).forEach((key) => {
|
|
126
|
+
if (Array.isArray(data[key])) {
|
|
127
|
+
normalizeData[key] = this._normalizeData(data[key], langCode);
|
|
128
|
+
}
|
|
129
|
+
else if (!data[key] || typeof data[key] !== 'object') {
|
|
130
|
+
normalizeData[key] = data[key];
|
|
131
|
+
}
|
|
132
|
+
else if (langCode in data[key]) {
|
|
133
|
+
// Field contains a translation map — pick the requested locale.
|
|
134
|
+
normalizeData[key] = data[key][langCode];
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
normalizeData[key] = this._normalizeData(data[key], langCode);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
return this._normalizeAttr(normalizeData);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
return data;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Normalizes the body of a POST request.
|
|
148
|
+
*
|
|
149
|
+
* Performs two transformations before sending to the API:
|
|
150
|
+
*
|
|
151
|
+
* 1. **phoneSMS cleanup**: the API rejects an empty string as a field value,
|
|
152
|
+
* so if `notificationData.phoneSMS === ''` the key is deleted entirely.
|
|
153
|
+
* A missing key is treated by the API as "not provided"; an empty string is not.
|
|
154
|
+
*
|
|
155
|
+
* 2. **formData localization**: the API expects formData as
|
|
156
|
+
* `{ "<langCode>": [...fields] }`, but callers pass a flat array or a single object.
|
|
157
|
+
* The method wraps the data into the required structure.
|
|
158
|
+
* If `formData` is absent from the body — return the body as-is (only phoneSMS cleanup applied).
|
|
159
|
+
* @param {any} body - The body to normalize.
|
|
160
|
+
* @param {string} [langCode] - The language code for normalization.
|
|
161
|
+
* @returns {any} Normalized body.
|
|
162
|
+
*/
|
|
163
|
+
_normalizePostBody(body, langCode = this.state.lang) {
|
|
164
|
+
// API does not accept phoneSMS = '' — delete the field if empty.
|
|
165
|
+
if (body.notificationData && body.notificationData.phoneSMS === '') {
|
|
166
|
+
delete body.notificationData.phoneSMS;
|
|
167
|
+
}
|
|
168
|
+
// If formData is not provided — no further normalization needed.
|
|
169
|
+
if (!body.formData)
|
|
170
|
+
return body;
|
|
171
|
+
// Wrap form fields in an object keyed by locale:
|
|
172
|
+
// [{ marker, value }] → { "en_US": [{ marker, value }] }
|
|
173
|
+
const formData = {};
|
|
174
|
+
formData[langCode] = Array.isArray(body.formData)
|
|
175
|
+
? body.formData
|
|
176
|
+
: [body.formData];
|
|
177
|
+
body.formData = formData;
|
|
178
|
+
return body;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Clears arrays within the data structure.
|
|
182
|
+
*
|
|
183
|
+
* Traverses the data and fixes a specific edge case with image attributes:
|
|
184
|
+
* when an `image` attribute has a single-element array in `value`,
|
|
185
|
+
* the API returns an array but consumers expect a plain object.
|
|
186
|
+
* In that case `value` is unwrapped: `[img]` → `img`.
|
|
187
|
+
*
|
|
188
|
+
* For all other keys the method recursively copies the structure unchanged.
|
|
189
|
+
* @param {Record<string, any>} data - The data to clear.
|
|
190
|
+
* @returns {any} Cleared data.
|
|
191
|
+
*/
|
|
192
|
+
_clearArray(data) {
|
|
193
|
+
if (Array.isArray(data)) {
|
|
194
|
+
return data.map((item) => this._clearArray(item));
|
|
195
|
+
}
|
|
196
|
+
else if (typeof data === 'object' && data) {
|
|
197
|
+
const normalizeData = {};
|
|
198
|
+
Object.keys(data).forEach((key) => {
|
|
199
|
+
if (Array.isArray(data[key])) {
|
|
200
|
+
normalizeData[key] = this._clearArray(data[key]);
|
|
201
|
+
}
|
|
202
|
+
else if (!data[key] || typeof data[key] !== 'object') {
|
|
203
|
+
normalizeData[key] = data[key];
|
|
204
|
+
}
|
|
205
|
+
else if (key === 'attributeValues') {
|
|
206
|
+
const attrs = data[key];
|
|
207
|
+
Object.keys(attrs).forEach((attr) => {
|
|
208
|
+
// If an image attribute has a single-element value array,
|
|
209
|
+
// unwrap it to a plain object for consumer convenience.
|
|
210
|
+
if (attrs[attr].type === 'image' &&
|
|
211
|
+
attrs[attr].value.length === 1) {
|
|
212
|
+
attrs[attr].value = attrs[attr].value[0];
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
normalizeData[key] = data[key];
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
normalizeData[key] = this._clearArray(data[key]);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
return normalizeData;
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
return data;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Adds a specified number of days to a date.
|
|
229
|
+
* @param {Date} date - The initial date.
|
|
230
|
+
* @param {number} days - The number of days to add.
|
|
231
|
+
* @returns {any} The new date with added days.
|
|
232
|
+
*/
|
|
233
|
+
_addDays(date, days) {
|
|
234
|
+
const result = new Date(date);
|
|
235
|
+
result.setUTCDate(result.getUTCDate() + days);
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Common logic for processing schedule dates (weekly, monthly, or both).
|
|
240
|
+
*
|
|
241
|
+
* Abstracts date iteration for three scheduling modes:
|
|
242
|
+
*
|
|
243
|
+
* - **`inEveryWeek` only**: starting from the start date, generates dates
|
|
244
|
+
* with a 7-day step until the end of the current month.
|
|
245
|
+
*
|
|
246
|
+
* - **`inEveryMonth` only**: pins the day-of-month from the start date
|
|
247
|
+
* and repeats it for each of the next 12 months. If the month does not
|
|
248
|
+
* have that day (e.g. Feb 31), the iteration is skipped.
|
|
249
|
+
*
|
|
250
|
+
* - **`inEveryWeek` + `inEveryMonth`**: for each of the next 12 months finds
|
|
251
|
+
* the first occurrence of the target weekday (from the start date), then
|
|
252
|
+
* iterates all occurrences of that weekday in the month with a 7-day step.
|
|
253
|
+
*
|
|
254
|
+
* `processDate(currentDate)` is called for every resolved date.
|
|
255
|
+
* @param {Date} date - The date for which to process intervals.
|
|
256
|
+
* @param {object} config - Configuration for schedule repetition.
|
|
257
|
+
* @param {boolean} config.inEveryWeek - Whether to repeat weekly.
|
|
258
|
+
* @param {boolean} config.inEveryMonth - Whether to repeat monthly.
|
|
259
|
+
* @param {(currentDate: Date) => void} processDate - Callback function to process each date.
|
|
260
|
+
*/
|
|
261
|
+
_processScheduleDates(date, config, processDate) {
|
|
262
|
+
// Handle weekly schedules
|
|
263
|
+
if (config.inEveryWeek && !config.inEveryMonth) {
|
|
264
|
+
let currentDate = new Date(date);
|
|
265
|
+
// Calculate the last day of the current month
|
|
266
|
+
const endOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0);
|
|
267
|
+
while (currentDate <= endOfMonth) {
|
|
268
|
+
processDate(currentDate);
|
|
269
|
+
// Move to the next week
|
|
270
|
+
currentDate = this._addDays(currentDate, 7);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// Handle monthly schedules
|
|
274
|
+
if (config.inEveryMonth && !config.inEveryWeek) {
|
|
275
|
+
const startDate = new Date(date);
|
|
276
|
+
const targetDayOfMonth = startDate.getUTCDate();
|
|
277
|
+
const numberOfMonths = 12;
|
|
278
|
+
for (let i = 0; i < numberOfMonths; i++) {
|
|
279
|
+
const currentDate = new Date(startDate);
|
|
280
|
+
currentDate.setUTCMonth(currentDate.getUTCMonth() + i);
|
|
281
|
+
// Try setting the current date to the target day of the month
|
|
282
|
+
currentDate.setUTCDate(targetDayOfMonth);
|
|
283
|
+
// Check if we have exceeded the month
|
|
284
|
+
if (currentDate.getUTCMonth() !== (startDate.getUTCMonth() + i) % 12) {
|
|
285
|
+
continue; // Skip this month if exceeded
|
|
286
|
+
}
|
|
287
|
+
processDate(currentDate);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// Handle both weekly and monthly schedules
|
|
291
|
+
if (config.inEveryMonth && config.inEveryWeek) {
|
|
292
|
+
const startDate = new Date(date);
|
|
293
|
+
const targetDayOfWeek = startDate.getUTCDay();
|
|
294
|
+
const numberOfMonths = 12;
|
|
295
|
+
for (let i = 0; i < numberOfMonths; i++) {
|
|
296
|
+
const currentDate = new Date(startDate);
|
|
297
|
+
currentDate.setUTCMonth(currentDate.getUTCMonth() + i);
|
|
298
|
+
// Set to the first day of the month
|
|
299
|
+
currentDate.setUTCDate(1);
|
|
300
|
+
// Find the first target day of the week in the current month
|
|
301
|
+
const daysUntilTargetDay = (targetDayOfWeek - currentDate.getUTCDay() + 7) % 7;
|
|
302
|
+
currentDate.setUTCDate(currentDate.getUTCDate() + daysUntilTargetDay);
|
|
303
|
+
// Iterate over all target days of the week in the current month
|
|
304
|
+
while (currentDate.getUTCMonth() ===
|
|
305
|
+
(startDate.getUTCMonth() + i) % 12) {
|
|
306
|
+
processDate(currentDate);
|
|
307
|
+
// Move to the next week (same day of the week)
|
|
308
|
+
currentDate.setUTCDate(currentDate.getUTCDate() + 7);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Generates intervals for a specific date based on a schedule.
|
|
315
|
+
*
|
|
316
|
+
* For each date resolved by `_processScheduleDates`, iterates over
|
|
317
|
+
* the `schedule.times` array of time ranges. Each range is a pair
|
|
318
|
+
* `[startTime, endTime]` with `{ hours, minutes }` fields.
|
|
319
|
+
* Creates an ISO interval `[start.toISOString(), end.toISOString()]`
|
|
320
|
+
* and adds it to `utcIntervals` (Set deduplicates automatically).
|
|
321
|
+
* @param {Date} date - The date for which to generate intervals.
|
|
322
|
+
* @param {object} schedule - The schedule defining the intervals.
|
|
323
|
+
* @param {boolean} schedule.inEveryWeek - The number of weeks between intervals.
|
|
324
|
+
* @param {any[]} schedule.times - The times for each interval.
|
|
325
|
+
* @param {boolean} schedule.inEveryMonth - The month intervals for each interval.
|
|
326
|
+
* @param {Set<Array<string>>} utcIntervals - A set to store unique intervals.
|
|
327
|
+
*/
|
|
328
|
+
_generateIntervalsForDate(date, schedule, utcIntervals) {
|
|
329
|
+
this._processScheduleDates(date, schedule, (currentDate) => {
|
|
330
|
+
schedule.times.forEach((timeRange) => {
|
|
331
|
+
const [startTime, endTime] = timeRange;
|
|
332
|
+
const intervalStart = new Date(currentDate);
|
|
333
|
+
intervalStart.setUTCHours(startTime.hours, startTime.minutes, 0, 0);
|
|
334
|
+
const intervalEnd = new Date(currentDate);
|
|
335
|
+
intervalEnd.setUTCHours(endTime.hours, endTime.minutes, 0, 0);
|
|
336
|
+
utcIntervals.add([
|
|
337
|
+
intervalStart.toISOString(),
|
|
338
|
+
intervalEnd.toISOString(),
|
|
339
|
+
]);
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Adds time intervals to schedules.
|
|
345
|
+
*
|
|
346
|
+
* Accepts an array of schedule groups (structure of `timeInterval` attributes
|
|
347
|
+
* for pages/products). For each group iterates over `values` — the set of
|
|
348
|
+
* concrete schedules. Each schedule contains a date range `dates[0..1]`.
|
|
349
|
+
*
|
|
350
|
+
* If both boundaries are equal (`isSameDay`), intervals are generated only
|
|
351
|
+
* for that single date. Otherwise — for every day in the range inclusive.
|
|
352
|
+
*
|
|
353
|
+
* The result (`schedule.timeIntervals`) is a sorted array of ISO pairs,
|
|
354
|
+
* ready to pass to UI components.
|
|
355
|
+
* @param {any[]} schedules - The schedules to process.
|
|
356
|
+
* @returns {any} Schedules with added time intervals.
|
|
357
|
+
*/
|
|
358
|
+
_addTimeIntervalsToSchedules(schedules) {
|
|
359
|
+
schedules === null || schedules === void 0 ? void 0 : schedules.forEach((scheduleGroup) => {
|
|
360
|
+
// Skip if scheduleGroup.values is not an array
|
|
361
|
+
if (!scheduleGroup ||
|
|
362
|
+
!scheduleGroup.values ||
|
|
363
|
+
!Array.isArray(scheduleGroup.values)) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
scheduleGroup.values.forEach((schedule) => {
|
|
367
|
+
const utcIntervals = new Set();
|
|
368
|
+
const startDate = new Date(schedule.dates[0]);
|
|
369
|
+
const endDate = new Date(schedule.dates[1]);
|
|
370
|
+
const isSameDay = startDate.toISOString() === endDate.toISOString();
|
|
371
|
+
if (isSameDay) {
|
|
372
|
+
this._generateIntervalsForDate(startDate, schedule, utcIntervals);
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
for (let currentDate = new Date(startDate); currentDate <= endDate; currentDate = this._addDays(currentDate, 1)) {
|
|
376
|
+
this._generateIntervalsForDate(currentDate, schedule, utcIntervals);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
schedule.timeIntervals = Array.from(utcIntervals).sort();
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
return schedules;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Generates intervals for a specific date for form schedules.
|
|
386
|
+
*
|
|
387
|
+
* Unlike `_generateIntervalsForDate`, time ranges here have a different shape:
|
|
388
|
+
* each `timeInterval` contains `start`, `end` and `period`
|
|
389
|
+
* (slot length in minutes). The method slices the [start, end) window into
|
|
390
|
+
* fixed-length slots of `period` minutes:
|
|
391
|
+
*
|
|
392
|
+
* start=09:00, end=12:00, period=30 → [09:00–09:30], [09:30–10:00], …, [11:30–12:00]
|
|
393
|
+
*
|
|
394
|
+
* Generation stops if the next slot would exceed `end`.
|
|
395
|
+
* Each slot is added to `utcIntervals` (Set deduplicates automatically).
|
|
396
|
+
* @param {Date} date - The date for which to generate intervals.
|
|
397
|
+
* @param {object} interval - The interval configuration.
|
|
398
|
+
* @param {boolean} interval.inEveryWeek - Indicates whether the schedule is weekly.
|
|
399
|
+
* @param {boolean} interval.inEveryMonth - Indicates whether the schedule is monthly.
|
|
400
|
+
* @param {any[]} timeIntervals - The time intervals to process.
|
|
401
|
+
* @param {Set<Array<string>>} utcIntervals - A set to store unique intervals.
|
|
402
|
+
*/
|
|
403
|
+
_generateIntervalsForFormDate(date, interval, timeIntervals, utcIntervals) {
|
|
404
|
+
const generateTimeSlotsForDate = (currentDate) => {
|
|
405
|
+
timeIntervals.forEach((timeInterval) => {
|
|
406
|
+
let currentStart = timeInterval.start;
|
|
407
|
+
const endTime = timeInterval.end;
|
|
408
|
+
// Slice the window into slots of `period` minutes each.
|
|
409
|
+
while (currentStart.hours < endTime.hours ||
|
|
410
|
+
(currentStart.hours === endTime.hours &&
|
|
411
|
+
currentStart.minutes < endTime.minutes)) {
|
|
412
|
+
const intervalStart = new Date(currentDate);
|
|
413
|
+
intervalStart.setUTCHours(currentStart.hours, currentStart.minutes, 0, 0);
|
|
414
|
+
// Compute slot end: add period minutes with hour carry normalization.
|
|
415
|
+
const nextMinutes = currentStart.minutes + timeInterval.period;
|
|
416
|
+
const nextHours = currentStart.hours + Math.floor(nextMinutes / 60);
|
|
417
|
+
const minutes = nextMinutes % 60;
|
|
418
|
+
// If the slot end exceeds `end` — stop; partial slots are not emitted.
|
|
419
|
+
if (nextHours > endTime.hours ||
|
|
420
|
+
(nextHours === endTime.hours && minutes > endTime.minutes)) {
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
const intervalEnd = new Date(currentDate);
|
|
424
|
+
intervalEnd.setUTCHours(nextHours, minutes, 0, 0);
|
|
425
|
+
utcIntervals.add([
|
|
426
|
+
intervalStart.toISOString(),
|
|
427
|
+
intervalEnd.toISOString(),
|
|
428
|
+
]);
|
|
429
|
+
currentStart = { hours: nextHours, minutes };
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
};
|
|
433
|
+
this._processScheduleDates(date, interval, generateTimeSlotsForDate);
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Adds time intervals to form schedules (different structure).
|
|
437
|
+
*
|
|
438
|
+
* Same as `_addTimeIntervalsToSchedules` but for `timeInterval` attributes
|
|
439
|
+
* in **forms** (different API data structure):
|
|
440
|
+
* - `interval.range[0..1]` instead of `schedule.dates[0..1]`
|
|
441
|
+
* - `interval.intervals` — array of time ranges with slots (`period`)
|
|
442
|
+
* instead of `[startTime, endTime]` pairs
|
|
443
|
+
*
|
|
444
|
+
* Result is written to `interval.timeIntervals`.
|
|
445
|
+
* @param {any[]} intervals - The intervals to process.
|
|
446
|
+
* @returns {any} Intervals with added time intervals.
|
|
447
|
+
*/
|
|
448
|
+
_addTimeIntervalsToFormSchedules(intervals) {
|
|
449
|
+
intervals.forEach((interval) => {
|
|
450
|
+
var _a, _b;
|
|
451
|
+
if (!interval.intervals || !Array.isArray(interval.intervals)) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
const utcIntervals = new Set();
|
|
455
|
+
const startDate = new Date(interval.range[0]);
|
|
456
|
+
const endDate = new Date(interval.range[1]);
|
|
457
|
+
const isSameDay = startDate.toISOString() === endDate.toISOString();
|
|
458
|
+
const intervalConfig = {
|
|
459
|
+
inEveryWeek: (_a = interval.inEveryWeek) !== null && _a !== void 0 ? _a : false,
|
|
460
|
+
inEveryMonth: (_b = interval.inEveryMonth) !== null && _b !== void 0 ? _b : false,
|
|
461
|
+
};
|
|
462
|
+
if (isSameDay) {
|
|
463
|
+
this._generateIntervalsForFormDate(startDate, intervalConfig, interval.intervals, utcIntervals);
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
for (let currentDate = new Date(startDate); currentDate <= endDate; currentDate = this._addDays(currentDate, 1)) {
|
|
467
|
+
this._generateIntervalsForFormDate(currentDate, intervalConfig, interval.intervals, utcIntervals);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
interval.timeIntervals = Array.from(utcIntervals).sort();
|
|
471
|
+
});
|
|
472
|
+
return intervals;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Transforms additionalFields from array to object keyed by marker.
|
|
476
|
+
*
|
|
477
|
+
* The API returns `additionalFields` as an array: `[{ marker, ... }, ...]`.
|
|
478
|
+
* For convenient key-based access (`attr.additionalFields['fieldName']`)
|
|
479
|
+
* the method converts it to an object `{ marker: { marker, ... } }`.
|
|
480
|
+
* Transformation is skipped when `rawData` mode is enabled in config
|
|
481
|
+
* (the consumer wants the data as-is, without transformations).
|
|
482
|
+
* @param {any} attr - The attribute object that may contain additionalFields.
|
|
483
|
+
*/
|
|
484
|
+
_normalizeAdditionalFields(attr) {
|
|
485
|
+
if (!this.state.rawData && Array.isArray(attr.additionalFields)) {
|
|
486
|
+
attr.additionalFields = Object.fromEntries(attr.additionalFields.map((field) => [field.marker, field]));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Normalizes attributes within the data.
|
|
491
|
+
*
|
|
492
|
+
* Handles three different attribute formats returned by the API:
|
|
493
|
+
*
|
|
494
|
+
* **1. `attributeValues`** — attributes of pages, products and other entities.
|
|
495
|
+
* Contains an object `{ marker: AttrObject }`. For each attribute:
|
|
496
|
+
* - `_normalizeAdditionalFields` is called;
|
|
497
|
+
* - numeric types (`integer`, `float`) are cast to a JS number (or `null`);
|
|
498
|
+
* - `timeInterval` attributes are enriched with computed `timeIntervals`;
|
|
499
|
+
* - the whole object is re-sorted by `position`.
|
|
500
|
+
*
|
|
501
|
+
* **2. `attributes`** — form attributes (different API structure).
|
|
502
|
+
* Same `additionalFields` and `timeInterval` processing,
|
|
503
|
+
* but numbers are not normalized here (commented out — logic differs).
|
|
504
|
+
*
|
|
505
|
+
* **3. `type`** — a single attribute from an attribute set.
|
|
506
|
+
* Same transformations as in case 1, but without sorting.
|
|
507
|
+
*
|
|
508
|
+
* If none of the keys are found — data is returned unchanged.
|
|
509
|
+
* @param {any} data - The data to normalize.
|
|
510
|
+
* @returns {any} Normalized attributes.
|
|
511
|
+
*/
|
|
512
|
+
_normalizeAttr(data) {
|
|
513
|
+
// For regular attributes collections - pages, products, etc.
|
|
514
|
+
if ('attributeValues' in data) {
|
|
515
|
+
Object.keys(data.attributeValues).forEach((attr) => {
|
|
516
|
+
const d = data.attributeValues[attr];
|
|
517
|
+
this._normalizeAdditionalFields(d);
|
|
518
|
+
// normalize numbers
|
|
519
|
+
if (d.type === 'integer' || d.type === 'float') {
|
|
520
|
+
const numValue = Number(d.value);
|
|
521
|
+
d.value = isNaN(numValue) ? null : numValue;
|
|
522
|
+
}
|
|
523
|
+
// add timeIntervals
|
|
524
|
+
if (data.attributeValues[attr].type === 'timeInterval') {
|
|
525
|
+
const schedules = data.attributeValues[attr].value;
|
|
526
|
+
// console.log('Schedules: ', JSON.stringify(schedules));
|
|
527
|
+
if (Array.isArray(schedules) && schedules.length > 0) {
|
|
528
|
+
const result = this._addTimeIntervalsToSchedules(schedules);
|
|
529
|
+
data.attributeValues[attr].value = result;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
return {
|
|
534
|
+
...data,
|
|
535
|
+
attributeValues: this._sortAttributes(data.attributeValues),
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
// for forms attributes - forms attributes collections
|
|
539
|
+
if ('attributes' in data) {
|
|
540
|
+
const d = data.attributes;
|
|
541
|
+
Object.keys(d).forEach((attr) => {
|
|
542
|
+
var _a;
|
|
543
|
+
this._normalizeAdditionalFields(d[attr]);
|
|
544
|
+
// Normalize numbers
|
|
545
|
+
// if (d[attr].type === 'integer' || d[attr].type === 'float') {
|
|
546
|
+
// const numValue = Number(d[attr].value);
|
|
547
|
+
// d[attr].value = isNaN(numValue) ? null : numValue;
|
|
548
|
+
// }
|
|
549
|
+
// Add time intervals
|
|
550
|
+
if (d[attr].type === 'timeInterval') {
|
|
551
|
+
const intervals = (_a = d[attr].localizeInfos) === null || _a === void 0 ? void 0 : _a.intervals;
|
|
552
|
+
// console.log('Schedules:: ', JSON.stringify(intervals));
|
|
553
|
+
if (intervals && Array.isArray(intervals) && intervals.length > 0) {
|
|
554
|
+
const result = this._addTimeIntervalsToFormSchedules(intervals);
|
|
555
|
+
d[attr].localizeInfos.intervals = result;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
return data;
|
|
560
|
+
}
|
|
561
|
+
// For single attribute - for attribute sets
|
|
562
|
+
if ('type' in data) {
|
|
563
|
+
this._normalizeAdditionalFields(data);
|
|
564
|
+
// Normalize numbers
|
|
565
|
+
if (data.type === 'integer' || data.type === 'float') {
|
|
566
|
+
const numValue = Number(data.value);
|
|
567
|
+
data.value = isNaN(numValue) ? null : numValue;
|
|
568
|
+
}
|
|
569
|
+
// Add time intervals
|
|
570
|
+
if (data.type === 'timeInterval') {
|
|
571
|
+
const schedules = data.value;
|
|
572
|
+
if (Array.isArray(schedules) && schedules.length > 0) {
|
|
573
|
+
const result = this._addTimeIntervalsToSchedules(schedules);
|
|
574
|
+
data.value = result;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return data;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Processes data after fetching or receiving it.
|
|
582
|
+
*
|
|
583
|
+
* Final post-processing of the API response: first unwraps localized fields
|
|
584
|
+
* (`_normalizeData`), then fixes single-element image attributes
|
|
585
|
+
* (`_clearArray`). Called at the end of every fetch method.
|
|
586
|
+
* @param {any} data - The data to process.
|
|
587
|
+
* @param {any} [langCode] - The language code for processing.
|
|
588
|
+
* @returns {any} Processed data.
|
|
589
|
+
*/
|
|
590
|
+
_dataPostProcess(data, langCode = this.state.lang) {
|
|
591
|
+
const normalize = this._normalizeData(data, langCode);
|
|
592
|
+
const result = this._clearArray(normalize);
|
|
593
|
+
return result;
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Sets the access token in the state.
|
|
597
|
+
* @param {string} accessToken - The access token to set.
|
|
598
|
+
* @returns {any} The instance of SyncModules for chaining.
|
|
599
|
+
*/
|
|
600
|
+
setAccessToken(accessToken) {
|
|
601
|
+
this.state.accessToken = accessToken;
|
|
602
|
+
return this;
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Sets the refresh token in the state.
|
|
606
|
+
* @param {string} refreshToken - The refresh token to set.
|
|
607
|
+
* @returns {any} The instance of SyncModules for chaining.
|
|
608
|
+
*/
|
|
609
|
+
setRefreshToken(refreshToken) {
|
|
610
|
+
this.state.refreshToken = refreshToken;
|
|
611
|
+
return this;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Get deviceMetadata
|
|
615
|
+
*
|
|
616
|
+
* Builds a device metadata object to be sent in request headers.
|
|
617
|
+
* Used for analytics and anti-fraud on the API side.
|
|
618
|
+
*
|
|
619
|
+
* Returned JSON structure:
|
|
620
|
+
* ```json
|
|
621
|
+
* {
|
|
622
|
+
* "fingerprint": "UQ_<hash>_<instanceId>",
|
|
623
|
+
* "deviceInfo": { "os": "...", "browser": "...", "location": "en-US" }
|
|
624
|
+
* }
|
|
625
|
+
* ```
|
|
626
|
+
*
|
|
627
|
+
* **Fingerprint algorithm:**
|
|
628
|
+
* 1. Builds a string from stable characteristics: platform, userAgent, language,
|
|
629
|
+
* screen resolution, colorDepth, timezone, private-browsing mode, instanceId.
|
|
630
|
+
* 2. Runs it through a simple 32-bit hash (djb2-like).
|
|
631
|
+
* 3. Concatenates the hash and the first 12 characters of instanceId.
|
|
632
|
+
*
|
|
633
|
+
* **instanceId strategy:**
|
|
634
|
+
* - Browser: `_getBrowserDeviceId()` — read from localStorage, stable across sessions.
|
|
635
|
+
* - Node.js / no localStorage: `_nodeDeviceId` — generated at instance creation,
|
|
636
|
+
* lives until the process restarts.
|
|
637
|
+
*
|
|
638
|
+
* In a Node.js environment (no `window`) returns a simplified object without screen/navigator.
|
|
639
|
+
* @returns {string} - Returns an object containing device metadata.
|
|
640
|
+
*/
|
|
641
|
+
_getDeviceMetadata() {
|
|
642
|
+
var _a;
|
|
643
|
+
// Check if we're in a browser environment
|
|
644
|
+
if (typeof globalThis === 'undefined') {
|
|
645
|
+
return '';
|
|
646
|
+
}
|
|
647
|
+
// Access navigator through globalThis.window object to avoid direct reference
|
|
648
|
+
const win = globalThis.window;
|
|
649
|
+
const instanceId = (_a = _getBrowserDeviceId()) !== null && _a !== void 0 ? _a : this._nodeDeviceId;
|
|
650
|
+
// Node.js environment
|
|
651
|
+
if (!win) {
|
|
652
|
+
return JSON.stringify({
|
|
653
|
+
fingerprint: `UQ_${instanceId}`,
|
|
654
|
+
deviceInfo: {
|
|
655
|
+
os: 'Node.js',
|
|
656
|
+
browser: `Node.js/${instanceId.substring(0, 10)}`,
|
|
657
|
+
location: 'en-US',
|
|
658
|
+
},
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
const nav = win.navigator || {};
|
|
662
|
+
const platform = nav.platform || 'Win32';
|
|
663
|
+
const userAgent = nav.userAgent || 'Node.js/22';
|
|
664
|
+
const language = nav.language || 'en-US';
|
|
665
|
+
// Get screen information if available
|
|
666
|
+
const screen = win.screen || {};
|
|
667
|
+
const screenWidth = screen.width || 0;
|
|
668
|
+
const screenHeight = screen.height || 0;
|
|
669
|
+
const colorDepth = screen.colorDepth || 0;
|
|
670
|
+
// Get timezone
|
|
671
|
+
let timezone = 'UTC';
|
|
672
|
+
try {
|
|
673
|
+
timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
674
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
675
|
+
}
|
|
676
|
+
catch (e) {
|
|
677
|
+
// Ignore error, fallback to UTC
|
|
678
|
+
}
|
|
679
|
+
// Detect private browsing mode
|
|
680
|
+
let isPrivateBrowsing = false;
|
|
681
|
+
try {
|
|
682
|
+
// Simple localStorage test to detect private browsing
|
|
683
|
+
if (win.localStorage) {
|
|
684
|
+
const testKey = 'test_private_browsing';
|
|
685
|
+
win.localStorage.setItem(testKey, '1');
|
|
686
|
+
win.localStorage.removeItem(testKey);
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
isPrivateBrowsing = true; // If localStorage is not available, likely private browsing
|
|
690
|
+
}
|
|
691
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
692
|
+
}
|
|
693
|
+
catch (e) {
|
|
694
|
+
isPrivateBrowsing = true; // Error during localStorage access indicates private browsing
|
|
695
|
+
}
|
|
696
|
+
// Create a stable fingerprint string using stable device/browser characteristics
|
|
697
|
+
const fingerprintString = `${platform}|${userAgent}|${language}|${screenWidth}|${screenHeight}|${colorDepth}|${timezone}|${isPrivateBrowsing ? 'private' : 'normal'}|${instanceId}`;
|
|
698
|
+
// Simple but stable hash function
|
|
699
|
+
let hash = 0;
|
|
700
|
+
for (let i = 0; i < fingerprintString.length; i++) {
|
|
701
|
+
const char = fingerprintString.charCodeAt(i);
|
|
702
|
+
hash = (hash << 5) - hash + char;
|
|
703
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
704
|
+
}
|
|
705
|
+
const deviceMetadata = {
|
|
706
|
+
fingerprint: `UQ_${Math.abs(hash).toString(36)}_${instanceId.substring(0, 12)}`,
|
|
707
|
+
deviceInfo: {
|
|
708
|
+
os: platform.replace(/ /g, '_'),
|
|
709
|
+
browser: userAgent.replace(/ /g, '_'),
|
|
710
|
+
location: language,
|
|
711
|
+
},
|
|
712
|
+
};
|
|
713
|
+
return JSON.stringify(deviceMetadata);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
exports.default = SyncModules;
|