@brainfish-ai/web-tracker 0.0.2-alpha.2 → 0.0.4-alpha.4
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/dist/index.js +1 -1
- package/package.json +2 -2
- package/src/index.ts +2 -190
- package/src/tracker.ts +192 -0
- package/src/types.d.ts +31 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainfish-ai/web-tracker",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4-alpha.4",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"description": "Brainfish Tracker for Web",
|
|
6
6
|
"private": false,
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"vite-tsconfig-paths": "^4.2.0"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@brainfish-ai/tracker-sdk": "
|
|
25
|
+
"@brainfish-ai/tracker-sdk": "0.0.1-alpha.2",
|
|
26
26
|
"html-to-image": "^1.11.11",
|
|
27
27
|
"redact-pii": "^3.4.0",
|
|
28
28
|
"rrweb": "^2.0.0-alpha.4"
|
package/src/index.ts
CHANGED
|
@@ -1,190 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
TrackerSdkOptions,
|
|
5
|
-
TrackProperties,
|
|
6
|
-
} from '@brainfish-ai/tracker-sdk';
|
|
7
|
-
import { TrackerSdk } from '@brainfish-ai/tracker-sdk';
|
|
8
|
-
import { screenshot } from './utils/snapshot';
|
|
9
|
-
|
|
10
|
-
export type * from '@brainfish-ai/tracker-sdk';
|
|
11
|
-
|
|
12
|
-
export type TrackerOptions = TrackerSdkOptions & {
|
|
13
|
-
trackOutgoingLinks?: boolean;
|
|
14
|
-
trackScreenViews?: boolean;
|
|
15
|
-
trackAttributes?: boolean;
|
|
16
|
-
trackHashChanges?: boolean;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
function toCamelCase(str: string) {
|
|
20
|
-
return str.replace(/([-_][a-z])/gi, ($1) =>
|
|
21
|
-
$1.toUpperCase().replace('-', '').replace('_', ''),
|
|
22
|
-
);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export class Tracker extends TrackerSdk {
|
|
26
|
-
private lastPath = '';
|
|
27
|
-
private debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
28
|
-
|
|
29
|
-
constructor(public options: TrackerOptions) {
|
|
30
|
-
super({
|
|
31
|
-
sdk: 'web',
|
|
32
|
-
sdkVersion: __PACKAGE_VERSION__,
|
|
33
|
-
...options,
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
if (!this.isServer()) {
|
|
37
|
-
this.setGlobalProperties({
|
|
38
|
-
__referrer: document.referrer,
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
if (this.options.trackOutgoingLinks) {
|
|
42
|
-
this.trackOutgoingLinks();
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (this.options.trackScreenViews) {
|
|
46
|
-
this.trackScreenViews();
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (this.options.trackAttributes) {
|
|
50
|
-
this.trackAttributes();
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
private debounce(func: () => void, delay: number) {
|
|
56
|
-
this.debounceTimer && clearTimeout(this.debounceTimer);
|
|
57
|
-
this.debounceTimer = setTimeout(func, delay);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
private isServer() {
|
|
61
|
-
return typeof document === 'undefined';
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
public trackOutgoingLinks() {
|
|
65
|
-
if (this.isServer()) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
document.addEventListener('click', (event) => {
|
|
70
|
-
const target = event.target as HTMLElement;
|
|
71
|
-
const link = target.closest('a');
|
|
72
|
-
if (link && target) {
|
|
73
|
-
const href = link.getAttribute('href');
|
|
74
|
-
if (href?.startsWith('http')) {
|
|
75
|
-
super.track('link_out', {
|
|
76
|
-
href,
|
|
77
|
-
text:
|
|
78
|
-
link.innerText ||
|
|
79
|
-
link.getAttribute('title') ||
|
|
80
|
-
target.getAttribute('alt') ||
|
|
81
|
-
target.getAttribute('title'),
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
public trackScreenViews() {
|
|
89
|
-
if (this.isServer()) {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
this.screenView();
|
|
94
|
-
|
|
95
|
-
const oldPushState = history.pushState;
|
|
96
|
-
history.pushState = function pushState(...args) {
|
|
97
|
-
const ret = oldPushState.apply(this, args);
|
|
98
|
-
window.dispatchEvent(new Event('pushstate'));
|
|
99
|
-
window.dispatchEvent(new Event('locationchange'));
|
|
100
|
-
return ret;
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const oldReplaceState = history.replaceState;
|
|
104
|
-
history.replaceState = function replaceState(...args) {
|
|
105
|
-
const ret = oldReplaceState.apply(this, args);
|
|
106
|
-
window.dispatchEvent(new Event('replacestate'));
|
|
107
|
-
window.dispatchEvent(new Event('locationchange'));
|
|
108
|
-
return ret;
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
window.addEventListener('popstate', function () {
|
|
112
|
-
window.dispatchEvent(new Event('locationchange'));
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
const eventHandler = () => this.debounce(() => this.screenView(), 50);
|
|
116
|
-
|
|
117
|
-
if (this.options.trackHashChanges) {
|
|
118
|
-
window.addEventListener('hashchange', eventHandler);
|
|
119
|
-
} else {
|
|
120
|
-
window.addEventListener('locationchange', eventHandler);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
public trackAttributes() {
|
|
125
|
-
if (this.isServer()) {
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
document.addEventListener('click', (event) => {
|
|
130
|
-
const target = event.target as HTMLElement;
|
|
131
|
-
const btn = target.closest('button');
|
|
132
|
-
const anchor = target.closest('a');
|
|
133
|
-
const element = btn?.getAttribute('data-track')
|
|
134
|
-
? btn
|
|
135
|
-
: anchor?.getAttribute('data-track')
|
|
136
|
-
? anchor
|
|
137
|
-
: null;
|
|
138
|
-
if (element) {
|
|
139
|
-
const properties: Record<string, unknown> = {};
|
|
140
|
-
for (const attr of element.attributes) {
|
|
141
|
-
if (attr.name.startsWith('data-') && attr.name !== 'data-track') {
|
|
142
|
-
properties[toCamelCase(attr.name.replace(/^data-/, ''))] =
|
|
143
|
-
attr.value;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
const name = element.getAttribute('data-track');
|
|
147
|
-
if (name) {
|
|
148
|
-
super.track(name, properties);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
async screenView(properties?: TrackProperties): Promise<void>;
|
|
155
|
-
async screenView(path: string, properties?: TrackProperties): Promise<void>;
|
|
156
|
-
async screenView(
|
|
157
|
-
pathOrProperties?: string | TrackProperties,
|
|
158
|
-
propertiesOrUndefined?: TrackProperties,
|
|
159
|
-
): Promise<void> {
|
|
160
|
-
if (this.isServer()) {
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
let path: string;
|
|
165
|
-
let properties: TrackProperties | undefined;
|
|
166
|
-
|
|
167
|
-
if (typeof pathOrProperties === 'string') {
|
|
168
|
-
path = pathOrProperties;
|
|
169
|
-
properties = propertiesOrUndefined;
|
|
170
|
-
} else {
|
|
171
|
-
path = window.location.href;
|
|
172
|
-
properties = pathOrProperties;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (this.lastPath === path) {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// capture screenshot
|
|
180
|
-
const snapshot = await screenshot();
|
|
181
|
-
|
|
182
|
-
this.lastPath = path;
|
|
183
|
-
super.track('screen_view', {
|
|
184
|
-
...(properties ?? {}),
|
|
185
|
-
screenshot: snapshot,
|
|
186
|
-
__path: path,
|
|
187
|
-
__title: document.title,
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
}
|
|
1
|
+
export * from './tracker';
|
|
2
|
+
export * from './types.d';
|
package/src/tracker.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
TrackerSdkOptions,
|
|
5
|
+
TrackProperties,
|
|
6
|
+
} from '@brainfish-ai/tracker-sdk';
|
|
7
|
+
import { TrackerSdk } from '@brainfish-ai/tracker-sdk';
|
|
8
|
+
import { screenshot } from './utils/snapshot';
|
|
9
|
+
|
|
10
|
+
export type * from '@brainfish-ai/tracker-sdk';
|
|
11
|
+
|
|
12
|
+
export type TrackerOptions = TrackerSdkOptions & {
|
|
13
|
+
trackOutgoingLinks?: boolean;
|
|
14
|
+
trackScreenViews?: boolean;
|
|
15
|
+
trackAttributes?: boolean;
|
|
16
|
+
trackHashChanges?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function toCamelCase(str: string) {
|
|
20
|
+
return str.replace(/([-_][a-z])/gi, ($1) =>
|
|
21
|
+
$1.toUpperCase().replace('-', '').replace('_', ''),
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class Tracker extends TrackerSdk {
|
|
26
|
+
private lastPath = '';
|
|
27
|
+
private debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
28
|
+
static mock: any;
|
|
29
|
+
|
|
30
|
+
constructor(public options: TrackerOptions) {
|
|
31
|
+
super({
|
|
32
|
+
sdk: 'web',
|
|
33
|
+
sdkVersion: __PACKAGE_VERSION__,
|
|
34
|
+
...options,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (!this.isServer()) {
|
|
38
|
+
this.setGlobalProperties({
|
|
39
|
+
__referrer: document.referrer,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (this.options.trackOutgoingLinks) {
|
|
43
|
+
this.trackOutgoingLinks();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (this.options.trackScreenViews) {
|
|
47
|
+
this.trackScreenViews();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (this.options.trackAttributes) {
|
|
51
|
+
this.trackAttributes();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private debounce(func: () => void, delay: number) {
|
|
57
|
+
this.debounceTimer && clearTimeout(this.debounceTimer);
|
|
58
|
+
this.debounceTimer = setTimeout(func, delay);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private isServer() {
|
|
62
|
+
return typeof document === 'undefined';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public trackOutgoingLinks() {
|
|
66
|
+
if (this.isServer()) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
document.addEventListener('click', (event) => {
|
|
71
|
+
const target = event.target as HTMLElement;
|
|
72
|
+
const link = target.closest('a');
|
|
73
|
+
if (link && target) {
|
|
74
|
+
const href = link.getAttribute('href');
|
|
75
|
+
if (href?.startsWith('http')) {
|
|
76
|
+
super.track('link_out', {
|
|
77
|
+
href,
|
|
78
|
+
text:
|
|
79
|
+
link.innerText ||
|
|
80
|
+
link.getAttribute('title') ||
|
|
81
|
+
target.getAttribute('alt') ||
|
|
82
|
+
target.getAttribute('title'),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public trackScreenViews() {
|
|
90
|
+
if (this.isServer()) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
this.screenView();
|
|
95
|
+
|
|
96
|
+
const oldPushState = history.pushState;
|
|
97
|
+
history.pushState = function pushState(...args) {
|
|
98
|
+
const ret = oldPushState.apply(this, args);
|
|
99
|
+
window.dispatchEvent(new Event('pushstate'));
|
|
100
|
+
window.dispatchEvent(new Event('locationchange'));
|
|
101
|
+
return ret;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const oldReplaceState = history.replaceState;
|
|
105
|
+
history.replaceState = function replaceState(...args) {
|
|
106
|
+
const ret = oldReplaceState.apply(this, args);
|
|
107
|
+
window.dispatchEvent(new Event('replacestate'));
|
|
108
|
+
window.dispatchEvent(new Event('locationchange'));
|
|
109
|
+
return ret;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
window.addEventListener('popstate', function () {
|
|
113
|
+
window.dispatchEvent(new Event('locationchange'));
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const eventHandler = () => this.debounce(() => this.screenView(), 50);
|
|
117
|
+
|
|
118
|
+
if (this.options.trackHashChanges) {
|
|
119
|
+
window.addEventListener('hashchange', eventHandler);
|
|
120
|
+
} else {
|
|
121
|
+
window.addEventListener('locationchange', eventHandler);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
public trackAttributes() {
|
|
126
|
+
if (this.isServer()) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
document.addEventListener('click', (event) => {
|
|
131
|
+
const target = event.target as HTMLElement;
|
|
132
|
+
const btn = target.closest('button');
|
|
133
|
+
const anchor = target.closest('a');
|
|
134
|
+
const element = btn?.getAttribute('data-track')
|
|
135
|
+
? btn
|
|
136
|
+
: anchor?.getAttribute('data-track')
|
|
137
|
+
? anchor
|
|
138
|
+
: null;
|
|
139
|
+
if (element) {
|
|
140
|
+
const properties: Record<string, unknown> = {};
|
|
141
|
+
for (const attr of element.attributes) {
|
|
142
|
+
if (attr.name.startsWith('data-') && attr.name !== 'data-track') {
|
|
143
|
+
properties[toCamelCase(attr.name.replace(/^data-/, ''))] =
|
|
144
|
+
attr.value;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const name = element.getAttribute('data-track');
|
|
148
|
+
if (name) {
|
|
149
|
+
super.track(name, properties);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async screenView(properties?: TrackProperties): Promise<void>;
|
|
156
|
+
async screenView(path: string, properties?: TrackProperties): Promise<void>;
|
|
157
|
+
async screenView(
|
|
158
|
+
pathOrProperties?: string | TrackProperties,
|
|
159
|
+
propertiesOrUndefined?: TrackProperties,
|
|
160
|
+
): Promise<void> {
|
|
161
|
+
if (this.isServer()) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let path: string;
|
|
166
|
+
let properties: TrackProperties | undefined;
|
|
167
|
+
|
|
168
|
+
if (typeof pathOrProperties === 'string') {
|
|
169
|
+
path = pathOrProperties;
|
|
170
|
+
properties = propertiesOrUndefined;
|
|
171
|
+
} else {
|
|
172
|
+
path = window.location.href;
|
|
173
|
+
properties = pathOrProperties;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (this.lastPath === path) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// capture screenshot
|
|
181
|
+
const snapshot = await screenshot();
|
|
182
|
+
|
|
183
|
+
this.lastPath = path;
|
|
184
|
+
super.track('screen_view', {
|
|
185
|
+
...(properties ?? {}),
|
|
186
|
+
screenshot: snapshot,
|
|
187
|
+
__path: path,
|
|
188
|
+
__title: document.title,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Tracker, TrackerOptions, TrackProperties } from "./index";
|
|
2
|
+
|
|
3
|
+
type ExposedMethodsNames =
|
|
4
|
+
| 'track'
|
|
5
|
+
| 'identify'
|
|
6
|
+
| 'setGlobalProperties'
|
|
7
|
+
| 'alias'
|
|
8
|
+
| 'increment'
|
|
9
|
+
| 'decrement'
|
|
10
|
+
| 'clear';
|
|
11
|
+
|
|
12
|
+
export type ExposedMethods = {
|
|
13
|
+
[K in ExposedMethodsNames]: Tracker[K] extends (...args: any[]) => any
|
|
14
|
+
? [K, ...Parameters<Tracker[K]>]
|
|
15
|
+
: never;
|
|
16
|
+
}[ExposedMethodsNames];
|
|
17
|
+
|
|
18
|
+
export type TrackerMethodNames = ExposedMethodsNames | 'init' | 'screenView';
|
|
19
|
+
export type TrackerMethods =
|
|
20
|
+
| ExposedMethods
|
|
21
|
+
| ['init', TrackerOptions]
|
|
22
|
+
| ['screenView', string | TrackProperties, TrackProperties];
|
|
23
|
+
|
|
24
|
+
declare global {
|
|
25
|
+
interface Window {
|
|
26
|
+
BrainfishAnalytics: {
|
|
27
|
+
q?: [string, ...any[]];
|
|
28
|
+
(method: string, ...args: any[]): void;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|