@duffcloudservices/cms 0.12.0 → 0.13.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 (39) hide show
  1. package/README.md +244 -8
  2. package/dist/chunk-A5F4C72F.js +500 -0
  3. package/dist/chunk-A5F4C72F.js.map +1 -0
  4. package/dist/{chunk-F3EIWEZD.js → chunk-HVSF23P7.js} +971 -73
  5. package/dist/chunk-HVSF23P7.js.map +1 -0
  6. package/dist/editor/editorBridge.d.ts +13 -1
  7. package/dist/editor/editorBridge.js +75 -5
  8. package/dist/editor/editorBridge.js.map +1 -1
  9. package/dist/headHonesty-OzxvLuwd.d.ts +222 -0
  10. package/dist/index.d.ts +365 -22
  11. package/dist/index.js +421 -21
  12. package/dist/index.js.map +1 -1
  13. package/dist/installSeoHead-kWQwObez.d.ts +627 -0
  14. package/dist/plugins/index.d.ts +90 -6
  15. package/dist/plugins/index.js +530 -49
  16. package/dist/plugins/index.js.map +1 -1
  17. package/dist/seo/index.d.ts +763 -4
  18. package/dist/seo/index.js +2 -2
  19. package/dist/{vitepressTransform-DfmABXmK.d.ts → vitepressTransform-JG_zlaux.d.ts} +99 -6
  20. package/package.json +17 -6
  21. package/src/components/DcsCallButton.test.ts +58 -0
  22. package/src/components/DcsCallButton.vue +19 -4
  23. package/src/components/LiteMediaEmbed.vue +3 -3
  24. package/src/components/ManagedImage.test.ts +34 -0
  25. package/src/components/ManagedImage.vue +5 -0
  26. package/src/components/PreviewRibbon.vue +4 -1
  27. package/src/composables/useConversionTracking.test.ts +492 -0
  28. package/src/composables/useConversionTracking.ts +770 -0
  29. package/src/composables/useReleaseNotes.ts +7 -1
  30. package/src/composables/useSEO.applyHead.test.ts +150 -0
  31. package/src/composables/useSEO.ts +63 -17
  32. package/src/composables/useSiteVersion.ts +4 -1
  33. package/src/composables/useSiteVisitorSession.test.ts +56 -0
  34. package/src/composables/useSiteVisitorSession.ts +39 -3
  35. package/src/composables/useTextContent.ts +9 -1
  36. package/dist/chunk-DAYLLSEE.js +0 -3
  37. package/dist/chunk-DAYLLSEE.js.map +0 -1
  38. package/dist/chunk-F3EIWEZD.js.map +0 -1
  39. package/dist/spliceHeadHtml-CsBEucGy.d.ts +0 -254
package/dist/index.js CHANGED
@@ -1,11 +1,398 @@
1
- import './chunk-DAYLLSEE.js';
2
- import { resolvePageSeo, generateJsonLd, buildHeadTags } from './chunk-F3EIWEZD.js';
3
- export { AI_BOTS, absolutizeUrl, breadcrumbTrailFromRoute, buildBlogPosting, buildBreadcrumbList, buildFaqPage, buildGlobalGraph, buildHeadTags, buildLlmsTxt, buildReviewSchemaParts, buildRobotsTxt, buildSitemapXml, buildVitePressSeoHead, createSeoTransformPageData, defaultRelativePathToRoute, escapeJsonLd, filterRealFaq, filterRealReviews, findReviewItemsForPage, generateJsonLd, generateOpenGraphMeta, generateTwitterMeta, isRouteIndexable, renderHeadTags, resolvePageSeo, slugToTitle, spliceHeadHtml, stripManagedHeadTags } from './chunk-F3EIWEZD.js';
4
- import { shallowRef, ref, computed, onMounted, readonly, toValue, onUnmounted } from 'vue';
5
- import { useHead } from '@unhead/vue';
6
- import { isCdnAssetUrl, resolveResponsiveImage } from '@duffcloudservices/cms-core';
1
+ import './chunk-A5F4C72F.js';
2
+ import { resolvePageSeo, generateJsonLd, buildHeadTags } from './chunk-HVSF23P7.js';
3
+ export { AI_BOTS, absolutizeUrl, breadcrumbTrailFromRoute, buildBlogPosting, buildBreadcrumbList, buildFaqPage, buildGlobalGraph, buildHeadTags, buildLlmsTxt, buildReviewSchemaParts, buildRobotsTxt, buildSeoHeadRouteMap, buildSitemapXml, buildVitePressSeoHead, createSeoTransformPageData, defaultRelativePathToRoute, escapeJsonLd, filterRealFaq, filterRealReviews, findReviewItemsForPage, generateJsonLd, generateOpenGraphMeta, generateTwitterMeta, installSeoHead, isRouteIndexable, normalizeSeoHeadPath, renderHeadTags, resolvePageSeo, slugToTitle, spliceHeadHtml, stripManagedHeadTags } from './chunk-HVSF23P7.js';
4
+ import { shallowRef, getCurrentInstance, onMounted, onBeforeUnmount, ref, computed, readonly, toValue, onUnmounted } from 'vue';
5
+ import { isCdnAssetUrl, resolveResponsiveImage, platformFetch, isPlatformFetchError } from '@duffcloudservices/cms-core';
7
6
  export { isCdnAssetUrl, resolveResponsiveImage } from '@duffcloudservices/cms-core';
7
+ import { useHead } from '@unhead/vue';
8
8
 
9
+ var CONVERSION_INTERACTION_TYPES = [
10
+ "booking",
11
+ "phone",
12
+ "email",
13
+ "form_submit"
14
+ ];
15
+ function isConversionType(type) {
16
+ return CONVERSION_INTERACTION_TYPES.includes(type);
17
+ }
18
+ var CONVERSION_CAPTURE_VERSION = "2";
19
+ var DEFAULT_BOOKING_HOSTS = [
20
+ "stridethera.com",
21
+ // KEPT — physical therapy
22
+ "momence.com",
23
+ // KEPT — gym / recovery
24
+ "vagaro.com",
25
+ // Just Posh
26
+ "acuityscheduling.com",
27
+ "booksy.com",
28
+ "calendly.com",
29
+ "fresha.com",
30
+ "janeapp.com",
31
+ "mindbodyonline.com",
32
+ "schedulicity.com",
33
+ "setmore.com",
34
+ "simplepractice.com",
35
+ "square.site",
36
+ "squareup.com"
37
+ ];
38
+ var DEFAULT_SOCIAL_HOSTS = [
39
+ "facebook.com",
40
+ "instagram.com",
41
+ "linkedin.com",
42
+ "pinterest.com",
43
+ "threads.net",
44
+ "tiktok.com",
45
+ "x.com",
46
+ "youtube.com"
47
+ ];
48
+ var LABEL_MAX_LENGTH = 120;
49
+ var DEFAULT_BUFFER_LIMIT = 50;
50
+ var DEDUPE_WINDOW_MS = 400;
51
+ function hostMatches(host, patterns) {
52
+ const h = host.toLowerCase();
53
+ return patterns.some((p) => {
54
+ const needle = p.toLowerCase();
55
+ return h === needle || h.endsWith(`.${needle}`);
56
+ });
57
+ }
58
+ function sanitizeUrl(raw) {
59
+ const trimmed = raw.trim();
60
+ if (!trimmed) return "";
61
+ const cut = trimmed.split("#")[0].split("?")[0];
62
+ return cut.slice(0, 300);
63
+ }
64
+ var CONTACT_SCHEMES = ["tel:", "sms:", "mailto:"];
65
+ function hashTarget(value) {
66
+ let h = 2166136261;
67
+ for (let i = 0; i < value.length; i += 1) {
68
+ h ^= value.charCodeAt(i);
69
+ h = Math.imul(h, 16777619) >>> 0;
70
+ }
71
+ return h.toString(36);
72
+ }
73
+ function hrefScheme(href, pageHost) {
74
+ const raw = href.trim();
75
+ if (!raw) return "";
76
+ const lower = raw.toLowerCase();
77
+ const contact = CONTACT_SCHEMES.find((s) => lower.startsWith(s));
78
+ if (contact) return contact;
79
+ if (lower.startsWith("#")) return "";
80
+ try {
81
+ return new URL(raw, `https://${pageHost || "localhost"}/`).protocol;
82
+ } catch {
83
+ return "";
84
+ }
85
+ }
86
+ function redactHref(href, pageHost) {
87
+ const raw = href.trim();
88
+ if (!raw) return { href: "", scheme: "", hash: "" };
89
+ const lower = raw.toLowerCase();
90
+ const contact = CONTACT_SCHEMES.find((s) => lower.startsWith(s));
91
+ if (contact) {
92
+ const target = raw.slice(contact.length).split("?")[0].trim().toLowerCase();
93
+ const normalized = contact === "mailto:" ? target : target.replace(/[^0-9+]/gu, "");
94
+ const hash = normalized ? hashTarget(normalized) : "";
95
+ return { href: `${contact}#${hash}`, scheme: contact, hash };
96
+ }
97
+ return { href: sanitizeUrl(raw), scheme: hrefScheme(raw, pageHost), hash: "" };
98
+ }
99
+ function isSubmitControl(el) {
100
+ const tag = el.tagName.toLowerCase();
101
+ if (tag !== "button" && tag !== "input") return false;
102
+ const type = (el.getAttribute("type") ?? "").toLowerCase();
103
+ if (type === "submit") return true;
104
+ if (type) return false;
105
+ return tag === "button" && !!el.closest("form");
106
+ }
107
+ function doNotTrackEnabled() {
108
+ if (typeof navigator === "undefined" && typeof window === "undefined") return false;
109
+ const nav = typeof navigator !== "undefined" ? navigator : void 0;
110
+ const win = typeof window !== "undefined" ? window : void 0;
111
+ const signal = nav?.doNotTrack ?? win?.doNotTrack ?? nav?.msDoNotTrack;
112
+ return signal === "1" || signal === "yes";
113
+ }
114
+ function labelFor(element) {
115
+ const aria = element.getAttribute("aria-label");
116
+ const source = aria && aria.trim() || (element.textContent ?? "").replace(/\s+/gu, " ").trim() || element.getAttribute("title") || element.getAttribute("href") || element.tagName.toLowerCase();
117
+ return source.slice(0, LABEL_MAX_LENGTH);
118
+ }
119
+ function classifyHref(href, opts) {
120
+ const raw = href.trim();
121
+ if (!raw) return "button";
122
+ const lower = raw.toLowerCase();
123
+ if (lower.startsWith("tel:") || lower.startsWith("sms:")) return "phone";
124
+ if (lower.startsWith("mailto:")) return "email";
125
+ if (lower.startsWith("#") || lower.startsWith("javascript:")) return "button";
126
+ let url;
127
+ try {
128
+ url = new URL(raw, `https://${opts.pageHost || "localhost"}/`);
129
+ } catch {
130
+ return "internal";
131
+ }
132
+ if (hostMatches(url.hostname, opts.bookingHosts)) return "booking";
133
+ const sameOrigin = !!opts.pageHost && url.hostname.toLowerCase() === opts.pageHost.toLowerCase();
134
+ if (sameOrigin && opts.bookingPaths.some((p) => url.pathname.startsWith(p))) {
135
+ return "booking";
136
+ }
137
+ if (hostMatches(url.hostname, opts.socialHosts)) return "social";
138
+ if (sameOrigin) return "internal";
139
+ if (url.protocol === "http:" || url.protocol === "https:") return "external";
140
+ return "external";
141
+ }
142
+ var registeredTrackers = /* @__PURE__ */ new Set();
143
+ var boundDocuments = /* @__PURE__ */ new WeakSet();
144
+ var warnedDoubleBind = false;
145
+ function attachConversionSink(sink) {
146
+ let count = 0;
147
+ for (const tracker of registeredTrackers) {
148
+ tracker.attachSink(sink);
149
+ count += 1;
150
+ }
151
+ return count;
152
+ }
153
+ function createConversionTracker(options = {}) {
154
+ const eventName = options.eventName ?? "site_interaction";
155
+ const bookingHosts = [...DEFAULT_BOOKING_HOSTS, ...options.bookingHosts ?? []];
156
+ const socialHosts = [...DEFAULT_SOCIAL_HOSTS, ...options.socialHosts ?? []];
157
+ const bookingPaths = options.bookingPaths ?? [];
158
+ const bufferLimit = Math.max(0, options.bufferLimit ?? DEFAULT_BUFFER_LIMIT);
159
+ const requireTrusted = options.requireTrusted ?? false;
160
+ const captureFormSubmits = options.captureFormSubmits ?? true;
161
+ const respectDoNotTrack = options.respectDoNotTrack ?? true;
162
+ const force = options.force ?? false;
163
+ const mirrors = options.mirrors ?? [];
164
+ let sink = options.sink ?? null;
165
+ let buffer = [];
166
+ let listening = false;
167
+ let lastKey = "";
168
+ let lastAt = 0;
169
+ const doc = () => options.target ?? (typeof document !== "undefined" ? document : null);
170
+ function pageHost() {
171
+ return typeof window !== "undefined" ? window.location.hostname : "";
172
+ }
173
+ function pagePath() {
174
+ return typeof window !== "undefined" ? window.location.pathname : "";
175
+ }
176
+ function emit(event) {
177
+ for (const mirror of mirrors) {
178
+ try {
179
+ mirror(event);
180
+ } catch {
181
+ }
182
+ }
183
+ if (sink) {
184
+ try {
185
+ sink(event);
186
+ } catch {
187
+ }
188
+ return;
189
+ }
190
+ if (bufferLimit === 0) return;
191
+ buffer.push(event);
192
+ if (buffer.length > bufferLimit) buffer.shift();
193
+ }
194
+ function compose(actionable, interactionType, rawHref) {
195
+ const host = pageHost();
196
+ const { href, scheme, hash } = redactHref(rawHref, host);
197
+ let hrefHost = "";
198
+ if (href && !hash) {
199
+ try {
200
+ hrefHost = new URL(href, `https://${host || "localhost"}/`).hostname;
201
+ } catch {
202
+ hrefHost = "";
203
+ }
204
+ }
205
+ return {
206
+ name: eventName,
207
+ properties: {
208
+ interaction_type: interactionType,
209
+ is_conversion: isConversionType(interactionType) ? "true" : "false",
210
+ label: labelFor(actionable),
211
+ href,
212
+ href_scheme: scheme,
213
+ href_host: hrefHost,
214
+ href_hash: hash,
215
+ page_path: pagePath(),
216
+ host,
217
+ capture_version: CONVERSION_CAPTURE_VERSION
218
+ }
219
+ };
220
+ }
221
+ function build(element) {
222
+ const anchor = element.closest("a");
223
+ const actionable = anchor ?? element.closest("button");
224
+ if (!actionable) return null;
225
+ if (captureFormSubmits && !anchor && isSubmitControl(actionable)) return null;
226
+ const rawHref = anchor?.getAttribute("href") ?? "";
227
+ const interactionType = anchor ? classifyHref(rawHref, { pageHost: pageHost(), bookingHosts, bookingPaths, socialHosts }) : "button";
228
+ const resolved = anchor && !CONTACT_SCHEMES.some((s) => rawHref.trim().toLowerCase().startsWith(s)) ? anchor.href || rawHref : rawHref;
229
+ return compose(actionable, interactionType, resolved);
230
+ }
231
+ function buildSubmit(form) {
232
+ const control = form.querySelector('button[type="submit"], input[type="submit"]') ?? form.querySelector("button") ?? form;
233
+ return compose(control, "form_submit", "");
234
+ }
235
+ function emitDeduped(event) {
236
+ const key = `${event.properties.interaction_type}|${event.properties.href}|${event.properties.label}`;
237
+ const now = Date.now();
238
+ if (key === lastKey && now - lastAt < DEDUPE_WINDOW_MS) return null;
239
+ lastKey = key;
240
+ lastAt = now;
241
+ emit(event);
242
+ return event;
243
+ }
244
+ function capture(element) {
245
+ const event = build(element);
246
+ if (!event) return null;
247
+ return emitDeduped(event);
248
+ }
249
+ const onClick = (event) => {
250
+ if (requireTrusted && !event.isTrusted) return;
251
+ const target = event.target;
252
+ if (!target || !target.closest) return;
253
+ try {
254
+ capture(target);
255
+ } catch {
256
+ }
257
+ };
258
+ const onSubmit = (event) => {
259
+ if (requireTrusted && !event.isTrusted) return;
260
+ const form = event.target;
261
+ if (!form || !form.tagName) return;
262
+ try {
263
+ emitDeduped(buildSubmit(form));
264
+ } catch {
265
+ }
266
+ };
267
+ const tracker = {
268
+ start() {
269
+ if (listening) return;
270
+ const d = doc();
271
+ if (!d) return;
272
+ if (respectDoNotTrack && doNotTrackEnabled()) {
273
+ return;
274
+ }
275
+ if (boundDocuments.has(d) && !force) {
276
+ if (!warnedDoubleBind) {
277
+ warnedDoubleBind = true;
278
+ console.warn(
279
+ "[dcs-conversion] start() REFUSED: a delegated conversion listener is already bound to this document. Binding a second one counts every click twice \u2014 the defect class that put 52% duplicate page views in a live owner report (C-288). Since C-326 the tracker auto-installs with @duffcloudservices/cms, so a manual useConversionTracking() in App.vue is no longer needed; remove it, or pass { force: true } if you have genuinely torn the first one down."
280
+ );
281
+ }
282
+ return;
283
+ }
284
+ d.addEventListener("click", onClick, { capture: true });
285
+ if (captureFormSubmits) d.addEventListener("submit", onSubmit, { capture: true });
286
+ listening = true;
287
+ boundDocuments.add(d);
288
+ registeredTrackers.add(tracker);
289
+ },
290
+ stop() {
291
+ const d = doc();
292
+ if (d && listening) {
293
+ d.removeEventListener("click", onClick, { capture: true });
294
+ if (captureFormSubmits) d.removeEventListener("submit", onSubmit, { capture: true });
295
+ boundDocuments.delete(d);
296
+ }
297
+ listening = false;
298
+ buffer = [];
299
+ registeredTrackers.delete(tracker);
300
+ },
301
+ attachSink(next) {
302
+ sink = next;
303
+ const pending = buffer;
304
+ buffer = [];
305
+ for (const event of pending) {
306
+ try {
307
+ next(event);
308
+ } catch {
309
+ }
310
+ }
311
+ },
312
+ buffered() {
313
+ return [...buffer];
314
+ },
315
+ capture
316
+ };
317
+ return tracker;
318
+ }
319
+ function useConversionTracking(options = {}) {
320
+ const tracker = createConversionTracker(options);
321
+ if (getCurrentInstance()) {
322
+ onMounted(() => tracker.start());
323
+ onBeforeUnmount(() => tracker.stop());
324
+ } else {
325
+ tracker.start();
326
+ }
327
+ return tracker;
328
+ }
329
+
330
+ // src/conversionAutoInstall.ts
331
+ var installed = null;
332
+ function conversionWindow() {
333
+ return typeof window !== "undefined" ? window : null;
334
+ }
335
+ function conversionOptedOut() {
336
+ const w = conversionWindow();
337
+ if (w?.__dcsConversionOptOut === true) return true;
338
+ if (typeof document !== "undefined") {
339
+ const flag = document.documentElement?.getAttribute("data-dcs-analytics");
340
+ if (flag === "off" || flag === "false") return true;
341
+ }
342
+ return false;
343
+ }
344
+ function inFrame() {
345
+ const w = conversionWindow();
346
+ if (!w) return false;
347
+ try {
348
+ return w.parent !== w || w.top !== w.self;
349
+ } catch {
350
+ return true;
351
+ }
352
+ }
353
+ function gtagSink(event) {
354
+ const w = conversionWindow();
355
+ if (typeof w?.gtag !== "function") return;
356
+ w.gtag("event", event.name, { ...event.properties });
357
+ }
358
+ function installConversionCapture(options = {}) {
359
+ const skip = (reason) => ({
360
+ installed: false,
361
+ reason,
362
+ tracker: null
363
+ });
364
+ if (typeof document === "undefined") return skip("no-document");
365
+ const w = conversionWindow();
366
+ if (installed || w?.__dcsConversionInstalled === true) return skip("already-installed");
367
+ if (inFrame()) return skip("in-frame");
368
+ if ((options.respectDoNotTrack ?? true) && doNotTrackEnabled()) return skip("do-not-track");
369
+ if (conversionOptedOut()) return skip("opted-out");
370
+ const useGtag = options.gtag ?? true;
371
+ const tracker = createConversionTracker({
372
+ ...options,
373
+ sink: void 0,
374
+ mirrors: useGtag ? [gtagSink] : []
375
+ });
376
+ tracker.start();
377
+ installed = tracker;
378
+ if (w) {
379
+ w.__dcsConversionInstalled = true;
380
+ w.__dcsConversionAttach = (sink) => attachConversionSink(sink);
381
+ }
382
+ return { installed: true, reason: null, tracker };
383
+ }
384
+ function installedConversionTracker() {
385
+ return installed;
386
+ }
387
+ function resetConversionCapture() {
388
+ installed?.stop();
389
+ installed = null;
390
+ const w = conversionWindow();
391
+ if (w) {
392
+ delete w.__dcsConversionInstalled;
393
+ delete w.__dcsConversionAttach;
394
+ }
395
+ }
9
396
  var fetchCache = /* @__PURE__ */ new Map();
10
397
  function getBuildTimeContent() {
11
398
  try {
@@ -102,7 +489,7 @@ function useTextContent(config) {
102
489
  error.value = null;
103
490
  try {
104
491
  const url = `${apiBaseUrl}/api/v1/pages/${pageSlug}/text`;
105
- const response = await fetch(url, {
492
+ const response = await platformFetch(url, {
106
493
  headers: {
107
494
  Accept: "application/json"
108
495
  }
@@ -163,6 +550,15 @@ function getBuildTimeSeo() {
163
550
  }
164
551
  return void 0;
165
552
  }
553
+ var HEAD_OVERRIDE_REFUSED_MESSAGE = "[dcs-seo] applyHead() was called WITH AN ARGUMENT and the argument was IGNORED. .dcs/seo.yaml is the only writer of title/description/keywords/canonical/OG/Twitter/JSON-LD (head-authority contract, C-356). A hardcoded value here overwrites the owner-approved baked head for Google and every human while non-JS crawlers keep receiving the approved one. Move the value into .dcs/seo.yaml and call applyHead() with no arguments.";
554
+ function reportHeadOverrideRefused(pageSlug, refused) {
555
+ const keys = refused && typeof refused === "object" ? Object.keys(refused).join(", ") : String(refused);
556
+ console.error(
557
+ `${HEAD_OVERRIDE_REFUSED_MESSAGE}
558
+ page slug: ${pageSlug}
559
+ ignored keys: ${keys}`
560
+ );
561
+ }
166
562
  function useSEO(pageSlug, pagePath) {
167
563
  const seoConfig = getBuildTimeSeo();
168
564
  const hasBuildTimeSeo = seoConfig !== void 0;
@@ -175,14 +571,11 @@ function useSEO(pageSlug, pagePath) {
175
571
  function getCanonical() {
176
572
  return config.value.canonical;
177
573
  }
178
- function applyHead(overrides) {
179
- const { title, meta, link, script } = buildHeadTags(pageSlug, pagePath, seoConfig, {
180
- title: overrides?.title,
181
- description: overrides?.description,
182
- keywords: overrides?.keywords,
183
- schemas: overrides?.schemas,
184
- meta: overrides?.meta
185
- });
574
+ function applyHeadImpl(...refused) {
575
+ if (refused.length > 0 && refused[0] !== void 0 && refused[0] !== null) {
576
+ reportHeadOverrideRefused(pageSlug, refused[0]);
577
+ }
578
+ const { title, meta, link, script } = buildHeadTags(pageSlug, pagePath, seoConfig);
186
579
  useHead({
187
580
  title,
188
581
  meta,
@@ -190,6 +583,7 @@ function useSEO(pageSlug, pagePath) {
190
583
  script
191
584
  });
192
585
  }
586
+ const applyHead = applyHeadImpl;
193
587
  return {
194
588
  config,
195
589
  applyHead,
@@ -233,7 +627,7 @@ function useReleaseNotes(version, options = {}) {
233
627
  error.value = null;
234
628
  try {
235
629
  const url = `${apiBaseUrl}/api/v1/release-notes/${version}`;
236
- const response = await fetch(url, {
630
+ const response = await platformFetch(url, {
237
631
  headers: {
238
632
  Accept: "application/json"
239
633
  }
@@ -313,7 +707,7 @@ function useSiteVersion(options = {}) {
313
707
  isLoading.value = true;
314
708
  try {
315
709
  const url = `${apiBaseUrl}/api/v1/release-notes/latest`;
316
- const response = await fetch(url, {
710
+ const response = await platformFetch(url, {
317
711
  headers: {
318
712
  Accept: "application/json"
319
713
  }
@@ -540,13 +934,16 @@ async function fetchSiteVisitorSession(options = {}) {
540
934
  const base = apiBase(options.apiBaseUrl);
541
935
  let response;
542
936
  try {
543
- response = await fetch(`${base}/site-auth/session`, {
937
+ response = await platformFetch(`${base}/site-auth/session`, {
544
938
  method: "GET",
545
939
  credentials: "include",
546
940
  headers: { Accept: "application/json" },
547
941
  signal: options.signal
548
942
  });
549
- } catch {
943
+ } catch (e) {
944
+ if (isPlatformFetchError(e)) {
945
+ return { visitor: null, authenticated: false, apiUnreachable: true };
946
+ }
550
947
  return SIGNED_OUT;
551
948
  }
552
949
  if (!response.ok) {
@@ -565,12 +962,14 @@ function useSiteVisitorSession(options = {}) {
565
962
  const { fetchOnMount = true, ...fetchOptions } = options;
566
963
  const visitor = ref(null);
567
964
  const isLoading = ref(false);
965
+ const apiUnreachable = ref(false);
568
966
  const isAuthenticated = computed(() => visitor.value !== null);
569
967
  async function refresh() {
570
968
  isLoading.value = true;
571
969
  try {
572
970
  const result = await fetchSiteVisitorSession(fetchOptions);
573
971
  visitor.value = result.visitor;
972
+ apiUnreachable.value = result.apiUnreachable === true;
574
973
  } finally {
575
974
  isLoading.value = false;
576
975
  }
@@ -580,9 +979,10 @@ function useSiteVisitorSession(options = {}) {
580
979
  void refresh();
581
980
  });
582
981
  }
583
- return { visitor, isAuthenticated, isLoading, refresh };
982
+ return { visitor, isAuthenticated, isLoading, apiUnreachable, refresh };
584
983
  }
984
+ installConversionCapture();
585
985
 
586
- export { createSiteSEO, fetchSiteVisitorSession, useMediaCarousel, useReleaseNotes, useResponsiveImage, useReviewContent, useSEO, useSiteVersion, useSiteVisitorSession, useTextContent };
986
+ export { CONVERSION_CAPTURE_VERSION, CONVERSION_INTERACTION_TYPES, DEFAULT_BOOKING_HOSTS, DEFAULT_SOCIAL_HOSTS, attachConversionSink, classifyHref, conversionOptedOut, createConversionTracker, createSiteSEO, doNotTrackEnabled, fetchSiteVisitorSession, hashTarget, hrefScheme, inFrame, installConversionCapture, installedConversionTracker, isConversionType, isSubmitControl, redactHref, resetConversionCapture, useConversionTracking, useMediaCarousel, useReleaseNotes, useResponsiveImage, useReviewContent, useSEO, useSiteVersion, useSiteVisitorSession, useTextContent };
587
987
  //# sourceMappingURL=index.js.map
588
988
  //# sourceMappingURL=index.js.map