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.
- package/README.md +2 -0
- package/changelog.md +56 -1
- package/dist/admins/adminsApi.d.ts +15 -1
- package/dist/admins/adminsApi.js +29 -0
- package/dist/admins/adminsInterfaces.d.ts +37 -1
- package/dist/base/lazySchema.d.ts +15 -0
- package/dist/base/lazySchema.js +20 -0
- package/dist/discounts/discountsApi.d.ts +14 -1
- package/dist/discounts/discountsApi.js +21 -0
- package/dist/discounts/discountsInterfaces.d.ts +27 -1
- package/dist/filters/filtersInterfaces.d.ts +2 -2
- package/dist/filters/filtersSchemas.d.ts +1 -1
- package/dist/filters/filtersSchemas.js +4 -1
- package/dist/forms-data/formsDataApi.d.ts +14 -1
- package/dist/forms-data/formsDataApi.js +21 -0
- package/dist/forms-data/formsDataInterfaces.d.ts +74 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/orders/ordersApi.d.ts +15 -1
- package/dist/orders/ordersApi.js +22 -0
- package/dist/orders/ordersInterfaces.d.ts +27 -1
- package/dist/pages/pagesApi.d.ts +14 -1
- package/dist/pages/pagesApi.js +21 -0
- package/dist/pages/pagesInterfaces.d.ts +38 -1
- package/dist/payments/paymentsInterfaces.d.ts +2 -2
- package/dist/products/productsInterfaces.d.ts +1 -1
- package/dist/search/searchApi.d.ts +35 -0
- package/dist/search/searchApi.js +86 -0
- package/dist/search/searchInterfaces.d.ts +94 -0
- package/dist/search/searchInterfaces.js +2 -0
- package/dist/search/searchSchemas.d.ts +73 -0
- package/dist/search/searchSchemas.js +44 -0
- package/dist/templates-preview/templatesPreviewInterfaces.d.ts +0 -2
- package/dist/templates-preview/templatesPreviewSchemas.d.ts +0 -2
- package/dist/templates-preview/templatesPreviewSchemas.js +0 -1
- package/dist/types.d.ts +35 -0
- package/dist/types.js +2 -0
- package/dist/users/usersApi.d.ts +15 -1
- package/dist/users/usersApi.js +22 -0
- package/dist/users/usersInterfaces.d.ts +39 -1
- package/dist/web-socket/lazySocket.d.ts +18 -0
- package/dist/web-socket/lazySocket.js +70 -0
- package/esm/admins/adminsApi.d.ts +66 -0
- package/esm/admins/adminsApi.js +90 -0
- package/esm/admins/adminsInterfaces.d.ts +137 -0
- package/esm/admins/adminsInterfaces.js +1 -0
- package/esm/admins/adminsSchemas.d.ts +48 -0
- package/esm/admins/adminsSchemas.js +31 -0
- package/esm/attribute-sets/attributeSetsApi.d.ts +67 -0
- package/esm/attribute-sets/attributeSetsApi.js +97 -0
- package/esm/attribute-sets/attributeSetsInterfaces.d.ts +285 -0
- package/esm/attribute-sets/attributeSetsInterfaces.js +1 -0
- package/esm/attribute-sets/attributeSetsSchemas.d.ts +84 -0
- package/esm/attribute-sets/attributeSetsSchemas.js +66 -0
- package/esm/auth-provider/authProviderApi.d.ts +262 -0
- package/esm/auth-provider/authProviderApi.js +362 -0
- package/esm/auth-provider/authProviderSchemas.d.ts +124 -0
- package/esm/auth-provider/authProviderSchemas.js +92 -0
- package/esm/auth-provider/authProvidersInterfaces.d.ts +430 -0
- package/esm/auth-provider/authProvidersInterfaces.js +1 -0
- package/esm/base/asyncModules.d.ts +123 -0
- package/esm/base/asyncModules.js +382 -0
- package/esm/base/lazySchema.d.ts +15 -0
- package/esm/base/lazySchema.js +17 -0
- package/esm/base/stateModule.d.ts +35 -0
- package/esm/base/stateModule.js +51 -0
- package/esm/base/syncModules.d.ts +245 -0
- package/esm/base/syncModules.js +555 -0
- package/esm/base/timeIntervals.d.ts +95 -0
- package/esm/base/timeIntervals.js +317 -0
- package/esm/base/utils.d.ts +354 -0
- package/esm/base/utils.js +1 -0
- package/esm/base/validation.d.ts +98 -0
- package/esm/base/validation.js +118 -0
- package/esm/blocks/blocksApi.d.ts +252 -0
- package/esm/blocks/blocksApi.js +394 -0
- package/esm/blocks/blocksInterfaces.d.ts +361 -0
- package/esm/blocks/blocksInterfaces.js +1 -0
- package/esm/blocks/blocksSchemas.d.ts +213 -0
- package/esm/blocks/blocksSchemas.js +40 -0
- package/esm/discounts/discountsApi.d.ts +94 -0
- package/esm/discounts/discountsApi.js +136 -0
- package/esm/discounts/discountsInterfaces.d.ts +293 -0
- package/esm/discounts/discountsInterfaces.js +1 -0
- package/esm/events/eventsApi.d.ts +108 -0
- package/esm/events/eventsApi.js +141 -0
- package/esm/events/eventsInterfaces.d.ts +176 -0
- package/esm/events/eventsInterfaces.js +1 -0
- package/esm/file-uploading/fileUploadingApi.d.ts +91 -0
- package/esm/file-uploading/fileUploadingApi.js +127 -0
- package/esm/file-uploading/fileUploadingInterfaces.d.ts +116 -0
- package/esm/file-uploading/fileUploadingInterfaces.js +1 -0
- package/esm/file-uploading/fileUploadingSchemas.d.ts +24 -0
- package/esm/file-uploading/fileUploadingSchemas.js +19 -0
- package/esm/filters/filtersApi.d.ts +32 -0
- package/esm/filters/filtersApi.js +36 -0
- package/esm/filters/filtersInterfaces.d.ts +56 -0
- package/esm/filters/filtersInterfaces.js +1 -0
- package/esm/filters/filtersSchemas.d.ts +32 -0
- package/esm/filters/filtersSchemas.js +29 -0
- package/esm/forms/formsApi.d.ts +44 -0
- package/esm/forms/formsApi.js +54 -0
- package/esm/forms/formsInterfaces.d.ts +237 -0
- package/esm/forms/formsInterfaces.js +1 -0
- package/esm/forms/formsSchemas.d.ts +56 -0
- package/esm/forms/formsSchemas.js +32 -0
- package/esm/forms-data/formsDataApi.d.ts +124 -0
- package/esm/forms-data/formsDataApi.js +210 -0
- package/esm/forms-data/formsDataInterfaces.d.ts +670 -0
- package/esm/forms-data/formsDataInterfaces.js +1 -0
- package/esm/forms-data/formsDataSchemas.d.ts +107 -0
- package/esm/forms-data/formsDataSchemas.js +83 -0
- package/esm/general-types/generalTypesApi.d.ts +29 -0
- package/esm/general-types/generalTypesApi.js +34 -0
- package/esm/general-types/generalTypesInterfaces.d.ts +29 -0
- package/esm/general-types/generalTypesInterfaces.js +1 -0
- package/esm/general-types/generalTypesSchemas.d.ts +68 -0
- package/esm/general-types/generalTypesSchemas.js +42 -0
- package/esm/index.d.ts +143 -0
- package/esm/index.js +124 -0
- package/esm/integration-collections/integrationCollectionsApi.d.ts +172 -0
- package/esm/integration-collections/integrationCollectionsApi.js +224 -0
- package/esm/integration-collections/integrationCollectionsInterfaces.d.ts +317 -0
- package/esm/integration-collections/integrationCollectionsInterfaces.js +1 -0
- package/esm/integration-collections/integrationCollectionsSchemas.d.ts +84 -0
- package/esm/integration-collections/integrationCollectionsSchemas.js +60 -0
- package/esm/locales/localesApi.d.ts +28 -0
- package/esm/locales/localesApi.js +33 -0
- package/esm/locales/localesInterfaces.d.ts +40 -0
- package/esm/locales/localesInterfaces.js +1 -0
- package/esm/locales/localesSchemas.d.ts +32 -0
- package/esm/locales/localesSchemas.js +23 -0
- package/esm/menus/menusApi.d.ts +30 -0
- package/esm/menus/menusApi.js +35 -0
- package/esm/menus/menusInterfaces.d.ts +87 -0
- package/esm/menus/menusInterfaces.js +2 -0
- package/esm/menus/menusSchemas.d.ts +16 -0
- package/esm/menus/menusSchemas.js +25 -0
- package/esm/orders/ordersApi.d.ts +213 -0
- package/esm/orders/ordersApi.js +288 -0
- package/esm/orders/ordersInterfaces.d.ts +763 -0
- package/esm/orders/ordersInterfaces.js +1 -0
- package/esm/orders/ordersSchemas.d.ts +312 -0
- package/esm/orders/ordersSchemas.js +171 -0
- package/esm/package.json +4 -0
- package/esm/pages/pagesApi.d.ts +173 -0
- package/esm/pages/pagesApi.js +414 -0
- package/esm/pages/pagesInterfaces.d.ts +344 -0
- package/esm/pages/pagesInterfaces.js +1 -0
- package/esm/pages/pagesSchemas.d.ts +121 -0
- package/esm/pages/pagesSchemas.js +60 -0
- package/esm/payments/paymentsApi.d.ts +88 -0
- package/esm/payments/paymentsApi.js +122 -0
- package/esm/payments/paymentsInterfaces.d.ts +189 -0
- package/esm/payments/paymentsInterfaces.js +1 -0
- package/esm/payments/paymentsSchemas.d.ts +94 -0
- package/esm/payments/paymentsSchemas.js +53 -0
- package/esm/product-statuses/productStatusesApi.d.ts +50 -0
- package/esm/product-statuses/productStatusesApi.js +68 -0
- package/esm/product-statuses/productStatusesInterfaces.d.ts +62 -0
- package/esm/product-statuses/productStatusesInterfaces.js +1 -0
- package/esm/product-statuses/productStatusesSchemas.d.ts +34 -0
- package/esm/product-statuses/productStatusesSchemas.js +27 -0
- package/esm/products/productsApi.d.ts +366 -0
- package/esm/products/productsApi.js +468 -0
- package/esm/products/productsInterfaces.d.ts +723 -0
- package/esm/products/productsInterfaces.js +1 -0
- package/esm/products/productsSchemas.d.ts +234 -0
- package/esm/products/productsSchemas.js +121 -0
- package/esm/search/searchApi.d.ts +35 -0
- package/esm/search/searchApi.js +47 -0
- package/esm/search/searchInterfaces.d.ts +94 -0
- package/esm/search/searchInterfaces.js +1 -0
- package/esm/search/searchSchemas.d.ts +73 -0
- package/esm/search/searchSchemas.js +41 -0
- package/esm/sitemap/sitemapApi.d.ts +37 -0
- package/esm/sitemap/sitemapApi.js +41 -0
- package/esm/sitemap/sitemapInterfaces.d.ts +51 -0
- package/esm/sitemap/sitemapInterfaces.js +1 -0
- package/esm/subscriptions/subscriptionsApi.d.ts +74 -0
- package/esm/subscriptions/subscriptionsApi.js +90 -0
- package/esm/subscriptions/subscriptionsInterfaces.d.ts +111 -0
- package/esm/subscriptions/subscriptionsInterfaces.js +1 -0
- package/esm/subscriptions/subscriptionsSchemas.d.ts +46 -0
- package/esm/subscriptions/subscriptionsSchemas.js +38 -0
- package/esm/system/systemApi.d.ts +46 -0
- package/esm/system/systemApi.js +53 -0
- package/esm/system/systemInterfaces.d.ts +29 -0
- package/esm/system/systemInterfaces.js +1 -0
- package/esm/templates/templatesApi.d.ts +53 -0
- package/esm/templates/templatesApi.js +78 -0
- package/esm/templates/templatesInterfaces.d.ts +68 -0
- package/esm/templates/templatesInterfaces.js +1 -0
- package/esm/templates/templatesSchemas.d.ts +48 -0
- package/esm/templates/templatesSchemas.js +28 -0
- package/esm/templates-preview/templatesPreviewApi.d.ts +40 -0
- package/esm/templates-preview/templatesPreviewApi.js +50 -0
- package/esm/templates-preview/templatesPreviewInterfaces.d.ts +129 -0
- package/esm/templates-preview/templatesPreviewInterfaces.js +1 -0
- package/esm/templates-preview/templatesPreviewSchemas.d.ts +79 -0
- package/esm/templates-preview/templatesPreviewSchemas.js +43 -0
- package/esm/types.d.ts +35 -0
- package/esm/types.js +1 -0
- package/esm/user-activity/userActivityApi.d.ts +31 -0
- package/esm/user-activity/userActivityApi.js +37 -0
- package/esm/user-activity/userActivityInterfaces.d.ts +42 -0
- package/esm/user-activity/userActivityInterfaces.js +1 -0
- package/esm/users/usersApi.d.ts +249 -0
- package/esm/users/usersApi.js +312 -0
- package/esm/users/usersInterfaces.d.ts +419 -0
- package/esm/users/usersInterfaces.js +1 -0
- package/esm/users/usersSchemas.d.ts +72 -0
- package/esm/users/usersSchemas.js +55 -0
- package/esm/web-socket/lazySocket.d.ts +18 -0
- package/esm/web-socket/lazySocket.js +67 -0
- package/esm/web-socket/wsApi.d.ts +27 -0
- package/esm/web-socket/wsApi.js +43 -0
- package/esm/web-socket/wsInterfaces.d.ts +16 -0
- package/esm/web-socket/wsInterfaces.js +1 -0
- package/package.json +47 -2
- package/types.d.ts +1 -0
- 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[];
|