@01.software/sdk 0.36.0 → 0.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +142 -55
- package/dist/analytics/react.cjs +33 -9
- package/dist/analytics/react.cjs.map +1 -1
- package/dist/analytics/react.d.cts +1 -1
- package/dist/analytics/react.d.ts +1 -1
- package/dist/analytics/react.js +33 -9
- package/dist/analytics/react.js.map +1 -1
- package/dist/analytics.cjs +30 -8
- package/dist/analytics.cjs.map +1 -1
- package/dist/analytics.d.cts +6 -0
- package/dist/analytics.d.ts +6 -0
- package/dist/analytics.js +30 -8
- package/dist/analytics.js.map +1 -1
- package/dist/client.cjs +150 -207
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +6 -6
- package/dist/client.d.ts +6 -6
- package/dist/client.js +150 -207
- package/dist/client.js.map +1 -1
- package/dist/{collection-client-Cv0D2w1Q.d.cts → collection-client-B0J9wMNE.d.cts} +5 -5
- package/dist/{collection-client-Bq5Zd7p7.d.ts → collection-client-BroIWHY1.d.ts} +5 -5
- package/dist/const-6XHz_jej.d.ts +32 -0
- package/dist/const-B5KT72c7.d.cts +32 -0
- package/dist/{image-BDz2-AaO.d.cts → image-BDjHp03R.d.cts} +13 -9
- package/dist/{image-BDz2-AaO.d.ts → image-BDjHp03R.d.ts} +13 -9
- package/dist/{index-DTSXUYkr.d.ts → index-BOLQxveo.d.cts} +9 -6
- package/dist/{index-BHDKJ6B3.d.cts → index-CSwR2HSg.d.ts} +9 -6
- package/dist/index.cjs +256 -244
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -11
- package/dist/index.d.ts +12 -11
- package/dist/index.js +256 -244
- package/dist/index.js.map +1 -1
- package/dist/metadata.cjs +5 -3
- package/dist/metadata.cjs.map +1 -1
- package/dist/metadata.js +5 -3
- package/dist/metadata.js.map +1 -1
- package/dist/{payload-types-BCui2Oml.d.cts → payload-types-m3jjhxk9.d.cts} +669 -184
- package/dist/{payload-types-BCui2Oml.d.ts → payload-types-m3jjhxk9.d.ts} +669 -184
- package/dist/query.cjs +3 -1033
- package/dist/query.cjs.map +1 -1
- package/dist/query.d.cts +13 -13
- package/dist/query.d.ts +13 -13
- package/dist/query.js +3 -1033
- package/dist/query.js.map +1 -1
- package/dist/realtime.d.cts +2 -2
- package/dist/realtime.d.ts +2 -2
- package/dist/server.cjs +121 -85
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +7 -7
- package/dist/server.d.ts +7 -7
- package/dist/server.js +121 -85
- package/dist/server.js.map +1 -1
- package/dist/{types-Dib-zdK6.d.cts → types-CIGscmus.d.cts} +1471 -1100
- package/dist/{types-3qV6sY7T.d.ts → types-Cmrd1ezc.d.ts} +1 -15
- package/dist/{types-CEzLf3PX.d.cts → types-D0ubzQw0.d.cts} +1 -15
- package/dist/{types-DK9EnLwJ.d.ts → types-D2xYdz4P.d.ts} +1471 -1100
- package/dist/ui/canvas.cjs +15 -5
- package/dist/ui/canvas.cjs.map +1 -1
- package/dist/ui/canvas.d.cts +1 -1
- package/dist/ui/canvas.d.ts +1 -1
- package/dist/ui/canvas.js +15 -5
- package/dist/ui/canvas.js.map +1 -1
- package/dist/ui/form.d.cts +1 -1
- package/dist/ui/form.d.ts +1 -1
- package/dist/ui/image.cjs +15 -5
- package/dist/ui/image.cjs.map +1 -1
- package/dist/ui/image.d.cts +1 -1
- package/dist/ui/image.d.ts +1 -1
- package/dist/ui/image.js +15 -5
- package/dist/ui/image.js.map +1 -1
- package/dist/ui/video.d.cts +1 -1
- package/dist/ui/video.d.ts +1 -1
- package/dist/webhook.cjs +5 -1
- package/dist/webhook.cjs.map +1 -1
- package/dist/webhook.d.cts +4 -4
- package/dist/webhook.d.ts +4 -4
- package/dist/webhook.js +5 -1
- package/dist/webhook.js.map +1 -1
- package/package.json +3 -3
- package/dist/const-BDUKFP9w.d.ts +0 -32
- package/dist/const-DVcM7Ac_.d.cts +0 -32
package/dist/analytics.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/client/types.ts","../src/analytics.ts"],"sourcesContent":["import type {\n Collection,\n PublicCollection,\n ServerCollection,\n ServerOnlyCollection,\n} from '../collection/const'\nimport type { CollectionType } from '../collection/types'\nimport type { CommunityClient } from '../community/community-client'\nimport type {\n BanCustomerParams,\n CommunityBan,\n UnbanCustomerParams,\n} from '../community/moderation-api'\nimport type { CommerceClient } from '../commerce/commerce-client'\nimport type { ServerCommerceClient } from '../commerce/server-commerce-client'\nimport type { CustomerNamespace } from '../customer/customer-namespace'\nimport type { EventsClient } from '../events/events-client'\nimport type { TenantIntrospectionClient } from '../api/tenant-introspection-api'\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(apiUrl?: string): string {\n if (apiUrl) {\n return apiUrl.replace(/\\/$/, '')\n }\n\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 /** API base URL for staging, self-hosted, preview, or proxy deployments. */\n apiUrl?: 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\nexport type Sort = string | string[]\nexport type Where = Record<string, unknown>\n\n/**\n * Do NOT replace with `Pick<FindOptions>` from `payload` or import Payload\n * types here. Payload's generic query types depend on `PayloadTypes` module\n * augmentation; external SDK consumers who only use `createClient` should not\n * install Payload just to type REST query objects. Excluded vs native:\n * Local-API-only fields, `locale`/`fallbackLocale`.\n */\nexport interface ApiQueryOptions {\n page?: number\n limit?: number\n sort?: Sort\n /**\n * Filter documents. Id-based relation filters (`where: { product: { equals: id } }`) are the\n * most reliable pattern. Dotted-path relation filters (`where: { 'product.slug': { equals } }`)\n * are Payload-native but may silently return empty when access control restricts the related\n * document or when the relation is polymorphic. String shorthand (`where: { slug: 'x' }`)\n * silently matches nothing — always use `{ slug: { equals: 'x' } }`.\n */\n where?: Where\n /**\n * Controls how deeply relationship fields are populated. This is the primary control for\n * populating relationships like `category`, `images`, `brand`. The configured Payload default\n * applies when unset.\n */\n depth?: number\n select?: Record<string, boolean>\n /**\n * Controls which fields are returned for already-populated relationships, keyed by collection\n * slug. Does NOT control which relationships to populate — that is `depth`.\n *\n * @example\n * // depth: 2 populates category; populate trims which fields come back\n * populate: { categories: { title: true, slug: true } }\n */\n populate?: Record<string, boolean | Record<string, boolean>>\n /**\n * Controls Payload `type: 'join'` virtual reverse-relation fields only (pagination, sort,\n * filter, count per join field, or `false` to disable all join-field population).\n *\n * Does NOT populate normal relationship fields like `category`, `images`, or `brand`.\n * For normal relationship population use `depth` (and optionally `populate` for field\n * selection).\n *\n * Pass `joins: false` to disable all join-field population — useful for lightweight list\n * queries where join fields are not needed.\n *\n * @example\n * // `article-authors` has a `type: 'join'` field `articles` (reverse-relation)\n * joins: { articles: { limit: 10, sort: '-publishedAt' } }\n *\n * // depth: 2 populates product.category — joins has no effect on this\n * depth: 2\n *\n * // Disable all join-field population\n * joins: false\n */\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// Lightweight root entry contracts\n// ============================================================================\n\ninterface RootQueryLookup<T extends string> {\n find(\n options?: ApiQueryOptions,\n ): Promise<PayloadFindResponse<CollectionType<T>>>\n findById(\n id: string | number,\n options?: ApiQueryOptions,\n ): Promise<CollectionType<T>>\n count(options?: ApiQueryOptions): Promise<{ totalDocs: number }>\n}\n\nexport type RootReadOnlyQueryBuilder<T extends PublicCollection> =\n RootQueryLookup<T>\n\nexport interface RootServerQueryBuilder<\n T extends ServerCollection,\n> extends RootQueryLookup<T> {\n create(\n data: Partial<CollectionType<T>>,\n options?: { file?: File | Blob; filename?: string },\n ): Promise<PayloadMutationResponse<CollectionType<T>>>\n update(\n id: string,\n data: Partial<CollectionType<T>>,\n options?: { file?: File | Blob; filename?: string },\n ): Promise<PayloadMutationResponse<CollectionType<T>>>\n updateMany(\n where: ApiQueryOptions['where'],\n data: Partial<CollectionType<T>>,\n ): Promise<PayloadFindResponse<CollectionType<T>>>\n remove(id: string): Promise<CollectionType<T>>\n removeMany(\n where: ApiQueryOptions['where'],\n ): Promise<PayloadFindResponse<CollectionType<T>>>\n}\n\nexport interface RootCollectionClient {\n from<T extends PublicCollection>(collection: T): RootReadOnlyQueryBuilder<T>\n}\n\nexport interface RootServerCollectionClient {\n from<T extends ServerCollection>(collection: T): RootServerQueryBuilder<T>\n}\n\nexport interface RootClient {\n commerce: CommerceClient\n community: CommunityClient\n /** Set on {@link createClient} return values; optional for structural mocks. */\n events?: EventsClient\n customer: CustomerNamespace\n collections: RootCollectionClient\n lastRequestId: string | null\n getState(): ClientState\n getConfig(): ClientConfig\n}\n\nexport interface RootServerClient {\n commerce: ServerCommerceClient\n tenant: TenantIntrospectionClient\n /** Set on {@link createServerClient} return values; optional for structural mocks. */\n events?: EventsClient\n community: CommunityClient & {\n moderation: {\n banCustomer: (p: BanCustomerParams) => Promise<CommunityBan>\n unbanCustomer: (p: UnbanCustomerParams) => Promise<{ success: true }>\n }\n }\n collections: RootServerCollectionClient\n lastRequestId: string | null\n getState(): ClientState\n getConfig(): Omit<ClientServerConfig, 'secretKey'>\n}\n\nexport type RootClientWithEvents = RootClient & { events: EventsClient }\n\nexport type RootServerClientWithEvents = RootServerClient & { events: EventsClient }\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"],"mappings":";AAgCO,SAAS,cAAc,QAAyB;AACrD,MAAI,QAAQ;AACV,WAAO,OAAO,QAAQ,OAAO,EAAE;AAAA,EACjC;AAEA,MAAI,OAAO,YAAY,eAAe,QAAQ,KAAK;AACjD,UAAM,SACJ,QAAQ,IAAI,oBAAoB,QAAQ,IAAI;AAC9C,QAAI,QAAQ;AACV,aAAO,OAAO,QAAQ,OAAO,EAAE;AAAA,IACjC;AAAA,EACF;AACA,SAAO;AACT;;;AC2BO,SAAS,gBAAgB,QAAoC;AAElE,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,EAAE,WAAW;AAAA,IAAC,GAAG,QAAQ;AAAA,IAAC,GAAG,UAAU;AAAA,IAAC,EAAE;AAAA,EACnD;AAEA,QAAM,WACJ,OAAO,YAAY,GAAG,cAAc,CAAC;AAGvC,QAAM,aAAa,OAAO,eAAe;AACzC,WAAS,cAAuB;AAC9B,QAAI,CAAC,WAAY,QAAO;AACxB,UAAM,MAAM;AACZ,WAAO,IAAI,eAAe,OAAO,IAAI,yBAAyB;AAAA,EAChE;AAGA,MAAI,WAA0B;AAC9B,MAAI,SAAS;AAGb,QAAM,YAAY,OAAO,cAAc;AACvC,QAAM,oBAAoB,QAAQ;AAClC,QAAM,uBAAuB,QAAQ;AACrC,MAAI,YAAY;AAOhB,WAAS,aAAqB;AAC5B,WAAO,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,aACjE,OAAO,WAAW,IAClB,OAAO,KAAK,IAAI,CAAC,IAAI,OAAO,KAAK,OAAO,CAAC;AAAA,EAC/C;AAIA,WAAS,kBAAkB,MAAoB;AAC7C,QAAI;AACF,UAAI,OAAO,UAAU,eAAe,YAAY;AAC9C,cAAM,OAAO,IAAI,KAAK,CAAC,IAAI,GAAG,EAAE,MAAM,aAAa,CAAC;AACpD,cAAM,OAAO,UAAU,WAAW,UAAU,IAAI;AAChD,YAAI,KAAM;AAAA,MAEZ;AAEA,YAAM,UAAU;AAAA,QACd,QAAQ;AAAA,QACR,WAAW;AAAA,QACX,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C;AAAA,MACF,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACnB,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,WAAS,aAAa,UAAwB;AAE5C,QAAI,YAAY,EAAG;AAGnB,UAAM,MAAM;AAEZ,QAAI,IAAI,iBAAiB,QAAS,SAAS,oBAA+B,YAAa;AAGvF,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,aAAa,YAAY,MAAM,SAAS,IAAK;AACjD,eAAW;AACX,aAAS;AAET,UAAM,OAAO,KAAK,UAAU;AAAA,MAC1B,gBAAgB,OAAO;AAAA,MACvB;AAAA,MACA,UAAU,SAAS,YAAY;AAAA,MAC/B,SAAS,WAAW;AAAA,MACpB,SAAS,KAAK,IAAI;AAAA,IACpB,CAAC;AAED,sBAAkB,IAAI;AAAA,EACxB;AAKA,WAAS,mBAAyB;AAChC,QAAI,UAAW;AACf,iBAAa,SAAS,QAAQ;AAAA,EAChC;AAEA,WAAS,iBAEP,MACA,QACA,KACM;AACN,sBAAkB,MAAM,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAyC;AACzF,QAAI,CAAC,UAAW,YAAW,kBAAkB,CAAC;AAAA,EAChD;AAEA,WAAS,oBAEP,MACA,QACA,KACM;AACN,yBAAqB,MAAM,MAAM,CAAC,MAAM,QAAQ,GAAG,CAA4C;AAC/F,QAAI,CAAC,UAAW,YAAW,kBAAkB,CAAC;AAAA,EAChD;AAEA,MAAI,WAAW;AACb,YAAQ,YAAY;AACpB,YAAQ,eAAe;AACvB,WAAO,iBAAiB,YAAY,gBAAgB;AAGpD,QAAI,SAAS,eAAe,YAAY;AACtC,uBAAiB;AAAA,IACnB,OAAO;AACL,aAAO,iBAAiB,QAAQ,kBAAkB,EAAE,MAAM,KAAK,CAAC;AAAA,IAClE;AAAA,EACF;AAWA,QAAM,gBAAyB,MAAM;AACnC,QAAI;AACF,YAAM,WAAW,SAAS;AAC1B,aACE,aAAa,eACb,aAAa,eACb,CAAC,SAAS,SAAS,QAAQ;AAAA,IAE/B,QAAQ;AAEN,aAAO;AAAA,IACT;AAAA,EACF,GAAG;AAGH,QAAM,gBAAgB,oBAAI,IAAY;AAEtC,WAAS,QAAQ,MAAc,QAAsB;AACnD,QAAI,aAAc;AAClB,QAAI,cAAc,IAAI,MAAM,EAAG;AAC/B,kBAAc,IAAI,MAAM;AACxB,YAAQ,KAAK,gCAAgC,IAAI,KAAK,MAAM,EAAE;AAAA,EAChE;AAEA,QAAM,gBAAgB;AACtB,QAAM,oBAAoB,CAAC,MAAM,MAAM;AAEvC,WAAS,kBAAkB,MAA6B;AACtD,QAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,eAAW,UAAU,mBAAmB;AACtC,UAAI,KAAK,WAAW,MAAM,EAAG,QAAO;AAAA,IACtC;AACA,QAAI,CAAC,cAAc,KAAK,IAAI,EAAG,QAAO;AACtC,WAAO;AAAA,EACT;AAEA,QAAM,cAAc;AAEpB,WAAS,mBACP,OACe;AACf,QAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,QAAI,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,EAAG,QAAO;AAC9D,UAAM,OAAO,OAAO,KAAK,KAAK;AAC9B,QAAI,KAAK,SAAS,GAAI,QAAO;AAC7B,eAAW,KAAK,MAAM;AACpB,YAAM,IAAI,MAAM,CAAC;AACjB,UAAI,CAAC,YAAY,KAAK,CAAC,EAAG,QAAO;AACjC,UAAI,OAAO,MAAM,UAAU;AACzB,YAAI,EAAE,SAAS,GAAI,QAAO;AAAA,MAC5B,WAAW,OAAO,MAAM,UAAU;AAChC,YAAI,CAAC,SAAS,CAAC,EAAG,QAAO;AAAA,MAC3B,WAAW,OAAO,MAAM,WAAW;AAAA,MAEnC,OAAO;AACL,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAKA,SAAO;AAAA,IACL,SAAS,MAAqB;AAC5B,UAAI,UAAW;AACf,mBAAa,QAAQ,SAAS,QAAQ;AAAA,IACxC;AAAA,IAEA,MAAM,MAAc,OAAyD;AAC3E,UAAI,UAAW;AAGf,UAAI,YAAY,EAAG;AAGnB,YAAM,MAAM;AACZ,UAAI,IAAI,iBAAiB,QAAS,SAAS,oBAA+B,YAAa;AAGvF,YAAM,UAAU,kBAAkB,IAAI;AACtC,UAAI,SAAS;AACX,gBAAQ,MAAM,OAAO;AACrB;AAAA,MACF;AAEA,UAAI,UAAU,QAAW;AACvB,cAAM,WAAW,mBAAmB,KAAK;AACzC,YAAI,UAAU;AACZ,kBAAQ,MAAM,QAAQ;AACtB;AAAA,QACF;AAAA,MACF;AAGA,YAAM,OAAO,KAAK,UAAU;AAAA,QAC1B,gBAAgB,OAAO;AAAA,QACvB,UAAU,SAAS;AAAA,QACnB,UAAU,SAAS,YAAY;AAAA,QAC/B,SAAS,WAAW;AAAA,QACpB,WAAW;AAAA,QACX,YAAY;AAAA,QACZ,SAAS,KAAK,IAAI;AAAA,MACpB,CAAC;AAED,wBAAkB,IAAI;AAAA,IACxB;AAAA,IAEA,UAAgB;AACd,UAAI,UAAW;AACf,kBAAY;AAEZ,UAAI,WAAW;AAEb,gBAAQ,YAAY;AACpB,gBAAQ,eAAe;AACvB,eAAO,oBAAoB,YAAY,gBAAgB;AAAA,MACzD;AAGA,iBAAW;AACX,eAAS;AAAA,IACX;AAAA,EACF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/core/client/types.ts","../src/analytics.ts"],"sourcesContent":["import type {\n Collection,\n PublicCollection,\n ServerCollection,\n ServerOnlyCollection,\n} from '../collection/const'\nimport type { CollectionType } from '../collection/types'\nimport type { CommunityClient } from '../community/community-client'\nimport type {\n BanCustomerParams,\n CommunityBan,\n UnbanCustomerParams,\n} from '../community/moderation-api'\nimport type { CommerceClient } from '../commerce/commerce-client'\nimport type { ServerCommerceClient } from '../commerce/server-commerce-client'\nimport type { CustomerNamespace } from '../customer/customer-namespace'\nimport type { EventsClient } from '../events/events-client'\nimport type { TenantIntrospectionClient } from '../api/tenant-introspection-api'\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(apiUrl?: string): string {\n if (apiUrl) {\n return apiUrl.replace(/\\/$/, '')\n }\n\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 /** API base URL for staging, self-hosted, preview, or proxy deployments. */\n apiUrl?: 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\nexport type Sort = string | string[]\nexport type Where = Record<string, unknown>\n\n/**\n * Do NOT replace with `Pick<FindOptions>` from `payload` or import Payload\n * types here. Payload's generic query types depend on `PayloadTypes` module\n * augmentation; external SDK consumers who only use `createClient` should not\n * install Payload just to type REST query objects. Excluded vs native:\n * Local-API-only fields, `locale`/`fallbackLocale`.\n */\nexport interface ApiQueryOptions {\n page?: number\n limit?: number\n sort?: Sort\n /**\n * Filter documents. Id-based relation filters (`where: { product: { equals: id } }`) are the\n * most reliable pattern. Dotted-path relation filters (`where: { 'product.slug': { equals } }`)\n * are Payload-native but may silently return empty when access control restricts the related\n * document or when the relation is polymorphic. String shorthand (`where: { slug: 'x' }`)\n * silently matches nothing — always use `{ slug: { equals: 'x' } }`.\n */\n where?: Where\n /**\n * Controls how deeply relationship fields are populated. This is the primary control for\n * populating relationships like `category`, `images`, `brand`. The configured Payload default\n * applies when unset.\n */\n depth?: number\n select?: Record<string, boolean>\n /**\n * Controls which fields are returned for already-populated relationships, keyed by collection\n * slug. Does NOT control which relationships to populate — that is `depth`.\n *\n * @example\n * // depth: 2 populates category; populate trims which fields come back\n * populate: { categories: { title: true, slug: true } }\n */\n populate?: Record<string, boolean | Record<string, boolean>>\n /**\n * Controls Payload `type: 'join'` virtual reverse-relation fields only (pagination, sort,\n * filter, count per join field, or `false` to disable all join-field population).\n *\n * Does NOT populate normal relationship fields like `category`, `images`, or `brand`.\n * For normal relationship population use `depth` (and optionally `populate` for field\n * selection).\n *\n * Pass `joins: false` to disable all join-field population — useful for lightweight list\n * queries where join fields are not needed.\n *\n * @example\n * // `article-authors` has a `type: 'join'` field `articles` (reverse-relation)\n * joins: { articles: { limit: 10, sort: '-publishedAt' } }\n *\n * // depth: 2 populates product.category — joins has no effect on this\n * depth: 2\n *\n * // Disable all join-field population\n * joins: false\n */\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// Lightweight root entry contracts\n// ============================================================================\n\ninterface RootQueryLookup<T extends string> {\n find(\n options?: ApiQueryOptions,\n ): Promise<PayloadFindResponse<CollectionType<T>>>\n findById(\n id: string | number,\n options?: ApiQueryOptions,\n ): Promise<CollectionType<T>>\n count(options?: ApiQueryOptions): Promise<{ totalDocs: number }>\n}\n\nexport type RootReadOnlyQueryBuilder<T extends PublicCollection> =\n RootQueryLookup<T>\n\nexport interface RootServerQueryBuilder<\n T extends ServerCollection,\n> extends RootQueryLookup<T> {\n create(\n data: Partial<CollectionType<T>>,\n options?: { file?: File | Blob; filename?: string },\n ): Promise<PayloadMutationResponse<CollectionType<T>>>\n update(\n id: string,\n data: Partial<CollectionType<T>>,\n options?: { file?: File | Blob; filename?: string },\n ): Promise<PayloadMutationResponse<CollectionType<T>>>\n updateMany(\n where: ApiQueryOptions['where'],\n data: Partial<CollectionType<T>>,\n ): Promise<PayloadFindResponse<CollectionType<T>>>\n remove(id: string): Promise<CollectionType<T>>\n removeMany(\n where: ApiQueryOptions['where'],\n ): Promise<PayloadFindResponse<CollectionType<T>>>\n}\n\nexport interface RootCollectionClient {\n from<T extends PublicCollection>(collection: T): RootReadOnlyQueryBuilder<T>\n}\n\nexport interface RootServerCollectionClient {\n from<T extends ServerCollection>(collection: T): RootServerQueryBuilder<T>\n}\n\nexport interface RootClient {\n commerce: CommerceClient\n community: CommunityClient\n /** Set on {@link createClient} return values; optional for structural mocks. */\n events?: EventsClient\n customer: CustomerNamespace\n collections: RootCollectionClient\n lastRequestId: string | null\n getState(): ClientState\n getConfig(): ClientConfig\n}\n\nexport interface RootServerClient {\n commerce: ServerCommerceClient\n tenant: TenantIntrospectionClient\n /** Set on {@link createServerClient} return values; optional for structural mocks. */\n events?: EventsClient\n community: CommunityClient & {\n moderation: {\n banCustomer: (p: BanCustomerParams) => Promise<CommunityBan>\n unbanCustomer: (p: UnbanCustomerParams) => Promise<{ success: true }>\n }\n }\n collections: RootServerCollectionClient\n lastRequestId: string | null\n getState(): ClientState\n getConfig(): Omit<ClientServerConfig, 'secretKey'>\n}\n\nexport type RootClientWithEvents = RootClient & { events: EventsClient }\n\nexport type RootServerClientWithEvents = RootServerClient & { events: EventsClient }\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 *\n * 9. Dev / local-host send skip: in 'auto' mode (the default) all send paths\n * become no-ops when the page runs on a local host — location.hostname is\n * 'localhost', '127.0.0.1', or ends with '.local'. Validation and dev\n * warnings still run; only the network send is suppressed, and a one-time\n * console notice is emitted. SSR (6), DNT/GPC (1), and prerender (2) skips\n * are evaluated first. Overrides: the SDK accepts mode 'production' (always\n * send, even locally) and 'development' (never send, on any host); the\n * mirrored hosted snippet accepts captureOnLocalhost === true (via\n * window.__01_analytics__.captureOnLocalhost or the script's\n * data-capture-localhost attribute) to force sending on a local host.\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 * Send mode. 'auto' (default) skips all sends on local hosts\n * (localhost / 127.0.0.1 / *.local). 'production' always sends, even\n * locally. 'development' never sends, on any host. See INVARIANT 9.\n */\n mode?: 'auto' | 'production' | 'development'\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 9: local-host detection drives both dev warnings and the\n // auto send-skip. Hostname is the one signal shared with the hosted snippet.\n const isLocalHost: boolean = (() => {\n try {\n const h = location.hostname\n return h === 'localhost' || h === '127.0.0.1' || h.endsWith('.local')\n } catch {\n return false\n }\n })()\n\n const mode = config.mode ?? 'auto'\n const sendSuppressed =\n mode === 'development' || (mode === 'auto' && isLocalHost)\n\n let suppressNoticeShown = false\n function devSuppressNotice(): void {\n if (suppressNoticeShown) return\n suppressNoticeShown = true\n try {\n console.info(\n `[01 analytics] events disabled on local host (mode: '${mode}'). ` +\n `Pass mode: 'production' to send while developing.`,\n )\n } catch {\n // INVARIANT 7: swallow\n }\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 9: skip sends on local hosts in auto mode\n if (sendSuppressed) {\n devSuppressNotice()\n return\n }\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 for warnings: warn in dev, silent in production.\n // Reuses the same local-host signal as the send-skip (INVARIANT 9).\n const isProduction = !isLocalHost\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 // INVARIANT 9: skip sends on local hosts in auto mode (validation above\n // still runs so dev warnings fire)\n if (sendSuppressed) {\n devSuppressNotice()\n return\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"],"mappings":"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","names":[]}
|
package/dist/client.cjs
CHANGED
|
@@ -771,71 +771,14 @@ var HttpClient = class {
|
|
|
771
771
|
}
|
|
772
772
|
};
|
|
773
773
|
|
|
774
|
-
// src/
|
|
775
|
-
|
|
776
|
-
if (typeof ref === "string" || typeof ref === "number" || ref === null || ref === void 0)
|
|
777
|
-
return null;
|
|
778
|
-
return ref;
|
|
779
|
-
};
|
|
780
|
-
|
|
781
|
-
// src/core/metadata/index.ts
|
|
782
|
-
function extractSeo(doc) {
|
|
783
|
-
const seo = doc.seo ?? {};
|
|
784
|
-
const og = seo.openGraph ?? {};
|
|
785
|
-
return {
|
|
786
|
-
title: seo.title ?? doc.title ?? null,
|
|
787
|
-
description: seo.description ?? null,
|
|
788
|
-
noIndex: seo.noIndex ?? null,
|
|
789
|
-
canonical: seo.canonical ?? null,
|
|
790
|
-
openGraph: {
|
|
791
|
-
title: og.title ?? null,
|
|
792
|
-
description: og.description ?? null,
|
|
793
|
-
image: og.image ?? null
|
|
794
|
-
}
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
function generateMetadata(input, options) {
|
|
798
|
-
const title = input.title ?? void 0;
|
|
799
|
-
const description = input.description ?? void 0;
|
|
800
|
-
const ogTitle = input.openGraph?.title ?? title;
|
|
801
|
-
const ogDescription = input.openGraph?.description ?? description;
|
|
802
|
-
const image = resolveMetaImage(input.openGraph?.image);
|
|
803
|
-
return {
|
|
804
|
-
title,
|
|
805
|
-
description,
|
|
806
|
-
...input.noIndex && { robots: { index: false, follow: false } },
|
|
807
|
-
...input.canonical && { alternates: { canonical: input.canonical } },
|
|
808
|
-
openGraph: {
|
|
809
|
-
...ogTitle && { title: ogTitle },
|
|
810
|
-
...ogDescription && { description: ogDescription },
|
|
811
|
-
...options?.siteName && { siteName: options.siteName },
|
|
812
|
-
...image && { images: [image] }
|
|
813
|
-
},
|
|
814
|
-
twitter: {
|
|
815
|
-
card: image ? "summary_large_image" : "summary",
|
|
816
|
-
...ogTitle && { title: ogTitle },
|
|
817
|
-
...ogDescription && { description: ogDescription },
|
|
818
|
-
...image && { images: [image.url] }
|
|
819
|
-
}
|
|
820
|
-
};
|
|
821
|
-
}
|
|
822
|
-
function resolveMetaImage(ref) {
|
|
823
|
-
const image = resolveRelation(ref);
|
|
824
|
-
if (!image) return null;
|
|
825
|
-
const sized = image.sizes?.["1536"];
|
|
826
|
-
const url = sized?.url || image.url;
|
|
827
|
-
if (!url) return null;
|
|
828
|
-
const width = sized?.url ? sized.width : image.width;
|
|
829
|
-
const height = sized?.url ? sized.height : image.height;
|
|
774
|
+
// src/core/collection/query-builder.ts
|
|
775
|
+
function withDefaultPublicReadOptions(options) {
|
|
830
776
|
return {
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
...image.alt && { alt: image.alt }
|
|
777
|
+
...options,
|
|
778
|
+
depth: options?.depth ?? 0,
|
|
779
|
+
joins: options?.joins ?? false
|
|
835
780
|
};
|
|
836
781
|
}
|
|
837
|
-
|
|
838
|
-
// src/core/collection/query-builder.ts
|
|
839
782
|
var ReadOnlyCollectionQueryBuilder = class {
|
|
840
783
|
constructor(api, collection) {
|
|
841
784
|
this.api = api;
|
|
@@ -844,13 +787,13 @@ var ReadOnlyCollectionQueryBuilder = class {
|
|
|
844
787
|
async find(options) {
|
|
845
788
|
return this.api.requestFind(
|
|
846
789
|
`/api/${String(this.collection)}`,
|
|
847
|
-
options
|
|
790
|
+
withDefaultPublicReadOptions(options)
|
|
848
791
|
);
|
|
849
792
|
}
|
|
850
793
|
async findById(id, options) {
|
|
851
794
|
return this.api.requestFindById(
|
|
852
795
|
`/api/${String(this.collection)}/${String(id)}`,
|
|
853
|
-
options
|
|
796
|
+
withDefaultPublicReadOptions(options)
|
|
854
797
|
);
|
|
855
798
|
}
|
|
856
799
|
async count(options) {
|
|
@@ -859,22 +802,6 @@ var ReadOnlyCollectionQueryBuilder = class {
|
|
|
859
802
|
options
|
|
860
803
|
);
|
|
861
804
|
}
|
|
862
|
-
async findMetadata(options, metadataOptions) {
|
|
863
|
-
const { docs } = await this.find({ ...options, limit: 1, depth: 1 });
|
|
864
|
-
const doc = docs[0];
|
|
865
|
-
if (!doc) return null;
|
|
866
|
-
return generateMetadata(
|
|
867
|
-
extractSeo(doc),
|
|
868
|
-
metadataOptions
|
|
869
|
-
);
|
|
870
|
-
}
|
|
871
|
-
async findMetadataById(id, metadataOptions) {
|
|
872
|
-
const doc = await this.findById(id, { depth: 1 });
|
|
873
|
-
return generateMetadata(
|
|
874
|
-
extractSeo(doc),
|
|
875
|
-
metadataOptions
|
|
876
|
-
);
|
|
877
|
-
}
|
|
878
805
|
};
|
|
879
806
|
|
|
880
807
|
// src/core/collection/collection-client.ts
|
|
@@ -890,6 +817,21 @@ var ReadOnlyCollectionClient = class extends HttpClient {
|
|
|
890
817
|
});
|
|
891
818
|
return this.parseFindResponse(response);
|
|
892
819
|
}
|
|
820
|
+
async requestFindEndpoint(endpoint, data) {
|
|
821
|
+
const response = await this.fetchWithTracking(endpoint, {
|
|
822
|
+
...this.defaultOptions,
|
|
823
|
+
method: "POST",
|
|
824
|
+
body: data ? JSON.stringify(data) : void 0
|
|
825
|
+
});
|
|
826
|
+
return this.parseFindResponse(response);
|
|
827
|
+
}
|
|
828
|
+
async requestFindEndpointGet(endpoint) {
|
|
829
|
+
const response = await this.fetchWithTracking(endpoint, {
|
|
830
|
+
...this.defaultOptions,
|
|
831
|
+
method: "GET"
|
|
832
|
+
});
|
|
833
|
+
return this.parseFindResponse(response);
|
|
834
|
+
}
|
|
893
835
|
async requestFindById(endpoint, options) {
|
|
894
836
|
const url = this.buildUrl(endpoint, options);
|
|
895
837
|
const response = await this.fetchWithTracking(url, {
|
|
@@ -954,13 +896,16 @@ async function parseApiResponse(response, endpoint) {
|
|
|
954
896
|
return data;
|
|
955
897
|
}
|
|
956
898
|
|
|
957
|
-
// src/core/
|
|
958
|
-
var
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
899
|
+
// src/core/api/customer-scoped-api.ts
|
|
900
|
+
var CustomerScopedApi = class {
|
|
901
|
+
constructor(apiName, options) {
|
|
902
|
+
if (options.requiresCredential !== false && !options.secretKey && !options.customerToken) {
|
|
903
|
+
throw createConfigError(
|
|
904
|
+
`Either secretKey or customerToken is required for ${apiName}.`
|
|
905
|
+
);
|
|
906
|
+
}
|
|
962
907
|
this.publishableKey = requirePublishableKeyForSecret(
|
|
963
|
-
|
|
908
|
+
apiName,
|
|
964
909
|
options.publishableKey,
|
|
965
910
|
options.secretKey
|
|
966
911
|
);
|
|
@@ -970,6 +915,44 @@ var CommunityClient = class {
|
|
|
970
915
|
this.onUnauthorized = options.onUnauthorized;
|
|
971
916
|
this.onRequestId = options.onRequestId;
|
|
972
917
|
}
|
|
918
|
+
async request(endpoint, options) {
|
|
919
|
+
const token = typeof this.customerToken === "function" ? this.customerToken() : this.customerToken;
|
|
920
|
+
try {
|
|
921
|
+
const response = await httpFetch(endpoint, {
|
|
922
|
+
method: options.method,
|
|
923
|
+
apiUrl: this.apiUrl,
|
|
924
|
+
publishableKey: this.publishableKey,
|
|
925
|
+
secretKey: this.secretKey,
|
|
926
|
+
customerToken: token ?? void 0,
|
|
927
|
+
...token && this.onUnauthorized && { onUnauthorized: this.onUnauthorized },
|
|
928
|
+
...options.body !== void 0 && {
|
|
929
|
+
body: JSON.stringify(options.body)
|
|
930
|
+
},
|
|
931
|
+
...options.headers && { headers: options.headers }
|
|
932
|
+
});
|
|
933
|
+
this.onRequestId?.(response.headers.get("x-request-id") ?? null);
|
|
934
|
+
return parseApiResponse(response, endpoint);
|
|
935
|
+
} catch (err) {
|
|
936
|
+
const id = err instanceof SDKError ? err.requestId ?? null : null;
|
|
937
|
+
this.onRequestId?.(id);
|
|
938
|
+
throw err;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
// src/core/community/community-client.ts
|
|
944
|
+
var DEFAULT_POST_LIST_SORT = "-lastActivityAt";
|
|
945
|
+
var DEFAULT_COMMENT_LIST_SORT = "-createdAt";
|
|
946
|
+
function unwrapPayloadDoc(value) {
|
|
947
|
+
if (value && typeof value === "object" && "doc" in value) {
|
|
948
|
+
return value.doc;
|
|
949
|
+
}
|
|
950
|
+
return value;
|
|
951
|
+
}
|
|
952
|
+
var CommunityClient = class extends CustomerScopedApi {
|
|
953
|
+
constructor(options) {
|
|
954
|
+
super("CommunityClient", { ...options, requiresCredential: false });
|
|
955
|
+
}
|
|
973
956
|
buildQuery(params) {
|
|
974
957
|
if (!params) return "";
|
|
975
958
|
const entries = Object.entries(params).filter((e) => e[1] !== void 0).map(([k, v]) => [k, String(v)]);
|
|
@@ -979,7 +962,8 @@ var CommunityClient = class {
|
|
|
979
962
|
const urlParams = new URLSearchParams();
|
|
980
963
|
const sort = params?.sort ?? DEFAULT_POST_LIST_SORT;
|
|
981
964
|
urlParams.set("sort", sort);
|
|
982
|
-
if (params?.limit !== void 0)
|
|
965
|
+
if (params?.limit !== void 0)
|
|
966
|
+
urlParams.set("limit", String(params.limit));
|
|
983
967
|
if (params?.page !== void 0) urlParams.set("page", String(params.page));
|
|
984
968
|
if (params?.categoryId !== void 0) {
|
|
985
969
|
urlParams.set("where[categories][in]", params.categoryId);
|
|
@@ -987,7 +971,7 @@ var CommunityClient = class {
|
|
|
987
971
|
if (params?.tagId !== void 0) {
|
|
988
972
|
urlParams.set("where[tags][in]", params.tagId);
|
|
989
973
|
}
|
|
990
|
-
return `/api/posts?${urlParams.toString()}`;
|
|
974
|
+
return `/api/posts/public?${urlParams.toString()}`;
|
|
991
975
|
}
|
|
992
976
|
buildCommentsListQuery(params) {
|
|
993
977
|
const urlParams = new URLSearchParams();
|
|
@@ -1007,30 +991,21 @@ var CommunityClient = class {
|
|
|
1007
991
|
}
|
|
1008
992
|
if (params.limit !== void 0) urlParams.set("limit", String(params.limit));
|
|
1009
993
|
if (params.page !== void 0) urlParams.set("page", String(params.page));
|
|
1010
|
-
return `/api/comments?${urlParams.toString()}`;
|
|
994
|
+
return `/api/comments/public?${urlParams.toString()}`;
|
|
1011
995
|
}
|
|
1012
996
|
async execute(endpoint, method, body) {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
publishableKey: this.publishableKey,
|
|
1019
|
-
secretKey: this.secretKey,
|
|
1020
|
-
customerToken: token ?? void 0,
|
|
1021
|
-
...token && this.onUnauthorized && { onUnauthorized: this.onUnauthorized },
|
|
1022
|
-
...body !== void 0 && { body: JSON.stringify(body) }
|
|
1023
|
-
});
|
|
1024
|
-
this.onRequestId?.(response.headers.get("x-request-id") ?? null);
|
|
1025
|
-
return parseApiResponse(response, endpoint);
|
|
1026
|
-
} catch (err) {
|
|
1027
|
-
const id = err instanceof SDKError ? err.requestId ?? null : null;
|
|
1028
|
-
this.onRequestId?.(id);
|
|
1029
|
-
throw err;
|
|
1030
|
-
}
|
|
997
|
+
return this.request(endpoint, { method, body });
|
|
998
|
+
}
|
|
999
|
+
async executeDoc(endpoint, method, body) {
|
|
1000
|
+
const response = await this.execute(endpoint, method, body);
|
|
1001
|
+
return unwrapPayloadDoc(response);
|
|
1031
1002
|
}
|
|
1032
1003
|
createPost(params) {
|
|
1033
|
-
return this.
|
|
1004
|
+
return this.executeDoc(
|
|
1005
|
+
"/api/posts",
|
|
1006
|
+
"POST",
|
|
1007
|
+
params
|
|
1008
|
+
);
|
|
1034
1009
|
}
|
|
1035
1010
|
/**
|
|
1036
1011
|
* Public post feed. Server applies the same visibility contract as
|
|
@@ -1087,7 +1062,11 @@ var CommunityClient = class {
|
|
|
1087
1062
|
if (parentId !== void 0) {
|
|
1088
1063
|
body.parent = parentId;
|
|
1089
1064
|
}
|
|
1090
|
-
return this.
|
|
1065
|
+
return this.executeDoc(
|
|
1066
|
+
"/api/comments",
|
|
1067
|
+
"POST",
|
|
1068
|
+
body
|
|
1069
|
+
);
|
|
1091
1070
|
}
|
|
1092
1071
|
/**
|
|
1093
1072
|
* List comments for a post.
|
|
@@ -1125,7 +1104,7 @@ var CommunityClient = class {
|
|
|
1125
1104
|
}
|
|
1126
1105
|
updateComment(params) {
|
|
1127
1106
|
const { commentId, body } = params;
|
|
1128
|
-
return this.
|
|
1107
|
+
return this.executeDoc(
|
|
1129
1108
|
`/api/comments/${commentId}`,
|
|
1130
1109
|
"PATCH",
|
|
1131
1110
|
{ body }
|
|
@@ -1156,10 +1135,14 @@ var CommunityClient = class {
|
|
|
1156
1135
|
400
|
|
1157
1136
|
);
|
|
1158
1137
|
}
|
|
1159
|
-
return this.
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1138
|
+
return this.executeDoc(
|
|
1139
|
+
"/api/reactions",
|
|
1140
|
+
"POST",
|
|
1141
|
+
{
|
|
1142
|
+
post: postId,
|
|
1143
|
+
type: reactionType
|
|
1144
|
+
}
|
|
1145
|
+
);
|
|
1163
1146
|
}
|
|
1164
1147
|
removeReaction(params) {
|
|
1165
1148
|
const { postId, type } = params;
|
|
@@ -1178,10 +1161,14 @@ var CommunityClient = class {
|
|
|
1178
1161
|
400
|
|
1179
1162
|
);
|
|
1180
1163
|
}
|
|
1181
|
-
return this.
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1164
|
+
return this.executeDoc(
|
|
1165
|
+
"/api/reactions",
|
|
1166
|
+
"POST",
|
|
1167
|
+
{
|
|
1168
|
+
comment: commentId,
|
|
1169
|
+
type: reactionType
|
|
1170
|
+
}
|
|
1171
|
+
);
|
|
1185
1172
|
}
|
|
1186
1173
|
removeCommentReaction(params) {
|
|
1187
1174
|
const { commentId, type } = params;
|
|
@@ -1210,9 +1197,13 @@ var CommunityClient = class {
|
|
|
1210
1197
|
}
|
|
1211
1198
|
// Bookmarks
|
|
1212
1199
|
addBookmark(params) {
|
|
1213
|
-
return this.
|
|
1214
|
-
|
|
1215
|
-
|
|
1200
|
+
return this.executeDoc(
|
|
1201
|
+
"/api/bookmarks",
|
|
1202
|
+
"POST",
|
|
1203
|
+
{
|
|
1204
|
+
post: params.postId
|
|
1205
|
+
}
|
|
1206
|
+
);
|
|
1216
1207
|
}
|
|
1217
1208
|
removeBookmark(params) {
|
|
1218
1209
|
return this.execute(
|
|
@@ -1228,10 +1219,7 @@ var CommunityClient = class {
|
|
|
1228
1219
|
}
|
|
1229
1220
|
// Profiles
|
|
1230
1221
|
listProfileLists(params) {
|
|
1231
|
-
return this.execute(
|
|
1232
|
-
`/api/customer-profile-lists${this.buildQuery(params)}`,
|
|
1233
|
-
"GET"
|
|
1234
|
-
);
|
|
1222
|
+
return this.execute(`/api/customer-profile-lists${this.buildQuery(params)}`, "GET");
|
|
1235
1223
|
}
|
|
1236
1224
|
async getProfileList(params) {
|
|
1237
1225
|
const query = "slug" in params ? `?where[slug][equals]=${encodeURIComponent(params.slug)}&limit=1` : `?where[id][equals]=${encodeURIComponent(params.id)}&limit=1`;
|
|
@@ -1461,46 +1449,18 @@ var CustomerNamespace = class {
|
|
|
1461
1449
|
};
|
|
1462
1450
|
|
|
1463
1451
|
// src/core/api/cart-api.ts
|
|
1464
|
-
var CartApi = class {
|
|
1452
|
+
var CartApi = class extends CustomerScopedApi {
|
|
1465
1453
|
constructor(options) {
|
|
1466
|
-
|
|
1467
|
-
throw createConfigError(
|
|
1468
|
-
"Either secretKey or customerToken is required for CartApi."
|
|
1469
|
-
);
|
|
1470
|
-
}
|
|
1471
|
-
this.publishableKey = requirePublishableKeyForSecret(
|
|
1472
|
-
"CartApi",
|
|
1473
|
-
options.publishableKey,
|
|
1474
|
-
options.secretKey
|
|
1475
|
-
);
|
|
1476
|
-
this.secretKey = options.secretKey;
|
|
1477
|
-
this.apiUrl = options.apiUrl;
|
|
1478
|
-
this.customerToken = options.customerToken;
|
|
1479
|
-
this.onUnauthorized = options.onUnauthorized;
|
|
1480
|
-
this.onRequestId = options.onRequestId;
|
|
1454
|
+
super("CartApi", options);
|
|
1481
1455
|
}
|
|
1482
1456
|
async execute(endpoint, method, body) {
|
|
1483
|
-
|
|
1484
|
-
try {
|
|
1485
|
-
const response = await httpFetch(endpoint, {
|
|
1486
|
-
method,
|
|
1487
|
-
apiUrl: this.apiUrl,
|
|
1488
|
-
publishableKey: this.publishableKey,
|
|
1489
|
-
secretKey: this.secretKey,
|
|
1490
|
-
customerToken: token ?? void 0,
|
|
1491
|
-
...token && this.onUnauthorized && { onUnauthorized: this.onUnauthorized },
|
|
1492
|
-
...body !== void 0 && { body: JSON.stringify(body) }
|
|
1493
|
-
});
|
|
1494
|
-
this.onRequestId?.(response.headers.get("x-request-id") ?? null);
|
|
1495
|
-
return parseApiResponse(response, endpoint);
|
|
1496
|
-
} catch (err) {
|
|
1497
|
-
const id = err instanceof SDKError ? err.requestId ?? null : null;
|
|
1498
|
-
this.onRequestId?.(id);
|
|
1499
|
-
throw err;
|
|
1500
|
-
}
|
|
1457
|
+
return this.request(endpoint, { method, body });
|
|
1501
1458
|
}
|
|
1502
1459
|
getCart(cartId) {
|
|
1503
|
-
return this.execute(
|
|
1460
|
+
return this.execute(
|
|
1461
|
+
`/api/carts/${cartId}?depth=0&joins=false`,
|
|
1462
|
+
"GET"
|
|
1463
|
+
);
|
|
1504
1464
|
}
|
|
1505
1465
|
addItem(params) {
|
|
1506
1466
|
return this.execute("/api/carts/add-item", "POST", params);
|
|
@@ -1564,6 +1524,18 @@ function splitIdempotencyKey(params) {
|
|
|
1564
1524
|
}
|
|
1565
1525
|
|
|
1566
1526
|
// src/core/commerce/commerce-client.ts
|
|
1527
|
+
var BrowserCommerceApi = class extends CustomerScopedApi {
|
|
1528
|
+
post(endpoint, body, requestOptions) {
|
|
1529
|
+
return this.request(endpoint, {
|
|
1530
|
+
method: "POST",
|
|
1531
|
+
body,
|
|
1532
|
+
headers: requestOptions?.headers
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
get(endpoint) {
|
|
1536
|
+
return this.request(endpoint, { method: "GET" });
|
|
1537
|
+
}
|
|
1538
|
+
};
|
|
1567
1539
|
var CommerceClient = class {
|
|
1568
1540
|
constructor(options) {
|
|
1569
1541
|
const cartApi = new CartApi({
|
|
@@ -1573,52 +1545,23 @@ var CommerceClient = class {
|
|
|
1573
1545
|
onUnauthorized: options.onUnauthorized,
|
|
1574
1546
|
onRequestId: options.onRequestId
|
|
1575
1547
|
});
|
|
1576
|
-
const
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
customerToken: token ?? void 0,
|
|
1584
|
-
...token && options.onUnauthorized && { onUnauthorized: options.onUnauthorized },
|
|
1585
|
-
body: JSON.stringify(body),
|
|
1586
|
-
...requestOptions?.headers && { headers: requestOptions.headers }
|
|
1587
|
-
});
|
|
1588
|
-
options.onRequestId?.(response.headers.get("x-request-id") ?? null);
|
|
1589
|
-
return parseApiResponse(response, endpoint);
|
|
1590
|
-
} catch (err) {
|
|
1591
|
-
const id = err instanceof SDKError ? err.requestId ?? null : null;
|
|
1592
|
-
options.onRequestId?.(id);
|
|
1593
|
-
throw err;
|
|
1594
|
-
}
|
|
1595
|
-
};
|
|
1596
|
-
const executeGet = async (endpoint) => {
|
|
1597
|
-
const token = options.customerToken();
|
|
1598
|
-
try {
|
|
1599
|
-
const response = await httpFetch(endpoint, {
|
|
1600
|
-
method: "GET",
|
|
1601
|
-
apiUrl: options.apiUrl,
|
|
1602
|
-
publishableKey: options.publishableKey,
|
|
1603
|
-
customerToken: token ?? void 0,
|
|
1604
|
-
...token && options.onUnauthorized && { onUnauthorized: options.onUnauthorized }
|
|
1605
|
-
});
|
|
1606
|
-
options.onRequestId?.(response.headers.get("x-request-id") ?? null);
|
|
1607
|
-
return parseApiResponse(response, endpoint);
|
|
1608
|
-
} catch (err) {
|
|
1609
|
-
const id = err instanceof SDKError ? err.requestId ?? null : null;
|
|
1610
|
-
options.onRequestId?.(id);
|
|
1611
|
-
throw err;
|
|
1612
|
-
}
|
|
1613
|
-
};
|
|
1548
|
+
const api = new BrowserCommerceApi("CommerceClient", {
|
|
1549
|
+
publishableKey: options.publishableKey,
|
|
1550
|
+
apiUrl: options.apiUrl,
|
|
1551
|
+
customerToken: options.customerToken,
|
|
1552
|
+
onUnauthorized: options.onUnauthorized,
|
|
1553
|
+
onRequestId: options.onRequestId
|
|
1554
|
+
});
|
|
1614
1555
|
this.product = {
|
|
1615
|
-
stockCheck: (params) =>
|
|
1616
|
-
stockSnapshot: (params) =>
|
|
1617
|
-
listingGroups: (params) =>
|
|
1618
|
-
listingGroupsCatalog: (params) =>
|
|
1556
|
+
stockCheck: (params) => api.post("/api/products/stock-check", params),
|
|
1557
|
+
stockSnapshot: (params) => api.get(stockSnapshotQuery(params)),
|
|
1558
|
+
listingGroups: (params) => api.get(listingGroupsQuery(params)),
|
|
1559
|
+
listingGroupsCatalog: (params) => api.get(
|
|
1560
|
+
listingGroupsCatalogQuery(params)
|
|
1561
|
+
),
|
|
1619
1562
|
detail: async (params) => {
|
|
1620
1563
|
try {
|
|
1621
|
-
const product = await
|
|
1564
|
+
const product = await api.get(productDetailQuery(params));
|
|
1622
1565
|
return { found: true, product };
|
|
1623
1566
|
} catch (err) {
|
|
1624
1567
|
const notFoundResult = productDetailResultFromError(err);
|
|
@@ -1628,7 +1571,7 @@ var CommerceClient = class {
|
|
|
1628
1571
|
},
|
|
1629
1572
|
detailCatalog: async (params) => {
|
|
1630
1573
|
try {
|
|
1631
|
-
const product = await
|
|
1574
|
+
const product = await api.get(
|
|
1632
1575
|
productDetailCatalogQuery(params)
|
|
1633
1576
|
);
|
|
1634
1577
|
return { found: true, product };
|
|
@@ -1651,7 +1594,7 @@ var CommerceClient = class {
|
|
|
1651
1594
|
this.orders = {
|
|
1652
1595
|
checkout: (params) => {
|
|
1653
1596
|
const { body, idempotencyKey } = splitIdempotencyKey(params);
|
|
1654
|
-
return
|
|
1597
|
+
return api.post(
|
|
1655
1598
|
"/api/orders/checkout",
|
|
1656
1599
|
body,
|
|
1657
1600
|
idempotencyRequestOptions(idempotencyKey)
|
|
@@ -1660,10 +1603,10 @@ var CommerceClient = class {
|
|
|
1660
1603
|
listMine: (params) => options.customerAuth.getMyOrders(params)
|
|
1661
1604
|
};
|
|
1662
1605
|
this.discounts = {
|
|
1663
|
-
validate: (params) =>
|
|
1606
|
+
validate: (params) => api.post("/api/discounts/validate", params)
|
|
1664
1607
|
};
|
|
1665
1608
|
this.shipping = {
|
|
1666
|
-
calculate: (params) =>
|
|
1609
|
+
calculate: (params) => api.post("/api/shipping-policies/calculate", params)
|
|
1667
1610
|
};
|
|
1668
1611
|
}
|
|
1669
1612
|
};
|