@cloff/sdk 1.0.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 ADDED
@@ -0,0 +1,244 @@
1
+ # Clof SDK (Client Analytics & Session Replay)
2
+
3
+ A lightweight, performance-optimized, and privacy-preserving Web Analytics, Session Replay, and Canister APM observability library, complete with an offline-first buffering engine and embeddable analytics UI widget.
4
+
5
+ Designed for decentralized apps (dApps) to easily record active user metrics, sessions, custom events, campaign UTM parameters, Core Web Vitals, exceptions (with timeline breadcrumbs), and securely embed verified analytics charts for public display.
6
+
7
+ ---
8
+
9
+ ## Key Features
10
+
11
+ - ⚡ **Lightweight**: Minimal dependencies, keeping your bundle sizes ultra-small.
12
+ - 📶 **Offline-First Buffering**: Telemetry data (page views, events, exceptions) is safely queued in `localStorage` if the user is offline or a request fails, preventing data loss.
13
+ - 🔄 **Auto-Flush Sync**: Automatically listens to browser connectivity transitions (`online` event) to seamlessly flush buffered logs to the registry database.
14
+ - 🐞 **Error & Exception Tracker**: Capture uncaught runtime exceptions and promise rejections with auto-collected chronological breadcrumbs.
15
+ - 🕒 **Session Lifecycle Manager**: Automatically maintains sessions in `sessionStorage` with a 30-minute idle inactivity timeout.
16
+ - 🏷️ **UTM & Referrer Tracking**: Automatically parses and reports campaign sources (`utm_source`, `utm_medium`, `utm_campaign`) and traffic referrers.
17
+ - ⚙️ **Automatic Page View Tracking**: Listeners to automatically track route changes in single-page apps (SPA) using `window.history` monkeypatching.
18
+ - 📈 **Core Web Vitals Observer**: Natively tracks performance metrics (Page Load Time, Largest Contentful Paint, Cumulative Layout Shift, and Interaction to Next Paint).
19
+ - 🧬 **ICP Canister Observability (APM)**: Track and monitor duration, latency, and status (success/reject/error) of actors and canister calls.
20
+ - 📊 **Embeddable Chart Widget**: A responsive, framework-agnostic Custom Element Web Component `<clof-widget>` to show verified public analytics.
21
+
22
+ ---
23
+
24
+ ## Installation
25
+
26
+ Install the package via your preferred package manager:
27
+
28
+ ```bash
29
+ npm install @cloff/sdk
30
+ # or
31
+ yarn add @cloff/sdk
32
+ # or
33
+ pnpm add @cloff/sdk
34
+ ```
35
+
36
+ ---
37
+
38
+ ## 🚀 Usage
39
+
40
+ ### 1. Initialization Config
41
+ Configure the tracker by passing your API Key and optional configuration attributes:
42
+
43
+ ```typescript
44
+ import { Clof } from '@cloff/sdk';
45
+
46
+ const tracker = new Clof('YOUR_API_KEY', {
47
+ debug: false, // Enable console logs for debugging
48
+ autoTrackPageViews: true, // Automatically listen to SPA route changes
49
+ userId: () => getCurrentUid(), // Dynamic getter to retrieve the current user's ID
50
+ enableErrors: true, // Automatically capture uncaught errors & unhandled rejections
51
+ enableAPM: true, // Track network request durations and Page Vitals
52
+ enableCanisterAPM: true, // Track ICP canister calls automatically
53
+ captureConsole: false // Capture console.error statements as breadcrumbs
54
+ });
55
+ ```
56
+
57
+ #### Configuration Options
58
+ * `debug`: `boolean` (default `false`) — Logs SDK actions to the developer console.
59
+ * `autoTrackPageViews`: `boolean` (default `false`) — Automatically hooks into browser navigation/history to log page views.
60
+ * `userId`: `string | (() => string | null | undefined)` — Required if `autoTrackPageViews` is enabled. A string or function returning the active user ID.
61
+ * `enableErrors`: `boolean` (default `true`) — Automatically catches unhandled browser errors, promise rejections, and enables manual error reporting.
62
+ * `enableAPM`: `boolean` (default `true`) — Captures web performance metrics and Core Web Vitals (LCP, CLS, INP).
63
+ * `enableCanisterAPM`: `boolean` (default `true`) — Captures duration, canister ID, method name, call types, and responses for smart contract interactions.
64
+ * `captureConsole`: `boolean` (default `false`) — Monopoles `console.error` calls and appends them as diagnostic breadcrumbs.
65
+
66
+ ---
67
+
68
+ ### 2. Manual Tracking APIs
69
+
70
+ #### A. Track Page Views (with Performance Vitals)
71
+ Manually track page views. The SDK automatically appends page load speeds and Web Vitals if observed:
72
+
73
+ ```typescript
74
+ tracker.trackPageView('user_wallet_address', {
75
+ path: '/marketplace', // Defaults to window.location.pathname
76
+ title: 'NFT Marketplace', // Defaults to document.title
77
+ referrer: 'https://google.com' // Defaults to document.referrer
78
+ });
79
+ ```
80
+
81
+ #### B. Track Custom Events
82
+ Log custom interaction metrics, user goals, or conversion triggers:
83
+
84
+ ```typescript
85
+ tracker.trackEvent('user_wallet_address', 'swap_completed', {
86
+ pair: 'ICP-USDT',
87
+ amount: 250.0,
88
+ slippage: 0.005
89
+ });
90
+ ```
91
+
92
+ #### C. Deduplicated Active User Ping
93
+ Pings the server once per day per user (automatically cached and deduplicated in `localStorage` to save network bandwidth):
94
+
95
+ ```typescript
96
+ tracker.track('user_wallet_address');
97
+ ```
98
+
99
+ ---
100
+
101
+ ### 3. Canister APM Tracking
102
+
103
+ Track calls to smart contract canisters on the Internet Computer. The SDK collects latencies, success rates, and errors.
104
+
105
+ ```typescript
106
+ tracker.trackCanisterCall(
107
+ 'ryjl3-tyaaa-aaaaa-aaaba-cai', // Canister ID
108
+ 'submitPost', // Method Name
109
+ 'success', // 'success' | 'error'
110
+ 140, // duration in milliseconds
111
+ { gasLimit: 300000 } // Optional metadata payload
112
+ );
113
+ ```
114
+
115
+ ---
116
+
117
+ ### 4. Error & Exception Handling APIs
118
+
119
+ You can capture exceptions manually in try/catch blocks or hook them into framework-level error boundaries.
120
+
121
+ #### A. Capture Exception
122
+ Send a standard `Error` object or string description with optional contextual metadata. Uncaught exceptions will also package a chronological text timeline of the last 50 user journey actions (breadcrumbs) to enable **Session Replay**:
123
+
124
+ ```typescript
125
+ try {
126
+ executeContractCall();
127
+ } catch (error) {
128
+ tracker.captureException(error, {
129
+ gasLimit: 3000000,
130
+ canisterId: 'ryjl3-tyaaa-aaaaa-aaaba-cai'
131
+ });
132
+ }
133
+ ```
134
+
135
+ #### B. Capture Message
136
+ Log arbitrary log events, warning states, or telemetry markers:
137
+
138
+ ```typescript
139
+ tracker.captureMessage('Transaction took longer than expected', 'warning', {
140
+ durationMs: 8200
141
+ });
142
+ ```
143
+
144
+ ---
145
+
146
+ ### 5. Offline-First Queuing Engine
147
+
148
+ If a user's device loses internet connection, the SDK ensures no analytics or crash data is lost:
149
+ 1. All events (`trackPageView`, `trackEvent`, `captureException`) are serialized and buffered in `localStorage` under the key `clof_telemetry_queue_[apiKey]`.
150
+ 2. A queue buffer holds up to `500` events using First-In-First-Out eviction.
151
+ 3. The SDK hooks into the browser `online` window event. When the internet connection returns, it automatically flushes all buffered events sequentially in the background.
152
+
153
+ ---
154
+
155
+ ## 📊 Public Analytics Widget Embed
156
+
157
+ Embed the responsive `<clof-widget>` (or legacy `<socio-dau-widget>`) Custom Element to display active metrics charts and demographics publicly.
158
+
159
+ ### Widget Properties
160
+ - `api-key`: (Required) Your registered app's API key.
161
+ - `days`: Number of days of history to display on the SVG chart (default `30`).
162
+ - `theme`: `dark` or `light` (default auto-detects browser/system theme).
163
+ - `accent-color`: Hex color overriding the primary purple style (e.g. `#10b981` for emerald).
164
+
165
+ ### 1. React SPA (Vite / CRA)
166
+ ```tsx
167
+ import { useEffect } from 'react';
168
+
169
+ export default function VerifiedAnalytics() {
170
+ useEffect(() => {
171
+ // Loads custom element client-side
172
+ import('@cloff/sdk/widget');
173
+ }, []);
174
+
175
+ return (
176
+ <div style={{ width: '100%', maxWidth: '600px' }}>
177
+ {/* @ts-ignore */}
178
+ <clof-widget api-key="YOUR_API_KEY" days="30" theme="dark" />
179
+ </div>
180
+ );
181
+ }
182
+ ```
183
+
184
+ ### 2. Next.js (SSR with Dynamic Import)
185
+ To prevent server-side custom element rendering warnings and hydration mismatches, wrap the widget in a client component and load it dynamically with `ssr: false`:
186
+
187
+ **Step 1: Create a client component wrapper (e.g. `WidgetWrapper.tsx`)**
188
+ ```tsx
189
+ 'use client';
190
+
191
+ import { useEffect } from 'react';
192
+
193
+ export default function WidgetWrapper() {
194
+ useEffect(() => {
195
+ import('@cloff/sdk/widget');
196
+ }, []);
197
+
198
+ return <clof-widget api-key="YOUR_API_KEY" days="30" theme="dark" />;
199
+ }
200
+ ```
201
+
202
+ **Step 2: Load with dynamic import in your page**
203
+ ```tsx
204
+ import dynamic from 'next/dynamic';
205
+
206
+ const PublicWidget = dynamic(() => import('./WidgetWrapper'), {
207
+ ssr: false,
208
+ loading: () => <p>Loading Analytics Widget...</p>,
209
+ });
210
+ ```
211
+
212
+ ### 3. HTML / CDN Embed
213
+ ```html
214
+ <!-- Load the widget Custom Element script -->
215
+ <script type="module" src="https://esm.sh/@cloff/sdk@1.0.0/dist/widget.js"></script>
216
+
217
+ <!-- Embed the custom element -->
218
+ <clof-widget api-key="YOUR_API_KEY" days="30" theme="dark"></clof-widget>
219
+ ```
220
+
221
+ ### TypeScript JSX Declarations
222
+ Add this interface to your `global.d.ts` or type declaration file to prevent JSX compilation element errors:
223
+
224
+ ```typescript
225
+ declare namespace JSX {
226
+ interface IntrinsicElements {
227
+ 'clof-widget': React.DetailedHTMLProps<
228
+ React.HTMLAttributes<HTMLElement> & {
229
+ 'api-key': string;
230
+ days?: string | number;
231
+ theme?: 'dark' | 'light';
232
+ 'accent-color'?: string;
233
+ },
234
+ HTMLElement
235
+ >;
236
+ }
237
+ }
238
+ ```
239
+
240
+ ---
241
+
242
+ ## License
243
+
244
+ MIT License.
@@ -0,0 +1,205 @@
1
+ export interface TrackerConfig {
2
+ supabaseUrl?: string;
3
+ supabaseAnonKey?: string;
4
+ debug?: boolean;
5
+ autoTrackPageViews?: boolean;
6
+ userId?: string | (() => string | null | undefined);
7
+ enableErrors?: boolean;
8
+ enableAPM?: boolean;
9
+ enableCanisterAPM?: boolean;
10
+ captureConsole?: boolean;
11
+ }
12
+ export declare class Clof {
13
+ private apiKey;
14
+ private supabase;
15
+ private debug;
16
+ private sessionId;
17
+ private configUserId;
18
+ private enableErrors;
19
+ private enableAPM;
20
+ private enableCanisterAPM;
21
+ private captureConsole;
22
+ private breadcrumbs;
23
+ private maxBreadcrumbs;
24
+ private vitals;
25
+ constructor(apiKey: string, config?: TrackerConfig);
26
+ /**
27
+ * Helper to identify device form-factor from Navigator UA string
28
+ */
29
+ private getDeviceType;
30
+ /**
31
+ * Helper to extract browser name from UA string
32
+ */
33
+ private getBrowser;
34
+ /**
35
+ * Helper to extract OS name from UA string
36
+ */
37
+ private getOS;
38
+ /**
39
+ * Helper to retrieve timezone identifier as location
40
+ */
41
+ private getLocation;
42
+ /**
43
+ * Helper to parse UTM parameters from search URL
44
+ */
45
+ private getUTMParams;
46
+ /**
47
+ * Generate UUID v4
48
+ */
49
+ private generateUUID;
50
+ /**
51
+ * Resolve and ping the current user session
52
+ */
53
+ private getSessionId;
54
+ private registerSession;
55
+ private pingSession;
56
+ /**
57
+ * Observes page loading performance and layout stability
58
+ */
59
+ private setupVitalsObserver;
60
+ /**
61
+ * Setup a click listener to log interaction breadcrumbs
62
+ */
63
+ private setupClickObserver;
64
+ /**
65
+ * Intercept console warnings & errors to record breadcrumbs
66
+ */
67
+ private setupConsoleObserver;
68
+ /**
69
+ * Listen to global unhandled exceptions and promise rejections
70
+ */
71
+ private setupErrorObservers;
72
+ /**
73
+ * Hook global fetch to auto-trace API durations and catch status >= 400 silent errors
74
+ */
75
+ private setupFetchInterception;
76
+ /**
77
+ * Estimate cycles consumed by a canister call using client-side heuristics.
78
+ * Query calls are free on ICP. Update calls incur:
79
+ * base_fee (5M) + est_instructions × 1 cycle/instruction + ingress_bytes × 2000 cycles/byte
80
+ */
81
+ static estimateCanisterCycles(callType: 'query' | 'update', durationMs: number, requestSize?: number): number;
82
+ /**
83
+ * Log a canister call action manually
84
+ */
85
+ trackCanisterCall(canisterId: string, method: string, status: 'success' | 'error', durationMs?: number, metadata?: any): void;
86
+ /**
87
+ * Add manual trace breadcrumbs
88
+ */
89
+ addBreadcrumb(message: string, category?: string, metadata?: any): void;
90
+ /**
91
+ * Track user activity (Legacy DAU Ping).
92
+ * Uses localStorage to deduplicate pings on the same day for a given user.
93
+ */
94
+ track(userId: string | number, force?: boolean): Promise<{
95
+ success: boolean;
96
+ error?: string;
97
+ }>;
98
+ /**
99
+ * Track specific Page Views.
100
+ * Logs page navigation events, referrer, and captures performance Web Vitals.
101
+ */
102
+ trackPageView(userId: string | number, options?: {
103
+ path?: string;
104
+ title?: string;
105
+ referrer?: string;
106
+ }): Promise<{
107
+ success: boolean;
108
+ error?: string;
109
+ }>;
110
+ /**
111
+ * Track Custom Events (actions, goals, conversions)
112
+ */
113
+ trackEvent(userId: string | number, eventName: string, properties?: Record<string, any>): Promise<{
114
+ success: boolean;
115
+ error?: string;
116
+ }>;
117
+ /**
118
+ * Log an exception manually to the database
119
+ */
120
+ captureException(error: any, extraContext?: any): Promise<{
121
+ success: boolean;
122
+ error?: string;
123
+ }>;
124
+ /**
125
+ * Log a custom severity warning or info message to the database
126
+ */
127
+ captureMessage(message: string, severity?: 'error' | 'warning' | 'info', extraContext?: any): Promise<{
128
+ success: boolean;
129
+ error?: string;
130
+ }>;
131
+ /**
132
+ * Helper function to execute error logging RPC
133
+ */
134
+ private logErrorToDatabase;
135
+ /**
136
+ * Log latency of a duration in milliseconds manually
137
+ */
138
+ trackPerformanceSpan(name: string, durationMs: number, entryType?: string, metadata?: any): Promise<{
139
+ success: boolean;
140
+ error?: string;
141
+ }>;
142
+ /**
143
+ * Log latency of an ICP canister call with method-level granularity
144
+ */
145
+ trackCanisterSpan(canisterId: string, methodName: string, callType: 'query' | 'update', durationMs: number, status?: 'success' | 'reject' | 'error', errorMsg?: string, requestSize?: number, responseSize?: number): Promise<{
146
+ success: boolean;
147
+ error?: string;
148
+ }>;
149
+ /**
150
+ * Wrap an @dfinity/agent HttpAgent to intercept canister calls for APM.
151
+ * Returns the same agent proxied — no @dfinity/agent dependency needed.
152
+ *
153
+ * Usage:
154
+ * import { HttpAgent } from '@dfinity/agent';
155
+ * const agent = new HttpAgent();
156
+ * const wrapped = tracker.wrapHttpAgent(agent);
157
+ * // use wrapped everywhere instead of agent
158
+ */
159
+ wrapHttpAgent<T>(agent: T): T;
160
+ /**
161
+ * Start a performance span timer
162
+ */
163
+ startSpan(name: string, entryType?: string): {
164
+ end: (metadata?: any) => Promise<void>;
165
+ };
166
+ /**
167
+ * Measure latency of a function and resolve its result
168
+ */
169
+ measure<T>(name: string, fn: () => Promise<T> | T, entryType?: string, metadata?: any): Promise<T>;
170
+ /**
171
+ * Define a user funnel for conversion analysis.
172
+ * Stores the ordered event sequence to the database so the
173
+ * dashboard can visualize drop-off at each step.
174
+ *
175
+ * Example:
176
+ * tracker.defineFunnel('Swap Flow', [
177
+ * 'connect_wallet',
178
+ * 'click_swap_token',
179
+ * 'sign_transaction',
180
+ * 'swap_success'
181
+ * ])
182
+ */
183
+ defineFunnel(name: string, steps: string[]): Promise<{
184
+ success: boolean;
185
+ error?: string;
186
+ }>;
187
+ private resolveUserId;
188
+ /**
189
+ * Listen to Single-Page App (SPA) route changes and track page views automatically
190
+ */
191
+ private setupAutoTracking;
192
+ private setupOnlineListener;
193
+ private isOnline;
194
+ private isNetworkError;
195
+ private getQueueKey;
196
+ private getOfflineQueue;
197
+ private saveOfflineQueue;
198
+ private enqueueOfflineAction;
199
+ private isFlushing;
200
+ private flushOfflineQueue;
201
+ private executeRpc;
202
+ private logDebug;
203
+ private logError;
204
+ }
205
+ export { Clof as SocioDauTracker, Clof as ClofTracker };