oneentry 1.0.159 → 1.0.161

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/README.md +2 -0
  2. package/changelog.md +56 -1
  3. package/dist/admins/adminsApi.d.ts +15 -1
  4. package/dist/admins/adminsApi.js +29 -0
  5. package/dist/admins/adminsInterfaces.d.ts +37 -1
  6. package/dist/base/lazySchema.d.ts +15 -0
  7. package/dist/base/lazySchema.js +20 -0
  8. package/dist/discounts/discountsApi.d.ts +14 -1
  9. package/dist/discounts/discountsApi.js +21 -0
  10. package/dist/discounts/discountsInterfaces.d.ts +27 -1
  11. package/dist/filters/filtersInterfaces.d.ts +2 -2
  12. package/dist/filters/filtersSchemas.d.ts +1 -1
  13. package/dist/filters/filtersSchemas.js +4 -1
  14. package/dist/forms-data/formsDataApi.d.ts +14 -1
  15. package/dist/forms-data/formsDataApi.js +21 -0
  16. package/dist/forms-data/formsDataInterfaces.d.ts +74 -1
  17. package/dist/index.d.ts +4 -0
  18. package/dist/index.js +3 -0
  19. package/dist/orders/ordersApi.d.ts +15 -1
  20. package/dist/orders/ordersApi.js +22 -0
  21. package/dist/orders/ordersInterfaces.d.ts +27 -1
  22. package/dist/pages/pagesApi.d.ts +14 -1
  23. package/dist/pages/pagesApi.js +21 -0
  24. package/dist/pages/pagesInterfaces.d.ts +38 -1
  25. package/dist/payments/paymentsInterfaces.d.ts +2 -2
  26. package/dist/products/productsInterfaces.d.ts +1 -1
  27. package/dist/search/searchApi.d.ts +35 -0
  28. package/dist/search/searchApi.js +86 -0
  29. package/dist/search/searchInterfaces.d.ts +94 -0
  30. package/dist/search/searchInterfaces.js +2 -0
  31. package/dist/search/searchSchemas.d.ts +73 -0
  32. package/dist/search/searchSchemas.js +44 -0
  33. package/dist/templates-preview/templatesPreviewInterfaces.d.ts +0 -2
  34. package/dist/templates-preview/templatesPreviewSchemas.d.ts +0 -2
  35. package/dist/templates-preview/templatesPreviewSchemas.js +0 -1
  36. package/dist/types.d.ts +35 -0
  37. package/dist/types.js +2 -0
  38. package/dist/users/usersApi.d.ts +15 -1
  39. package/dist/users/usersApi.js +22 -0
  40. package/dist/users/usersInterfaces.d.ts +39 -1
  41. package/dist/web-socket/lazySocket.d.ts +18 -0
  42. package/dist/web-socket/lazySocket.js +70 -0
  43. package/esm/admins/adminsApi.d.ts +66 -0
  44. package/esm/admins/adminsApi.js +90 -0
  45. package/esm/admins/adminsInterfaces.d.ts +137 -0
  46. package/esm/admins/adminsInterfaces.js +1 -0
  47. package/esm/admins/adminsSchemas.d.ts +48 -0
  48. package/esm/admins/adminsSchemas.js +31 -0
  49. package/esm/attribute-sets/attributeSetsApi.d.ts +67 -0
  50. package/esm/attribute-sets/attributeSetsApi.js +97 -0
  51. package/esm/attribute-sets/attributeSetsInterfaces.d.ts +285 -0
  52. package/esm/attribute-sets/attributeSetsInterfaces.js +1 -0
  53. package/esm/attribute-sets/attributeSetsSchemas.d.ts +84 -0
  54. package/esm/attribute-sets/attributeSetsSchemas.js +66 -0
  55. package/esm/auth-provider/authProviderApi.d.ts +262 -0
  56. package/esm/auth-provider/authProviderApi.js +362 -0
  57. package/esm/auth-provider/authProviderSchemas.d.ts +124 -0
  58. package/esm/auth-provider/authProviderSchemas.js +92 -0
  59. package/esm/auth-provider/authProvidersInterfaces.d.ts +430 -0
  60. package/esm/auth-provider/authProvidersInterfaces.js +1 -0
  61. package/esm/base/asyncModules.d.ts +123 -0
  62. package/esm/base/asyncModules.js +382 -0
  63. package/esm/base/lazySchema.d.ts +15 -0
  64. package/esm/base/lazySchema.js +17 -0
  65. package/esm/base/stateModule.d.ts +35 -0
  66. package/esm/base/stateModule.js +51 -0
  67. package/esm/base/syncModules.d.ts +245 -0
  68. package/esm/base/syncModules.js +555 -0
  69. package/esm/base/timeIntervals.d.ts +95 -0
  70. package/esm/base/timeIntervals.js +317 -0
  71. package/esm/base/utils.d.ts +354 -0
  72. package/esm/base/utils.js +1 -0
  73. package/esm/base/validation.d.ts +98 -0
  74. package/esm/base/validation.js +118 -0
  75. package/esm/blocks/blocksApi.d.ts +252 -0
  76. package/esm/blocks/blocksApi.js +394 -0
  77. package/esm/blocks/blocksInterfaces.d.ts +361 -0
  78. package/esm/blocks/blocksInterfaces.js +1 -0
  79. package/esm/blocks/blocksSchemas.d.ts +213 -0
  80. package/esm/blocks/blocksSchemas.js +40 -0
  81. package/esm/discounts/discountsApi.d.ts +94 -0
  82. package/esm/discounts/discountsApi.js +136 -0
  83. package/esm/discounts/discountsInterfaces.d.ts +293 -0
  84. package/esm/discounts/discountsInterfaces.js +1 -0
  85. package/esm/events/eventsApi.d.ts +108 -0
  86. package/esm/events/eventsApi.js +141 -0
  87. package/esm/events/eventsInterfaces.d.ts +176 -0
  88. package/esm/events/eventsInterfaces.js +1 -0
  89. package/esm/file-uploading/fileUploadingApi.d.ts +91 -0
  90. package/esm/file-uploading/fileUploadingApi.js +127 -0
  91. package/esm/file-uploading/fileUploadingInterfaces.d.ts +116 -0
  92. package/esm/file-uploading/fileUploadingInterfaces.js +1 -0
  93. package/esm/file-uploading/fileUploadingSchemas.d.ts +24 -0
  94. package/esm/file-uploading/fileUploadingSchemas.js +19 -0
  95. package/esm/filters/filtersApi.d.ts +32 -0
  96. package/esm/filters/filtersApi.js +36 -0
  97. package/esm/filters/filtersInterfaces.d.ts +56 -0
  98. package/esm/filters/filtersInterfaces.js +1 -0
  99. package/esm/filters/filtersSchemas.d.ts +32 -0
  100. package/esm/filters/filtersSchemas.js +29 -0
  101. package/esm/forms/formsApi.d.ts +44 -0
  102. package/esm/forms/formsApi.js +54 -0
  103. package/esm/forms/formsInterfaces.d.ts +237 -0
  104. package/esm/forms/formsInterfaces.js +1 -0
  105. package/esm/forms/formsSchemas.d.ts +56 -0
  106. package/esm/forms/formsSchemas.js +32 -0
  107. package/esm/forms-data/formsDataApi.d.ts +124 -0
  108. package/esm/forms-data/formsDataApi.js +210 -0
  109. package/esm/forms-data/formsDataInterfaces.d.ts +670 -0
  110. package/esm/forms-data/formsDataInterfaces.js +1 -0
  111. package/esm/forms-data/formsDataSchemas.d.ts +107 -0
  112. package/esm/forms-data/formsDataSchemas.js +83 -0
  113. package/esm/general-types/generalTypesApi.d.ts +29 -0
  114. package/esm/general-types/generalTypesApi.js +34 -0
  115. package/esm/general-types/generalTypesInterfaces.d.ts +29 -0
  116. package/esm/general-types/generalTypesInterfaces.js +1 -0
  117. package/esm/general-types/generalTypesSchemas.d.ts +68 -0
  118. package/esm/general-types/generalTypesSchemas.js +42 -0
  119. package/esm/index.d.ts +143 -0
  120. package/esm/index.js +124 -0
  121. package/esm/integration-collections/integrationCollectionsApi.d.ts +172 -0
  122. package/esm/integration-collections/integrationCollectionsApi.js +224 -0
  123. package/esm/integration-collections/integrationCollectionsInterfaces.d.ts +317 -0
  124. package/esm/integration-collections/integrationCollectionsInterfaces.js +1 -0
  125. package/esm/integration-collections/integrationCollectionsSchemas.d.ts +84 -0
  126. package/esm/integration-collections/integrationCollectionsSchemas.js +60 -0
  127. package/esm/locales/localesApi.d.ts +28 -0
  128. package/esm/locales/localesApi.js +33 -0
  129. package/esm/locales/localesInterfaces.d.ts +40 -0
  130. package/esm/locales/localesInterfaces.js +1 -0
  131. package/esm/locales/localesSchemas.d.ts +32 -0
  132. package/esm/locales/localesSchemas.js +23 -0
  133. package/esm/menus/menusApi.d.ts +30 -0
  134. package/esm/menus/menusApi.js +35 -0
  135. package/esm/menus/menusInterfaces.d.ts +87 -0
  136. package/esm/menus/menusInterfaces.js +2 -0
  137. package/esm/menus/menusSchemas.d.ts +16 -0
  138. package/esm/menus/menusSchemas.js +25 -0
  139. package/esm/orders/ordersApi.d.ts +213 -0
  140. package/esm/orders/ordersApi.js +288 -0
  141. package/esm/orders/ordersInterfaces.d.ts +763 -0
  142. package/esm/orders/ordersInterfaces.js +1 -0
  143. package/esm/orders/ordersSchemas.d.ts +312 -0
  144. package/esm/orders/ordersSchemas.js +171 -0
  145. package/esm/package.json +4 -0
  146. package/esm/pages/pagesApi.d.ts +173 -0
  147. package/esm/pages/pagesApi.js +414 -0
  148. package/esm/pages/pagesInterfaces.d.ts +344 -0
  149. package/esm/pages/pagesInterfaces.js +1 -0
  150. package/esm/pages/pagesSchemas.d.ts +121 -0
  151. package/esm/pages/pagesSchemas.js +60 -0
  152. package/esm/payments/paymentsApi.d.ts +88 -0
  153. package/esm/payments/paymentsApi.js +122 -0
  154. package/esm/payments/paymentsInterfaces.d.ts +189 -0
  155. package/esm/payments/paymentsInterfaces.js +1 -0
  156. package/esm/payments/paymentsSchemas.d.ts +94 -0
  157. package/esm/payments/paymentsSchemas.js +53 -0
  158. package/esm/product-statuses/productStatusesApi.d.ts +50 -0
  159. package/esm/product-statuses/productStatusesApi.js +68 -0
  160. package/esm/product-statuses/productStatusesInterfaces.d.ts +62 -0
  161. package/esm/product-statuses/productStatusesInterfaces.js +1 -0
  162. package/esm/product-statuses/productStatusesSchemas.d.ts +34 -0
  163. package/esm/product-statuses/productStatusesSchemas.js +27 -0
  164. package/esm/products/productsApi.d.ts +366 -0
  165. package/esm/products/productsApi.js +468 -0
  166. package/esm/products/productsInterfaces.d.ts +723 -0
  167. package/esm/products/productsInterfaces.js +1 -0
  168. package/esm/products/productsSchemas.d.ts +234 -0
  169. package/esm/products/productsSchemas.js +121 -0
  170. package/esm/search/searchApi.d.ts +35 -0
  171. package/esm/search/searchApi.js +47 -0
  172. package/esm/search/searchInterfaces.d.ts +94 -0
  173. package/esm/search/searchInterfaces.js +1 -0
  174. package/esm/search/searchSchemas.d.ts +73 -0
  175. package/esm/search/searchSchemas.js +41 -0
  176. package/esm/sitemap/sitemapApi.d.ts +37 -0
  177. package/esm/sitemap/sitemapApi.js +41 -0
  178. package/esm/sitemap/sitemapInterfaces.d.ts +51 -0
  179. package/esm/sitemap/sitemapInterfaces.js +1 -0
  180. package/esm/subscriptions/subscriptionsApi.d.ts +74 -0
  181. package/esm/subscriptions/subscriptionsApi.js +90 -0
  182. package/esm/subscriptions/subscriptionsInterfaces.d.ts +111 -0
  183. package/esm/subscriptions/subscriptionsInterfaces.js +1 -0
  184. package/esm/subscriptions/subscriptionsSchemas.d.ts +46 -0
  185. package/esm/subscriptions/subscriptionsSchemas.js +38 -0
  186. package/esm/system/systemApi.d.ts +46 -0
  187. package/esm/system/systemApi.js +53 -0
  188. package/esm/system/systemInterfaces.d.ts +29 -0
  189. package/esm/system/systemInterfaces.js +1 -0
  190. package/esm/templates/templatesApi.d.ts +53 -0
  191. package/esm/templates/templatesApi.js +78 -0
  192. package/esm/templates/templatesInterfaces.d.ts +68 -0
  193. package/esm/templates/templatesInterfaces.js +1 -0
  194. package/esm/templates/templatesSchemas.d.ts +48 -0
  195. package/esm/templates/templatesSchemas.js +28 -0
  196. package/esm/templates-preview/templatesPreviewApi.d.ts +40 -0
  197. package/esm/templates-preview/templatesPreviewApi.js +50 -0
  198. package/esm/templates-preview/templatesPreviewInterfaces.d.ts +129 -0
  199. package/esm/templates-preview/templatesPreviewInterfaces.js +1 -0
  200. package/esm/templates-preview/templatesPreviewSchemas.d.ts +79 -0
  201. package/esm/templates-preview/templatesPreviewSchemas.js +43 -0
  202. package/esm/types.d.ts +35 -0
  203. package/esm/types.js +1 -0
  204. package/esm/user-activity/userActivityApi.d.ts +31 -0
  205. package/esm/user-activity/userActivityApi.js +37 -0
  206. package/esm/user-activity/userActivityInterfaces.d.ts +42 -0
  207. package/esm/user-activity/userActivityInterfaces.js +1 -0
  208. package/esm/users/usersApi.d.ts +249 -0
  209. package/esm/users/usersApi.js +312 -0
  210. package/esm/users/usersInterfaces.d.ts +419 -0
  211. package/esm/users/usersInterfaces.js +1 -0
  212. package/esm/users/usersSchemas.d.ts +72 -0
  213. package/esm/users/usersSchemas.js +55 -0
  214. package/esm/web-socket/lazySocket.d.ts +18 -0
  215. package/esm/web-socket/lazySocket.js +67 -0
  216. package/esm/web-socket/wsApi.d.ts +27 -0
  217. package/esm/web-socket/wsApi.js +43 -0
  218. package/esm/web-socket/wsInterfaces.d.ts +16 -0
  219. package/esm/web-socket/wsInterfaces.js +1 -0
  220. package/package.json +47 -2
  221. package/types.d.ts +1 -0
  222. package/types.js +1 -0
@@ -0,0 +1,555 @@
1
+ // localStorage keys for the persistent browser-side identifiers.
2
+ const _DEVICE_ID_STORAGE_KEY = 'oneentry_device_id';
3
+ const _GUEST_ID_STORAGE_KEY = 'oneentry_guest_id';
4
+ // Module-level cache of persistent IDs keyed by storage key — survives class
5
+ // re-instantiation, but resets on page reload if localStorage is unavailable.
6
+ const _persistentIds = {};
7
+ /**
8
+ * Generates a new opaque identifier.
9
+ *
10
+ * Prefers a cryptographically secure source (Web Crypto, exposed as
11
+ * `globalThis.crypto` in browsers and Node.js >= 18): `randomUUID()` when
12
+ * available, otherwise random bytes rendered as a base-36 string. Falls back to
13
+ * a timestamp + `Math.random()` only when no CSPRNG exists (e.g. very old Node).
14
+ *
15
+ * The result contains no special characters, so it is safe to use both in
16
+ * localStorage and in HTTP headers.
17
+ * @returns {string} A freshly generated identifier.
18
+ */
19
+ function _generateId() {
20
+ const cryptoObj = typeof globalThis !== 'undefined' ? globalThis.crypto : undefined;
21
+ if (cryptoObj === null || cryptoObj === void 0 ? void 0 : cryptoObj.randomUUID) {
22
+ return cryptoObj.randomUUID();
23
+ }
24
+ if (cryptoObj === null || cryptoObj === void 0 ? void 0 : cryptoObj.getRandomValues) {
25
+ const bytes = cryptoObj.getRandomValues(new Uint8Array(18));
26
+ let result = '';
27
+ for (let i = 0; i < bytes.length; i++) {
28
+ result += bytes[i].toString(36).padStart(2, '0');
29
+ }
30
+ return result;
31
+ }
32
+ // Last-resort fallback for environments without Web Crypto.
33
+ // Non-cryptographic, but adequate when nothing better is available.
34
+ return (Date.now().toString(36) +
35
+ Math.random().toString(36).substring(2, 15) +
36
+ Math.random().toString(36).substring(2, 15));
37
+ }
38
+ /**
39
+ * Returns a stable identifier for the browser environment, persisted in localStorage.
40
+ *
41
+ * Algorithm (per `storageKey`):
42
+ * 1. If the module-level cache is already populated — return it (fastest path).
43
+ * 2. Otherwise try reading the ID from localStorage under `storageKey`.
44
+ * This ensures the same browser gets the same ID across sessions and tabs.
45
+ * 3. If localStorage has nothing — generate a new ID, persist it to localStorage
46
+ * and cache it in the module variable.
47
+ * 4. If localStorage is unavailable (SSR, private mode, iframe sandbox) —
48
+ * return null; the caller falls back to a per-instance Node id.
49
+ * @param {string} storageKey - localStorage key under which the ID is stored.
50
+ * @returns {string | null} The identifier or null if localStorage is unavailable.
51
+ */
52
+ function _getPersistentBrowserId(storageKey) {
53
+ const win = typeof globalThis !== 'undefined' ? globalThis.window : undefined;
54
+ if (!(win === null || win === void 0 ? void 0 : win.localStorage))
55
+ return null;
56
+ if (_persistentIds[storageKey])
57
+ return _persistentIds[storageKey];
58
+ try {
59
+ const stored = win.localStorage.getItem(storageKey);
60
+ if (stored) {
61
+ _persistentIds[storageKey] = stored;
62
+ return _persistentIds[storageKey];
63
+ }
64
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
65
+ }
66
+ catch (_e) {
67
+ // ignore
68
+ }
69
+ const id = _generateId();
70
+ _persistentIds[storageKey] = id;
71
+ try {
72
+ win.localStorage.setItem(storageKey, id);
73
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
74
+ }
75
+ catch (_e) {
76
+ // ignore
77
+ }
78
+ return _persistentIds[storageKey];
79
+ }
80
+ /**
81
+ * Abstract class representing synchronization modules.
82
+ */
83
+ export default class SyncModules {
84
+ /**
85
+ * Constructor to initialize state and URL.
86
+ * @param {StateModule} state - StateModule instance.
87
+ */
88
+ constructor(state) {
89
+ /**
90
+ * Sorts attributes by their positions.
91
+ *
92
+ * Each attribute has a `position` field, and the API returns them in no
93
+ * particular order. The method rebuilds the collection sorted by ascending
94
+ * `position`, so the display order matches the order defined in the CMS.
95
+ * Both container shapes the API uses are handled: an object keyed by marker
96
+ * (`attributeValues`) and an array (form `attributes`).
97
+ * @param {any} data - The attributes collection to sort.
98
+ * @returns {any} Sorted attributes, in the same container shape.
99
+ */
100
+ this._sortAttributes = (data) => Array.isArray(data)
101
+ ? [...data].sort((a, b) => a.position - b.position)
102
+ : Object.fromEntries(Object.entries(data).sort(([, a], [, b]) => a.position - b.position));
103
+ this.state = state;
104
+ this._url = state.url;
105
+ this._nodeDeviceId = _generateId();
106
+ }
107
+ /**
108
+ * Resolves the guest identifier for the current request, or `undefined`.
109
+ *
110
+ * Resolution order:
111
+ * 1. An explicitly configured `state.guestId` (from config or `setGuestId`) — wins.
112
+ * 2. A persistent browser ID stored in localStorage (key `oneentry_guest_id`),
113
+ * stable across sessions and tabs — mirrors the device-metadata strategy.
114
+ * Once resolved it is cached into `state.guestId` (safe: one browser = one user).
115
+ * 3. Otherwise `undefined` — the `x-guest-id` header is simply omitted.
116
+ *
117
+ * IMPORTANT (server-side): the SDK never invents a guest id on the server.
118
+ * `defineOneEntry` is typically created once and shared across many visitors,
119
+ * so a silently-generated server id would be cached into the shared state and
120
+ * leak one guest cart/wishlist across all anonymous visitors. On the server
121
+ * you must pass a per-visitor `guestId` (config or `setGuestId`) yourself.
122
+ * @returns {string | undefined} The guest identifier, or undefined when none is available.
123
+ */
124
+ _getGuestId() {
125
+ if (this.state.guestId)
126
+ return this.state.guestId;
127
+ // Browser only: a localStorage-backed id is safe to cache into shared state,
128
+ // because a single browser maps to a single guest.
129
+ const browserId = _getPersistentBrowserId(_GUEST_ID_STORAGE_KEY);
130
+ if (browserId) {
131
+ this.state.guestId = browserId;
132
+ return browserId;
133
+ }
134
+ // No localStorage and no explicit id (e.g. Node/SSR) — do not invent one.
135
+ return undefined;
136
+ }
137
+ /**
138
+ * Constructs the full URL path by appending the given path to the base URL.
139
+ * @param {string} path - The path to append to the base URL.
140
+ * @returns {string} The full URL as a string.
141
+ */
142
+ _getFullPath(path) {
143
+ return this._url + path;
144
+ }
145
+ /**
146
+ * Converts query parameters into a query string.
147
+ *
148
+ * Values are percent-encoded so free-text and special characters (spaces,
149
+ * `&`, `=`, non-ASCII) cannot corrupt the query string. Keys are known,
150
+ * machine-safe field names and are left as-is.
151
+ * @param {IProductsQuery | IUploadingQuery | any} query - The query object containing key-value pairs.
152
+ * @returns {string} A string representation of the query parameters.
153
+ */
154
+ _queryParamsToString(query) {
155
+ // Skip null/undefined to avoid sending empty parameters.
156
+ // 0, false and '' are kept — they are semantically meaningful.
157
+ return Object.keys(query)
158
+ .filter((key) => query[key] !== null && query[key] !== undefined)
159
+ .map((key) => `${key}=${encodeURIComponent(query[key])}`)
160
+ .join('&');
161
+ }
162
+ /**
163
+ * Normalizes data based on language code.
164
+ *
165
+ * Recursively traverses the API response and unwraps localized fields.
166
+ * The API stores translations as objects like
167
+ * `{ "en_US": "Hello", "ru_RU": "Hello" }`. The method replaces such an object
168
+ * with the value for the requested `langCode` when that key is present.
169
+ *
170
+ * Traversal order:
171
+ * 1. Array → recursively normalize each element, then `_normalizeAttr`.
172
+ * 2. Object → for each key:
173
+ * - value is an array: recurse;
174
+ * - value is a non-object (primitive) or falsy: copy as-is;
175
+ * - object has key `langCode`: take only the needed translation;
176
+ * - otherwise: recurse deeper.
177
+ * 3. Primitive → return as-is.
178
+ * @param {any} data - The data to normalize.
179
+ * @param {string} langCode - The language code for normalization.
180
+ * @returns {any} Normalized data.
181
+ */
182
+ _normalizeData(data, langCode = this.state.lang) {
183
+ if (Array.isArray(data)) {
184
+ return this._normalizeAttr(data.map((item) => this._normalizeData(item, langCode)));
185
+ }
186
+ else if (typeof data === 'object' && data) {
187
+ const normalizeData = {};
188
+ Object.keys(data).forEach((key) => {
189
+ if (Array.isArray(data[key])) {
190
+ normalizeData[key] = this._normalizeData(data[key], langCode);
191
+ }
192
+ else if (!data[key] || typeof data[key] !== 'object') {
193
+ normalizeData[key] = data[key];
194
+ }
195
+ else if (langCode in data[key]) {
196
+ // Field contains a translation map — pick the requested locale.
197
+ normalizeData[key] = data[key][langCode];
198
+ }
199
+ else {
200
+ normalizeData[key] = this._normalizeData(data[key], langCode);
201
+ }
202
+ });
203
+ return this._normalizeAttr(normalizeData);
204
+ }
205
+ else {
206
+ return data;
207
+ }
208
+ }
209
+ /**
210
+ * Normalizes the body of a POST request.
211
+ *
212
+ * Performs two transformations before sending to the API:
213
+ *
214
+ * 1. **phoneSMS cleanup**: the API rejects an empty string as a field value,
215
+ * so if `notificationData.phoneSMS === ''` the key is deleted entirely.
216
+ * A missing key is treated by the API as "not provided"; an empty string is not.
217
+ *
218
+ * 2. **formData localization**: the API expects formData as
219
+ * `{ "<langCode>": [...fields] }`, but callers pass a flat array or a single object.
220
+ * The method wraps the data into the required structure.
221
+ * If `formData` is absent from the body — return the body as-is (only phoneSMS cleanup applied).
222
+ * @param {any} body - The body to normalize.
223
+ * @param {string} [langCode] - The language code for normalization.
224
+ * @returns {any} Normalized body.
225
+ */
226
+ _normalizePostBody(body, langCode = this.state.lang) {
227
+ // API does not accept phoneSMS = '' — delete the field if empty.
228
+ if (body.notificationData && body.notificationData.phoneSMS === '') {
229
+ delete body.notificationData.phoneSMS;
230
+ }
231
+ // If formData is not provided — no further normalization needed.
232
+ if (!body.formData)
233
+ return body;
234
+ // Wrap form fields in an object keyed by locale:
235
+ // [{ marker, value }] → { "en_US": [{ marker, value }] }
236
+ const formData = {};
237
+ formData[langCode] = Array.isArray(body.formData)
238
+ ? body.formData
239
+ : [body.formData];
240
+ body.formData = formData;
241
+ return body;
242
+ }
243
+ /**
244
+ * Normalizes the value of a single attribute in place.
245
+ *
246
+ * Applies the three type-driven fixes the API response needs, so that an
247
+ * attribute of a given type always reaches the consumer in the same shape,
248
+ * no matter which collection it arrived in:
249
+ *
250
+ * 1. **Single-file values** (`image`, `file`) — the API always sends an array,
251
+ * even for one file: `[img]` → `img`. Multi-file values and `groupOfImages`
252
+ * (a collection by definition) stay an array.
253
+ * 2. **Empty values** — an attribute with no value comes back as an empty
254
+ * localization map `{}`; it is replaced with `null`, the same marker the
255
+ * numeric branch already produced.
256
+ * 3. **Numbers** (`integer`, `float`, `real`) — cast to a JS number; anything
257
+ * that is not a number (including an empty value) becomes `null`.
258
+ * @param {any} attr - The attribute object to normalize in place.
259
+ */
260
+ _normalizeAttrValue(attr) {
261
+ if ((attr.type === 'image' || attr.type === 'file') &&
262
+ Array.isArray(attr.value) &&
263
+ attr.value.length === 1) {
264
+ attr.value = attr.value[0];
265
+ }
266
+ // An attribute with no value arrives as an empty localization map.
267
+ if (attr.value &&
268
+ typeof attr.value === 'object' &&
269
+ !Array.isArray(attr.value) &&
270
+ Object.keys(attr.value).length === 0) {
271
+ attr.value = null;
272
+ }
273
+ if (attr.type === 'integer' ||
274
+ attr.type === 'float' ||
275
+ attr.type === 'real') {
276
+ // Number(null) is 0 and Number('') is 0 — an empty value must stay empty.
277
+ const isEmpty = attr.value === null || attr.value === undefined || attr.value === '';
278
+ const numValue = isEmpty ? NaN : Number(attr.value);
279
+ attr.value = isNaN(numValue) ? null : numValue;
280
+ }
281
+ }
282
+ /**
283
+ * Transforms additionalFields from array to object keyed by marker.
284
+ *
285
+ * The API returns `additionalFields` as an array: `[{ marker, ... }, ...]`.
286
+ * For convenient key-based access (`attr.additionalFields['fieldName']`)
287
+ * the method converts it to an object `{ marker: { marker, ... } }`.
288
+ * Transformation is skipped when `rawData` mode is enabled in config
289
+ * (the consumer wants the data as-is, without transformations).
290
+ * @param {any} attr - The attribute object that may contain additionalFields.
291
+ */
292
+ _normalizeAdditionalFields(attr) {
293
+ if (!this.state.rawData && Array.isArray(attr.additionalFields)) {
294
+ attr.additionalFields = Object.fromEntries(attr.additionalFields.map((field) => [field.marker, field]));
295
+ }
296
+ }
297
+ /**
298
+ * Normalizes attributes within the data.
299
+ *
300
+ * Handles three different attribute formats returned by the API:
301
+ *
302
+ * All three go through the same steps, so an attribute of a given type looks
303
+ * the same whichever collection it arrived in:
304
+ * - `_normalizeAdditionalFields` turns nested fields into a marker map;
305
+ * - `_normalizeAttrValue` unwraps single-file values, empties to `null` and
306
+ * casts numbers;
307
+ * - the collection is re-sorted by `position`.
308
+ *
309
+ * **1. `attributeValues`** — attributes of pages, products and other entities,
310
+ * an object `{ marker: AttrObject }`.
311
+ *
312
+ * **2. `attributes`** — form attributes, an array (or a marker map on some
313
+ * endpoints); form-only boolean flags (`isLogin`, `isSignUp`, notifications)
314
+ * are additionally coerced from `null` to `false`. A form with no attributes
315
+ * arrives as an empty object `{}` — it is normalized to an empty array.
316
+ *
317
+ * **3. `type`** — a standalone attribute: an attribute-set entry, a form-data
318
+ * field or a nested `additionalFields` entry. Same transformations, but there
319
+ * is no collection to sort.
320
+ *
321
+ * `timeInterval` attributes are left exactly as the API returned them — a
322
+ * compact recurrence rule. Resolving one into concrete slots is the caller's
323
+ * job, via `expandTimeIntervals`: the rule is open-ended, so only the caller
324
+ * knows how wide a window it needs.
325
+ *
326
+ * If none of the keys are found — data is returned unchanged.
327
+ * @param {any} data - The data to normalize.
328
+ * @returns {any} Normalized attributes.
329
+ */
330
+ _normalizeAttr(data) {
331
+ // For regular attributes collections - pages, products, etc.
332
+ if ('attributeValues' in data) {
333
+ Object.keys(data.attributeValues).forEach((attr) => {
334
+ const d = data.attributeValues[attr];
335
+ this._normalizeAdditionalFields(d);
336
+ this._normalizeAttrValue(d);
337
+ });
338
+ return {
339
+ ...data,
340
+ attributeValues: this._sortAttributes(data.attributeValues),
341
+ };
342
+ }
343
+ const booleanFields = [
344
+ 'isLogin',
345
+ 'isSignUp',
346
+ 'isNotificationEmail',
347
+ 'isNotificationPhonePush',
348
+ 'isNotificationPhoneSMS',
349
+ ];
350
+ // for forms attributes - forms attributes collections
351
+ if ('attributes' in data) {
352
+ const d = data.attributes;
353
+ // A form with no attributes arrives as an empty object `{}` — normalize
354
+ // it to an empty array, the container every non-empty form uses.
355
+ if (d &&
356
+ typeof d === 'object' &&
357
+ !Array.isArray(d) &&
358
+ Object.keys(d).length === 0) {
359
+ return { ...data, attributes: [] };
360
+ }
361
+ Object.keys(d).forEach((attr) => {
362
+ this._normalizeAdditionalFields(d[attr]);
363
+ this._normalizeAttrValue(d[attr]);
364
+ for (const field of booleanFields) {
365
+ if (field in d[attr] && d[attr][field] === null) {
366
+ d[attr][field] = false;
367
+ }
368
+ }
369
+ });
370
+ return { ...data, attributes: this._sortAttributes(d) };
371
+ }
372
+ // For single attribute - for attribute sets
373
+ if ('type' in data) {
374
+ this._normalizeAdditionalFields(data);
375
+ this._normalizeAttrValue(data);
376
+ for (const field of booleanFields) {
377
+ if (field in data && data[field] === null) {
378
+ data[field] = false;
379
+ }
380
+ }
381
+ }
382
+ return data;
383
+ }
384
+ /**
385
+ * Sets the access token in the state.
386
+ * @param {string} accessToken - The access token to set.
387
+ * @returns {any} The instance of SyncModules for chaining.
388
+ */
389
+ setAccessToken(accessToken) {
390
+ this.state.accessToken = accessToken;
391
+ return this;
392
+ }
393
+ /**
394
+ * Sets the refresh token in the state.
395
+ * @param {string} refreshToken - The refresh token to set.
396
+ * @returns {any} The instance of SyncModules for chaining.
397
+ */
398
+ setRefreshToken(refreshToken) {
399
+ this.state.refreshToken = refreshToken;
400
+ return this;
401
+ }
402
+ /**
403
+ * Sets the guest identifier in the state.
404
+ *
405
+ * Once set, it is sent as the `x-guest-id` header on unauthenticated requests,
406
+ * enabling guest cart/wishlist/activity flows. Pass an empty string to clear it
407
+ * (the SDK then falls back to the localStorage-backed id in the browser, or to
408
+ * no guest id at all on the server).
409
+ * @param {string} guestId - The guest identifier to set (empty string clears it).
410
+ * @returns {any} The instance of SyncModules for chaining.
411
+ */
412
+ setGuestId(guestId) {
413
+ this.state.guestId = guestId || undefined;
414
+ return this;
415
+ }
416
+ /**
417
+ * Sets the device-metadata override in the state.
418
+ *
419
+ * Once set, the string is sent as the `x-device-metadata` header on POST
420
+ * requests and token refresh instead of the environment-derived fingerprint.
421
+ * The API binds refresh tokens to this header, so server-side flows that issue
422
+ * tokens on behalf of a browser (e.g. an OAuth code exchange) must set the
423
+ * browser's string (obtained there via `getDeviceMetadata`). Pass an empty
424
+ * string to clear the override and fall back to the computed fingerprint.
425
+ * @param {string} deviceMetadata - The metadata string to send (empty string clears the override).
426
+ * @returns {any} The instance of SyncModules for chaining.
427
+ */
428
+ setDeviceMetadata(deviceMetadata) {
429
+ this.state.deviceMetadata = deviceMetadata || undefined;
430
+ return this;
431
+ }
432
+ /**
433
+ * Returns the device-metadata string the SDK sends as the `x-device-metadata` header.
434
+ *
435
+ * Public counterpart of `_getDeviceMetadata`: an explicit override (config or
436
+ * `setDeviceMetadata`) wins, otherwise the environment-derived fingerprint is
437
+ * computed. Use it in the browser to obtain the string that a server-side
438
+ * token-issuing flow (OAuth code exchange) must forward, so the issued refresh
439
+ * token stays refreshable from this browser.
440
+ * @returns {string} The metadata string sent with requests from this instance.
441
+ */
442
+ getDeviceMetadata() {
443
+ return this._getDeviceMetadata();
444
+ }
445
+ /**
446
+ * Get deviceMetadata
447
+ *
448
+ * Builds a device metadata object to be sent in request headers.
449
+ * Used for analytics and anti-fraud on the API side.
450
+ *
451
+ * Returned JSON structure:
452
+ * ```json
453
+ * {
454
+ * "fingerprint": "UQ_<hash>_<instanceId>",
455
+ * "deviceInfo": { "os": "...", "browser": "...", "location": "en-US" }
456
+ * }
457
+ * ```
458
+ *
459
+ * **Fingerprint algorithm:**
460
+ * 1. Builds a string from stable characteristics: platform, userAgent, language,
461
+ * screen resolution, colorDepth, timezone, private-browsing mode, instanceId.
462
+ * 2. Runs it through a simple 32-bit hash (djb2-like).
463
+ * 3. Concatenates the hash and the first 12 characters of instanceId.
464
+ *
465
+ * **instanceId strategy:**
466
+ * - Browser: `_getPersistentBrowserId('oneentry_device_id')` — read from localStorage, stable across sessions.
467
+ * - Node.js / no localStorage: `_nodeDeviceId` — generated at instance creation,
468
+ * lives until the process restarts.
469
+ *
470
+ * In a Node.js environment (no `window`) returns a simplified object without screen/navigator.
471
+ *
472
+ * An explicitly provided string (`deviceMetadata` in config or `setDeviceMetadata`)
473
+ * takes precedence over the environment-derived fingerprint — this lets a server
474
+ * issue tokens bound to the browser's fingerprint (see `IConfig.deviceMetadata`).
475
+ * @returns {string} - Returns an object containing device metadata.
476
+ */
477
+ _getDeviceMetadata() {
478
+ var _a;
479
+ // Explicit override wins: the API binds refresh tokens to this header, so
480
+ // server-side token issuance must be able to stamp the browser's string.
481
+ if (this.state.deviceMetadata)
482
+ return this.state.deviceMetadata;
483
+ // Check if we're in a browser environment
484
+ if (typeof globalThis === 'undefined') {
485
+ return '';
486
+ }
487
+ // Access navigator through globalThis.window object to avoid direct reference
488
+ const win = globalThis.window;
489
+ const instanceId = (_a = _getPersistentBrowserId(_DEVICE_ID_STORAGE_KEY)) !== null && _a !== void 0 ? _a : this._nodeDeviceId;
490
+ // Node.js environment
491
+ if (!win) {
492
+ return JSON.stringify({
493
+ fingerprint: `UQ_${instanceId}`,
494
+ deviceInfo: {
495
+ os: 'Node.js',
496
+ browser: `Node.js/${instanceId.substring(0, 10)}`,
497
+ location: 'en-US',
498
+ },
499
+ });
500
+ }
501
+ const nav = win.navigator || {};
502
+ const platform = nav.platform || 'Win32';
503
+ const userAgent = nav.userAgent || 'Node.js/22';
504
+ const language = nav.language || 'en-US';
505
+ // Get screen information if available
506
+ const screen = win.screen || {};
507
+ const screenWidth = screen.width || 0;
508
+ const screenHeight = screen.height || 0;
509
+ const colorDepth = screen.colorDepth || 0;
510
+ // Get timezone
511
+ let timezone = 'UTC';
512
+ try {
513
+ timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
514
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
515
+ }
516
+ catch (e) {
517
+ // Ignore error, fallback to UTC
518
+ }
519
+ // Detect private browsing mode
520
+ let isPrivateBrowsing = false;
521
+ try {
522
+ // Simple localStorage test to detect private browsing
523
+ if (win.localStorage) {
524
+ const testKey = 'test_private_browsing';
525
+ win.localStorage.setItem(testKey, '1');
526
+ win.localStorage.removeItem(testKey);
527
+ }
528
+ else {
529
+ isPrivateBrowsing = true; // If localStorage is not available, likely private browsing
530
+ }
531
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
532
+ }
533
+ catch (e) {
534
+ isPrivateBrowsing = true; // Error during localStorage access indicates private browsing
535
+ }
536
+ // Create a stable fingerprint string using stable device/browser characteristics
537
+ const fingerprintString = `${platform}|${userAgent}|${language}|${screenWidth}|${screenHeight}|${colorDepth}|${timezone}|${isPrivateBrowsing ? 'private' : 'normal'}|${instanceId}`;
538
+ // Simple but stable hash function
539
+ let hash = 0;
540
+ for (let i = 0; i < fingerprintString.length; i++) {
541
+ const char = fingerprintString.charCodeAt(i);
542
+ hash = (hash << 5) - hash + char;
543
+ hash = hash & hash; // Convert to 32-bit integer
544
+ }
545
+ const deviceMetadata = {
546
+ fingerprint: `UQ_${Math.abs(hash).toString(36)}_${instanceId.substring(0, 12)}`,
547
+ deviceInfo: {
548
+ os: platform.replace(/ /g, '_'),
549
+ browser: userAgent.replace(/ /g, '_'),
550
+ location: language,
551
+ },
552
+ };
553
+ return JSON.stringify(deviceMetadata);
554
+ }
555
+ }
@@ -0,0 +1,95 @@
1
+ import type { IAttributeValue, ITimeIntervalAttributeValue, ITimeIntervalEntitySchedule, ITimeIntervalSchedule, ITimeIntervalWindow, TimeIntervalPair } from './utils.js';
2
+ /**
3
+ * Expands a `timeInterval` schedule into concrete UTC slots for a given window.
4
+ *
5
+ * A schedule as returned by the API is a compact **recurrence rule** — an
6
+ * anchor date plus daily time ranges plus repeat flags — not a list of slots.
7
+ * Materializing it wholesale is what makes `timeInterval` attributes expensive
8
+ * (a year of half-hour slots runs to megabytes), so expansion is on demand and
9
+ * the window is required: only the caller knows how far it needs to resolve.
10
+ *
11
+ * Both schedule shapes the API returns are accepted:
12
+ * - **entity** — `attributeValues[marker].value[].values[]` on pages, products,
13
+ * blocks and attribute sets: a `dates` range with `times` pairs;
14
+ * - **form** — `attributes[marker].localizeInfos.intervals[]`: a `range` with
15
+ * `intervals` that carry a slot `period` in minutes.
16
+ *
17
+ * Semantics:
18
+ * - `dates[0]` / `range[0]` is both the recurrence phase and the first valid
19
+ * day — nothing earlier is emitted, however wide the window;
20
+ * - `dates[1]` / `range[1]` ends validity; when it does not extend past the
21
+ * start, the schedule is anchored to that day — with a recurrence flag set,
22
+ * recurrence is then open-ended and the window alone bounds the result;
23
+ * - `inEveryWeek` repeats every 7 days from the anchor; `inEveryMonth` repeats
24
+ * on the same day-of-month, skipping months that are too short; with both set
25
+ * the weekly rule applies, which is what it has always meant in practice;
26
+ * - with neither flag the schedule is a plain date range — every day of it;
27
+ * - the result is deduplicated and sorted by start, then end.
28
+ * @param {ITimeIntervalEntitySchedule | ITimeIntervalSchedule} schedule - A single schedule entry from a `timeInterval` attribute.
29
+ * @param {ITimeIntervalWindow} window - Inclusive `{ from, to }` range to resolve, compared at UTC day granularity.
30
+ * @returns {TimeIntervalPair[]} Sorted, deduplicated `[start, end]` ISO pairs; empty when the schedule is malformed or does not overlap the window.
31
+ * To expand a whole attribute at once, prefer {@link expandAttributeTimeIntervals}
32
+ * — it walks the groups and merges the results for you. Reach for this function
33
+ * directly when you already hold a single schedule, e.g. a form's
34
+ * `localizeInfos.intervals[]`.
35
+ * @example
36
+ * ```ts
37
+ * import { expandTimeIntervals } from 'oneentry';
38
+ *
39
+ * // Form attributes are an array keyed by `marker`, and carry their schedules
40
+ * // already typed on `localizeInfos.intervals`.
41
+ * const field = form.attributes.find((a) => a.marker === 'booking');
42
+ *
43
+ * const slots = (field?.localizeInfos.intervals ?? []).flatMap((schedule) =>
44
+ * expandTimeIntervals(schedule, { from: '2025-05-01', to: '2025-05-31' }),
45
+ * );
46
+ * // [['2025-05-07T09:00:00.000Z', '2025-05-07T10:00:00.000Z'], …]
47
+ * ```
48
+ */
49
+ export declare function expandTimeIntervals(schedule: ITimeIntervalEntitySchedule | ITimeIntervalSchedule, window: ITimeIntervalWindow): TimeIntervalPair[];
50
+ /**
51
+ * Narrows an attribute value to a `timeInterval` attribute.
52
+ *
53
+ * `IAttributeValue.value` is `unknown` — its shape depends on `type` — so this
54
+ * guard is what lets you reach the schedules without a cast.
55
+ * @param {IAttributeValue | undefined} attr - The attribute value to test.
56
+ * @returns {boolean} True when the attribute is a `timeInterval` carrying an array of groups.
57
+ * @example
58
+ * ```ts
59
+ * const attr = page.attributeValues.interval;
60
+ * if (isTimeIntervalAttribute(attr)) {
61
+ * attr.value[0].values[0].dates; // fully typed, no cast
62
+ * }
63
+ * ```
64
+ */
65
+ export declare function isTimeIntervalAttribute(attr: IAttributeValue | undefined): attr is ITimeIntervalAttributeValue;
66
+ /**
67
+ * Expands a whole `timeInterval` attribute into concrete UTC slots for a window.
68
+ *
69
+ * The one-call path for the common case: it walks the attribute's groups and
70
+ * their schedules, expands each with {@link expandTimeIntervals}, and merges the
71
+ * results. Merging matters — deduplication and ordering only hold within a
72
+ * single schedule, so combining groups by hand can yield duplicate or unsorted
73
+ * slots.
74
+ *
75
+ * Anything that is not a `timeInterval` attribute yields an empty array, so this
76
+ * is safe to call on an arbitrary attribute without checking `type` first.
77
+ *
78
+ * For **form** attributes the schedules are already typed at
79
+ * `localizeInfos.intervals`, so no equivalent helper is needed — map over them
80
+ * and call {@link expandTimeIntervals} directly.
81
+ * @param {IAttributeValue | undefined} attr - A `timeInterval` attribute value, e.g. `page.attributeValues.interval`.
82
+ * @param {ITimeIntervalWindow} window - Inclusive `{ from, to }` range to resolve, compared at UTC day granularity.
83
+ * @returns {TimeIntervalPair[]} Sorted, deduplicated `[start, end]` ISO pairs across every group; empty when the attribute is not a `timeInterval`.
84
+ * @example
85
+ * ```ts
86
+ * import { expandAttributeTimeIntervals } from 'oneentry';
87
+ *
88
+ * const slots = expandAttributeTimeIntervals(page.attributeValues.interval, {
89
+ * from: '2025-04-01',
90
+ * to: '2025-04-30',
91
+ * });
92
+ * // [['2025-04-14T09:00:00.000Z', '2025-04-14T10:00:00.000Z'], …]
93
+ * ```
94
+ */
95
+ export declare function expandAttributeTimeIntervals(attr: IAttributeValue | undefined, window: ITimeIntervalWindow): TimeIntervalPair[];