@deriv-com/analytics 1.36.0 → 1.38.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 +853 -72
- package/dist/browser/analytics.bundle.global.js +23 -0
- package/dist/browser/analytics.bundle.global.js.map +1 -0
- package/dist/browser/analytics.esm.mjs +9 -0
- package/dist/browser/analytics.esm.mjs.map +1 -0
- package/dist/chunk-3LFZFQL4.mjs +3 -0
- package/dist/chunk-3LFZFQL4.mjs.map +1 -0
- package/dist/chunk-DNCZM4KN.mjs +4 -0
- package/dist/chunk-DNCZM4KN.mjs.map +1 -0
- package/dist/chunk-EMF3RT6E.mjs +14 -0
- package/dist/chunk-EMF3RT6E.mjs.map +1 -0
- package/dist/chunk-HQX3Z7PF.mjs +3 -0
- package/dist/chunk-HQX3Z7PF.mjs.map +1 -0
- package/dist/chunk-KDJ46GSX.mjs +3 -0
- package/dist/chunk-KDJ46GSX.mjs.map +1 -0
- package/dist/chunk-LKLVBL24.js +3 -0
- package/dist/chunk-LKLVBL24.js.map +1 -0
- package/dist/chunk-NOEKD4DT.js +4 -0
- package/dist/chunk-NOEKD4DT.js.map +1 -0
- package/dist/chunk-NZ35MU4O.js +3 -0
- package/dist/chunk-NZ35MU4O.js.map +1 -0
- package/dist/growthbook-NJGOOJH4.mjs +3 -0
- package/dist/growthbook-NJGOOJH4.mjs.map +1 -0
- package/dist/index.d.mts +114 -0
- package/dist/index.d.ts +114 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +4 -0
- package/dist/index.mjs.map +1 -0
- package/dist/metafile-cjs.json +1 -0
- package/dist/metafile-esm.json +1 -0
- package/dist/metafile-iife.json +1 -0
- package/dist/posthog-D3QL9y8a.d.ts +95 -0
- package/dist/posthog-DGiwrEsA.d.mts +95 -0
- package/dist/posthog-P3AXUMLM.mjs +6 -0
- package/dist/posthog-P3AXUMLM.mjs.map +1 -0
- package/dist/providers/growthbook/index.d.mts +73 -0
- package/dist/providers/growthbook/index.d.ts +73 -0
- package/dist/providers/growthbook/index.js +3 -0
- package/dist/providers/growthbook/index.js.map +1 -0
- package/dist/providers/growthbook/index.mjs +3 -0
- package/dist/providers/growthbook/index.mjs.map +1 -0
- package/dist/providers/posthog/index.d.mts +3 -0
- package/dist/providers/posthog/index.d.ts +3 -0
- package/dist/providers/posthog/index.js +3 -0
- package/dist/providers/posthog/index.js.map +1 -0
- package/dist/providers/posthog/index.mjs +3 -0
- package/dist/providers/posthog/index.mjs.map +1 -0
- package/dist/providers/rudderstack/index.d.mts +73 -0
- package/dist/providers/rudderstack/index.d.ts +73 -0
- package/dist/providers/rudderstack/index.js +2 -0
- package/dist/providers/rudderstack/index.js.map +1 -0
- package/dist/providers/rudderstack/index.mjs +2 -0
- package/dist/providers/rudderstack/index.mjs.map +1 -0
- package/{lib/types.d.ts → dist/types-DoMejCXv.d.mts} +18 -29
- package/dist/types-DoMejCXv.d.ts +361 -0
- package/dist/utils/analytics-cache/index.d.mts +138 -0
- package/dist/utils/analytics-cache/index.d.ts +138 -0
- package/dist/utils/analytics-cache/index.js +2 -0
- package/dist/utils/analytics-cache/index.js.map +1 -0
- package/dist/utils/analytics-cache/index.mjs +2 -0
- package/dist/utils/analytics-cache/index.mjs.map +1 -0
- package/package.json +97 -68
- package/dist/analytics.bundle.js +0 -1
- package/lib/analytics.d.ts +0 -66
- package/lib/analytics.js +0 -418
- package/lib/growthbook.d.ts +0 -29
- package/lib/growthbook.js +0 -144
- package/lib/index.d.ts +0 -2
- package/lib/index.js +0 -5
- package/lib/posthog.d.ts +0 -28
- package/lib/posthog.js +0 -154
- package/lib/rudderstack.d.ts +0 -23
- package/lib/rudderstack.js +0 -100
- package/lib/types.js +0 -2
package/README.md
CHANGED
|
@@ -1,112 +1,893 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @deriv-com/analytics
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A modern, tree-shakeable analytics library for tracking user events with RudderStack and PostHog. Designed for optimal performance with advanced caching, batching, and offline support.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
- 📊 **Multi-Provider Support**: RudderStack for event tracking and PostHog for analytics & session recording
|
|
8
|
+
- 🎄 **Tree-Shakeable**: Only bundle what you use - each provider can be imported independently
|
|
9
|
+
- 📡 **Offline-First**: Automatic event caching when offline with replay on reconnection
|
|
10
|
+
- ⚡ **Performance Optimized**: Batching, deduplication, and SendBeacon API for fast tracking
|
|
11
|
+
- 🔐 **Type-Safe**: Full TypeScript support with discriminated unions for event payloads
|
|
12
|
+
- 🔄 **Backward Compatible**: Supports older React, Node.js, and other legacy package versions
|
|
13
|
+
- 💾 **Advanced Caching**: Cookie-based and in-memory caching for robust event delivery
|
|
14
|
+
- 🎥 **Session Recording**: Built-in PostHog session recording with customizable settings
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
> **Note**: GrowthBook support is deprecated and will be removed in a future major version. For A/B testing and feature flags, we recommend using PostHog's built-in feature flag capabilities.
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
## Table of Contents
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
- [Installation](#installation)
|
|
21
|
+
- [NPM/Yarn](#npmyarn)
|
|
22
|
+
- [Browser (CDN)](#browser-cdn)
|
|
23
|
+
- [Quick Start](#quick-start)
|
|
24
|
+
- [Framework Integration](#framework-integration)
|
|
25
|
+
- [React](#react-integration)
|
|
26
|
+
- [Next.js](#nextjs-integration)
|
|
27
|
+
- [Vue.js](#vuejs-integration)
|
|
28
|
+
- [Vanilla JavaScript](#vanilla-javascript)
|
|
29
|
+
- [Configuration](#configuration)
|
|
30
|
+
- [RudderStack](#rudderstack-configuration)
|
|
31
|
+
- [PostHog](#posthog-configuration)
|
|
32
|
+
- [Core API](#core-api)
|
|
33
|
+
- [Initialization](#initialization)
|
|
34
|
+
- [Event Tracking](#event-tracking)
|
|
35
|
+
- [User Identification](#user-identification)
|
|
36
|
+
- [Page Views](#page-views)
|
|
37
|
+
- [User Attributes](#user-attributes)
|
|
38
|
+
- [Caching & Offline Support](#caching--offline-support)
|
|
39
|
+
- [Advanced Usage](#advanced-usage)
|
|
40
|
+
- [API Reference](#api-reference)
|
|
41
|
+
- [Performance](#performance)
|
|
42
|
+
- [Troubleshooting](#troubleshooting)
|
|
43
|
+
- [Migration Guide](#migration-guide)
|
|
17
44
|
|
|
18
|
-
|
|
45
|
+
## Installation
|
|
19
46
|
|
|
47
|
+
### NPM/Yarn
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Using npm
|
|
51
|
+
npm install @deriv-com/analytics
|
|
52
|
+
|
|
53
|
+
# Using yarn
|
|
54
|
+
yarn add @deriv-com/analytics
|
|
55
|
+
|
|
56
|
+
# Using pnpm
|
|
57
|
+
pnpm add @deriv-com/analytics
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Core dependencies** (`@rudderstack/analytics-js`, `js-cookie`, and `posthog-js`) are installed automatically.
|
|
61
|
+
|
|
62
|
+
### Browser (CDN)
|
|
63
|
+
|
|
64
|
+
Use directly in browsers without a build tool:
|
|
65
|
+
|
|
66
|
+
```html
|
|
67
|
+
<!-- Load from jsdelivr CDN -->
|
|
68
|
+
<script src="https://cdn.jsdelivr.net/npm/@deriv-com/analytics@latest/dist/browser/analytics.bundle.global.js"></script>
|
|
69
|
+
|
|
70
|
+
<script>
|
|
71
|
+
const { Analytics } = window.DerivAnalytics
|
|
72
|
+
|
|
73
|
+
Analytics.initialise({
|
|
74
|
+
rudderstackKey: 'YOUR_RUDDERSTACK_KEY',
|
|
75
|
+
posthogOptions: {
|
|
76
|
+
apiKey: 'YOUR_POSTHOG_KEY',
|
|
77
|
+
config: {
|
|
78
|
+
autocapture: true,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
}).then(() => {
|
|
82
|
+
Analytics.trackEvent('page_view', { page: 'home' })
|
|
83
|
+
})
|
|
84
|
+
</script>
|
|
20
85
|
```
|
|
21
|
-
|
|
86
|
+
|
|
87
|
+
**Bundle Size**: ~380 KB minified / ~125 KB gzipped (includes RudderStack + PostHog + all dependencies)
|
|
88
|
+
|
|
89
|
+
## Quick Start
|
|
90
|
+
|
|
91
|
+
### Basic Usage (RudderStack Only)
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { Analytics } from '@deriv-com/analytics'
|
|
95
|
+
|
|
96
|
+
// Initialize with RudderStack
|
|
97
|
+
await Analytics.initialise({
|
|
98
|
+
rudderstackKey: 'YOUR_RUDDERSTACK_KEY',
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// Track events
|
|
102
|
+
Analytics.trackEvent('ce_virtual_signup_form', {
|
|
103
|
+
action: 'signup_done',
|
|
104
|
+
signup_provider: 'email',
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// Track page views
|
|
108
|
+
Analytics.pageView('/dashboard', 'Deriv App')
|
|
109
|
+
|
|
110
|
+
// Identify users
|
|
111
|
+
Analytics.identifyEvent('CR123456')
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Using Both RudderStack and PostHog
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { Analytics } from '@deriv-com/analytics'
|
|
118
|
+
|
|
119
|
+
await Analytics.initialise({
|
|
120
|
+
// RudderStack for event tracking (required)
|
|
121
|
+
rudderstackKey: 'YOUR_RUDDERSTACK_KEY',
|
|
122
|
+
|
|
123
|
+
// PostHog for analytics and session recording (optional)
|
|
124
|
+
posthogOptions: {
|
|
125
|
+
apiKey: 'phc_YOUR_POSTHOG_KEY',
|
|
126
|
+
allowedDomains: ['deriv.com', 'deriv.team', 'deriv.ae'],
|
|
127
|
+
config: {
|
|
128
|
+
session_recording: {
|
|
129
|
+
recordCrossOriginIframes: true,
|
|
130
|
+
minimumDurationMilliseconds: 30000,
|
|
131
|
+
},
|
|
132
|
+
autocapture: true,
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// Events are automatically sent to both providers
|
|
138
|
+
Analytics.trackEvent('ce_login_form', {
|
|
139
|
+
action: 'login_cta',
|
|
140
|
+
login_provider: 'google',
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// User identification syncs with both providers
|
|
144
|
+
Analytics.identifyEvent('CR123456', {
|
|
145
|
+
language: 'en',
|
|
146
|
+
country_of_residence: 'US',
|
|
147
|
+
})
|
|
22
148
|
```
|
|
23
149
|
|
|
24
|
-
|
|
150
|
+
## Framework Integration
|
|
25
151
|
|
|
26
|
-
|
|
27
|
-
Analytics?.initialise({
|
|
28
|
-
growthbookKey: process.env.GROWTHBOOK_CLIENT_KEY, // optional key to enable A/B tests
|
|
29
|
-
growthbookDecryptionKey: process.env.GROWTHBOOK_DECRYPTION_KEY, // optional key to enable A/B tests
|
|
30
|
-
// mandatory key to enable userevent tracking
|
|
31
|
-
rudderstackKey: RUDDERSTACK_KEY,
|
|
152
|
+
### React Integration
|
|
32
153
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
154
|
+
Create an analytics initialization hook:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// hooks/useAnalytics.ts
|
|
158
|
+
import { useEffect } from 'react'
|
|
159
|
+
import { Analytics } from '@deriv-com/analytics'
|
|
160
|
+
|
|
161
|
+
export function useAnalytics() {
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
Analytics.initialise({
|
|
164
|
+
rudderstackKey: process.env.REACT_APP_RUDDERSTACK_KEY!,
|
|
165
|
+
posthogOptions: {
|
|
166
|
+
apiKey: process.env.REACT_APP_POSTHOG_KEY!,
|
|
167
|
+
config: {
|
|
168
|
+
autocapture: true,
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
})
|
|
172
|
+
}, [])
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// App.tsx
|
|
176
|
+
import { useAnalytics } from './hooks/useAnalytics'
|
|
177
|
+
|
|
178
|
+
function App() {
|
|
179
|
+
useAnalytics()
|
|
180
|
+
|
|
181
|
+
const handleSignup = () => {
|
|
182
|
+
Analytics.trackEvent('ce_virtual_signup_form', {
|
|
183
|
+
action: 'signup_modal_open',
|
|
184
|
+
form_source: 'header_cta',
|
|
185
|
+
})
|
|
45
186
|
}
|
|
187
|
+
|
|
188
|
+
return <button onClick={handleSignup}>Sign Up</button>
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Next.js Integration
|
|
193
|
+
|
|
194
|
+
#### App Router (Next.js 13+)
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
// app/providers.tsx
|
|
198
|
+
'use client'
|
|
199
|
+
|
|
200
|
+
import { Analytics } from '@deriv-com/analytics'
|
|
201
|
+
import { useEffect } from 'react'
|
|
202
|
+
|
|
203
|
+
export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
Analytics.initialise({
|
|
206
|
+
rudderstackKey: process.env.NEXT_PUBLIC_RUDDERSTACK_KEY!,
|
|
207
|
+
posthogOptions: {
|
|
208
|
+
apiKey: process.env.NEXT_PUBLIC_POSTHOG_KEY!,
|
|
209
|
+
},
|
|
210
|
+
})
|
|
211
|
+
}, [])
|
|
212
|
+
|
|
213
|
+
return <>{children}</>
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// app/layout.tsx
|
|
217
|
+
import { AnalyticsProvider } from './providers'
|
|
218
|
+
|
|
219
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
220
|
+
return (
|
|
221
|
+
<html>
|
|
222
|
+
<body>
|
|
223
|
+
<AnalyticsProvider>{children}</AnalyticsProvider>
|
|
224
|
+
</body>
|
|
225
|
+
</html>
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
#### Pages Router (Next.js 12 and below)
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
// pages/_app.tsx
|
|
234
|
+
import { Analytics } from '@deriv-com/analytics'
|
|
235
|
+
import { useEffect } from 'react'
|
|
236
|
+
import type { AppProps } from 'next/app'
|
|
237
|
+
|
|
238
|
+
export default function App({ Component, pageProps }: AppProps) {
|
|
239
|
+
useEffect(() => {
|
|
240
|
+
Analytics.initialise({
|
|
241
|
+
rudderstackKey: process.env.NEXT_PUBLIC_RUDDERSTACK_KEY!,
|
|
242
|
+
posthogOptions: {
|
|
243
|
+
apiKey: process.env.NEXT_PUBLIC_POSTHOG_KEY!,
|
|
244
|
+
},
|
|
245
|
+
})
|
|
246
|
+
}, [])
|
|
247
|
+
|
|
248
|
+
return <Component {...pageProps} />
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Vue.js Integration
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
// main.ts or main.js
|
|
256
|
+
import { createApp } from 'vue'
|
|
257
|
+
import { Analytics } from '@deriv-com/analytics'
|
|
258
|
+
import App from './App.vue'
|
|
259
|
+
|
|
260
|
+
// Initialize analytics
|
|
261
|
+
Analytics.initialise({
|
|
262
|
+
rudderstackKey: import.meta.env.VITE_RUDDERSTACK_KEY,
|
|
263
|
+
posthogOptions: {
|
|
264
|
+
apiKey: import.meta.env.VITE_POSTHOG_KEY,
|
|
265
|
+
},
|
|
46
266
|
})
|
|
267
|
+
|
|
268
|
+
// Make Analytics available globally
|
|
269
|
+
const app = createApp(App)
|
|
270
|
+
app.config.globalProperties.$analytics = Analytics
|
|
271
|
+
app.mount('#app')
|
|
272
|
+
|
|
273
|
+
// Usage in components
|
|
274
|
+
export default {
|
|
275
|
+
methods: {
|
|
276
|
+
handleClick() {
|
|
277
|
+
this.$analytics.trackEvent('button_clicked', { button_name: 'submit' })
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Vanilla JavaScript
|
|
284
|
+
|
|
285
|
+
```html
|
|
286
|
+
<!doctype html>
|
|
287
|
+
<html>
|
|
288
|
+
<head>
|
|
289
|
+
<script src="https://cdn.jsdelivr.net/npm/@deriv-com/analytics@latest/dist/browser/analytics.bundle.global.js"></script>
|
|
290
|
+
</head>
|
|
291
|
+
<body>
|
|
292
|
+
<button id="signup-btn">Sign Up</button>
|
|
293
|
+
|
|
294
|
+
<script>
|
|
295
|
+
const { Analytics } = window.DerivAnalytics
|
|
296
|
+
|
|
297
|
+
// Initialize
|
|
298
|
+
Analytics.initialise({
|
|
299
|
+
rudderstackKey: 'YOUR_KEY',
|
|
300
|
+
posthogOptions: {
|
|
301
|
+
apiKey: 'YOUR_POSTHOG_KEY',
|
|
302
|
+
},
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
// Track button clicks
|
|
306
|
+
document.getElementById('signup-btn').addEventListener('click', () => {
|
|
307
|
+
Analytics.trackEvent('ce_signup_button', {
|
|
308
|
+
action: 'click',
|
|
309
|
+
location: 'header',
|
|
310
|
+
})
|
|
311
|
+
})
|
|
312
|
+
</script>
|
|
313
|
+
</body>
|
|
314
|
+
</html>
|
|
47
315
|
```
|
|
48
316
|
|
|
49
|
-
|
|
317
|
+
## Configuration
|
|
318
|
+
|
|
319
|
+
### RudderStack Configuration
|
|
50
320
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
321
|
+
RudderStack is used for event tracking and includes performance optimizations:
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
await Analytics.initialise({
|
|
325
|
+
rudderstackKey: 'YOUR_RUDDERSTACK_KEY',
|
|
56
326
|
})
|
|
57
327
|
```
|
|
58
328
|
|
|
59
|
-
|
|
329
|
+
**Built-in Performance Features:**
|
|
60
330
|
|
|
61
|
-
|
|
331
|
+
- **Event Batching**: Flushes after 10 events or 10 seconds
|
|
332
|
+
- **SendBeacon API**: Uses `navigator.sendBeacon` for better performance on page unload
|
|
333
|
+
- **Automatic Retry**: Failed requests are automatically retried
|
|
334
|
+
- **Cookie Management**: Automatic anonymous ID generation and persistence (2-year cookie lifetime)
|
|
335
|
+
- **Offline Support**: Events are cached when offline and replayed when connection is restored
|
|
62
336
|
|
|
63
|
-
|
|
337
|
+
### PostHog Configuration
|
|
64
338
|
|
|
65
|
-
|
|
66
|
-
import { Analytics } from '@deriv-com/analytics';
|
|
339
|
+
PostHog provides powerful analytics, session recording, and feature flags:
|
|
67
340
|
|
|
68
|
-
|
|
69
|
-
Analytics
|
|
70
|
-
|
|
71
|
-
|
|
341
|
+
```typescript
|
|
342
|
+
await Analytics.initialise({
|
|
343
|
+
rudderstackKey: 'YOUR_RUDDERSTACK_KEY',
|
|
344
|
+
posthogOptions: {
|
|
345
|
+
// Required: API key
|
|
346
|
+
apiKey: 'phc_YOUR_KEY',
|
|
72
347
|
|
|
348
|
+
// Optional: Domain allowlist for security
|
|
349
|
+
allowedDomains: ['deriv.com', 'deriv.team', 'deriv.ae'],
|
|
73
350
|
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
351
|
+
// Optional: PostHog configuration
|
|
352
|
+
config: {
|
|
353
|
+
// API endpoints (defaults shown)
|
|
354
|
+
api_host: 'https://ph.deriv.com',
|
|
355
|
+
ui_host: 'https://us.posthog.com',
|
|
356
|
+
|
|
357
|
+
// Session recording
|
|
358
|
+
session_recording: {
|
|
359
|
+
recordCrossOriginIframes: true,
|
|
360
|
+
maskAllInputs: false,
|
|
361
|
+
minimumDurationMilliseconds: 30000, // Only save sessions longer than 30 seconds
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
// Feature capture
|
|
365
|
+
autocapture: true, // Automatically capture clicks, form submissions, etc.
|
|
366
|
+
capture_pageview: true, // Automatically capture page views
|
|
367
|
+
capture_pageleave: true, // Capture when users leave pages
|
|
368
|
+
|
|
369
|
+
// Console log recording (useful for debugging)
|
|
370
|
+
enable_recording_console_log: true,
|
|
371
|
+
|
|
372
|
+
// Disable features if needed
|
|
373
|
+
disable_session_recording: false,
|
|
374
|
+
disable_surveys: false,
|
|
375
|
+
|
|
376
|
+
// Custom event filtering
|
|
377
|
+
before_send: event => {
|
|
378
|
+
// Custom logic to filter or modify events
|
|
379
|
+
return event
|
|
380
|
+
},
|
|
381
|
+
},
|
|
382
|
+
},
|
|
80
383
|
})
|
|
384
|
+
```
|
|
81
385
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
386
|
+
#### Domain Allowlisting
|
|
387
|
+
|
|
388
|
+
For security, PostHog can be configured to only send events from specific domains:
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
posthogOptions: {
|
|
392
|
+
apiKey: 'phc_YOUR_KEY',
|
|
393
|
+
allowedDomains: ['deriv.com', 'deriv.team', 'deriv.ae'],
|
|
85
394
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Events from `app.deriv.com`, `staging.deriv.team`, etc. will be sent. Events from other domains will be blocked.
|
|
398
|
+
|
|
399
|
+
#### Session Recording Customization
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
posthogOptions: {
|
|
403
|
+
apiKey: 'phc_YOUR_KEY',
|
|
404
|
+
config: {
|
|
405
|
+
session_recording: {
|
|
406
|
+
// Record content from iframes
|
|
407
|
+
recordCrossOriginIframes: true,
|
|
408
|
+
|
|
409
|
+
// Mask sensitive input fields
|
|
410
|
+
maskAllInputs: true,
|
|
411
|
+
maskInputOptions: {
|
|
412
|
+
password: true,
|
|
413
|
+
email: true,
|
|
414
|
+
},
|
|
415
|
+
|
|
416
|
+
// Only save sessions longer than 1 minute
|
|
417
|
+
minimumDurationMilliseconds: 60000,
|
|
418
|
+
|
|
419
|
+
// Sampling (record only 50% of sessions)
|
|
420
|
+
sessionRecordingSampleRate: 0.5,
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## Core API
|
|
427
|
+
|
|
428
|
+
### Initialization
|
|
429
|
+
|
|
430
|
+
Initialize the analytics instance before tracking events:
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
await Analytics.initialise({
|
|
434
|
+
rudderstackKey: 'YOUR_RUDDERSTACK_KEY',
|
|
435
|
+
posthogOptions: {
|
|
436
|
+
apiKey: 'phc_YOUR_POSTHOG_KEY',
|
|
437
|
+
config: {
|
|
438
|
+
autocapture: true,
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
})
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Event Tracking
|
|
445
|
+
|
|
446
|
+
Track custom events with associated data. Supports both V1 (flat) and V2 (structured) formats:
|
|
447
|
+
|
|
448
|
+
#### V1 Event Format (Flat Structure)
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
Analytics.trackEvent('ce_login_form', {
|
|
452
|
+
action: 'login_cta',
|
|
453
|
+
login_provider: 'email',
|
|
454
|
+
form_name: 'main_login',
|
|
455
|
+
})
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
#### V2 Event Format (Structured with Metadata)
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
Analytics.trackEvent('ce_get_start_page', {
|
|
462
|
+
action: 'click',
|
|
463
|
+
form_name: 'signup_form',
|
|
464
|
+
cta_information: {
|
|
465
|
+
cta_name: 'get_started',
|
|
466
|
+
section_name: 'hero',
|
|
467
|
+
},
|
|
468
|
+
event_metadata: {
|
|
469
|
+
page_name: '/home',
|
|
470
|
+
user_language: 'en',
|
|
471
|
+
},
|
|
472
|
+
})
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### User Identification
|
|
476
|
+
|
|
477
|
+
Identify users and sync traits across analytics providers:
|
|
478
|
+
|
|
479
|
+
#### Simple Identification
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
// Identify user with ID only
|
|
483
|
+
Analytics.identifyEvent('CR123456')
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
#### Identification with Custom Traits
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
// Send same traits to both RudderStack and PostHog
|
|
490
|
+
Analytics.identifyEvent('CR123456', {
|
|
491
|
+
language: 'en',
|
|
492
|
+
country_of_residence: 'US',
|
|
493
|
+
account_type: 'real',
|
|
494
|
+
})
|
|
495
|
+
|
|
496
|
+
// Send different traits to each provider
|
|
497
|
+
Analytics.identifyEvent('CR123456', {
|
|
498
|
+
rudderstack: {
|
|
499
|
+
language: 'en',
|
|
500
|
+
custom_field: 'value',
|
|
501
|
+
},
|
|
502
|
+
posthog: {
|
|
503
|
+
language: 'en',
|
|
504
|
+
country_of_residence: 'US',
|
|
505
|
+
subscription_tier: 'premium',
|
|
506
|
+
},
|
|
507
|
+
})
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
**How it works:**
|
|
511
|
+
|
|
512
|
+
- If you pass a simple object (e.g., `{ language: 'en' }`), the same traits are sent to both providers
|
|
513
|
+
- If you pass an object with `rudderstack` or `posthog` keys, provider-specific traits are used
|
|
514
|
+
- Queues identify calls if provider not yet initialized
|
|
515
|
+
- PostHog automatically handles aliasing between anonymous and identified users
|
|
516
|
+
|
|
517
|
+
### Page Views
|
|
518
|
+
|
|
519
|
+
Track page navigation:
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
// Basic page view
|
|
523
|
+
Analytics.pageView('/dashboard')
|
|
524
|
+
|
|
525
|
+
// With custom platform name
|
|
526
|
+
Analytics.pageView('/dashboard', 'Deriv Trader')
|
|
527
|
+
|
|
528
|
+
// With additional properties
|
|
529
|
+
Analytics.pageView('/trade', 'Deriv App', {
|
|
530
|
+
section: 'multipliers',
|
|
531
|
+
instrument: 'forex',
|
|
532
|
+
})
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
**Note**: PostHog automatically captures page views when `capture_pageview: true` is set in config. Manual page view tracking is primarily for RudderStack.
|
|
536
|
+
|
|
537
|
+
### User Attributes
|
|
538
|
+
|
|
539
|
+
Set user and context attributes that are automatically included in all events:
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
Analytics.setAttributes({
|
|
543
|
+
country: 'US',
|
|
544
|
+
user_language: 'en',
|
|
545
|
+
account_type: 'real',
|
|
546
|
+
device_type: 'mobile',
|
|
547
|
+
account_currency: 'USD',
|
|
548
|
+
account_mode: 'demo',
|
|
549
|
+
residence_country: 'US',
|
|
550
|
+
loggedIn: true,
|
|
551
|
+
})
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
**All subsequent events will include these attributes automatically.**
|
|
555
|
+
|
|
556
|
+
### Reset User Session
|
|
557
|
+
|
|
558
|
+
Clear user session from all providers (e.g., on logout):
|
|
559
|
+
|
|
560
|
+
```typescript
|
|
561
|
+
Analytics.reset()
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
## Caching & Offline Support
|
|
565
|
+
|
|
566
|
+
The package includes robust caching mechanisms to ensure no events are lost:
|
|
567
|
+
|
|
568
|
+
### Cookie-Based Caching
|
|
569
|
+
|
|
570
|
+
Events are cached in cookies when:
|
|
571
|
+
|
|
572
|
+
- **RudderStack SDK hasn't loaded yet** - Events are stored and replayed once the SDK initializes
|
|
573
|
+
- **User is offline** - Events are queued and sent when connection is restored
|
|
574
|
+
|
|
575
|
+
```typescript
|
|
576
|
+
// Automatic - no configuration needed
|
|
577
|
+
Analytics.trackEvent('button_clicked', { button: 'submit' })
|
|
578
|
+
// ↓ If offline or SDK not ready, stored in cookies
|
|
579
|
+
// ↓ Automatically sent when online/SDK ready
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
**Technical Details:**
|
|
583
|
+
|
|
584
|
+
- Cookies have a 7-day expiration
|
|
585
|
+
- Maximum 10 cached events (oldest events dropped if exceeded)
|
|
586
|
+
- Automatic cleanup after successful replay
|
|
587
|
+
- SameSite=Lax for security
|
|
588
|
+
|
|
589
|
+
### In-Memory Caching
|
|
590
|
+
|
|
591
|
+
In addition to cookie caching, events are cached in memory when the user is offline but the SDK is initialized:
|
|
592
|
+
|
|
593
|
+
```typescript
|
|
594
|
+
// While offline
|
|
595
|
+
Analytics.trackEvent('offline_event', { data: 'cached' })
|
|
596
|
+
// ↓ Stored in memory
|
|
597
|
+
// ↓ Sent immediately when online
|
|
598
|
+
|
|
599
|
+
// Check offline status
|
|
600
|
+
window.addEventListener('online', () => {
|
|
601
|
+
// Cached events automatically sent
|
|
602
|
+
})
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### Page View Caching
|
|
606
|
+
|
|
607
|
+
Page views are also cached using the same mechanism:
|
|
608
|
+
|
|
609
|
+
```typescript
|
|
610
|
+
// While SDK is loading
|
|
611
|
+
Analytics.pageView('/dashboard')
|
|
612
|
+
// ↓ Cached in cookies
|
|
613
|
+
// ↓ Replayed once SDK is ready
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
### Advanced Caching Utilities
|
|
617
|
+
|
|
618
|
+
For complex scenarios requiring more control, use the advanced caching utilities:
|
|
619
|
+
|
|
620
|
+
```typescript
|
|
621
|
+
import { cacheTrackEvents } from '@deriv-com/analytics'
|
|
622
|
+
|
|
623
|
+
// Track events with automatic caching before SDK loads
|
|
624
|
+
cacheTrackEvents.track({
|
|
625
|
+
name: 'ce_login_form',
|
|
626
|
+
properties: { action: 'open' },
|
|
90
627
|
})
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
628
|
+
|
|
629
|
+
// Add click event listeners with auto-retry
|
|
630
|
+
cacheTrackEvents.addEventHandler([
|
|
631
|
+
{
|
|
632
|
+
element: '.signup-button',
|
|
633
|
+
event: {
|
|
634
|
+
name: 'ce_button_click',
|
|
635
|
+
properties: { button_name: 'signup' },
|
|
636
|
+
},
|
|
637
|
+
cache: true, // Cache if SDK not ready
|
|
638
|
+
},
|
|
639
|
+
])
|
|
640
|
+
|
|
641
|
+
// Track page-specific events
|
|
642
|
+
cacheTrackEvents.pageLoadEvent([
|
|
643
|
+
{
|
|
644
|
+
pages: ['dashboard', 'profile'],
|
|
645
|
+
event: {
|
|
646
|
+
name: 'ce_page_load',
|
|
647
|
+
properties: { page_type: 'authenticated' },
|
|
648
|
+
},
|
|
649
|
+
},
|
|
650
|
+
])
|
|
651
|
+
|
|
652
|
+
// Automatic pageview tracking
|
|
653
|
+
cacheTrackEvents.pageView()
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
## Advanced Usage
|
|
657
|
+
|
|
658
|
+
### Independent Package Usage
|
|
659
|
+
|
|
660
|
+
Each provider can be used independently for maximum flexibility:
|
|
661
|
+
|
|
662
|
+
#### PostHog Only
|
|
663
|
+
|
|
664
|
+
```typescript
|
|
665
|
+
import { Posthog } from '@deriv-com/analytics/posthog'
|
|
666
|
+
|
|
667
|
+
const posthog = Posthog.getPosthogInstance({
|
|
668
|
+
apiKey: 'phc_YOUR_KEY',
|
|
669
|
+
allowedDomains: ['deriv.com'],
|
|
670
|
+
config: {
|
|
671
|
+
autocapture: true,
|
|
672
|
+
session_recording: {
|
|
673
|
+
recordCrossOriginIframes: true,
|
|
674
|
+
},
|
|
675
|
+
},
|
|
95
676
|
})
|
|
96
677
|
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
678
|
+
// Track events
|
|
679
|
+
posthog.capture('button_clicked', { button_name: 'signup' })
|
|
680
|
+
|
|
681
|
+
// Identify users
|
|
682
|
+
posthog.identifyEvent('CR123', { language: 'en' })
|
|
683
|
+
|
|
684
|
+
// Check feature flags
|
|
685
|
+
const isEnabled = posthog.isFeatureEnabled('new-feature')
|
|
686
|
+
const variant = posthog.getFeatureFlag('button-color')
|
|
100
687
|
```
|
|
101
688
|
|
|
102
|
-
|
|
689
|
+
#### RudderStack Only
|
|
690
|
+
|
|
691
|
+
```typescript
|
|
692
|
+
import { RudderStack } from '@deriv-com/analytics/rudderstack'
|
|
693
|
+
|
|
694
|
+
const rudderstack = RudderStack.getRudderStackInstance('YOUR_KEY', () => {
|
|
695
|
+
console.log('RudderStack loaded')
|
|
696
|
+
})
|
|
697
|
+
|
|
698
|
+
// Track events
|
|
699
|
+
rudderstack.track('button_clicked', { button: 'signup' })
|
|
103
700
|
|
|
104
|
-
|
|
105
|
-
|
|
701
|
+
// Identify users
|
|
702
|
+
rudderstack.identifyEvent('CR123', { language: 'en' })
|
|
703
|
+
|
|
704
|
+
// Track page views
|
|
705
|
+
rudderstack.pageView('/dashboard', 'Deriv App', 'CR123')
|
|
106
706
|
```
|
|
107
707
|
|
|
108
|
-
|
|
708
|
+
### Access Provider Instances
|
|
709
|
+
|
|
710
|
+
Access raw provider instances for advanced use cases:
|
|
109
711
|
|
|
110
|
-
```
|
|
111
|
-
|
|
712
|
+
```typescript
|
|
713
|
+
const { tracking, posthog } = Analytics.getInstances()
|
|
714
|
+
|
|
715
|
+
// Access PostHog directly
|
|
716
|
+
if (posthog?.has_initialized) {
|
|
717
|
+
posthog.capture('custom_event', { property: 'value' })
|
|
718
|
+
|
|
719
|
+
// Access PostHog feature flags
|
|
720
|
+
const isEnabled = posthog.isFeatureEnabled('new-feature')
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Access RudderStack directly
|
|
724
|
+
if (tracking?.has_initialized) {
|
|
725
|
+
const userId = tracking.getUserId()
|
|
726
|
+
const anonId = tracking.getAnonymousId()
|
|
727
|
+
}
|
|
112
728
|
```
|
|
729
|
+
|
|
730
|
+
## API Reference
|
|
731
|
+
|
|
732
|
+
### `initialise(options: Options): Promise<void>`
|
|
733
|
+
|
|
734
|
+
Initialize the analytics instance.
|
|
735
|
+
|
|
736
|
+
**Parameters:**
|
|
737
|
+
|
|
738
|
+
```typescript
|
|
739
|
+
interface Options {
|
|
740
|
+
rudderstackKey?: string
|
|
741
|
+
posthogOptions?: {
|
|
742
|
+
apiKey: string
|
|
743
|
+
allowedDomains?: string[]
|
|
744
|
+
config?: PostHogConfig
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
### `trackEvent<T>(event: T, payload: TAllEvents[T]): void`
|
|
750
|
+
|
|
751
|
+
Track a typed event.
|
|
752
|
+
|
|
753
|
+
### `pageView(url: string, platform?: string, properties?: Record<string, unknown>): void`
|
|
754
|
+
|
|
755
|
+
Track page navigation.
|
|
756
|
+
|
|
757
|
+
### `identifyEvent(userId?: string, traits?: Record<string, any>): void`
|
|
758
|
+
|
|
759
|
+
Link anonymous session to a user ID with optional traits.
|
|
760
|
+
|
|
761
|
+
### `setAttributes(attributes: TCoreAttributes): void`
|
|
762
|
+
|
|
763
|
+
Update user attributes that flow to all providers.
|
|
764
|
+
|
|
765
|
+
### `reset(): void`
|
|
766
|
+
|
|
767
|
+
Clear user session from all providers.
|
|
768
|
+
|
|
769
|
+
### `getId(): string`
|
|
770
|
+
|
|
771
|
+
Get the current user ID.
|
|
772
|
+
|
|
773
|
+
### `getAnonymousId(): string`
|
|
774
|
+
|
|
775
|
+
Get the anonymous user ID.
|
|
776
|
+
|
|
777
|
+
### `getInstances(): { tracking, posthog }`
|
|
778
|
+
|
|
779
|
+
Access raw provider instances.
|
|
780
|
+
|
|
781
|
+
## Performance
|
|
782
|
+
|
|
783
|
+
### Benchmarks
|
|
784
|
+
|
|
785
|
+
- **Event tracking**: <5ms (average)
|
|
786
|
+
- **Page view tracking**: <3ms (average)
|
|
787
|
+
- **Initialization**: ~200ms (with both providers)
|
|
788
|
+
- **Offline cache replay**: <50ms for 10 events
|
|
789
|
+
|
|
790
|
+
### Optimizations
|
|
791
|
+
|
|
792
|
+
- **Tree-Shaking**: Unused providers completely removed from bundle
|
|
793
|
+
- **Lazy Loading**: PostHog loaded dynamically only when configured
|
|
794
|
+
- **Event Batching**: RudderStack batches events (10 events or 10 seconds)
|
|
795
|
+
- **SendBeacon**: Uses `navigator.sendBeacon` for reliable event delivery on page unload
|
|
796
|
+
- **Deduplication**: Prevents duplicate events from being sent
|
|
797
|
+
|
|
798
|
+
### Bundle Sizes
|
|
799
|
+
|
|
800
|
+
Estimated sizes (minified + gzipped):
|
|
801
|
+
|
|
802
|
+
- **Core (RudderStack + PostHog)**: ~32 KB
|
|
803
|
+
- **RudderStack Only**: ~18 KB
|
|
804
|
+
- **PostHog Only**: ~20 KB
|
|
805
|
+
- **Browser Bundle (all included)**: ~125 KB gzipped
|
|
806
|
+
|
|
807
|
+
## Troubleshooting
|
|
808
|
+
|
|
809
|
+
### Events not appearing in RudderStack
|
|
810
|
+
|
|
811
|
+
1. **Verify API key**: Check that `rudderstackKey` is correct
|
|
812
|
+
2. **Check network requests**: Open DevTools → Network tab → Look for requests to RudderStack dataplane
|
|
813
|
+
3. **Verify initialization**: Run `Analytics.getInstances().tracking.has_initialized` in console
|
|
814
|
+
4. **Check batching**: Events are batched - wait ~10 seconds or send 10 events
|
|
815
|
+
|
|
816
|
+
### PostHog not receiving events
|
|
817
|
+
|
|
818
|
+
1. **Verify API key**: Check that PostHog API key is correct (starts with `phc_`)
|
|
819
|
+
2. **Check domain allowlist**: Verify your domain is in the `allowedDomains` list
|
|
820
|
+
3. **Check initialization**: Run `Analytics.getInstances().posthog?.has_initialized` in console
|
|
821
|
+
4. **Verify network requests**: Check DevTools for requests to `ph.deriv.com` or your PostHog host
|
|
822
|
+
5. **Check browser console**: Look for PostHog errors or warnings
|
|
823
|
+
|
|
824
|
+
### Session recording not working
|
|
825
|
+
|
|
826
|
+
1. **Verify config**: Ensure `disable_session_recording: false` (or omit it)
|
|
827
|
+
2. **Check minimum duration**: Sessions shorter than `minimumDurationMilliseconds` are not saved
|
|
828
|
+
3. **Verify domain**: Check that PostHog is initialized and domain is allowed
|
|
829
|
+
4. **Check PostHog dashboard**: Recordings may take a few minutes to appear
|
|
830
|
+
|
|
831
|
+
### Events being cached but not sent
|
|
832
|
+
|
|
833
|
+
1. **Check online status**: Run `navigator.onLine` in console
|
|
834
|
+
2. **Verify SDK loaded**: Run `Analytics.getInstances().tracking.has_initialized`
|
|
835
|
+
3. **Check cookies**: Look for `rudder_*` and `analytics_cached_*` cookies in DevTools
|
|
836
|
+
4. **Clear cache manually**: Clear cookies or run `Analytics.reset()`
|
|
837
|
+
|
|
838
|
+
## Migration Guide
|
|
839
|
+
|
|
840
|
+
### From v1.x to v2.x
|
|
841
|
+
|
|
842
|
+
#### Breaking Changes
|
|
843
|
+
|
|
844
|
+
1. **identifyEvent signature changed**:
|
|
845
|
+
|
|
846
|
+
```typescript
|
|
847
|
+
// Old (v1.x) - hardcoded traits
|
|
848
|
+
Analytics.identifyEvent('CR123')
|
|
849
|
+
|
|
850
|
+
// New (v2.x) - custom traits
|
|
851
|
+
Analytics.identifyEvent('CR123', {
|
|
852
|
+
language: 'en',
|
|
853
|
+
country_of_residence: 'US',
|
|
854
|
+
})
|
|
855
|
+
|
|
856
|
+
// Or provider-specific
|
|
857
|
+
Analytics.identifyEvent('CR123', {
|
|
858
|
+
rudderstack: { language: 'en' },
|
|
859
|
+
posthog: { language: 'en', country_of_residence: 'US' },
|
|
860
|
+
})
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
2. **GrowthBook deprecated**: Migrate to PostHog feature flags
|
|
864
|
+
|
|
865
|
+
```typescript
|
|
866
|
+
// Old (GrowthBook)
|
|
867
|
+
const isEnabled = Analytics.isFeatureOn('new-feature')
|
|
868
|
+
|
|
869
|
+
// New (PostHog)
|
|
870
|
+
const { posthog } = Analytics.getInstances()
|
|
871
|
+
const isEnabled = posthog?.isFeatureEnabled('new-feature')
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
## Contributing
|
|
875
|
+
|
|
876
|
+
Contributions are welcome! Please follow these guidelines:
|
|
877
|
+
|
|
878
|
+
1. Fork the repository
|
|
879
|
+
2. Create a feature branch
|
|
880
|
+
3. Write tests for your changes
|
|
881
|
+
4. Run `npm test` and `npm run build`
|
|
882
|
+
5. Submit a pull request
|
|
883
|
+
|
|
884
|
+
## License
|
|
885
|
+
|
|
886
|
+
MIT
|
|
887
|
+
|
|
888
|
+
## Support
|
|
889
|
+
|
|
890
|
+
For issues and questions:
|
|
891
|
+
|
|
892
|
+
- **GitHub Issues**: https://github.com/binary-com/deriv-analytics/issues
|
|
893
|
+
- **Documentation**: https://github.com/binary-com/deriv-analytics
|