@tekibo/feedpulse-sdk 2.0.1 → 2.1.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/README.md CHANGED
@@ -21,35 +21,6 @@ The SDK automatically tracks:
21
21
  - `scroll_into_view`: when tagged elements enter viewport
22
22
  - `feedback`: explicit rating/message submissions from widget or manual API
23
23
 
24
- Each event includes context such as session id, visitor id, page path, device, browser, OS, metadata, and timestamp.
25
-
26
- ## Backend requirements
27
-
28
- Your backend must expose an ingest endpoint compatible with this payload:
29
-
30
- ```json
31
- {
32
- "events": [
33
- {
34
- "projectApiKey": "fp_live_xxx",
35
- "elementId": "hero-cta",
36
- "eventType": "click",
37
- "sessionId": "session-1",
38
- "visitorId": "visitor-1",
39
- "page": "/",
40
- "device": "desktop",
41
- "metadata": {},
42
- "timestamp": "2026-03-12T00:00:00.000Z"
43
- }
44
- ]
45
- }
46
- ```
47
-
48
- In the FeedPulse codebase, the default server endpoint is:
49
-
50
- - `POST /api/ingest`
51
-
52
- ## Quick start (Nuxt)
53
24
 
54
25
  Import from `@tekibo/feedpulse-sdk/nuxt` and wrap your app:
55
26
 
@@ -60,12 +31,16 @@ import { FeedPulseProvider, FeedPulseWidget } from "@tekibo/feedpulse-sdk/nuxt";
60
31
 
61
32
  <template>
62
33
  <FeedPulseProvider api-key="fp_live_your_project_key">
63
- <NuxtPage />
34
+ <FeedPulseProvider
35
+ api-key="fp_live_your_project_key"
36
+ endpoint="https://feed-pulse.vercel.app/api/ingest"
37
+ >
64
38
  <FeedPulseWidget id="global-feedback" position="bottom-right" />
65
39
  </FeedPulseProvider>
66
40
  </template>
67
41
  ```
68
42
 
43
+
69
44
  Tag any interactive element with `data-fp-id`:
70
45
 
71
46
  ```vue
@@ -112,7 +87,10 @@ function SaveButton() {
112
87
 
113
88
  export default function App() {
114
89
  return (
115
- <FeedPulseProvider apiKey="fp_live_your_project_key">
90
+ <FeedPulseProvider
91
+ apiKey="fp_live_your_project_key"
92
+ endpoint="https://feed-pulse.vercel.app/api/ingest"
93
+ >
116
94
  <button data-fp-id="hero-cta">Get Started</button>
117
95
  <SaveButton />
118
96
  <FeedPulseWidget id="global-feedback" position="bottom-right" />
@@ -132,7 +110,7 @@ import { FeedPulseTracker } from "@tekibo/feedpulse-sdk";
132
110
  `FeedPulseTracker` configuration:
133
111
 
134
112
  - `apiKey: string` required
135
- - `endpoint?: string` optional, defaults to `https://feedpulse.dev/api/ingest`
113
+ - `endpoint?: string` optional, defaults to `https://feed-pulse.vercel.app/api/ingest`
136
114
  - `batchInterval?: number` optional, defaults to `5000` ms
137
115
 
138
116
  Key methods:
@@ -150,32 +128,6 @@ Nuxt and React widgets support:
150
128
  - `placeholder?: string` prompt text
151
129
  - `position?: "bottom-right" | "bottom-left" | "inline"` default is `"bottom-right"`
152
130
 
153
- ## Endpoint override
154
-
155
- Use this when sending to staging/self-hosted backend:
156
-
157
- ### Nuxt
158
-
159
- ```vue
160
- <FeedPulseProvider
161
- api-key="fp_live_your_project_key"
162
- endpoint="https://staging.your-domain.com/api/ingest"
163
- >
164
- <NuxtPage />
165
- </FeedPulseProvider>
166
- ```
167
-
168
- ### React
169
-
170
- ```tsx
171
- <FeedPulseProvider
172
- apiKey="fp_live_your_project_key"
173
- endpoint="https://staging.your-domain.com/api/ingest"
174
- >
175
- {children}
176
- </FeedPulseProvider>
177
- ```
178
-
179
131
  ## Integration checklist for your app
180
132
 
181
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://feedpulse.dev/api/ingest",batchInterval:5e3,...e},this.visitorId=this.getOrCreateVisitorId(),this.sessionId=this.generateSessionId()}init(){this.startFlushInterval(),this.attachGlobalListeners(),this.observeDOM()}attachGlobalListeners(){document.addEventListener("click",i=>{const s=i.target.closest("[data-fp-id]");s&&this.track("click",s.getAttribute("data-fp-id"),{x:i.clientX,y:i.clientY})},{passive:!0});let e=0,t=null;document.addEventListener("mouseover",i=>{const s=i.target.closest("[data-fp-id]");s&&(e=Date.now(),t=s.getAttribute("data-fp-id"))},{passive:!0}),document.addEventListener("mouseout",i=>{if(!i.target.closest("[data-fp-id]")||!t)return;const r=Date.now()-e;r>500&&this.track("hover",t,{hoverDuration:r}),t=null},{passive:!0})}observeDOM(){this.intersectionObserver=new IntersectionObserver(t=>{for(const i of t){const s=i.target.dataset.fpId;!s||!i.isIntersecting||this.seenInView.has(s)||(this.seenInView.add(s),this.track("scroll_into_view",s,{scrollDepth:Math.round(window.scrollY/Math.max(1,document.body.scrollHeight)*100)}))}},{threshold:.5});const e=()=>{document.querySelectorAll("[data-fp-id]").forEach(t=>{this.intersectionObserver?.observe(t)})};e(),this.observer=new MutationObserver(()=>e()),this.observer.observe(document.body,{childList:!0,subtree:!0})}track(e,t,i){this.queue.push({projectApiKey:this.config.apiKey,elementId:t,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:i,timestamp:new Date().toISOString()})}trackFeedback(e,t,i){!t&&!i||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:t,message:i},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";let t=localStorage.getItem(e);return t||(t=crypto.randomUUID(),localStorage.setItem(e,t)),t}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;
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.getOrCreateVisitorId(),this.sessionId=this.generateSessionId()}init(){this.startFlushInterval(),this.attachGlobalListeners(),this.observeDOM()}attachGlobalListeners(){document.addEventListener("click",i=>{const s=i.target.closest("[data-fp-id]");s&&this.track("click",s.getAttribute("data-fp-id"),{x:i.clientX,y:i.clientY})},{passive:!0});let e=0,t=null;document.addEventListener("mouseover",i=>{const s=i.target.closest("[data-fp-id]");s&&(e=Date.now(),t=s.getAttribute("data-fp-id"))},{passive:!0}),document.addEventListener("mouseout",i=>{if(!i.target.closest("[data-fp-id]")||!t)return;const r=Date.now()-e;r>500&&this.track("hover",t,{hoverDuration:r}),t=null},{passive:!0})}observeDOM(){this.intersectionObserver=new IntersectionObserver(t=>{for(const i of t){const s=i.target.dataset.fpId;!s||!i.isIntersecting||this.seenInView.has(s)||(this.seenInView.add(s),this.track("scroll_into_view",s,{scrollDepth:Math.round(window.scrollY/Math.max(1,document.body.scrollHeight)*100)}))}},{threshold:.5});const e=()=>{document.querySelectorAll("[data-fp-id]").forEach(t=>{this.intersectionObserver?.observe(t)})};e(),this.observer=new MutationObserver(()=>e()),this.observer.observe(document.body,{childList:!0,subtree:!0})}track(e,t,i){this.queue.push({projectApiKey:this.config.apiKey,elementId:t,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:i,timestamp:new Date().toISOString()})}trackFeedback(e,t,i){!t&&!i||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:t,message:i},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";let t=localStorage.getItem(e);return t||(t=crypto.randomUUID(),localStorage.setItem(e,t)),t}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,7 +1,7 @@
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://feedpulse.dev/api/ingest",
4
+ endpoint: "https://feed-pulse.vercel.app/api/ingest",
5
5
  batchInterval: 5e3,
6
6
  ...e
7
7
  }, this.visitorId = this.getOrCreateVisitorId(), this.sessionId = this.generateSessionId();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekibo/feedpulse-sdk",
3
- "version": "2.0.1",
3
+ "version": "2.1.1",
4
4
  "description": "Embeddable analytics and feedback SDK for FeedPulse",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",