@cavuno/board 4.14.0 → 4.16.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 (43) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/{_spec-CzS_E61o.d.mts → _spec-DdWB8z1Z.d.mts} +11 -1
  3. package/dist/{_spec-CzS_E61o.d.ts → _spec-DdWB8z1Z.d.ts} +11 -1
  4. package/dist/analytics.d.mts +58 -0
  5. package/dist/analytics.d.ts +58 -0
  6. package/dist/analytics.js +222 -0
  7. package/dist/analytics.mjs +199 -0
  8. package/dist/bin.mjs +99 -26
  9. package/dist/{blog-CtRz0ZlI.d.ts → blog-B7xorPBw.d.ts} +1 -1
  10. package/dist/{blog-D4R04cZP.d.mts → blog-DopIQsKV.d.mts} +1 -1
  11. package/dist/{board-MqLMXY48.d.ts → board-Bpnqn4Rg.d.ts} +2 -2
  12. package/dist/{board-CXE7dnk9.d.mts → board-CkgTW4cC.d.mts} +2 -2
  13. package/dist/{breadcrumbs-bbEYB2CD.d.mts → breadcrumbs-CAppxMoS.d.mts} +2 -2
  14. package/dist/{breadcrumbs-DdEy2bpQ.d.ts → breadcrumbs-DelEqbSz.d.ts} +2 -2
  15. package/dist/browser/cavuno-board.global.min.js +1 -1
  16. package/dist/doctor.js +93 -21
  17. package/dist/doctor.mjs +93 -21
  18. package/dist/filters.d.mts +2 -2
  19. package/dist/filters.d.ts +2 -2
  20. package/dist/format.d.mts +4 -4
  21. package/dist/format.d.ts +4 -4
  22. package/dist/index.d.mts +16 -11
  23. package/dist/index.d.ts +16 -11
  24. package/dist/index.js +1 -1
  25. package/dist/index.mjs +1 -1
  26. package/dist/{jobs-DDJDUH3q.d.ts → jobs-CExc4QdI.d.ts} +1 -1
  27. package/dist/{jobs-CsrNGyvS.d.mts → jobs-Dq0TTcRf.d.mts} +1 -1
  28. package/dist/{search-B3nCRBqd.d.ts → search-CZOec95V.d.ts} +1 -1
  29. package/dist/{search-Bnx30Fe2.d.mts → search-D6hau3d4.d.mts} +1 -1
  30. package/dist/{seo-BUDWv2pv.d.ts → seo-C1hFa0-v.d.ts} +1 -1
  31. package/dist/{seo-aSJ3zVx9.d.mts → seo-CJSwd_ss.d.mts} +1 -1
  32. package/dist/seo-files.d.mts +2 -2
  33. package/dist/seo-files.d.ts +2 -2
  34. package/dist/seo.d.mts +6 -6
  35. package/dist/seo.d.ts +6 -6
  36. package/dist/server.d.mts +6 -6
  37. package/dist/server.d.ts +6 -6
  38. package/dist/sitemap.d.mts +6 -6
  39. package/dist/sitemap.d.ts +6 -6
  40. package/dist/suggest.d.mts +2 -2
  41. package/dist/suggest.d.ts +2 -2
  42. package/package.json +11 -1
  43. package/skills/manifest.json +1 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,28 @@
3
3
  This changelog records changes that affect Board API compatibility, exported
4
4
  types, runtime behavior, or supported integration patterns.
5
5
 
6
+ ## 4.16.0 — 2026-08-31
7
+
8
+ - **`@cavuno/board/analytics`**: new subpath with `install({ publishableKey })`
9
+ and `track(action, payload?)`. Uses the board publishable key (`pk_…`) only.
10
+ Default collect URL is Cavuno central collect; pass a first-party
11
+ `/.well-known/cavuno/collect` URL when the board host serves those handlers.
12
+ - **Stable analytics tenant**: events are keyed by the immutable public board
13
+ id (`boards_<accountId>`), not the mutable board slug.
14
+ - **Doctor**: `npx @cavuno/board doctor` includes an analytics-surface scan that
15
+ flags legacy third-party tracker credentials in app source.
16
+
17
+ ## 4.15.0 — 2026-08-30
18
+
19
+ - **LinkedIn conversion IDs on `board.context().analytics`**: five nullable
20
+ Campaign Manager conversion IDs for direct Insight Tag installs (without
21
+ GTM): `linkedInConversionSignUpId`, `linkedInConversionLoginId`,
22
+ `linkedInConversionApplyClickId`, `linkedInConversionApplySubmitId`,
23
+ `linkedInConversionJobAlertSubscribeId`. Use with `linkedInPartnerId` to
24
+ fire `lintrk('track', { conversion_id })` for the same moments as the
25
+ hosted board. Leave null when LinkedIn conversions are fired only via GTM.
26
+ Meta still needs only `metaPixelId`; GTM still needs only `gtmId`.
27
+
6
28
  ## 4.14.0 — 2026-08-29
7
29
 
8
30
  - **Talent lists**: `board.me.companies.talentLists` (`list` / `create` /
@@ -1846,6 +1846,16 @@ interface components {
1846
1846
  gtmId: string | null;
1847
1847
  metaPixelId: string | null;
1848
1848
  linkedInPartnerId: string | null;
1849
+ /** @description LinkedIn Campaign Manager conversion ID for account creation (`sign_up`). Null when unset. Used with a direct Insight Tag install (`linkedInPartnerId`); leave null when LinkedIn conversions are fired only via GTM. */
1850
+ linkedInConversionSignUpId: string | null;
1851
+ /** @description LinkedIn Campaign Manager conversion ID for successful sign-in (`login`). Null when unset. */
1852
+ linkedInConversionLoginId: string | null;
1853
+ /** @description LinkedIn Campaign Manager conversion ID for opening the apply flow (`apply_click`). Null when unset. */
1854
+ linkedInConversionApplyClickId: string | null;
1855
+ /** @description LinkedIn Campaign Manager conversion ID for a successful native application (`apply_submit`). Null when unset. */
1856
+ linkedInConversionApplySubmitId: string | null;
1857
+ /** @description LinkedIn Campaign Manager conversion ID for job-alert subscription (`job_alert_subscribe`). Null when unset. */
1858
+ linkedInConversionJobAlertSubscribeId: string | null;
1849
1859
  cookieConsentRequired: boolean;
1850
1860
  };
1851
1861
  /** @description Public AdSense switch + publisher id from board advertising settings. Not the slot map (Puck/Cavuno placements) and not `ads.txt` (`board.seo()`). */
@@ -3065,7 +3075,7 @@ interface components {
3065
3075
  UpdateTalentListBody: {
3066
3076
  name?: string;
3067
3077
  filters?: components["schemas"]["TalentListFilters"];
3068
- job?: string | unknown | unknown;
3078
+ job?: string | unknown;
3069
3079
  };
3070
3080
  VerifyEmailOtpBody: {
3071
3081
  code: string;
@@ -1846,6 +1846,16 @@ interface components {
1846
1846
  gtmId: string | null;
1847
1847
  metaPixelId: string | null;
1848
1848
  linkedInPartnerId: string | null;
1849
+ /** @description LinkedIn Campaign Manager conversion ID for account creation (`sign_up`). Null when unset. Used with a direct Insight Tag install (`linkedInPartnerId`); leave null when LinkedIn conversions are fired only via GTM. */
1850
+ linkedInConversionSignUpId: string | null;
1851
+ /** @description LinkedIn Campaign Manager conversion ID for successful sign-in (`login`). Null when unset. */
1852
+ linkedInConversionLoginId: string | null;
1853
+ /** @description LinkedIn Campaign Manager conversion ID for opening the apply flow (`apply_click`). Null when unset. */
1854
+ linkedInConversionApplyClickId: string | null;
1855
+ /** @description LinkedIn Campaign Manager conversion ID for a successful native application (`apply_submit`). Null when unset. */
1856
+ linkedInConversionApplySubmitId: string | null;
1857
+ /** @description LinkedIn Campaign Manager conversion ID for job-alert subscription (`job_alert_subscribe`). Null when unset. */
1858
+ linkedInConversionJobAlertSubscribeId: string | null;
1849
1859
  cookieConsentRequired: boolean;
1850
1860
  };
1851
1861
  /** @description Public AdSense switch + publisher id from board advertising settings. Not the slot map (Puck/Cavuno placements) and not `ads.txt` (`board.seo()`). */
@@ -3065,7 +3075,7 @@ interface components {
3065
3075
  UpdateTalentListBody: {
3066
3076
  name?: string;
3067
3077
  filters?: components["schemas"]["TalentListFilters"];
3068
- job?: string | unknown | unknown;
3078
+ job?: string | unknown;
3069
3079
  };
3070
3080
  VerifyEmailOtpBody: {
3071
3081
  code: string;
@@ -0,0 +1,58 @@
1
+ declare const DEFAULT_COLLECT_URL = "https://cavuno.com/api/analytics/collect";
2
+ declare const DEFAULT_SCRIPT_URL = "https://cavuno.com/js/metrics.js";
3
+ /** Placeholder tenant; collect rewrites from the publishable key. */
4
+ declare const PENDING_TENANT_ID = "boards_pending";
5
+ declare const WELL_KNOWN_ANALYTICS_SCRIPT_PATH = "/.well-known/cavuno/analytics.js";
6
+ declare const WELL_KNOWN_SCRIPT_PATH = "/.well-known/cavuno/analytics.js";
7
+ declare const WELL_KNOWN_COLLECT_PATH = "/.well-known/cavuno/collect";
8
+
9
+ /**
10
+ * Optional first-party well-known handlers for board origin Workers.
11
+ * Central collect remains the default when these paths are not mounted.
12
+ */
13
+
14
+ declare const WELL_KNOWN_COLLECT_EVENTS_PATH = "/.well-known/cavuno/collect/v0/events";
15
+ /**
16
+ * Handle `/.well-known/cavuno/{analytics.js,collect}` when mounted on a Worker.
17
+ * Returns null for every other path so the host can fall through.
18
+ */
19
+ declare function matchAnalyticsWellKnown(request: Request): Promise<Response | null>;
20
+
21
+ /**
22
+ * `@cavuno/board/analytics` — client write surface for board analytics.
23
+ *
24
+ * Emits pageviews (via hosted script) and custom events to Cavuno collect.
25
+ * Uses the board publishable key only; no second analytics credential.
26
+ */
27
+
28
+ type AnalyticsInstallOptions = {
29
+ publishableKey: string;
30
+ /** Default: Cavuno central collect (self-host safe) */
31
+ collectUrl?: string;
32
+ /** Default: Cavuno-hosted metrics script */
33
+ scriptUrl?: string;
34
+ };
35
+ /**
36
+ * Load the Cavuno-hosted metrics script and remember collect credentials.
37
+ * Safe to call once per page; subsequent calls update collect targets only.
38
+ */
39
+ declare function install(options: AnalyticsInstallOptions): void;
40
+ /**
41
+ * Emit a custom analytics event to Cavuno collect.
42
+ * Prefer `install` first so pageviews and vitals are also recorded.
43
+ */
44
+ declare function track(action: string, payload?: Record<string, unknown>): void;
45
+ declare const analytics: {
46
+ install: typeof install;
47
+ track: typeof track;
48
+ matchAnalyticsWellKnown: typeof matchAnalyticsWellKnown;
49
+ DEFAULT_COLLECT_URL: string;
50
+ DEFAULT_SCRIPT_URL: string;
51
+ PENDING_TENANT_ID: string;
52
+ WELL_KNOWN_ANALYTICS_SCRIPT_PATH: string;
53
+ WELL_KNOWN_SCRIPT_PATH: string;
54
+ WELL_KNOWN_COLLECT_PATH: string;
55
+ WELL_KNOWN_COLLECT_EVENTS_PATH: string;
56
+ };
57
+
58
+ export { type AnalyticsInstallOptions, DEFAULT_COLLECT_URL, DEFAULT_SCRIPT_URL, PENDING_TENANT_ID, WELL_KNOWN_ANALYTICS_SCRIPT_PATH, WELL_KNOWN_COLLECT_EVENTS_PATH, WELL_KNOWN_COLLECT_PATH, WELL_KNOWN_SCRIPT_PATH, analytics, install, matchAnalyticsWellKnown, track };
@@ -0,0 +1,58 @@
1
+ declare const DEFAULT_COLLECT_URL = "https://cavuno.com/api/analytics/collect";
2
+ declare const DEFAULT_SCRIPT_URL = "https://cavuno.com/js/metrics.js";
3
+ /** Placeholder tenant; collect rewrites from the publishable key. */
4
+ declare const PENDING_TENANT_ID = "boards_pending";
5
+ declare const WELL_KNOWN_ANALYTICS_SCRIPT_PATH = "/.well-known/cavuno/analytics.js";
6
+ declare const WELL_KNOWN_SCRIPT_PATH = "/.well-known/cavuno/analytics.js";
7
+ declare const WELL_KNOWN_COLLECT_PATH = "/.well-known/cavuno/collect";
8
+
9
+ /**
10
+ * Optional first-party well-known handlers for board origin Workers.
11
+ * Central collect remains the default when these paths are not mounted.
12
+ */
13
+
14
+ declare const WELL_KNOWN_COLLECT_EVENTS_PATH = "/.well-known/cavuno/collect/v0/events";
15
+ /**
16
+ * Handle `/.well-known/cavuno/{analytics.js,collect}` when mounted on a Worker.
17
+ * Returns null for every other path so the host can fall through.
18
+ */
19
+ declare function matchAnalyticsWellKnown(request: Request): Promise<Response | null>;
20
+
21
+ /**
22
+ * `@cavuno/board/analytics` — client write surface for board analytics.
23
+ *
24
+ * Emits pageviews (via hosted script) and custom events to Cavuno collect.
25
+ * Uses the board publishable key only; no second analytics credential.
26
+ */
27
+
28
+ type AnalyticsInstallOptions = {
29
+ publishableKey: string;
30
+ /** Default: Cavuno central collect (self-host safe) */
31
+ collectUrl?: string;
32
+ /** Default: Cavuno-hosted metrics script */
33
+ scriptUrl?: string;
34
+ };
35
+ /**
36
+ * Load the Cavuno-hosted metrics script and remember collect credentials.
37
+ * Safe to call once per page; subsequent calls update collect targets only.
38
+ */
39
+ declare function install(options: AnalyticsInstallOptions): void;
40
+ /**
41
+ * Emit a custom analytics event to Cavuno collect.
42
+ * Prefer `install` first so pageviews and vitals are also recorded.
43
+ */
44
+ declare function track(action: string, payload?: Record<string, unknown>): void;
45
+ declare const analytics: {
46
+ install: typeof install;
47
+ track: typeof track;
48
+ matchAnalyticsWellKnown: typeof matchAnalyticsWellKnown;
49
+ DEFAULT_COLLECT_URL: string;
50
+ DEFAULT_SCRIPT_URL: string;
51
+ PENDING_TENANT_ID: string;
52
+ WELL_KNOWN_ANALYTICS_SCRIPT_PATH: string;
53
+ WELL_KNOWN_SCRIPT_PATH: string;
54
+ WELL_KNOWN_COLLECT_PATH: string;
55
+ WELL_KNOWN_COLLECT_EVENTS_PATH: string;
56
+ };
57
+
58
+ export { type AnalyticsInstallOptions, DEFAULT_COLLECT_URL, DEFAULT_SCRIPT_URL, PENDING_TENANT_ID, WELL_KNOWN_ANALYTICS_SCRIPT_PATH, WELL_KNOWN_COLLECT_EVENTS_PATH, WELL_KNOWN_COLLECT_PATH, WELL_KNOWN_SCRIPT_PATH, analytics, install, matchAnalyticsWellKnown, track };
@@ -0,0 +1,222 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/analytics/index.ts
21
+ var analytics_exports = {};
22
+ __export(analytics_exports, {
23
+ DEFAULT_COLLECT_URL: () => DEFAULT_COLLECT_URL,
24
+ DEFAULT_SCRIPT_URL: () => DEFAULT_SCRIPT_URL,
25
+ PENDING_TENANT_ID: () => PENDING_TENANT_ID,
26
+ WELL_KNOWN_ANALYTICS_SCRIPT_PATH: () => WELL_KNOWN_ANALYTICS_SCRIPT_PATH,
27
+ WELL_KNOWN_COLLECT_EVENTS_PATH: () => WELL_KNOWN_COLLECT_EVENTS_PATH,
28
+ WELL_KNOWN_COLLECT_PATH: () => WELL_KNOWN_COLLECT_PATH,
29
+ WELL_KNOWN_SCRIPT_PATH: () => WELL_KNOWN_SCRIPT_PATH,
30
+ analytics: () => analytics,
31
+ install: () => install,
32
+ matchAnalyticsWellKnown: () => matchAnalyticsWellKnown,
33
+ track: () => track
34
+ });
35
+ module.exports = __toCommonJS(analytics_exports);
36
+
37
+ // src/analytics/defaults.ts
38
+ var DEFAULT_COLLECT_URL = "https://cavuno.com/api/analytics/collect";
39
+ var DEFAULT_SCRIPT_URL = "https://cavuno.com/js/metrics.js";
40
+ var PENDING_TENANT_ID = "boards_pending";
41
+ var WELL_KNOWN_ANALYTICS_SCRIPT_PATH = "/.well-known/cavuno/analytics.js";
42
+ var WELL_KNOWN_SCRIPT_PATH = WELL_KNOWN_ANALYTICS_SCRIPT_PATH;
43
+ var WELL_KNOWN_COLLECT_PATH = "/.well-known/cavuno/collect";
44
+
45
+ // src/analytics/well-known.ts
46
+ var WELL_KNOWN_COLLECT_EVENTS_PATH = "/.well-known/cavuno/collect/v0/events";
47
+ var CORS_HEADERS = {
48
+ "Access-Control-Allow-Origin": "*",
49
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
50
+ "Access-Control-Allow-Headers": "Authorization, Content-Type, Accept",
51
+ "Access-Control-Max-Age": "86400",
52
+ "Cross-Origin-Resource-Policy": "cross-origin"
53
+ };
54
+ function stripTrailingSlash(pathname) {
55
+ return pathname.length > 1 && pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
56
+ }
57
+ function withCors(response) {
58
+ const headers = new Headers(response.headers);
59
+ for (const [key, value] of Object.entries(CORS_HEADERS)) {
60
+ headers.set(key, value);
61
+ }
62
+ return new Response(response.body, {
63
+ status: response.status,
64
+ statusText: response.statusText,
65
+ headers
66
+ });
67
+ }
68
+ function corsPreflight() {
69
+ return withCors(new Response(null, { status: 204 }));
70
+ }
71
+ async function proxyCollect(request, targetUrl) {
72
+ if (request.method === "OPTIONS") {
73
+ return corsPreflight();
74
+ }
75
+ if (request.method !== "POST") {
76
+ return withCors(new Response(null, { status: 405 }));
77
+ }
78
+ const headers = new Headers();
79
+ const authorization = request.headers.get("Authorization");
80
+ const contentType = request.headers.get("Content-Type");
81
+ const accept = request.headers.get("Accept");
82
+ if (authorization) headers.set("Authorization", authorization);
83
+ if (contentType) headers.set("Content-Type", contentType);
84
+ if (accept) headers.set("Accept", accept);
85
+ const body = await request.arrayBuffer();
86
+ const upstream = await fetch(targetUrl, {
87
+ method: "POST",
88
+ headers,
89
+ body
90
+ });
91
+ return withCors(
92
+ new Response(upstream.body, {
93
+ status: upstream.status,
94
+ headers: {
95
+ "Content-Type": upstream.headers.get("Content-Type") ?? "application/json"
96
+ }
97
+ })
98
+ );
99
+ }
100
+ async function matchAnalyticsWellKnown(request) {
101
+ const path = stripTrailingSlash(new URL(request.url).pathname);
102
+ if (path === WELL_KNOWN_ANALYTICS_SCRIPT_PATH) {
103
+ if (request.method !== "GET" && request.method !== "HEAD") {
104
+ return new Response(null, { status: 405 });
105
+ }
106
+ return Response.redirect(DEFAULT_SCRIPT_URL, 302);
107
+ }
108
+ if (path === WELL_KNOWN_COLLECT_PATH) {
109
+ return proxyCollect(request, DEFAULT_COLLECT_URL);
110
+ }
111
+ if (path === WELL_KNOWN_COLLECT_EVENTS_PATH) {
112
+ return proxyCollect(request, `${DEFAULT_COLLECT_URL}/v0/events`);
113
+ }
114
+ return null;
115
+ }
116
+
117
+ // src/analytics/index.ts
118
+ var GLOBAL_NAME = "CavunoAnalytics";
119
+ var installed = null;
120
+ function getRoot() {
121
+ return globalThis;
122
+ }
123
+ function normalizeCollectUrl(url) {
124
+ return url.replace(/\/+$/, "");
125
+ }
126
+ function resolveDefaultCollectUrl() {
127
+ return DEFAULT_COLLECT_URL;
128
+ }
129
+ function resolveDefaultScriptUrl() {
130
+ return DEFAULT_SCRIPT_URL;
131
+ }
132
+ function postCollectJson(state, action, payload) {
133
+ const body = JSON.stringify({
134
+ publishableKey: state.publishableKey,
135
+ action,
136
+ payload: payload ?? {}
137
+ });
138
+ void fetch(state.collectUrl, {
139
+ method: "POST",
140
+ headers: {
141
+ Authorization: `Bearer ${state.publishableKey}`,
142
+ "Content-Type": "application/json",
143
+ Accept: "application/json"
144
+ },
145
+ body,
146
+ keepalive: true
147
+ }).catch(() => {
148
+ });
149
+ }
150
+ function exposeGlobal(state) {
151
+ const root = getRoot();
152
+ root[GLOBAL_NAME] = {
153
+ trackEvent(action, payload) {
154
+ postCollectJson(state, action, payload);
155
+ }
156
+ };
157
+ }
158
+ function injectScript(state, scriptUrl) {
159
+ const doc = getRoot().document;
160
+ if (!doc?.createElement) {
161
+ return;
162
+ }
163
+ if (doc.querySelector?.(`script[data-cavuno-analytics="1"]`)) {
164
+ return;
165
+ }
166
+ const el = doc.createElement("script");
167
+ el.defer = true;
168
+ el.src = scriptUrl;
169
+ el.setAttribute("data-cavuno-analytics", "1");
170
+ el.setAttribute("data-token", state.publishableKey);
171
+ el.setAttribute("data-host", state.collectUrl);
172
+ el.setAttribute("data-tenant-id", PENDING_TENANT_ID);
173
+ el.setAttribute("data-web-vitals", "true");
174
+ const head = doc.head;
175
+ if (head?.appendChild) {
176
+ head.appendChild(el);
177
+ return;
178
+ }
179
+ const first = doc.getElementsByTagName("script")[0];
180
+ first?.parentNode?.insertBefore(el, first);
181
+ }
182
+ function install(options) {
183
+ const publishableKey = options.publishableKey.trim();
184
+ if (!publishableKey.startsWith("pk_")) {
185
+ throw new Error("analytics.install requires a publishable key (pk_\u2026)");
186
+ }
187
+ const collectUrl = normalizeCollectUrl(
188
+ (options.collectUrl ?? resolveDefaultCollectUrl()).trim() || resolveDefaultCollectUrl()
189
+ );
190
+ const scriptUrl = (options.scriptUrl ?? resolveDefaultScriptUrl()).trim() || resolveDefaultScriptUrl();
191
+ installed = { publishableKey, collectUrl };
192
+ exposeGlobal(installed);
193
+ injectScript(installed, scriptUrl);
194
+ }
195
+ function track(action, payload) {
196
+ const trimmed = action.trim();
197
+ if (!trimmed) {
198
+ return;
199
+ }
200
+ const root = getRoot();
201
+ const globalTrack = root[GLOBAL_NAME]?.trackEvent;
202
+ if (typeof globalTrack === "function" && installed) {
203
+ globalTrack(trimmed, payload);
204
+ return;
205
+ }
206
+ if (!installed) {
207
+ return;
208
+ }
209
+ postCollectJson(installed, trimmed, payload);
210
+ }
211
+ var analytics = {
212
+ install,
213
+ track,
214
+ matchAnalyticsWellKnown,
215
+ DEFAULT_COLLECT_URL,
216
+ DEFAULT_SCRIPT_URL,
217
+ PENDING_TENANT_ID,
218
+ WELL_KNOWN_ANALYTICS_SCRIPT_PATH,
219
+ WELL_KNOWN_SCRIPT_PATH,
220
+ WELL_KNOWN_COLLECT_PATH,
221
+ WELL_KNOWN_COLLECT_EVENTS_PATH
222
+ };
@@ -0,0 +1,199 @@
1
+ // src/analytics/defaults.ts
2
+ var DEFAULT_COLLECT_URL = "https://cavuno.com/api/analytics/collect";
3
+ var DEFAULT_SCRIPT_URL = "https://cavuno.com/js/metrics.js";
4
+ var PENDING_TENANT_ID = "boards_pending";
5
+ var WELL_KNOWN_ANALYTICS_SCRIPT_PATH = "/.well-known/cavuno/analytics.js";
6
+ var WELL_KNOWN_SCRIPT_PATH = WELL_KNOWN_ANALYTICS_SCRIPT_PATH;
7
+ var WELL_KNOWN_COLLECT_PATH = "/.well-known/cavuno/collect";
8
+
9
+ // src/analytics/well-known.ts
10
+ var WELL_KNOWN_COLLECT_EVENTS_PATH = "/.well-known/cavuno/collect/v0/events";
11
+ var CORS_HEADERS = {
12
+ "Access-Control-Allow-Origin": "*",
13
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
14
+ "Access-Control-Allow-Headers": "Authorization, Content-Type, Accept",
15
+ "Access-Control-Max-Age": "86400",
16
+ "Cross-Origin-Resource-Policy": "cross-origin"
17
+ };
18
+ function stripTrailingSlash(pathname) {
19
+ return pathname.length > 1 && pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
20
+ }
21
+ function withCors(response) {
22
+ const headers = new Headers(response.headers);
23
+ for (const [key, value] of Object.entries(CORS_HEADERS)) {
24
+ headers.set(key, value);
25
+ }
26
+ return new Response(response.body, {
27
+ status: response.status,
28
+ statusText: response.statusText,
29
+ headers
30
+ });
31
+ }
32
+ function corsPreflight() {
33
+ return withCors(new Response(null, { status: 204 }));
34
+ }
35
+ async function proxyCollect(request, targetUrl) {
36
+ if (request.method === "OPTIONS") {
37
+ return corsPreflight();
38
+ }
39
+ if (request.method !== "POST") {
40
+ return withCors(new Response(null, { status: 405 }));
41
+ }
42
+ const headers = new Headers();
43
+ const authorization = request.headers.get("Authorization");
44
+ const contentType = request.headers.get("Content-Type");
45
+ const accept = request.headers.get("Accept");
46
+ if (authorization) headers.set("Authorization", authorization);
47
+ if (contentType) headers.set("Content-Type", contentType);
48
+ if (accept) headers.set("Accept", accept);
49
+ const body = await request.arrayBuffer();
50
+ const upstream = await fetch(targetUrl, {
51
+ method: "POST",
52
+ headers,
53
+ body
54
+ });
55
+ return withCors(
56
+ new Response(upstream.body, {
57
+ status: upstream.status,
58
+ headers: {
59
+ "Content-Type": upstream.headers.get("Content-Type") ?? "application/json"
60
+ }
61
+ })
62
+ );
63
+ }
64
+ async function matchAnalyticsWellKnown(request) {
65
+ const path = stripTrailingSlash(new URL(request.url).pathname);
66
+ if (path === WELL_KNOWN_ANALYTICS_SCRIPT_PATH) {
67
+ if (request.method !== "GET" && request.method !== "HEAD") {
68
+ return new Response(null, { status: 405 });
69
+ }
70
+ return Response.redirect(DEFAULT_SCRIPT_URL, 302);
71
+ }
72
+ if (path === WELL_KNOWN_COLLECT_PATH) {
73
+ return proxyCollect(request, DEFAULT_COLLECT_URL);
74
+ }
75
+ if (path === WELL_KNOWN_COLLECT_EVENTS_PATH) {
76
+ return proxyCollect(request, `${DEFAULT_COLLECT_URL}/v0/events`);
77
+ }
78
+ return null;
79
+ }
80
+
81
+ // src/analytics/index.ts
82
+ var GLOBAL_NAME = "CavunoAnalytics";
83
+ var installed = null;
84
+ function getRoot() {
85
+ return globalThis;
86
+ }
87
+ function normalizeCollectUrl(url) {
88
+ return url.replace(/\/+$/, "");
89
+ }
90
+ function resolveDefaultCollectUrl() {
91
+ return DEFAULT_COLLECT_URL;
92
+ }
93
+ function resolveDefaultScriptUrl() {
94
+ return DEFAULT_SCRIPT_URL;
95
+ }
96
+ function postCollectJson(state, action, payload) {
97
+ const body = JSON.stringify({
98
+ publishableKey: state.publishableKey,
99
+ action,
100
+ payload: payload ?? {}
101
+ });
102
+ void fetch(state.collectUrl, {
103
+ method: "POST",
104
+ headers: {
105
+ Authorization: `Bearer ${state.publishableKey}`,
106
+ "Content-Type": "application/json",
107
+ Accept: "application/json"
108
+ },
109
+ body,
110
+ keepalive: true
111
+ }).catch(() => {
112
+ });
113
+ }
114
+ function exposeGlobal(state) {
115
+ const root = getRoot();
116
+ root[GLOBAL_NAME] = {
117
+ trackEvent(action, payload) {
118
+ postCollectJson(state, action, payload);
119
+ }
120
+ };
121
+ }
122
+ function injectScript(state, scriptUrl) {
123
+ const doc = getRoot().document;
124
+ if (!doc?.createElement) {
125
+ return;
126
+ }
127
+ if (doc.querySelector?.(`script[data-cavuno-analytics="1"]`)) {
128
+ return;
129
+ }
130
+ const el = doc.createElement("script");
131
+ el.defer = true;
132
+ el.src = scriptUrl;
133
+ el.setAttribute("data-cavuno-analytics", "1");
134
+ el.setAttribute("data-token", state.publishableKey);
135
+ el.setAttribute("data-host", state.collectUrl);
136
+ el.setAttribute("data-tenant-id", PENDING_TENANT_ID);
137
+ el.setAttribute("data-web-vitals", "true");
138
+ const head = doc.head;
139
+ if (head?.appendChild) {
140
+ head.appendChild(el);
141
+ return;
142
+ }
143
+ const first = doc.getElementsByTagName("script")[0];
144
+ first?.parentNode?.insertBefore(el, first);
145
+ }
146
+ function install(options) {
147
+ const publishableKey = options.publishableKey.trim();
148
+ if (!publishableKey.startsWith("pk_")) {
149
+ throw new Error("analytics.install requires a publishable key (pk_\u2026)");
150
+ }
151
+ const collectUrl = normalizeCollectUrl(
152
+ (options.collectUrl ?? resolveDefaultCollectUrl()).trim() || resolveDefaultCollectUrl()
153
+ );
154
+ const scriptUrl = (options.scriptUrl ?? resolveDefaultScriptUrl()).trim() || resolveDefaultScriptUrl();
155
+ installed = { publishableKey, collectUrl };
156
+ exposeGlobal(installed);
157
+ injectScript(installed, scriptUrl);
158
+ }
159
+ function track(action, payload) {
160
+ const trimmed = action.trim();
161
+ if (!trimmed) {
162
+ return;
163
+ }
164
+ const root = getRoot();
165
+ const globalTrack = root[GLOBAL_NAME]?.trackEvent;
166
+ if (typeof globalTrack === "function" && installed) {
167
+ globalTrack(trimmed, payload);
168
+ return;
169
+ }
170
+ if (!installed) {
171
+ return;
172
+ }
173
+ postCollectJson(installed, trimmed, payload);
174
+ }
175
+ var analytics = {
176
+ install,
177
+ track,
178
+ matchAnalyticsWellKnown,
179
+ DEFAULT_COLLECT_URL,
180
+ DEFAULT_SCRIPT_URL,
181
+ PENDING_TENANT_ID,
182
+ WELL_KNOWN_ANALYTICS_SCRIPT_PATH,
183
+ WELL_KNOWN_SCRIPT_PATH,
184
+ WELL_KNOWN_COLLECT_PATH,
185
+ WELL_KNOWN_COLLECT_EVENTS_PATH
186
+ };
187
+ export {
188
+ DEFAULT_COLLECT_URL,
189
+ DEFAULT_SCRIPT_URL,
190
+ PENDING_TENANT_ID,
191
+ WELL_KNOWN_ANALYTICS_SCRIPT_PATH,
192
+ WELL_KNOWN_COLLECT_EVENTS_PATH,
193
+ WELL_KNOWN_COLLECT_PATH,
194
+ WELL_KNOWN_SCRIPT_PATH,
195
+ analytics,
196
+ install,
197
+ matchAnalyticsWellKnown,
198
+ track
199
+ };