@kanca-app/ikas 0.1.1
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/LICENSE +21 -0
- package/README.md +173 -0
- package/dist/browser.cjs +172 -0
- package/dist/browser.cjs.map +1 -0
- package/dist/browser.d.cts +17 -0
- package/dist/browser.d.ts +17 -0
- package/dist/browser.js +151 -0
- package/dist/browser.js.map +1 -0
- package/dist/index.cjs +803 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +104 -0
- package/dist/index.d.ts +104 -0
- package/dist/index.js +780 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +181 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +22 -0
- package/dist/react.d.ts +22 -0
- package/dist/react.js +161 -0
- package/dist/react.js.map +1 -0
- package/package.json +97 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/graphql.ts","../src/shared.ts","../src/transport.ts","../src/index.ts"],"sourcesContent":["// GraphQL helpers: operation-name extraction and ikas Admin API URL detection.\n\ntype Tok = { t: \"name\" | \"punct\"; v: string };\n\n/** Minimal GraphQL lexer: names and punctuation only; strings/comments/numbers skipped. */\nfunction lex(src: string): Tok[] {\n const out: Tok[] = [];\n let i = 0;\n const n = src.length;\n while (i < n) {\n const c = src[i]!;\n if (c === \"#\") {\n while (i < n && src[i] !== \"\\n\" && src[i] !== \"\\r\") i++;\n continue;\n }\n if (c === '\"') {\n if (src.startsWith('\"\"\"', i)) {\n const end = src.indexOf('\"\"\"', i + 3);\n i = end === -1 ? n : end + 3;\n } else {\n i++;\n while (i < n && src[i] !== '\"' && src[i] !== \"\\n\") i += src[i] === \"\\\\\" ? 2 : 1;\n i++;\n }\n continue;\n }\n if (/[A-Za-z_]/.test(c)) {\n let j = i + 1;\n while (j < n && /[A-Za-z0-9_]/.test(src[j]!)) j++;\n out.push({ t: \"name\", v: src.slice(i, j) });\n i = j;\n continue;\n }\n if (src.startsWith(\"...\", i)) {\n out.push({ t: \"punct\", v: \"...\" });\n i += 3;\n continue;\n }\n if (\"{}()[]:@$!=|&\".includes(c)) out.push({ t: \"punct\", v: c });\n i++;\n }\n return out;\n}\n\n/** Index just past the balanced block that opens at `start` (a \"{\" or \"(\" token). */\nfunction skipBlock(toks: Tok[], start: number): number {\n const open = toks[start]!.v;\n const close = open === \"{\" ? \"}\" : \")\";\n let depth = 0;\n for (let i = start; i < toks.length; i++) {\n const v = toks[i]!;\n if (v.t !== \"punct\") continue;\n if (v.v === open) depth++;\n else if (v.v === close && --depth === 0) return i + 1;\n }\n return toks.length;\n}\n\n/** First root field name inside the selection set that opens at `start`. */\nfunction firstRootField(toks: Tok[], start: number): string | undefined {\n for (let i = start + 1; i < toks.length; i++) {\n const tk = toks[i]!;\n if (tk.t === \"punct\") {\n if (tk.v === \"}\") return undefined;\n if (tk.v === \"...\") return undefined; // fragment spread at root: no field name to report\n continue;\n }\n const next = toks[i + 1];\n if (next && next.t === \"punct\" && next.v === \":\") {\n const real = toks[i + 2];\n return real && real.t === \"name\" ? real.v : undefined; // alias: report the real field\n }\n return tk.v;\n }\n return undefined;\n}\n\n/**\n * Operation name per PROTOCOL.md: the operation name; if anonymous, the first\n * root field; else \"anonymous\". Uses `operationName` from the request body to\n * pick among multiple operations when given.\n */\nexport function operationName(query: unknown, requestedName?: unknown): string {\n if (typeof query !== \"string\" || !query.trim()) return \"anonymous\";\n let toks: Tok[];\n try {\n toks = lex(query);\n } catch {\n return \"anonymous\";\n }\n const ops: { name?: string; field?: string }[] = [];\n let i = 0;\n while (i < toks.length) {\n const tk = toks[i]!;\n if (tk.t === \"punct\" && tk.v === \"{\") {\n ops.push({ field: firstRootField(toks, i) });\n i = skipBlock(toks, i);\n continue;\n }\n if (tk.t === \"name\" && (tk.v === \"query\" || tk.v === \"mutation\" || tk.v === \"subscription\")) {\n let j = i + 1;\n let name: string | undefined;\n if (toks[j]?.t === \"name\") name = toks[j++]!.v;\n // skip variable definitions and directives until the selection set\n while (j < toks.length && !(toks[j]!.t === \"punct\" && toks[j]!.v === \"{\")) {\n if (toks[j]!.t === \"punct\" && toks[j]!.v === \"(\") j = skipBlock(toks, j);\n else j++;\n }\n if (j >= toks.length) {\n ops.push({ name });\n break;\n }\n ops.push({ name, field: firstRootField(toks, j) });\n i = skipBlock(toks, j);\n continue;\n }\n if (tk.t === \"name\" && tk.v === \"fragment\") {\n let j = i + 1;\n while (j < toks.length && !(toks[j]!.t === \"punct\" && toks[j]!.v === \"{\")) j++;\n i = j < toks.length ? skipBlock(toks, j) : j;\n continue;\n }\n i++;\n }\n if (!ops.length) return \"anonymous\";\n let op = ops[0]!;\n if (typeof requestedName === \"string\" && requestedName) {\n op = ops.find((o) => o.name === requestedName) ?? op;\n }\n return op.name ?? op.field ?? \"anonymous\";\n}\n\n/** ikas Admin GraphQL: api.myikas.com/api/v1/admin/graphql, or any *.myikas.com URL whose path contains \"graphql\". */\nexport function isIkasGraphqlUrl(url: URL): boolean {\n const host = url.hostname.toLowerCase();\n if (host !== \"myikas.com\" && !host.endsWith(\".myikas.com\")) return false;\n return url.pathname.toLowerCase().includes(\"graphql\");\n}\n\n/** Extracts the operation name from a GraphQL-over-HTTP JSON body (single or batched). */\nexport function operationNameFromBody(body: unknown): string {\n if (typeof body !== \"string\") return \"anonymous\";\n try {\n let parsed: unknown = JSON.parse(body);\n if (Array.isArray(parsed)) parsed = parsed[0];\n if (!parsed || typeof parsed !== \"object\") return \"anonymous\";\n const p = parsed as { query?: unknown; operationName?: unknown };\n return operationName(p.query, p.operationName);\n } catch {\n return \"anonymous\";\n }\n}\n\nexport interface GraphqlOutcome {\n ok: boolean;\n code?: string;\n message?: string;\n}\n\n/** Classifies a GraphQL response: ok = 2xx AND no `errors`. */\nexport function classifyGraphql(status: number, json: unknown): GraphqlOutcome {\n const payload = Array.isArray(json) ? json.find((j) => j && typeof j === \"object\" && Array.isArray(j.errors) && j.errors.length) ?? json[0] : json;\n const errors =\n payload && typeof payload === \"object\" && Array.isArray((payload as { errors?: unknown }).errors)\n ? ((payload as { errors: unknown[] }).errors as unknown[])\n : [];\n const is2xx = status >= 200 && status < 300;\n if (is2xx && errors.length === 0) return { ok: true };\n const first = errors[0] as { message?: unknown; extensions?: { code?: unknown } } | undefined;\n const extCode = first?.extensions?.code;\n const code = typeof extCode === \"string\" && extCode ? extCode : `HTTP_${status}`;\n const message = typeof first?.message === \"string\" ? first.message : undefined;\n return { ok: false, code, message };\n}\n","// Shared, runtime-agnostic helpers (used by both the Node and browser entries).\n// Wire format: see docs/PROTOCOL.md (v1).\n\nexport const SDK_NAME = \"@kanca-app/ikas\";\nexport const SDK_VERSION = \"0.1.1\";\nexport const DEFAULT_ENDPOINT = \"https://kanca.onurhan.dev\";\n\nexport const MAX_NAME = 200;\nexport const MAX_ERROR_MESSAGE = 500;\nexport const MAX_ERROR_CODE = 100;\nexport const MAX_ATTR_KEYS = 20;\nexport const MAX_ATTR_STRING = 500;\nexport const MAX_ATTR_KEY = 64;\nexport const MAX_EVENTS_PER_BATCH = 500;\n/** Protocol limit is 512 KB; keep headroom for the envelope. */\nexport const MAX_BATCH_BYTES = 480 * 1024;\n\nexport type EventType = \"webhook\" | \"graphql\" | \"rest\" | \"install\" | \"uninstall\" | \"vital\";\n\nexport type AttrValue = string | number | boolean;\n\nexport interface KancaError {\n code: string;\n message?: string;\n}\n\n/** A single wire event (PROTOCOL.md → Event). */\nexport interface KancaEvent {\n id: string;\n type: EventType;\n ts: number;\n name: string;\n status?: number;\n ok?: boolean;\n durationMs?: number;\n value?: number;\n error?: KancaError;\n merchantId?: string;\n deliveryId?: string;\n attrs?: Record<string, AttrValue>;\n}\n\nexport interface Batch {\n sdk: { name: string; version: string };\n events: KancaEvent[];\n}\n\nexport function truncate(s: string, max: number): string {\n return s.length > max ? s.slice(0, max) : s;\n}\n\nfunction hex(bytes: Uint8Array): string {\n let out = \"\";\n for (const b of bytes) out += b.toString(16).padStart(2, \"0\");\n return out;\n}\n\n/** UUID v4 via crypto.randomUUID, with a getRandomValues fallback (non-secure browser contexts). */\nexport function uuid(): string {\n const c = (globalThis as { crypto?: Crypto }).crypto;\n if (c && typeof c.randomUUID === \"function\") return c.randomUUID();\n const b = new Uint8Array(16);\n if (c && typeof c.getRandomValues === \"function\") c.getRandomValues(b);\n else for (let i = 0; i < 16; i++) b[i] = Math.floor(Math.random() * 256);\n b[6] = (b[6]! & 0x0f) | 0x40;\n b[8] = (b[8]! & 0x3f) | 0x80;\n const h = hex(b);\n return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;\n}\n\nfunction isFiniteNumber(v: unknown): v is number {\n return typeof v === \"number\" && Number.isFinite(v);\n}\n\nfunction cleanAttrs(attrs: unknown): Record<string, AttrValue> | undefined {\n if (!attrs || typeof attrs !== \"object\") return undefined;\n const out: Record<string, AttrValue> = {};\n let n = 0;\n for (const [k, v] of Object.entries(attrs as Record<string, unknown>)) {\n if (n >= MAX_ATTR_KEYS) break;\n if (v === undefined || v === null || !k) continue;\n let val: AttrValue;\n if (typeof v === \"boolean\") val = v;\n else if (typeof v === \"number\") {\n if (!Number.isFinite(v)) continue;\n val = v;\n } else if (typeof v === \"string\") val = truncate(v, MAX_ATTR_STRING);\n else continue; // attrs are flat: nested values are dropped, never stringified\n out[truncate(k, MAX_ATTR_KEY)] = val;\n n++;\n }\n return n > 0 ? out : undefined;\n}\n\n/**\n * Normalises an event so it always satisfies the protocol limits. Unknown\n * fields are dropped — only the whitelisted metadata below ever leaves the process.\n */\nexport function sanitizeEvent(e: KancaEvent): KancaEvent {\n const out: KancaEvent = {\n id: typeof e.id === \"string\" && e.id ? e.id : uuid(),\n type: e.type,\n ts: isFiniteNumber(e.ts) ? Math.round(e.ts) : Date.now(),\n name: truncate(typeof e.name === \"string\" && e.name ? e.name : \"unknown\", MAX_NAME),\n };\n if (isFiniteNumber(e.status) && e.status >= 100 && e.status <= 599) out.status = Math.trunc(e.status);\n if (typeof e.ok === \"boolean\") out.ok = e.ok;\n if (isFiniteNumber(e.durationMs)) out.durationMs = Math.min(86_400_000, Math.max(0, Math.round(e.durationMs * 10) / 10));\n if (isFiniteNumber(e.value)) out.value = e.value;\n if (e.error && typeof e.error.code === \"string\") {\n out.error = { code: truncate(e.error.code, MAX_ERROR_CODE) };\n if (typeof e.error.message === \"string\" && e.error.message) {\n out.error.message = truncate(e.error.message, MAX_ERROR_MESSAGE);\n }\n }\n if (typeof e.merchantId === \"string\" && e.merchantId) out.merchantId = truncate(e.merchantId, 200);\n if (typeof e.deliveryId === \"string\" && e.deliveryId) out.deliveryId = truncate(e.deliveryId, 200);\n const attrs = cleanAttrs(e.attrs);\n if (attrs) out.attrs = attrs;\n return out;\n}\n\nexport function errorMessage(err: unknown): string | undefined {\n try {\n if (err instanceof Error) return truncate(err.message || err.name, MAX_ERROR_MESSAGE);\n if (typeof err === \"string\") return truncate(err, MAX_ERROR_MESSAGE);\n } catch {\n /* ignore */\n }\n return undefined;\n}\n\nexport function joinUrl(endpoint: string, path: string): string {\n return endpoint.replace(/\\/+$/, \"\") + path;\n}\n\nconst encoder = typeof TextEncoder !== \"undefined\" ? new TextEncoder() : undefined;\nexport function byteLength(s: string): number {\n return encoder ? encoder.encode(s).length : s.length * 3;\n}\n\n/**\n * Splits events into protocol-sized batches (<= maxEvents events and\n * <= MAX_BATCH_BYTES serialized). Returns serialized bodies.\n */\nexport function buildBodies(events: KancaEvent[], maxEvents: number): { body: string; events: KancaEvent[] }[] {\n const sdk = { name: SDK_NAME, version: SDK_VERSION };\n const envelope = byteLength(JSON.stringify({ sdk, events: [] }));\n const out: { body: string; events: KancaEvent[] }[] = [];\n let cur: KancaEvent[] = [];\n let size = envelope;\n const push = () => {\n if (cur.length) out.push({ body: JSON.stringify({ sdk, events: cur } satisfies Batch), events: cur });\n cur = [];\n size = envelope;\n };\n for (const ev of events) {\n const len = byteLength(JSON.stringify(ev)) + 1;\n if (cur.length && (cur.length >= maxEvents || size + len > MAX_BATCH_BYTES)) push();\n cur.push(ev);\n size += len;\n }\n push();\n return out;\n}\n","// Server-side transport: bounded in-memory queue, batched POST /api/ingest,\n// retry 429/5xx/network with exponential backoff (max 3 attempts). Never throws.\n\nimport { buildBodies, joinUrl, type KancaEvent } from \"./shared\";\n\nexport interface TransportOptions {\n key: string;\n endpoint: string;\n flushIntervalMs: number;\n maxBatchSize: number;\n maxQueueSize: number;\n maxAttempts: number;\n retryBaseMs: number;\n log: (msg: string, ...args: unknown[]) => void;\n warn: (msg: string) => void;\n /** Resolved at send time so tests / polyfills can replace globalThis.fetch. */\n getFetch: () => typeof fetch | undefined;\n}\n\ntype Timer = ReturnType<typeof setTimeout>;\n\nfunction unref(t: Timer): Timer {\n (t as { unref?: () => void }).unref?.();\n return t;\n}\n\nconst sleep = (ms: number) => new Promise<void>((r) => unref(setTimeout(r, ms)));\n\nexport class Transport {\n private queue: KancaEvent[] = [];\n private timer: Timer | undefined;\n private immediate: Timer | undefined;\n private chain: Promise<void> = Promise.resolve();\n private warned401 = false;\n dropped = 0;\n sent = 0;\n\n constructor(private readonly o: TransportOptions) {}\n\n get size(): number {\n return this.queue.length;\n }\n\n enqueue(ev: KancaEvent): void {\n this.queue.push(ev);\n if (this.queue.length > this.o.maxQueueSize) {\n const over = this.queue.length - this.o.maxQueueSize;\n this.queue.splice(0, over); // drop oldest\n this.dropped += over;\n this.o.log(`queue full, dropped ${over} oldest event(s) (total dropped: ${this.dropped})`);\n }\n if (this.queue.length >= this.o.maxBatchSize) this.scheduleSoon();\n else this.arm();\n }\n\n /** Arms the interval flush if not armed. */\n private arm(): void {\n if (this.timer) return;\n this.timer = unref(\n setTimeout(() => {\n this.timer = undefined;\n void this.flush();\n }, this.o.flushIntervalMs),\n );\n }\n\n /** Coalesced flush on the next macrotask (does not block the caller). */\n scheduleSoon(): void {\n if (this.immediate) return;\n this.immediate = unref(\n setTimeout(() => {\n this.immediate = undefined;\n void this.flush();\n }, 0),\n );\n }\n\n /**\n * Sends everything queued at call time. Calls are serialized, so awaiting\n * flush() always covers events enqueued before it. Always resolves.\n */\n flush(): Promise<void> {\n this.chain = this.chain.then(() => this.drain()).catch((e) => this.o.log(\"flush failed\", e));\n return this.chain;\n }\n\n private async drain(): Promise<void> {\n if (this.timer) {\n clearTimeout(this.timer);\n this.timer = undefined;\n }\n if (!this.queue.length) return;\n const events = this.queue.splice(0, this.queue.length);\n await this.sendAll(events);\n }\n\n private async sendAll(events: KancaEvent[]): Promise<void> {\n for (const { body, events: evs } of buildBodies(events, this.o.maxBatchSize)) {\n const ok = await this.send(body);\n if (ok) this.sent += evs.length;\n else {\n this.dropped += evs.length;\n this.o.log(`dropped batch of ${evs.length} event(s) (total dropped: ${this.dropped})`);\n }\n }\n }\n\n private async send(body: string): Promise<boolean> {\n const f = this.o.getFetch();\n if (!f) {\n this.o.log(\"no fetch available; cannot send\");\n return false;\n }\n const url = joinUrl(this.o.endpoint, \"/api/ingest\");\n for (let attempt = 1; attempt <= this.o.maxAttempts; attempt++) {\n let retryAfterMs: number | undefined;\n try {\n const res = await f(url, {\n method: \"POST\",\n headers: { authorization: `Bearer ${this.o.key}`, \"content-type\": \"application/json\" },\n body,\n });\n // drain so the connection can be reused\n res.text().catch(() => undefined);\n if (res.status >= 200 && res.status < 300) {\n this.o.log(`sent batch (${res.status})`);\n return true;\n }\n if (res.status === 401 && !this.warned401) {\n this.warned401 = true;\n this.o.warn(\"[kanca] ingest rejected the key (401). Check KANCA_KEY — events are being dropped.\");\n }\n if (res.status !== 429 && res.status < 500) {\n this.o.log(`ingest responded ${res.status}; not retrying`);\n return false;\n }\n const ra = Number(res.headers.get(\"retry-after\"));\n if (res.status === 429 && Number.isFinite(ra) && ra >= 0) retryAfterMs = Math.min(ra * 1000, 30_000);\n this.o.log(`ingest responded ${res.status} (attempt ${attempt}/${this.o.maxAttempts})`);\n } catch (err) {\n this.o.log(`network error (attempt ${attempt}/${this.o.maxAttempts})`, err);\n }\n if (attempt < this.o.maxAttempts) {\n const backoff = this.o.retryBaseMs * 2 ** (attempt - 1);\n const jitter = backoff * 0.2 * Math.random();\n await sleep(retryAfterMs ?? backoff + jitter);\n }\n }\n return false;\n }\n}\n","// @kanca-app/ikas — server entry (Node 18+, Next.js route handlers, any runtime with fetch/Request).\n// Contract: docs/PROTOCOL.md (v1). Metadata only — never payloads, variables, headers or tokens.\n\nimport { classifyGraphql, isIkasGraphqlUrl, operationName, operationNameFromBody } from \"./graphql\";\nimport {\n DEFAULT_ENDPOINT,\n MAX_EVENTS_PER_BATCH,\n errorMessage,\n sanitizeEvent,\n uuid,\n type AttrValue,\n type EventType,\n type KancaError,\n type KancaEvent,\n} from \"./shared\";\nimport { Transport } from \"./transport\";\n\nexport { SDK_NAME, SDK_VERSION } from \"./shared\";\nexport type { AttrValue, EventType, KancaError, KancaEvent } from \"./shared\";\nexport { operationName } from \"./graphql\";\n\nexport interface KancaOptions {\n /** Server key (`kk_...`). If missing, the SDK is a silent no-op. */\n key?: string | undefined;\n /** Kanca base URL. Default `https://kanca.onurhan.dev`. */\n endpoint?: string;\n /** Log SDK activity to the console. */\n debug?: boolean;\n /** Turn the SDK into a no-op (e.g. in tests). */\n disabled?: boolean;\n /** Flush interval in ms. Default 2000. */\n flushIntervalMs?: number;\n /** Events per request. Default 100, max 500 (protocol limit). */\n maxBatchSize?: number;\n /** Max events buffered in memory; oldest are dropped beyond this. Default 5000. */\n maxQueueSize?: number;\n /**\n * Optional hook to keep serverless functions alive until telemetry is sent,\n * e.g. `waitUntil` from `@vercel/functions`. Called with a flush promise\n * whenever events are recorded. Never delays the response.\n */\n waitUntil?: (promise: Promise<unknown>) => void;\n /** @internal Base backoff delay in ms (default 500). */\n retryBaseMs?: number;\n}\n\nexport interface TrackFields {\n name?: string;\n ts?: number;\n status?: number;\n ok?: boolean;\n durationMs?: number;\n value?: number;\n error?: KancaError;\n merchantId?: string;\n deliveryId?: string;\n /** install: shop name, stored as `attrs.storeName`. */\n storeName?: string;\n attrs?: Record<string, AttrValue>;\n}\n\nexport interface InstallFields {\n merchantId: string;\n storeName?: string;\n attrs?: Record<string, AttrValue>;\n}\n\nexport interface InstrumentOptions {\n /** merchantId to attach to every call. Defaults to `client.tokenData.merchantId` when present. */\n merchantId?: string;\n}\n\nexport type WebhookHandler<R extends Request, A extends unknown[]> = (req: R, ...rest: A) => Response | Promise<Response>;\n\nexport interface Kanca {\n /** Wraps a webhook route handler (Next.js `POST` compatible). Records one `webhook` event per delivery. */\n webhook<R extends Request, A extends unknown[]>(handler: WebhookHandler<R, A>): (req: R, ...rest: A) => Promise<Response>;\n /** Drop-in `fetch` that records ikas Admin GraphQL calls (`graphql`) and any other call (`rest`). */\n fetch: typeof globalThis.fetch;\n /** Instruments an `@ikas/admin-api-client` client (BaseGraphQLAPIClient subclass) in place. Returns it. */\n instrument<T>(client: T, options?: InstrumentOptions): T;\n /** Record an install (call in the OAuth callback after a successful token exchange). */\n track(type: \"install\", fields: InstallFields): void;\n /** Record any event manually. */\n track(type: Exclude<EventType, \"install\">, fields?: TrackFields): void;\n /** Send everything buffered. Never rejects. `await` it at the end of serverless invocations. */\n flush(): Promise<void>;\n /** True when a valid key is configured and the SDK is not disabled. */\n readonly enabled: boolean;\n}\n\n// ---- flush on natural process exit (Node). One listener for all instances; an\n// instance is registered only while it has unsent events, so nothing leaks.\nconst exitFlushers = new Set<() => Promise<void>>();\nlet exitHookInstalled = false;\nfunction registerExitFlush(fn: () => Promise<void>): void {\n exitFlushers.add(fn);\n if (exitHookInstalled) return;\n try {\n const proc = (globalThis as { process?: { on?: (ev: string, fn: () => void) => void } }).process;\n if (proc && typeof proc.on === \"function\") {\n proc.on(\"beforeExit\", () => {\n // Scheduling work here keeps the process alive until the flush settles;\n // beforeExit fires again afterwards and finds the set empty.\n for (const f of [...exitFlushers]) void f();\n });\n exitHookInstalled = true;\n }\n } catch {\n /* not Node (edge runtime, browser) */\n }\n}\n\n/**\n * Webhook scopes that mean \"this store removed the app\". ikas documents\n * `store/app/deleted`; real apps also subscribe to the other two names, so\n * all of them count as an uninstall (deduped server-side by deliveryId).\n */\nexport const UNINSTALL_SCOPES: readonly string[] = [\n \"store/app/deleted\",\n \"store/app/uninstalled\",\n \"store/authorizedApp/deleted\",\n];\nconst MARK = Symbol.for(\"kanca.instrumented\");\n\nconst now = (): number =>\n typeof performance !== \"undefined\" && typeof performance.now === \"function\" ? performance.now() : Date.now();\n\nfunction clampInt(v: unknown, def: number, min: number, max: number): number {\n const n = typeof v === \"number\" && Number.isFinite(v) ? Math.floor(v) : def;\n return Math.min(max, Math.max(min, n));\n}\n\nfunction isRequest(v: unknown): v is Request {\n return typeof Request !== \"undefined\" && v instanceof Request;\n}\n\nfunction toUrl(input: unknown): URL | undefined {\n try {\n if (typeof input === \"string\") return new URL(input);\n if (input instanceof URL) return input;\n if (input && typeof (input as { url?: unknown }).url === \"string\") return new URL((input as { url: string }).url);\n } catch {\n /* relative or invalid URL */\n }\n return undefined;\n}\n\nfunction bodyToString(body: unknown): string | undefined {\n if (typeof body === \"string\") return body;\n try {\n if (body instanceof Uint8Array) return new TextDecoder().decode(body);\n if (body instanceof ArrayBuffer) return new TextDecoder().decode(new Uint8Array(body));\n } catch {\n /* ignore */\n }\n return undefined;\n}\n\ninterface WebhookMeta {\n scope?: string;\n id?: string;\n merchantId?: string;\n}\n\nfunction parseWebhookMeta(text: string): WebhookMeta | null {\n try {\n const j: unknown = JSON.parse(text);\n if (!j || typeof j !== \"object\" || Array.isArray(j)) return null;\n const o = j as Record<string, unknown>;\n // Only these three fields are read. Nothing else from the body is kept.\n return {\n scope: typeof o.scope === \"string\" && o.scope ? o.scope : undefined,\n id: typeof o.id === \"string\" && o.id ? o.id : undefined,\n merchantId: typeof o.merchantId === \"string\" && o.merchantId ? o.merchantId : undefined,\n };\n } catch {\n return null;\n }\n}\n\nfunction noopKanca(): Kanca {\n return {\n webhook: (handler) => async (req, ...rest) => handler(req, ...rest),\n fetch: ((input: RequestInfo | URL, init?: RequestInit) => globalThis.fetch(input, init)) as typeof fetch,\n instrument: (client) => client,\n track: () => undefined,\n flush: () => Promise.resolve(),\n enabled: false,\n };\n}\n\nexport function createKanca(options: KancaOptions = {}): Kanca {\n const debug = !!options.debug;\n const log = (msg: string, ...args: unknown[]) => {\n if (debug) console.debug(`[kanca] ${msg}`, ...args);\n };\n const warn = (msg: string) => console.warn(msg);\n\n if (options.disabled) {\n log(\"disabled; SDK is a no-op\");\n return noopKanca();\n }\n const key = typeof options.key === \"string\" ? options.key.trim() : \"\";\n if (!key) {\n log(\"no key (KANCA_KEY) configured; SDK is a no-op\");\n return noopKanca();\n }\n if (!key.startsWith(\"kk_\")) {\n warn(\n key.startsWith(\"kp_\")\n ? \"[kanca] createKanca() needs a server key (kk_...), got a public key (kp_...). Public keys are for reportVitals()/<KancaVitals/> only. SDK disabled.\"\n : \"[kanca] KANCA_KEY must start with kk_. SDK disabled.\",\n );\n return noopKanca();\n }\n\n const maxBatchSize = clampInt(options.maxBatchSize, 100, 1, MAX_EVENTS_PER_BATCH);\n const transport = new Transport({\n key,\n endpoint: options.endpoint || DEFAULT_ENDPOINT,\n flushIntervalMs: clampInt(options.flushIntervalMs, 2000, 0, 60 * 60 * 1000),\n maxBatchSize,\n maxQueueSize: clampInt(options.maxQueueSize, 5000, maxBatchSize, 1_000_000),\n maxAttempts: 3,\n retryBaseMs: clampInt(options.retryBaseMs, 500, 0, 60_000),\n log,\n warn,\n getFetch: () => (typeof globalThis.fetch === \"function\" ? globalThis.fetch.bind(globalThis) : undefined),\n });\n\n // Observations still being finalised (e.g. waiting for a cloned body). flush() awaits them.\n const pending = new Set<Promise<unknown>>();\n const observe = (p: Promise<unknown>) => {\n const tracked = p.catch((e) => log(\"observation failed\", e)).finally(() => pending.delete(tracked));\n pending.add(tracked);\n };\n\n // With waitUntil, every event (not only webhooks) keeps the invocation alive until sent;\n // otherwise a serverless function can be frozen before the interval flush fires.\n let keepAlive = false;\n const holdUntilSent = () => {\n if (!options.waitUntil || keepAlive) return;\n keepAlive = true;\n try {\n options.waitUntil(\n new Promise<void>((r) => setTimeout(r, 0))\n .then(flush)\n .finally(() => {\n keepAlive = false;\n }),\n );\n } catch (e) {\n keepAlive = false;\n log(\"waitUntil threw\", e);\n }\n };\n\n const emit = (ev: Omit<KancaEvent, \"id\"> & { id?: string }) => {\n try {\n const clean = sanitizeEvent({ ...ev, id: ev.id ?? uuid() } as KancaEvent);\n transport.enqueue(clean);\n registerExitFlush(flush);\n holdUntilSent();\n log(`${clean.type} ${clean.name}`, { status: clean.status, ok: clean.ok, durationMs: clean.durationMs });\n } catch (e) {\n log(\"failed to record event\", e);\n }\n };\n\n const flush = async (): Promise<void> => {\n try {\n while (pending.size) await Promise.allSettled([...pending]);\n await transport.flush();\n } catch (e) {\n log(\"flush error\", e);\n }\n if (!transport.size && !pending.size) exitFlushers.delete(flush);\n };\n\n const afterDelivery = () => {\n transport.scheduleSoon();\n if (options.waitUntil) {\n try {\n options.waitUntil(flush());\n } catch (e) {\n log(\"waitUntil threw\", e);\n }\n }\n };\n\n // ---- webhook ---------------------------------------------------------------------------------\n\n const webhook: Kanca[\"webhook\"] = (handler) =>\n async function kancaWebhook(this: unknown, req, ...rest) {\n const ts = Date.now();\n const t0 = now();\n let meta: Promise<WebhookMeta | null>;\n try {\n // Clone before the handler touches the body; the original stays fully readable.\n meta = req.clone().text().then(parseWebhookMeta, () => null);\n } catch {\n meta = Promise.resolve(null);\n }\n\n const record = (status: number | undefined, error: KancaError | undefined, durationMs: number) => {\n observe(\n meta.then((m) => {\n const base = { ts, merchantId: m?.merchantId, deliveryId: m?.id };\n emit({\n ...base,\n type: \"webhook\",\n name: m?.scope ?? \"unknown\",\n status,\n ok: status === 200,\n durationMs,\n error,\n });\n if (m?.scope && UNINSTALL_SCOPES.includes(m.scope)) emit({ ...base, type: \"uninstall\", name: \"uninstall\" });\n }),\n );\n afterDelivery();\n };\n\n let res: Response;\n try {\n res = await handler.call(this, req, ...rest);\n } catch (err) {\n try {\n record(500, { code: \"HANDLER_THREW\", message: errorMessage(err) }, now() - t0);\n } catch {\n /* never mask the handler's error */\n }\n throw err;\n }\n try {\n const status = res && typeof (res as { status?: unknown }).status === \"number\" ? res.status : undefined;\n record(status, status === undefined ? { code: \"NO_RESPONSE\", message: \"handler did not return a Response\" } : status === 200 ? undefined : { code: `HTTP_${status}` }, now() - t0);\n } catch {\n /* ignore */\n }\n return res;\n };\n\n // ---- fetch -----------------------------------------------------------------------------------\n\n const kfetch = async function kancaFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {\n const base = globalThis.fetch;\n let url: URL | undefined;\n let method = \"GET\";\n let gql = false;\n let opName: Promise<string> | undefined;\n try {\n url = toUrl(input);\n method = (init?.method ?? (isRequest(input) ? input.method : \"GET\")).toUpperCase();\n gql = !!url && isIkasGraphqlUrl(url);\n if (gql) {\n if (init && init.body != null) opName = Promise.resolve(operationNameFromBody(bodyToString(init.body)));\n else if (isRequest(input) && input.body) opName = input.clone().text().then(operationNameFromBody, () => \"anonymous\");\n else opName = Promise.resolve(\"anonymous\");\n }\n } catch {\n url = undefined;\n }\n if (!url) return base(input, init);\n\n const ts = Date.now();\n const t0 = now();\n let res: Response;\n try {\n res = await base(input, init);\n } catch (err) {\n const durationMs = now() - t0;\n try {\n const error = { code: \"NETWORK\", message: errorMessage(err) };\n if (gql) observe((opName ?? Promise.resolve(\"anonymous\")).then((name) => emit({ type: \"graphql\", name, ts, ok: false, durationMs, error })));\n else emit({ type: \"rest\", name: `${method} ${url.host}${url.pathname}`, ts, ok: false, durationMs, error });\n } catch {\n /* ignore */\n }\n throw err;\n }\n const durationMs = now() - t0;\n try {\n const status = res.status;\n if (gql) {\n let clone: Response | undefined;\n try {\n clone = res.clone(); // synchronously, before the caller can consume the body\n } catch {\n clone = undefined;\n }\n observe(\n (async () => {\n const name = await (opName ?? Promise.resolve(\"anonymous\"));\n let json: unknown;\n try {\n json = clone ? JSON.parse(await clone.text()) : undefined;\n } catch {\n json = undefined;\n }\n const out = classifyGraphql(status, json);\n emit({\n type: \"graphql\",\n name,\n ts,\n status,\n ok: out.ok,\n durationMs,\n error: out.ok ? undefined : { code: out.code ?? `HTTP_${status}`, message: out.message },\n });\n })(),\n );\n } else {\n const ok = status < 400;\n emit({ type: \"rest\", name: `${method} ${url.host}${url.pathname}`, ts, status, ok, durationMs, error: ok ? undefined : { code: `HTTP_${status}` } });\n }\n } catch {\n /* ignore */\n }\n return res;\n } as typeof fetch;\n\n // ---- instrument (@ikas/admin-api-client) -----------------------------------------------------\n\n let warnedInstrument = false;\n const instrument: Kanca[\"instrument\"] = (client, opts) => {\n const c = client as unknown as {\n [MARK]?: boolean;\n _client?: { request?: unknown };\n createGraphQLClient?: (...a: unknown[]) => unknown;\n tokenData?: { merchantId?: unknown };\n };\n try {\n if (!c || typeof c !== \"object\" || typeof c.createGraphQLClient !== \"function\" || !c._client || typeof c._client.request !== \"function\") {\n if (!warnedInstrument) {\n warnedInstrument = true;\n warn(\n \"[kanca] instrument(): not an @ikas/admin-api-client client (expected createGraphQLClient() and _client.request()). Nothing will be recorded for it — use kanca.fetch instead.\",\n );\n }\n return client;\n }\n if (c[MARK]) return client;\n\n const merchantIdFor = (): string | undefined => {\n if (opts?.merchantId) return opts.merchantId;\n const m = c.tokenData?.merchantId;\n return typeof m === \"string\" && m ? m : undefined;\n };\n\n const wrap = (gc: unknown): unknown => {\n const g = gc as { [MARK]?: boolean; request?: (...a: unknown[]) => Promise<unknown> };\n if (!g || typeof g.request !== \"function\" || g[MARK]) return gc;\n const orig = g.request;\n g.request = async function (this: unknown, ...args: unknown[]) {\n let name = \"anonymous\";\n try {\n const a0 = args[0] as { document?: unknown; operationName?: unknown; loc?: { source?: { body?: unknown } } } | string | undefined;\n const doc = typeof a0 === \"string\" ? a0 : a0 && typeof a0 === \"object\" && \"document\" in a0 ? a0.document : a0;\n const text =\n typeof doc === \"string\" ? doc : (doc as { loc?: { source?: { body?: unknown } } } | undefined)?.loc?.source?.body;\n name = operationName(text, typeof a0 === \"object\" ? a0?.operationName : undefined);\n } catch {\n /* keep anonymous */\n }\n const ts = Date.now();\n const t0 = now();\n try {\n const data = await orig.apply(this, args);\n // Success means the library saw a 2xx without `errors`; the exact status is not exposed, so it is omitted.\n emit({ type: \"graphql\", name, ts, ok: true, durationMs: now() - t0, merchantId: merchantIdFor() });\n return data;\n } catch (err) {\n try {\n const durationMs = now() - t0;\n const resp = (err as { response?: { status?: unknown; errors?: unknown } } | undefined)?.response;\n if (resp && typeof resp.status === \"number\") {\n const out = classifyGraphql(resp.status, { errors: Array.isArray(resp.errors) ? resp.errors : [] });\n // A thrown ClientError is always a failure, even for a 2xx without parsable errors.\n emit({\n type: \"graphql\",\n name,\n ts,\n status: resp.status,\n ok: false,\n durationMs,\n merchantId: merchantIdFor(),\n error: { code: out.code ?? `HTTP_${resp.status}`, message: out.message },\n });\n } else {\n emit({ type: \"graphql\", name, ts, ok: false, durationMs, merchantId: merchantIdFor(), error: { code: \"NETWORK\", message: errorMessage(err) } });\n }\n } catch {\n /* ignore */\n }\n throw err;\n }\n };\n g[MARK] = true;\n return gc;\n };\n\n const origCreate = c.createGraphQLClient;\n // The client rebuilds `_client` after a token refresh, so wrap the factory too.\n c.createGraphQLClient = function (this: unknown, ...a: unknown[]) {\n return wrap(origCreate.apply(this, a));\n };\n c._client = wrap(c._client) as typeof c._client;\n c[MARK] = true;\n log(\"instrumented @ikas/admin-api-client\");\n } catch (e) {\n log(\"instrument failed\", e);\n }\n return client;\n };\n\n // ---- track -----------------------------------------------------------------------------------\n\n const track = (type: EventType, fields: TrackFields = {}): void => {\n try {\n const f = fields ?? {};\n let name = f.name;\n if (type === \"install\" || type === \"uninstall\") name = type;\n let ok = f.ok;\n if (ok === undefined && typeof f.status === \"number\") {\n if (type === \"webhook\") ok = f.status === 200;\n else if (type === \"graphql\") ok = f.status >= 200 && f.status < 300;\n else if (type === \"rest\") ok = f.status < 400;\n }\n const attrs = { ...(f.attrs ?? {}) };\n if (typeof f.storeName === \"string\" && f.storeName) attrs.storeName = f.storeName;\n emit({\n type,\n name: name ?? \"unknown\",\n ts: f.ts ?? Date.now(),\n status: f.status,\n ok,\n durationMs: f.durationMs,\n value: f.value,\n error: f.error,\n merchantId: f.merchantId,\n deliveryId: f.deliveryId,\n attrs,\n });\n if (type === \"install\") transport.scheduleSoon();\n } catch (e) {\n log(\"track failed\", e);\n }\n };\n\n log(`enabled → ${options.endpoint || DEFAULT_ENDPOINT}`);\n\n return {\n webhook,\n fetch: kfetch,\n instrument,\n track: track as Kanca[\"track\"],\n flush,\n enabled: true,\n };\n}\n"],"mappings":"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","names":["durationMs"]}
|
package/dist/react.cjs
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
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/react.ts
|
|
22
|
+
var react_exports = {};
|
|
23
|
+
__export(react_exports, {
|
|
24
|
+
KancaVitals: () => KancaVitals,
|
|
25
|
+
reportVitals: () => reportVitals
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(react_exports);
|
|
28
|
+
var import_react = require("react");
|
|
29
|
+
|
|
30
|
+
// src/browser.ts
|
|
31
|
+
var import_web_vitals = require("web-vitals");
|
|
32
|
+
|
|
33
|
+
// src/shared.ts
|
|
34
|
+
var SDK_NAME = "@kanca-app/ikas";
|
|
35
|
+
var SDK_VERSION = "0.1.1";
|
|
36
|
+
var DEFAULT_ENDPOINT = "https://kanca.onurhan.dev";
|
|
37
|
+
var MAX_NAME = 200;
|
|
38
|
+
var MAX_ERROR_MESSAGE = 500;
|
|
39
|
+
var MAX_ERROR_CODE = 100;
|
|
40
|
+
var MAX_ATTR_KEYS = 20;
|
|
41
|
+
var MAX_ATTR_STRING = 500;
|
|
42
|
+
var MAX_ATTR_KEY = 64;
|
|
43
|
+
var MAX_BATCH_BYTES = 480 * 1024;
|
|
44
|
+
function truncate(s, max) {
|
|
45
|
+
return s.length > max ? s.slice(0, max) : s;
|
|
46
|
+
}
|
|
47
|
+
function hex(bytes) {
|
|
48
|
+
let out = "";
|
|
49
|
+
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
function uuid() {
|
|
53
|
+
const c = globalThis.crypto;
|
|
54
|
+
if (c && typeof c.randomUUID === "function") return c.randomUUID();
|
|
55
|
+
const b = new Uint8Array(16);
|
|
56
|
+
if (c && typeof c.getRandomValues === "function") c.getRandomValues(b);
|
|
57
|
+
else for (let i = 0; i < 16; i++) b[i] = Math.floor(Math.random() * 256);
|
|
58
|
+
b[6] = b[6] & 15 | 64;
|
|
59
|
+
b[8] = b[8] & 63 | 128;
|
|
60
|
+
const h = hex(b);
|
|
61
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
62
|
+
}
|
|
63
|
+
function isFiniteNumber(v) {
|
|
64
|
+
return typeof v === "number" && Number.isFinite(v);
|
|
65
|
+
}
|
|
66
|
+
function cleanAttrs(attrs) {
|
|
67
|
+
if (!attrs || typeof attrs !== "object") return void 0;
|
|
68
|
+
const out = {};
|
|
69
|
+
let n = 0;
|
|
70
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
71
|
+
if (n >= MAX_ATTR_KEYS) break;
|
|
72
|
+
if (v === void 0 || v === null || !k) continue;
|
|
73
|
+
let val;
|
|
74
|
+
if (typeof v === "boolean") val = v;
|
|
75
|
+
else if (typeof v === "number") {
|
|
76
|
+
if (!Number.isFinite(v)) continue;
|
|
77
|
+
val = v;
|
|
78
|
+
} else if (typeof v === "string") val = truncate(v, MAX_ATTR_STRING);
|
|
79
|
+
else continue;
|
|
80
|
+
out[truncate(k, MAX_ATTR_KEY)] = val;
|
|
81
|
+
n++;
|
|
82
|
+
}
|
|
83
|
+
return n > 0 ? out : void 0;
|
|
84
|
+
}
|
|
85
|
+
function sanitizeEvent(e) {
|
|
86
|
+
const out = {
|
|
87
|
+
id: typeof e.id === "string" && e.id ? e.id : uuid(),
|
|
88
|
+
type: e.type,
|
|
89
|
+
ts: isFiniteNumber(e.ts) ? Math.round(e.ts) : Date.now(),
|
|
90
|
+
name: truncate(typeof e.name === "string" && e.name ? e.name : "unknown", MAX_NAME)
|
|
91
|
+
};
|
|
92
|
+
if (isFiniteNumber(e.status) && e.status >= 100 && e.status <= 599) out.status = Math.trunc(e.status);
|
|
93
|
+
if (typeof e.ok === "boolean") out.ok = e.ok;
|
|
94
|
+
if (isFiniteNumber(e.durationMs)) out.durationMs = Math.min(864e5, Math.max(0, Math.round(e.durationMs * 10) / 10));
|
|
95
|
+
if (isFiniteNumber(e.value)) out.value = e.value;
|
|
96
|
+
if (e.error && typeof e.error.code === "string") {
|
|
97
|
+
out.error = { code: truncate(e.error.code, MAX_ERROR_CODE) };
|
|
98
|
+
if (typeof e.error.message === "string" && e.error.message) {
|
|
99
|
+
out.error.message = truncate(e.error.message, MAX_ERROR_MESSAGE);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (typeof e.merchantId === "string" && e.merchantId) out.merchantId = truncate(e.merchantId, 200);
|
|
103
|
+
if (typeof e.deliveryId === "string" && e.deliveryId) out.deliveryId = truncate(e.deliveryId, 200);
|
|
104
|
+
const attrs = cleanAttrs(e.attrs);
|
|
105
|
+
if (attrs) out.attrs = attrs;
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
function joinUrl(endpoint, path) {
|
|
109
|
+
return endpoint.replace(/\/+$/, "") + path;
|
|
110
|
+
}
|
|
111
|
+
var encoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : void 0;
|
|
112
|
+
|
|
113
|
+
// src/browser.ts
|
|
114
|
+
var VITALS = ["LCP", "INP", "CLS", "FCP", "TTFB"];
|
|
115
|
+
var FLAG = "__kancaVitalsStarted";
|
|
116
|
+
var g = globalThis;
|
|
117
|
+
function reportVitals(options) {
|
|
118
|
+
try {
|
|
119
|
+
if (g[FLAG] || typeof window === "undefined" || typeof document === "undefined") return;
|
|
120
|
+
const key = typeof options?.publicKey === "string" ? options.publicKey.trim() : "";
|
|
121
|
+
const debug = !!options?.debug;
|
|
122
|
+
if (!key.startsWith("kp_")) {
|
|
123
|
+
if (debug || key) console.warn("[kanca] reportVitals(): a public key (kp_...) is required; vitals disabled.");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
g[FLAG] = true;
|
|
127
|
+
const url = `${joinUrl(options.endpoint || DEFAULT_ENDPOINT, "/api/ingest/browser")}?key=${encodeURIComponent(key)}`;
|
|
128
|
+
const path = location.pathname;
|
|
129
|
+
const ts = typeof performance !== "undefined" && typeof performance.timeOrigin === "number" && performance.timeOrigin > 0 ? Math.round(performance.timeOrigin) : Date.now();
|
|
130
|
+
const queue = [];
|
|
131
|
+
const seen = /* @__PURE__ */ new Set();
|
|
132
|
+
const send = () => {
|
|
133
|
+
if (!queue.length) return;
|
|
134
|
+
const events = queue.splice(0, queue.length);
|
|
135
|
+
const body = JSON.stringify({ sdk: { name: SDK_NAME, version: SDK_VERSION }, events });
|
|
136
|
+
if (debug) console.debug("[kanca] vitals", events);
|
|
137
|
+
try {
|
|
138
|
+
if (typeof fetch === "function") {
|
|
139
|
+
void fetch(url, { method: "POST", body, keepalive: true, mode: "cors", credentials: "omit", headers: { "content-type": "text/plain" } }).catch(() => void 0);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") navigator.sendBeacon(url, body);
|
|
146
|
+
} catch {
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
const onMetric = (m) => {
|
|
150
|
+
try {
|
|
151
|
+
if (!VITALS.includes(m.name) || seen.has(m.name)) return;
|
|
152
|
+
seen.add(m.name);
|
|
153
|
+
queue.push(sanitizeEvent({ id: uuid(), type: "vital", ts, name: m.name, value: m.value, attrs: { path, rating: m.rating } }));
|
|
154
|
+
} catch {
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
(0, import_web_vitals.onLCP)(onMetric);
|
|
158
|
+
(0, import_web_vitals.onINP)(onMetric);
|
|
159
|
+
(0, import_web_vitals.onCLS)(onMetric);
|
|
160
|
+
(0, import_web_vitals.onFCP)(onMetric);
|
|
161
|
+
(0, import_web_vitals.onTTFB)(onMetric);
|
|
162
|
+
addEventListener(
|
|
163
|
+
"visibilitychange",
|
|
164
|
+
() => {
|
|
165
|
+
if (document.visibilityState === "hidden") queueMicrotask(send);
|
|
166
|
+
},
|
|
167
|
+
true
|
|
168
|
+
);
|
|
169
|
+
addEventListener("pagehide", send, true);
|
|
170
|
+
} catch {
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/react.ts
|
|
175
|
+
function KancaVitals({ publicKey, endpoint, debug }) {
|
|
176
|
+
(0, import_react.useEffect)(() => {
|
|
177
|
+
reportVitals({ publicKey, endpoint, debug });
|
|
178
|
+
}, []);
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=react.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react.ts","../src/browser.ts","../src/shared.ts"],"sourcesContent":["// @kanca-app/ikas/react — <KancaVitals /> client component. The build prepends \"use client\".\nimport { useEffect } from \"react\";\nimport { reportVitals, type ReportVitalsOptions } from \"./browser\";\n\nexport type KancaVitalsProps = ReportVitalsOptions;\n\n/**\n * Reports Core Web Vitals of the admin UI to Kanca. Render once, e.g. in the\n * root layout. Renders nothing.\n */\nexport function KancaVitals({ publicKey, endpoint, debug }: KancaVitalsProps): null {\n useEffect(() => {\n reportVitals({ publicKey, endpoint, debug });\n // reportVitals is idempotent; later prop changes are intentionally ignored.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n return null;\n}\n\nexport { reportVitals };\nexport type { ReportVitalsOptions };\n","// @kanca-app/ikas/browser — Core Web Vitals for the ikas admin iframe.\n// Sends only { name, value, rating, pathname } per metric. Uses a public key (kp_...).\n\nimport { onCLS, onFCP, onINP, onLCP, onTTFB, type Metric } from \"web-vitals\";\nimport { DEFAULT_ENDPOINT, SDK_NAME, SDK_VERSION, joinUrl, sanitizeEvent, uuid, type KancaEvent } from \"./shared\";\n\nexport interface ReportVitalsOptions {\n /** Public key (`kp_...`). Safe to expose in the browser; only accepts `vital` events. */\n publicKey: string | undefined;\n /** Kanca base URL. Default `https://kanca.onurhan.dev`. */\n endpoint?: string;\n debug?: boolean;\n}\n\nconst VITALS = [\"LCP\", \"INP\", \"CLS\", \"FCP\", \"TTFB\"] as const;\n\n// Global (not module) flag so duplicate copies (ESM + CJS, browser + react bundles) still start once.\nconst FLAG = \"__kancaVitalsStarted\";\nconst g = globalThis as { [FLAG]?: boolean };\n\n/**\n * Starts collecting LCP, INP, CLS, FCP and TTFB (final values, once per page\n * load per metric) and sends them when the page is hidden or unloaded.\n * Safe to call more than once — only the first call has an effect.\n */\nexport function reportVitals(options: ReportVitalsOptions): void {\n try {\n if (g[FLAG] || typeof window === \"undefined\" || typeof document === \"undefined\") return;\n const key = typeof options?.publicKey === \"string\" ? options.publicKey.trim() : \"\";\n const debug = !!options?.debug;\n if (!key.startsWith(\"kp_\")) {\n if (debug || key) console.warn(\"[kanca] reportVitals(): a public key (kp_...) is required; vitals disabled.\");\n return;\n }\n g[FLAG] = true;\n\n const url = `${joinUrl(options.endpoint || DEFAULT_ENDPOINT, \"/api/ingest/browser\")}?key=${encodeURIComponent(key)}`;\n const path = location.pathname;\n const ts =\n typeof performance !== \"undefined\" && typeof performance.timeOrigin === \"number\" && performance.timeOrigin > 0\n ? Math.round(performance.timeOrigin)\n : Date.now();\n const queue: KancaEvent[] = [];\n const seen = new Set<string>();\n\n const send = () => {\n if (!queue.length) return;\n const events = queue.splice(0, queue.length);\n const body = JSON.stringify({ sdk: { name: SDK_NAME, version: SDK_VERSION }, events });\n if (debug) console.debug(\"[kanca] vitals\", events);\n // fetch+keepalive first: content blockers drop sendBeacon (\"ping\") requests\n // while sendBeacon still returns true, so vitals would vanish silently.\n // A text/plain body keeps it a simple request (no CORS preflight).\n try {\n if (typeof fetch === \"function\") {\n void fetch(url, { method: \"POST\", body, keepalive: true, mode: \"cors\", credentials: \"omit\", headers: { \"content-type\": \"text/plain\" } }).catch(() => undefined);\n return;\n }\n } catch {\n /* fall through */\n }\n try {\n if (typeof navigator !== \"undefined\" && typeof navigator.sendBeacon === \"function\") navigator.sendBeacon(url, body);\n } catch {\n /* never throw */\n }\n };\n\n const onMetric = (m: Metric) => {\n try {\n if (!(VITALS as readonly string[]).includes(m.name) || seen.has(m.name)) return;\n seen.add(m.name);\n queue.push(sanitizeEvent({ id: uuid(), type: \"vital\", ts, name: m.name, value: m.value, attrs: { path, rating: m.rating } }));\n } catch {\n /* ignore */\n }\n };\n\n // Without reportAllChanges, web-vitals reports each metric's final value.\n onLCP(onMetric);\n onINP(onMetric);\n onCLS(onMetric);\n onFCP(onMetric);\n onTTFB(onMetric);\n\n // Registered after web-vitals' own listeners, so final values are queued before we send.\n addEventListener(\n \"visibilitychange\",\n () => {\n // Microtask: lets any report web-vitals triggers on this same event land first.\n if (document.visibilityState === \"hidden\") queueMicrotask(send);\n },\n true,\n );\n addEventListener(\"pagehide\", send, true);\n } catch {\n /* never break the host page */\n }\n}\n\n/** @internal for tests */\nexport function __resetVitalsForTests(): void {\n g[FLAG] = false;\n}\n","// Shared, runtime-agnostic helpers (used by both the Node and browser entries).\n// Wire format: see docs/PROTOCOL.md (v1).\n\nexport const SDK_NAME = \"@kanca-app/ikas\";\nexport const SDK_VERSION = \"0.1.1\";\nexport const DEFAULT_ENDPOINT = \"https://kanca.onurhan.dev\";\n\nexport const MAX_NAME = 200;\nexport const MAX_ERROR_MESSAGE = 500;\nexport const MAX_ERROR_CODE = 100;\nexport const MAX_ATTR_KEYS = 20;\nexport const MAX_ATTR_STRING = 500;\nexport const MAX_ATTR_KEY = 64;\nexport const MAX_EVENTS_PER_BATCH = 500;\n/** Protocol limit is 512 KB; keep headroom for the envelope. */\nexport const MAX_BATCH_BYTES = 480 * 1024;\n\nexport type EventType = \"webhook\" | \"graphql\" | \"rest\" | \"install\" | \"uninstall\" | \"vital\";\n\nexport type AttrValue = string | number | boolean;\n\nexport interface KancaError {\n code: string;\n message?: string;\n}\n\n/** A single wire event (PROTOCOL.md → Event). */\nexport interface KancaEvent {\n id: string;\n type: EventType;\n ts: number;\n name: string;\n status?: number;\n ok?: boolean;\n durationMs?: number;\n value?: number;\n error?: KancaError;\n merchantId?: string;\n deliveryId?: string;\n attrs?: Record<string, AttrValue>;\n}\n\nexport interface Batch {\n sdk: { name: string; version: string };\n events: KancaEvent[];\n}\n\nexport function truncate(s: string, max: number): string {\n return s.length > max ? s.slice(0, max) : s;\n}\n\nfunction hex(bytes: Uint8Array): string {\n let out = \"\";\n for (const b of bytes) out += b.toString(16).padStart(2, \"0\");\n return out;\n}\n\n/** UUID v4 via crypto.randomUUID, with a getRandomValues fallback (non-secure browser contexts). */\nexport function uuid(): string {\n const c = (globalThis as { crypto?: Crypto }).crypto;\n if (c && typeof c.randomUUID === \"function\") return c.randomUUID();\n const b = new Uint8Array(16);\n if (c && typeof c.getRandomValues === \"function\") c.getRandomValues(b);\n else for (let i = 0; i < 16; i++) b[i] = Math.floor(Math.random() * 256);\n b[6] = (b[6]! & 0x0f) | 0x40;\n b[8] = (b[8]! & 0x3f) | 0x80;\n const h = hex(b);\n return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;\n}\n\nfunction isFiniteNumber(v: unknown): v is number {\n return typeof v === \"number\" && Number.isFinite(v);\n}\n\nfunction cleanAttrs(attrs: unknown): Record<string, AttrValue> | undefined {\n if (!attrs || typeof attrs !== \"object\") return undefined;\n const out: Record<string, AttrValue> = {};\n let n = 0;\n for (const [k, v] of Object.entries(attrs as Record<string, unknown>)) {\n if (n >= MAX_ATTR_KEYS) break;\n if (v === undefined || v === null || !k) continue;\n let val: AttrValue;\n if (typeof v === \"boolean\") val = v;\n else if (typeof v === \"number\") {\n if (!Number.isFinite(v)) continue;\n val = v;\n } else if (typeof v === \"string\") val = truncate(v, MAX_ATTR_STRING);\n else continue; // attrs are flat: nested values are dropped, never stringified\n out[truncate(k, MAX_ATTR_KEY)] = val;\n n++;\n }\n return n > 0 ? out : undefined;\n}\n\n/**\n * Normalises an event so it always satisfies the protocol limits. Unknown\n * fields are dropped — only the whitelisted metadata below ever leaves the process.\n */\nexport function sanitizeEvent(e: KancaEvent): KancaEvent {\n const out: KancaEvent = {\n id: typeof e.id === \"string\" && e.id ? e.id : uuid(),\n type: e.type,\n ts: isFiniteNumber(e.ts) ? Math.round(e.ts) : Date.now(),\n name: truncate(typeof e.name === \"string\" && e.name ? e.name : \"unknown\", MAX_NAME),\n };\n if (isFiniteNumber(e.status) && e.status >= 100 && e.status <= 599) out.status = Math.trunc(e.status);\n if (typeof e.ok === \"boolean\") out.ok = e.ok;\n if (isFiniteNumber(e.durationMs)) out.durationMs = Math.min(86_400_000, Math.max(0, Math.round(e.durationMs * 10) / 10));\n if (isFiniteNumber(e.value)) out.value = e.value;\n if (e.error && typeof e.error.code === \"string\") {\n out.error = { code: truncate(e.error.code, MAX_ERROR_CODE) };\n if (typeof e.error.message === \"string\" && e.error.message) {\n out.error.message = truncate(e.error.message, MAX_ERROR_MESSAGE);\n }\n }\n if (typeof e.merchantId === \"string\" && e.merchantId) out.merchantId = truncate(e.merchantId, 200);\n if (typeof e.deliveryId === \"string\" && e.deliveryId) out.deliveryId = truncate(e.deliveryId, 200);\n const attrs = cleanAttrs(e.attrs);\n if (attrs) out.attrs = attrs;\n return out;\n}\n\nexport function errorMessage(err: unknown): string | undefined {\n try {\n if (err instanceof Error) return truncate(err.message || err.name, MAX_ERROR_MESSAGE);\n if (typeof err === \"string\") return truncate(err, MAX_ERROR_MESSAGE);\n } catch {\n /* ignore */\n }\n return undefined;\n}\n\nexport function joinUrl(endpoint: string, path: string): string {\n return endpoint.replace(/\\/+$/, \"\") + path;\n}\n\nconst encoder = typeof TextEncoder !== \"undefined\" ? new TextEncoder() : undefined;\nexport function byteLength(s: string): number {\n return encoder ? encoder.encode(s).length : s.length * 3;\n}\n\n/**\n * Splits events into protocol-sized batches (<= maxEvents events and\n * <= MAX_BATCH_BYTES serialized). Returns serialized bodies.\n */\nexport function buildBodies(events: KancaEvent[], maxEvents: number): { body: string; events: KancaEvent[] }[] {\n const sdk = { name: SDK_NAME, version: SDK_VERSION };\n const envelope = byteLength(JSON.stringify({ sdk, events: [] }));\n const out: { body: string; events: KancaEvent[] }[] = [];\n let cur: KancaEvent[] = [];\n let size = envelope;\n const push = () => {\n if (cur.length) out.push({ body: JSON.stringify({ sdk, events: cur } satisfies Batch), events: cur });\n cur = [];\n size = envelope;\n };\n for (const ev of events) {\n const len = byteLength(JSON.stringify(ev)) + 1;\n if (cur.length && (cur.length >= maxEvents || size + len > MAX_BATCH_BYTES)) push();\n cur.push(ev);\n size += len;\n }\n push();\n return out;\n}\n"],"mappings":"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","names":[]}
|
package/dist/react.d.cts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface ReportVitalsOptions {
|
|
2
|
+
/** Public key (`kp_...`). Safe to expose in the browser; only accepts `vital` events. */
|
|
3
|
+
publicKey: string | undefined;
|
|
4
|
+
/** Kanca base URL. Default `https://kanca.onurhan.dev`. */
|
|
5
|
+
endpoint?: string;
|
|
6
|
+
debug?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Starts collecting LCP, INP, CLS, FCP and TTFB (final values, once per page
|
|
10
|
+
* load per metric) and sends them when the page is hidden or unloaded.
|
|
11
|
+
* Safe to call more than once — only the first call has an effect.
|
|
12
|
+
*/
|
|
13
|
+
declare function reportVitals(options: ReportVitalsOptions): void;
|
|
14
|
+
|
|
15
|
+
type KancaVitalsProps = ReportVitalsOptions;
|
|
16
|
+
/**
|
|
17
|
+
* Reports Core Web Vitals of the admin UI to Kanca. Render once, e.g. in the
|
|
18
|
+
* root layout. Renders nothing.
|
|
19
|
+
*/
|
|
20
|
+
declare function KancaVitals({ publicKey, endpoint, debug }: KancaVitalsProps): null;
|
|
21
|
+
|
|
22
|
+
export { KancaVitals, type KancaVitalsProps, type ReportVitalsOptions, reportVitals };
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface ReportVitalsOptions {
|
|
2
|
+
/** Public key (`kp_...`). Safe to expose in the browser; only accepts `vital` events. */
|
|
3
|
+
publicKey: string | undefined;
|
|
4
|
+
/** Kanca base URL. Default `https://kanca.onurhan.dev`. */
|
|
5
|
+
endpoint?: string;
|
|
6
|
+
debug?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Starts collecting LCP, INP, CLS, FCP and TTFB (final values, once per page
|
|
10
|
+
* load per metric) and sends them when the page is hidden or unloaded.
|
|
11
|
+
* Safe to call more than once — only the first call has an effect.
|
|
12
|
+
*/
|
|
13
|
+
declare function reportVitals(options: ReportVitalsOptions): void;
|
|
14
|
+
|
|
15
|
+
type KancaVitalsProps = ReportVitalsOptions;
|
|
16
|
+
/**
|
|
17
|
+
* Reports Core Web Vitals of the admin UI to Kanca. Render once, e.g. in the
|
|
18
|
+
* root layout. Renders nothing.
|
|
19
|
+
*/
|
|
20
|
+
declare function KancaVitals({ publicKey, endpoint, debug }: KancaVitalsProps): null;
|
|
21
|
+
|
|
22
|
+
export { KancaVitals, type KancaVitalsProps, type ReportVitalsOptions, reportVitals };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/react.ts
|
|
4
|
+
import { useEffect } from "react";
|
|
5
|
+
|
|
6
|
+
// src/browser.ts
|
|
7
|
+
import { onCLS, onFCP, onINP, onLCP, onTTFB } from "web-vitals";
|
|
8
|
+
|
|
9
|
+
// src/shared.ts
|
|
10
|
+
var SDK_NAME = "@kanca-app/ikas";
|
|
11
|
+
var SDK_VERSION = "0.1.1";
|
|
12
|
+
var DEFAULT_ENDPOINT = "https://kanca.onurhan.dev";
|
|
13
|
+
var MAX_NAME = 200;
|
|
14
|
+
var MAX_ERROR_MESSAGE = 500;
|
|
15
|
+
var MAX_ERROR_CODE = 100;
|
|
16
|
+
var MAX_ATTR_KEYS = 20;
|
|
17
|
+
var MAX_ATTR_STRING = 500;
|
|
18
|
+
var MAX_ATTR_KEY = 64;
|
|
19
|
+
var MAX_BATCH_BYTES = 480 * 1024;
|
|
20
|
+
function truncate(s, max) {
|
|
21
|
+
return s.length > max ? s.slice(0, max) : s;
|
|
22
|
+
}
|
|
23
|
+
function hex(bytes) {
|
|
24
|
+
let out = "";
|
|
25
|
+
for (const b of bytes) out += b.toString(16).padStart(2, "0");
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
function uuid() {
|
|
29
|
+
const c = globalThis.crypto;
|
|
30
|
+
if (c && typeof c.randomUUID === "function") return c.randomUUID();
|
|
31
|
+
const b = new Uint8Array(16);
|
|
32
|
+
if (c && typeof c.getRandomValues === "function") c.getRandomValues(b);
|
|
33
|
+
else for (let i = 0; i < 16; i++) b[i] = Math.floor(Math.random() * 256);
|
|
34
|
+
b[6] = b[6] & 15 | 64;
|
|
35
|
+
b[8] = b[8] & 63 | 128;
|
|
36
|
+
const h = hex(b);
|
|
37
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
|
|
38
|
+
}
|
|
39
|
+
function isFiniteNumber(v) {
|
|
40
|
+
return typeof v === "number" && Number.isFinite(v);
|
|
41
|
+
}
|
|
42
|
+
function cleanAttrs(attrs) {
|
|
43
|
+
if (!attrs || typeof attrs !== "object") return void 0;
|
|
44
|
+
const out = {};
|
|
45
|
+
let n = 0;
|
|
46
|
+
for (const [k, v] of Object.entries(attrs)) {
|
|
47
|
+
if (n >= MAX_ATTR_KEYS) break;
|
|
48
|
+
if (v === void 0 || v === null || !k) continue;
|
|
49
|
+
let val;
|
|
50
|
+
if (typeof v === "boolean") val = v;
|
|
51
|
+
else if (typeof v === "number") {
|
|
52
|
+
if (!Number.isFinite(v)) continue;
|
|
53
|
+
val = v;
|
|
54
|
+
} else if (typeof v === "string") val = truncate(v, MAX_ATTR_STRING);
|
|
55
|
+
else continue;
|
|
56
|
+
out[truncate(k, MAX_ATTR_KEY)] = val;
|
|
57
|
+
n++;
|
|
58
|
+
}
|
|
59
|
+
return n > 0 ? out : void 0;
|
|
60
|
+
}
|
|
61
|
+
function sanitizeEvent(e) {
|
|
62
|
+
const out = {
|
|
63
|
+
id: typeof e.id === "string" && e.id ? e.id : uuid(),
|
|
64
|
+
type: e.type,
|
|
65
|
+
ts: isFiniteNumber(e.ts) ? Math.round(e.ts) : Date.now(),
|
|
66
|
+
name: truncate(typeof e.name === "string" && e.name ? e.name : "unknown", MAX_NAME)
|
|
67
|
+
};
|
|
68
|
+
if (isFiniteNumber(e.status) && e.status >= 100 && e.status <= 599) out.status = Math.trunc(e.status);
|
|
69
|
+
if (typeof e.ok === "boolean") out.ok = e.ok;
|
|
70
|
+
if (isFiniteNumber(e.durationMs)) out.durationMs = Math.min(864e5, Math.max(0, Math.round(e.durationMs * 10) / 10));
|
|
71
|
+
if (isFiniteNumber(e.value)) out.value = e.value;
|
|
72
|
+
if (e.error && typeof e.error.code === "string") {
|
|
73
|
+
out.error = { code: truncate(e.error.code, MAX_ERROR_CODE) };
|
|
74
|
+
if (typeof e.error.message === "string" && e.error.message) {
|
|
75
|
+
out.error.message = truncate(e.error.message, MAX_ERROR_MESSAGE);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (typeof e.merchantId === "string" && e.merchantId) out.merchantId = truncate(e.merchantId, 200);
|
|
79
|
+
if (typeof e.deliveryId === "string" && e.deliveryId) out.deliveryId = truncate(e.deliveryId, 200);
|
|
80
|
+
const attrs = cleanAttrs(e.attrs);
|
|
81
|
+
if (attrs) out.attrs = attrs;
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
function joinUrl(endpoint, path) {
|
|
85
|
+
return endpoint.replace(/\/+$/, "") + path;
|
|
86
|
+
}
|
|
87
|
+
var encoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : void 0;
|
|
88
|
+
|
|
89
|
+
// src/browser.ts
|
|
90
|
+
var VITALS = ["LCP", "INP", "CLS", "FCP", "TTFB"];
|
|
91
|
+
var FLAG = "__kancaVitalsStarted";
|
|
92
|
+
var g = globalThis;
|
|
93
|
+
function reportVitals(options) {
|
|
94
|
+
try {
|
|
95
|
+
if (g[FLAG] || typeof window === "undefined" || typeof document === "undefined") return;
|
|
96
|
+
const key = typeof options?.publicKey === "string" ? options.publicKey.trim() : "";
|
|
97
|
+
const debug = !!options?.debug;
|
|
98
|
+
if (!key.startsWith("kp_")) {
|
|
99
|
+
if (debug || key) console.warn("[kanca] reportVitals(): a public key (kp_...) is required; vitals disabled.");
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
g[FLAG] = true;
|
|
103
|
+
const url = `${joinUrl(options.endpoint || DEFAULT_ENDPOINT, "/api/ingest/browser")}?key=${encodeURIComponent(key)}`;
|
|
104
|
+
const path = location.pathname;
|
|
105
|
+
const ts = typeof performance !== "undefined" && typeof performance.timeOrigin === "number" && performance.timeOrigin > 0 ? Math.round(performance.timeOrigin) : Date.now();
|
|
106
|
+
const queue = [];
|
|
107
|
+
const seen = /* @__PURE__ */ new Set();
|
|
108
|
+
const send = () => {
|
|
109
|
+
if (!queue.length) return;
|
|
110
|
+
const events = queue.splice(0, queue.length);
|
|
111
|
+
const body = JSON.stringify({ sdk: { name: SDK_NAME, version: SDK_VERSION }, events });
|
|
112
|
+
if (debug) console.debug("[kanca] vitals", events);
|
|
113
|
+
try {
|
|
114
|
+
if (typeof fetch === "function") {
|
|
115
|
+
void fetch(url, { method: "POST", body, keepalive: true, mode: "cors", credentials: "omit", headers: { "content-type": "text/plain" } }).catch(() => void 0);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
} catch {
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") navigator.sendBeacon(url, body);
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
const onMetric = (m) => {
|
|
126
|
+
try {
|
|
127
|
+
if (!VITALS.includes(m.name) || seen.has(m.name)) return;
|
|
128
|
+
seen.add(m.name);
|
|
129
|
+
queue.push(sanitizeEvent({ id: uuid(), type: "vital", ts, name: m.name, value: m.value, attrs: { path, rating: m.rating } }));
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
onLCP(onMetric);
|
|
134
|
+
onINP(onMetric);
|
|
135
|
+
onCLS(onMetric);
|
|
136
|
+
onFCP(onMetric);
|
|
137
|
+
onTTFB(onMetric);
|
|
138
|
+
addEventListener(
|
|
139
|
+
"visibilitychange",
|
|
140
|
+
() => {
|
|
141
|
+
if (document.visibilityState === "hidden") queueMicrotask(send);
|
|
142
|
+
},
|
|
143
|
+
true
|
|
144
|
+
);
|
|
145
|
+
addEventListener("pagehide", send, true);
|
|
146
|
+
} catch {
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/react.ts
|
|
151
|
+
function KancaVitals({ publicKey, endpoint, debug }) {
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
reportVitals({ publicKey, endpoint, debug });
|
|
154
|
+
}, []);
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
export {
|
|
158
|
+
KancaVitals,
|
|
159
|
+
reportVitals
|
|
160
|
+
};
|
|
161
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react.ts","../src/browser.ts","../src/shared.ts"],"sourcesContent":["// @kanca-app/ikas/react — <KancaVitals /> client component. The build prepends \"use client\".\nimport { useEffect } from \"react\";\nimport { reportVitals, type ReportVitalsOptions } from \"./browser\";\n\nexport type KancaVitalsProps = ReportVitalsOptions;\n\n/**\n * Reports Core Web Vitals of the admin UI to Kanca. Render once, e.g. in the\n * root layout. Renders nothing.\n */\nexport function KancaVitals({ publicKey, endpoint, debug }: KancaVitalsProps): null {\n useEffect(() => {\n reportVitals({ publicKey, endpoint, debug });\n // reportVitals is idempotent; later prop changes are intentionally ignored.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n return null;\n}\n\nexport { reportVitals };\nexport type { ReportVitalsOptions };\n","// @kanca-app/ikas/browser — Core Web Vitals for the ikas admin iframe.\n// Sends only { name, value, rating, pathname } per metric. Uses a public key (kp_...).\n\nimport { onCLS, onFCP, onINP, onLCP, onTTFB, type Metric } from \"web-vitals\";\nimport { DEFAULT_ENDPOINT, SDK_NAME, SDK_VERSION, joinUrl, sanitizeEvent, uuid, type KancaEvent } from \"./shared\";\n\nexport interface ReportVitalsOptions {\n /** Public key (`kp_...`). Safe to expose in the browser; only accepts `vital` events. */\n publicKey: string | undefined;\n /** Kanca base URL. Default `https://kanca.onurhan.dev`. */\n endpoint?: string;\n debug?: boolean;\n}\n\nconst VITALS = [\"LCP\", \"INP\", \"CLS\", \"FCP\", \"TTFB\"] as const;\n\n// Global (not module) flag so duplicate copies (ESM + CJS, browser + react bundles) still start once.\nconst FLAG = \"__kancaVitalsStarted\";\nconst g = globalThis as { [FLAG]?: boolean };\n\n/**\n * Starts collecting LCP, INP, CLS, FCP and TTFB (final values, once per page\n * load per metric) and sends them when the page is hidden or unloaded.\n * Safe to call more than once — only the first call has an effect.\n */\nexport function reportVitals(options: ReportVitalsOptions): void {\n try {\n if (g[FLAG] || typeof window === \"undefined\" || typeof document === \"undefined\") return;\n const key = typeof options?.publicKey === \"string\" ? options.publicKey.trim() : \"\";\n const debug = !!options?.debug;\n if (!key.startsWith(\"kp_\")) {\n if (debug || key) console.warn(\"[kanca] reportVitals(): a public key (kp_...) is required; vitals disabled.\");\n return;\n }\n g[FLAG] = true;\n\n const url = `${joinUrl(options.endpoint || DEFAULT_ENDPOINT, \"/api/ingest/browser\")}?key=${encodeURIComponent(key)}`;\n const path = location.pathname;\n const ts =\n typeof performance !== \"undefined\" && typeof performance.timeOrigin === \"number\" && performance.timeOrigin > 0\n ? Math.round(performance.timeOrigin)\n : Date.now();\n const queue: KancaEvent[] = [];\n const seen = new Set<string>();\n\n const send = () => {\n if (!queue.length) return;\n const events = queue.splice(0, queue.length);\n const body = JSON.stringify({ sdk: { name: SDK_NAME, version: SDK_VERSION }, events });\n if (debug) console.debug(\"[kanca] vitals\", events);\n // fetch+keepalive first: content blockers drop sendBeacon (\"ping\") requests\n // while sendBeacon still returns true, so vitals would vanish silently.\n // A text/plain body keeps it a simple request (no CORS preflight).\n try {\n if (typeof fetch === \"function\") {\n void fetch(url, { method: \"POST\", body, keepalive: true, mode: \"cors\", credentials: \"omit\", headers: { \"content-type\": \"text/plain\" } }).catch(() => undefined);\n return;\n }\n } catch {\n /* fall through */\n }\n try {\n if (typeof navigator !== \"undefined\" && typeof navigator.sendBeacon === \"function\") navigator.sendBeacon(url, body);\n } catch {\n /* never throw */\n }\n };\n\n const onMetric = (m: Metric) => {\n try {\n if (!(VITALS as readonly string[]).includes(m.name) || seen.has(m.name)) return;\n seen.add(m.name);\n queue.push(sanitizeEvent({ id: uuid(), type: \"vital\", ts, name: m.name, value: m.value, attrs: { path, rating: m.rating } }));\n } catch {\n /* ignore */\n }\n };\n\n // Without reportAllChanges, web-vitals reports each metric's final value.\n onLCP(onMetric);\n onINP(onMetric);\n onCLS(onMetric);\n onFCP(onMetric);\n onTTFB(onMetric);\n\n // Registered after web-vitals' own listeners, so final values are queued before we send.\n addEventListener(\n \"visibilitychange\",\n () => {\n // Microtask: lets any report web-vitals triggers on this same event land first.\n if (document.visibilityState === \"hidden\") queueMicrotask(send);\n },\n true,\n );\n addEventListener(\"pagehide\", send, true);\n } catch {\n /* never break the host page */\n }\n}\n\n/** @internal for tests */\nexport function __resetVitalsForTests(): void {\n g[FLAG] = false;\n}\n","// Shared, runtime-agnostic helpers (used by both the Node and browser entries).\n// Wire format: see docs/PROTOCOL.md (v1).\n\nexport const SDK_NAME = \"@kanca-app/ikas\";\nexport const SDK_VERSION = \"0.1.1\";\nexport const DEFAULT_ENDPOINT = \"https://kanca.onurhan.dev\";\n\nexport const MAX_NAME = 200;\nexport const MAX_ERROR_MESSAGE = 500;\nexport const MAX_ERROR_CODE = 100;\nexport const MAX_ATTR_KEYS = 20;\nexport const MAX_ATTR_STRING = 500;\nexport const MAX_ATTR_KEY = 64;\nexport const MAX_EVENTS_PER_BATCH = 500;\n/** Protocol limit is 512 KB; keep headroom for the envelope. */\nexport const MAX_BATCH_BYTES = 480 * 1024;\n\nexport type EventType = \"webhook\" | \"graphql\" | \"rest\" | \"install\" | \"uninstall\" | \"vital\";\n\nexport type AttrValue = string | number | boolean;\n\nexport interface KancaError {\n code: string;\n message?: string;\n}\n\n/** A single wire event (PROTOCOL.md → Event). */\nexport interface KancaEvent {\n id: string;\n type: EventType;\n ts: number;\n name: string;\n status?: number;\n ok?: boolean;\n durationMs?: number;\n value?: number;\n error?: KancaError;\n merchantId?: string;\n deliveryId?: string;\n attrs?: Record<string, AttrValue>;\n}\n\nexport interface Batch {\n sdk: { name: string; version: string };\n events: KancaEvent[];\n}\n\nexport function truncate(s: string, max: number): string {\n return s.length > max ? s.slice(0, max) : s;\n}\n\nfunction hex(bytes: Uint8Array): string {\n let out = \"\";\n for (const b of bytes) out += b.toString(16).padStart(2, \"0\");\n return out;\n}\n\n/** UUID v4 via crypto.randomUUID, with a getRandomValues fallback (non-secure browser contexts). */\nexport function uuid(): string {\n const c = (globalThis as { crypto?: Crypto }).crypto;\n if (c && typeof c.randomUUID === \"function\") return c.randomUUID();\n const b = new Uint8Array(16);\n if (c && typeof c.getRandomValues === \"function\") c.getRandomValues(b);\n else for (let i = 0; i < 16; i++) b[i] = Math.floor(Math.random() * 256);\n b[6] = (b[6]! & 0x0f) | 0x40;\n b[8] = (b[8]! & 0x3f) | 0x80;\n const h = hex(b);\n return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;\n}\n\nfunction isFiniteNumber(v: unknown): v is number {\n return typeof v === \"number\" && Number.isFinite(v);\n}\n\nfunction cleanAttrs(attrs: unknown): Record<string, AttrValue> | undefined {\n if (!attrs || typeof attrs !== \"object\") return undefined;\n const out: Record<string, AttrValue> = {};\n let n = 0;\n for (const [k, v] of Object.entries(attrs as Record<string, unknown>)) {\n if (n >= MAX_ATTR_KEYS) break;\n if (v === undefined || v === null || !k) continue;\n let val: AttrValue;\n if (typeof v === \"boolean\") val = v;\n else if (typeof v === \"number\") {\n if (!Number.isFinite(v)) continue;\n val = v;\n } else if (typeof v === \"string\") val = truncate(v, MAX_ATTR_STRING);\n else continue; // attrs are flat: nested values are dropped, never stringified\n out[truncate(k, MAX_ATTR_KEY)] = val;\n n++;\n }\n return n > 0 ? out : undefined;\n}\n\n/**\n * Normalises an event so it always satisfies the protocol limits. Unknown\n * fields are dropped — only the whitelisted metadata below ever leaves the process.\n */\nexport function sanitizeEvent(e: KancaEvent): KancaEvent {\n const out: KancaEvent = {\n id: typeof e.id === \"string\" && e.id ? e.id : uuid(),\n type: e.type,\n ts: isFiniteNumber(e.ts) ? Math.round(e.ts) : Date.now(),\n name: truncate(typeof e.name === \"string\" && e.name ? e.name : \"unknown\", MAX_NAME),\n };\n if (isFiniteNumber(e.status) && e.status >= 100 && e.status <= 599) out.status = Math.trunc(e.status);\n if (typeof e.ok === \"boolean\") out.ok = e.ok;\n if (isFiniteNumber(e.durationMs)) out.durationMs = Math.min(86_400_000, Math.max(0, Math.round(e.durationMs * 10) / 10));\n if (isFiniteNumber(e.value)) out.value = e.value;\n if (e.error && typeof e.error.code === \"string\") {\n out.error = { code: truncate(e.error.code, MAX_ERROR_CODE) };\n if (typeof e.error.message === \"string\" && e.error.message) {\n out.error.message = truncate(e.error.message, MAX_ERROR_MESSAGE);\n }\n }\n if (typeof e.merchantId === \"string\" && e.merchantId) out.merchantId = truncate(e.merchantId, 200);\n if (typeof e.deliveryId === \"string\" && e.deliveryId) out.deliveryId = truncate(e.deliveryId, 200);\n const attrs = cleanAttrs(e.attrs);\n if (attrs) out.attrs = attrs;\n return out;\n}\n\nexport function errorMessage(err: unknown): string | undefined {\n try {\n if (err instanceof Error) return truncate(err.message || err.name, MAX_ERROR_MESSAGE);\n if (typeof err === \"string\") return truncate(err, MAX_ERROR_MESSAGE);\n } catch {\n /* ignore */\n }\n return undefined;\n}\n\nexport function joinUrl(endpoint: string, path: string): string {\n return endpoint.replace(/\\/+$/, \"\") + path;\n}\n\nconst encoder = typeof TextEncoder !== \"undefined\" ? new TextEncoder() : undefined;\nexport function byteLength(s: string): number {\n return encoder ? encoder.encode(s).length : s.length * 3;\n}\n\n/**\n * Splits events into protocol-sized batches (<= maxEvents events and\n * <= MAX_BATCH_BYTES serialized). Returns serialized bodies.\n */\nexport function buildBodies(events: KancaEvent[], maxEvents: number): { body: string; events: KancaEvent[] }[] {\n const sdk = { name: SDK_NAME, version: SDK_VERSION };\n const envelope = byteLength(JSON.stringify({ sdk, events: [] }));\n const out: { body: string; events: KancaEvent[] }[] = [];\n let cur: KancaEvent[] = [];\n let size = envelope;\n const push = () => {\n if (cur.length) out.push({ body: JSON.stringify({ sdk, events: cur } satisfies Batch), events: cur });\n cur = [];\n size = envelope;\n };\n for (const ev of events) {\n const len = byteLength(JSON.stringify(ev)) + 1;\n if (cur.length && (cur.length >= maxEvents || size + len > MAX_BATCH_BYTES)) push();\n cur.push(ev);\n size += len;\n }\n push();\n return out;\n}\n"],"mappings":"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","names":[]}
|