@refraction-ui/astro 0.5.0 → 0.6.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/dist/analytics/analytics-manager.ts +361 -0
- package/dist/analytics/consent.ts +40 -0
- package/dist/analytics/console-sink.ts +37 -0
- package/dist/analytics/http-sink.ts +213 -0
- package/dist/analytics/identity.ts +64 -0
- package/dist/analytics/index.ts +60 -0
- package/dist/analytics/mock-sink.ts +62 -0
- package/dist/analytics/noop.ts +48 -0
- package/dist/analytics/redaction.ts +93 -0
- package/dist/analytics/session.ts +162 -0
- package/dist/analytics/storage.ts +119 -0
- package/dist/analytics/types.ts +273 -0
- package/dist/analytics/uuid.ts +63 -0
- package/dist/astro-analytics/AnalyticsScript.astro +130 -0
- package/dist/astro-analytics/index.ts +49 -0
- package/dist/astro-logger/TelemetryScript.astro +108 -0
- package/dist/astro-logger/index.ts +48 -0
- package/dist/astro-logger/library-error-capture.ts +121 -0
- package/dist/astro-logger/telemetry-middleware.ts +92 -0
- package/dist/astro-waveform/Waveform.astro +8 -2
- package/dist/index.ts +2 -0
- package/dist/logger/console-sink.ts +64 -0
- package/dist/logger/faro-engine.ts +130 -0
- package/dist/logger/index.ts +39 -0
- package/dist/logger/mock-sink.ts +38 -0
- package/dist/logger/noop.ts +52 -0
- package/dist/logger/presets.ts +51 -0
- package/dist/logger/redact.ts +33 -0
- package/dist/logger/telemetry-manager.ts +229 -0
- package/dist/logger/types.ts +121 -0
- package/dist/shared/dev-feedback.ts +154 -0
- package/dist/shared/index.ts +25 -0
- package/dist/shared/library-origin-error.ts +240 -0
- package/dist/voice-pill/voice-pill.styles.ts +14 -13
- package/dist/voice-pill/voice-pill.ts +3 -2
- package/dist/waveform/waveform-renderer.ts +13 -6
- package/dist/waveform/waveform.ts +15 -2
- package/package.json +4 -2
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @refraction-ui/analytics — type contracts.
|
|
3
|
+
*
|
|
4
|
+
* The library is a neutral Segment-spec collector/router. The app instruments
|
|
5
|
+
* once via the canonical API; the router fans the canonical envelope out to
|
|
6
|
+
* N pluggable sinks. There is NO privileged engine — every vendor is a sink.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Canonical Segment event types. */
|
|
10
|
+
export type AnalyticsEventType =
|
|
11
|
+
| 'track'
|
|
12
|
+
| 'identify'
|
|
13
|
+
| 'page'
|
|
14
|
+
| 'screen'
|
|
15
|
+
| 'group'
|
|
16
|
+
| 'alias'
|
|
17
|
+
|
|
18
|
+
/** Schema version stamped onto every envelope and the wire contract path. */
|
|
19
|
+
export const SCHEMA_VERSION = 1
|
|
20
|
+
|
|
21
|
+
/** Arbitrary, JSON-serialisable bag of values. */
|
|
22
|
+
export type AnalyticsProperties = Record<string, unknown>
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The context block attached to every event. `library` identifies the
|
|
26
|
+
* collector; `app`/`env` identify the product instance.
|
|
27
|
+
*/
|
|
28
|
+
export interface AnalyticsContext {
|
|
29
|
+
app: string
|
|
30
|
+
env: string
|
|
31
|
+
page?: {
|
|
32
|
+
path?: string
|
|
33
|
+
url?: string
|
|
34
|
+
referrer?: string
|
|
35
|
+
title?: string
|
|
36
|
+
search?: string
|
|
37
|
+
}
|
|
38
|
+
library: {
|
|
39
|
+
name: string
|
|
40
|
+
version: string
|
|
41
|
+
}
|
|
42
|
+
/** Free-form additions contributed by `with(context)` children. */
|
|
43
|
+
[key: string]: unknown
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The canonical Segment envelope. Every sink receives events in exactly this
|
|
48
|
+
* shape; the built-in HTTP sink ships it verbatim over the wire contract.
|
|
49
|
+
*/
|
|
50
|
+
export interface AnalyticsEvent {
|
|
51
|
+
/** Segment call type. */
|
|
52
|
+
type: AnalyticsEventType
|
|
53
|
+
/** Event name (track/screen) or page name (page). */
|
|
54
|
+
event?: string
|
|
55
|
+
/** Idempotency key — backends MUST dedupe on this. */
|
|
56
|
+
messageId: string
|
|
57
|
+
/** Persistent, non-PII, resettable client id. */
|
|
58
|
+
anonymousId: string
|
|
59
|
+
/** Opaque app-supplied id (set after identify). */
|
|
60
|
+
userId?: string
|
|
61
|
+
/** Group id (group calls). */
|
|
62
|
+
groupId?: string
|
|
63
|
+
/** Previous id (alias calls). */
|
|
64
|
+
previousId?: string
|
|
65
|
+
/** Analytics session id (UUIDv4, minted at session start). */
|
|
66
|
+
sessionId: string
|
|
67
|
+
/** track/page/screen/group payload. */
|
|
68
|
+
properties?: AnalyticsProperties
|
|
69
|
+
/** identify/group trait payload. */
|
|
70
|
+
traits?: AnalyticsProperties
|
|
71
|
+
/** Collector + product context. */
|
|
72
|
+
context: AnalyticsContext
|
|
73
|
+
/** ISO-8601 client timestamp. */
|
|
74
|
+
timestamp: string
|
|
75
|
+
/** Envelope schema version. */
|
|
76
|
+
schemaVersion: number
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Sink SPI — implemented by adapter packages (GA4, Azure, PostHog) and by the
|
|
81
|
+
* built-in HTTP sink. A sink declares the consent categories it requires; the
|
|
82
|
+
* router will not deliver to a sink whose categories are not all granted.
|
|
83
|
+
*/
|
|
84
|
+
export interface AnalyticsSink {
|
|
85
|
+
/** Stable sink identifier. */
|
|
86
|
+
name: string
|
|
87
|
+
/** Consent categories this sink requires (e.g. ['analytics']). */
|
|
88
|
+
consentCategories?: string[]
|
|
89
|
+
/** Called once before the first delivery. */
|
|
90
|
+
init?(ctx: SinkInitContext): void | Promise<void>
|
|
91
|
+
/** Deliver a batch of canonical events. */
|
|
92
|
+
deliver(batch: AnalyticsEvent[], ctx: SinkDeliverContext): void | Promise<void>
|
|
93
|
+
/** Flush any buffered state (best-effort). */
|
|
94
|
+
flush?(): void | Promise<void>
|
|
95
|
+
/** Release resources on reset()/shutdown. */
|
|
96
|
+
shutdown?(): void | Promise<void>
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Context handed to `sink.init`. */
|
|
100
|
+
export interface SinkInitContext {
|
|
101
|
+
app: string
|
|
102
|
+
env: string
|
|
103
|
+
endpoint?: string
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Context handed to every `sink.deliver`. */
|
|
107
|
+
export interface SinkDeliverContext {
|
|
108
|
+
/** True on the unload path — sinks should prefer sendBeacon. */
|
|
109
|
+
unload: boolean
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Cross-tab persistence SPI. Defaults pick localStorage/cookie/memory. */
|
|
113
|
+
export interface AnalyticsStorage {
|
|
114
|
+
get(key: string): string | null
|
|
115
|
+
set(key: string, value: string): void
|
|
116
|
+
remove(key: string): void
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Session engine configuration. */
|
|
120
|
+
export interface SessionConfig {
|
|
121
|
+
/** Inactivity timeout in ms before a new session is minted. GA4 = 30min. */
|
|
122
|
+
timeoutMs?: number
|
|
123
|
+
/** Reset the session when a campaign/utm change is detected. */
|
|
124
|
+
resetOnCampaign?: boolean
|
|
125
|
+
/** Persistence backend; defaults to localStorage→cookie→memory. */
|
|
126
|
+
storage?: AnalyticsStorage
|
|
127
|
+
/** Storage key namespace. */
|
|
128
|
+
storageKey?: string
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Identity engine configuration. */
|
|
132
|
+
export interface IdentityConfig {
|
|
133
|
+
/** Persistence backend; defaults to localStorage→cookie→memory. */
|
|
134
|
+
storage?: AnalyticsStorage
|
|
135
|
+
/** Storage key namespace. */
|
|
136
|
+
storageKey?: string
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Consent gate configuration. */
|
|
140
|
+
export interface ConsentConfig {
|
|
141
|
+
/** Categories granted at boot. */
|
|
142
|
+
granted?: string[]
|
|
143
|
+
/**
|
|
144
|
+
* If true, an event is dropped entirely when NO sink can receive it.
|
|
145
|
+
* If false (default), events are still buffered until consent is granted.
|
|
146
|
+
*/
|
|
147
|
+
strict?: boolean
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Consent gate runtime API. */
|
|
151
|
+
export interface ConsentAPI {
|
|
152
|
+
grant(...categories: string[]): void
|
|
153
|
+
revoke(...categories: string[]): void
|
|
154
|
+
granted(): string[]
|
|
155
|
+
isGranted(category: string): boolean
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Session runtime API. */
|
|
159
|
+
export interface SessionAPI {
|
|
160
|
+
/** Current session id (mints one lazily if none active). */
|
|
161
|
+
id(): string
|
|
162
|
+
/** Force-start a new session, returning the new id. */
|
|
163
|
+
start(): string
|
|
164
|
+
/** End the current session. */
|
|
165
|
+
end(): void
|
|
166
|
+
/** Attach arbitrary session-scoped properties. */
|
|
167
|
+
set(props: AnalyticsProperties): void
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Built-in HTTP sink options (Segment HTTP Tracking API wire contract). */
|
|
171
|
+
export interface HttpSinkOptions {
|
|
172
|
+
/** Base endpoint; events POST to `{endpoint}/v{schemaVersion}/batch`. */
|
|
173
|
+
endpoint: string
|
|
174
|
+
/** Write key — sent as `Authorization: Basic base64(writeKey:)`. */
|
|
175
|
+
writeKey: string
|
|
176
|
+
/** Max retries for 429/5xx (exponential backoff). Default 3. */
|
|
177
|
+
maxRetries?: number
|
|
178
|
+
/** Base backoff delay in ms. Default 500. */
|
|
179
|
+
backoffBaseMs?: number
|
|
180
|
+
/** Injected fetch (defaults to global fetch). */
|
|
181
|
+
fetchImpl?: typeof fetch
|
|
182
|
+
/** Injected sendBeacon (defaults to navigator.sendBeacon). */
|
|
183
|
+
beaconImpl?: (url: string, body: string) => boolean
|
|
184
|
+
/** Consent categories this sink requires. Default ['analytics']. */
|
|
185
|
+
consentCategories?: string[]
|
|
186
|
+
/** Soft per-batch byte cap (Segment ≈ 500KB). Default 500_000. */
|
|
187
|
+
maxBatchBytes?: number
|
|
188
|
+
/** Soft per-event byte cap (Segment ≈ 32KB). Default 32_000. */
|
|
189
|
+
maxEventBytes?: number
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** `createAnalytics` configuration. */
|
|
193
|
+
export interface AnalyticsConfig {
|
|
194
|
+
/** Product/app identifier (stamped into context.app). */
|
|
195
|
+
app: string
|
|
196
|
+
/** Environment, e.g. 'development' | 'production'. Drives presets. */
|
|
197
|
+
env: string
|
|
198
|
+
/** When set, a built-in HTTP sink is auto-registered to this endpoint. */
|
|
199
|
+
endpoint?: string
|
|
200
|
+
/** Write key for the auto-registered HTTP sink. */
|
|
201
|
+
writeKey?: string
|
|
202
|
+
/** Kill switch — when false, a tree-shakeable noop is returned. Default true. */
|
|
203
|
+
enabled?: boolean
|
|
204
|
+
/** 0..1 sampling rate applied per top-level call. Default 1. */
|
|
205
|
+
sampleRate?: number
|
|
206
|
+
/** Extra property/trait keys to redact (in addition to the PII deny-list). */
|
|
207
|
+
redactKeys?: string[]
|
|
208
|
+
/** Explicit sinks (in addition to / instead of the auto HTTP sink). */
|
|
209
|
+
sinks?: AnalyticsSink[]
|
|
210
|
+
/** Session engine config. */
|
|
211
|
+
session?: SessionConfig
|
|
212
|
+
/** Identity engine config. */
|
|
213
|
+
identity?: IdentityConfig
|
|
214
|
+
/** Consent gate config. */
|
|
215
|
+
consent?: ConsentConfig
|
|
216
|
+
/**
|
|
217
|
+
* Force a preset. Defaults: env==='production' → 'prod', else 'dev'.
|
|
218
|
+
* dev = sync + console; prod = batch + sample + beacon flush on unload.
|
|
219
|
+
*/
|
|
220
|
+
preset?: 'dev' | 'prod'
|
|
221
|
+
/** Batch size before an automatic flush (prod). Default 20. */
|
|
222
|
+
batchSize?: number
|
|
223
|
+
/** Auto-flush interval in ms (prod). Default 10_000. */
|
|
224
|
+
flushIntervalMs?: number
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** Options accepted by every top-level call. */
|
|
228
|
+
export interface CallOptions {
|
|
229
|
+
/** Per-call timestamp override (ISO-8601). */
|
|
230
|
+
timestamp?: string
|
|
231
|
+
/** Per-call context overrides (shallow-merged). */
|
|
232
|
+
context?: Partial<AnalyticsContext>
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** The public analytics surface returned by `createAnalytics`. */
|
|
236
|
+
export interface Analytics {
|
|
237
|
+
track(event: string, properties?: AnalyticsProperties, opts?: CallOptions): void
|
|
238
|
+
identify(userId: string, traits?: AnalyticsProperties, opts?: CallOptions): void
|
|
239
|
+
page(name?: string, properties?: AnalyticsProperties, opts?: CallOptions): void
|
|
240
|
+
screen(name?: string, properties?: AnalyticsProperties, opts?: CallOptions): void
|
|
241
|
+
group(groupId: string, traits?: AnalyticsProperties, opts?: CallOptions): void
|
|
242
|
+
alias(userId: string, previousId?: string, opts?: CallOptions): void
|
|
243
|
+
|
|
244
|
+
/** Analytics-session API (NOT replay). */
|
|
245
|
+
session: SessionAPI
|
|
246
|
+
/** Consent gate API. */
|
|
247
|
+
consent: ConsentAPI
|
|
248
|
+
|
|
249
|
+
/** Persistent, non-PII anonymous id. */
|
|
250
|
+
anonymousId(): string
|
|
251
|
+
/** Current opaque user id (if identified). */
|
|
252
|
+
userId(): string | undefined
|
|
253
|
+
|
|
254
|
+
/** Derive a child that merges extra context into every event. */
|
|
255
|
+
with(context: Partial<AnalyticsContext>): Analytics
|
|
256
|
+
|
|
257
|
+
/** Register an additional sink at runtime. */
|
|
258
|
+
addSink(sink: AnalyticsSink): void
|
|
259
|
+
/** Remove a sink by name. */
|
|
260
|
+
removeSink(name: string): void
|
|
261
|
+
/** Registered sink names. */
|
|
262
|
+
readonly sinks: string[]
|
|
263
|
+
|
|
264
|
+
/** Flush all sinks (and the internal batch). */
|
|
265
|
+
flush(): Promise<void>
|
|
266
|
+
/**
|
|
267
|
+
* Reset identity + session (privacy-safe logout). Mints a fresh
|
|
268
|
+
* anonymousId and ends the session.
|
|
269
|
+
*/
|
|
270
|
+
reset(): void
|
|
271
|
+
/** Whether this is a live collector (false for the noop). */
|
|
272
|
+
readonly enabled: boolean
|
|
273
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* uuidv4 — RFC 4122 version 4 UUID.
|
|
3
|
+
*
|
|
4
|
+
* Prefers `crypto.randomUUID` / `crypto.getRandomValues` when available
|
|
5
|
+
* (browser, modern Node) and degrades to `Math.random` only as a last
|
|
6
|
+
* resort. No external dependency by design (this is the neutral router).
|
|
7
|
+
*/
|
|
8
|
+
export function uuidv4(): string {
|
|
9
|
+
const c: Crypto | undefined =
|
|
10
|
+
typeof globalThis !== 'undefined'
|
|
11
|
+
? (globalThis.crypto as Crypto | undefined)
|
|
12
|
+
: undefined
|
|
13
|
+
|
|
14
|
+
if (c && typeof c.randomUUID === 'function') {
|
|
15
|
+
return c.randomUUID()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const bytes = new Uint8Array(16)
|
|
19
|
+
if (c && typeof c.getRandomValues === 'function') {
|
|
20
|
+
c.getRandomValues(bytes)
|
|
21
|
+
} else {
|
|
22
|
+
for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Per RFC 4122 §4.4: set version (4) and variant (10xx) bits.
|
|
26
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40
|
|
27
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80
|
|
28
|
+
|
|
29
|
+
const hex: string[] = []
|
|
30
|
+
for (let i = 0; i < 256; i++) hex.push((i + 0x100).toString(16).slice(1))
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
hex[bytes[0]] +
|
|
34
|
+
hex[bytes[1]] +
|
|
35
|
+
hex[bytes[2]] +
|
|
36
|
+
hex[bytes[3]] +
|
|
37
|
+
'-' +
|
|
38
|
+
hex[bytes[4]] +
|
|
39
|
+
hex[bytes[5]] +
|
|
40
|
+
'-' +
|
|
41
|
+
hex[bytes[6]] +
|
|
42
|
+
hex[bytes[7]] +
|
|
43
|
+
'-' +
|
|
44
|
+
hex[bytes[8]] +
|
|
45
|
+
hex[bytes[9]] +
|
|
46
|
+
'-' +
|
|
47
|
+
hex[bytes[10]] +
|
|
48
|
+
hex[bytes[11]] +
|
|
49
|
+
hex[bytes[12]] +
|
|
50
|
+
hex[bytes[13]] +
|
|
51
|
+
hex[bytes[14]] +
|
|
52
|
+
hex[bytes[15]]
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** RFC 4122 v4 shape matcher (case-insensitive). */
|
|
57
|
+
export const UUID_V4_RE =
|
|
58
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
|
59
|
+
|
|
60
|
+
/** True when `value` is a well-formed v4 UUID. */
|
|
61
|
+
export function isUuidV4(value: unknown): value is string {
|
|
62
|
+
return typeof value === 'string' && UUID_V4_RE.test(value)
|
|
63
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* AnalyticsScript — injects a <script> that initializes the analytics
|
|
4
|
+
* router on the page. Exposes `window.__rfr_analytics` so other scripts
|
|
5
|
+
* can call track / identify / page / screen / group / alias.
|
|
6
|
+
*
|
|
7
|
+
* Props mirror the consumer-facing slice of `AnalyticsConfig` from
|
|
8
|
+
* `@refraction-ui/analytics`. The app instruments once and never names a
|
|
9
|
+
* vendor — `endpoint` points at your server relay (recommended prod
|
|
10
|
+
* topology: client → your endpoint → server-side fan-out), so the browser
|
|
11
|
+
* ships only our neutral router and stays ad-blocker-proof.
|
|
12
|
+
*
|
|
13
|
+
* Because inline scripts cannot import ES modules, this island stashes the
|
|
14
|
+
* config and a deferred `init(createAnalytics, sinks)` helper. The consumer
|
|
15
|
+
* calls it from their own module script with the real core:
|
|
16
|
+
*
|
|
17
|
+
* ```astro
|
|
18
|
+
* ---
|
|
19
|
+
* import { AnalyticsScript } from '../astro-analytics/index.ts'
|
|
20
|
+
* ---
|
|
21
|
+
* <AnalyticsScript app="my-app" env="production" endpoint="/relay" />
|
|
22
|
+
* <script>
|
|
23
|
+
* import { createAnalytics } from '../analytics/index.ts'
|
|
24
|
+
* window.__rfr_analytics.init(createAnalytics)
|
|
25
|
+
* </script>
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
30
|
+
/** Product/app identifier (stamped into context.app). */
|
|
31
|
+
app: string
|
|
32
|
+
/** Environment, e.g. 'development' | 'production'. Drives presets. */
|
|
33
|
+
env: string
|
|
34
|
+
/** Server-relay endpoint; an HTTP sink auto-registers here. */
|
|
35
|
+
endpoint?: string
|
|
36
|
+
/** Write key for the auto-registered HTTP sink. */
|
|
37
|
+
writeKey?: string
|
|
38
|
+
/** Kill switch — when false, a tree-shakeable noop is returned. */
|
|
39
|
+
enabled?: boolean
|
|
40
|
+
/** 0..1 sampling rate applied per top-level call. */
|
|
41
|
+
sampleRate?: number
|
|
42
|
+
/** Extra property/trait keys to redact (in addition to PII deny-list). */
|
|
43
|
+
redactKeys?: string[]
|
|
44
|
+
/** Force a preset (defaults from env). */
|
|
45
|
+
preset?: 'dev' | 'prod'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const {
|
|
49
|
+
app,
|
|
50
|
+
env,
|
|
51
|
+
endpoint,
|
|
52
|
+
writeKey,
|
|
53
|
+
enabled,
|
|
54
|
+
sampleRate,
|
|
55
|
+
redactKeys,
|
|
56
|
+
preset,
|
|
57
|
+
...props
|
|
58
|
+
} = Astro.props
|
|
59
|
+
|
|
60
|
+
const configJSON = JSON.stringify({
|
|
61
|
+
app,
|
|
62
|
+
env,
|
|
63
|
+
endpoint,
|
|
64
|
+
writeKey,
|
|
65
|
+
enabled,
|
|
66
|
+
sampleRate,
|
|
67
|
+
redactKeys,
|
|
68
|
+
preset,
|
|
69
|
+
})
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
<script
|
|
73
|
+
data-rfr-analytics-config={configJSON}
|
|
74
|
+
{...props}
|
|
75
|
+
is:inline
|
|
76
|
+
>
|
|
77
|
+
;(function () {
|
|
78
|
+
// Only initialize once
|
|
79
|
+
if (window.__rfr_analytics) return
|
|
80
|
+
|
|
81
|
+
// We cannot import ES modules from an inline script, so we stash the
|
|
82
|
+
// config and let the consumer call init() from their own module script.
|
|
83
|
+
var configEl = document.querySelector('[data-rfr-analytics-config]')
|
|
84
|
+
var config = {}
|
|
85
|
+
if (configEl) {
|
|
86
|
+
try { config = JSON.parse(configEl.getAttribute('data-rfr-analytics-config') || '{}') } catch (_) { /* */ }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function notReady() {
|
|
90
|
+
throw new Error('Analytics not initialized. Call window.__rfr_analytics.init(createAnalytics, sinks) first.')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Placeholder that will be replaced once createAnalytics is called
|
|
94
|
+
window.__rfr_analytics = {
|
|
95
|
+
_config: config,
|
|
96
|
+
_api: null,
|
|
97
|
+
_ready: false,
|
|
98
|
+
init: function (createAnalytics, sinks) {
|
|
99
|
+
var api = createAnalytics(
|
|
100
|
+
sinks && sinks.length
|
|
101
|
+
? Object.assign({}, config, { sinks: sinks })
|
|
102
|
+
: config
|
|
103
|
+
)
|
|
104
|
+
window.__rfr_analytics._api = api
|
|
105
|
+
window.__rfr_analytics._ready = true
|
|
106
|
+
window.__rfr_analytics.track = api.track.bind(api)
|
|
107
|
+
window.__rfr_analytics.identify = api.identify.bind(api)
|
|
108
|
+
window.__rfr_analytics.page = api.page.bind(api)
|
|
109
|
+
window.__rfr_analytics.screen = api.screen.bind(api)
|
|
110
|
+
window.__rfr_analytics.group = api.group.bind(api)
|
|
111
|
+
window.__rfr_analytics.alias = api.alias.bind(api)
|
|
112
|
+
window.__rfr_analytics.flush = api.flush.bind(api)
|
|
113
|
+
window.__rfr_analytics.reset = api.reset.bind(api)
|
|
114
|
+
window.__rfr_analytics.session = api.session
|
|
115
|
+
window.__rfr_analytics.consent = api.consent
|
|
116
|
+
return api
|
|
117
|
+
},
|
|
118
|
+
track: notReady,
|
|
119
|
+
identify: notReady,
|
|
120
|
+
page: notReady,
|
|
121
|
+
screen: notReady,
|
|
122
|
+
group: notReady,
|
|
123
|
+
alias: notReady,
|
|
124
|
+
flush: notReady,
|
|
125
|
+
reset: notReady,
|
|
126
|
+
session: null,
|
|
127
|
+
consent: null,
|
|
128
|
+
}
|
|
129
|
+
})()
|
|
130
|
+
</script>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export { default as AnalyticsScript } from './AnalyticsScript.astro'
|
|
2
|
+
|
|
3
|
+
// Re-export core factories and types for convenience. The Astro adapter is a
|
|
4
|
+
// thin island over the neutral router — no privileged engine, every vendor is
|
|
5
|
+
// a sink, and the recommended prod topology routes through your server relay.
|
|
6
|
+
export {
|
|
7
|
+
createAnalytics,
|
|
8
|
+
createHttpSink,
|
|
9
|
+
createConsoleSink,
|
|
10
|
+
createMockSink,
|
|
11
|
+
createSession,
|
|
12
|
+
createIdentity,
|
|
13
|
+
createConsent,
|
|
14
|
+
createRedactor,
|
|
15
|
+
createMemoryStorage,
|
|
16
|
+
createLocalStorageAdapter,
|
|
17
|
+
createCookieAdapter,
|
|
18
|
+
resolveStorage,
|
|
19
|
+
campaignFingerprint,
|
|
20
|
+
uuidv4,
|
|
21
|
+
isUuidV4,
|
|
22
|
+
UUID_V4_RE,
|
|
23
|
+
PII_DENY_LIST,
|
|
24
|
+
PII_EXACT_KEYS,
|
|
25
|
+
REDACTED,
|
|
26
|
+
SCHEMA_VERSION,
|
|
27
|
+
DEFAULT_SESSION_TIMEOUT_MS,
|
|
28
|
+
type Analytics,
|
|
29
|
+
type AnalyticsConfig,
|
|
30
|
+
type AnalyticsEvent,
|
|
31
|
+
type AnalyticsEventType,
|
|
32
|
+
type AnalyticsProperties,
|
|
33
|
+
type AnalyticsContext,
|
|
34
|
+
type AnalyticsSink,
|
|
35
|
+
type AnalyticsStorage,
|
|
36
|
+
type SinkInitContext,
|
|
37
|
+
type SinkDeliverContext,
|
|
38
|
+
type SessionConfig,
|
|
39
|
+
type SessionAPI,
|
|
40
|
+
type IdentityConfig,
|
|
41
|
+
type ConsentConfig,
|
|
42
|
+
type ConsentAPI,
|
|
43
|
+
type HttpSinkOptions,
|
|
44
|
+
type CallOptions,
|
|
45
|
+
type ConsoleSinkOptions,
|
|
46
|
+
type MockSink,
|
|
47
|
+
type CreateMockSinkOptions,
|
|
48
|
+
type Redactor,
|
|
49
|
+
} from '../analytics/index.ts'
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* TelemetryScript — injects a <script> that initializes the telemetry client
|
|
4
|
+
* on the page. Exposes `window.__rfr_telemetry` for other scripts to call
|
|
5
|
+
* debug / info / warn / error / fatal / child / startSpan / flush.
|
|
6
|
+
*
|
|
7
|
+
* Props mirror TelemetryConfig from @refraction-ui/logger. Faro stays fully
|
|
8
|
+
* hidden — the only wiring a consumer supplies is the backend `endpoint`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
interface Props extends astroHTML.JSX.HTMLAttributes {
|
|
12
|
+
/** Logical app/service name attached to every record. */
|
|
13
|
+
app: string
|
|
14
|
+
/** Environment preset selector. */
|
|
15
|
+
env: 'development' | 'production'
|
|
16
|
+
/** Remote collector endpoint. Omit for console-only (zero-config dev). */
|
|
17
|
+
endpoint?: string
|
|
18
|
+
/** Master kill switch. `false` -> tree-shakeable noop. Defaults to true. */
|
|
19
|
+
enabled?: boolean
|
|
20
|
+
/** Fraction of records kept, 0..1. Defaults to the preset value. */
|
|
21
|
+
sampleRate?: number
|
|
22
|
+
/** Context keys to strip (deep) before a record is emitted. */
|
|
23
|
+
redactKeys?: string[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const {
|
|
27
|
+
app,
|
|
28
|
+
env,
|
|
29
|
+
endpoint,
|
|
30
|
+
enabled,
|
|
31
|
+
sampleRate,
|
|
32
|
+
redactKeys,
|
|
33
|
+
...props
|
|
34
|
+
} = Astro.props
|
|
35
|
+
|
|
36
|
+
const configJSON = JSON.stringify({
|
|
37
|
+
app,
|
|
38
|
+
env,
|
|
39
|
+
endpoint,
|
|
40
|
+
enabled,
|
|
41
|
+
sampleRate,
|
|
42
|
+
redactKeys,
|
|
43
|
+
})
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
<script
|
|
47
|
+
data-rfr-telemetry-config={configJSON}
|
|
48
|
+
{...props}
|
|
49
|
+
is:inline
|
|
50
|
+
>
|
|
51
|
+
;(function () {
|
|
52
|
+
// Only initialize once
|
|
53
|
+
if (window.__rfr_telemetry) return
|
|
54
|
+
|
|
55
|
+
// We cannot import ES modules from an inline script, so we stash the config
|
|
56
|
+
// and let the consumer call the init function from their own module script.
|
|
57
|
+
// This inline script sets up the namespace and a deferred init helper.
|
|
58
|
+
var configEl = document.querySelector('[data-rfr-telemetry-config]')
|
|
59
|
+
var config = {}
|
|
60
|
+
if (configEl) {
|
|
61
|
+
try { config = JSON.parse(configEl.getAttribute('data-rfr-telemetry-config') || '{}') } catch (_) { /* */ }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function notReady() {
|
|
65
|
+
throw new Error('Telemetry not initialized. Call window.__rfr_telemetry.init(createTelemetry) first.')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Placeholder that will be replaced once createTelemetry is called
|
|
69
|
+
window.__rfr_telemetry = {
|
|
70
|
+
_config: config,
|
|
71
|
+
_api: null,
|
|
72
|
+
_ready: false,
|
|
73
|
+
init: function (createTelemetry, sinks) {
|
|
74
|
+
var api = createTelemetry(config)
|
|
75
|
+
if (sinks) {
|
|
76
|
+
sinks.forEach(function (sink) {
|
|
77
|
+
api.addSink(sink)
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
window.__rfr_telemetry._api = api
|
|
81
|
+
window.__rfr_telemetry._ready = true
|
|
82
|
+
window.__rfr_telemetry.debug = api.debug.bind(api)
|
|
83
|
+
window.__rfr_telemetry.info = api.info.bind(api)
|
|
84
|
+
window.__rfr_telemetry.warn = api.warn.bind(api)
|
|
85
|
+
window.__rfr_telemetry.error = api.error.bind(api)
|
|
86
|
+
window.__rfr_telemetry.fatal = api.fatal.bind(api)
|
|
87
|
+
window.__rfr_telemetry.child = api.child.bind(api)
|
|
88
|
+
window.__rfr_telemetry.startSpan = api.startSpan.bind(api)
|
|
89
|
+
window.__rfr_telemetry.flush = api.flush.bind(api)
|
|
90
|
+
window.__rfr_telemetry.addSink = api.addSink.bind(api)
|
|
91
|
+
window.__rfr_telemetry.removeSink = api.removeSink.bind(api)
|
|
92
|
+
window.__rfr_telemetry.sinks = api.sinks
|
|
93
|
+
return api
|
|
94
|
+
},
|
|
95
|
+
debug: notReady,
|
|
96
|
+
info: notReady,
|
|
97
|
+
warn: notReady,
|
|
98
|
+
error: notReady,
|
|
99
|
+
fatal: notReady,
|
|
100
|
+
child: notReady,
|
|
101
|
+
startSpan: notReady,
|
|
102
|
+
flush: function () { return Promise.resolve() },
|
|
103
|
+
addSink: notReady,
|
|
104
|
+
removeSink: notReady,
|
|
105
|
+
sinks: [],
|
|
106
|
+
}
|
|
107
|
+
})()
|
|
108
|
+
</script>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export { default as TelemetryScript } from './TelemetryScript.astro'
|
|
2
|
+
|
|
3
|
+
// Astro-idiomatic server-side hook
|
|
4
|
+
export {
|
|
5
|
+
createTelemetryMiddleware,
|
|
6
|
+
type TelemetryMiddlewareContext,
|
|
7
|
+
type TelemetryMiddlewareNext,
|
|
8
|
+
type TelemetryMiddlewareOptions,
|
|
9
|
+
} from './telemetry-middleware.js'
|
|
10
|
+
|
|
11
|
+
// Library-origin error capture seam (epic #247 / issue #249)
|
|
12
|
+
export {
|
|
13
|
+
createLibraryErrorCapture,
|
|
14
|
+
captureAstroLibraryError,
|
|
15
|
+
type LibraryErrorCaptureContext,
|
|
16
|
+
type LibraryErrorCaptureNext,
|
|
17
|
+
type LibraryErrorCaptureOptions,
|
|
18
|
+
} from './library-error-capture.js'
|
|
19
|
+
export type {
|
|
20
|
+
LibraryOriginIdentity,
|
|
21
|
+
DevFeedbackRecord,
|
|
22
|
+
DevFeedbackSink,
|
|
23
|
+
} from '../shared/index.ts'
|
|
24
|
+
|
|
25
|
+
// Re-export core types and factories for convenience (Faro stays hidden)
|
|
26
|
+
export {
|
|
27
|
+
createTelemetry,
|
|
28
|
+
createConsoleSink,
|
|
29
|
+
createNoopTelemetry,
|
|
30
|
+
createMockSink,
|
|
31
|
+
redact,
|
|
32
|
+
PRESETS,
|
|
33
|
+
resolvePreset,
|
|
34
|
+
LEVEL_ORDER,
|
|
35
|
+
type Telemetry,
|
|
36
|
+
type TelemetryConfig,
|
|
37
|
+
type TelemetryEnv,
|
|
38
|
+
type TelemetryPreset,
|
|
39
|
+
type Logger,
|
|
40
|
+
type Span,
|
|
41
|
+
type LogLevel,
|
|
42
|
+
type LogContext,
|
|
43
|
+
type LogRecord,
|
|
44
|
+
type SpanRecord,
|
|
45
|
+
type TelemetrySink,
|
|
46
|
+
type ConsoleSinkOptions,
|
|
47
|
+
type MockSinkExtended,
|
|
48
|
+
} from '../logger/index.ts'
|