teacupweb 2.2.1
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/Analytics/analytics.controller.js +19 -0
- package/Analytics/data.controller.js +109 -0
- package/Analytics/identity.js +25 -0
- package/Analytics/save.js +27 -0
- package/Analytics/services/click.service.js +13 -0
- package/Analytics/services/forms.service.js +30 -0
- package/Analytics/services/page.service.js +68 -0
- package/CHANGELOG.md +75 -0
- package/Controllers/data.controller.js +22 -0
- package/Functions/FetchAPI.js +20 -0
- package/LICENSE +15 -0
- package/README.md +331 -0
- package/index.d.ts +6 -0
- package/index.js +24 -0
- package/package.json +46 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import clicks from './services/click.service';
|
|
2
|
+
import page from './services/page.service';
|
|
3
|
+
import forms from './services/forms.service';
|
|
4
|
+
import save from './save';
|
|
5
|
+
|
|
6
|
+
function track() {
|
|
7
|
+
// Track button clicks
|
|
8
|
+
clicks.track();
|
|
9
|
+
|
|
10
|
+
// Track page views and scrolling
|
|
11
|
+
page.track();
|
|
12
|
+
|
|
13
|
+
// forms
|
|
14
|
+
forms.track();
|
|
15
|
+
|
|
16
|
+
// save
|
|
17
|
+
save();
|
|
18
|
+
}
|
|
19
|
+
export default { track };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const data = {
|
|
2
|
+
button: [],
|
|
3
|
+
form: [],
|
|
4
|
+
page: [],
|
|
5
|
+
};
|
|
6
|
+
const prevData = {
|
|
7
|
+
button: [],
|
|
8
|
+
form: [],
|
|
9
|
+
page: [],
|
|
10
|
+
};
|
|
11
|
+
const clearData = () => {
|
|
12
|
+
prevData.button = [...data.button];
|
|
13
|
+
prevData.form = [...data.form];
|
|
14
|
+
prevData.page = [...data.page];
|
|
15
|
+
data.button = [];
|
|
16
|
+
data.form = [];
|
|
17
|
+
data.page = [];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const getNewData = () => {
|
|
21
|
+
const newData = {
|
|
22
|
+
button: [],
|
|
23
|
+
form: [],
|
|
24
|
+
page: [],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Compare buttons - only include if not in prevData
|
|
28
|
+
data.button.forEach((btn) => {
|
|
29
|
+
const isDuplicate = prevData.button.some(
|
|
30
|
+
(prevBtn) => prevBtn.button === btn.button && prevBtn.page === btn.page
|
|
31
|
+
);
|
|
32
|
+
if (!isDuplicate) {
|
|
33
|
+
newData.button.push(btn);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Compare pages - only include if page+percentage combo not in prevData
|
|
38
|
+
data.page.forEach((pg) => {
|
|
39
|
+
const isDuplicate = prevData.page.some(
|
|
40
|
+
(prevPg) =>
|
|
41
|
+
prevPg.page === pg.page && prevPg.percentage === pg.percentage
|
|
42
|
+
);
|
|
43
|
+
if (!isDuplicate) {
|
|
44
|
+
newData.page.push(pg);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Compare forms - only include if form+percent combo not in prevData
|
|
49
|
+
data.form.forEach((frm) => {
|
|
50
|
+
const isDuplicate = prevData.form.some(
|
|
51
|
+
(prevFrm) =>
|
|
52
|
+
prevFrm.form === frm.form && prevFrm.percent === frm.percent
|
|
53
|
+
);
|
|
54
|
+
if (!isDuplicate) {
|
|
55
|
+
newData.form.push(frm);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return newData;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const setData = (type, result) => {
|
|
63
|
+
if (type === 'page') {
|
|
64
|
+
// Find existing page entry
|
|
65
|
+
const existingPageIndex = data.page.findIndex(
|
|
66
|
+
(page) => page.page === result.page
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (existingPageIndex !== -1) {
|
|
70
|
+
// Update only if new percentage is higher
|
|
71
|
+
const currentPercentage =
|
|
72
|
+
parseFloat(data.page[existingPageIndex].percentage) || 0;
|
|
73
|
+
const newPercentage = parseFloat(result.percentage) || 0;
|
|
74
|
+
|
|
75
|
+
if (newPercentage > currentPercentage) {
|
|
76
|
+
data.page[existingPageIndex].percentage = result.percentage;
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
// Add new entry if it doesn't exist
|
|
80
|
+
data.page.push(result);
|
|
81
|
+
}
|
|
82
|
+
} else if (type === 'form') {
|
|
83
|
+
// Find existing form entry
|
|
84
|
+
const existingFormIndex = data.form.findIndex(
|
|
85
|
+
(form) => form.form === result.form
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
if (existingFormIndex !== -1) {
|
|
89
|
+
// Update only if new percentage is higher
|
|
90
|
+
const currentPercent =
|
|
91
|
+
parseFloat(data.form[existingFormIndex].percent) || 0;
|
|
92
|
+
const newPercent = parseFloat(result.percent) || 0;
|
|
93
|
+
|
|
94
|
+
if (newPercent > currentPercent) {
|
|
95
|
+
data.form[existingFormIndex].percent = result.percent;
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
// Add new entry if it doesn't exist
|
|
99
|
+
data.form.push(result);
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
// For other types (like 'button'), just push without deduplication
|
|
103
|
+
data[type].push(result);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return data;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export default { setData, data, clearData, getNewData };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const FINGERPRINT_KEY = 'teacup_fingerprint';
|
|
2
|
+
|
|
3
|
+
export default function fingerprint() {
|
|
4
|
+
// Check if fingerprint already exists in localStorage
|
|
5
|
+
const existingFingerprint = localStorage.getItem(FINGERPRINT_KEY);
|
|
6
|
+
|
|
7
|
+
if (existingFingerprint) {
|
|
8
|
+
return existingFingerprint;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Generate new fingerprint if not found
|
|
12
|
+
const canvas = document.createElement('canvas');
|
|
13
|
+
canvas.width = 20;
|
|
14
|
+
canvas.height = 2220;
|
|
15
|
+
const ctx = canvas.getContext('2d');
|
|
16
|
+
ctx.fillText('Hello', 10, 10);
|
|
17
|
+
|
|
18
|
+
const data = canvas.toDataURL();
|
|
19
|
+
const newFingerprint = data.slice(-32);
|
|
20
|
+
|
|
21
|
+
// Store in localStorage for future use
|
|
22
|
+
localStorage.setItem(FINGERPRINT_KEY, newFingerprint);
|
|
23
|
+
|
|
24
|
+
return newFingerprint;
|
|
25
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import fetchAPI from '../Functions/FetchAPI';
|
|
2
|
+
import fingerprint from './identity';
|
|
3
|
+
import data from './data.controller';
|
|
4
|
+
import { initial } from '..';
|
|
5
|
+
|
|
6
|
+
export default function save() {
|
|
7
|
+
setInterval(() => {
|
|
8
|
+
const newData = data.getNewData();
|
|
9
|
+
|
|
10
|
+
// Check if there's any new data to send
|
|
11
|
+
const hasNewData =
|
|
12
|
+
newData.button.length > 0 ||
|
|
13
|
+
newData.form.length > 0 ||
|
|
14
|
+
newData.page.length > 0;
|
|
15
|
+
|
|
16
|
+
if (hasNewData) {
|
|
17
|
+
fetchAPI('api/analytics', 'POST', {
|
|
18
|
+
data: newData,
|
|
19
|
+
initial: initial,
|
|
20
|
+
fingerprint: fingerprint(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Clear data after sending
|
|
24
|
+
data.clearData();
|
|
25
|
+
}
|
|
26
|
+
}, 5000);
|
|
27
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import data from './../data.controller';
|
|
2
|
+
|
|
3
|
+
function track() {
|
|
4
|
+
addEventListener('click', (e) => {
|
|
5
|
+
if (e.target.tagName == 'BUTTON') {
|
|
6
|
+
data.setData('button', {
|
|
7
|
+
button: e.target.innerText,
|
|
8
|
+
page: window.location.pathname,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export default { track };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import data from '../data.controller';
|
|
2
|
+
function track() {
|
|
3
|
+
document.addEventListener('input', (e) => {
|
|
4
|
+
const form = e.target.closest('form');
|
|
5
|
+
if (!form) return;
|
|
6
|
+
|
|
7
|
+
function findHeadingBeforeForm(form) {
|
|
8
|
+
let el = form.previousElementSibling;
|
|
9
|
+
while (el) {
|
|
10
|
+
if (/H[1-3]/.test(el.tagName)) return el;
|
|
11
|
+
el = el.previousElementSibling;
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const heading = findHeadingBeforeForm(form);
|
|
17
|
+
|
|
18
|
+
const fields = form.querySelectorAll('input, textarea, select');
|
|
19
|
+
const filled = [...fields].filter((f) => f.value.trim() !== '').length;
|
|
20
|
+
|
|
21
|
+
const percent = (filled / fields.length) * 100;
|
|
22
|
+
|
|
23
|
+
data.setData('form', {
|
|
24
|
+
form: form.dataset.formName || heading?.innerText || 'unknown-form',
|
|
25
|
+
percentage: Math.ceil(percent),
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default { track };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import data from '../data.controller';
|
|
2
|
+
|
|
3
|
+
let currentPath = window.location.pathname;
|
|
4
|
+
let topPercent = 0;
|
|
5
|
+
|
|
6
|
+
function trackRoute() {
|
|
7
|
+
['pushState', 'replaceState'].forEach((method) => {
|
|
8
|
+
const original = history[method];
|
|
9
|
+
history[method] = function () {
|
|
10
|
+
const result = original.apply(this, arguments);
|
|
11
|
+
|
|
12
|
+
// New page navigation detected
|
|
13
|
+
const newPath = window.location.pathname;
|
|
14
|
+
if (newPath !== currentPath) {
|
|
15
|
+
// Initialize new page with 0%
|
|
16
|
+
currentPath = newPath;
|
|
17
|
+
topPercent = 0;
|
|
18
|
+
data.setData('page', {
|
|
19
|
+
page: currentPath,
|
|
20
|
+
percentage: 0,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return result;
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function trackPageView() {
|
|
30
|
+
document.addEventListener('scroll', () => {
|
|
31
|
+
const scrollTop = document.documentElement.scrollTop;
|
|
32
|
+
const scrollHeight = document.documentElement.scrollHeight;
|
|
33
|
+
const clientHeight = document.documentElement.clientHeight;
|
|
34
|
+
|
|
35
|
+
const percent = Math.ceil(
|
|
36
|
+
(scrollTop / (scrollHeight - clientHeight)) * 100
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Update only if percentage increased
|
|
40
|
+
if (percent > topPercent) {
|
|
41
|
+
topPercent = percent;
|
|
42
|
+
|
|
43
|
+
// Update the data with new percentage
|
|
44
|
+
data.setData('page', {
|
|
45
|
+
page: currentPath,
|
|
46
|
+
percentage: topPercent,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function track() {
|
|
53
|
+
// Initialize current page
|
|
54
|
+
data.setData('page', {
|
|
55
|
+
page: currentPath,
|
|
56
|
+
percentage: 0,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Set up route tracking for navigation
|
|
60
|
+
trackRoute();
|
|
61
|
+
|
|
62
|
+
// Set up scroll tracking
|
|
63
|
+
trackPageView();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default {
|
|
67
|
+
track,
|
|
68
|
+
};
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
## [1.1.0] - 2025-12-26
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **Duplicate Prevention System**: Analytics data is now compared with previously sent data to prevent duplicates from being stored in the database
|
|
13
|
+
- **LocalStorage Fingerprint**: Visitor fingerprint now persists in localStorage across browser sessions for consistent identification
|
|
14
|
+
- `getNewData()` function in data controller that filters out duplicate analytics before sending
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- Fingerprint generation now happens only once per browser and is stored in localStorage with key `teacupnet_fingerprint`
|
|
18
|
+
- Analytics save function now only sends API requests when there is new data to transmit
|
|
19
|
+
- Data comparison logic ensures button clicks, page views, and form interactions are only sent once
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- Duplicate button clicks on the same page are no longer sent multiple times
|
|
23
|
+
- Same page scroll percentages are no longer duplicated in database
|
|
24
|
+
- Form completion percentages only update when they change
|
|
25
|
+
- Reduced unnecessary API calls when no new analytics data is available
|
|
26
|
+
|
|
27
|
+
### Performance
|
|
28
|
+
- Reduced database writes by filtering duplicates before transmission
|
|
29
|
+
- Reduced network requests by skipping empty data intervals
|
|
30
|
+
- Fingerprint generation optimized to run only once per browser session
|
|
31
|
+
|
|
32
|
+
## [1.0.0] - 2025-12-17
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- Initial release of TeacupNet Library
|
|
36
|
+
- Analytics tracking module with automatic data collection
|
|
37
|
+
- Button click tracking across all pages
|
|
38
|
+
- Page view and scroll depth tracking
|
|
39
|
+
- Form completion percentage tracking
|
|
40
|
+
- Canvas fingerprinting for unique visitor identification
|
|
41
|
+
- Data controller for blog management
|
|
42
|
+
- API for posting data to inboxes
|
|
43
|
+
- Automatic data synchronization every 5 seconds
|
|
44
|
+
- Smart deduplication for page and form analytics
|
|
45
|
+
- TypeScript definitions for better IDE support
|
|
46
|
+
|
|
47
|
+
### Features
|
|
48
|
+
- **Analytics Module**
|
|
49
|
+
- `analytics.track()` - Start automatic tracking of user interactions
|
|
50
|
+
- Real-time button click tracking
|
|
51
|
+
- Scroll depth monitoring with percentage calculation
|
|
52
|
+
- Form field completion tracking
|
|
53
|
+
- Custom form naming via `data-form-name` or heading detection
|
|
54
|
+
|
|
55
|
+
- **Data Module**
|
|
56
|
+
- `data.getBlogs()` - Retrieve all blogs for client
|
|
57
|
+
- `data.getBlog(id)` - Get specific blog by ID
|
|
58
|
+
- `data.postData(inboxId, data)` - Post data to inbox
|
|
59
|
+
|
|
60
|
+
### Technical Details
|
|
61
|
+
- ES modules support
|
|
62
|
+
- Production-ready with Vercel backend integration
|
|
63
|
+
- Browser-based fingerprinting for visitor tracking
|
|
64
|
+
- Optimized data storage preventing duplicates
|
|
65
|
+
- Credential validation on initialization
|
|
66
|
+
|
|
67
|
+
## [Unreleased]
|
|
68
|
+
|
|
69
|
+
### Planned
|
|
70
|
+
- Custom event tracking
|
|
71
|
+
- Configurable sync intervals
|
|
72
|
+
- Offline data queue
|
|
73
|
+
- Enhanced TypeScript definitions
|
|
74
|
+
- Custom analytics filters
|
|
75
|
+
- Dashboard integration examples
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fetchAPI from '../Functions/FetchAPI';
|
|
2
|
+
import { initial } from '../index.js';
|
|
3
|
+
|
|
4
|
+
const getBlogs = async () => {
|
|
5
|
+
const id = initial.clientID;
|
|
6
|
+
const result = await fetchAPI(`dashboard/blogs/${id}`);
|
|
7
|
+
return result;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const getBlog = async (id) => {
|
|
11
|
+
const clientID = initial.clientID;
|
|
12
|
+
return await fetchAPI(`dashboard/blogs/${clientID}/${id}`);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const postData = async (id, data) => {
|
|
16
|
+
return await fetchAPI('api/inboxData', 'POST', {
|
|
17
|
+
inbox_id: id,
|
|
18
|
+
data: data,
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default { getBlogs, getBlog, postData };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export default async function fetchAPI(path, method = 'GET', body = null) {
|
|
2
|
+
const req = {
|
|
3
|
+
method: method,
|
|
4
|
+
headers: {
|
|
5
|
+
'Content-Type': 'application/json',
|
|
6
|
+
},
|
|
7
|
+
body: JSON.stringify(body),
|
|
8
|
+
};
|
|
9
|
+
if (method === 'GET') delete req.body;
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const data = await fetch(
|
|
13
|
+
`https://backend.teacup.website/${path}`,
|
|
14
|
+
req
|
|
15
|
+
);
|
|
16
|
+
return await data.json();
|
|
17
|
+
} catch (error) {
|
|
18
|
+
console.log(error);
|
|
19
|
+
}
|
|
20
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Tahmid Jihan, TeacupNet
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# TeacupNet Library
|
|
2
|
+
|
|
3
|
+
A powerful JavaScript library for integrating TeacupNet analytics and data management capabilities into your web applications. Track user interactions, page views, form completions, and seamlessly connect to the TeacupNet backend.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 📊 **Analytics Tracking**: Automatic tracking of button clicks, page views, and form interactions
|
|
8
|
+
- 🎯 **Smart Form Tracking**: Track form completion percentage in real-time
|
|
9
|
+
- 📈 **Page Scroll Tracking**: Monitor how far users scroll on each page
|
|
10
|
+
- 🔒 **Fingerprinting**: Unique visitor identification using canvas fingerprinting
|
|
11
|
+
- 🚀 **Easy Integration**: Simple initialization and automatic data synchronization
|
|
12
|
+
- 📦 **Lightweight**: Minimal dependencies, optimized for performance
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install teacupnet-lib
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
### 1. Initialize the Library
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
import teacupnet from 'teacupnet-lib';
|
|
26
|
+
|
|
27
|
+
// Initialize with your credentials
|
|
28
|
+
const client = teacupnet('YOUR_CLIENT_ID', 'YOUR_CLIENT_KEY');
|
|
29
|
+
|
|
30
|
+
if (client.error) {
|
|
31
|
+
console.error(client.message);
|
|
32
|
+
} else {
|
|
33
|
+
console.log('TeacupNet initialized successfully!');
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Enable Analytics Tracking
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
import teacupnet from 'teacupnet-lib';
|
|
41
|
+
|
|
42
|
+
const client = teacupnet('YOUR_CLIENT_ID', 'YOUR_CLIENT_KEY');
|
|
43
|
+
|
|
44
|
+
// Start tracking analytics
|
|
45
|
+
client.analytics.track();
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
That's it! The library will now automatically track:
|
|
49
|
+
|
|
50
|
+
- Button clicks
|
|
51
|
+
- Page navigation and scroll depth
|
|
52
|
+
- Form completion percentages
|
|
53
|
+
|
|
54
|
+
## API Reference
|
|
55
|
+
|
|
56
|
+
### Initialization
|
|
57
|
+
|
|
58
|
+
#### `teacupnet(clientID, clientKey)`
|
|
59
|
+
|
|
60
|
+
Initializes the TeacupNet library with your credentials.
|
|
61
|
+
|
|
62
|
+
**Parameters:**
|
|
63
|
+
|
|
64
|
+
- `clientID` (string): Your unique client identifier
|
|
65
|
+
- `clientKey` (string): Your secret client key
|
|
66
|
+
|
|
67
|
+
**Returns:**
|
|
68
|
+
|
|
69
|
+
- On success: Object with `analytics` and `data` controllers
|
|
70
|
+
- On failure: Object with `error` and `message` properties
|
|
71
|
+
|
|
72
|
+
**Example:**
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
const client = teacupnet('123', 'secretKey456');
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Analytics Module
|
|
79
|
+
|
|
80
|
+
#### `client.analytics.track()`
|
|
81
|
+
|
|
82
|
+
Starts automatic tracking of user interactions including:
|
|
83
|
+
|
|
84
|
+
- **Button Clicks**: Tracks every button click with the button text and page location
|
|
85
|
+
- **Page Views**: Tracks page navigation and scroll depth percentage
|
|
86
|
+
- **Form Interactions**: Monitors form completion percentage
|
|
87
|
+
|
|
88
|
+
**Example:**
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
client.analytics.track();
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Data Collection:**
|
|
95
|
+
|
|
96
|
+
- Automatically sends analytics data to the server every 5 seconds
|
|
97
|
+
- Uses canvas fingerprinting for unique visitor identification
|
|
98
|
+
- Tracks only the highest scroll percentage per page (no duplicates)
|
|
99
|
+
- Tracks only the highest form completion percentage per form
|
|
100
|
+
|
|
101
|
+
### Data Module
|
|
102
|
+
|
|
103
|
+
#### `client.data.getBlogs()`
|
|
104
|
+
|
|
105
|
+
Retrieves all blogs associated with your client account.
|
|
106
|
+
|
|
107
|
+
**Returns:** Promise resolving to an array of blog objects
|
|
108
|
+
|
|
109
|
+
**Example:**
|
|
110
|
+
|
|
111
|
+
```javascript
|
|
112
|
+
const blogs = await client.data.getBlogs();
|
|
113
|
+
console.log(blogs);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### `client.data.getBlog(blogId)`
|
|
117
|
+
|
|
118
|
+
Retrieves a specific blog by its ID.
|
|
119
|
+
|
|
120
|
+
**Parameters:**
|
|
121
|
+
|
|
122
|
+
- `blogId` (string): The unique identifier of the blog
|
|
123
|
+
|
|
124
|
+
**Returns:** Promise resolving to a blog object
|
|
125
|
+
|
|
126
|
+
**Example:**
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
const blog = await client.data.getBlog('blog123');
|
|
130
|
+
console.log(blog);
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### `client.data.postData(inboxId, data)`
|
|
134
|
+
|
|
135
|
+
Posts data to a specific inbox.
|
|
136
|
+
|
|
137
|
+
**Parameters:**
|
|
138
|
+
|
|
139
|
+
- `inboxId` (string): The unique identifier of the inbox
|
|
140
|
+
- `data` (object): The data to be posted
|
|
141
|
+
|
|
142
|
+
**Returns:** Promise resolving to the server response
|
|
143
|
+
|
|
144
|
+
**Example:**
|
|
145
|
+
|
|
146
|
+
```javascript
|
|
147
|
+
const response = await client.data.postData('inbox123', {
|
|
148
|
+
name: 'John Doe',
|
|
149
|
+
email: 'john@example.com',
|
|
150
|
+
message: 'Hello!',
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Analytics Details
|
|
155
|
+
|
|
156
|
+
### Button Click Tracking
|
|
157
|
+
|
|
158
|
+
Tracks all button clicks automatically:
|
|
159
|
+
|
|
160
|
+
```javascript
|
|
161
|
+
// Captured data format:
|
|
162
|
+
{
|
|
163
|
+
button: "Submit", // Button text
|
|
164
|
+
page: "/contact" // Current page path
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Page View Tracking
|
|
169
|
+
|
|
170
|
+
Monitors page navigation and scroll depth:
|
|
171
|
+
|
|
172
|
+
```javascript
|
|
173
|
+
// Captured data format:
|
|
174
|
+
{
|
|
175
|
+
page: "/about",
|
|
176
|
+
percentage: 75 // Highest scroll depth reached (0-100)
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Note:** Only the highest scroll percentage is stored for each page visit.
|
|
181
|
+
|
|
182
|
+
### Form Tracking
|
|
183
|
+
|
|
184
|
+
Tracks form completion in real-time:
|
|
185
|
+
|
|
186
|
+
```javascript
|
|
187
|
+
// Captured data format:
|
|
188
|
+
{
|
|
189
|
+
form: "Contact Us", // From data-form-name attribute or preceding H1-H3 heading
|
|
190
|
+
percent: 66.7 // Percentage of filled fields
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Form Naming:**
|
|
195
|
+
|
|
196
|
+
1. Use `data-form-name` attribute on your form element
|
|
197
|
+
2. Or place an `<h1>`, `<h2>`, or `<h3>` heading before the form
|
|
198
|
+
3. Fallback: "unknown-form"
|
|
199
|
+
|
|
200
|
+
**Example:**
|
|
201
|
+
|
|
202
|
+
```html
|
|
203
|
+
<!-- Option 1: Using data-form-name -->
|
|
204
|
+
<form data-form-name="Newsletter Signup">
|
|
205
|
+
<input type="email" name="email" />
|
|
206
|
+
<button type="submit">Subscribe</button>
|
|
207
|
+
</form>
|
|
208
|
+
|
|
209
|
+
<!-- Option 2: Using heading -->
|
|
210
|
+
<h2>Contact Us</h2>
|
|
211
|
+
<form>
|
|
212
|
+
<input type="text" name="name" />
|
|
213
|
+
<input type="email" name="email" />
|
|
214
|
+
<textarea name="message"></textarea>
|
|
215
|
+
<button type="submit">Send</button>
|
|
216
|
+
</form>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Advanced Usage
|
|
220
|
+
|
|
221
|
+
### Custom Analytics Implementation
|
|
222
|
+
|
|
223
|
+
If you want more control over tracking:
|
|
224
|
+
|
|
225
|
+
```javascript
|
|
226
|
+
import teacupnet from 'teacupnet-lib';
|
|
227
|
+
|
|
228
|
+
const client = teacupnet('YOUR_CLIENT_ID', 'YOUR_CLIENT_KEY');
|
|
229
|
+
|
|
230
|
+
// Enable only specific tracking
|
|
231
|
+
// Note: This requires manual implementation
|
|
232
|
+
// The track() method enables all tracking automatically
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Working with React/Vue/Angular
|
|
236
|
+
|
|
237
|
+
The library works seamlessly with modern frameworks:
|
|
238
|
+
|
|
239
|
+
```javascript
|
|
240
|
+
// React example
|
|
241
|
+
import { useEffect } from 'react';
|
|
242
|
+
import teacupnet from 'teacupnet-lib';
|
|
243
|
+
|
|
244
|
+
function App() {
|
|
245
|
+
useEffect(() => {
|
|
246
|
+
const client = teacupnet(
|
|
247
|
+
process.env.REACT_APP_CLIENT_ID,
|
|
248
|
+
process.env.REACT_APP_CLIENT_KEY
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
if (!client.error) {
|
|
252
|
+
client.analytics.track();
|
|
253
|
+
}
|
|
254
|
+
}, []);
|
|
255
|
+
|
|
256
|
+
return <div>Your App</div>;
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Data Synchronization
|
|
261
|
+
|
|
262
|
+
Analytics data is automatically sent to the TeacupNet backend:
|
|
263
|
+
|
|
264
|
+
- **Interval**: Every 5 seconds
|
|
265
|
+
- **Endpoint**: `https://teacupnet-backend.vercel.app/api/analytics`
|
|
266
|
+
- **Included**: Fingerprint, client credentials, and collected analytics data
|
|
267
|
+
|
|
268
|
+
## Browser Support
|
|
269
|
+
|
|
270
|
+
- Chrome (latest)
|
|
271
|
+
- Firefox (latest)
|
|
272
|
+
- Safari (latest)
|
|
273
|
+
- Edge (latest)
|
|
274
|
+
|
|
275
|
+
**Note:** This library uses ES modules and requires a modern browser or build tool.
|
|
276
|
+
|
|
277
|
+
## Security Considerations
|
|
278
|
+
|
|
279
|
+
- **Credentials**: Store your `clientID` and `clientKey` securely
|
|
280
|
+
- **Environment Variables**: Use environment variables for credentials in production
|
|
281
|
+
- **HTTPS**: The library communicates with the backend over HTTPS
|
|
282
|
+
|
|
283
|
+
**Example `.env` file:**
|
|
284
|
+
|
|
285
|
+
```env
|
|
286
|
+
TEACUPNET_CLIENT_ID=your_client_id
|
|
287
|
+
TEACUPNET_CLIENT_KEY=your_client_key
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Troubleshooting
|
|
291
|
+
|
|
292
|
+
### Library not tracking
|
|
293
|
+
|
|
294
|
+
Make sure you've called `client.analytics.track()` after initialization:
|
|
295
|
+
|
|
296
|
+
```javascript
|
|
297
|
+
const client = teacupnet('CLIENT_ID', 'CLIENT_KEY');
|
|
298
|
+
client.analytics.track(); // Don't forget this!
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Invalid Credentials error
|
|
302
|
+
|
|
303
|
+
Verify your `clientID` and `clientKey` are correct:
|
|
304
|
+
|
|
305
|
+
```javascript
|
|
306
|
+
const client = teacupnet('CLIENT_ID', 'CLIENT_KEY');
|
|
307
|
+
if (client.error) {
|
|
308
|
+
console.error('Initialization failed:', client.message);
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Data not appearing in dashboard
|
|
313
|
+
|
|
314
|
+
- Check browser console for errors
|
|
315
|
+
- Verify network requests are being sent to the backend
|
|
316
|
+
- Confirm your credentials have the correct permissions
|
|
317
|
+
|
|
318
|
+
## Support
|
|
319
|
+
|
|
320
|
+
For issues, questions, or feature requests:
|
|
321
|
+
|
|
322
|
+
- GitHub Issues: [https://github.com/tahmidjihan/teacupnet-lib/issues](https://github.com/tahmidjihan/teacupnet-lib/issues)
|
|
323
|
+
- Repository: [https://github.com/tahmidjihan/teacupnet-lib](https://github.com/tahmidjihan/teacupnet-lib)
|
|
324
|
+
|
|
325
|
+
## License
|
|
326
|
+
|
|
327
|
+
ISC © Tahmid Jihan, Teacupnet
|
|
328
|
+
|
|
329
|
+
## Changelog
|
|
330
|
+
|
|
331
|
+
See [CHANGELOG.md](CHANGELOG.md) for version history and updates.
|
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import dataController from './Controllers/data.controller.js';
|
|
2
|
+
import analytics from './Analytics/analytics.controller.js';
|
|
3
|
+
|
|
4
|
+
export const initial = {
|
|
5
|
+
clientID: '',
|
|
6
|
+
clientKey: '',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function init(clientID, clientKey) {
|
|
10
|
+
if (!clientID || !clientKey) {
|
|
11
|
+
return {
|
|
12
|
+
error: 'Invalid Credentials',
|
|
13
|
+
message: 'Please provide clientID and clientKey',
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
initial.clientID = clientID;
|
|
17
|
+
initial.clientKey = clientKey;
|
|
18
|
+
return {
|
|
19
|
+
data: dataController,
|
|
20
|
+
analytics: analytics,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default init;
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "teacupweb",
|
|
3
|
+
"version": "2.2.1",
|
|
4
|
+
"description": "A powerful JavaScript library for integrating Teacup analytics and data management. Track user interactions, page views, form completions, and seamlessly connect to the Teacup backend.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"analytics",
|
|
7
|
+
"tracking",
|
|
8
|
+
"teacup",
|
|
9
|
+
"user-analytics",
|
|
10
|
+
"form-tracking",
|
|
11
|
+
"page-tracking",
|
|
12
|
+
"click-tracking",
|
|
13
|
+
"visitor-tracking",
|
|
14
|
+
"fingerprinting",
|
|
15
|
+
"web-analytics"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://github.com/tahmidjihan/teacup-lib#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/tahmidjihan/teacup-lib/issues"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/tahmidjihan/teacup-lib.git"
|
|
24
|
+
},
|
|
25
|
+
"license": "ISC",
|
|
26
|
+
"author": "Tahmid Jihan, Teacup",
|
|
27
|
+
"type": "module",
|
|
28
|
+
"main": "index.js",
|
|
29
|
+
"exports": "./index.js",
|
|
30
|
+
"files": [
|
|
31
|
+
"index.js",
|
|
32
|
+
"index.d.ts",
|
|
33
|
+
"Analytics/",
|
|
34
|
+
"Controllers/",
|
|
35
|
+
"Functions/",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE",
|
|
38
|
+
"CHANGELOG.md"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
42
|
+
},
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=14.0.0"
|
|
45
|
+
}
|
|
46
|
+
}
|