@crimson-education/browser-logger 5.0.1 → 5.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/README.md +72 -94
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +23 -46
- package/lib/index.js.map +1 -1
- package/lib/logger/consoleTransport.d.ts +1 -1
- package/lib/logger/consoleTransport.d.ts.map +1 -1
- package/lib/logger/consoleTransport.js +16 -22
- package/lib/logger/consoleTransport.js.map +1 -1
- package/lib/logger/datadogTransport.d.ts +1 -1
- package/lib/logger/datadogTransport.d.ts.map +1 -1
- package/lib/logger/datadogTransport.js +4 -8
- package/lib/logger/datadogTransport.js.map +1 -1
- package/lib/logger/index.js +29 -49
- package/lib/logger/index.js.map +1 -1
- package/lib/logger/index.test.js +16 -18
- package/lib/logger/index.test.js.map +1 -1
- package/lib/logger/utils.js +4 -9
- package/lib/logger/utils.js.map +1 -1
- package/lib/reporters/datadogReporter.d.ts +13 -1
- package/lib/reporters/datadogReporter.d.ts.map +1 -1
- package/lib/reporters/datadogReporter.js +122 -49
- package/lib/reporters/datadogReporter.js.map +1 -1
- package/lib/reporters/gtmReporter.d.ts +1 -1
- package/lib/reporters/gtmReporter.d.ts.map +1 -1
- package/lib/reporters/gtmReporter.js +1 -5
- package/lib/reporters/gtmReporter.js.map +1 -1
- package/lib/reporters/index.js +46 -71
- package/lib/reporters/index.js.map +1 -1
- package/lib/reporters/logReporter.js +24 -34
- package/lib/reporters/logReporter.js.map +1 -1
- package/lib/reporters/posthogReporter.d.ts +1 -1
- package/lib/reporters/posthogReporter.d.ts.map +1 -1
- package/lib/reporters/posthogReporter.js +41 -55
- package/lib/reporters/posthogReporter.js.map +1 -1
- package/lib/reporters/posthogReporter.test.js +15 -22
- package/lib/reporters/posthogReporter.test.js.map +1 -1
- package/lib/types/index.js +2 -18
- package/lib/types/index.js.map +1 -1
- package/lib/types/logger.d.ts +3 -3
- package/lib/types/logger.d.ts.map +1 -1
- package/lib/types/logger.js +2 -5
- package/lib/types/logger.js.map +1 -1
- package/lib/types/reporter.d.ts +6 -12
- package/lib/types/reporter.d.ts.map +1 -1
- package/lib/types/reporter.js +1 -2
- package/lib/utils.js +1 -5
- package/lib/utils.js.map +1 -1
- package/lib/utils.test.js +2 -4
- package/lib/utils.test.js.map +1 -1
- package/package.json +16 -16
- package/src/index.ts +0 -6
- package/src/reporters/datadogReporter.ts +133 -22
- package/src/types/reporter.ts +0 -7
- package/lib/reporters/amplifyReporter.d.ts +0 -86
- package/lib/reporters/amplifyReporter.d.ts.map +0 -1
- package/lib/reporters/amplifyReporter.js +0 -225
- package/lib/reporters/amplifyReporter.js.map +0 -1
- package/lib/reporters/amplifyReporter.test.d.ts +0 -2
- package/lib/reporters/amplifyReporter.test.d.ts.map +0 -1
- package/lib/reporters/amplifyReporter.test.js +0 -51
- package/lib/reporters/amplifyReporter.test.js.map +0 -1
- package/src/reporters/amplifyReporter.test.ts +0 -61
- package/src/reporters/amplifyReporter.ts +0 -344
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crimson-education/browser-logger",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.2",
|
|
4
4
|
"description": "An abstract logger and reporting utility for browser environments",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"prepack": "npm run build",
|
|
@@ -28,25 +28,25 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://github.com/crimson-education/browser-logger#readme",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@datadog/browser-logs": "^
|
|
32
|
-
"@datadog/browser-rum": "^
|
|
31
|
+
"@datadog/browser-logs": "^6.13.0",
|
|
32
|
+
"@datadog/browser-rum": "^6.13.0",
|
|
33
33
|
"posthog-js": "^1.396.5"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@crimson-education/eslint-config": "^2.1.0",
|
|
37
|
-
"@types/jest": "^
|
|
38
|
-
"eslint": "^
|
|
39
|
-
"eslint
|
|
40
|
-
"eslint-
|
|
41
|
-
"jest": "^
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
37
|
+
"@types/jest": "^29.5.12",
|
|
38
|
+
"@typescript-eslint/eslint-plugin": "^5.15.0",
|
|
39
|
+
"eslint": "^8.57.0",
|
|
40
|
+
"eslint-config-prettier": "^9.1.0",
|
|
41
|
+
"eslint-plugin-jest": "^28.9.0",
|
|
42
|
+
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
43
|
+
"eslint-plugin-prettier": "^5.5.6",
|
|
44
|
+
"eslint-plugin-react": "^7.37.5",
|
|
45
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
46
|
+
"jest": "^29.7.0",
|
|
47
|
+
"rimraf": "^5.0.5",
|
|
48
|
+
"ts-jest": "^29.1.2",
|
|
49
|
+
"typescript": "5.1.6"
|
|
50
50
|
},
|
|
51
51
|
"files": [
|
|
52
52
|
"lib",
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { ReporterConfigurations, ServiceInfo } from './types';
|
|
|
2
2
|
import { setLogLevel } from './logger';
|
|
3
3
|
import { addMetadata, reporters, setEventLevel } from './reporters';
|
|
4
4
|
import { gtmReporter } from './reporters/gtmReporter';
|
|
5
|
-
import { amplifyReporter } from './reporters/amplifyReporter';
|
|
6
5
|
import { datadogReporter } from './reporters/datadogReporter';
|
|
7
6
|
import { logReporter } from './reporters/logReporter';
|
|
8
7
|
import { posthogReporter } from './reporters/posthogReporter';
|
|
@@ -36,14 +35,9 @@ export function init(config: LoggerConfig) {
|
|
|
36
35
|
reporters['datadog'] = datadogReporter(config, config.reporters.datadog);
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
if (config.reporters?.amplify) {
|
|
40
|
-
reporters['amplify'] = amplifyReporter(config, config.reporters.amplify);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
38
|
if (config.reporters?.posthog) {
|
|
44
39
|
reporters['posthog'] = posthogReporter(config, config.reporters.posthog);
|
|
45
40
|
}
|
|
46
|
-
|
|
47
41
|
if (config.reporters?.gtm) {
|
|
48
42
|
reporters['gtm'] = gtmReporter(config, typeof config.reporters.gtm === 'boolean' ? {} : config.reporters.gtm);
|
|
49
43
|
}
|
|
@@ -15,6 +15,95 @@ import { datadogRum, DefaultPrivacyLevel, RumInitConfiguration } from '@datadog/
|
|
|
15
15
|
import { logTransports } from '../logger';
|
|
16
16
|
import { DatadogLogTransportConfig, datadogTransport } from '../logger/datadogTransport';
|
|
17
17
|
|
|
18
|
+
// User frustration detection for Datadog Gen2
|
|
19
|
+
class DatadogFrustrationDetector {
|
|
20
|
+
private config: DatadogReporterConfig;
|
|
21
|
+
private frustrationEvents: Array<{ type: string; timestamp: number }> = [];
|
|
22
|
+
private readonly FRUSTRATION_THRESHOLD = 3; // Number of events to trigger frustration
|
|
23
|
+
private readonly FRUSTRATION_WINDOW = 5000; // Time window in ms
|
|
24
|
+
|
|
25
|
+
constructor(config: DatadogReporterConfig) {
|
|
26
|
+
this.config = config;
|
|
27
|
+
if (config.trackFrustrations) {
|
|
28
|
+
this.setupFrustrationDetection();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private setupFrustrationDetection() {
|
|
33
|
+
// Track rapid clicks (potential frustration)
|
|
34
|
+
let clickCount = 0;
|
|
35
|
+
let lastClickTime = 0;
|
|
36
|
+
|
|
37
|
+
document.addEventListener('click', () => {
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
if (now - lastClickTime < 1000) {
|
|
40
|
+
// Rapid clicks within 1 second
|
|
41
|
+
clickCount++;
|
|
42
|
+
if (clickCount >= 3) {
|
|
43
|
+
this.recordFrustrationEvent('rapid_clicks');
|
|
44
|
+
clickCount = 0;
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
clickCount = 1;
|
|
48
|
+
}
|
|
49
|
+
lastClickTime = now;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Track form errors (potential frustration)
|
|
53
|
+
document.addEventListener('invalid', () => {
|
|
54
|
+
this.recordFrustrationEvent('form_validation_error');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Track 404 errors (potential frustration)
|
|
58
|
+
window.addEventListener('error', (event) => {
|
|
59
|
+
if (event.message.includes('404') || event.message.includes('Not Found')) {
|
|
60
|
+
this.recordFrustrationEvent('page_not_found');
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Track network errors (potential frustration)
|
|
65
|
+
window.addEventListener('unhandledrejection', (event) => {
|
|
66
|
+
if (event.reason && typeof event.reason === 'object' && 'status' in event.reason) {
|
|
67
|
+
const status = (event.reason as any).status;
|
|
68
|
+
if (status >= 400) {
|
|
69
|
+
this.recordFrustrationEvent('network_error');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private recordFrustrationEvent(type: string) {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
this.frustrationEvents.push({ type, timestamp: now });
|
|
78
|
+
|
|
79
|
+
// Clean old events outside the window
|
|
80
|
+
this.frustrationEvents = this.frustrationEvents.filter((event) => now - event.timestamp < this.FRUSTRATION_WINDOW);
|
|
81
|
+
|
|
82
|
+
// Check if we have enough events to trigger frustration
|
|
83
|
+
if (this.frustrationEvents.length >= this.FRUSTRATION_THRESHOLD) {
|
|
84
|
+
this.triggerFrustrationDetection();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private triggerFrustrationDetection() {
|
|
89
|
+
const frustrationData = {
|
|
90
|
+
frustrationType: 'user_frustration_detected',
|
|
91
|
+
frustrationEvents: this.frustrationEvents.map((e) => e.type),
|
|
92
|
+
frustrationCount: this.frustrationEvents.length,
|
|
93
|
+
pageUrl: window.location.href,
|
|
94
|
+
timestamp: new Date().toISOString(),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Send frustration event to Datadog
|
|
98
|
+
if (typeof datadogRum !== 'undefined') {
|
|
99
|
+
datadogRum.addAction('User Frustration Detected', frustrationData);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Clear events after reporting
|
|
103
|
+
this.frustrationEvents = [];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
18
107
|
export interface DatadogReporterConfig extends ReporterConfigBase {
|
|
19
108
|
/** The RUM application ID. */
|
|
20
109
|
applicationId: string;
|
|
@@ -36,11 +125,17 @@ export interface DatadogReporterConfig extends ReporterConfigBase {
|
|
|
36
125
|
* @deprecated Prefer using the dedicated `logSampleRate`, `rumSampleRate`, and `replaySampleRate` options.
|
|
37
126
|
*/
|
|
38
127
|
sampleRate?: number;
|
|
128
|
+
|
|
129
|
+
sessionSampleRate?: number;
|
|
130
|
+
sessionReplaySampleRate?: number;
|
|
131
|
+
|
|
39
132
|
/**
|
|
40
133
|
* Sampling rate for browser log collection. Defaults to 100, meaning every log message is sent to Datadog.
|
|
41
134
|
*/
|
|
42
135
|
logSampleRate?: number;
|
|
136
|
+
|
|
43
137
|
/**
|
|
138
|
+
* @deprecated Prefer using `sessionSampleRate`.
|
|
44
139
|
* Sampling rate for RUM session collection. Defaults to 100, meaning every browser session is tracked.
|
|
45
140
|
*
|
|
46
141
|
* RUM can be used to track things like core web vitals.
|
|
@@ -48,7 +143,9 @@ export interface DatadogReporterConfig extends ReporterConfigBase {
|
|
|
48
143
|
* @see https://www.datadoghq.com/product/real-user-monitoring/
|
|
49
144
|
*/
|
|
50
145
|
rumSampleRate?: number;
|
|
146
|
+
|
|
51
147
|
/**
|
|
148
|
+
* @deprecated Prefer using `sessionReplaySampleRate`.
|
|
52
149
|
* Applied after the RUM sample rate, and controls the percentage of sessions tracked as Browser RUM & Session Replay.
|
|
53
150
|
* It defaults to 100, so every session is tracked as Browser RUM & Session Replay by default.
|
|
54
151
|
*
|
|
@@ -60,11 +157,21 @@ export interface DatadogReporterConfig extends ReporterConfigBase {
|
|
|
60
157
|
* Use a secure session cookie. This disables RUM events sent on insecure (non-HTTPS) connections.
|
|
61
158
|
*/
|
|
62
159
|
useSecureSessionCookie?: boolean;
|
|
160
|
+
|
|
63
161
|
/**
|
|
162
|
+
* @deprecated Prefer using `usePartitionedCrossSiteSessionCookie`.
|
|
64
163
|
* Use a secure cross-site session cookie.
|
|
65
164
|
* This allows the RUM Browser SDK to run when the site is loaded from another one (iframe). Implies `useSecureSessionCookie`
|
|
66
165
|
*/
|
|
67
166
|
useCrossSiteSessionCookie?: boolean;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Use a partitioned secure cross-site session cookie. This allows the RUM Browser SDK to run when the site is loaded from another one (iframe). Implies `useSecureSessionCookie`.
|
|
170
|
+
* Important: If you are using the RUM and Logs Browser SDKs, this option must be configured with identical values
|
|
171
|
+
* @default false
|
|
172
|
+
*/
|
|
173
|
+
usePartitionedCrossSiteSessionCookie?: boolean;
|
|
174
|
+
|
|
68
175
|
/**
|
|
69
176
|
* Preserve the session across subdomains for the same site.
|
|
70
177
|
*/
|
|
@@ -117,7 +224,8 @@ export interface DatadogReporterConfig extends ReporterConfigBase {
|
|
|
117
224
|
/**
|
|
118
225
|
* A list of request origins used to inject tracing headers, to be able to connect RUM and backend tracing.
|
|
119
226
|
*/
|
|
120
|
-
|
|
227
|
+
allowedTrackingOrigins?: (string | RegExp)[];
|
|
228
|
+
allowedTracingUrls?: (string | RegExp)[];
|
|
121
229
|
/**
|
|
122
230
|
* Enables action tracking for user interactions. This enables the Heatmap tab within Datadog
|
|
123
231
|
*/
|
|
@@ -140,23 +248,23 @@ export function datadogReporter(info: ServiceInfo, config: DatadogReporterConfig
|
|
|
140
248
|
const enableLogTransport = config.logTransport !== false;
|
|
141
249
|
|
|
142
250
|
// Only init datadog logs if something is using it.
|
|
143
|
-
if (forwardErrorsToLogs
|
|
251
|
+
if (forwardErrorsToLogs === true || enableLogTransport === true) {
|
|
144
252
|
datadogLogs.init({
|
|
145
|
-
site: config.site,
|
|
146
|
-
proxyUrl: config.proxyUrl,
|
|
253
|
+
site: config.site as any,
|
|
147
254
|
clientToken: config.clientToken,
|
|
148
255
|
service: info.service,
|
|
149
256
|
env: info.environment,
|
|
150
257
|
version: config.version ?? info.version,
|
|
151
258
|
|
|
152
|
-
sampleRate: config.logSampleRate ?? config.sampleRate ?? 100,
|
|
153
259
|
beforeSend: config.beforeLogsSend,
|
|
154
260
|
|
|
261
|
+
usePartitionedCrossSiteSessionCookie: config.usePartitionedCrossSiteSessionCookie,
|
|
155
262
|
useSecureSessionCookie: config.useSecureSessionCookie ?? !isLocalhost,
|
|
156
|
-
|
|
263
|
+
// Note: useCrossSiteSessionCookie is not available in newer Datadog versions
|
|
157
264
|
trackSessionAcrossSubdomains: config.trackSessionAcrossSubdomains,
|
|
158
265
|
|
|
159
266
|
forwardErrorsToLogs,
|
|
267
|
+
allowedTrackingOrigins: config.allowedTrackingOrigins,
|
|
160
268
|
});
|
|
161
269
|
datadogLogs.logger.setHandler(HandlerType.http);
|
|
162
270
|
}
|
|
@@ -168,34 +276,37 @@ export function datadogReporter(info: ServiceInfo, config: DatadogReporterConfig
|
|
|
168
276
|
|
|
169
277
|
datadogRum.init({
|
|
170
278
|
enableExperimentalFeatures: ['feature_flags'],
|
|
171
|
-
site: config.site,
|
|
172
|
-
proxyUrl: config.proxyUrl,
|
|
279
|
+
site: config.site as any,
|
|
173
280
|
clientToken: config.clientToken,
|
|
174
281
|
applicationId: config.applicationId,
|
|
175
282
|
service: info.service,
|
|
176
283
|
env: info.environment,
|
|
177
284
|
version: config.version ?? info.version,
|
|
178
285
|
|
|
179
|
-
|
|
180
|
-
|
|
286
|
+
usePartitionedCrossSiteSessionCookie: config.usePartitionedCrossSiteSessionCookie,
|
|
287
|
+
// Use correct Datadog v6 SDK options
|
|
288
|
+
sessionSampleRate: config.sessionSampleRate ?? config.rumSampleRate ?? config.sampleRate ?? 100,
|
|
289
|
+
sessionReplaySampleRate: config.sessionReplaySampleRate?? config.replaySampleRate ?? 100,
|
|
290
|
+
|
|
291
|
+
// Track interactions (Note: trackFrustrations is not available in Datadog v6)
|
|
292
|
+
trackUserInteractions: config.trackUserInteractions ?? config.trackInteractions ?? false,
|
|
181
293
|
|
|
182
294
|
useSecureSessionCookie: config.useSecureSessionCookie ?? !isLocalhost,
|
|
183
|
-
|
|
295
|
+
// Note: useCrossSiteSessionCookie is not available in newer Datadog versions
|
|
184
296
|
trackSessionAcrossSubdomains: config.trackSessionAcrossSubdomains,
|
|
185
|
-
|
|
186
|
-
trackInteractions: config.trackInteractions ?? false,
|
|
187
|
-
trackFrustrations: config.trackFrustrations ?? false,
|
|
188
297
|
trackViewsManually: config.trackViewsManually ?? false,
|
|
189
298
|
actionNameAttribute: config.actionNameAttribute ?? 'data-analytics-name',
|
|
190
299
|
beforeSend: config.beforeSend,
|
|
191
300
|
|
|
192
301
|
defaultPrivacyLevel: config.defaultPrivacyLevel ?? 'mask-user-input',
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
302
|
+
allowedTracingUrls: config.allowedTracingUrls ?? config.allowedTrackingOrigins,
|
|
303
|
+
|
|
196
304
|
excludedActivityUrls: config.excludedActivityUrls,
|
|
197
305
|
});
|
|
198
306
|
|
|
307
|
+
// Initialize frustration detector
|
|
308
|
+
new DatadogFrustrationDetector(config);
|
|
309
|
+
|
|
199
310
|
const reporter: IReporter = {
|
|
200
311
|
trackEvent: function (event: ReporterEvent): void {
|
|
201
312
|
datadogRum.addAction(event.message, {
|
|
@@ -214,16 +325,16 @@ export function datadogReporter(info: ServiceInfo, config: DatadogReporterConfig
|
|
|
214
325
|
addMetadata: function (metadata: Metadata): void {
|
|
215
326
|
for (const [key, value] of Object.entries(metadata)) {
|
|
216
327
|
if (value !== null) {
|
|
217
|
-
datadogRum.
|
|
328
|
+
datadogRum.setGlobalContextProperty(key, value);
|
|
218
329
|
|
|
219
330
|
// Note, this will add duplicate context data in logs.
|
|
220
331
|
// But this is valuable for logs ingested outside of the browser logger.
|
|
221
|
-
datadogLogs.
|
|
332
|
+
datadogLogs.setGlobalContextProperty(key, value);
|
|
222
333
|
} else {
|
|
223
|
-
datadogRum.
|
|
334
|
+
datadogRum.removeGlobalContextProperty(key);
|
|
224
335
|
|
|
225
336
|
// But this is valuable for logs ingested outside of the browser logger.
|
|
226
|
-
datadogLogs.
|
|
337
|
+
datadogLogs.removeGlobalContextProperty(key);
|
|
227
338
|
}
|
|
228
339
|
}
|
|
229
340
|
},
|
|
@@ -236,7 +347,7 @@ export function datadogReporter(info: ServiceInfo, config: DatadogReporterConfig
|
|
|
236
347
|
name: user.name ?? user.email,
|
|
237
348
|
});
|
|
238
349
|
} else {
|
|
239
|
-
datadogRum.
|
|
350
|
+
datadogRum.setUser({});
|
|
240
351
|
}
|
|
241
352
|
},
|
|
242
353
|
setRouteName: function (routeName: string): void {
|
package/src/types/reporter.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { AmplifyReporterConfig } from '../reporters/amplifyReporter';
|
|
2
1
|
import { DatadogReporterConfig } from '../reporters/datadogReporter';
|
|
3
2
|
import { GTMReporterConfig } from '../reporters/gtmReporter';
|
|
4
3
|
import { LogReporterConfig } from '../reporters/logReporter';
|
|
@@ -18,12 +17,6 @@ export type ReporterConfigurations = {
|
|
|
18
17
|
*/
|
|
19
18
|
datadog?: DatadogReporterConfig;
|
|
20
19
|
|
|
21
|
-
/**
|
|
22
|
-
* Amplify/Pinpoint reporter configuration.
|
|
23
|
-
* If not set, the amplify reporter will not be used.
|
|
24
|
-
*/
|
|
25
|
-
amplify?: AmplifyReporterConfig;
|
|
26
|
-
|
|
27
20
|
/**
|
|
28
21
|
* PostHog reporter configuration.
|
|
29
22
|
* If not set, the PostHog reporter will not be used.
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { IReporter, Metadata, ReporterConfigBase, ServiceInfo } from '../types';
|
|
2
|
-
declare type AttributeMap = Record<string, string[] | string | null>;
|
|
3
|
-
declare type AmplifyAutoTrackSource = 'pageView' | 'event' | 'session';
|
|
4
|
-
export interface AmplifyReporterConfig extends ReporterConfigBase {
|
|
5
|
-
/**
|
|
6
|
-
* AWS Region for Amplify.
|
|
7
|
-
*/
|
|
8
|
-
region: string;
|
|
9
|
-
/**
|
|
10
|
-
* The Identity Pool Id to use for reporting, if set to false, Auth.configure is not called.
|
|
11
|
-
* This must be called manually for the reporter to work.
|
|
12
|
-
*/
|
|
13
|
-
identityPoolId: string | false;
|
|
14
|
-
/**
|
|
15
|
-
* The Pinpoint App Id to report to.
|
|
16
|
-
*/
|
|
17
|
-
analyticsAppId: string;
|
|
18
|
-
/** Optional proxy URL */
|
|
19
|
-
proxyUrl?: string;
|
|
20
|
-
/**
|
|
21
|
-
* The Cognito User Pool to configure in Auth.configure.
|
|
22
|
-
* If you are using Cognito, it is better to set identityPoolId to false and configure Auth manually.
|
|
23
|
-
*/
|
|
24
|
-
userPoolId?: string;
|
|
25
|
-
/**
|
|
26
|
-
* The Cognito Web Client Id to configure in Auth.configure.
|
|
27
|
-
* If you are using Cognito, it is better to set identityPoolId to false and configure Auth manually.
|
|
28
|
-
*/
|
|
29
|
-
userPoolWebClientId?: string;
|
|
30
|
-
/**
|
|
31
|
-
* If you want to track which page/url in your webapp is the most frequently viewed one, you can use this feature.
|
|
32
|
-
* It will automatically send events containing url information when the page is visited.
|
|
33
|
-
*/
|
|
34
|
-
autoTrackPageViews?: boolean;
|
|
35
|
-
/**
|
|
36
|
-
* If you want to track user interactions with elements on the page, you can use this feature.
|
|
37
|
-
* All you need to do is attach the specified selectors to your dom element and turn on the auto tracking.
|
|
38
|
-
*/
|
|
39
|
-
autoTrackEvents?: boolean;
|
|
40
|
-
/**
|
|
41
|
-
* A web session can be defined in different ways.
|
|
42
|
-
* To keep it simple we define that the web session is active when the page is not hidden and inactive when the page is hidden.
|
|
43
|
-
*/
|
|
44
|
-
autoTrackSessions?: boolean;
|
|
45
|
-
/**
|
|
46
|
-
* Optional function to run before autotracked analytics events are sent out.
|
|
47
|
-
* The returned metadata is attached to the event.
|
|
48
|
-
*/
|
|
49
|
-
beforeAutoTrack?(source: AmplifyAutoTrackSource): Metadata | undefined;
|
|
50
|
-
/**
|
|
51
|
-
* The data tag prefix to use for attributing HTML elements. Defaults to data-analytics-
|
|
52
|
-
*/
|
|
53
|
-
selectorPrefix?: string;
|
|
54
|
-
/**
|
|
55
|
-
* Modify how the reporter sends events to Amplify.
|
|
56
|
-
*/
|
|
57
|
-
buffering?: AmplifyReporterBufferingConfig;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Configuration options for the buffering behavior of Pinpoint's event tracker.
|
|
61
|
-
*
|
|
62
|
-
* @see https://docs.amplify.aws/lib/analytics/getting-started/q/platform/js/#set-up-existing-analytics-backend
|
|
63
|
-
*/
|
|
64
|
-
declare type AmplifyReporterBufferingConfig = {
|
|
65
|
-
/** Number of items to buffer for sending. */
|
|
66
|
-
bufferSize?: number;
|
|
67
|
-
/** Number of events sent each time Pinpoint flushes. */
|
|
68
|
-
flushSize?: number;
|
|
69
|
-
/** Interval Pinpoint flushes analytics events. Measured in milliseconds. */
|
|
70
|
-
flushInterval?: number;
|
|
71
|
-
/** The maximum number of times Pinpoint will retry to send an event. */
|
|
72
|
-
resendLimit?: number;
|
|
73
|
-
};
|
|
74
|
-
export declare function amplifyReporter(info: ServiceInfo, config: AmplifyReporterConfig): IReporter;
|
|
75
|
-
/**
|
|
76
|
-
* Pinpoint has strict attribute name and value length limits
|
|
77
|
-
*/
|
|
78
|
-
export declare function asAttributeMap(values: Record<string, unknown>, groupValues?: boolean): AttributeMap;
|
|
79
|
-
/**
|
|
80
|
-
* Pinpoint expects `endpoint.attributes` and `endpoint.userAttributes` to have
|
|
81
|
-
* values which are string arrays. This function takes in an object and ensures
|
|
82
|
-
* all of its values are of type `string[]` to appease Pinpoint.
|
|
83
|
-
*/
|
|
84
|
-
export declare function buildAttributeMap(values: Record<string, any>, parentKey?: string | undefined, groupValues?: boolean): AttributeMap;
|
|
85
|
-
export {};
|
|
86
|
-
//# sourceMappingURL=amplifyReporter.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"amplifyReporter.d.ts","sourceRoot":"","sources":["../../src/reporters/amplifyReporter.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,SAAS,EACT,QAAQ,EAER,kBAAkB,EAGlB,WAAW,EACZ,MAAM,UAAU,CAAC;AAIlB,aAAK,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAAC,CAAC;AAE7D,aAAK,sBAAsB,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,CAAC;AAE/D,MAAM,WAAW,qBAAsB,SAAQ,kBAAkB;IAC/D;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,cAAc,EAAE,MAAM,GAAG,KAAK,CAAC;IAC/B;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB,yBAAyB;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;OAGG;IACH,eAAe,CAAC,CAAC,MAAM,EAAE,sBAAsB,GAAG,QAAQ,GAAG,SAAS,CAAC;IAEvE;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;OAEG;IACH,SAAS,CAAC,EAAE,8BAA8B,CAAC;CAC5C;AAED;;;;GAIG;AACH,aAAK,8BAA8B,GAAG;IACpC,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,qBAAqB,GAAG,SAAS,CA8H3F;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,UAAO,GAAG,YAAY,CAqBhG;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC3B,SAAS,GAAE,MAAM,GAAG,SAAqB,EACzC,WAAW,UAAO,GACjB,YAAY,CAsBd"}
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.buildAttributeMap = exports.asAttributeMap = exports.amplifyReporter = void 0;
|
|
4
|
-
const auth_1 = require("@aws-amplify/auth");
|
|
5
|
-
const analytics_1 = require("@aws-amplify/analytics");
|
|
6
|
-
const protocol_http_1 = require("@aws-sdk/protocol-http");
|
|
7
|
-
const logger_1 = require("../logger");
|
|
8
|
-
function amplifyReporter(info, config) {
|
|
9
|
-
var _a;
|
|
10
|
-
if (config.identityPoolId !== false) {
|
|
11
|
-
auth_1.Auth.configure({
|
|
12
|
-
region: config.region,
|
|
13
|
-
identityPoolId: config.identityPoolId,
|
|
14
|
-
userPoolId: config.userPoolId,
|
|
15
|
-
userPoolWebClientId: config.userPoolWebClientId,
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
const wrapAutoTrackMiddleware = (source) => {
|
|
19
|
-
const { beforeAutoTrack } = config;
|
|
20
|
-
return typeof beforeAutoTrack === 'function' ? () => { var _a; return (_a = beforeAutoTrack(source)) !== null && _a !== void 0 ? _a : {}; } : undefined;
|
|
21
|
-
};
|
|
22
|
-
const allMetadata = asAttributeMap({
|
|
23
|
-
appName: info.service,
|
|
24
|
-
service: info.service,
|
|
25
|
-
domain: window.location.host,
|
|
26
|
-
environment: info.environment,
|
|
27
|
-
version: info.version,
|
|
28
|
-
});
|
|
29
|
-
analytics_1.Analytics.configure({
|
|
30
|
-
region: config.region,
|
|
31
|
-
appId: config.analyticsAppId,
|
|
32
|
-
endpoint: {
|
|
33
|
-
attributes: allMetadata,
|
|
34
|
-
},
|
|
35
|
-
...config.buffering,
|
|
36
|
-
});
|
|
37
|
-
// Session autotracking is enabled by default for backwards compatibility reasons, so we _must_
|
|
38
|
-
// call this unconditionally to ensure we opt out of session tracking when `autoTrackSessions` isn't set
|
|
39
|
-
// See: https://docs.amplify.aws/lib/analytics/autotrack/q/platform/js/#session-tracking
|
|
40
|
-
analytics_1.Analytics.autoTrack('session', {
|
|
41
|
-
enable: config.autoTrackSessions === true,
|
|
42
|
-
attributes: wrapAutoTrackMiddleware('session'),
|
|
43
|
-
});
|
|
44
|
-
if (config.autoTrackPageViews) {
|
|
45
|
-
analytics_1.Analytics.autoTrack('pageView', {
|
|
46
|
-
enable: true,
|
|
47
|
-
eventName: 'pageView',
|
|
48
|
-
type: 'SPA',
|
|
49
|
-
provider: 'AWSPinpoint',
|
|
50
|
-
attributes: wrapAutoTrackMiddleware('pageView'),
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
if (config.autoTrackEvents) {
|
|
54
|
-
analytics_1.Analytics.autoTrack('event', {
|
|
55
|
-
enable: true,
|
|
56
|
-
selectorPrefix: (_a = config.selectorPrefix) !== null && _a !== void 0 ? _a : 'data-analytics-',
|
|
57
|
-
attributes: wrapAutoTrackMiddleware('event'),
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
if (config.proxyUrl) {
|
|
61
|
-
installPinpointProxy(new URL(config.proxyUrl));
|
|
62
|
-
}
|
|
63
|
-
const reporter = {
|
|
64
|
-
trackEvent: function (event) {
|
|
65
|
-
analytics_1.Analytics.record({
|
|
66
|
-
name: event.message,
|
|
67
|
-
attributes: asAttributeMap({
|
|
68
|
-
...event.metadata,
|
|
69
|
-
...event.tags,
|
|
70
|
-
}, false),
|
|
71
|
-
metrics: event.metrics,
|
|
72
|
-
}).catch(() => {
|
|
73
|
-
// Swallow; see: https://crimsonhq.slack.com/archives/G4UN6Q4KF/p1648599302847539
|
|
74
|
-
});
|
|
75
|
-
},
|
|
76
|
-
addBreadcrumb: function (breadcrumb) {
|
|
77
|
-
reporter.trackEvent({
|
|
78
|
-
message: breadcrumb.message,
|
|
79
|
-
metadata: {
|
|
80
|
-
category: breadcrumb.category,
|
|
81
|
-
...breadcrumb.metadata,
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
},
|
|
85
|
-
addMetadata: function (metadata) {
|
|
86
|
-
Object.assign(allMetadata, asAttributeMap(metadata, true));
|
|
87
|
-
analytics_1.Analytics.updateEndpoint({
|
|
88
|
-
attributes: allMetadata,
|
|
89
|
-
}).catch(() => {
|
|
90
|
-
// Swallow; see: https://crimsonhq.slack.com/archives/G4UN6Q4KF/p1648599302847539
|
|
91
|
-
});
|
|
92
|
-
},
|
|
93
|
-
setUser: function (user) {
|
|
94
|
-
var _a, _b;
|
|
95
|
-
const userMetadata = user
|
|
96
|
-
? asAttributeMap({
|
|
97
|
-
userId: user.id,
|
|
98
|
-
})
|
|
99
|
-
: {};
|
|
100
|
-
Object.assign(allMetadata, asAttributeMap(userMetadata, true));
|
|
101
|
-
analytics_1.Analytics.updateEndpoint({
|
|
102
|
-
userId: (_a = user === null || user === void 0 ? void 0 : user.id) !== null && _a !== void 0 ? _a : '',
|
|
103
|
-
attributes: allMetadata,
|
|
104
|
-
userAttributes: user
|
|
105
|
-
? asAttributeMap({
|
|
106
|
-
id: user.id,
|
|
107
|
-
email: user.email,
|
|
108
|
-
name: (_b = user.name) !== null && _b !== void 0 ? _b : user.email,
|
|
109
|
-
username: user.username,
|
|
110
|
-
})
|
|
111
|
-
: {},
|
|
112
|
-
}).catch(() => {
|
|
113
|
-
// Swallow; see: https://crimsonhq.slack.com/archives/G4UN6Q4KF/p1648599302847539
|
|
114
|
-
});
|
|
115
|
-
},
|
|
116
|
-
setRouteName: function (routeName) {
|
|
117
|
-
reporter.addMetadata({ routeName });
|
|
118
|
-
},
|
|
119
|
-
setPageName: function (pageName) {
|
|
120
|
-
reporter.addMetadata({ pageName });
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
return reporter;
|
|
124
|
-
}
|
|
125
|
-
exports.amplifyReporter = amplifyReporter;
|
|
126
|
-
/**
|
|
127
|
-
* Pinpoint has strict attribute name and value length limits
|
|
128
|
-
*/
|
|
129
|
-
function asAttributeMap(values, groupValues = true) {
|
|
130
|
-
const attributeMap = buildAttributeMap(values, undefined, groupValues);
|
|
131
|
-
const checkedEntries = Object.entries(attributeMap).map(([key, value]) => {
|
|
132
|
-
var _a, _b;
|
|
133
|
-
const truncatedKey = key.length > 50 ? `___${key.slice(-47)}` : key;
|
|
134
|
-
const truncatedValue = Array.isArray(value)
|
|
135
|
-
? (_a = value === null || value === void 0 ? void 0 : value.map((val) => val.slice(0, 100))) !== null && _a !== void 0 ? _a : null
|
|
136
|
-
: (_b = value === null || value === void 0 ? void 0 : value.slice(0, 100)) !== null && _b !== void 0 ? _b : null;
|
|
137
|
-
return [truncatedKey, truncatedValue];
|
|
138
|
-
});
|
|
139
|
-
// Pinpoint only accepts 40 attributes
|
|
140
|
-
if (checkedEntries.length > 40) {
|
|
141
|
-
logger_1.logger.error(`Amplify only allows 40 attributes per event, truncating to 40 attributes`, {
|
|
142
|
-
attributes: checkedEntries,
|
|
143
|
-
});
|
|
144
|
-
checkedEntries.length = 40;
|
|
145
|
-
}
|
|
146
|
-
return Object.fromEntries(checkedEntries);
|
|
147
|
-
}
|
|
148
|
-
exports.asAttributeMap = asAttributeMap;
|
|
149
|
-
/**
|
|
150
|
-
* Pinpoint expects `endpoint.attributes` and `endpoint.userAttributes` to have
|
|
151
|
-
* values which are string arrays. This function takes in an object and ensures
|
|
152
|
-
* all of its values are of type `string[]` to appease Pinpoint.
|
|
153
|
-
*/
|
|
154
|
-
function buildAttributeMap(values, parentKey = undefined, groupValues = true) {
|
|
155
|
-
const valuesWithStringArrays = {};
|
|
156
|
-
Object.entries(values).forEach(([key, value]) => {
|
|
157
|
-
const combinedKey = parentKey ? [parentKey, key].join('.') : key;
|
|
158
|
-
if (!value) {
|
|
159
|
-
valuesWithStringArrays[combinedKey] = null;
|
|
160
|
-
}
|
|
161
|
-
else if (groupValues && Array.isArray(value)) {
|
|
162
|
-
valuesWithStringArrays[combinedKey] = value.map((element) => typeof element === 'string' ? element : JSON.stringify(element));
|
|
163
|
-
}
|
|
164
|
-
else if (typeof value === 'object') {
|
|
165
|
-
const flattenedAttribute = buildAttributeMap(value, combinedKey, groupValues);
|
|
166
|
-
Object.assign(valuesWithStringArrays, flattenedAttribute);
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
170
|
-
valuesWithStringArrays[combinedKey] = groupValues ? [stringValue] : stringValue;
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
return valuesWithStringArrays;
|
|
174
|
-
}
|
|
175
|
-
exports.buildAttributeMap = buildAttributeMap;
|
|
176
|
-
function installPinpointProxy(proxyUrl) {
|
|
177
|
-
// No public API for overriding where the Pinpoint client sends events to... 🤮
|
|
178
|
-
// In theory you can pass in an `endpoint` to the Pinpoint client's constructor like any other AWS
|
|
179
|
-
// client, but Amplify's analytics doesn't expose anything we can use to get an endpoint threaded
|
|
180
|
-
// down to the Pinpoint client's constructor.
|
|
181
|
-
//
|
|
182
|
-
// The Pinpoint client _also_ isn't available synchronously because it is instantiated when events
|
|
183
|
-
// get sent out, and then reconfigured whenever the API credentials change. We need to hook `_initClients`
|
|
184
|
-
// to ensure that the Pinpoint client being used is always patched with our custom endpoint.
|
|
185
|
-
const provider = analytics_1.Analytics.getPluggable('AWSPinpoint');
|
|
186
|
-
if (!provider || typeof provider._initClients !== 'function') {
|
|
187
|
-
logger_1.logger.error('Installation of the Pinpoint proxy failed. This likely means the internals of the @aws-amplify/analytics package have changed.');
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
const originalInitClients = provider._initClients;
|
|
191
|
-
const requestMiddleware = (next) => async (args) => {
|
|
192
|
-
const pinpointClient = provider.pinpointClient;
|
|
193
|
-
if (pinpointClient && proxyUrl.pathname !== '/' && protocol_http_1.HttpRequest.isInstance(args.request)) {
|
|
194
|
-
// Add proxyUrl.pathname to final request url if it was provided
|
|
195
|
-
const shouldStripSlash = proxyUrl.pathname.endsWith('/');
|
|
196
|
-
args.request.path = `${proxyUrl.pathname}${args.request.path.slice(shouldStripSlash ? 1 : 0)}`;
|
|
197
|
-
// Wrap request body so the proxy has signing info
|
|
198
|
-
if (typeof args.request.body === 'string') {
|
|
199
|
-
const credentials = await pinpointClient.config.credentials();
|
|
200
|
-
args.request.body = JSON.stringify({
|
|
201
|
-
credentials,
|
|
202
|
-
data: JSON.parse(args.request.body),
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return next(args);
|
|
207
|
-
};
|
|
208
|
-
provider._initClients = async (credentials) => {
|
|
209
|
-
const result = await originalInitClients.call(provider, credentials);
|
|
210
|
-
const pinpointClient = provider.pinpointClient;
|
|
211
|
-
if (pinpointClient) {
|
|
212
|
-
pinpointClient.config.endpoint = () => Promise.resolve({
|
|
213
|
-
hostname: proxyUrl.hostname,
|
|
214
|
-
// Passing proxyUrl.pathname here doesn't work; it gets overridden
|
|
215
|
-
path: '/',
|
|
216
|
-
port: undefined,
|
|
217
|
-
protocol: proxyUrl.protocol,
|
|
218
|
-
});
|
|
219
|
-
pinpointClient.middlewareStack.remove(requestMiddleware);
|
|
220
|
-
pinpointClient.middlewareStack.add(requestMiddleware, { step: 'finalizeRequest' });
|
|
221
|
-
}
|
|
222
|
-
return result;
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
//# sourceMappingURL=amplifyReporter.js.map
|