@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.
- package/dist/advanced.d.mts +3 -3
- package/dist/advanced.d.ts +3 -3
- package/dist/advanced.js +20 -20
- package/dist/advanced.mjs +1 -1
- package/dist/{chunk-KJP4U6L3.js → chunk-6Y7KC5YZ.js} +23 -0
- package/dist/{chunk-JVWFIFQ7.js → chunk-AWNWP5J3.js} +4 -4
- package/dist/{chunk-2UDEWILL.mjs → chunk-FKARGZK3.mjs} +307 -90
- package/dist/{chunk-T7Z25EY4.js → chunk-HKN3YG76.js} +84 -0
- package/dist/{chunk-BN4V4ONE.js → chunk-QLGZIBRO.js} +348 -128
- package/dist/{chunk-NGE37REW.mjs → chunk-QXWX7CD2.mjs} +2 -2
- package/dist/{chunk-OZDDHSIB.mjs → chunk-RRBY35TJ.mjs} +80 -1
- package/dist/{chunk-GHFDYM5K.js → chunk-VEOAMBEX.js} +11 -4
- package/dist/{chunk-ZU4ZRYUR.mjs → chunk-VUSHG3HA.mjs} +11 -4
- package/dist/{chunk-NNGKC3MM.mjs → chunk-X36FZZT6.mjs} +23 -0
- package/dist/{client-YiSelM7x.d.mts → client-BTL9d_nN.d.mts} +117 -11
- package/dist/{client-CR1f94wY.d.ts → client-BX30FAWZ.d.ts} +117 -11
- package/dist/{client-DMttx1xk.d.mts → client-Bhe2TnFW.d.mts} +2 -2
- package/dist/{client-CqpOcbin.d.ts → client-DrkuEK_I.d.ts} +2 -2
- package/dist/{index-DD9z396M.d.mts → index-BuNjyfaV.d.mts} +3 -3
- package/dist/{index-CxTVu0HZ.d.ts → index-D-w5h82t.d.ts} +3 -3
- package/dist/index.d.mts +49 -7
- package/dist/index.d.ts +49 -7
- package/dist/index.js +103 -83
- package/dist/index.mjs +5 -5
- package/dist/mock/cli.mjs +23 -0
- package/dist/mock/library.js +23 -0
- package/dist/mock/library.mjs +23 -0
- package/dist/mock/msw.js +23 -0
- package/dist/mock/msw.mjs +23 -0
- package/dist/{payment-BX7ccIKY.d.ts → payment-By0JidTe.d.ts} +1 -1
- package/dist/{payment-5d8FTA1q.d.mts → payment-pKPpaORI.d.mts} +1 -1
- package/dist/{price-DtfP_Xfq.d.ts → price-23RNXftz.d.ts} +1 -1
- package/dist/{price-arl_4Rzn.d.mts → price-BvRtp6ak.d.mts} +1 -1
- package/dist/{product-DZX4QuKS.d.ts → product-O8-wcHSe.d.mts} +49 -0
- package/dist/{product-DZX4QuKS.d.mts → product-O8-wcHSe.d.ts} +49 -0
- package/dist/react.d.mts +79 -6
- package/dist/react.d.ts +79 -6
- package/dist/react.js +607 -379
- package/dist/react.mjs +539 -315
- package/dist/{server-BceZMxcK.d.mts → server-BN9ZCAKT.d.mts} +5 -1
- package/dist/{server-DoJGSC7L.d.ts → server-DDGbfW_S.d.ts} +5 -1
- package/dist/server.d.mts +4 -4
- package/dist/server.d.ts +4 -4
- package/dist/server.js +7 -7
- package/dist/server.mjs +2 -2
- package/dist/styles.css +1 -1
- package/dist/testing/msw.d.mts +2 -2
- package/dist/testing/msw.d.ts +2 -2
- package/dist/testing/msw.js +2 -2
- package/dist/testing/msw.mjs +1 -1
- package/dist/testing/suite.d.mts +6 -6
- package/dist/testing/suite.d.ts +6 -6
- package/dist/testing/suite.js +23 -23
- package/dist/testing/suite.mjs +4 -4
- package/dist/testing.d.mts +5 -5
- package/dist/testing.d.ts +5 -5
- package/dist/testing.js +79 -79
- package/dist/testing.mjs +5 -5
- package/dist/utils.d.mts +4 -4
- package/dist/utils.d.ts +4 -4
- package/package.json +1 -1
- package/registry/booking-page.json +1 -1
- package/registry/chat-widget.json +1 -1
- package/registry/customer-input-fields.json +1 -1
- package/registry/date-slot-picker.json +1 -1
- package/registry/product-customizer.json +1 -1
- 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-
|
|
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
|
-
|
|
586
|
-
|
|
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
|
-
|
|
596
|
-
|
|
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
|
-
|
|
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
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
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
|
-
|
|
813
|
+
try {
|
|
814
|
+
const storage = this.safeStorage();
|
|
815
|
+
if (!storage) return;
|
|
623
816
|
const keysToRemove = [];
|
|
624
|
-
for (let i = 0; i <
|
|
625
|
-
const key =
|
|
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) =>
|
|
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
|
-
|
|
890
|
+
safeStorage() {
|
|
697
891
|
try {
|
|
698
892
|
if (typeof window !== "undefined" && window.localStorage) {
|
|
699
|
-
return localStorage
|
|
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
|
-
|
|
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
|
|
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(
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
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(
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
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(
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
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(
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
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;
|