@react-native-firebase/analytics 20.1.0 → 20.2.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/CHANGELOG.md +6 -0
- package/lib/index.js +5 -0
- package/lib/version.js +1 -1
- package/lib/web/RNFBAnalyticsModule.android.js +2 -0
- package/lib/web/RNFBAnalyticsModule.ios.js +2 -0
- package/lib/web/RNFBAnalyticsModule.js +119 -0
- package/lib/web/api.js +297 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
@@ -3,6 +3,12 @@
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
5
5
|
|
6
|
+
## [20.2.0](https://github.com/invertase/react-native-firebase/compare/v20.1.0...v20.2.0) (2024-07-15)
|
7
|
+
|
8
|
+
### Features
|
9
|
+
|
10
|
+
- **other:** Add analytics support ([#7899](https://github.com/invertase/react-native-firebase/issues/7899)) ([cbdf9ec](https://github.com/invertase/react-native-firebase/commit/cbdf9ec78452a73751d1afeca428842898f63134))
|
11
|
+
|
6
12
|
## [20.1.0](https://github.com/invertase/react-native-firebase/compare/v20.0.0...v20.1.0) (2024-06-04)
|
7
13
|
|
8
14
|
**Note:** Version bump only for package @react-native-firebase/analytics
|
package/lib/index.js
CHANGED
@@ -33,8 +33,10 @@ import {
|
|
33
33
|
FirebaseModule,
|
34
34
|
getFirebaseRoot,
|
35
35
|
} from '@react-native-firebase/app/lib/internal';
|
36
|
+
import { setReactNativeModule } from '@react-native-firebase/app/lib/internal/nativeModule';
|
36
37
|
import { isBoolean } from '@react-native-firebase/app/lib/common';
|
37
38
|
|
39
|
+
import fallBackModule from './web/RNFBAnalyticsModule';
|
38
40
|
import version from './version';
|
39
41
|
import * as structs from './structs';
|
40
42
|
|
@@ -828,3 +830,6 @@ export default createModuleNamespace({
|
|
828
830
|
// analytics().logEvent(...);
|
829
831
|
// firebase.analytics().logEvent(...);
|
830
832
|
export const firebase = getFirebaseRoot();
|
833
|
+
|
834
|
+
// Register the interop module for non-native platforms.
|
835
|
+
setReactNativeModule(nativeModuleName, fallBackModule);
|
package/lib/version.js
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
// Generated by genversion.
|
2
|
-
module.exports = '20.
|
2
|
+
module.exports = '20.2.0';
|
@@ -0,0 +1,119 @@
|
|
1
|
+
import { getApp } from '@react-native-firebase/app/lib/internal/web/firebaseApp';
|
2
|
+
import { guard } from '@react-native-firebase/app/lib/internal/web/utils';
|
3
|
+
|
4
|
+
import { AnalyticsApi } from './api';
|
5
|
+
|
6
|
+
let analyticsInstances = {};
|
7
|
+
|
8
|
+
function getAnalyticsApi() {
|
9
|
+
const app = getApp('[DEFAULT]');
|
10
|
+
const measurementId = app.options.measurementId;
|
11
|
+
if (!measurementId) {
|
12
|
+
// eslint-disable-next-line no-console
|
13
|
+
console.warn(
|
14
|
+
'No measurement id found for Firebase Analytics. Analytics will be unavailable. Have you set gaTrackingId in your Firebase config?',
|
15
|
+
);
|
16
|
+
}
|
17
|
+
if (!analyticsInstances[measurementId]) {
|
18
|
+
analyticsInstances[measurementId] = new AnalyticsApi('[DEFAULT]', measurementId);
|
19
|
+
if (global.RNFBDebug) {
|
20
|
+
analyticsInstances[measurementId].setDebug(true);
|
21
|
+
}
|
22
|
+
}
|
23
|
+
return analyticsInstances[measurementId];
|
24
|
+
}
|
25
|
+
|
26
|
+
/**
|
27
|
+
* This is a 'NativeModule' for the web platform.
|
28
|
+
* Methods here are identical to the ones found in
|
29
|
+
* the native android/ios modules e.g. `@ReactMethod` annotated
|
30
|
+
* java methods on Android.
|
31
|
+
*/
|
32
|
+
export default {
|
33
|
+
logEvent(name, params) {
|
34
|
+
return guard(async () => {
|
35
|
+
const api = getAnalyticsApi('[DEFAULT]');
|
36
|
+
api.logEvent(name, params);
|
37
|
+
return null;
|
38
|
+
});
|
39
|
+
},
|
40
|
+
|
41
|
+
setUserId(userId) {
|
42
|
+
return guard(async () => {
|
43
|
+
const api = getAnalyticsApi('[DEFAULT]');
|
44
|
+
api.setUserId(userId);
|
45
|
+
return null;
|
46
|
+
});
|
47
|
+
},
|
48
|
+
|
49
|
+
setUserProperty(key, value) {
|
50
|
+
return guard(async () => {
|
51
|
+
const api = getAnalyticsApi('[DEFAULT]');
|
52
|
+
api.setUserProperty(key, value);
|
53
|
+
return null;
|
54
|
+
});
|
55
|
+
},
|
56
|
+
|
57
|
+
setUserProperties(properties) {
|
58
|
+
return guard(async () => {
|
59
|
+
const api = getAnalyticsApi('[DEFAULT]');
|
60
|
+
api.setUserProperties(properties);
|
61
|
+
return null;
|
62
|
+
});
|
63
|
+
},
|
64
|
+
|
65
|
+
setDefaultEventParameters(params) {
|
66
|
+
return guard(async () => {
|
67
|
+
const api = getAnalyticsApi('[DEFAULT]');
|
68
|
+
api.setDefaultEventParameters(params);
|
69
|
+
return null;
|
70
|
+
});
|
71
|
+
},
|
72
|
+
|
73
|
+
setConsent(consent) {
|
74
|
+
return guard(async () => {
|
75
|
+
const api = getAnalyticsApi('[DEFAULT]');
|
76
|
+
// TODO currently we only support ad_personalization
|
77
|
+
if (consent && consent.ad_personalization) {
|
78
|
+
api.setConsent({ ad_personalization: consent.ad_personalization });
|
79
|
+
}
|
80
|
+
return null;
|
81
|
+
});
|
82
|
+
},
|
83
|
+
|
84
|
+
setAnalyticsCollectionEnabled(enabled) {
|
85
|
+
return guard(async () => {
|
86
|
+
const api = getAnalyticsApi('[DEFAULT]');
|
87
|
+
api.setAnalyticsCollectionEnabled(enabled);
|
88
|
+
return null;
|
89
|
+
});
|
90
|
+
},
|
91
|
+
|
92
|
+
resetAnalyticsData() {
|
93
|
+
// Unsupported for web.
|
94
|
+
return guard(async () => {
|
95
|
+
return null;
|
96
|
+
});
|
97
|
+
},
|
98
|
+
|
99
|
+
setSessionTimeoutDuration() {
|
100
|
+
// Unsupported for web.
|
101
|
+
return guard(async () => {
|
102
|
+
return null;
|
103
|
+
});
|
104
|
+
},
|
105
|
+
|
106
|
+
getAppInstanceId() {
|
107
|
+
// Unsupported for web.
|
108
|
+
return guard(async () => {
|
109
|
+
return null;
|
110
|
+
});
|
111
|
+
},
|
112
|
+
|
113
|
+
getSessionId() {
|
114
|
+
// Unsupported for web.
|
115
|
+
return guard(async () => {
|
116
|
+
return null;
|
117
|
+
});
|
118
|
+
},
|
119
|
+
};
|
package/lib/web/api.js
ADDED
@@ -0,0 +1,297 @@
|
|
1
|
+
/* eslint-disable no-console */
|
2
|
+
import {
|
3
|
+
getApp,
|
4
|
+
getId,
|
5
|
+
onIdChange,
|
6
|
+
getInstallations,
|
7
|
+
makeIDBAvailable,
|
8
|
+
} from '@react-native-firebase/app/lib/internal/web/firebaseInstallations';
|
9
|
+
import { isNumber } from '@react-native-firebase/app/lib/common';
|
10
|
+
|
11
|
+
/**
|
12
|
+
* Generates a Google Analytics client ID.
|
13
|
+
* @returns {string} The generated client ID.
|
14
|
+
*/
|
15
|
+
function generateGAClientId() {
|
16
|
+
const randomNumber = Math.round(Math.random() * 2147483647);
|
17
|
+
// TODO: Don't seem to need this for now.
|
18
|
+
// var hash = 1;
|
19
|
+
// if (seed) {
|
20
|
+
// for (var i = seed.length - 1; i >= 0; i--) {
|
21
|
+
// var char = seed.charCodeAt(i);
|
22
|
+
// hash = ((hash << 6) & 268435455) + char + (char << 14);
|
23
|
+
// var flag = hash & 266338304;
|
24
|
+
// hash = flag !== 0 ? hash ^ (flag >> 21) : hash;
|
25
|
+
// }
|
26
|
+
// }
|
27
|
+
// const randomPart = seed ? String(randomNumber ^ (hash & 2147483647)) : String(randomNumber);
|
28
|
+
const randomPart = String(randomNumber);
|
29
|
+
const timestamp = Math.round(Date.now() / 1000);
|
30
|
+
return randomPart + '.' + timestamp;
|
31
|
+
}
|
32
|
+
|
33
|
+
class AnalyticsApi {
|
34
|
+
constructor(appName, measurementId) {
|
35
|
+
this.appName = appName;
|
36
|
+
this.measurementId = measurementId;
|
37
|
+
this.eventQueue = [];
|
38
|
+
this.queueTimer = null;
|
39
|
+
this.queueInterval = 250;
|
40
|
+
this.defaultEventParameters = {};
|
41
|
+
this.userId = null;
|
42
|
+
this.userProperties = {};
|
43
|
+
this.consent = {};
|
44
|
+
this.analyticsCollectionEnabled = true;
|
45
|
+
this.started = false;
|
46
|
+
this.installationId = null;
|
47
|
+
this.debug = false;
|
48
|
+
this.currentScreen = null;
|
49
|
+
|
50
|
+
// TODO this should be persisted once we have a way to do so in app internals
|
51
|
+
this.cid = generateGAClientId();
|
52
|
+
this._getInstallationId().catch(error => {
|
53
|
+
if (global.RNFBDebug) {
|
54
|
+
console.debug('[RNFB->Analytics][🔴] Error getting Firebase Installation Id:', error);
|
55
|
+
} else {
|
56
|
+
// No-op. This is a non-critical error.
|
57
|
+
}
|
58
|
+
});
|
59
|
+
}
|
60
|
+
|
61
|
+
setDefaultEventParameters(params) {
|
62
|
+
if (params === null || params === undefined) {
|
63
|
+
this.defaultEventParameters = {};
|
64
|
+
} else {
|
65
|
+
for (const [key, value] of Object.entries(params)) {
|
66
|
+
this.defaultEventParameters[key] = value;
|
67
|
+
if (value === null) {
|
68
|
+
delete this.defaultEventParameters[key];
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
setDebug(enabled) {
|
75
|
+
this.debug = enabled;
|
76
|
+
}
|
77
|
+
|
78
|
+
setUserId(userId) {
|
79
|
+
this.userId = userId;
|
80
|
+
}
|
81
|
+
|
82
|
+
setCurrentScreen(screenName) {
|
83
|
+
this.currentScreen = screenName;
|
84
|
+
}
|
85
|
+
|
86
|
+
setUserProperty(key, value) {
|
87
|
+
this.userProperties[key] = value;
|
88
|
+
if (value === null) {
|
89
|
+
delete this.userProperties[key];
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
setUserProperties(properties) {
|
94
|
+
for (const [key, value] of Object.entries(properties)) {
|
95
|
+
this.setUserProperty(key, value);
|
96
|
+
}
|
97
|
+
}
|
98
|
+
|
99
|
+
setConsent(consentSettings) {
|
100
|
+
this.consent = { ...this.consent, ...consentSettings };
|
101
|
+
}
|
102
|
+
|
103
|
+
setAnalyticsCollectionEnabled(enabled) {
|
104
|
+
this.analyticsCollectionEnabled = enabled;
|
105
|
+
if (!enabled) {
|
106
|
+
this._stopQueueProcessing();
|
107
|
+
} else {
|
108
|
+
this._startQueueProcessing();
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
logEvent(eventName, eventParams = {}) {
|
113
|
+
if (!this.analyticsCollectionEnabled) return;
|
114
|
+
this.eventQueue.push({
|
115
|
+
name: eventName,
|
116
|
+
params: { ...this.defaultEventParameters, ...eventParams },
|
117
|
+
});
|
118
|
+
this._startQueueProcessing();
|
119
|
+
}
|
120
|
+
|
121
|
+
async _getInstallationId() {
|
122
|
+
navigator.onLine = true;
|
123
|
+
makeIDBAvailable();
|
124
|
+
const app = getApp(this.appName);
|
125
|
+
const installations = getInstallations(app);
|
126
|
+
const id = await getId(installations);
|
127
|
+
if (global.RNFBDebug) {
|
128
|
+
console.debug('[RNFB->Analytics][📊] Firebase Installation Id:', id);
|
129
|
+
}
|
130
|
+
this.installationId = id;
|
131
|
+
onIdChange(installations, newId => {
|
132
|
+
this.installationId = newId;
|
133
|
+
});
|
134
|
+
}
|
135
|
+
|
136
|
+
_startQueueProcessing() {
|
137
|
+
if (this.started) return;
|
138
|
+
this.sessionId = Math.floor(Date.now() / 1000);
|
139
|
+
this.started = true;
|
140
|
+
this.queueTimer = setInterval(
|
141
|
+
() => this._processQueue().catch(console.error),
|
142
|
+
this.queueInterval,
|
143
|
+
);
|
144
|
+
}
|
145
|
+
|
146
|
+
_stopQueueProcessing() {
|
147
|
+
if (!this.started) return;
|
148
|
+
this.started = false;
|
149
|
+
clearInterval(this.queueTimer);
|
150
|
+
}
|
151
|
+
|
152
|
+
async _processQueue() {
|
153
|
+
if (this.eventQueue.length === 0) return;
|
154
|
+
const events = this.eventQueue.splice(0, 5);
|
155
|
+
await this._sendEvents(events);
|
156
|
+
if (this.eventQueue.length === 0) {
|
157
|
+
this._stopQueueProcessing();
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
async _sendEvents(events) {
|
162
|
+
for (const event of events) {
|
163
|
+
const queryParams = new URLSearchParams({
|
164
|
+
v: '2',
|
165
|
+
tid: this.measurementId,
|
166
|
+
en: event.name,
|
167
|
+
cid: this.cid,
|
168
|
+
pscdl: 'noapi',
|
169
|
+
sid: this.sessionId,
|
170
|
+
'ep.origin': 'firebase',
|
171
|
+
_z: 'fetch',
|
172
|
+
_p: '' + Date.now(),
|
173
|
+
_s: 1,
|
174
|
+
_ee: 1,
|
175
|
+
dma: 0,
|
176
|
+
tfd: Math.round(performance.now()),
|
177
|
+
are: 1,
|
178
|
+
sct: 2,
|
179
|
+
seg: 1,
|
180
|
+
frm: 0,
|
181
|
+
});
|
182
|
+
|
183
|
+
if (this.debug) {
|
184
|
+
queryParams.append('_dbg', '1');
|
185
|
+
queryParams.append('ep.debug_mode', '1');
|
186
|
+
}
|
187
|
+
|
188
|
+
if (this.consent && !this.consent.ad_personalization) {
|
189
|
+
queryParams.append('npa', '1');
|
190
|
+
} else {
|
191
|
+
queryParams.append('npa', '0');
|
192
|
+
}
|
193
|
+
|
194
|
+
if (this.userId) {
|
195
|
+
queryParams.append('uid', this.userId);
|
196
|
+
}
|
197
|
+
|
198
|
+
if (this.installationId) {
|
199
|
+
queryParams.append('_fid', this.installationId);
|
200
|
+
}
|
201
|
+
|
202
|
+
if (this.userProperties && Object.keys(this.userProperties).length > 0) {
|
203
|
+
for (const [key, value] of Object.entries(this.userProperties)) {
|
204
|
+
queryParams.append(`up.${key}`, `${value}`);
|
205
|
+
}
|
206
|
+
}
|
207
|
+
|
208
|
+
if (this.currentScreen) {
|
209
|
+
queryParams.append('ep.screen_name', this.currentScreen);
|
210
|
+
queryParams.append('ep.firebase_screen', this.currentScreen);
|
211
|
+
}
|
212
|
+
|
213
|
+
if (event.params && Object.keys(event.params).length > 0) {
|
214
|
+
// TODO we need to handle 'items' arrays and also key name conversions based on the following map;
|
215
|
+
// const keyConvert = {
|
216
|
+
// item_id: 'id',
|
217
|
+
// item_name: 'nm',
|
218
|
+
// item_brand: 'br',
|
219
|
+
// item_category: 'ca',
|
220
|
+
// item_category2: 'c2',
|
221
|
+
// item_category3: 'c3',
|
222
|
+
// item_category4: 'c4',
|
223
|
+
// item_category5: 'c5',
|
224
|
+
// item_variant: 'va',
|
225
|
+
// price: 'pr',
|
226
|
+
// quantity: 'qt',
|
227
|
+
// coupon: 'cp',
|
228
|
+
// item_list_name: 'ln',
|
229
|
+
// index: 'lp',
|
230
|
+
// item_list_id: 'li',
|
231
|
+
// discount: 'ds',
|
232
|
+
// affiliation: 'af',
|
233
|
+
// promotion_id: 'pi',
|
234
|
+
// promotion_name: 'pn',
|
235
|
+
// creative_name: 'cn',
|
236
|
+
// creative_slot: 'cs',
|
237
|
+
// location_id: 'lo',
|
238
|
+
// id: 'id',
|
239
|
+
// name: 'nm',
|
240
|
+
// brand: 'br',
|
241
|
+
// variant: 'va',
|
242
|
+
// list_name: 'ln',
|
243
|
+
// list_position: 'lp',
|
244
|
+
// list: 'ln',
|
245
|
+
// position: 'lp',
|
246
|
+
// creative: 'cn',
|
247
|
+
// };
|
248
|
+
// items array should for example become:
|
249
|
+
// pr1 for items[0]
|
250
|
+
// pr2 for items[1]
|
251
|
+
// ... etc
|
252
|
+
// with the format for each looking something like:
|
253
|
+
// iditem_id~nmitem_name~britem_brand~caitem_category~c2item_category2~c3item_category3~c4item_category4~c5item_category5~vaitem_variant~prprice~qtquantity~cpcoupon~lnitem_list_name~lpindex~liitem_list_id~dsdiscount~afaffiliation~pipromotion_id~pnpromotion_name~cncreative_name~cscreative_slot~lolocation_id
|
254
|
+
for (const [key, value] of Object.entries(event.params)) {
|
255
|
+
if (isNumber(value)) {
|
256
|
+
queryParams.append(`epn.${key}`, `${value}`);
|
257
|
+
} else {
|
258
|
+
queryParams.append(`ep.${key}`, `${value}`);
|
259
|
+
}
|
260
|
+
}
|
261
|
+
}
|
262
|
+
|
263
|
+
try {
|
264
|
+
const url = `https://www.google-analytics.com/g/collect?${queryParams.toString()}`;
|
265
|
+
if (global.RNFBDebug) {
|
266
|
+
console.debug(`[RNFB-->Fetch][📊] Sending analytics call: ${url}`);
|
267
|
+
}
|
268
|
+
const response = await fetch(url, {
|
269
|
+
method: 'POST',
|
270
|
+
headers: {
|
271
|
+
accept: '*/*',
|
272
|
+
'accept-encoding': 'gzip, deflate, br',
|
273
|
+
'Content-Type': 'text/plain;charset=UTF-8',
|
274
|
+
'accept-language': 'en-US,en;q=0.9',
|
275
|
+
'cache-control': 'no-cache',
|
276
|
+
'content-length': '0',
|
277
|
+
origin: 'firebase',
|
278
|
+
pragma: 'no-cache',
|
279
|
+
'sec-fetch-dest': 'empty',
|
280
|
+
'sec-fetch-mode': 'no-cors',
|
281
|
+
'sec-fetch-site': 'cross-site',
|
282
|
+
'user-agent': 'react-native-firebase',
|
283
|
+
},
|
284
|
+
});
|
285
|
+
if (global.RNFBDebug) {
|
286
|
+
console.debug(`[RNFB<--Fetch][📊] Response: ${response.status}`);
|
287
|
+
}
|
288
|
+
} catch (error) {
|
289
|
+
if (global.RNFBDebug) {
|
290
|
+
console.debug('[RNFB<--Fetch][🔴] Error sending Analytics event:', error);
|
291
|
+
}
|
292
|
+
}
|
293
|
+
}
|
294
|
+
}
|
295
|
+
}
|
296
|
+
|
297
|
+
export { AnalyticsApi };
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@react-native-firebase/analytics",
|
3
|
-
"version": "20.
|
3
|
+
"version": "20.2.0",
|
4
4
|
"author": "Invertase <oss@invertase.io> (http://invertase.io)",
|
5
5
|
"description": "React Native Firebase - The analytics module provides out of the box support with Google Analytics for Firebase. Integration with the Android & iOS allows for in-depth analytical insight reporting, such as device information, location, user actions and more.",
|
6
6
|
"main": "lib/index.js",
|
@@ -22,10 +22,10 @@
|
|
22
22
|
"analytics"
|
23
23
|
],
|
24
24
|
"peerDependencies": {
|
25
|
-
"@react-native-firebase/app": "20.
|
25
|
+
"@react-native-firebase/app": "20.2.0"
|
26
26
|
},
|
27
27
|
"publishConfig": {
|
28
28
|
"access": "public"
|
29
29
|
},
|
30
|
-
"gitHead": "
|
30
|
+
"gitHead": "82c50138d07e673213cd8dee5ce9a2f9b5656649"
|
31
31
|
}
|