@formo/analytics 0.1.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 +141 -0
- package/dist/cjs/src/FormoAnalytics.d.ts +30 -0
- package/dist/cjs/src/FormoAnalytics.d.ts.map +1 -0
- package/dist/cjs/src/FormoAnalytics.js +297 -0
- package/dist/cjs/src/FormoAnalytics.js.map +1 -0
- package/dist/cjs/src/FormoAnalyticsProvider.d.ts +7 -0
- package/dist/cjs/src/FormoAnalyticsProvider.d.ts.map +1 -0
- package/dist/cjs/src/FormoAnalyticsProvider.js +45 -0
- package/dist/cjs/src/FormoAnalyticsProvider.js.map +1 -0
- package/dist/cjs/src/constants/config.d.ts +430 -0
- package/dist/cjs/src/constants/config.d.ts.map +1 -0
- package/dist/cjs/src/constants/config.js +433 -0
- package/dist/cjs/src/constants/config.js.map +1 -0
- package/dist/cjs/src/constants/index.d.ts +2 -0
- package/dist/cjs/src/constants/index.d.ts.map +1 -0
- package/dist/cjs/src/constants/index.js +18 -0
- package/dist/cjs/src/constants/index.js.map +1 -0
- package/dist/cjs/src/index.d.ts +4 -0
- package/dist/cjs/src/index.d.ts.map +1 -0
- package/dist/cjs/src/index.js +20 -0
- package/dist/cjs/src/index.js.map +1 -0
- package/dist/cjs/src/types/base.d.ts +8 -0
- package/dist/cjs/src/types/base.d.ts.map +1 -0
- package/dist/cjs/src/types/base.js +3 -0
- package/dist/cjs/src/types/base.js.map +1 -0
- package/dist/cjs/src/types/index.d.ts +2 -0
- package/dist/cjs/src/types/index.d.ts.map +1 -0
- package/dist/cjs/src/types/index.js +18 -0
- package/dist/cjs/src/types/index.js.map +1 -0
- package/dist/cjs/src/utils/index.d.ts +2 -0
- package/dist/cjs/src/utils/index.d.ts.map +1 -0
- package/dist/cjs/src/utils/index.js +18 -0
- package/dist/cjs/src/utils/index.js.map +1 -0
- package/dist/cjs/src/utils/isNotEmptyObject.d.ts +2 -0
- package/dist/cjs/src/utils/isNotEmptyObject.d.ts.map +1 -0
- package/dist/cjs/src/utils/isNotEmptyObject.js +10 -0
- package/dist/cjs/src/utils/isNotEmptyObject.js.map +1 -0
- package/dist/cjs/tsconfig.tsbuildinfo +1 -0
- package/dist/esm/src/FormoAnalytics.d.ts +30 -0
- package/dist/esm/src/FormoAnalytics.d.ts.map +1 -0
- package/dist/esm/src/FormoAnalytics.js +291 -0
- package/dist/esm/src/FormoAnalytics.js.map +1 -0
- package/dist/esm/src/FormoAnalyticsProvider.d.ts +7 -0
- package/dist/esm/src/FormoAnalyticsProvider.d.ts.map +1 -0
- package/dist/esm/src/FormoAnalyticsProvider.js +40 -0
- package/dist/esm/src/FormoAnalyticsProvider.js.map +1 -0
- package/dist/esm/src/constants/config.d.ts +430 -0
- package/dist/esm/src/constants/config.d.ts.map +1 -0
- package/dist/esm/src/constants/config.js +430 -0
- package/dist/esm/src/constants/config.js.map +1 -0
- package/dist/esm/src/constants/index.d.ts +2 -0
- package/dist/esm/src/constants/index.d.ts.map +1 -0
- package/dist/esm/src/constants/index.js +2 -0
- package/dist/esm/src/constants/index.js.map +1 -0
- package/dist/esm/src/index.d.ts +4 -0
- package/dist/esm/src/index.d.ts.map +1 -0
- package/dist/esm/src/index.js +4 -0
- package/dist/esm/src/index.js.map +1 -0
- package/dist/esm/src/types/base.d.ts +8 -0
- package/dist/esm/src/types/base.d.ts.map +1 -0
- package/dist/esm/src/types/base.js +2 -0
- package/dist/esm/src/types/base.js.map +1 -0
- package/dist/esm/src/types/index.d.ts +2 -0
- package/dist/esm/src/types/index.d.ts.map +1 -0
- package/dist/esm/src/types/index.js +2 -0
- package/dist/esm/src/types/index.js.map +1 -0
- package/dist/esm/src/utils/index.d.ts +2 -0
- package/dist/esm/src/utils/index.d.ts.map +1 -0
- package/dist/esm/src/utils/index.js +2 -0
- package/dist/esm/src/utils/index.js.map +1 -0
- package/dist/esm/src/utils/isNotEmptyObject.d.ts +2 -0
- package/dist/esm/src/utils/isNotEmptyObject.d.ts.map +1 -0
- package/dist/esm/src/utils/isNotEmptyObject.js +6 -0
- package/dist/esm/src/utils/isNotEmptyObject.js.map +1 -0
- package/dist/esm/tsconfig.tsbuildinfo +1 -0
- package/dist/index.umd.min.js +3 -0
- package/dist/index.umd.min.js.LICENSE.txt +19 -0
- package/dist/index.umd.min.js.map +1 -0
- package/package.json +89 -0
- package/src/FormoAnalytics.ts +264 -0
- package/src/FormoAnalyticsProvider.tsx +54 -0
- package/src/constants/config.ts +429 -0
- package/src/constants/index.ts +1 -0
- package/src/global.d.ts +8 -0
- package/src/index.ts +3 -0
- package/src/types/base.ts +6 -0
- package/src/types/index.ts +1 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/isNotEmptyObject.ts +5 -0
- package/tsconfig.json +28 -0
- package/webpack.config.ts +23 -0
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@formo/analytics",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/getformo/sdk.git"
|
|
7
|
+
},
|
|
8
|
+
"main": "dist/cjs/src/index.js",
|
|
9
|
+
"types": "dist/esm/src/index.d.ts",
|
|
10
|
+
"module": "dist/esm/src/index.js",
|
|
11
|
+
"unpkg": "dist/index.umd.min.js",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/esm/src/index.js",
|
|
15
|
+
"require": "./dist/cjs/src/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"private": false,
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"axios": "^1.7.7"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@babel/core": "^7.x",
|
|
28
|
+
"@babel/plugin-syntax-flow": "^7.14.5",
|
|
29
|
+
"@babel/plugin-transform-react-jsx": "^7.14.9",
|
|
30
|
+
"@commitlint/cli": "^17.3.0",
|
|
31
|
+
"@commitlint/config-conventional": "^17.3.0",
|
|
32
|
+
"@semantic-release/github": "^8.0.7",
|
|
33
|
+
"@testing-library/react": "^13.4.0",
|
|
34
|
+
"@types/chai": "^4.3.1",
|
|
35
|
+
"@types/jsdom": "^20.0.1",
|
|
36
|
+
"@types/mocha": "^9.1.1",
|
|
37
|
+
"@types/node": "^18.11.9",
|
|
38
|
+
"@types/react": "^18.0.25",
|
|
39
|
+
"@types/sinon": "^10.0.12",
|
|
40
|
+
"@types/sinon-chai": "^3.2.9",
|
|
41
|
+
"@typescript-eslint/eslint-plugin": "^5.30.4",
|
|
42
|
+
"@typescript-eslint/parser": "^5.30.4",
|
|
43
|
+
"chai": "^4.3.6",
|
|
44
|
+
"commitizen": "^4.2.5",
|
|
45
|
+
"cz-conventional-changelog": "3.3.0",
|
|
46
|
+
"eslint": "^8.19.0",
|
|
47
|
+
"eslint-config-react-app": "^7.0.1",
|
|
48
|
+
"global-jsdom": "^8.6.0",
|
|
49
|
+
"husky": "^8.0.0",
|
|
50
|
+
"jsdom": "^21.1.0",
|
|
51
|
+
"mocha": "^10.0.0",
|
|
52
|
+
"nodemon": "^2.0.20",
|
|
53
|
+
"nyc": "^15.1.0",
|
|
54
|
+
"prettier": "^2.6.1",
|
|
55
|
+
"react": "^18.3.1",
|
|
56
|
+
"react-dom": "^18.3.1",
|
|
57
|
+
"semantic-release": "^19.0.5",
|
|
58
|
+
"semantic-release-export-data": "^1.0.1",
|
|
59
|
+
"sinon": "^14.0.0",
|
|
60
|
+
"sinon-chai": "^3.7.0",
|
|
61
|
+
"ts-loader": "^9.3.1",
|
|
62
|
+
"ts-node": "^10.8.2",
|
|
63
|
+
"typescript": "~4.8.0",
|
|
64
|
+
"webpack": "^5.74.0",
|
|
65
|
+
"webpack-cli": "^4.10.0"
|
|
66
|
+
},
|
|
67
|
+
"scripts": {
|
|
68
|
+
"prebuild": "yarn clean",
|
|
69
|
+
"publish": "npm version && npm publish",
|
|
70
|
+
"build": "yarn build-cjs && yarn build-esm && yarn webpack --mode=production",
|
|
71
|
+
"build-cjs": "yarn tsc --build",
|
|
72
|
+
"build-esm": "yarn tsc -m es6 --outdir dist/esm",
|
|
73
|
+
"clean": "rm -rf dist",
|
|
74
|
+
"lint": "eslint '{src,test}/**/*.{ts,tsx}'",
|
|
75
|
+
"test": "nyc mocha",
|
|
76
|
+
"test-watch": "nodemon --config test.nodemon.json",
|
|
77
|
+
"prepare": "husky install",
|
|
78
|
+
"commit": "git add . && cz"
|
|
79
|
+
},
|
|
80
|
+
"peerDependencies": {
|
|
81
|
+
"@types/react": ">=16.14.34",
|
|
82
|
+
"react": ">=16.14.0"
|
|
83
|
+
},
|
|
84
|
+
"config": {
|
|
85
|
+
"commitizen": {
|
|
86
|
+
"path": "./node_modules/cz-conventional-changelog"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { COUNTRY_LIST, EVENTS_API, SESSION_STORAGE_ID_KEY } from './constants';
|
|
3
|
+
import { isNotEmpty } from './utils';
|
|
4
|
+
|
|
5
|
+
interface IFormoAnalytics {
|
|
6
|
+
init(apiKey: string, projectId: string): Promise<FormoAnalytics>;
|
|
7
|
+
identify(userData: any): void;
|
|
8
|
+
page(): void;
|
|
9
|
+
track(eventName: string, eventData: any): void;
|
|
10
|
+
}
|
|
11
|
+
export class FormoAnalytics implements IFormoAnalytics {
|
|
12
|
+
private config: any;
|
|
13
|
+
private sessionIdKey: string = SESSION_STORAGE_ID_KEY;
|
|
14
|
+
private timezoneToCountry: Record<string, string> = COUNTRY_LIST;
|
|
15
|
+
|
|
16
|
+
private constructor(
|
|
17
|
+
public readonly apiKey: string,
|
|
18
|
+
public projectId: string
|
|
19
|
+
) {
|
|
20
|
+
this.config = {
|
|
21
|
+
token: this.apiKey,
|
|
22
|
+
};
|
|
23
|
+
this.trackPageHit();
|
|
24
|
+
}
|
|
25
|
+
static async init(
|
|
26
|
+
apiKey: string,
|
|
27
|
+
projectId: string
|
|
28
|
+
): Promise<FormoAnalytics> {
|
|
29
|
+
const config = {
|
|
30
|
+
token: apiKey,
|
|
31
|
+
};
|
|
32
|
+
const instance = new FormoAnalytics(apiKey, projectId);
|
|
33
|
+
instance.config = config;
|
|
34
|
+
|
|
35
|
+
return instance;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private identifyUser(userData: any) {
|
|
39
|
+
this.trackEvent('identify', userData);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private getSessionId() {
|
|
43
|
+
const existingSessionId = this.getCookieValue(this.sessionIdKey);
|
|
44
|
+
|
|
45
|
+
if (existingSessionId) {
|
|
46
|
+
return existingSessionId;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const newSessionId = this.generateSessionId();
|
|
50
|
+
return newSessionId;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Function to set the session cookie
|
|
54
|
+
private setSessionCookie(domain?: string) {
|
|
55
|
+
const sessionId = this.getSessionId();
|
|
56
|
+
let cookieValue = `${this.sessionIdKey}=${sessionId}; Max-Age=1800; path=/; secure`;
|
|
57
|
+
if (domain) {
|
|
58
|
+
cookieValue += `; domain=${domain}`;
|
|
59
|
+
}
|
|
60
|
+
document.cookie = cookieValue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Function to generate a new session ID
|
|
64
|
+
private generateSessionId(): string {
|
|
65
|
+
return crypto.randomUUID();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Function to get a cookie value by name
|
|
69
|
+
private getCookieValue(name: string): string | undefined {
|
|
70
|
+
const cookies = document.cookie.split(';').reduce((acc, cookie) => {
|
|
71
|
+
const [key, value] = cookie.split('=');
|
|
72
|
+
acc[key.trim()] = value;
|
|
73
|
+
return acc;
|
|
74
|
+
}, {} as Record<string, string>);
|
|
75
|
+
return cookies[name];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Function to send tracking data
|
|
79
|
+
private async trackEvent(action: string, payload: any) {
|
|
80
|
+
const maxRetries = 3;
|
|
81
|
+
let attempt = 0;
|
|
82
|
+
|
|
83
|
+
this.setSessionCookie(this.config.domain);
|
|
84
|
+
const apiUrl = this.buildApiUrl();
|
|
85
|
+
|
|
86
|
+
const requestData = {
|
|
87
|
+
project_id: this.projectId,
|
|
88
|
+
address: '', // TODO: get cached / session wallet address
|
|
89
|
+
session_id: this.getSessionId(),
|
|
90
|
+
timestamp: new Date().toISOString(),
|
|
91
|
+
action: action,
|
|
92
|
+
version: '1',
|
|
93
|
+
payload: isNotEmpty(payload) ? this.maskSensitiveData(payload) : payload,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
console.log('Request data:', JSON.stringify(requestData));
|
|
97
|
+
|
|
98
|
+
const sendRequest = async (): Promise<void> => {
|
|
99
|
+
try {
|
|
100
|
+
const response = await axios.post(apiUrl, JSON.stringify(requestData), {
|
|
101
|
+
headers: {
|
|
102
|
+
'Content-Type': 'application/json',
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (response.status >= 200 && response.status < 300) {
|
|
107
|
+
console.log('Event sent successfully:', action);
|
|
108
|
+
} else {
|
|
109
|
+
throw new Error(`Failed with status: ${response.status}`);
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
attempt++;
|
|
113
|
+
if (attempt <= maxRetries) {
|
|
114
|
+
const retryDelay = Math.pow(2, attempt) * 1000;
|
|
115
|
+
console.error(
|
|
116
|
+
`Attempt ${attempt}: Retrying event "${action}" in ${
|
|
117
|
+
retryDelay / 1000
|
|
118
|
+
} seconds...`
|
|
119
|
+
);
|
|
120
|
+
setTimeout(sendRequest, retryDelay);
|
|
121
|
+
} else {
|
|
122
|
+
console.error(
|
|
123
|
+
`Event "${action}" failed after ${maxRetries} attempts. Error: ${error}`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Start the initial request
|
|
130
|
+
await sendRequest();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Function to mask sensitive data in the payload
|
|
134
|
+
private maskSensitiveData(
|
|
135
|
+
data: string | undefined | null
|
|
136
|
+
): Record<string, any> | null {
|
|
137
|
+
// Check if data is null or undefined
|
|
138
|
+
if (data === null || data === undefined) {
|
|
139
|
+
console.warn('Data is null or undefined, returning null');
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Check if data is a string; if so, parse it to an object
|
|
144
|
+
if (typeof data === 'string') {
|
|
145
|
+
let parsedData: Record<string, any>;
|
|
146
|
+
try {
|
|
147
|
+
parsedData = JSON.parse(data);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('Failed to parse JSON:', error);
|
|
150
|
+
return null; // Return null if parsing fails
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const sensitiveFields = [
|
|
154
|
+
'username',
|
|
155
|
+
'user',
|
|
156
|
+
'user_id',
|
|
157
|
+
'password',
|
|
158
|
+
'email',
|
|
159
|
+
'phone',
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
// Create a new object to store masked data
|
|
163
|
+
const maskedData = { ...parsedData };
|
|
164
|
+
|
|
165
|
+
// Mask sensitive fields
|
|
166
|
+
sensitiveFields.forEach((field) => {
|
|
167
|
+
if (field in maskedData) {
|
|
168
|
+
maskedData[field] = '********'; // Replace value with masked string
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return maskedData; // Return the new object with masked fields
|
|
173
|
+
} else if (typeof data === 'object') {
|
|
174
|
+
// If data is already an object, handle masking directly
|
|
175
|
+
const sensitiveFields = [
|
|
176
|
+
'username',
|
|
177
|
+
'user',
|
|
178
|
+
'user_id',
|
|
179
|
+
'password',
|
|
180
|
+
'email',
|
|
181
|
+
'phone',
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
const maskedData = { ...(data as Record<string, any>) };
|
|
185
|
+
|
|
186
|
+
// Mask sensitive fields
|
|
187
|
+
sensitiveFields.forEach((field) => {
|
|
188
|
+
if (field in maskedData) {
|
|
189
|
+
maskedData[field] = '********'; // Replace value with masked string
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return maskedData; // Return the new object with masked fields
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return data;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Function to track page hits
|
|
200
|
+
private trackPageHit() {
|
|
201
|
+
if (window.__nightmare || window.navigator.webdriver || window.Cypress)
|
|
202
|
+
return;
|
|
203
|
+
|
|
204
|
+
let location: string | undefined;
|
|
205
|
+
let language: string;
|
|
206
|
+
try {
|
|
207
|
+
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
208
|
+
location = this.timezoneToCountry[timezone];
|
|
209
|
+
language =
|
|
210
|
+
navigator.languages && navigator.languages.length
|
|
211
|
+
? navigator.languages[0]
|
|
212
|
+
: navigator.language || 'en';
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error('Error resolving timezone or language:', error);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
setTimeout(() => {
|
|
218
|
+
this.trackEvent('page_hit', {
|
|
219
|
+
'user-agent': window.navigator.userAgent,
|
|
220
|
+
locale: language,
|
|
221
|
+
location: location,
|
|
222
|
+
referrer: document.referrer,
|
|
223
|
+
pathname: window.location.pathname,
|
|
224
|
+
href: window.location.href,
|
|
225
|
+
});
|
|
226
|
+
}, 300);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Function to build the API URL
|
|
230
|
+
private buildApiUrl(): string {
|
|
231
|
+
const { host, proxy, token, dataSource = 'analytics_events' } = this.config;
|
|
232
|
+
if (token) {
|
|
233
|
+
if (proxy) {
|
|
234
|
+
return `${proxy}/api/tracking`;
|
|
235
|
+
}
|
|
236
|
+
if (host) {
|
|
237
|
+
return `${host.replace(
|
|
238
|
+
/\/+$/,
|
|
239
|
+
''
|
|
240
|
+
)}/v0/events?name=${dataSource}&token=${token}`;
|
|
241
|
+
}
|
|
242
|
+
return `${EVENTS_API}?name=${dataSource}&token=${token}`;
|
|
243
|
+
}
|
|
244
|
+
return 'Error: No token provided';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
init(apiKey: string, projectId: string): Promise<FormoAnalytics> {
|
|
248
|
+
const instance = new FormoAnalytics(apiKey, projectId);
|
|
249
|
+
|
|
250
|
+
return Promise.resolve(instance);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
identify(userData: any) {
|
|
254
|
+
this.identifyUser(userData);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
page() {
|
|
258
|
+
this.trackPageHit();
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
track(eventName: string, eventData: any) {
|
|
262
|
+
this.trackEvent(eventName, eventData);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
createContext,
|
|
3
|
+
useContext,
|
|
4
|
+
useEffect,
|
|
5
|
+
useState,
|
|
6
|
+
useRef,
|
|
7
|
+
} from 'react';
|
|
8
|
+
import { FormoAnalytics } from './FormoAnalytics';
|
|
9
|
+
import { FormoAnalyticsProviderProps } from './types';
|
|
10
|
+
|
|
11
|
+
export const FormoAnalyticsContext = createContext<FormoAnalytics | undefined>(
|
|
12
|
+
undefined
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
export const FormoAnalyticsProvider = ({
|
|
16
|
+
apiKey,
|
|
17
|
+
projectId,
|
|
18
|
+
disabled,
|
|
19
|
+
children,
|
|
20
|
+
}: FormoAnalyticsProviderProps) => {
|
|
21
|
+
const [sdk, setSdk] = useState<FormoAnalytics | undefined>();
|
|
22
|
+
const initializedStartedRef = useRef(false);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!apiKey) {
|
|
26
|
+
throw new Error('FormoAnalyticsProvider: No API key provided');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (disabled) return;
|
|
30
|
+
|
|
31
|
+
if (initializedStartedRef.current) return;
|
|
32
|
+
initializedStartedRef.current = true;
|
|
33
|
+
|
|
34
|
+
FormoAnalytics.init(apiKey, projectId).then((sdkInstance) => setSdk(sdkInstance));
|
|
35
|
+
}, [apiKey, disabled]);
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<FormoAnalyticsContext.Provider value={sdk}>
|
|
39
|
+
{children}
|
|
40
|
+
</FormoAnalyticsContext.Provider>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const useFormoAnalytics = () => {
|
|
45
|
+
const context = useContext(FormoAnalyticsContext);
|
|
46
|
+
|
|
47
|
+
if (!context) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
'useFormoAnalytics must be used within a FormoAnalyticsProvider'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return context;
|
|
54
|
+
};
|