@01.software/sdk 0.26.0 → 0.27.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 (48) hide show
  1. package/README.md +28 -3
  2. package/dist/analytics/react.cjs +249 -0
  3. package/dist/analytics/react.cjs.map +1 -0
  4. package/dist/analytics/react.d.cts +8 -0
  5. package/dist/analytics/react.d.ts +8 -0
  6. package/dist/analytics/react.js +228 -0
  7. package/dist/analytics/react.js.map +1 -0
  8. package/dist/{const-CEiFBZMW.d.cts → const-C0GlmeJ_.d.cts} +5 -5
  9. package/dist/{const-R3AyqOHY.d.ts → const-D-xucnw4.d.ts} +5 -5
  10. package/dist/{image-TT8lTsk5.d.cts → image-BDz2-AaO.d.cts} +1 -1
  11. package/dist/{image-TT8lTsk5.d.ts → image-BDz2-AaO.d.ts} +1 -1
  12. package/dist/index.cjs +13 -3
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +9 -9
  15. package/dist/index.d.ts +9 -9
  16. package/dist/index.js +13 -3
  17. package/dist/index.js.map +1 -1
  18. package/dist/{payload-types-BF8VVFt6.d.cts → payload-types-BPvUmPAq.d.cts} +221 -53
  19. package/dist/{payload-types-BF8VVFt6.d.ts → payload-types-BPvUmPAq.d.ts} +221 -53
  20. package/dist/realtime.d.cts +2 -2
  21. package/dist/realtime.d.ts +2 -2
  22. package/dist/{server-DLdbWJVv.d.cts → server-BINWywT8.d.cts} +1 -1
  23. package/dist/{server-DLdbWJVv.d.ts → server-BINWywT8.d.ts} +1 -1
  24. package/dist/{server-CZrUwqDs.d.cts → server-_zvihptw.d.ts} +27 -4
  25. package/dist/{server-DULueNYi.d.ts → server-n3xK4Nks.d.cts} +27 -4
  26. package/dist/server.cjs +8 -0
  27. package/dist/server.cjs.map +1 -1
  28. package/dist/server.d.cts +4 -4
  29. package/dist/server.d.ts +4 -4
  30. package/dist/server.js +8 -0
  31. package/dist/server.js.map +1 -1
  32. package/dist/{types-CZ1laT6s.d.ts → types-BLdthWiW.d.ts} +1 -1
  33. package/dist/{types-lPRgx_rC.d.cts → types-DzWNu9pw.d.cts} +1 -1
  34. package/dist/ui/canvas/server.d.cts +1 -1
  35. package/dist/ui/canvas/server.d.ts +1 -1
  36. package/dist/ui/canvas.d.cts +3 -3
  37. package/dist/ui/canvas.d.ts +3 -3
  38. package/dist/ui/form.d.cts +1 -1
  39. package/dist/ui/form.d.ts +1 -1
  40. package/dist/ui/image.d.cts +1 -1
  41. package/dist/ui/image.d.ts +1 -1
  42. package/dist/ui/video.d.cts +2 -2
  43. package/dist/ui/video.d.ts +2 -2
  44. package/dist/{video-DbLL8yuc.d.cts → video-WR_TFO9a.d.cts} +1 -1
  45. package/dist/{video-DbLL8yuc.d.ts → video-WR_TFO9a.d.ts} +1 -1
  46. package/dist/webhook.d.cts +3 -3
  47. package/dist/webhook.d.ts +3 -3
  48. package/package.json +42 -26
package/README.md CHANGED
@@ -32,6 +32,15 @@ const analytics = createAnalytics({ publishableKey: 'pk_xxx' })
32
32
  analytics.track('signup', { plan: 'pro', trial: false }) // custom event with optional props
33
33
  ```
34
34
 
35
+ ```tsx
36
+ // Analytics React component — Vercel Analytics-style mount helper
37
+ import { Analytics } from '@01.software/sdk/analytics/react'
38
+
39
+ export function App() {
40
+ return <Analytics />
41
+ }
42
+ ```
43
+
35
44
  ```typescript
36
45
  // Main entry - browser client, query builder, hooks, utilities
37
46
  import { createClient } from '@01.software/sdk'
@@ -49,6 +58,7 @@ import {
49
58
  import { createRealtimeClient } from '@01.software/sdk/realtime'
50
59
 
51
60
  // Components - sub-path imports per domain
61
+ import { Analytics } from '@01.software/sdk/analytics/react'
52
62
  import { RichTextContent } from '@01.software/sdk/ui/rich-text'
53
63
  import { Image } from '@01.software/sdk/ui/image'
54
64
  import { FormRenderer } from '@01.software/sdk/ui/form'
@@ -384,6 +394,18 @@ await server.commerce.orders.createFulfillment({ orderNumber, carrier, trackingN
384
394
  await server.commerce.orders.bulkImportFulfillments({ items: [{ orderNumber, carrier?, trackingNumber? }] })
385
395
  await server.commerce.orders.updateTransaction({ pgPaymentId, status, paymentMethod, receiptUrl })
386
396
 
397
+ // Provider-verified payment confirmation
398
+ // Existing Toss server-confirm callers may keep using updateTransaction with paymentKey + amount.
399
+ // PortOne/Stripe/etc. webhook handlers should verify with the provider first, then call:
400
+ await server.commerce.orders.confirmPayment({
401
+ orderNumber,
402
+ pgProvider: 'portone',
403
+ pgPaymentId,
404
+ amount,
405
+ providerStatus: 'PAID',
406
+ providerEventId,
407
+ })
408
+
387
409
  // Returns
388
410
  await server.commerce.orders.createReturn({ orderNumber, returnItems, refundAmount, reason? })
389
411
  await server.commerce.orders.updateReturn({ returnId, status })
@@ -490,7 +512,7 @@ const customerAuthHandler = createCustomerAuthWebhookHandler({
490
512
 
491
513
  ## Supported Collections
492
514
 
493
- Source of truth: `packages/sdk/src/core/collection/const.ts` (`COLLECTIONS`: 75).
515
+ Source of truth: `packages/sdk/src/core/collection/const.ts` (`COLLECTIONS`: 73).
494
516
 
495
517
  | Category | Collections |
496
518
  | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -508,10 +530,13 @@ Source of truth: `packages/sdk/src/core/collection/const.ts` (`COLLECTIONS`: 75)
508
530
  | Live Streams | `live-streams` |
509
531
  | Media | `images` |
510
532
  | Forms | `forms`, `form-submissions` |
511
- | Community | `posts`, `comments`, `reactions`, `reaction-types`, `bookmarks`, `post-categories`, `reports`, `community-bans` |
533
+ | Community | `posts`, `comments`, `reactions`, `reaction-types`, `bookmarks`, `post-categories` |
512
534
  | Events | `event-calendars`, `events`, `event-categories`, `event-occurrences`, `event-tags` |
513
535
 
514
- Server-only collections: `customer-groups` is available from `createServerClient().collections` for segmentation and campaign targeting, but is intentionally absent from browser collection discovery.
536
+ Server-only collections: `customer-groups`, `reports`, and `community-bans`
537
+ are available from `createServerClient().collections` for segmentation and
538
+ moderation workflows, but are intentionally absent from browser collection
539
+ discovery.
515
540
 
516
541
  ## Utilities
517
542
 
@@ -0,0 +1,249 @@
1
+ "use strict";
2
+ "use client";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/analytics/react.tsx
22
+ var react_exports = {};
23
+ __export(react_exports, {
24
+ Analytics: () => Analytics,
25
+ default: () => react_default
26
+ });
27
+ module.exports = __toCommonJS(react_exports);
28
+ var import_react = require("react");
29
+
30
+ // src/core/client/types.ts
31
+ function resolveApiUrl() {
32
+ if (typeof process !== "undefined" && process.env) {
33
+ const envUrl = process.env.SOFTWARE_API_URL || process.env.NEXT_PUBLIC_SOFTWARE_API_URL;
34
+ if (envUrl) {
35
+ return envUrl.replace(/\/$/, "");
36
+ }
37
+ }
38
+ return "https://api.01.software";
39
+ }
40
+
41
+ // src/analytics.ts
42
+ function createAnalytics(config) {
43
+ if (typeof window === "undefined") {
44
+ return { pageview() {
45
+ }, track() {
46
+ }, destroy() {
47
+ } };
48
+ }
49
+ const endpoint = config.endpoint ?? `${resolveApiUrl()}/api/analytics/collect`;
50
+ const respectDnt = config.respectDnt !== false;
51
+ function isDntActive() {
52
+ if (!respectDnt) return false;
53
+ const nav = navigator;
54
+ return nav.doNotTrack === "1" || nav.globalPrivacyControl === true;
55
+ }
56
+ let lastPath = null;
57
+ let lastAt = 0;
58
+ const autoTrack = config.autoTrack !== false;
59
+ const originalPushState = history.pushState;
60
+ const originalReplaceState = history.replaceState;
61
+ let destroyed = false;
62
+ function newEventId() {
63
+ return typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : String(Date.now()) + String(Math.random());
64
+ }
65
+ function sendBeaconOrFetch(body) {
66
+ try {
67
+ if (typeof navigator.sendBeacon === "function") {
68
+ const blob = new Blob([body], { type: "text/plain" });
69
+ const sent = navigator.sendBeacon(endpoint, blob);
70
+ if (sent) return;
71
+ }
72
+ fetch(endpoint, {
73
+ method: "POST",
74
+ keepalive: true,
75
+ headers: { "Content-Type": "application/json" },
76
+ body
77
+ }).catch(() => {
78
+ });
79
+ } catch {
80
+ }
81
+ }
82
+ function sendPageview(pathname) {
83
+ if (isDntActive()) return;
84
+ const doc = document;
85
+ if (doc.prerendering === true || document.visibilityState === "prerender") return;
86
+ const now = Date.now();
87
+ if (pathname === lastPath && now - lastAt < 500) return;
88
+ lastPath = pathname;
89
+ lastAt = now;
90
+ const body = JSON.stringify({
91
+ publishableKey: config.publishableKey,
92
+ pathname,
93
+ referrer: document.referrer || "",
94
+ eventId: newEventId(),
95
+ eventTs: Date.now()
96
+ });
97
+ sendBeaconOrFetch(body);
98
+ }
99
+ function trackCurrentPath() {
100
+ if (destroyed) return;
101
+ sendPageview(location.pathname);
102
+ }
103
+ function patchedPushState(data, unused, url) {
104
+ originalPushState.apply(this, [data, unused, url]);
105
+ if (!destroyed) setTimeout(trackCurrentPath, 0);
106
+ }
107
+ function patchedReplaceState(data, unused, url) {
108
+ originalReplaceState.apply(this, [data, unused, url]);
109
+ if (!destroyed) setTimeout(trackCurrentPath, 0);
110
+ }
111
+ if (autoTrack) {
112
+ history.pushState = patchedPushState;
113
+ history.replaceState = patchedReplaceState;
114
+ window.addEventListener("popstate", trackCurrentPath);
115
+ if (document.readyState === "complete") {
116
+ trackCurrentPath();
117
+ } else {
118
+ window.addEventListener("load", trackCurrentPath, { once: true });
119
+ }
120
+ }
121
+ const isProduction = (() => {
122
+ try {
123
+ const hostname = location.hostname;
124
+ return hostname !== "localhost" && hostname !== "127.0.0.1" && !hostname.endsWith(".local");
125
+ } catch {
126
+ return true;
127
+ }
128
+ })();
129
+ const warnedReasons = /* @__PURE__ */ new Set();
130
+ function devWarn(name, reason) {
131
+ if (isProduction) return;
132
+ if (warnedReasons.has(reason)) return;
133
+ warnedReasons.add(reason);
134
+ console.warn(`[01 analytics] dropped event ${name}: ${reason}`);
135
+ }
136
+ const EVENT_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_:-]{0,49}$/;
137
+ const RESERVED_PREFIXES = ["__", "_pv_"];
138
+ function validateEventName(name) {
139
+ if (!name || typeof name !== "string") return "name-empty";
140
+ for (const prefix of RESERVED_PREFIXES) {
141
+ if (name.startsWith(prefix)) return "name-reserved";
142
+ }
143
+ if (!EVENT_NAME_RE.test(name)) return "name-regex";
144
+ return null;
145
+ }
146
+ const PROP_KEY_RE = /^[a-zA-Z_][a-zA-Z0-9_]{0,31}$/;
147
+ function validateEventProps(props) {
148
+ if (props === void 0 || props === null) return null;
149
+ if (typeof props !== "object" || Array.isArray(props)) return "props-value-type";
150
+ const keys = Object.keys(props);
151
+ if (keys.length > 10) return "props-too-many-keys";
152
+ for (const k of keys) {
153
+ const v = props[k];
154
+ if (!PROP_KEY_RE.test(k)) return "props-key-regex";
155
+ if (typeof v === "string") {
156
+ if (v.length > 80) return "props-value-too-long";
157
+ } else if (typeof v === "number") {
158
+ if (!isFinite(v)) return "props-value-not-finite";
159
+ } else if (typeof v === "boolean") {
160
+ } else {
161
+ return "props-value-type";
162
+ }
163
+ }
164
+ return null;
165
+ }
166
+ return {
167
+ pageview(path) {
168
+ if (destroyed) return;
169
+ sendPageview(path ?? location.pathname);
170
+ },
171
+ track(name, props) {
172
+ if (destroyed) return;
173
+ if (isDntActive()) return;
174
+ const doc = document;
175
+ if (doc.prerendering === true || document.visibilityState === "prerender") return;
176
+ const nameErr = validateEventName(name);
177
+ if (nameErr) {
178
+ devWarn(name, nameErr);
179
+ return;
180
+ }
181
+ if (props !== void 0) {
182
+ const propsErr = validateEventProps(props);
183
+ if (propsErr) {
184
+ devWarn(name, propsErr);
185
+ return;
186
+ }
187
+ }
188
+ const body = JSON.stringify({
189
+ publishableKey: config.publishableKey,
190
+ pathname: location.pathname,
191
+ referrer: document.referrer || "",
192
+ eventId: newEventId(),
193
+ eventName: name,
194
+ eventProps: props,
195
+ eventTs: Date.now()
196
+ });
197
+ sendBeaconOrFetch(body);
198
+ },
199
+ destroy() {
200
+ if (destroyed) return;
201
+ destroyed = true;
202
+ if (autoTrack) {
203
+ history.pushState = originalPushState;
204
+ history.replaceState = originalReplaceState;
205
+ window.removeEventListener("popstate", trackCurrentPath);
206
+ }
207
+ lastPath = null;
208
+ lastAt = 0;
209
+ }
210
+ };
211
+ }
212
+
213
+ // src/analytics/env.ts
214
+ var import_meta = {};
215
+ function readAnalyticsRuntimeEnv() {
216
+ const nextPublicKey = typeof process !== "undefined" ? process.env?.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY : void 0;
217
+ const viteEnv = import_meta.env;
218
+ return {
219
+ nextPublicKey,
220
+ vitePublicKey: viteEnv?.VITE_SOFTWARE_PUBLISHABLE_KEY
221
+ };
222
+ }
223
+ function resolveAnalyticsPublishableKey(explicit, env = readAnalyticsRuntimeEnv()) {
224
+ if (explicit !== void 0) return explicit || void 0;
225
+ return env.nextPublicKey || env.vitePublicKey;
226
+ }
227
+
228
+ // src/analytics/react.tsx
229
+ function Analytics({
230
+ autoTrack,
231
+ endpoint,
232
+ publishableKey,
233
+ respectDnt
234
+ }) {
235
+ (0, import_react.useEffect)(() => {
236
+ const resolvedPublishableKey = resolveAnalyticsPublishableKey(publishableKey);
237
+ if (!resolvedPublishableKey) return;
238
+ const analytics = createAnalytics({
239
+ autoTrack,
240
+ endpoint,
241
+ publishableKey: resolvedPublishableKey,
242
+ respectDnt
243
+ });
244
+ return () => analytics.destroy();
245
+ }, [autoTrack, endpoint, publishableKey, respectDnt]);
246
+ return null;
247
+ }
248
+ var react_default = Analytics;
249
+ //# sourceMappingURL=react.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/analytics/react.tsx","../../src/core/client/types.ts","../../src/analytics.ts","../../src/analytics/env.ts"],"sourcesContent":["'use client'\n\nimport { useEffect } from 'react'\nimport { createAnalytics, type AnalyticsConfig } from '../analytics'\nimport { resolveAnalyticsPublishableKey } from './env'\n\nexport type AnalyticsProps = Omit<AnalyticsConfig, 'publishableKey'> & {\n publishableKey?: string\n}\n\nexport function Analytics({\n autoTrack,\n endpoint,\n publishableKey,\n respectDnt,\n}: AnalyticsProps) {\n useEffect(() => {\n const resolvedPublishableKey =\n resolveAnalyticsPublishableKey(publishableKey)\n if (!resolvedPublishableKey) return\n\n const analytics = createAnalytics({\n autoTrack,\n endpoint,\n publishableKey: resolvedPublishableKey,\n respectDnt,\n })\n\n return () => analytics.destroy()\n }, [autoTrack, endpoint, publishableKey, respectDnt])\n\n return null\n}\n\nexport default Analytics\n","import type { Sort, Where } from 'payload'\n\nimport type {\n Collection,\n PublicCollection,\n ServerCollection,\n ServerOnlyCollection,\n} from '../collection/const'\n\nexport type {\n Collection,\n PublicCollection,\n ServerCollection,\n ServerOnlyCollection,\n}\n\n// ============================================================================\n// API URL Configuration\n// ============================================================================\n\ndeclare const __DEFAULT_API_URL__: string\n\nexport function resolveApiUrl(): string {\n if (typeof process !== 'undefined' && process.env) {\n const envUrl =\n process.env.SOFTWARE_API_URL || process.env.NEXT_PUBLIC_SOFTWARE_API_URL\n if (envUrl) {\n return envUrl.replace(/\\/$/, '')\n }\n }\n return __DEFAULT_API_URL__\n}\n\n// ============================================================================\n// Client Configuration\n// ============================================================================\n\nexport interface ClientConfig {\n publishableKey: string\n /**\n * Customer authentication options.\n * Used to initialize CustomerAuth on Client.\n */\n customer?: {\n /**\n * Persist token in localStorage. Defaults to `true`.\n * - `true` (default): uses key `'customer-token'`\n * - `string`: uses the given string as localStorage key\n * - `false`: disables persistence (token/onTokenChange used instead)\n *\n * Handles SSR safely (no-op on server).\n * When enabled, `token` and `onTokenChange` are ignored.\n */\n persist?: boolean | string\n /** Initial token (e.g. from SSR cookie) */\n token?: string\n /** Called when token changes (login/logout) — use to persist in localStorage/cookie */\n onTokenChange?: (token: string | null) => void\n }\n}\n\n// Server client: requires both publishableKey (for CDN routing + rate limit +\n// monthly quota enforcement via the edge proxy) and secretKey (sk01_ opaque\n// bearer token, the authentication credential).\n// The proxy keys its tenant lookup off `X-Publishable-Key`, so omitting\n// publishableKey would silently bypass rate limiting and plan-based quota\n// enforcement.\nexport interface ClientServerConfig extends ClientConfig {\n secretKey: string\n}\n\nexport interface ClientMetadata {\n userAgent?: string\n timestamp: number\n}\n\nexport interface ClientState {\n metadata: ClientMetadata\n}\n\nexport interface PaginationMeta {\n page: number\n limit: number\n totalDocs: number\n totalPages: number\n hasNextPage: boolean\n hasPrevPage: boolean\n pagingCounter: number\n prevPage: number | null\n nextPage: number | null\n}\n\n// ============================================================================\n// Payload CMS Native Response Types\n// ============================================================================\n\n/**\n * Payload CMS Find (List) Response\n * GET /api/{collection}\n */\nexport interface PayloadFindResponse<T = unknown> {\n docs: T[]\n totalDocs: number\n limit: number\n totalPages: number\n page: number\n pagingCounter: number\n hasPrevPage: boolean\n hasNextPage: boolean\n prevPage: number | null\n nextPage: number | null\n}\n\n/**\n * Payload CMS Create/Update Response\n * POST /api/{collection}\n * PATCH /api/{collection}/{id}\n */\nexport interface PayloadMutationResponse<T = unknown> {\n message: string\n doc: T\n errors?: unknown[]\n}\n\n// ============================================================================\n// Query Options\n// ============================================================================\n\n/**\n * Do NOT replace with `Pick<FindOptions>` from `payload`. Payload's generic\n * types (`JoinQuery<TSlug>`, `PopulateType`) depend on `PayloadTypes` module\n * augmentation; external SDK consumers who skip that get degenerate types\n * (`never` / `{}`). Only non-generic `Sort`/`Where` are safe to import.\n * Excluded vs native: Local-API-only fields, `locale`/`fallbackLocale`.\n */\nexport interface ApiQueryOptions {\n page?: number\n limit?: number\n sort?: Sort\n where?: Where\n depth?: number\n select?: Record<string, boolean>\n /** Per-collection field selection for populated relationships (keyed by collection slug) */\n populate?: Record<string, boolean | Record<string, boolean>>\n /** Join field control: pagination/filter per join, or false to disable */\n joins?:\n | Record<\n string,\n | {\n limit?: number\n page?: number\n sort?: string\n where?: Where\n count?: boolean\n }\n | false\n >\n | false\n /** Set to `false` to skip the count query — returns docs without totalDocs/totalPages */\n pagination?: boolean\n /** Include draft versions (access control still applies on the server) */\n draft?: boolean\n /** Include soft-deleted documents (requires `trash` enabled on the collection) */\n trash?: boolean\n}\n\n// ============================================================================\n// Debug & Retry Configuration\n// ============================================================================\n\nexport interface DebugConfig {\n logRequests?: boolean\n logResponses?: boolean\n logErrors?: boolean\n}\n\nexport interface RetryConfig {\n maxRetries?: number\n retryableStatuses?: number[]\n retryDelay?: (attempt: number) => number\n}\n\n// ============================================================================\n// Type Utilities\n// ============================================================================\n\nexport type DeepPartial<T> = {\n [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]\n}\n\nexport type ExtractArrayType<T> = T extends (infer U)[] ? U : never\n","/**\n * @01.software/sdk — Analytics Helper\n */\n\n/* ANALYTICS INVARIANTS START\n * @01.software/sdk — Analytics Helper\n *\n * ANALYTICS INVARIANTS\n * ====================\n * These invariants are the single source of truth for observable behavior.\n * They are mirrored verbatim in apps/console/src/app/api/analytics/script.js/route.ts.\n * Any change here MUST be reflected there, and vice versa.\n *\n * 1. DNT/GPC respect: when config.respectDnt !== false (default true) AND\n * (navigator.doNotTrack === '1' OR navigator.globalPrivacyControl === true),\n * all methods become no-ops. Zero network requests are made.\n *\n * 2. Prerender skip: when document.prerendering === true OR\n * document.visibilityState === 'prerender', pageview() sends zero requests.\n *\n * 3. 500ms same-path dedup: a pageview for the same pathname within 500ms of\n * the previous send is silently dropped. After 500ms the next call sends.\n *\n * 4. Transport: sendBeacon → fetch keepalive fallback.\n * Primary: navigator.sendBeacon(endpoint, new Blob([json], { type: 'text/plain' })).\n * Fallback (sendBeacon unavailable OR returns false):\n * fetch(endpoint, { method: 'POST', keepalive: true,\n * headers: { 'Content-Type': 'application/json' }, body: json }).catch(() => {})\n *\n * 5. Body-only publishableKey: publishableKey is always in the request body,\n * never in any HTTP header.\n *\n * 6. SSR no-op: when typeof window === 'undefined', createAnalytics() returns\n * a stub where all methods are no-ops. No side effects occur.\n *\n * 7. Error swallowing: all transport errors are caught and swallowed.\n * createAnalytics() and all returned methods never throw into the caller.\n *\n * 8. Client timestamp: every send carries eventTs (milliseconds since epoch)\n * captured with Date.now() immediately before transport. The collect\n * endpoint uses eventTs (a) to bucket the event into the client's\n * tenant-local day and (b) to enforce the late-arrival cutoff; events\n * submitted after the local-day-end grace window are dropped with\n * reason \"late\".\n * ANALYTICS INVARIANTS END */\n\nimport { resolveApiUrl } from './core/client/types'\n\n// ============================================================================\n// Public Types\n// ============================================================================\n\nexport interface AnalyticsConfig {\n publishableKey: string\n /** Override the collect endpoint URL. Defaults to {SDK_BASE_URL}/api/analytics/collect */\n endpoint?: string\n /** Auto-patch history.pushState/replaceState and listen to popstate. Default: true */\n autoTrack?: boolean\n /** Respect navigator.doNotTrack and navigator.globalPrivacyControl. Default: true */\n respectDnt?: boolean\n}\n\nexport interface Analytics {\n pageview(path?: string): void\n track(name: string, props?: Record<string, string | number | boolean>): void\n destroy(): void\n}\n\n// ============================================================================\n// Implementation\n// ============================================================================\n\nexport function createAnalytics(config: AnalyticsConfig): Analytics {\n // INVARIANT 6: SSR no-op\n if (typeof window === 'undefined') {\n return { pageview() {}, track() {}, destroy() {} }\n }\n\n const endpoint =\n config.endpoint ?? `${resolveApiUrl()}/api/analytics/collect`\n\n // INVARIANT 1: DNT/GPC check (evaluated once at init; stays as closure)\n const respectDnt = config.respectDnt !== false\n function isDntActive(): boolean {\n if (!respectDnt) return false\n const nav = navigator as Navigator & { globalPrivacyControl?: boolean }\n return nav.doNotTrack === '1' || nav.globalPrivacyControl === true\n }\n\n // INVARIANT 3: 500ms same-path dedup state\n let lastPath: string | null = null\n let lastAt = 0\n\n // autoTrack state — save originals for destroy()\n const autoTrack = config.autoTrack !== false\n const originalPushState = history.pushState\n const originalReplaceState = history.replaceState\n let destroyed = false\n\n // -------------------------------------------------------------------------\n // Core send logic\n // -------------------------------------------------------------------------\n\n // Generate a unique event ID (crypto.randomUUID when available, Date+Math.random fallback)\n function newEventId(): string {\n return typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'\n ? crypto.randomUUID()\n : String(Date.now()) + String(Math.random())\n }\n\n // INVARIANT 4: sendBeacon → fetch keepalive fallback\n // INVARIANT 5: publishableKey in body only\n function sendBeaconOrFetch(body: string): void {\n try {\n if (typeof navigator.sendBeacon === 'function') {\n const blob = new Blob([body], { type: 'text/plain' })\n const sent = navigator.sendBeacon(endpoint, blob)\n if (sent) return\n // sent === false → fall through to fetch\n }\n // Fetch fallback\n fetch(endpoint, {\n method: 'POST',\n keepalive: true,\n headers: { 'Content-Type': 'application/json' },\n body,\n }).catch(() => {})\n } catch {\n // INVARIANT 7: swallow all errors\n }\n }\n\n function sendPageview(pathname: string): void {\n // INVARIANT 1: DNT/GPC\n if (isDntActive()) return\n\n // INVARIANT 2: prerender skip\n const doc = document as Document & { prerendering?: boolean }\n // visibilityState cast to string to accommodate non-standard 'prerender' value\n if (doc.prerendering === true || (document.visibilityState as string) === 'prerender') return\n\n // INVARIANT 3: 500ms same-path dedup\n const now = Date.now()\n if (pathname === lastPath && now - lastAt < 500) return\n lastPath = pathname\n lastAt = now\n\n const body = JSON.stringify({\n publishableKey: config.publishableKey,\n pathname,\n referrer: document.referrer || '',\n eventId: newEventId(),\n eventTs: Date.now(),\n })\n\n sendBeaconOrFetch(body)\n }\n\n // -------------------------------------------------------------------------\n // autoTrack: patch history methods + listen to popstate\n // -------------------------------------------------------------------------\n function trackCurrentPath(): void {\n if (destroyed) return\n sendPageview(location.pathname)\n }\n\n function patchedPushState(\n this: History,\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ): void {\n originalPushState.apply(this, [data, unused, url] as Parameters<typeof history.pushState>)\n if (!destroyed) setTimeout(trackCurrentPath, 0)\n }\n\n function patchedReplaceState(\n this: History,\n data: unknown,\n unused: string,\n url?: string | URL | null,\n ): void {\n originalReplaceState.apply(this, [data, unused, url] as Parameters<typeof history.replaceState>)\n if (!destroyed) setTimeout(trackCurrentPath, 0)\n }\n\n if (autoTrack) {\n history.pushState = patchedPushState\n history.replaceState = patchedReplaceState\n window.addEventListener('popstate', trackCurrentPath)\n\n // Initial pageview\n if (document.readyState === 'complete') {\n trackCurrentPath()\n } else {\n window.addEventListener('load', trackCurrentPath, { once: true })\n }\n }\n\n // -------------------------------------------------------------------------\n // track() — client-side validation + send\n // -------------------------------------------------------------------------\n\n // Dev-mode detection: warn in dev, silent in production.\n // process.env.NODE_ENV is unreliable in browser bundles (tsup does not replace it\n // by default). Instead we detect production at runtime via hostname heuristics.\n // SSR (window undefined) is caught at the top of createAnalytics and returns a\n // stub, so window is always defined here.\n const isProduction: boolean = (() => {\n try {\n const hostname = location.hostname\n return (\n hostname !== 'localhost' &&\n hostname !== '127.0.0.1' &&\n !hostname.endsWith('.local')\n )\n } catch {\n // hostname access failed (non-browser) — default to silent\n return true\n }\n })()\n\n // One-shot warn dedup per reason per page load (keyed by reason only)\n const warnedReasons = new Set<string>()\n\n function devWarn(name: string, reason: string): void {\n if (isProduction) return\n if (warnedReasons.has(reason)) return\n warnedReasons.add(reason)\n console.warn(`[01 analytics] dropped event ${name}: ${reason}`)\n }\n\n const EVENT_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_:-]{0,49}$/\n const RESERVED_PREFIXES = ['__', '_pv_']\n\n function validateEventName(name: string): string | null {\n if (!name || typeof name !== 'string') return 'name-empty'\n for (const prefix of RESERVED_PREFIXES) {\n if (name.startsWith(prefix)) return 'name-reserved'\n }\n if (!EVENT_NAME_RE.test(name)) return 'name-regex'\n return null\n }\n\n const PROP_KEY_RE = /^[a-zA-Z_][a-zA-Z0-9_]{0,31}$/\n\n function validateEventProps(\n props: Record<string, string | number | boolean> | undefined,\n ): string | null {\n if (props === undefined || props === null) return null\n if (typeof props !== 'object' || Array.isArray(props)) return 'props-value-type'\n const keys = Object.keys(props)\n if (keys.length > 10) return 'props-too-many-keys'\n for (const k of keys) {\n const v = props[k]\n if (!PROP_KEY_RE.test(k)) return 'props-key-regex'\n if (typeof v === 'string') {\n if (v.length > 80) return 'props-value-too-long'\n } else if (typeof v === 'number') {\n if (!isFinite(v)) return 'props-value-not-finite'\n } else if (typeof v === 'boolean') {\n // ok\n } else {\n return 'props-value-type'\n }\n }\n return null\n }\n\n // -------------------------------------------------------------------------\n // Public API\n // -------------------------------------------------------------------------\n return {\n pageview(path?: string): void {\n if (destroyed) return\n sendPageview(path ?? location.pathname)\n },\n\n track(name: string, props?: Record<string, string | number | boolean>): void {\n if (destroyed) return\n\n // INVARIANT 1: DNT/GPC (same as pageview)\n if (isDntActive()) return\n\n // INVARIANT 2: prerender skip\n const doc = document as Document & { prerendering?: boolean }\n if (doc.prerendering === true || (document.visibilityState as string) === 'prerender') return\n\n // Client-side validation\n const nameErr = validateEventName(name)\n if (nameErr) {\n devWarn(name, nameErr)\n return\n }\n\n if (props !== undefined) {\n const propsErr = validateEventProps(props)\n if (propsErr) {\n devWarn(name, propsErr)\n return\n }\n }\n\n // Build body — no dedup for track() events\n const body = JSON.stringify({\n publishableKey: config.publishableKey,\n pathname: location.pathname,\n referrer: document.referrer || '',\n eventId: newEventId(),\n eventName: name,\n eventProps: props,\n eventTs: Date.now(),\n })\n\n sendBeaconOrFetch(body)\n },\n\n destroy(): void {\n if (destroyed) return\n destroyed = true\n\n if (autoTrack) {\n // Restore original history methods\n history.pushState = originalPushState\n history.replaceState = originalReplaceState\n window.removeEventListener('popstate', trackCurrentPath)\n }\n\n // Null out dedup state\n lastPath = null\n lastAt = 0\n },\n }\n}\n","export type AnalyticsRuntimeEnv = {\n nextPublicKey?: string\n vitePublicKey?: string\n}\n\nexport function readAnalyticsRuntimeEnv(): AnalyticsRuntimeEnv {\n const nextPublicKey =\n typeof process !== 'undefined'\n ? process.env?.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY\n : undefined\n\n const viteEnv = (\n import.meta as ImportMeta & {\n env?: Record<string, string | undefined>\n }\n ).env\n\n return {\n nextPublicKey,\n vitePublicKey: viteEnv?.VITE_SOFTWARE_PUBLISHABLE_KEY,\n }\n}\n\nexport function resolveAnalyticsPublishableKey(\n explicit?: string,\n env: AnalyticsRuntimeEnv = readAnalyticsRuntimeEnv(),\n): string | undefined {\n if (explicit !== undefined) return explicit || undefined\n\n return env.nextPublicKey || env.vitePublicKey\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,8 @@
1
+ import { AnalyticsConfig } from '../analytics.cjs';
2
+
3
+ type AnalyticsProps = Omit<AnalyticsConfig, 'publishableKey'> & {
4
+ publishableKey?: string;
5
+ };
6
+ declare function Analytics({ autoTrack, endpoint, publishableKey, respectDnt, }: AnalyticsProps): null;
7
+
8
+ export { Analytics, type AnalyticsProps, Analytics as default };
@@ -0,0 +1,8 @@
1
+ import { AnalyticsConfig } from '../analytics.js';
2
+
3
+ type AnalyticsProps = Omit<AnalyticsConfig, 'publishableKey'> & {
4
+ publishableKey?: string;
5
+ };
6
+ declare function Analytics({ autoTrack, endpoint, publishableKey, respectDnt, }: AnalyticsProps): null;
7
+
8
+ export { Analytics, type AnalyticsProps, Analytics as default };
@@ -0,0 +1,228 @@
1
+ "use client";
2
+
3
+ // src/analytics/react.tsx
4
+ import { useEffect } from "react";
5
+
6
+ // src/core/client/types.ts
7
+ function resolveApiUrl() {
8
+ if (typeof process !== "undefined" && process.env) {
9
+ const envUrl = process.env.SOFTWARE_API_URL || process.env.NEXT_PUBLIC_SOFTWARE_API_URL;
10
+ if (envUrl) {
11
+ return envUrl.replace(/\/$/, "");
12
+ }
13
+ }
14
+ return "https://api.01.software";
15
+ }
16
+
17
+ // src/analytics.ts
18
+ function createAnalytics(config) {
19
+ if (typeof window === "undefined") {
20
+ return { pageview() {
21
+ }, track() {
22
+ }, destroy() {
23
+ } };
24
+ }
25
+ const endpoint = config.endpoint ?? `${resolveApiUrl()}/api/analytics/collect`;
26
+ const respectDnt = config.respectDnt !== false;
27
+ function isDntActive() {
28
+ if (!respectDnt) return false;
29
+ const nav = navigator;
30
+ return nav.doNotTrack === "1" || nav.globalPrivacyControl === true;
31
+ }
32
+ let lastPath = null;
33
+ let lastAt = 0;
34
+ const autoTrack = config.autoTrack !== false;
35
+ const originalPushState = history.pushState;
36
+ const originalReplaceState = history.replaceState;
37
+ let destroyed = false;
38
+ function newEventId() {
39
+ return typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : String(Date.now()) + String(Math.random());
40
+ }
41
+ function sendBeaconOrFetch(body) {
42
+ try {
43
+ if (typeof navigator.sendBeacon === "function") {
44
+ const blob = new Blob([body], { type: "text/plain" });
45
+ const sent = navigator.sendBeacon(endpoint, blob);
46
+ if (sent) return;
47
+ }
48
+ fetch(endpoint, {
49
+ method: "POST",
50
+ keepalive: true,
51
+ headers: { "Content-Type": "application/json" },
52
+ body
53
+ }).catch(() => {
54
+ });
55
+ } catch {
56
+ }
57
+ }
58
+ function sendPageview(pathname) {
59
+ if (isDntActive()) return;
60
+ const doc = document;
61
+ if (doc.prerendering === true || document.visibilityState === "prerender") return;
62
+ const now = Date.now();
63
+ if (pathname === lastPath && now - lastAt < 500) return;
64
+ lastPath = pathname;
65
+ lastAt = now;
66
+ const body = JSON.stringify({
67
+ publishableKey: config.publishableKey,
68
+ pathname,
69
+ referrer: document.referrer || "",
70
+ eventId: newEventId(),
71
+ eventTs: Date.now()
72
+ });
73
+ sendBeaconOrFetch(body);
74
+ }
75
+ function trackCurrentPath() {
76
+ if (destroyed) return;
77
+ sendPageview(location.pathname);
78
+ }
79
+ function patchedPushState(data, unused, url) {
80
+ originalPushState.apply(this, [data, unused, url]);
81
+ if (!destroyed) setTimeout(trackCurrentPath, 0);
82
+ }
83
+ function patchedReplaceState(data, unused, url) {
84
+ originalReplaceState.apply(this, [data, unused, url]);
85
+ if (!destroyed) setTimeout(trackCurrentPath, 0);
86
+ }
87
+ if (autoTrack) {
88
+ history.pushState = patchedPushState;
89
+ history.replaceState = patchedReplaceState;
90
+ window.addEventListener("popstate", trackCurrentPath);
91
+ if (document.readyState === "complete") {
92
+ trackCurrentPath();
93
+ } else {
94
+ window.addEventListener("load", trackCurrentPath, { once: true });
95
+ }
96
+ }
97
+ const isProduction = (() => {
98
+ try {
99
+ const hostname = location.hostname;
100
+ return hostname !== "localhost" && hostname !== "127.0.0.1" && !hostname.endsWith(".local");
101
+ } catch {
102
+ return true;
103
+ }
104
+ })();
105
+ const warnedReasons = /* @__PURE__ */ new Set();
106
+ function devWarn(name, reason) {
107
+ if (isProduction) return;
108
+ if (warnedReasons.has(reason)) return;
109
+ warnedReasons.add(reason);
110
+ console.warn(`[01 analytics] dropped event ${name}: ${reason}`);
111
+ }
112
+ const EVENT_NAME_RE = /^[a-zA-Z][a-zA-Z0-9_:-]{0,49}$/;
113
+ const RESERVED_PREFIXES = ["__", "_pv_"];
114
+ function validateEventName(name) {
115
+ if (!name || typeof name !== "string") return "name-empty";
116
+ for (const prefix of RESERVED_PREFIXES) {
117
+ if (name.startsWith(prefix)) return "name-reserved";
118
+ }
119
+ if (!EVENT_NAME_RE.test(name)) return "name-regex";
120
+ return null;
121
+ }
122
+ const PROP_KEY_RE = /^[a-zA-Z_][a-zA-Z0-9_]{0,31}$/;
123
+ function validateEventProps(props) {
124
+ if (props === void 0 || props === null) return null;
125
+ if (typeof props !== "object" || Array.isArray(props)) return "props-value-type";
126
+ const keys = Object.keys(props);
127
+ if (keys.length > 10) return "props-too-many-keys";
128
+ for (const k of keys) {
129
+ const v = props[k];
130
+ if (!PROP_KEY_RE.test(k)) return "props-key-regex";
131
+ if (typeof v === "string") {
132
+ if (v.length > 80) return "props-value-too-long";
133
+ } else if (typeof v === "number") {
134
+ if (!isFinite(v)) return "props-value-not-finite";
135
+ } else if (typeof v === "boolean") {
136
+ } else {
137
+ return "props-value-type";
138
+ }
139
+ }
140
+ return null;
141
+ }
142
+ return {
143
+ pageview(path) {
144
+ if (destroyed) return;
145
+ sendPageview(path ?? location.pathname);
146
+ },
147
+ track(name, props) {
148
+ if (destroyed) return;
149
+ if (isDntActive()) return;
150
+ const doc = document;
151
+ if (doc.prerendering === true || document.visibilityState === "prerender") return;
152
+ const nameErr = validateEventName(name);
153
+ if (nameErr) {
154
+ devWarn(name, nameErr);
155
+ return;
156
+ }
157
+ if (props !== void 0) {
158
+ const propsErr = validateEventProps(props);
159
+ if (propsErr) {
160
+ devWarn(name, propsErr);
161
+ return;
162
+ }
163
+ }
164
+ const body = JSON.stringify({
165
+ publishableKey: config.publishableKey,
166
+ pathname: location.pathname,
167
+ referrer: document.referrer || "",
168
+ eventId: newEventId(),
169
+ eventName: name,
170
+ eventProps: props,
171
+ eventTs: Date.now()
172
+ });
173
+ sendBeaconOrFetch(body);
174
+ },
175
+ destroy() {
176
+ if (destroyed) return;
177
+ destroyed = true;
178
+ if (autoTrack) {
179
+ history.pushState = originalPushState;
180
+ history.replaceState = originalReplaceState;
181
+ window.removeEventListener("popstate", trackCurrentPath);
182
+ }
183
+ lastPath = null;
184
+ lastAt = 0;
185
+ }
186
+ };
187
+ }
188
+
189
+ // src/analytics/env.ts
190
+ function readAnalyticsRuntimeEnv() {
191
+ const nextPublicKey = typeof process !== "undefined" ? process.env?.NEXT_PUBLIC_SOFTWARE_PUBLISHABLE_KEY : void 0;
192
+ const viteEnv = import.meta.env;
193
+ return {
194
+ nextPublicKey,
195
+ vitePublicKey: viteEnv?.VITE_SOFTWARE_PUBLISHABLE_KEY
196
+ };
197
+ }
198
+ function resolveAnalyticsPublishableKey(explicit, env = readAnalyticsRuntimeEnv()) {
199
+ if (explicit !== void 0) return explicit || void 0;
200
+ return env.nextPublicKey || env.vitePublicKey;
201
+ }
202
+
203
+ // src/analytics/react.tsx
204
+ function Analytics({
205
+ autoTrack,
206
+ endpoint,
207
+ publishableKey,
208
+ respectDnt
209
+ }) {
210
+ useEffect(() => {
211
+ const resolvedPublishableKey = resolveAnalyticsPublishableKey(publishableKey);
212
+ if (!resolvedPublishableKey) return;
213
+ const analytics = createAnalytics({
214
+ autoTrack,
215
+ endpoint,
216
+ publishableKey: resolvedPublishableKey,
217
+ respectDnt
218
+ });
219
+ return () => analytics.destroy();
220
+ }, [autoTrack, endpoint, publishableKey, respectDnt]);
221
+ return null;
222
+ }
223
+ var react_default = Analytics;
224
+ export {
225
+ Analytics,
226
+ react_default as default
227
+ };
228
+ //# sourceMappingURL=react.js.map