@cimplify/sdk 0.90.0 → 0.93.0

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 (67) hide show
  1. package/dist/advanced.d.mts +3 -3
  2. package/dist/advanced.d.ts +3 -3
  3. package/dist/advanced.js +20 -20
  4. package/dist/advanced.mjs +1 -1
  5. package/dist/{chunk-KJP4U6L3.js → chunk-6Y7KC5YZ.js} +23 -0
  6. package/dist/{chunk-JVWFIFQ7.js → chunk-AWNWP5J3.js} +4 -4
  7. package/dist/{chunk-2UDEWILL.mjs → chunk-FKARGZK3.mjs} +307 -90
  8. package/dist/{chunk-T7Z25EY4.js → chunk-HKN3YG76.js} +84 -0
  9. package/dist/{chunk-BN4V4ONE.js → chunk-QLGZIBRO.js} +348 -128
  10. package/dist/{chunk-NGE37REW.mjs → chunk-QXWX7CD2.mjs} +2 -2
  11. package/dist/{chunk-OZDDHSIB.mjs → chunk-RRBY35TJ.mjs} +80 -1
  12. package/dist/{chunk-GHFDYM5K.js → chunk-VEOAMBEX.js} +11 -4
  13. package/dist/{chunk-ZU4ZRYUR.mjs → chunk-VUSHG3HA.mjs} +11 -4
  14. package/dist/{chunk-NNGKC3MM.mjs → chunk-X36FZZT6.mjs} +23 -0
  15. package/dist/{client-YiSelM7x.d.mts → client-BTL9d_nN.d.mts} +117 -11
  16. package/dist/{client-CR1f94wY.d.ts → client-BX30FAWZ.d.ts} +117 -11
  17. package/dist/{client-DMttx1xk.d.mts → client-Bhe2TnFW.d.mts} +2 -2
  18. package/dist/{client-CqpOcbin.d.ts → client-DrkuEK_I.d.ts} +2 -2
  19. package/dist/{index-DD9z396M.d.mts → index-BuNjyfaV.d.mts} +3 -3
  20. package/dist/{index-CxTVu0HZ.d.ts → index-D-w5h82t.d.ts} +3 -3
  21. package/dist/index.d.mts +49 -7
  22. package/dist/index.d.ts +49 -7
  23. package/dist/index.js +103 -83
  24. package/dist/index.mjs +5 -5
  25. package/dist/mock/cli.mjs +23 -0
  26. package/dist/mock/library.js +23 -0
  27. package/dist/mock/library.mjs +23 -0
  28. package/dist/mock/msw.js +23 -0
  29. package/dist/mock/msw.mjs +23 -0
  30. package/dist/{payment-BX7ccIKY.d.ts → payment-By0JidTe.d.ts} +1 -1
  31. package/dist/{payment-5d8FTA1q.d.mts → payment-pKPpaORI.d.mts} +1 -1
  32. package/dist/{price-DtfP_Xfq.d.ts → price-23RNXftz.d.ts} +1 -1
  33. package/dist/{price-arl_4Rzn.d.mts → price-BvRtp6ak.d.mts} +1 -1
  34. package/dist/{product-DZX4QuKS.d.ts → product-O8-wcHSe.d.mts} +49 -0
  35. package/dist/{product-DZX4QuKS.d.mts → product-O8-wcHSe.d.ts} +49 -0
  36. package/dist/react.d.mts +79 -6
  37. package/dist/react.d.ts +79 -6
  38. package/dist/react.js +607 -379
  39. package/dist/react.mjs +539 -315
  40. package/dist/{server-BceZMxcK.d.mts → server-BN9ZCAKT.d.mts} +5 -1
  41. package/dist/{server-DoJGSC7L.d.ts → server-DDGbfW_S.d.ts} +5 -1
  42. package/dist/server.d.mts +4 -4
  43. package/dist/server.d.ts +4 -4
  44. package/dist/server.js +7 -7
  45. package/dist/server.mjs +2 -2
  46. package/dist/styles.css +1 -1
  47. package/dist/testing/msw.d.mts +2 -2
  48. package/dist/testing/msw.d.ts +2 -2
  49. package/dist/testing/msw.js +2 -2
  50. package/dist/testing/msw.mjs +1 -1
  51. package/dist/testing/suite.d.mts +6 -6
  52. package/dist/testing/suite.d.ts +6 -6
  53. package/dist/testing/suite.js +23 -23
  54. package/dist/testing/suite.mjs +4 -4
  55. package/dist/testing.d.mts +5 -5
  56. package/dist/testing.d.ts +5 -5
  57. package/dist/testing.js +79 -79
  58. package/dist/testing.mjs +5 -5
  59. package/dist/utils.d.mts +4 -4
  60. package/dist/utils.d.ts +4 -4
  61. package/package.json +1 -1
  62. package/registry/booking-page.json +1 -1
  63. package/registry/chat-widget.json +1 -1
  64. package/registry/customer-input-fields.json +1 -1
  65. package/registry/date-slot-picker.json +1 -1
  66. package/registry/product-customizer.json +1 -1
  67. package/registry/rental-service-card.json +1 -1
@@ -1,4 +1,4 @@
1
- import { safe, generateIdempotencyKey, CatalogueQueries, CartOperations, CheckoutService, OrderQueries, LinkService, AuthService, BusinessService, InventoryService, SchedulingService, LiteService, FxService, createElements } from './chunk-ZU4ZRYUR.mjs';
1
+ import { safe, generateIdempotencyKey, err, ok, CatalogueQueries, CartOperations, CheckoutService, OrderQueries, LinkService, AuthService, BusinessService, InventoryService, SchedulingService, LiteService, FxService, createElements } from './chunk-VUSHG3HA.mjs';
2
2
  import { CimplifyError, ErrorCode, IdempotencyMismatchError, enrichError } from './chunk-K5EN2W2Q.mjs';
3
3
 
4
4
  // src/store-credit.ts
@@ -72,6 +72,21 @@ var ActivityService = class {
72
72
  is_new_visitor: isNewVisitor
73
73
  };
74
74
  }
75
+ /**
76
+ * Record a page the visitor landed on. Product and category views already
77
+ * imply one, so call this for everything else — home, listings, booking,
78
+ * checkout — or entry pages, exits and bounce rate are computed from
79
+ * product pages alone.
80
+ */
81
+ trackPageView(options = {}) {
82
+ this.sendEvents([
83
+ {
84
+ type: "page_view",
85
+ page_path: options.pagePath ?? currentPath(),
86
+ page_title: options.pageTitle ?? currentTitle()
87
+ }
88
+ ]);
89
+ }
75
90
  trackProductView(productId, options = {}) {
76
91
  const event = {
77
92
  type: "product_view",
@@ -120,6 +135,12 @@ function currentPath() {
120
135
  }
121
136
  return void 0;
122
137
  }
138
+ function currentTitle() {
139
+ if (typeof document !== "undefined" && document.title) {
140
+ return document.title;
141
+ }
142
+ return void 0;
143
+ }
123
144
  function resolveVisitor() {
124
145
  try {
125
146
  const existing = localStorage.getItem(VISITOR_ID_STORAGE_KEY);
@@ -293,10 +314,137 @@ var DeliveryService = class {
293
314
  }
294
315
  };
295
316
 
317
+ // src/internal/events.ts
318
+ var MUTATION_ERROR = "mutation:error";
319
+ var AUTH_TOKEN_CHANGED = "auth:token-changed";
320
+ var SESSION_CLAIMED = "session:claimed";
321
+ var SESSION_CLEARED = "session:cleared";
322
+ function createClientEvents() {
323
+ const listeners = /* @__PURE__ */ new Map();
324
+ return {
325
+ on(name, listener) {
326
+ let set = listeners.get(name);
327
+ if (!set) {
328
+ set = /* @__PURE__ */ new Set();
329
+ listeners.set(name, set);
330
+ }
331
+ set.add(listener);
332
+ return () => {
333
+ set?.delete(listener);
334
+ };
335
+ },
336
+ off(name, listener) {
337
+ listeners.get(name)?.delete(listener);
338
+ },
339
+ emit(name, payload) {
340
+ const set = listeners.get(name);
341
+ if (!set) return;
342
+ const snapshot = Array.from(set);
343
+ for (const listener of snapshot) {
344
+ try {
345
+ listener(payload);
346
+ } catch (err2) {
347
+ console.error(`[Cimplify] event listener for "${name}" threw:`, err2);
348
+ }
349
+ }
350
+ }
351
+ };
352
+ }
353
+
296
354
  // src/support.ts
355
+ var CLAIM_TIMEOUT_MS = 15e3;
297
356
  var SupportService = class {
298
357
  constructor(client) {
299
358
  this.client = client;
359
+ this.claimInFlight = null;
360
+ this.claimInFlightToken = null;
361
+ this.lastClaimedAccessToken = null;
362
+ this.claimedName = null;
363
+ this.client.events.on(SESSION_CLEARED, () => {
364
+ this.lastClaimedAccessToken = null;
365
+ this.claimedName = null;
366
+ });
367
+ this.client.events.on(SESSION_CLAIMED, (event) => {
368
+ if (event.customerName) this.claimedName = event.customerName;
369
+ });
370
+ }
371
+ /** The verified customer this session's conversation was claimed for, null while anonymous. */
372
+ get claimedCustomerName() {
373
+ return this.claimedName;
374
+ }
375
+ /**
376
+ * Claim the anonymous session for the verified Link identity: the server
377
+ * verifies the access token, rotates the session token (adopted here, so
378
+ * every subsequent request rides the bound session), folds the guest cart
379
+ * in, and adopts or merges the widget conversation.
380
+ *
381
+ * Idempotent and single-flight: concurrent calls share one request, and a
382
+ * token already claimed by this instance short-circuits without a network
383
+ * round trip. Emits `session:claimed` on the client's event channel.
384
+ */
385
+ async claimConversation() {
386
+ const accessToken = this.client.getAccessToken();
387
+ if (!accessToken) {
388
+ return err(new CimplifyError("not_authenticated", "No access token to claim with"));
389
+ }
390
+ if (this.claimInFlight) {
391
+ if (this.claimInFlightToken === accessToken) return this.claimInFlight;
392
+ await this.claimInFlight;
393
+ return this.claimConversation();
394
+ }
395
+ if (this.lastClaimedAccessToken === accessToken) {
396
+ return err(new CimplifyError("already_claimed", "Session already claimed for this token"));
397
+ }
398
+ const epoch = this.client.getSessionEpoch();
399
+ this.claimInFlightToken = accessToken;
400
+ this.claimInFlight = (async () => {
401
+ const result = await safe(
402
+ this.client.post(
403
+ "/api/v1/support/conversation/claim",
404
+ { access_token: accessToken },
405
+ { timeoutMs: CLAIM_TIMEOUT_MS }
406
+ )
407
+ );
408
+ if (result.ok) {
409
+ if (this.client.getSessionEpoch() !== epoch) {
410
+ return err(
411
+ new CimplifyError("session_cleared", "Session was cleared during the claim")
412
+ );
413
+ }
414
+ this.lastClaimedAccessToken = accessToken;
415
+ this.claimedName = result.value.customer_name || null;
416
+ this.client.adoptSessionToken(result.value.session_token);
417
+ this.client.publishSessionClaimed(this.claimedName);
418
+ this.client.events.emit(SESSION_CLAIMED, {
419
+ sessionToken: result.value.session_token,
420
+ threadId: result.value.thread_id ?? null,
421
+ customerName: result.value.customer_name
422
+ });
423
+ }
424
+ return result;
425
+ })();
426
+ try {
427
+ return await this.claimInFlight;
428
+ } finally {
429
+ this.claimInFlight = null;
430
+ this.claimInFlightToken = null;
431
+ }
432
+ }
433
+ /**
434
+ * Peek at the conversation without creating one: resolves the existing
435
+ * thread (or null) so a page load can hydrate history without littering
436
+ * the inbox with empty threads.
437
+ */
438
+ async peekConversation() {
439
+ const result = await safe(
440
+ this.client.get("/api/v1/support/conversation")
441
+ );
442
+ if (!result.ok) return result;
443
+ if (!result.value.conversation) return ok(null);
444
+ return ok({
445
+ conversation: result.value.conversation,
446
+ messages: result.value.messages
447
+ });
300
448
  }
301
449
  /** Open (or resume) the customer's widget conversation. */
302
450
  async openConversation(opts) {
@@ -365,41 +513,9 @@ var SupportService = class {
365
513
  }
366
514
  };
367
515
 
368
- // src/internal/events.ts
369
- function createClientEvents() {
370
- const listeners = /* @__PURE__ */ new Map();
371
- return {
372
- on(name, listener) {
373
- let set = listeners.get(name);
374
- if (!set) {
375
- set = /* @__PURE__ */ new Set();
376
- listeners.set(name, set);
377
- }
378
- set.add(listener);
379
- return () => {
380
- set?.delete(listener);
381
- };
382
- },
383
- off(name, listener) {
384
- listeners.get(name)?.delete(listener);
385
- },
386
- emit(name, payload) {
387
- const set = listeners.get(name);
388
- if (!set) return;
389
- const snapshot = Array.from(set);
390
- for (const listener of snapshot) {
391
- try {
392
- listener(payload);
393
- } catch (err) {
394
- console.error(`[Cimplify] event listener for "${name}" threw:`, err);
395
- }
396
- }
397
- }
398
- };
399
- }
400
-
401
516
  // src/client.ts
402
517
  var SESSION_TOKEN_STORAGE_KEY = "cimplify_session_token";
518
+ var SESSION_SYNC_STORAGE_KEY = "cimplify_session_sync";
403
519
  var ORDER_TOKEN_PREFIX = "cimplify_ot_";
404
520
  var SESSION_TOKEN_HEADER = "x-session-token";
405
521
  var ORDER_TOKEN_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -516,6 +632,7 @@ var CimplifyClient = class {
516
632
  constructor(config = {}) {
517
633
  this.accessToken = null;
518
634
  this.sessionToken = null;
635
+ this.sessionEpoch = 0;
519
636
  this.context = {};
520
637
  this.businessId = null;
521
638
  this.businessIdResolvePromise = null;
@@ -538,6 +655,7 @@ var CimplifyClient = class {
538
655
  this.hooks = config.hooks ?? {};
539
656
  this.fetchImpl = (config.fetch ?? globalThis.fetch).bind(globalThis);
540
657
  this.sessionToken = this.loadSessionToken() ?? this.mintSessionToken();
658
+ this.watchCrossTabSession();
541
659
  if (!this.publicKey && !config.suppressPublicKeyWarning) {
542
660
  console.warn(
543
661
  '[Cimplify] No public key found. Set NEXT_PUBLIC_CIMPLIFY_PUBLIC_KEY in your environment, or pass { publicKey: "cpk_..." } to createCimplifyClient().'
@@ -577,57 +695,133 @@ var CimplifyClient = class {
577
695
  newToken: token,
578
696
  source: "manual"
579
697
  });
698
+ this.events.emit(AUTH_TOKEN_CHANGED, { token });
699
+ }
700
+ /**
701
+ * Zero-UI claim: a freshly verified customer identity (OTP verify, silent
702
+ * refresh restore) binds the anonymous session so the conversation, cart
703
+ * and orders survive a cleared browser. Best-effort by design — a failed
704
+ * claim leaves the session anonymous, exactly as it was. Called by the
705
+ * auth flows, never by the raw token setter.
706
+ */
707
+ autoClaimChatIdentity() {
708
+ if (typeof window === "undefined" || !this.sessionToken || !this.accessToken) return;
709
+ void this.support.claimConversation();
710
+ }
711
+ /**
712
+ * Adopt a server-rotated session token (claim flow). The old token stays
713
+ * in flight-safe use until the next request; the storage swap makes the
714
+ * rotation durable across reloads.
715
+ */
716
+ adoptSessionToken(token) {
717
+ if (!token || token === this.sessionToken) return;
718
+ this.sessionToken = token;
719
+ this.saveSessionToken(token);
580
720
  }
581
721
  clearSession() {
582
722
  const previous = this.accessToken;
583
723
  this.accessToken = null;
584
724
  this.sessionToken = null;
585
- if (typeof window !== "undefined" && window.localStorage) {
586
- localStorage.removeItem(SESSION_TOKEN_STORAGE_KEY);
725
+ this.sessionEpoch++;
726
+ try {
727
+ this.safeStorage()?.removeItem(SESSION_TOKEN_STORAGE_KEY);
728
+ } catch {
587
729
  }
588
730
  this.sessionToken = this.mintSessionToken();
731
+ if (this.sessionToken) {
732
+ this.broadcastSessionSync({ type: "cleared", token: this.sessionToken });
733
+ }
589
734
  this.hooks.onSessionChange?.({
590
735
  previousToken: previous,
591
736
  newToken: null,
592
737
  source: "clear"
593
738
  });
739
+ this.events.emit(AUTH_TOKEN_CHANGED, { token: null });
740
+ this.events.emit(SESSION_CLEARED, {});
741
+ }
742
+ getSessionEpoch() {
743
+ return this.sessionEpoch;
744
+ }
745
+ publishSessionClaimed(customerName) {
746
+ if (!this.sessionToken) return;
747
+ this.broadcastSessionSync({
748
+ type: "claimed",
749
+ token: this.sessionToken,
750
+ name: customerName
751
+ });
594
752
  }
595
- setOrderToken(orderId, token) {
596
- if (typeof window !== "undefined" && window.localStorage) {
753
+ broadcastSessionSync(payload) {
754
+ try {
755
+ this.safeStorage()?.setItem(
756
+ SESSION_SYNC_STORAGE_KEY,
757
+ JSON.stringify({ ...payload, at: Date.now() })
758
+ );
759
+ } catch {
760
+ }
761
+ }
762
+ watchCrossTabSession() {
763
+ if (typeof window === "undefined" || typeof window.addEventListener !== "function") return;
764
+ window.addEventListener("storage", (event) => {
765
+ if (event.key !== SESSION_SYNC_STORAGE_KEY || !event.newValue) return;
766
+ let payload;
597
767
  try {
598
- const entry = JSON.stringify({ token, storedAt: Date.now() });
599
- localStorage.setItem(`${ORDER_TOKEN_PREFIX}${orderId}`, entry);
768
+ payload = JSON.parse(event.newValue);
600
769
  } catch {
770
+ return;
771
+ }
772
+ if (!payload?.token || payload.token === this.sessionToken) return;
773
+ if (payload.type === "claimed") {
774
+ this.sessionToken = payload.token;
775
+ this.events.emit(SESSION_CLAIMED, {
776
+ sessionToken: payload.token,
777
+ threadId: null,
778
+ customerName: payload.name ?? ""
779
+ });
780
+ } else if (payload.type === "cleared") {
781
+ this.accessToken = null;
782
+ this.sessionToken = payload.token;
783
+ this.sessionEpoch++;
784
+ this.events.emit(AUTH_TOKEN_CHANGED, { token: null });
785
+ this.events.emit(SESSION_CLEARED, {});
601
786
  }
787
+ });
788
+ }
789
+ setOrderToken(orderId, token) {
790
+ try {
791
+ const entry = JSON.stringify({ token, storedAt: Date.now() });
792
+ this.safeStorage()?.setItem(`${ORDER_TOKEN_PREFIX}${orderId}`, entry);
793
+ } catch {
602
794
  }
603
795
  }
604
796
  getOrderToken(orderId) {
605
- if (typeof window !== "undefined" && window.localStorage) {
606
- try {
607
- const raw = localStorage.getItem(`${ORDER_TOKEN_PREFIX}${orderId}`);
608
- if (!raw) return null;
609
- const entry = JSON.parse(raw);
610
- if (Date.now() - entry.storedAt > ORDER_TOKEN_TTL_MS) {
611
- localStorage.removeItem(`${ORDER_TOKEN_PREFIX}${orderId}`);
612
- return null;
613
- }
614
- return entry.token;
615
- } catch {
797
+ try {
798
+ const storage = this.safeStorage();
799
+ if (!storage) return null;
800
+ const raw = storage.getItem(`${ORDER_TOKEN_PREFIX}${orderId}`);
801
+ if (!raw) return null;
802
+ const entry = JSON.parse(raw);
803
+ if (Date.now() - entry.storedAt > ORDER_TOKEN_TTL_MS) {
804
+ storage.removeItem(`${ORDER_TOKEN_PREFIX}${orderId}`);
616
805
  return null;
617
806
  }
807
+ return entry.token;
808
+ } catch {
809
+ return null;
618
810
  }
619
- return null;
620
811
  }
621
812
  clearOrderTokens() {
622
- if (typeof window !== "undefined" && window.localStorage) {
813
+ try {
814
+ const storage = this.safeStorage();
815
+ if (!storage) return;
623
816
  const keysToRemove = [];
624
- for (let i = 0; i < localStorage.length; i++) {
625
- const key = localStorage.key(i);
817
+ for (let i = 0; i < storage.length; i++) {
818
+ const key = storage.key(i);
626
819
  if (key?.startsWith(ORDER_TOKEN_PREFIX)) {
627
820
  keysToRemove.push(key);
628
821
  }
629
822
  }
630
- keysToRemove.forEach((k) => localStorage.removeItem(k));
823
+ keysToRemove.forEach((k) => storage.removeItem(k));
824
+ } catch {
631
825
  }
632
826
  }
633
827
  setLocationId(locationId) {
@@ -686,22 +880,29 @@ var CimplifyClient = class {
686
880
  this.businessIdResolvePromise = null;
687
881
  }
688
882
  }
689
- async authedFetch(url, options) {
690
- return this.resilientFetch(url, options);
883
+ async authedFetch(url, options, timeoutMs) {
884
+ return this.resilientFetch(url, options, timeoutMs);
691
885
  }
692
886
  // Storage guards: even the `window.localStorage` property access throws in
693
887
  // some sandboxed iframes and strict private-browsing modes. These run in the
694
888
  // constructor at module load, so a throw would white-screen the whole app —
695
889
  // fall back to an in-memory session token instead.
696
- loadSessionToken() {
890
+ safeStorage() {
697
891
  try {
698
892
  if (typeof window !== "undefined" && window.localStorage) {
699
- return localStorage.getItem(SESSION_TOKEN_STORAGE_KEY);
893
+ return window.localStorage;
700
894
  }
701
895
  } catch {
702
896
  }
703
897
  return null;
704
898
  }
899
+ loadSessionToken() {
900
+ try {
901
+ return this.safeStorage()?.getItem(SESSION_TOKEN_STORAGE_KEY) ?? null;
902
+ } catch {
903
+ return null;
904
+ }
905
+ }
705
906
  /**
706
907
  * Mint the session token client-side so the very first request already
707
908
  * carries it. Without this, parallel first-load requests each get a
@@ -721,15 +922,15 @@ var CimplifyClient = class {
721
922
  }
722
923
  saveSessionToken(token) {
723
924
  try {
724
- if (typeof window !== "undefined" && window.localStorage) {
725
- localStorage.setItem(SESSION_TOKEN_STORAGE_KEY, token);
726
- }
925
+ this.safeStorage()?.setItem(SESSION_TOKEN_STORAGE_KEY, token);
727
926
  } catch {
728
927
  }
729
928
  }
730
929
  captureSessionToken(response) {
930
+ if (typeof window === "undefined") return;
931
+ if (this.sessionToken) return;
731
932
  const token = response.headers.get(SESSION_TOKEN_HEADER);
732
- if (token && token !== this.sessionToken) {
933
+ if (token) {
733
934
  this.sessionToken = token;
734
935
  this.saveSessionToken(token);
735
936
  }
@@ -757,7 +958,7 @@ var CimplifyClient = class {
757
958
  }
758
959
  return headers;
759
960
  }
760
- async resilientFetch(url, options) {
961
+ async resilientFetch(url, options, timeoutMs) {
761
962
  const method = options.method || "GET";
762
963
  const path = url.replace(this.baseUrl, "").replace(this.linkApiUrl, "");
763
964
  const startTime = Date.now();
@@ -774,7 +975,7 @@ var CimplifyClient = class {
774
975
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
775
976
  try {
776
977
  const controller = new AbortController();
777
- const timeoutId = setTimeout(() => controller.abort(), this.timeout);
978
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs ?? this.timeout);
778
979
  const response = await this.fetchImpl(url, {
779
980
  ...options,
780
981
  signal: controller.signal
@@ -905,29 +1106,41 @@ var CimplifyClient = class {
905
1106
  });
906
1107
  }
907
1108
  async post(path, body, opts) {
908
- const response = await this.authedFetch(this.buildUrl(this.baseUrl, path), {
909
- method: "POST",
910
- credentials: this.credentials,
911
- headers: mergeOptionHeaders(this.getHeaders(), opts),
912
- body: body ? JSON.stringify(body) : void 0
913
- });
1109
+ const response = await this.authedFetch(
1110
+ this.buildUrl(this.baseUrl, path),
1111
+ {
1112
+ method: "POST",
1113
+ credentials: this.credentials,
1114
+ headers: mergeOptionHeaders(this.getHeaders(), opts),
1115
+ body: body ? JSON.stringify(body) : void 0
1116
+ },
1117
+ opts?.timeoutMs
1118
+ );
914
1119
  return this.handleRestResponse(response);
915
1120
  }
916
1121
  async patch(path, body, opts) {
917
- const response = await this.authedFetch(this.buildUrl(this.baseUrl, path), {
918
- method: "PATCH",
919
- credentials: this.credentials,
920
- headers: mergeOptionHeaders(this.getHeaders(), opts),
921
- body: body ? JSON.stringify(body) : void 0
922
- });
1122
+ const response = await this.authedFetch(
1123
+ this.buildUrl(this.baseUrl, path),
1124
+ {
1125
+ method: "PATCH",
1126
+ credentials: this.credentials,
1127
+ headers: mergeOptionHeaders(this.getHeaders(), opts),
1128
+ body: body ? JSON.stringify(body) : void 0
1129
+ },
1130
+ opts?.timeoutMs
1131
+ );
923
1132
  return this.handleRestResponse(response);
924
1133
  }
925
1134
  async delete(path, opts) {
926
- const response = await this.authedFetch(this.buildUrl(this.baseUrl, path), {
927
- method: "DELETE",
928
- credentials: this.credentials,
929
- headers: mergeOptionHeaders(this.getHeaders(), opts)
930
- });
1135
+ const response = await this.authedFetch(
1136
+ this.buildUrl(this.baseUrl, path),
1137
+ {
1138
+ method: "DELETE",
1139
+ credentials: this.credentials,
1140
+ headers: mergeOptionHeaders(this.getHeaders(), opts)
1141
+ },
1142
+ opts?.timeoutMs
1143
+ );
931
1144
  return this.handleRestResponse(response);
932
1145
  }
933
1146
  async linkGet(path, opts) {
@@ -943,12 +1156,16 @@ var CimplifyClient = class {
943
1156
  });
944
1157
  }
945
1158
  async linkPost(path, body, opts) {
946
- const response = await this.authedFetch(`${this.linkApiUrl}${path}`, {
947
- method: "POST",
948
- credentials: this.credentials,
949
- headers: mergeOptionHeaders(this.getHeaders(), opts),
950
- body: body ? JSON.stringify(body) : void 0
951
- });
1159
+ const response = await this.authedFetch(
1160
+ `${this.linkApiUrl}${path}`,
1161
+ {
1162
+ method: "POST",
1163
+ credentials: this.credentials,
1164
+ headers: mergeOptionHeaders(this.getHeaders(), opts),
1165
+ body: body ? JSON.stringify(body) : void 0
1166
+ },
1167
+ opts?.timeoutMs
1168
+ );
952
1169
  return this.handleRestResponse(response);
953
1170
  }
954
1171
  async linkDelete(path, opts) {
@@ -1123,4 +1340,4 @@ function createCimplifyClient(config = {}) {
1123
1340
  return new CimplifyClient(config);
1124
1341
  }
1125
1342
 
1126
- export { ActivityService, CimplifyClient, DeliveryService, PlacesService, StoreCreditQueries, SubscriptionService, SupportService, UploadService, createCimplifyClient };
1343
+ export { ActivityService, CimplifyClient, DeliveryService, MUTATION_ERROR, PlacesService, SESSION_CLAIMED, SESSION_CLEARED, StoreCreditQueries, SubscriptionService, SupportService, UploadService, createCimplifyClient };
@@ -74,6 +74,85 @@ function getVariantDisplayName(variant, productName) {
74
74
  return productName || (variant.is_default ? "Default" : "Option");
75
75
  }
76
76
 
77
+ // src/utils/price-basis.ts
78
+ var MULTI_DAY_UNITS = /* @__PURE__ */ new Set([
79
+ "days",
80
+ "nights",
81
+ "weeks",
82
+ "months",
83
+ "years"
84
+ ]);
85
+ var PERIOD_LABELS = {
86
+ minutes: "session",
87
+ hours: "session",
88
+ days: "day",
89
+ nights: "night",
90
+ weeks: "week",
91
+ months: "month",
92
+ years: "year"
93
+ };
94
+ function isMultiDayService(product) {
95
+ if ((product.type || "product") !== "service") return false;
96
+ if (product.scheduling_mode) return product.scheduling_mode === "multi_day";
97
+ return product.duration_unit != null && MULTI_DAY_UNITS.has(product.duration_unit);
98
+ }
99
+ function priceBasisLabel(product) {
100
+ if ((product.type || "product") !== "service") return null;
101
+ const basis = product.price_basis ?? (isMultiDayService(product) ? "per_duration_unit" : null);
102
+ if (basis === null || basis === "flat") return null;
103
+ if (basis === "per_person") return "person";
104
+ return PERIOD_LABELS[product.duration_unit ?? "nights"];
105
+ }
106
+ var DAY_MS = 864e5;
107
+ function utcDateParts(iso) {
108
+ const date = new Date(iso);
109
+ if (Number.isNaN(date.getTime())) return null;
110
+ return { year: date.getUTCFullYear(), month: date.getUTCMonth() + 1, day: date.getUTCDate() };
111
+ }
112
+ function stayPeriods(scheduledStart, scheduledEnd, durationUnit) {
113
+ const start = utcDateParts(scheduledStart);
114
+ const end = utcDateParts(scheduledEnd);
115
+ if (!start || !end) return 1;
116
+ const days = Math.max(
117
+ 1,
118
+ Math.round(
119
+ (Date.UTC(end.year, end.month - 1, end.day) - Date.UTC(start.year, start.month - 1, start.day)) / DAY_MS
120
+ )
121
+ );
122
+ switch (durationUnit) {
123
+ case "weeks":
124
+ return Math.max(1, Math.ceil(days / 7));
125
+ case "months": {
126
+ let months = (end.year - start.year) * 12 + (end.month - start.month);
127
+ if (end.day > start.day) months += 1;
128
+ return Math.max(1, months);
129
+ }
130
+ case "years": {
131
+ let years = end.year - start.year;
132
+ if (end.month > start.month || end.month === start.month && end.day > start.day) {
133
+ years += 1;
134
+ }
135
+ return Math.max(1, years);
136
+ }
137
+ default:
138
+ return days;
139
+ }
140
+ }
141
+ function stayLength(scheduledStart, scheduledEnd, durationUnit) {
142
+ const periods = stayPeriods(scheduledStart, scheduledEnd, durationUnit);
143
+ const label = PERIOD_LABELS[durationUnit ?? "nights"];
144
+ return `${periods} ${label}${periods === 1 ? "" : "s"}`;
145
+ }
146
+ function serviceBillingMultiplier(product, selection) {
147
+ if ((product.type || "product") !== "service") return null;
148
+ const basis = product.price_basis ?? (isMultiDayService(product) ? "per_duration_unit" : "per_person");
149
+ const units = Math.max(1, selection.units ?? 1);
150
+ if (basis === "flat") return units;
151
+ if (basis === "per_person") return Math.max(1, selection.quantity) * units;
152
+ const periods = selection.scheduledStart && selection.scheduledEnd ? stayPeriods(selection.scheduledStart, selection.scheduledEnd, product.duration_unit) : 1;
153
+ return periods * units;
154
+ }
155
+
77
156
  // src/types/product.ts
78
157
  var PRODUCT_TYPE = {
79
158
  Product: "product",
@@ -174,4 +253,9 @@ exports.allGalleryImages = allGalleryImages;
174
253
  exports.assetUrl = assetUrl;
175
254
  exports.getVariantDisplayName = getVariantDisplayName;
176
255
  exports.isCimplifyAsset = isCimplifyAsset;
256
+ exports.isMultiDayService = isMultiDayService;
257
+ exports.priceBasisLabel = priceBasisLabel;
177
258
  exports.resolveGallery = resolveGallery;
259
+ exports.serviceBillingMultiplier = serviceBillingMultiplier;
260
+ exports.stayLength = stayLength;
261
+ exports.stayPeriods = stayPeriods;