@brainfish-ai/web-tracker 0.0.4-alpha.5 → 0.0.4-alpha.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/index.ts +2 -0
- package/package.json +2 -2
- package/src/index.ts +191 -2
- package/src/tracker.ts +26 -187
- package/vite.config.ts +1 -1
- package/dist/index.js +0 -1
- package/src/cdn.ts +0 -31
package/index.ts
ADDED
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,2 +1,191 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
+
}
|
package/src/tracker.ts
CHANGED
|
@@ -1,192 +1,31 @@
|
|
|
1
|
-
|
|
1
|
+
import { Tracker } from './index';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
);
|
|
3
|
+
declare global {
|
|
4
|
+
interface Window {
|
|
5
|
+
tracker: {
|
|
6
|
+
q?: [string, ...any[]];
|
|
7
|
+
(method: string, ...args: any[]): void;
|
|
8
|
+
};
|
|
23
9
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
((window) => {
|
|
13
|
+
if (window.tracker && 'q' in window.tracker) {
|
|
14
|
+
const queue = window.tracker.q || [];
|
|
15
|
+
const tracker = new Tracker(queue.shift()[1]) as any;
|
|
16
|
+
queue.forEach((item) => {
|
|
17
|
+
if (item[0] in tracker) {
|
|
18
|
+
tracker[item[0]](...item.slice(1));
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
window.tracker = (t, ...args) => {
|
|
23
|
+
const fn = tracker[t] ? tracker[t].bind(tracker) : undefined;
|
|
24
|
+
if (typeof fn === 'function') {
|
|
25
|
+
fn(...args);
|
|
171
26
|
} else {
|
|
172
|
-
|
|
173
|
-
properties = pathOrProperties;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (this.lastPath === path) {
|
|
177
|
-
return;
|
|
27
|
+
console.warn(`tracker.js: ${t} is not a function`);
|
|
178
28
|
}
|
|
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
|
-
}
|
|
29
|
+
};
|
|
191
30
|
}
|
|
192
|
-
|
|
31
|
+
})(window);
|
package/vite.config.ts
CHANGED
package/dist/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export{T as Tracker}from"./tracker-BndE_qUK.mjs";
|
package/src/cdn.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Tracker } from './index';
|
|
2
|
-
|
|
3
|
-
declare global {
|
|
4
|
-
interface Window {
|
|
5
|
-
tracker: {
|
|
6
|
-
q?: [string, ...any[]];
|
|
7
|
-
(method: string, ...args: any[]): void;
|
|
8
|
-
};
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
((window) => {
|
|
13
|
-
if (window.tracker && 'q' in window.tracker) {
|
|
14
|
-
const queue = window.tracker.q || [];
|
|
15
|
-
const tracker = new Tracker(queue.shift()[1]) as any;
|
|
16
|
-
queue.forEach((item) => {
|
|
17
|
-
if (item[0] in tracker) {
|
|
18
|
-
tracker[item[0]](...item.slice(1));
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
window.tracker = (t, ...args) => {
|
|
23
|
-
const fn = tracker[t] ? tracker[t].bind(tracker) : undefined;
|
|
24
|
-
if (typeof fn === 'function') {
|
|
25
|
-
fn(...args);
|
|
26
|
-
} else {
|
|
27
|
-
console.warn(`tracker.js: ${t} is not a function`);
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
})(window);
|