@flisk/analyze-tracking 0.8.5 → 0.8.7
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 +52 -11
- package/package.json +1 -1
- package/schema.json +2 -0
- package/src/analyze/javascript/constants.js +12 -0
- package/src/analyze/javascript/detectors/analytics-source.js +10 -1
- package/src/analyze/javascript/extractors/event-extractor.js +38 -0
- package/src/analyze/typescript/constants.js +12 -0
- package/src/analyze/typescript/detectors/analytics-source.js +10 -1
- package/src/analyze/typescript/extractors/event-extractor.js +55 -0
package/README.md
CHANGED
|
@@ -101,17 +101,19 @@ See [schema.json](schema.json) for a JSON Schema of the output.
|
|
|
101
101
|
|
|
102
102
|
| Library | JavaScript/TypeScript | Python | Ruby | Go |
|
|
103
103
|
|---------|:---------------------:|:------:|:----:|:--:|
|
|
104
|
-
| Google Analytics
|
|
105
|
-
|
|
|
106
|
-
|
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
|
|
|
111
|
-
|
|
|
112
|
-
|
|
|
113
|
-
|
|
|
114
|
-
|
|
|
104
|
+
| Google Analytics | ✅ | ❌ | ❌ | ❌ |
|
|
105
|
+
| Google Tag Manager | ✅ | ❌ | ❌ | ❌ |
|
|
106
|
+
| Segment | ✅ | ✅ | ✅ | ✅ |
|
|
107
|
+
| Mixpanel | ✅ | ✅ | ✅ | ✅ |
|
|
108
|
+
| Amplitude | ✅ | ✅ | ❌ | ✅ |
|
|
109
|
+
| Rudderstack | ✅ | ✅ | ✳️ | ✳️ |
|
|
110
|
+
| mParticle | ✅ | ❌ | ❌ | ❌ |
|
|
111
|
+
| PostHog | ✅ | ✅ | ✅ | ✅ |
|
|
112
|
+
| Pendo | ✅ | ❌ | ❌ | ❌ |
|
|
113
|
+
| Heap | ✅ | ❌ | ❌ | ❌ |
|
|
114
|
+
| Snowplow | ✅ | ✅ | ✅ | ✅ |
|
|
115
|
+
| Datadog RUM | ✅ | ❌ | ❌ | ❌ |
|
|
116
|
+
| Custom Function | ✅ | ✅ | ✅ | ✅ |
|
|
115
117
|
|
|
116
118
|
✳️ Rudderstack's SDKs often use the same format as Segment, so Rudderstack events may be detected as Segment events.
|
|
117
119
|
|
|
@@ -129,6 +131,24 @@ See [schema.json](schema.json) for a JSON Schema of the output.
|
|
|
129
131
|
```
|
|
130
132
|
</details>
|
|
131
133
|
|
|
134
|
+
<details>
|
|
135
|
+
<summary>Google Tag Manager</summary>
|
|
136
|
+
|
|
137
|
+
**JavaScript/TypeScript**
|
|
138
|
+
```js
|
|
139
|
+
dataLayer.push({
|
|
140
|
+
event: '<event_name>',
|
|
141
|
+
'<property_name>': '<property_value>'
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Or via window
|
|
145
|
+
window.dataLayer.push({
|
|
146
|
+
event: '<event_name>',
|
|
147
|
+
'<property_name>': '<property_value>'
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
</details>
|
|
151
|
+
|
|
132
152
|
<details>
|
|
133
153
|
<summary>Segment</summary>
|
|
134
154
|
|
|
@@ -375,6 +395,27 @@ See [schema.json](schema.json) for a JSON Schema of the output.
|
|
|
375
395
|
|
|
376
396
|
</details>
|
|
377
397
|
|
|
398
|
+
<details>
|
|
399
|
+
<summary>Datadog RUM</summary>
|
|
400
|
+
|
|
401
|
+
**JavaScript/TypeScript**
|
|
402
|
+
```js
|
|
403
|
+
datadogRum.addAction('<event_name>', {
|
|
404
|
+
'<property_name>': '<property_value>'
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// Or via window
|
|
408
|
+
window.DD_RUM.addAction('<event_name>', {
|
|
409
|
+
'<property_name>': '<property_value>'
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// Or via global DD_RUM
|
|
413
|
+
DD_RUM.addAction('<event_name>', {
|
|
414
|
+
'<property_name>': '<property_value>'
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
</details>
|
|
418
|
+
|
|
378
419
|
<details>
|
|
379
420
|
<summary>Snowplow (Structured Events)</summary>
|
|
380
421
|
|
package/package.json
CHANGED
package/schema.json
CHANGED
|
@@ -75,6 +75,18 @@ const ANALYTICS_PROVIDERS = {
|
|
|
75
75
|
name: 'googleanalytics',
|
|
76
76
|
functionName: 'gtag',
|
|
77
77
|
type: 'function'
|
|
78
|
+
},
|
|
79
|
+
DATADOG_RUM: {
|
|
80
|
+
name: 'datadog',
|
|
81
|
+
objectNames: ['datadogRum', 'DD_RUM'],
|
|
82
|
+
methodName: 'addAction',
|
|
83
|
+
type: 'member'
|
|
84
|
+
},
|
|
85
|
+
GOOGLE_TAG_MANAGER: {
|
|
86
|
+
name: 'gtm',
|
|
87
|
+
objectNames: ['dataLayer'],
|
|
88
|
+
methodName: 'push',
|
|
89
|
+
type: 'member'
|
|
78
90
|
}
|
|
79
91
|
};
|
|
80
92
|
|
|
@@ -131,8 +131,17 @@ function detectMemberBasedProvider(node) {
|
|
|
131
131
|
return 'unknown';
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
const objectName = node.callee.object.name;
|
|
135
134
|
const methodName = node.callee.property.name;
|
|
135
|
+
let objectName = node.callee.object.name;
|
|
136
|
+
|
|
137
|
+
// Handle nested member expressions like window.DD_RUM.addAction
|
|
138
|
+
if (!objectName && node.callee.object.type === NODE_TYPES.MEMBER_EXPRESSION) {
|
|
139
|
+
// For window.DD_RUM.addAction, we want to check if it matches DD_RUM.addAction pattern
|
|
140
|
+
const nestedObjectName = node.callee.object.property.name;
|
|
141
|
+
if (nestedObjectName) {
|
|
142
|
+
objectName = nestedObjectName;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
136
145
|
|
|
137
146
|
if (!objectName || !methodName) {
|
|
138
147
|
return 'unknown';
|
|
@@ -20,6 +20,7 @@ const EXTRACTION_STRATEGIES = {
|
|
|
20
20
|
googleanalytics: extractGoogleAnalyticsEvent,
|
|
21
21
|
snowplow: extractSnowplowEvent,
|
|
22
22
|
mparticle: extractMparticleEvent,
|
|
23
|
+
gtm: extractGTMEvent,
|
|
23
24
|
custom: extractCustomEvent,
|
|
24
25
|
default: extractDefaultEvent
|
|
25
26
|
};
|
|
@@ -105,6 +106,43 @@ function extractMparticleEvent(node, constantMap) {
|
|
|
105
106
|
return { eventName, propertiesNode };
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Extracts Google Tag Manager event data
|
|
111
|
+
* @param {Object} node - CallExpression node
|
|
112
|
+
* @param {Object} constantMap - Collected constant map
|
|
113
|
+
* @returns {EventData}
|
|
114
|
+
*/
|
|
115
|
+
function extractGTMEvent(node, constantMap) {
|
|
116
|
+
if (!node.arguments || node.arguments.length === 0) {
|
|
117
|
+
return { eventName: null, propertiesNode: null };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// dataLayer.push({ event: 'event_name', property1: 'value1', property2: 'value2' })
|
|
121
|
+
const firstArg = node.arguments[0];
|
|
122
|
+
|
|
123
|
+
if (firstArg.type !== NODE_TYPES.OBJECT_EXPRESSION) {
|
|
124
|
+
return { eventName: null, propertiesNode: null };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Find the 'event' property
|
|
128
|
+
const eventProperty = findPropertyByKey(firstArg, 'event');
|
|
129
|
+
if (!eventProperty) {
|
|
130
|
+
return { eventName: null, propertiesNode: null };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const eventName = getStringValue(eventProperty.value, constantMap);
|
|
134
|
+
|
|
135
|
+
// Create a modified properties node without the 'event' property
|
|
136
|
+
const modifiedPropertiesNode = {
|
|
137
|
+
...firstArg,
|
|
138
|
+
properties: firstArg.properties.filter(prop =>
|
|
139
|
+
prop.key && (prop.key.name !== 'event' && prop.key.value !== 'event')
|
|
140
|
+
)
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
return { eventName, propertiesNode: modifiedPropertiesNode };
|
|
144
|
+
}
|
|
145
|
+
|
|
108
146
|
/**
|
|
109
147
|
* Default event extraction for standard providers
|
|
110
148
|
* @param {Object} node - CallExpression node
|
|
@@ -75,6 +75,18 @@ const ANALYTICS_PROVIDERS = {
|
|
|
75
75
|
name: 'googleanalytics',
|
|
76
76
|
functionName: 'gtag',
|
|
77
77
|
type: 'function'
|
|
78
|
+
},
|
|
79
|
+
DATADOG_RUM: {
|
|
80
|
+
name: 'datadog',
|
|
81
|
+
objectNames: ['datadogRum', 'DD_RUM'],
|
|
82
|
+
methodName: 'addAction',
|
|
83
|
+
type: 'member'
|
|
84
|
+
},
|
|
85
|
+
GOOGLE_TAG_MANAGER: {
|
|
86
|
+
name: 'gtm',
|
|
87
|
+
objectNames: ['dataLayer'],
|
|
88
|
+
methodName: 'push',
|
|
89
|
+
type: 'member'
|
|
78
90
|
}
|
|
79
91
|
};
|
|
80
92
|
|
|
@@ -87,8 +87,17 @@ function detectMemberBasedProvider(node) {
|
|
|
87
87
|
return 'unknown';
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
const objectName = node.expression.expression?.escapedText;
|
|
91
90
|
const methodName = node.expression.name?.escapedText;
|
|
91
|
+
let objectName = node.expression.expression?.escapedText;
|
|
92
|
+
|
|
93
|
+
// Handle nested member expressions like window.DD_RUM.addAction
|
|
94
|
+
if (!objectName && ts.isPropertyAccessExpression(node.expression.expression)) {
|
|
95
|
+
// For window.DD_RUM.addAction, we want to check if it matches DD_RUM.addAction pattern
|
|
96
|
+
const nestedObjectName = node.expression.expression.name?.escapedText;
|
|
97
|
+
if (nestedObjectName) {
|
|
98
|
+
objectName = nestedObjectName;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
92
101
|
|
|
93
102
|
if (!objectName || !methodName) {
|
|
94
103
|
return 'unknown';
|
|
@@ -21,6 +21,7 @@ const EXTRACTION_STRATEGIES = {
|
|
|
21
21
|
googleanalytics: extractGoogleAnalyticsEvent,
|
|
22
22
|
snowplow: extractSnowplowEvent,
|
|
23
23
|
mparticle: extractMparticleEvent,
|
|
24
|
+
gtm: extractGTMEvent,
|
|
24
25
|
custom: extractCustomEvent,
|
|
25
26
|
default: extractDefaultEvent
|
|
26
27
|
};
|
|
@@ -126,6 +127,60 @@ function extractMparticleEvent(node, checker, sourceFile) {
|
|
|
126
127
|
return { eventName, propertiesNode };
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Extracts Google Tag Manager event data
|
|
132
|
+
* @param {Object} node - CallExpression node
|
|
133
|
+
* @param {Object} checker - TypeScript type checker
|
|
134
|
+
* @param {Object} sourceFile - TypeScript source file
|
|
135
|
+
* @returns {EventData}
|
|
136
|
+
*/
|
|
137
|
+
function extractGTMEvent(node, checker, sourceFile) {
|
|
138
|
+
if (!node.arguments || node.arguments.length === 0) {
|
|
139
|
+
return { eventName: null, propertiesNode: null };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// dataLayer.push({ event: 'event_name', property1: 'value1', property2: 'value2' })
|
|
143
|
+
const firstArg = node.arguments[0];
|
|
144
|
+
|
|
145
|
+
if (!ts.isObjectLiteralExpression(firstArg)) {
|
|
146
|
+
return { eventName: null, propertiesNode: null };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Find the 'event' property
|
|
150
|
+
const eventProperty = findPropertyByKey(firstArg, 'event');
|
|
151
|
+
if (!eventProperty) {
|
|
152
|
+
return { eventName: null, propertiesNode: null };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const eventName = getStringValue(eventProperty.initializer, checker, sourceFile);
|
|
156
|
+
|
|
157
|
+
// Create a modified properties node without the 'event' property
|
|
158
|
+
const modifiedProperties = firstArg.properties.filter(prop => {
|
|
159
|
+
if (ts.isPropertyAssignment(prop) && prop.name) {
|
|
160
|
+
if (ts.isIdentifier(prop.name)) {
|
|
161
|
+
return prop.name.escapedText !== 'event';
|
|
162
|
+
}
|
|
163
|
+
if (ts.isStringLiteral(prop.name)) {
|
|
164
|
+
return prop.name.text !== 'event';
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Create a synthetic object literal with the filtered properties
|
|
171
|
+
const modifiedPropertiesNode = ts.factory.createObjectLiteralExpression(modifiedProperties);
|
|
172
|
+
|
|
173
|
+
// Copy source positions for proper analysis
|
|
174
|
+
if (firstArg.pos !== undefined) {
|
|
175
|
+
modifiedPropertiesNode.pos = firstArg.pos;
|
|
176
|
+
}
|
|
177
|
+
if (firstArg.end !== undefined) {
|
|
178
|
+
modifiedPropertiesNode.end = firstArg.end;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return { eventName, propertiesNode: modifiedPropertiesNode };
|
|
182
|
+
}
|
|
183
|
+
|
|
129
184
|
/**
|
|
130
185
|
* Custom extraction
|
|
131
186
|
* @param {Object} node - CallExpression node
|