oneentry 1.0.154 → 1.0.156

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.
@@ -286,253 +286,6 @@ class SyncModules {
286
286
  return data;
287
287
  }
288
288
  }
289
- /**
290
- * Adds a specified number of days to a date.
291
- * @param {Date} date - The initial date.
292
- * @param {number} days - The number of days to add.
293
- * @returns {any} The new date with added days.
294
- */
295
- _addDays(date, days) {
296
- const result = new Date(date);
297
- result.setUTCDate(result.getUTCDate() + days);
298
- return result;
299
- }
300
- /**
301
- * Common logic for processing schedule dates (weekly, monthly, or both).
302
- *
303
- * Abstracts date iteration for three scheduling modes:
304
- *
305
- * - **`inEveryWeek` only**: starting from the start date, generates dates
306
- * with a 7-day step until the end of the current month.
307
- *
308
- * - **`inEveryMonth` only**: pins the day-of-month from the start date
309
- * and repeats it for each of the next 12 months. If the month does not
310
- * have that day (e.g. Feb 31), the iteration is skipped.
311
- *
312
- * - **`inEveryWeek` + `inEveryMonth`**: for each of the next 12 months finds
313
- * the first occurrence of the target weekday (from the start date), then
314
- * iterates all occurrences of that weekday in the month with a 7-day step.
315
- *
316
- * `processDate(currentDate)` is called for every resolved date.
317
- * @param {Date} date - The date for which to process intervals.
318
- * @param {object} config - Configuration for schedule repetition.
319
- * @param {boolean} config.inEveryWeek - Whether to repeat weekly.
320
- * @param {boolean} config.inEveryMonth - Whether to repeat monthly.
321
- * @param {(currentDate: Date) => void} processDate - Callback function to process each date.
322
- */
323
- _processScheduleDates(date, config, processDate) {
324
- // Handle weekly schedules
325
- if (config.inEveryWeek && !config.inEveryMonth) {
326
- let currentDate = new Date(date);
327
- // Calculate the last day of the current month
328
- const endOfMonth = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0);
329
- while (currentDate <= endOfMonth) {
330
- processDate(currentDate);
331
- // Move to the next week
332
- currentDate = this._addDays(currentDate, 7);
333
- }
334
- }
335
- // Handle monthly schedules
336
- if (config.inEveryMonth && !config.inEveryWeek) {
337
- const startDate = new Date(date);
338
- const targetDayOfMonth = startDate.getUTCDate();
339
- const numberOfMonths = 12;
340
- for (let i = 0; i < numberOfMonths; i++) {
341
- const currentDate = new Date(startDate);
342
- currentDate.setUTCMonth(currentDate.getUTCMonth() + i);
343
- // Try setting the current date to the target day of the month
344
- currentDate.setUTCDate(targetDayOfMonth);
345
- // Check if we have exceeded the month
346
- if (currentDate.getUTCMonth() !== (startDate.getUTCMonth() + i) % 12) {
347
- continue; // Skip this month if exceeded
348
- }
349
- processDate(currentDate);
350
- }
351
- }
352
- // Handle both weekly and monthly schedules
353
- if (config.inEveryMonth && config.inEveryWeek) {
354
- const startDate = new Date(date);
355
- const targetDayOfWeek = startDate.getUTCDay();
356
- const numberOfMonths = 12;
357
- for (let i = 0; i < numberOfMonths; i++) {
358
- const currentDate = new Date(startDate);
359
- currentDate.setUTCMonth(currentDate.getUTCMonth() + i);
360
- // Set to the first day of the month
361
- currentDate.setUTCDate(1);
362
- // Find the first target day of the week in the current month
363
- const daysUntilTargetDay = (targetDayOfWeek - currentDate.getUTCDay() + 7) % 7;
364
- currentDate.setUTCDate(currentDate.getUTCDate() + daysUntilTargetDay);
365
- // Iterate over all target days of the week in the current month
366
- while (currentDate.getUTCMonth() ===
367
- (startDate.getUTCMonth() + i) % 12) {
368
- processDate(currentDate);
369
- // Move to the next week (same day of the week)
370
- currentDate.setUTCDate(currentDate.getUTCDate() + 7);
371
- }
372
- }
373
- }
374
- }
375
- /**
376
- * Generates intervals for a specific date based on a schedule.
377
- *
378
- * For each date resolved by `_processScheduleDates`, iterates over
379
- * the `schedule.times` array of time ranges. Each range is a pair
380
- * `[startTime, endTime]` with `{ hours, minutes }` fields.
381
- * Creates an ISO interval `[start.toISOString(), end.toISOString()]`
382
- * and adds it to `utcIntervals` (Set deduplicates automatically).
383
- * @param {Date} date - The date for which to generate intervals.
384
- * @param {object} schedule - The schedule defining the intervals.
385
- * @param {boolean} schedule.inEveryWeek - The number of weeks between intervals.
386
- * @param {any[]} schedule.times - The times for each interval.
387
- * @param {boolean} schedule.inEveryMonth - The month intervals for each interval.
388
- * @param {Set<Array<string>>} utcIntervals - A set to store unique intervals.
389
- */
390
- _generateIntervalsForDate(date, schedule, utcIntervals) {
391
- this._processScheduleDates(date, schedule, (currentDate) => {
392
- schedule.times.forEach((timeRange) => {
393
- const [startTime, endTime] = timeRange;
394
- const intervalStart = new Date(currentDate);
395
- intervalStart.setUTCHours(startTime.hours, startTime.minutes, 0, 0);
396
- const intervalEnd = new Date(currentDate);
397
- intervalEnd.setUTCHours(endTime.hours, endTime.minutes, 0, 0);
398
- utcIntervals.add([
399
- intervalStart.toISOString(),
400
- intervalEnd.toISOString(),
401
- ]);
402
- });
403
- });
404
- }
405
- /**
406
- * Adds time intervals to schedules.
407
- *
408
- * Accepts an array of schedule groups (structure of `timeInterval` attributes
409
- * for pages/products). For each group iterates over `values` — the set of
410
- * concrete schedules. Each schedule contains a date range `dates[0..1]`.
411
- *
412
- * If both boundaries are equal (`isSameDay`), intervals are generated only
413
- * for that single date. Otherwise — for every day in the range inclusive.
414
- *
415
- * The result (`schedule.timeIntervals`) is a sorted array of ISO pairs,
416
- * ready to pass to UI components.
417
- * @param {any[]} schedules - The schedules to process.
418
- * @returns {any} Schedules with added time intervals.
419
- */
420
- _addTimeIntervalsToSchedules(schedules) {
421
- schedules === null || schedules === void 0 ? void 0 : schedules.forEach((scheduleGroup) => {
422
- // Skip if scheduleGroup.values is not an array
423
- if (!scheduleGroup ||
424
- !scheduleGroup.values ||
425
- !Array.isArray(scheduleGroup.values)) {
426
- return;
427
- }
428
- scheduleGroup.values.forEach((schedule) => {
429
- const utcIntervals = new Set();
430
- const startDate = new Date(schedule.dates[0]);
431
- const endDate = new Date(schedule.dates[1]);
432
- const isSameDay = startDate.toISOString() === endDate.toISOString();
433
- if (isSameDay) {
434
- this._generateIntervalsForDate(startDate, schedule, utcIntervals);
435
- }
436
- else {
437
- for (let currentDate = new Date(startDate); currentDate <= endDate; currentDate = this._addDays(currentDate, 1)) {
438
- this._generateIntervalsForDate(currentDate, schedule, utcIntervals);
439
- }
440
- }
441
- schedule.timeIntervals = Array.from(utcIntervals).sort();
442
- });
443
- });
444
- return schedules;
445
- }
446
- /**
447
- * Generates intervals for a specific date for form schedules.
448
- *
449
- * Unlike `_generateIntervalsForDate`, time ranges here have a different shape:
450
- * each `timeInterval` contains `start`, `end` and `period`
451
- * (slot length in minutes). The method slices the [start, end) window into
452
- * fixed-length slots of `period` minutes:
453
- *
454
- * start=09:00, end=12:00, period=30 → [09:00–09:30], [09:30–10:00], …, [11:30–12:00]
455
- *
456
- * Generation stops if the next slot would exceed `end`.
457
- * Each slot is added to `utcIntervals` (Set deduplicates automatically).
458
- * @param {Date} date - The date for which to generate intervals.
459
- * @param {object} interval - The interval configuration.
460
- * @param {boolean} interval.inEveryWeek - Indicates whether the schedule is weekly.
461
- * @param {boolean} interval.inEveryMonth - Indicates whether the schedule is monthly.
462
- * @param {any[]} timeIntervals - The time intervals to process.
463
- * @param {Set<Array<string>>} utcIntervals - A set to store unique intervals.
464
- */
465
- _generateIntervalsForFormDate(date, interval, timeIntervals, utcIntervals) {
466
- const generateTimeSlotsForDate = (currentDate) => {
467
- timeIntervals.forEach((timeInterval) => {
468
- let currentStart = timeInterval.start;
469
- const endTime = timeInterval.end;
470
- // Slice the window into slots of `period` minutes each.
471
- while (currentStart.hours < endTime.hours ||
472
- (currentStart.hours === endTime.hours &&
473
- currentStart.minutes < endTime.minutes)) {
474
- const intervalStart = new Date(currentDate);
475
- intervalStart.setUTCHours(currentStart.hours, currentStart.minutes, 0, 0);
476
- // Compute slot end: add period minutes with hour carry normalization.
477
- const nextMinutes = currentStart.minutes + timeInterval.period;
478
- const nextHours = currentStart.hours + Math.floor(nextMinutes / 60);
479
- const minutes = nextMinutes % 60;
480
- // If the slot end exceeds `end` — stop; partial slots are not emitted.
481
- if (nextHours > endTime.hours ||
482
- (nextHours === endTime.hours && minutes > endTime.minutes)) {
483
- break;
484
- }
485
- const intervalEnd = new Date(currentDate);
486
- intervalEnd.setUTCHours(nextHours, minutes, 0, 0);
487
- utcIntervals.add([
488
- intervalStart.toISOString(),
489
- intervalEnd.toISOString(),
490
- ]);
491
- currentStart = { hours: nextHours, minutes };
492
- }
493
- });
494
- };
495
- this._processScheduleDates(date, interval, generateTimeSlotsForDate);
496
- }
497
- /**
498
- * Adds time intervals to form schedules (different structure).
499
- *
500
- * Same as `_addTimeIntervalsToSchedules` but for `timeInterval` attributes
501
- * in **forms** (different API data structure):
502
- * - `interval.range[0..1]` instead of `schedule.dates[0..1]`
503
- * - `interval.intervals` — array of time ranges with slots (`period`)
504
- * instead of `[startTime, endTime]` pairs
505
- *
506
- * Result is written to `interval.timeIntervals`.
507
- * @param {any[]} intervals - The intervals to process.
508
- * @returns {any} Intervals with added time intervals.
509
- */
510
- _addTimeIntervalsToFormSchedules(intervals) {
511
- intervals.forEach((interval) => {
512
- var _a, _b;
513
- if (!interval.intervals || !Array.isArray(interval.intervals)) {
514
- return;
515
- }
516
- const utcIntervals = new Set();
517
- const startDate = new Date(interval.range[0]);
518
- const endDate = new Date(interval.range[1]);
519
- const isSameDay = startDate.toISOString() === endDate.toISOString();
520
- const intervalConfig = {
521
- inEveryWeek: (_a = interval.inEveryWeek) !== null && _a !== void 0 ? _a : false,
522
- inEveryMonth: (_b = interval.inEveryMonth) !== null && _b !== void 0 ? _b : false,
523
- };
524
- if (isSameDay) {
525
- this._generateIntervalsForFormDate(startDate, intervalConfig, interval.intervals, utcIntervals);
526
- }
527
- else {
528
- for (let currentDate = new Date(startDate); currentDate <= endDate; currentDate = this._addDays(currentDate, 1)) {
529
- this._generateIntervalsForFormDate(currentDate, intervalConfig, interval.intervals, utcIntervals);
530
- }
531
- }
532
- interval.timeIntervals = Array.from(utcIntervals).sort();
533
- });
534
- return intervals;
535
- }
536
289
  /**
537
290
  * Transforms additionalFields from array to object keyed by marker.
538
291
  *
@@ -557,16 +310,20 @@ class SyncModules {
557
310
  * Contains an object `{ marker: AttrObject }`. For each attribute:
558
311
  * - `_normalizeAdditionalFields` is called;
559
312
  * - numeric types (`integer`, `float`) are cast to a JS number (or `null`);
560
- * - `timeInterval` attributes are enriched with computed `timeIntervals`;
561
313
  * - the whole object is re-sorted by `position`.
562
314
  *
563
315
  * **2. `attributes`** — form attributes (different API structure).
564
- * Same `additionalFields` and `timeInterval` processing,
316
+ * Same `additionalFields` processing,
565
317
  * but numbers are not normalized here (commented out — logic differs).
566
318
  *
567
319
  * **3. `type`** — a single attribute from an attribute set.
568
320
  * Same transformations as in case 1, but without sorting.
569
321
  *
322
+ * `timeInterval` attributes are left exactly as the API returned them — a
323
+ * compact recurrence rule. Resolving one into concrete slots is the caller's
324
+ * job, via `expandTimeIntervals`: the rule is open-ended, so only the caller
325
+ * knows how wide a window it needs.
326
+ *
570
327
  * If none of the keys are found — data is returned unchanged.
571
328
  * @param {any} data - The data to normalize.
572
329
  * @returns {any} Normalized attributes.
@@ -582,15 +339,6 @@ class SyncModules {
582
339
  const numValue = Number(d.value);
583
340
  d.value = isNaN(numValue) ? null : numValue;
584
341
  }
585
- // add timeIntervals
586
- if (data.attributeValues[attr].type === 'timeInterval') {
587
- const schedules = data.attributeValues[attr].value;
588
- // console.log('Schedules: ', JSON.stringify(schedules));
589
- if (Array.isArray(schedules) && schedules.length > 0) {
590
- const result = this._addTimeIntervalsToSchedules(schedules);
591
- data.attributeValues[attr].value = result;
592
- }
593
- }
594
342
  });
595
343
  return {
596
344
  ...data,
@@ -608,22 +356,12 @@ class SyncModules {
608
356
  if ('attributes' in data) {
609
357
  const d = data.attributes;
610
358
  Object.keys(d).forEach((attr) => {
611
- var _a;
612
359
  this._normalizeAdditionalFields(d[attr]);
613
360
  for (const field of booleanFields) {
614
361
  if (field in d[attr] && d[attr][field] === null) {
615
362
  d[attr][field] = false;
616
363
  }
617
364
  }
618
- // Add time intervals
619
- if (d[attr].type === 'timeInterval') {
620
- const intervals = (_a = d[attr].localizeInfos) === null || _a === void 0 ? void 0 : _a.intervals;
621
- // console.log('Schedules:: ', JSON.stringify(intervals));
622
- if (intervals && Array.isArray(intervals) && intervals.length > 0) {
623
- const result = this._addTimeIntervalsToFormSchedules(intervals);
624
- d[attr].localizeInfos.intervals = result;
625
- }
626
- }
627
365
  });
628
366
  return data;
629
367
  }
@@ -640,14 +378,6 @@ class SyncModules {
640
378
  const numValue = Number(data.value);
641
379
  data.value = isNaN(numValue) ? null : numValue;
642
380
  }
643
- // Add time intervals
644
- if (data.type === 'timeInterval') {
645
- const schedules = data.value;
646
- if (Array.isArray(schedules) && schedules.length > 0) {
647
- const result = this._addTimeIntervalsToSchedules(schedules);
648
- data.value = result;
649
- }
650
- }
651
381
  }
652
382
  return data;
653
383
  }
@@ -698,6 +428,35 @@ class SyncModules {
698
428
  this.state.guestId = guestId || undefined;
699
429
  return this;
700
430
  }
431
+ /**
432
+ * Sets the device-metadata override in the state.
433
+ *
434
+ * Once set, the string is sent as the `x-device-metadata` header on POST
435
+ * requests and token refresh instead of the environment-derived fingerprint.
436
+ * The API binds refresh tokens to this header, so server-side flows that issue
437
+ * tokens on behalf of a browser (e.g. an OAuth code exchange) must set the
438
+ * browser's string (obtained there via `getDeviceMetadata`). Pass an empty
439
+ * string to clear the override and fall back to the computed fingerprint.
440
+ * @param {string} deviceMetadata - The metadata string to send (empty string clears the override).
441
+ * @returns {any} The instance of SyncModules for chaining.
442
+ */
443
+ setDeviceMetadata(deviceMetadata) {
444
+ this.state.deviceMetadata = deviceMetadata || undefined;
445
+ return this;
446
+ }
447
+ /**
448
+ * Returns the device-metadata string the SDK sends as the `x-device-metadata` header.
449
+ *
450
+ * Public counterpart of `_getDeviceMetadata`: an explicit override (config or
451
+ * `setDeviceMetadata`) wins, otherwise the environment-derived fingerprint is
452
+ * computed. Use it in the browser to obtain the string that a server-side
453
+ * token-issuing flow (OAuth code exchange) must forward, so the issued refresh
454
+ * token stays refreshable from this browser.
455
+ * @returns {string} The metadata string sent with requests from this instance.
456
+ */
457
+ getDeviceMetadata() {
458
+ return this._getDeviceMetadata();
459
+ }
701
460
  /**
702
461
  * Get deviceMetadata
703
462
  *
@@ -724,10 +483,18 @@ class SyncModules {
724
483
  * lives until the process restarts.
725
484
  *
726
485
  * In a Node.js environment (no `window`) returns a simplified object without screen/navigator.
486
+ *
487
+ * An explicitly provided string (`deviceMetadata` in config or `setDeviceMetadata`)
488
+ * takes precedence over the environment-derived fingerprint — this lets a server
489
+ * issue tokens bound to the browser's fingerprint (see `IConfig.deviceMetadata`).
727
490
  * @returns {string} - Returns an object containing device metadata.
728
491
  */
729
492
  _getDeviceMetadata() {
730
493
  var _a;
494
+ // Explicit override wins: the API binds refresh tokens to this header, so
495
+ // server-side token issuance must be able to stamp the browser's string.
496
+ if (this.state.deviceMetadata)
497
+ return this.state.deviceMetadata;
731
498
  // Check if we're in a browser environment
732
499
  if (typeof globalThis === 'undefined') {
733
500
  return '';
@@ -0,0 +1,95 @@
1
+ import type { IAttributeValue, ITimeIntervalAttributeValue, ITimeIntervalEntitySchedule, ITimeIntervalSchedule, ITimeIntervalWindow, TimeIntervalPair } from './utils';
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[];