@kanca-app/ikas 0.1.1 → 0.2.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 +94 -5
- package/dist/browser.cjs +1 -2
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.js +1 -2
- package/dist/browser.js.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -5
- package/dist/index.d.ts +8 -5
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1 -2
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +1 -2
- package/dist/react.js.map +1 -1
- package/dist/storefront.cjs +322 -0
- package/dist/storefront.cjs.map +1 -0
- package/dist/storefront.d.cts +88 -0
- package/dist/storefront.d.ts +88 -0
- package/dist/storefront.js +299 -0
- package/dist/storefront.js.map +1 -0
- package/package.json +19 -3
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# @kanca-app/ikas
|
|
2
2
|
|
|
3
3
|
Observability for [ikas](https://ikas.com) apps: webhook deliveries, Admin API
|
|
4
|
-
calls, installs/uninstalls
|
|
4
|
+
calls, installs/uninstalls, admin UI web vitals and your storefront script's
|
|
5
|
+
errors and load cost — shown in the
|
|
5
6
|
[Kanca](https://kanca.onurhan.dev) dashboard.
|
|
6
7
|
|
|
7
8
|
- **Metadata only.** Never payloads, variables, responses, headers or tokens.
|
|
@@ -18,7 +19,7 @@ pnpm add @kanca-app/ikas
|
|
|
18
19
|
| Variable | Where | Value |
|
|
19
20
|
| ------------------------------ | ------------- | ------------------------------------ |
|
|
20
21
|
| `KANCA_KEY` | backend only | server key `kk_...` |
|
|
21
|
-
| `NEXT_PUBLIC_KANCA_PUBLIC_KEY` | browser | public key `kp_...` (vitals only)
|
|
22
|
+
| `NEXT_PUBLIC_KANCA_PUBLIC_KEY` | browser | public key `kp_...` (vitals and storefront events only) |
|
|
22
23
|
|
|
23
24
|
Without `KANCA_KEY` the SDK is a silent no-op, so local dev and CI need no setup.
|
|
24
25
|
|
|
@@ -34,8 +35,14 @@ export const kanca = createKanca({ key: process.env.KANCA_KEY });
|
|
|
34
35
|
### 1. Webhooks — `src/app/api/webhooks/ikas/route.ts`
|
|
35
36
|
|
|
36
37
|
Wrap your route handler. Each delivery records scope, delivery id, merchant id,
|
|
37
|
-
your response status and duration.
|
|
38
|
-
|
|
38
|
+
your response status and duration.
|
|
39
|
+
|
|
40
|
+
Uninstalls: ikas does not document an app-uninstall webhook scope. If a
|
|
41
|
+
`store/app/deleted`, `store/app/uninstalled` or `store/authorizedApp/deleted`
|
|
42
|
+
delivery ever arrives, the SDK records it as an uninstall too. Kanca
|
|
43
|
+
additionally infers **access revoked** for a store when its Admin API calls
|
|
44
|
+
keep failing with an authorization error (see step 3 and
|
|
45
|
+
[`docs/PROTOCOL.md`](../../docs/PROTOCOL.md)).
|
|
39
46
|
|
|
40
47
|
```ts
|
|
41
48
|
import { kanca } from '@/lib/kanca';
|
|
@@ -83,6 +90,14 @@ named after the operation (`getMerchant`, `listOrder`, …). A `200` response wi
|
|
|
83
90
|
an `errors` array counts as a failure, with `errors[0].extensions.code` as the
|
|
84
91
|
error code.
|
|
85
92
|
|
|
93
|
+
Because each call carries the store's merchant id, Kanca can also show a store
|
|
94
|
+
as **Access revoked** when its calls keep failing with an authorization error
|
|
95
|
+
(HTTP 401, or an auth error code such as `UNAUTHORIZED`; not 403, which
|
|
96
|
+
usually means a missing permission) and none succeed after: at least 2 such
|
|
97
|
+
failures since the last success or install. It is
|
|
98
|
+
inferred (most likely an uninstall, or a token whose refresh failed), not an
|
|
99
|
+
uninstall event. `kanca.fetch` calls carry no merchant id, so they don't count.
|
|
100
|
+
|
|
86
101
|
`instrument()` works with `@ikas/admin-api-client` 2.x (any
|
|
87
102
|
`BaseGraphQLAPIClient` subclass) and survives token refreshes. For anything else
|
|
88
103
|
— your own GraphQL client, `graphql-request`, REST calls — use the drop-in
|
|
@@ -114,6 +129,76 @@ pathname and rating, sent with `fetch` + `keepalive` when the page is hidden
|
|
|
114
129
|
(`sendBeacon` only as a fallback — content blockers drop beacons silently).
|
|
115
130
|
Without React: `import { reportVitals } from '@kanca-app/ikas/browser'`.
|
|
116
131
|
|
|
132
|
+
## Storefront script
|
|
133
|
+
|
|
134
|
+
If your app injects a script into merchants' ikas storefronts, add
|
|
135
|
+
`@kanca-app/ikas/storefront` to it: errors thrown by **your** script and what
|
|
136
|
+
it costs visitors (download time, transfer size, init → ready). Zero
|
|
137
|
+
dependencies, under 3 KB min+gzip (`pnpm size`), never throws, never blocks,
|
|
138
|
+
never retries.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
// First lines of your script's entry — synchronous, before anything else runs.
|
|
142
|
+
import { initKancaStorefront } from '@kanca-app/ikas/storefront';
|
|
143
|
+
|
|
144
|
+
const kanca = initKancaStorefront({
|
|
145
|
+
publicKey: 'kp_...', // public key, safe to ship
|
|
146
|
+
scriptName: 'my-app', // label in the dashboard
|
|
147
|
+
release: '1.4.0', // optional: your script's version
|
|
148
|
+
// sampleRate: 0.1, // share of page views that send load/timing data
|
|
149
|
+
// scriptUrlMatch: /my-app\.js/, // default: document.currentScript.src
|
|
150
|
+
// merchantId: '...', // if the script knows it (or kanca.setMerchantId later)
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Callbacks the browser invokes later: errors are recorded, then rethrown.
|
|
154
|
+
window.requestIdleCallback(kanca.wrap(() => {
|
|
155
|
+
render();
|
|
156
|
+
kanca.ready(); // marks init → ready
|
|
157
|
+
}, 'boot'));
|
|
158
|
+
|
|
159
|
+
// fetch with timing: same Response and same errors as fetch.
|
|
160
|
+
const res = await kanca.fetch('config', `${origin}/api/config?key=...`);
|
|
161
|
+
|
|
162
|
+
kanca.timing('render', 12.5); // any duration, ms
|
|
163
|
+
kanca.captureError(err, { where: 'cart' }); // errors you catch yourself
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
| Method | What it does |
|
|
167
|
+
| --------------------------------- | ------------------------------------------------------------------------------- |
|
|
168
|
+
| `captureError(err, { where })` | Records an error. Deduped per page view, at most 10 per page view. |
|
|
169
|
+
| `wrap(fn, where)` | Returns `fn` wrapped. A throw or a rejected returned promise is recorded, then **rethrown / left rejected**: the page sees exactly what it would without Kanca, and the error still reaches `window.onerror` (it is not counted twice). |
|
|
170
|
+
| `timing(name, ms, { ok, status })`| Records a duration, sampled page views only. `name`: `[A-Za-z0-9_.-]{1,40}`. |
|
|
171
|
+
| `fetch(name, input, init)` | `fetch` + `timing(name)` with the HTTP status (or `ok: false` on network errors). |
|
|
172
|
+
| `ready()` | Marks your script ready. First call counts. |
|
|
173
|
+
| `setMerchantId(id)` | Attaches the ikas merchant id to every event sent from now on. |
|
|
174
|
+
| `flush()` | Sends queued events now. Rarely needed: events are sent when the page is hidden. |
|
|
175
|
+
|
|
176
|
+
How it works:
|
|
177
|
+
|
|
178
|
+
- **Only your errors.** Global `error` / `unhandledrejection` events are kept
|
|
179
|
+
only when the topmost stack frame with a URL is your script
|
|
180
|
+
(`scriptUrlMatch`, default `document.currentScript.src` without query).
|
|
181
|
+
Theme and other apps' errors are ignored, and `preventDefault` is never
|
|
182
|
+
called. A cross-origin script without a `crossorigin` attribute gets muted
|
|
183
|
+
`"Script error."` events that can't be attributed: wrap your entry points
|
|
184
|
+
with `wrap()` so those errors are still captured.
|
|
185
|
+
- **Sampling.** Errors are always sent. `load` and `timing:*` events come from
|
|
186
|
+
`sampleRate` of page views (default 10%), and each carries the rate, so
|
|
187
|
+
Kanca weights counts and percentiles (a row sampled at 0.1 counts as 10
|
|
188
|
+
page views).
|
|
189
|
+
- **Load cost.** Once per sampled page view: your script's download time from
|
|
190
|
+
resource timing, transfer and body size, cache hit, init → `ready()`, and
|
|
191
|
+
how many errors the page view had. Browsers hide sizes of cross-origin files
|
|
192
|
+
unless you serve the script with `Timing-Allow-Origin: *`.
|
|
193
|
+
- **Sending.** Once when the page is hidden (plus early at 10 queued events),
|
|
194
|
+
`fetch` + `keepalive` with a `text/plain` body (no CORS preflight),
|
|
195
|
+
`sendBeacon` as a fallback. No retries, no cookies (`credentials: 'omit'`).
|
|
196
|
+
- **Privacy.** Sent per event: host name, a path pattern (ids, numbers,
|
|
197
|
+
e-mails and tokens replaced, no query), script name, release, a random
|
|
198
|
+
page-view id, the sample rate; for errors the error name, a scrubbed
|
|
199
|
+
message (≤ 300 chars) and the top stack frame of your file. Never cookies,
|
|
200
|
+
storage, customer, cart or order data.
|
|
201
|
+
|
|
117
202
|
## Serverless
|
|
118
203
|
|
|
119
204
|
Events are batched in memory and sent every 2 s (or at 100 events), and on
|
|
@@ -151,7 +236,8 @@ attempts). Every event has a UUID, so retries never double count.
|
|
|
151
236
|
What is sent: event type, name (webhook scope / GraphQL operation name /
|
|
152
237
|
`METHOD host/path`), HTTP status, ok, duration, error code, error message
|
|
153
238
|
(≤ 500 chars), merchant id, webhook delivery id, and for installs the store
|
|
154
|
-
name. Vitals send metric name, value, rating and pathname.
|
|
239
|
+
name. Vitals send metric name, value, rating and pathname. For storefront
|
|
240
|
+
events see [Storefront script](#storefront-script).
|
|
155
241
|
|
|
156
242
|
What is never sent: webhook bodies, the webhook signature, GraphQL queries,
|
|
157
243
|
variables or responses, request/response headers, access tokens, query strings,
|
|
@@ -165,6 +251,9 @@ customer or order data. Kanca never asks for your ikas client secret.
|
|
|
165
251
|
- For `instrument()`, `@ikas/admin-api-client` does not expose the HTTP status
|
|
166
252
|
of successful calls, so those events carry `ok: true` without a status. Use
|
|
167
253
|
`kanca.fetch` if you need exact statuses.
|
|
254
|
+
- ikas does not document an app-uninstall webhook, so uninstall events may
|
|
255
|
+
never arrive. "Access revoked" is inferred from Admin API authorization
|
|
256
|
+
failures and only appears once your app calls the Admin API for that store.
|
|
168
257
|
- Events buffered in memory are lost if the process is killed hard (SIGKILL,
|
|
169
258
|
OOM) before the next send.
|
|
170
259
|
|
package/dist/browser.cjs
CHANGED
|
@@ -28,7 +28,7 @@ var import_web_vitals = require("web-vitals");
|
|
|
28
28
|
|
|
29
29
|
// src/shared.ts
|
|
30
30
|
var SDK_NAME = "@kanca-app/ikas";
|
|
31
|
-
var SDK_VERSION = "0.
|
|
31
|
+
var SDK_VERSION = "0.2.0";
|
|
32
32
|
var DEFAULT_ENDPOINT = "https://kanca.onurhan.dev";
|
|
33
33
|
var MAX_NAME = 200;
|
|
34
34
|
var MAX_ERROR_MESSAGE = 500;
|
|
@@ -104,7 +104,6 @@ function sanitizeEvent(e) {
|
|
|
104
104
|
function joinUrl(endpoint, path) {
|
|
105
105
|
return endpoint.replace(/\/+$/, "") + path;
|
|
106
106
|
}
|
|
107
|
-
var encoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : void 0;
|
|
108
107
|
|
|
109
108
|
// src/browser.ts
|
|
110
109
|
var VITALS = ["LCP", "INP", "CLS", "FCP", "TTFB"];
|
package/dist/browser.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/browser.ts","../src/shared.ts"],"sourcesContent":["// @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":[]}
|
|
1
|
+
{"version":3,"sources":["../src/browser.ts","../src/shared.ts"],"sourcesContent":["// @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.2.0\";\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\" | \"storefront\";\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\" ? /* @__PURE__ */ 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/browser.js
CHANGED
|
@@ -3,7 +3,7 @@ import { onCLS, onFCP, onINP, onLCP, onTTFB } from "web-vitals";
|
|
|
3
3
|
|
|
4
4
|
// src/shared.ts
|
|
5
5
|
var SDK_NAME = "@kanca-app/ikas";
|
|
6
|
-
var SDK_VERSION = "0.
|
|
6
|
+
var SDK_VERSION = "0.2.0";
|
|
7
7
|
var DEFAULT_ENDPOINT = "https://kanca.onurhan.dev";
|
|
8
8
|
var MAX_NAME = 200;
|
|
9
9
|
var MAX_ERROR_MESSAGE = 500;
|
|
@@ -79,7 +79,6 @@ function sanitizeEvent(e) {
|
|
|
79
79
|
function joinUrl(endpoint, path) {
|
|
80
80
|
return endpoint.replace(/\/+$/, "") + path;
|
|
81
81
|
}
|
|
82
|
-
var encoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : void 0;
|
|
83
82
|
|
|
84
83
|
// src/browser.ts
|
|
85
84
|
var VITALS = ["LCP", "INP", "CLS", "FCP", "TTFB"];
|
package/dist/browser.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/browser.ts","../src/shared.ts"],"sourcesContent":["// @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":[]}
|
|
1
|
+
{"version":3,"sources":["../src/browser.ts","../src/shared.ts"],"sourcesContent":["// @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.2.0\";\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\" | \"storefront\";\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\" ? /* @__PURE__ */ 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":";AAGA,SAAS,OAAO,OAAO,OAAO,OAAO,cAA2B;;;ACAzD,IAAM,WAAW;AACjB,IAAM,cAAc;AACpB,IAAM,mBAAmB;AAEzB,IAAM,WAAW;AACjB,IAAM,oBAAoB;AAC1B,IAAM,iBAAiB;AACvB,IAAM,gBAAgB;AACtB,IAAM,kBAAkB;AACxB,IAAM,eAAe;AAGrB,IAAM,kBAAkB,MAAM;AAgC9B,SAAS,SAAS,GAAW,KAAqB;AACvD,SAAO,EAAE,SAAS,MAAM,EAAE,MAAM,GAAG,GAAG,IAAI;AAC5C;AAEA,SAAS,IAAI,OAA2B;AACtC,MAAI,MAAM;AACV,aAAW,KAAK,MAAO,QAAO,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAC5D,SAAO;AACT;AAGO,SAAS,OAAe;AAC7B,QAAM,IAAK,WAAmC;AAC9C,MAAI,KAAK,OAAO,EAAE,eAAe,WAAY,QAAO,EAAE,WAAW;AACjE,QAAM,IAAI,IAAI,WAAW,EAAE;AAC3B,MAAI,KAAK,OAAO,EAAE,oBAAoB,WAAY,GAAE,gBAAgB,CAAC;AAAA,MAChE,UAAS,IAAI,GAAG,IAAI,IAAI,IAAK,GAAE,CAAC,IAAI,KAAK,MAAM,KAAK,OAAO,IAAI,GAAG;AACvE,IAAE,CAAC,IAAK,EAAE,CAAC,IAAK,KAAQ;AACxB,IAAE,CAAC,IAAK,EAAE,CAAC,IAAK,KAAQ;AACxB,QAAM,IAAI,IAAI,CAAC;AACf,SAAO,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;AAChG;AAEA,SAAS,eAAe,GAAyB;AAC/C,SAAO,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC;AACnD;AAEA,SAAS,WAAW,OAAuD;AACzE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,MAAiC,CAAC;AACxC,MAAI,IAAI;AACR,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAgC,GAAG;AACrE,QAAI,KAAK,cAAe;AACxB,QAAI,MAAM,UAAa,MAAM,QAAQ,CAAC,EAAG;AACzC,QAAI;AACJ,QAAI,OAAO,MAAM,UAAW,OAAM;AAAA,aACzB,OAAO,MAAM,UAAU;AAC9B,UAAI,CAAC,OAAO,SAAS,CAAC,EAAG;AACzB,YAAM;AAAA,IACR,WAAW,OAAO,MAAM,SAAU,OAAM,SAAS,GAAG,eAAe;AAAA,QAC9D;AACL,QAAI,SAAS,GAAG,YAAY,CAAC,IAAI;AACjC;AAAA,EACF;AACA,SAAO,IAAI,IAAI,MAAM;AACvB;AAMO,SAAS,cAAc,GAA2B;AACvD,QAAM,MAAkB;AAAA,IACtB,IAAI,OAAO,EAAE,OAAO,YAAY,EAAE,KAAK,EAAE,KAAK,KAAK;AAAA,IACnD,MAAM,EAAE;AAAA,IACR,IAAI,eAAe,EAAE,EAAE,IAAI,KAAK,MAAM,EAAE,EAAE,IAAI,KAAK,IAAI;AAAA,IACvD,MAAM,SAAS,OAAO,EAAE,SAAS,YAAY,EAAE,OAAO,EAAE,OAAO,WAAW,QAAQ;AAAA,EACpF;AACA,MAAI,eAAe,EAAE,MAAM,KAAK,EAAE,UAAU,OAAO,EAAE,UAAU,IAAK,KAAI,SAAS,KAAK,MAAM,EAAE,MAAM;AACpG,MAAI,OAAO,EAAE,OAAO,UAAW,KAAI,KAAK,EAAE;AAC1C,MAAI,eAAe,EAAE,UAAU,EAAG,KAAI,aAAa,KAAK,IAAI,OAAY,KAAK,IAAI,GAAG,KAAK,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;AACvH,MAAI,eAAe,EAAE,KAAK,EAAG,KAAI,QAAQ,EAAE;AAC3C,MAAI,EAAE,SAAS,OAAO,EAAE,MAAM,SAAS,UAAU;AAC/C,QAAI,QAAQ,EAAE,MAAM,SAAS,EAAE,MAAM,MAAM,cAAc,EAAE;AAC3D,QAAI,OAAO,EAAE,MAAM,YAAY,YAAY,EAAE,MAAM,SAAS;AAC1D,UAAI,MAAM,UAAU,SAAS,EAAE,MAAM,SAAS,iBAAiB;AAAA,IACjE;AAAA,EACF;AACA,MAAI,OAAO,EAAE,eAAe,YAAY,EAAE,WAAY,KAAI,aAAa,SAAS,EAAE,YAAY,GAAG;AACjG,MAAI,OAAO,EAAE,eAAe,YAAY,EAAE,WAAY,KAAI,aAAa,SAAS,EAAE,YAAY,GAAG;AACjG,QAAM,QAAQ,WAAW,EAAE,KAAK;AAChC,MAAI,MAAO,KAAI,QAAQ;AACvB,SAAO;AACT;AAYO,SAAS,QAAQ,UAAkB,MAAsB;AAC9D,SAAO,SAAS,QAAQ,QAAQ,EAAE,IAAI;AACxC;;;ADxHA,IAAM,SAAS,CAAC,OAAO,OAAO,OAAO,OAAO,MAAM;AAGlD,IAAM,OAAO;AACb,IAAM,IAAI;AAOH,SAAS,aAAa,SAAoC;AAC/D,MAAI;AACF,QAAI,EAAE,IAAI,KAAK,OAAO,WAAW,eAAe,OAAO,aAAa,YAAa;AACjF,UAAM,MAAM,OAAO,SAAS,cAAc,WAAW,QAAQ,UAAU,KAAK,IAAI;AAChF,UAAM,QAAQ,CAAC,CAAC,SAAS;AACzB,QAAI,CAAC,IAAI,WAAW,KAAK,GAAG;AAC1B,UAAI,SAAS,IAAK,SAAQ,KAAK,6EAA6E;AAC5G;AAAA,IACF;AACA,MAAE,IAAI,IAAI;AAEV,UAAM,MAAM,GAAG,QAAQ,QAAQ,YAAY,kBAAkB,qBAAqB,CAAC,QAAQ,mBAAmB,GAAG,CAAC;AAClH,UAAM,OAAO,SAAS;AACtB,UAAM,KACJ,OAAO,gBAAgB,eAAe,OAAO,YAAY,eAAe,YAAY,YAAY,aAAa,IACzG,KAAK,MAAM,YAAY,UAAU,IACjC,KAAK,IAAI;AACf,UAAM,QAAsB,CAAC;AAC7B,UAAM,OAAO,oBAAI,IAAY;AAE7B,UAAM,OAAO,MAAM;AACjB,UAAI,CAAC,MAAM,OAAQ;AACnB,YAAM,SAAS,MAAM,OAAO,GAAG,MAAM,MAAM;AAC3C,YAAM,OAAO,KAAK,UAAU,EAAE,KAAK,EAAE,MAAM,UAAU,SAAS,YAAY,GAAG,OAAO,CAAC;AACrF,UAAI,MAAO,SAAQ,MAAM,kBAAkB,MAAM;AAIjD,UAAI;AACF,YAAI,OAAO,UAAU,YAAY;AAC/B,eAAK,MAAM,KAAK,EAAE,QAAQ,QAAQ,MAAM,WAAW,MAAM,MAAM,QAAQ,aAAa,QAAQ,SAAS,EAAE,gBAAgB,aAAa,EAAE,CAAC,EAAE,MAAM,MAAM,MAAS;AAC9J;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAER;AACA,UAAI;AACF,YAAI,OAAO,cAAc,eAAe,OAAO,UAAU,eAAe,WAAY,WAAU,WAAW,KAAK,IAAI;AAAA,MACpH,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,UAAM,WAAW,CAAC,MAAc;AAC9B,UAAI;AACF,YAAI,CAAE,OAA6B,SAAS,EAAE,IAAI,KAAK,KAAK,IAAI,EAAE,IAAI,EAAG;AACzE,aAAK,IAAI,EAAE,IAAI;AACf,cAAM,KAAK,cAAc,EAAE,IAAI,KAAK,GAAG,MAAM,SAAS,IAAI,MAAM,EAAE,MAAM,OAAO,EAAE,OAAO,OAAO,EAAE,MAAM,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;AAAA,MAC9H,QAAQ;AAAA,MAER;AAAA,IACF;AAGA,UAAM,QAAQ;AACd,UAAM,QAAQ;AACd,UAAM,QAAQ;AACd,UAAM,QAAQ;AACd,WAAO,QAAQ;AAGf;AAAA,MACE;AAAA,MACA,MAAM;AAEJ,YAAI,SAAS,oBAAoB,SAAU,gBAAe,IAAI;AAAA,MAChE;AAAA,MACA;AAAA,IACF;AACA,qBAAiB,YAAY,MAAM,IAAI;AAAA,EACzC,QAAQ;AAAA,EAER;AACF;AAGO,SAAS,wBAA8B;AAC5C,IAAE,IAAI,IAAI;AACZ;","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -175,7 +175,7 @@ function classifyGraphql(status, json) {
|
|
|
175
175
|
|
|
176
176
|
// src/shared.ts
|
|
177
177
|
var SDK_NAME = "@kanca-app/ikas";
|
|
178
|
-
var SDK_VERSION = "0.
|
|
178
|
+
var SDK_VERSION = "0.2.0";
|
|
179
179
|
var DEFAULT_ENDPOINT = "https://kanca.onurhan.dev";
|
|
180
180
|
var MAX_NAME = 200;
|
|
181
181
|
var MAX_ERROR_MESSAGE = 500;
|
|
@@ -260,7 +260,7 @@ function errorMessage(err) {
|
|
|
260
260
|
function joinUrl(endpoint, path) {
|
|
261
261
|
return endpoint.replace(/\/+$/, "") + path;
|
|
262
262
|
}
|
|
263
|
-
var encoder = typeof TextEncoder !== "undefined" ? new TextEncoder() : void 0;
|
|
263
|
+
var encoder = typeof TextEncoder !== "undefined" ? /* @__PURE__ */ new TextEncoder() : void 0;
|
|
264
264
|
function byteLength(s) {
|
|
265
265
|
return encoder ? encoder.encode(s).length : s.length * 3;
|
|
266
266
|
}
|