@tekibo/feedpulse-sdk 0.6.1 → 0.7.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/dist/nuxt.js +20 -1
- package/dist/nuxt.mjs +522 -104
- package/dist/react.js +21 -2
- package/dist/react.mjs +449 -189
- package/dist/struggle-detector-BqTHgw95.mjs +73 -0
- package/dist/struggle-detector-DV52qDLp.js +1 -0
- package/package.json +1 -1
- package/templates/nuxt-bot-proxy.ts +56 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
class h {
|
|
2
|
+
constructor(e) {
|
|
3
|
+
this.opts = e, this.score = 0, this.triggered = !1, this.cooldownUntil = 0, this.reasons = [], this.clickMap = /* @__PURE__ */ new Map(), this.lastActivity = Date.now(), this.scrollPositions = [], this.idleTimer = null, this.clickHandler = (t) => this.onClick(t), this.scrollHandler = () => this.onScroll(), this.activityHandler = () => this.onActivity(), this.errorHandler = () => this.onError(), this.popstateHandler = () => this.onBackNavigation(), this.formListeners = [];
|
|
4
|
+
}
|
|
5
|
+
start() {
|
|
6
|
+
if (typeof window > "u" || typeof document > "u")
|
|
7
|
+
return;
|
|
8
|
+
document.addEventListener("click", this.clickHandler, { passive: !0 }), document.addEventListener("scroll", this.scrollHandler, { passive: !0 }), document.addEventListener("keydown", this.activityHandler, { passive: !0 }), document.addEventListener("mousemove", this.activityHandler, { passive: !0 }), window.addEventListener("error", this.errorHandler), window.addEventListener("popstate", this.popstateHandler), document.querySelectorAll("input, textarea, select").forEach((i) => {
|
|
9
|
+
const s = (o) => this.onFormBlur(o);
|
|
10
|
+
i.addEventListener("blur", s), this.formListeners.push({ element: i, handler: s });
|
|
11
|
+
}), this.idleTimer = setInterval(() => this.checkIdle(), 5e3);
|
|
12
|
+
const e = `__fp_visit_${window.location.pathname}`, t = Number(sessionStorage.getItem(e) ?? 0) + 1;
|
|
13
|
+
sessionStorage.setItem(e, String(t)), t >= 2 && this.addScore(20, "repeated_page");
|
|
14
|
+
}
|
|
15
|
+
onClick(e) {
|
|
16
|
+
this.onActivity();
|
|
17
|
+
const t = e.target;
|
|
18
|
+
if (!t)
|
|
19
|
+
return;
|
|
20
|
+
const i = `${t.tagName}#${t.id}.${t.className}`, s = Date.now(), r = [...this.clickMap.get(i) ?? [], s].filter((l) => s - l < 2e3);
|
|
21
|
+
this.clickMap.set(i, r), r.length >= 3 && this.addScore(40, "rage_click");
|
|
22
|
+
const n = t.tagName.toLowerCase(), c = ["a", "button", "input", "select", "textarea"], a = t.onclick || t.getAttribute("onclick");
|
|
23
|
+
!c.includes(n) && !a && !t.closest("a, button") && this.addScore(20, "dead_click");
|
|
24
|
+
}
|
|
25
|
+
onScroll() {
|
|
26
|
+
this.onActivity();
|
|
27
|
+
const e = window.scrollY;
|
|
28
|
+
if (this.scrollPositions = [...this.scrollPositions.slice(-5), e], this.scrollPositions.length >= 4) {
|
|
29
|
+
let t = 0;
|
|
30
|
+
for (let i = 2; i < this.scrollPositions.length; i++) {
|
|
31
|
+
const s = this.scrollPositions[i - 2], o = this.scrollPositions[i - 1], r = this.scrollPositions[i];
|
|
32
|
+
(o - s) * (r - o) < 0 && t++;
|
|
33
|
+
}
|
|
34
|
+
t >= 3 && this.addScore(25, "rapid_scroll");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
onError() {
|
|
38
|
+
this.addScore(35, "js_error");
|
|
39
|
+
}
|
|
40
|
+
onBackNavigation() {
|
|
41
|
+
this.addScore(30, "back_navigation");
|
|
42
|
+
}
|
|
43
|
+
onActivity() {
|
|
44
|
+
this.lastActivity = Date.now();
|
|
45
|
+
}
|
|
46
|
+
onFormBlur(e) {
|
|
47
|
+
const t = e.target?.closest("form");
|
|
48
|
+
if (!t)
|
|
49
|
+
return;
|
|
50
|
+
t.querySelector("[data-submitted='true']") || this.addScore(25, "form_abandonment");
|
|
51
|
+
}
|
|
52
|
+
checkIdle() {
|
|
53
|
+
const e = Date.now() - this.lastActivity;
|
|
54
|
+
e > 3e4 && this.addScore(15, "idle"), e > 9e4 && this.addScore(20, "long_page_time");
|
|
55
|
+
}
|
|
56
|
+
addScore(e, t) {
|
|
57
|
+
Date.now() < this.cooldownUntil || this.triggered || (this.reasons.includes(t) || this.reasons.push(t), this.score += e, this.score >= 60 && (this.triggered = !0, this.cooldownUntil = Date.now() + 300 * 1e3, this.opts.onTrigger(this.score, [...this.reasons])));
|
|
58
|
+
}
|
|
59
|
+
reset() {
|
|
60
|
+
this.score = 0, this.triggered = !1, this.reasons = [];
|
|
61
|
+
}
|
|
62
|
+
stop() {
|
|
63
|
+
if (!(typeof window > "u" || typeof document > "u")) {
|
|
64
|
+
document.removeEventListener("click", this.clickHandler), document.removeEventListener("scroll", this.scrollHandler), document.removeEventListener("keydown", this.activityHandler), document.removeEventListener("mousemove", this.activityHandler), window.removeEventListener("error", this.errorHandler), window.removeEventListener("popstate", this.popstateHandler);
|
|
65
|
+
for (const e of this.formListeners)
|
|
66
|
+
e.element.removeEventListener("blur", e.handler);
|
|
67
|
+
this.formListeners = [], this.idleTimer && clearInterval(this.idleTimer);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
h as S
|
|
73
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";class d{constructor(e){this.opts=e,this.score=0,this.triggered=!1,this.cooldownUntil=0,this.reasons=[],this.clickMap=new Map,this.lastActivity=Date.now(),this.scrollPositions=[],this.idleTimer=null,this.clickHandler=t=>this.onClick(t),this.scrollHandler=()=>this.onScroll(),this.activityHandler=()=>this.onActivity(),this.errorHandler=()=>this.onError(),this.popstateHandler=()=>this.onBackNavigation(),this.formListeners=[]}start(){if(typeof window>"u"||typeof document>"u")return;document.addEventListener("click",this.clickHandler,{passive:!0}),document.addEventListener("scroll",this.scrollHandler,{passive:!0}),document.addEventListener("keydown",this.activityHandler,{passive:!0}),document.addEventListener("mousemove",this.activityHandler,{passive:!0}),window.addEventListener("error",this.errorHandler),window.addEventListener("popstate",this.popstateHandler),document.querySelectorAll("input, textarea, select").forEach(i=>{const s=o=>this.onFormBlur(o);i.addEventListener("blur",s),this.formListeners.push({element:i,handler:s})}),this.idleTimer=setInterval(()=>this.checkIdle(),5e3);const e=`__fp_visit_${window.location.pathname}`,t=Number(sessionStorage.getItem(e)??0)+1;sessionStorage.setItem(e,String(t)),t>=2&&this.addScore(20,"repeated_page")}onClick(e){this.onActivity();const t=e.target;if(!t)return;const i=`${t.tagName}#${t.id}.${t.className}`,s=Date.now(),r=[...this.clickMap.get(i)??[],s].filter(a=>s-a<2e3);this.clickMap.set(i,r),r.length>=3&&this.addScore(40,"rage_click");const n=t.tagName.toLowerCase(),c=["a","button","input","select","textarea"],l=t.onclick||t.getAttribute("onclick");!c.includes(n)&&!l&&!t.closest("a, button")&&this.addScore(20,"dead_click")}onScroll(){this.onActivity();const e=window.scrollY;if(this.scrollPositions=[...this.scrollPositions.slice(-5),e],this.scrollPositions.length>=4){let t=0;for(let i=2;i<this.scrollPositions.length;i++){const s=this.scrollPositions[i-2],o=this.scrollPositions[i-1],r=this.scrollPositions[i];(o-s)*(r-o)<0&&t++}t>=3&&this.addScore(25,"rapid_scroll")}}onError(){this.addScore(35,"js_error")}onBackNavigation(){this.addScore(30,"back_navigation")}onActivity(){this.lastActivity=Date.now()}onFormBlur(e){const t=e.target?.closest("form");if(!t)return;t.querySelector("[data-submitted='true']")||this.addScore(25,"form_abandonment")}checkIdle(){const e=Date.now()-this.lastActivity;e>3e4&&this.addScore(15,"idle"),e>9e4&&this.addScore(20,"long_page_time")}addScore(e,t){Date.now()<this.cooldownUntil||this.triggered||(this.reasons.includes(t)||this.reasons.push(t),this.score+=e,this.score>=60&&(this.triggered=!0,this.cooldownUntil=Date.now()+300*1e3,this.opts.onTrigger(this.score,[...this.reasons])))}reset(){this.score=0,this.triggered=!1,this.reasons=[]}stop(){if(!(typeof window>"u"||typeof document>"u")){document.removeEventListener("click",this.clickHandler),document.removeEventListener("scroll",this.scrollHandler),document.removeEventListener("keydown",this.activityHandler),document.removeEventListener("mousemove",this.activityHandler),window.removeEventListener("error",this.errorHandler),window.removeEventListener("popstate",this.popstateHandler);for(const e of this.formListeners)e.element.removeEventListener("blur",e.handler);this.formListeners=[],this.idleTimer&&clearInterval(this.idleTimer)}}}exports.StruggleDetector=d;
|
package/package.json
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const chatProxyRoute = `export default defineEventHandler(async (event) => {
|
|
2
|
+
const config = useRuntimeConfig(event);
|
|
3
|
+
const apiKey = config.feedpulseApiKey;
|
|
4
|
+
const feedpulseBaseUrl = config.feedpulseApiBaseUrl || "http://localhost:3000";
|
|
5
|
+
if (!apiKey)
|
|
6
|
+
throw createError({ statusCode: 500, statusMessage: "FEEDPULSE_API_KEY is not configured" });
|
|
7
|
+
|
|
8
|
+
const body = await readBody<Record<string, unknown>>(event);
|
|
9
|
+
const response = await fetch(\`\${feedpulseBaseUrl}/api/bot/chat\`, {
|
|
10
|
+
method: "POST",
|
|
11
|
+
headers: { "content-type": "application/json" },
|
|
12
|
+
body: JSON.stringify({ ...body, projectApiKey: apiKey }),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
return new Response(response.body, {
|
|
16
|
+
status: response.status,
|
|
17
|
+
headers: {
|
|
18
|
+
"content-type": response.headers.get("content-type") || "text/plain; charset=utf-8",
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
});`;
|
|
22
|
+
|
|
23
|
+
export const sessionProxyRoute = `export default defineEventHandler(async (event) => {
|
|
24
|
+
const config = useRuntimeConfig(event);
|
|
25
|
+
const apiKey = config.feedpulseApiKey;
|
|
26
|
+
const feedpulseBaseUrl = config.feedpulseApiBaseUrl || "http://localhost:3000";
|
|
27
|
+
if (!apiKey)
|
|
28
|
+
throw createError({ statusCode: 500, statusMessage: "FEEDPULSE_API_KEY is not configured" });
|
|
29
|
+
|
|
30
|
+
const body = await readBody<Record<string, unknown>>(event);
|
|
31
|
+
const response = await fetch(\`\${feedpulseBaseUrl}/api/bot/session/start\`, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers: { "content-type": "application/json" },
|
|
34
|
+
body: JSON.stringify({ ...body, projectApiKey: apiKey }),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const payload = await response.json().catch(() => ({ error: "Invalid response" }));
|
|
38
|
+
if (!response.ok)
|
|
39
|
+
throw createError({ statusCode: response.status, statusMessage: payload?.error || "Session proxy failed", data: payload });
|
|
40
|
+
return payload;
|
|
41
|
+
});`;
|
|
42
|
+
|
|
43
|
+
export const sessionEndProxyRoute = `export default defineEventHandler(async (event) => {
|
|
44
|
+
const config = useRuntimeConfig(event);
|
|
45
|
+
const feedpulseBaseUrl = config.feedpulseApiBaseUrl || "http://localhost:3000";
|
|
46
|
+
const body = await readBody<Record<string, unknown>>(event);
|
|
47
|
+
const response = await fetch(\`\${feedpulseBaseUrl}/api/bot/session/end\`, {
|
|
48
|
+
method: "POST",
|
|
49
|
+
headers: { "content-type": "application/json" },
|
|
50
|
+
body: JSON.stringify(body),
|
|
51
|
+
});
|
|
52
|
+
const payload = await response.json().catch(() => ({ error: "Invalid response" }));
|
|
53
|
+
if (!response.ok)
|
|
54
|
+
throw createError({ statusCode: response.status, statusMessage: payload?.error || "Session end proxy failed", data: payload });
|
|
55
|
+
return payload;
|
|
56
|
+
});`;
|