@flightdev/analytics 0.0.2

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 Flight Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,363 @@
1
+ # @flight-framework/analytics
2
+
3
+ Privacy-focused, universal analytics for Flight Framework. Works with any analytics provider through adapters.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Features](#features)
8
+ - [Installation](#installation)
9
+ - [Quick Start](#quick-start)
10
+ - [Adapters](#adapters)
11
+ - [Core API](#core-api)
12
+ - [React Integration](#react-integration)
13
+ - [Server-Side Tracking](#server-side-tracking)
14
+ - [Privacy and Consent](#privacy-and-consent)
15
+ - [API Reference](#api-reference)
16
+ - [License](#license)
17
+
18
+ ---
19
+
20
+ ## Features
21
+
22
+ - Single API for multiple analytics providers
23
+ - Privacy-first design with consent management
24
+ - Automatic page view tracking
25
+ - Server-side event tracking
26
+ - Type-safe event definitions
27
+ - Batching and retry logic
28
+ - React hooks for easy integration
29
+ - GDPR/CCPA compliant consent handling
30
+
31
+ ---
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ npm install @flight-framework/analytics
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Quick Start
42
+
43
+ ```typescript
44
+ import { createAnalytics } from '@flight-framework/analytics';
45
+ import { plausible } from '@flight-framework/analytics/plausible';
46
+
47
+ const analytics = createAnalytics(plausible({
48
+ domain: 'example.com',
49
+ }));
50
+
51
+ // Track page view
52
+ analytics.trackPageview('/about');
53
+
54
+ // Track custom event
55
+ analytics.trackEvent('signup', { plan: 'pro', source: 'landing' });
56
+
57
+ // Identify user (for providers that support it)
58
+ analytics.identify('user_123', { email: 'user@example.com' });
59
+ ```
60
+
61
+ ---
62
+
63
+ ## Adapters
64
+
65
+ ### Plausible (Privacy-Focused)
66
+
67
+ Simple, privacy-friendly analytics without cookies.
68
+
69
+ ```typescript
70
+ import { plausible } from '@flight-framework/analytics/plausible';
71
+
72
+ const adapter = plausible({
73
+ domain: 'example.com',
74
+ apiHost: 'https://plausible.io', // Or self-hosted
75
+ hashMode: false, // Hash-based routing
76
+ });
77
+ ```
78
+
79
+ ### PostHog (Product Analytics)
80
+
81
+ Full-featured product analytics with session recording.
82
+
83
+ ```typescript
84
+ import { posthog } from '@flight-framework/analytics/posthog';
85
+
86
+ const adapter = posthog({
87
+ apiKey: process.env.POSTHOG_API_KEY,
88
+ host: 'https://app.posthog.com', // Or self-hosted
89
+ autocapture: true,
90
+ sessionRecording: true,
91
+ });
92
+ ```
93
+
94
+ ### Google Analytics 4
95
+
96
+ ```typescript
97
+ import { ga4 } from '@flight-framework/analytics/ga4';
98
+
99
+ const adapter = ga4({
100
+ measurementId: 'G-XXXXXXXXXX',
101
+ debug: process.env.NODE_ENV === 'development',
102
+ });
103
+ ```
104
+
105
+ ### Mixpanel
106
+
107
+ ```typescript
108
+ import { mixpanel } from '@flight-framework/analytics/mixpanel';
109
+
110
+ const adapter = mixpanel({
111
+ token: process.env.MIXPANEL_TOKEN,
112
+ debug: false,
113
+ });
114
+ ```
115
+
116
+ ### Amplitude
117
+
118
+ ```typescript
119
+ import { amplitude } from '@flight-framework/analytics/amplitude';
120
+
121
+ const adapter = amplitude({
122
+ apiKey: process.env.AMPLITUDE_API_KEY,
123
+ serverZone: 'US', // or 'EU'
124
+ });
125
+ ```
126
+
127
+ ### Multiple Providers
128
+
129
+ Send events to multiple providers simultaneously:
130
+
131
+ ```typescript
132
+ import { multi } from '@flight-framework/analytics/multi';
133
+
134
+ const analytics = createAnalytics(multi([
135
+ plausible({ domain: 'example.com' }),
136
+ posthog({ apiKey: '...' }),
137
+ ]));
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Core API
143
+
144
+ ### trackPageview(path, properties?)
145
+
146
+ Track a page view:
147
+
148
+ ```typescript
149
+ analytics.trackPageview('/products/widget');
150
+
151
+ // With additional properties
152
+ analytics.trackPageview('/products/widget', {
153
+ referrer: document.referrer,
154
+ title: document.title,
155
+ });
156
+ ```
157
+
158
+ ### trackEvent(name, properties?)
159
+
160
+ Track a custom event:
161
+
162
+ ```typescript
163
+ // Simple event
164
+ analytics.trackEvent('button_click');
165
+
166
+ // With properties
167
+ analytics.trackEvent('purchase', {
168
+ product_id: 'prod_123',
169
+ price: 49.99,
170
+ currency: 'USD',
171
+ });
172
+ ```
173
+
174
+ ### identify(userId, traits?)
175
+
176
+ Identify a user for user-level analytics:
177
+
178
+ ```typescript
179
+ analytics.identify('user_123', {
180
+ email: 'user@example.com',
181
+ plan: 'pro',
182
+ company: 'Acme Inc',
183
+ signupDate: new Date(),
184
+ });
185
+ ```
186
+
187
+ ### reset()
188
+
189
+ Clear user identity (on logout):
190
+
191
+ ```typescript
192
+ analytics.reset();
193
+ ```
194
+
195
+ ---
196
+
197
+ ## React Integration
198
+
199
+ ### AnalyticsProvider
200
+
201
+ Wrap your app with the provider:
202
+
203
+ ```tsx
204
+ import { AnalyticsProvider } from '@flight-framework/analytics/react';
205
+
206
+ function App() {
207
+ return (
208
+ <AnalyticsProvider client={analytics}>
209
+ <Routes />
210
+ </AnalyticsProvider>
211
+ );
212
+ }
213
+ ```
214
+
215
+ ### useAnalytics Hook
216
+
217
+ ```tsx
218
+ import { useAnalytics } from '@flight-framework/analytics/react';
219
+
220
+ function SignupButton() {
221
+ const { trackEvent } = useAnalytics();
222
+
223
+ return (
224
+ <button onClick={() => trackEvent('signup_click', { location: 'header' })}>
225
+ Sign Up
226
+ </button>
227
+ );
228
+ }
229
+ ```
230
+
231
+ ### usePageview Hook
232
+
233
+ Automatic page view tracking:
234
+
235
+ ```tsx
236
+ import { usePageview } from '@flight-framework/analytics/react';
237
+
238
+ function Page() {
239
+ usePageview(); // Tracks on mount and route changes
240
+
241
+ return <div>...</div>;
242
+ }
243
+ ```
244
+
245
+ ### TrackEvent Component
246
+
247
+ Declarative event tracking:
248
+
249
+ ```tsx
250
+ import { TrackEvent } from '@flight-framework/analytics/react';
251
+
252
+ function PricingPage() {
253
+ return (
254
+ <>
255
+ <TrackEvent name="pricing_view" properties={{ variant: 'A' }} />
256
+ <h1>Pricing</h1>
257
+ </>
258
+ );
259
+ }
260
+ ```
261
+
262
+ ---
263
+
264
+ ## Server-Side Tracking
265
+
266
+ Track events from your server or API routes:
267
+
268
+ ```typescript
269
+ import { createServerAnalytics } from '@flight-framework/analytics/server';
270
+
271
+ const serverAnalytics = createServerAnalytics(posthog({
272
+ apiKey: process.env.POSTHOG_API_KEY,
273
+ }));
274
+
275
+ // In your API handler
276
+ export async function POST(request: Request) {
277
+ const { userId, action } = await request.json();
278
+
279
+ await serverAnalytics.trackEvent('api_action', {
280
+ action,
281
+ userId,
282
+ ip: request.headers.get('x-forwarded-for'),
283
+ });
284
+
285
+ return Response.json({ success: true });
286
+ }
287
+ ```
288
+
289
+ ---
290
+
291
+ ## Privacy and Consent
292
+
293
+ ### Consent Management
294
+
295
+ ```typescript
296
+ const analytics = createAnalytics(adapter, {
297
+ consent: {
298
+ required: true, // Wait for consent before tracking
299
+ cookieName: 'consent',
300
+ defaultValue: false,
301
+ },
302
+ });
303
+
304
+ // User gives consent
305
+ analytics.setConsent(true);
306
+
307
+ // Check consent status
308
+ if (analytics.hasConsent()) {
309
+ // Track
310
+ }
311
+ ```
312
+
313
+ ### React Consent Hook
314
+
315
+ ```tsx
316
+ import { useConsent } from '@flight-framework/analytics/react';
317
+
318
+ function CookieBanner() {
319
+ const { hasConsent, setConsent } = useConsent();
320
+
321
+ if (hasConsent) return null;
322
+
323
+ return (
324
+ <div className="cookie-banner">
325
+ <p>We use analytics to improve our site.</p>
326
+ <button onClick={() => setConsent(true)}>Accept</button>
327
+ <button onClick={() => setConsent(false)}>Decline</button>
328
+ </div>
329
+ );
330
+ }
331
+ ```
332
+
333
+ ---
334
+
335
+ ## API Reference
336
+
337
+ ### createAnalytics Options
338
+
339
+ | Option | Type | Default | Description |
340
+ |--------|------|---------|-------------|
341
+ | `debug` | `boolean` | `false` | Log events to console |
342
+ | `disabled` | `boolean` | `false` | Disable all tracking |
343
+ | `consent.required` | `boolean` | `false` | Require consent before tracking |
344
+ | `batch.enabled` | `boolean` | `true` | Batch events |
345
+ | `batch.size` | `number` | `10` | Max events per batch |
346
+ | `batch.timeout` | `number` | `5000` | Flush timeout in ms |
347
+
348
+ ### Analytics Methods
349
+
350
+ | Method | Description |
351
+ |--------|-------------|
352
+ | `trackPageview(path, props?)` | Track page view |
353
+ | `trackEvent(name, props?)` | Track custom event |
354
+ | `identify(userId, traits?)` | Identify user |
355
+ | `reset()` | Clear user identity |
356
+ | `setConsent(value)` | Update consent |
357
+ | `hasConsent()` | Check consent status |
358
+
359
+ ---
360
+
361
+ ## License
362
+
363
+ MIT
@@ -0,0 +1,13 @@
1
+ import { AnalyticsAdapterFactory } from '../index.js';
2
+
3
+ /**
4
+ * Plausible Analytics Adapter
5
+ */
6
+
7
+ interface PlausibleConfig {
8
+ domain: string;
9
+ apiHost?: string;
10
+ }
11
+ declare const plausible: AnalyticsAdapterFactory<PlausibleConfig>;
12
+
13
+ export { type PlausibleConfig, plausible as default, plausible };
@@ -0,0 +1,36 @@
1
+ // src/adapters/plausible.ts
2
+ var plausible = (config) => {
3
+ const { domain, apiHost = "https://plausible.io" } = config;
4
+ async function send(eventName, props) {
5
+ try {
6
+ await fetch(`${apiHost}/api/event`, {
7
+ method: "POST",
8
+ headers: { "Content-Type": "application/json" },
9
+ body: JSON.stringify({
10
+ domain,
11
+ name: eventName,
12
+ url: typeof window !== "undefined" ? window.location.href : "",
13
+ referrer: typeof document !== "undefined" ? document.referrer : "",
14
+ screen_width: typeof window !== "undefined" ? window.innerWidth : void 0,
15
+ props
16
+ })
17
+ });
18
+ } catch {
19
+ }
20
+ }
21
+ const adapter = {
22
+ name: "plausible",
23
+ trackPageview(_data) {
24
+ send("pageview");
25
+ },
26
+ trackEvent(data) {
27
+ send(data.name, data.props);
28
+ }
29
+ };
30
+ return adapter;
31
+ };
32
+ var plausible_default = plausible;
33
+ export {
34
+ plausible_default as default,
35
+ plausible
36
+ };
@@ -0,0 +1,13 @@
1
+ import { AnalyticsAdapterFactory } from '../index.js';
2
+
3
+ /**
4
+ * PostHog Analytics Adapter
5
+ */
6
+
7
+ interface PostHogConfig {
8
+ apiKey: string;
9
+ apiHost?: string;
10
+ }
11
+ declare const posthog: AnalyticsAdapterFactory<PostHogConfig>;
12
+
13
+ export { type PostHogConfig, posthog as default, posthog };
@@ -0,0 +1,44 @@
1
+ // src/adapters/posthog.ts
2
+ var posthog = (config) => {
3
+ const { apiKey, apiHost = "https://app.posthog.com" } = config;
4
+ let distinctId = `anon_${Math.random().toString(36).slice(2)}`;
5
+ async function capture(event, properties) {
6
+ try {
7
+ await fetch(`${apiHost}/capture/`, {
8
+ method: "POST",
9
+ headers: { "Content-Type": "application/json" },
10
+ body: JSON.stringify({
11
+ api_key: apiKey,
12
+ event,
13
+ properties: { distinct_id: distinctId, ...properties },
14
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
15
+ })
16
+ });
17
+ } catch {
18
+ }
19
+ }
20
+ const adapter = {
21
+ name: "posthog",
22
+ trackPageview(data) {
23
+ capture("$pageview", { $current_url: data.url, $referrer: data.referrer });
24
+ },
25
+ trackEvent(data) {
26
+ capture(data.name, data.props);
27
+ },
28
+ identify(userId, traits) {
29
+ distinctId = userId;
30
+ if (traits) {
31
+ capture("$identify", { $set: traits });
32
+ }
33
+ },
34
+ setUserId(userId) {
35
+ distinctId = userId ?? `anon_${Math.random().toString(36).slice(2)}`;
36
+ }
37
+ };
38
+ return adapter;
39
+ };
40
+ var posthog_default = posthog;
41
+ export {
42
+ posthog_default as default,
43
+ posthog
44
+ };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @flightdev/analytics - Agnostic Analytics
3
+ *
4
+ * @example
5
+ * ```typescript
6
+ * import { createAnalytics } from '@flightdev/analytics';
7
+ * import { plausible } from '@flightdev/analytics/plausible';
8
+ *
9
+ * const analytics = createAnalytics(plausible({
10
+ * domain: 'example.com',
11
+ * }));
12
+ *
13
+ * analytics.trackPageview('/home');
14
+ * analytics.trackEvent('signup', { plan: 'pro' });
15
+ * ```
16
+ */
17
+ interface PageviewData {
18
+ url: string;
19
+ referrer?: string;
20
+ deviceWidth?: number;
21
+ }
22
+ interface EventData {
23
+ name: string;
24
+ props?: Record<string, string | number | boolean>;
25
+ revenue?: {
26
+ amount: number;
27
+ currency: string;
28
+ };
29
+ }
30
+ interface AnalyticsAdapter {
31
+ readonly name: string;
32
+ trackPageview(data: PageviewData): void;
33
+ trackEvent(data: EventData): void;
34
+ identify?(userId: string, traits?: Record<string, unknown>): void;
35
+ setUserId?(userId: string | null): void;
36
+ }
37
+ type AnalyticsAdapterFactory<TConfig = unknown> = (config: TConfig) => AnalyticsAdapter;
38
+ interface AnalyticsService {
39
+ readonly adapter: AnalyticsAdapter;
40
+ trackPageview(url: string, options?: Omit<PageviewData, 'url'>): void;
41
+ trackEvent(name: string, props?: Record<string, string | number | boolean>): void;
42
+ identify(userId: string, traits?: Record<string, unknown>): void;
43
+ reset(): void;
44
+ }
45
+ declare function createAnalytics(adapter: AnalyticsAdapter): AnalyticsService;
46
+ declare function getDeviceType(width: number): 'mobile' | 'tablet' | 'desktop';
47
+ declare function parseUTMParams(url: string): Record<string, string>;
48
+
49
+ export { type AnalyticsAdapter, type AnalyticsAdapterFactory, type AnalyticsService, type EventData, type PageviewData, createAnalytics, getDeviceType, parseUTMParams };
package/dist/index.js ADDED
@@ -0,0 +1,37 @@
1
+ // src/index.ts
2
+ function createAnalytics(adapter) {
3
+ return {
4
+ adapter,
5
+ trackPageview(url, options) {
6
+ adapter.trackPageview({ url, ...options });
7
+ },
8
+ trackEvent(name, props) {
9
+ adapter.trackEvent({ name, props });
10
+ },
11
+ identify(userId, traits) {
12
+ adapter.identify?.(userId, traits);
13
+ },
14
+ reset() {
15
+ adapter.setUserId?.(null);
16
+ }
17
+ };
18
+ }
19
+ function getDeviceType(width) {
20
+ if (width < 768) return "mobile";
21
+ if (width < 1024) return "tablet";
22
+ return "desktop";
23
+ }
24
+ function parseUTMParams(url) {
25
+ const params = new URL(url).searchParams;
26
+ const utm = {};
27
+ for (const key of ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content"]) {
28
+ const value = params.get(key);
29
+ if (value) utm[key] = value;
30
+ }
31
+ return utm;
32
+ }
33
+ export {
34
+ createAnalytics,
35
+ getDeviceType,
36
+ parseUTMParams
37
+ };
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@flightdev/analytics",
3
+ "version": "0.0.2",
4
+ "description": "Agnostic analytics for Flight Framework. Choose your backend: Plausible, Umami, PostHog, or custom.",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ },
11
+ "./plausible": {
12
+ "types": "./dist/adapters/plausible.d.ts",
13
+ "import": "./dist/adapters/plausible.js"
14
+ },
15
+ "./posthog": {
16
+ "types": "./dist/adapters/posthog.d.ts",
17
+ "import": "./dist/adapters/posthog.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "devDependencies": {
24
+ "tsup": "^8.0.0",
25
+ "typescript": "^5.7.0",
26
+ "vitest": "^2.0.0"
27
+ },
28
+ "license": "MIT",
29
+ "scripts": {
30
+ "build": "tsup",
31
+ "test": "vitest run",
32
+ "typecheck": "tsc --noEmit"
33
+ }
34
+ }