@tekibo/feedpulse-sdk 2.1.0 → 2.1.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 +10 -9
- package/dist/index.js +1 -1
- package/dist/index.mjs +43 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,11 +22,6 @@ The SDK automatically tracks:
|
|
|
22
22
|
- `feedback`: explicit rating/message submissions from widget or manual API
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
## Quick start (Nuxt)
|
|
29
|
-
|
|
30
25
|
Import from `@tekibo/feedpulse-sdk/nuxt` and wrap your app:
|
|
31
26
|
|
|
32
27
|
```vue
|
|
@@ -36,12 +31,16 @@ import { FeedPulseProvider, FeedPulseWidget } from "@tekibo/feedpulse-sdk/nuxt";
|
|
|
36
31
|
|
|
37
32
|
<template>
|
|
38
33
|
<FeedPulseProvider api-key="fp_live_your_project_key">
|
|
39
|
-
|
|
34
|
+
<FeedPulseProvider
|
|
35
|
+
api-key="fp_live_your_project_key"
|
|
36
|
+
endpoint="https://feed-pulse.vercel.app/api/ingest"
|
|
37
|
+
>
|
|
40
38
|
<FeedPulseWidget id="global-feedback" position="bottom-right" />
|
|
41
39
|
</FeedPulseProvider>
|
|
42
40
|
</template>
|
|
43
41
|
```
|
|
44
42
|
|
|
43
|
+
|
|
45
44
|
Tag any interactive element with `data-fp-id`:
|
|
46
45
|
|
|
47
46
|
```vue
|
|
@@ -88,7 +87,10 @@ function SaveButton() {
|
|
|
88
87
|
|
|
89
88
|
export default function App() {
|
|
90
89
|
return (
|
|
91
|
-
<FeedPulseProvider
|
|
90
|
+
<FeedPulseProvider
|
|
91
|
+
apiKey="fp_live_your_project_key"
|
|
92
|
+
endpoint="https://feed-pulse.vercel.app/api/ingest"
|
|
93
|
+
>
|
|
92
94
|
<button data-fp-id="hero-cta">Get Started</button>
|
|
93
95
|
<SaveButton />
|
|
94
96
|
<FeedPulseWidget id="global-feedback" position="bottom-right" />
|
|
@@ -108,7 +110,7 @@ import { FeedPulseTracker } from "@tekibo/feedpulse-sdk";
|
|
|
108
110
|
`FeedPulseTracker` configuration:
|
|
109
111
|
|
|
110
112
|
- `apiKey: string` required
|
|
111
|
-
- `endpoint?: string` optional, defaults to `https://
|
|
113
|
+
- `endpoint?: string` optional, defaults to `https://feed-pulse.vercel.app/api/ingest`
|
|
112
114
|
- `batchInterval?: number` optional, defaults to `5000` ms
|
|
113
115
|
|
|
114
116
|
Key methods:
|
|
@@ -126,7 +128,6 @@ Nuxt and React widgets support:
|
|
|
126
128
|
- `placeholder?: string` prompt text
|
|
127
129
|
- `position?: "bottom-right" | "bottom-left" | "inline"` default is `"bottom-right"`
|
|
128
130
|
|
|
129
|
-
|
|
130
131
|
## Integration checklist for your app
|
|
131
132
|
|
|
132
133
|
1. Create a project in FeedPulse dashboard and copy the project API key
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class n{constructor(e){this.queue=[],this.flushInterval=null,this.observer=null,this.intersectionObserver=null,this.seenInView=new Set,this.config={endpoint:"https://
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class n{constructor(e){this.queue=[],this.flushInterval=null,this.observer=null,this.intersectionObserver=null,this.seenInView=new Set,this.config={endpoint:"https://feed-pulse.vercel.app/api/ingest",batchInterval:5e3,...e},this.visitorId="",this.sessionId=""}init(){this.isBrowser()&&(this.visitorId=this.getOrCreateVisitorId(),this.sessionId=this.generateSessionId(),this.startFlushInterval(),this.attachGlobalListeners(),this.observeDOM())}attachGlobalListeners(){document.addEventListener("click",t=>{const r=t.target.closest("[data-fp-id]");r&&this.track("click",r.getAttribute("data-fp-id"),{x:t.clientX,y:t.clientY})},{passive:!0});let e=0,i=null;document.addEventListener("mouseover",t=>{const r=t.target.closest("[data-fp-id]");r&&(e=Date.now(),i=r.getAttribute("data-fp-id"))},{passive:!0}),document.addEventListener("mouseout",t=>{if(!t.target.closest("[data-fp-id]")||!i)return;const s=Date.now()-e;s>500&&this.track("hover",i,{hoverDuration:s}),i=null},{passive:!0})}observeDOM(){this.intersectionObserver=new IntersectionObserver(i=>{for(const t of i){const r=t.target.dataset.fpId;!r||!t.isIntersecting||this.seenInView.has(r)||(this.seenInView.add(r),this.track("scroll_into_view",r,{scrollDepth:Math.round(window.scrollY/Math.max(1,document.body.scrollHeight)*100)}))}},{threshold:.5});const e=()=>{document.querySelectorAll("[data-fp-id]").forEach(i=>{this.intersectionObserver?.observe(i)})};e(),this.observer=new MutationObserver(()=>e()),this.observer.observe(document.body,{childList:!0,subtree:!0})}track(e,i,t){this.isBrowser()&&this.queue.push({projectApiKey:this.config.apiKey,elementId:i,eventType:e,sessionId:this.sessionId,visitorId:this.visitorId,page:window.location.pathname,referrer:document.referrer,device:this.getDeviceType(),browser:this.getBrowser(),os:this.getOS(),metadata:t,timestamp:new Date().toISOString()})}trackFeedback(e,i,t){this.isBrowser()&&(!i&&!t||this.sendImmediate([{projectApiKey:this.config.apiKey,elementId:e??"__page__",eventType:"feedback",sessionId:this.sessionId,visitorId:this.visitorId,page:window.location.pathname,device:this.getDeviceType(),metadata:{rating:i,message:t},timestamp:new Date().toISOString()}]))}async flush(){if(this.queue.length===0)return;const e=[...this.queue];this.queue=[],await this.sendImmediate(e)}async sendImmediate(e){try{await fetch(this.config.endpoint,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({events:e}),keepalive:!0})}catch{}}startFlushInterval(){this.flushInterval=setInterval(()=>this.flush(),this.config.batchInterval),window.addEventListener("beforeunload",()=>this.flush()),document.addEventListener("visibilitychange",()=>{document.visibilityState==="hidden"&&this.flush()})}getOrCreateVisitorId(){const e="__fp_vid",i=this.generateId();try{let t=localStorage.getItem(e);return t||(t=i,localStorage.setItem(e,t)),t}catch{return i}}generateId(){return typeof crypto<"u"&&typeof crypto.randomUUID=="function"?crypto.randomUUID():`${Date.now()}-${Math.random().toString(36).slice(2)}`}isBrowser(){return typeof window<"u"&&typeof document<"u"}generateSessionId(){return`${Date.now()}-${Math.random().toString(36).slice(2)}`}getDeviceType(){const e=navigator.userAgent;return/Mobi|Android/i.test(e)?"mobile":/Tablet|iPad/i.test(e)?"tablet":"desktop"}getBrowser(){const e=navigator.userAgent;return e.includes("Edg")?"Edge":e.includes("Chrome")?"Chrome":e.includes("Firefox")?"Firefox":e.includes("Safari")?"Safari":"Other"}getOS(){const e=navigator.userAgent;return e.includes("Windows")?"Windows":e.includes("Mac")?"macOS":e.includes("Linux")?"Linux":e.includes("Android")?"Android":/iPhone|iOS/.test(e)?"iOS":"Other"}destroy(){this.flushInterval&&clearInterval(this.flushInterval),this.observer?.disconnect(),this.intersectionObserver?.disconnect(),this.flush()}}exports.FeedPulseTracker=n;
|
package/dist/index.mjs
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
1
|
class o {
|
|
2
2
|
constructor(e) {
|
|
3
3
|
this.queue = [], this.flushInterval = null, this.observer = null, this.intersectionObserver = null, this.seenInView = /* @__PURE__ */ new Set(), this.config = {
|
|
4
|
-
endpoint: "https://
|
|
4
|
+
endpoint: "https://feed-pulse.vercel.app/api/ingest",
|
|
5
5
|
batchInterval: 5e3,
|
|
6
6
|
...e
|
|
7
|
-
}, this.visitorId =
|
|
7
|
+
}, this.visitorId = "", this.sessionId = "";
|
|
8
8
|
}
|
|
9
9
|
init() {
|
|
10
|
-
this.startFlushInterval(), this.attachGlobalListeners(), this.observeDOM();
|
|
10
|
+
this.isBrowser() && (this.visitorId = this.getOrCreateVisitorId(), this.sessionId = this.generateSessionId(), this.startFlushInterval(), this.attachGlobalListeners(), this.observeDOM());
|
|
11
11
|
}
|
|
12
12
|
attachGlobalListeners() {
|
|
13
|
-
document.addEventListener("click", (
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
x:
|
|
17
|
-
y:
|
|
13
|
+
document.addEventListener("click", (t) => {
|
|
14
|
+
const r = t.target.closest("[data-fp-id]");
|
|
15
|
+
r && this.track("click", r.getAttribute("data-fp-id"), {
|
|
16
|
+
x: t.clientX,
|
|
17
|
+
y: t.clientY
|
|
18
18
|
});
|
|
19
19
|
}, { passive: !0 });
|
|
20
|
-
let e = 0,
|
|
21
|
-
document.addEventListener("mouseover", (
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
}, { passive: !0 }), document.addEventListener("mouseout", (
|
|
25
|
-
if (!
|
|
20
|
+
let e = 0, i = null;
|
|
21
|
+
document.addEventListener("mouseover", (t) => {
|
|
22
|
+
const r = t.target.closest("[data-fp-id]");
|
|
23
|
+
r && (e = Date.now(), i = r.getAttribute("data-fp-id"));
|
|
24
|
+
}, { passive: !0 }), document.addEventListener("mouseout", (t) => {
|
|
25
|
+
if (!t.target.closest("[data-fp-id]") || !i)
|
|
26
26
|
return;
|
|
27
|
-
const
|
|
28
|
-
|
|
27
|
+
const s = Date.now() - e;
|
|
28
|
+
s > 500 && this.track("hover", i, { hoverDuration: s }), i = null;
|
|
29
29
|
}, { passive: !0 });
|
|
30
30
|
}
|
|
31
31
|
observeDOM() {
|
|
32
|
-
this.intersectionObserver = new IntersectionObserver((
|
|
33
|
-
for (const
|
|
34
|
-
const
|
|
35
|
-
!
|
|
32
|
+
this.intersectionObserver = new IntersectionObserver((i) => {
|
|
33
|
+
for (const t of i) {
|
|
34
|
+
const r = t.target.dataset.fpId;
|
|
35
|
+
!r || !t.isIntersecting || this.seenInView.has(r) || (this.seenInView.add(r), this.track("scroll_into_view", r, {
|
|
36
36
|
scrollDepth: Math.round(window.scrollY / Math.max(1, document.body.scrollHeight) * 100)
|
|
37
37
|
}));
|
|
38
38
|
}
|
|
39
39
|
}, { threshold: 0.5 });
|
|
40
40
|
const e = () => {
|
|
41
|
-
document.querySelectorAll("[data-fp-id]").forEach((
|
|
42
|
-
this.intersectionObserver?.observe(
|
|
41
|
+
document.querySelectorAll("[data-fp-id]").forEach((i) => {
|
|
42
|
+
this.intersectionObserver?.observe(i);
|
|
43
43
|
});
|
|
44
44
|
};
|
|
45
45
|
e(), this.observer = new MutationObserver(() => e()), this.observer.observe(document.body, { childList: !0, subtree: !0 });
|
|
46
46
|
}
|
|
47
|
-
track(e,
|
|
48
|
-
this.queue.push({
|
|
47
|
+
track(e, i, t) {
|
|
48
|
+
this.isBrowser() && this.queue.push({
|
|
49
49
|
projectApiKey: this.config.apiKey,
|
|
50
|
-
elementId:
|
|
50
|
+
elementId: i,
|
|
51
51
|
eventType: e,
|
|
52
52
|
sessionId: this.sessionId,
|
|
53
53
|
visitorId: this.visitorId,
|
|
@@ -56,12 +56,12 @@ class o {
|
|
|
56
56
|
device: this.getDeviceType(),
|
|
57
57
|
browser: this.getBrowser(),
|
|
58
58
|
os: this.getOS(),
|
|
59
|
-
metadata:
|
|
59
|
+
metadata: t,
|
|
60
60
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
|
-
trackFeedback(e,
|
|
64
|
-
|
|
63
|
+
trackFeedback(e, i, t) {
|
|
64
|
+
this.isBrowser() && (!i && !t || this.sendImmediate([{
|
|
65
65
|
projectApiKey: this.config.apiKey,
|
|
66
66
|
elementId: e ?? "__page__",
|
|
67
67
|
eventType: "feedback",
|
|
@@ -69,9 +69,9 @@ class o {
|
|
|
69
69
|
visitorId: this.visitorId,
|
|
70
70
|
page: window.location.pathname,
|
|
71
71
|
device: this.getDeviceType(),
|
|
72
|
-
metadata: { rating:
|
|
72
|
+
metadata: { rating: i, message: t },
|
|
73
73
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
74
|
-
}]);
|
|
74
|
+
}]));
|
|
75
75
|
}
|
|
76
76
|
async flush() {
|
|
77
77
|
if (this.queue.length === 0)
|
|
@@ -96,9 +96,19 @@ class o {
|
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
98
|
getOrCreateVisitorId() {
|
|
99
|
-
const e = "__fp_vid";
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
const e = "__fp_vid", i = this.generateId();
|
|
100
|
+
try {
|
|
101
|
+
let t = localStorage.getItem(e);
|
|
102
|
+
return t || (t = i, localStorage.setItem(e, t)), t;
|
|
103
|
+
} catch {
|
|
104
|
+
return i;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
generateId() {
|
|
108
|
+
return typeof crypto < "u" && typeof crypto.randomUUID == "function" ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
109
|
+
}
|
|
110
|
+
isBrowser() {
|
|
111
|
+
return typeof window < "u" && typeof document < "u";
|
|
102
112
|
}
|
|
103
113
|
generateSessionId() {
|
|
104
114
|
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|