@heedb/web-sdk 0.1.0

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 ADDED
@@ -0,0 +1,49 @@
1
+ # @heedb/web-sdk
2
+
3
+ Drop-in feedback widget with email conversations for any website.
4
+
5
+ ## CDN (quickest)
6
+
7
+ ```html
8
+ <script
9
+ src="https://cdn.jsdelivr.net/npm/@heedb/web-sdk/dist/widget.js"
10
+ data-api-key="YOUR_API_KEY"
11
+ data-host="https://heedb.com"
12
+ ></script>
13
+ ```
14
+
15
+ ## npm
16
+
17
+ ```bash
18
+ npm install @heedb/web-sdk
19
+ ```
20
+
21
+ ```ts
22
+ import { Heedb } from "@heedb/web-sdk";
23
+
24
+ Heedb.init({
25
+ apiKey: "YOUR_API_KEY",
26
+ email: user.email, // optional
27
+ name: user.name, // optional
28
+ userHash: serverHash, // optional — HMAC-SHA256 for verified identity
29
+ });
30
+ ```
31
+
32
+ ## Identify users
33
+
34
+ Generate a `userHash` on your server to verify user identity:
35
+
36
+ ```js
37
+ const crypto = require("crypto");
38
+ const userHash = crypto
39
+ .createHmac("sha256", WIDGET_SECRET)
40
+ .update(userEmail)
41
+ .digest("hex");
42
+ ```
43
+
44
+ Then pass it to `Heedb.init({ email, userHash })`.
45
+
46
+ ## Links
47
+
48
+ - [Dashboard](https://heedb.com)
49
+ - [GitHub](https://github.com/TheAleSch/heedb)
package/dist/index.cjs ADDED
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Heedb: () => Heedb
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+ var DEFAULT_HOST = "https://heedb.com";
27
+ var Heedb = {
28
+ _scriptLoaded: false,
29
+ /**
30
+ * Load the widget and optionally identify the user.
31
+ *
32
+ * ```ts
33
+ * import { Heedb } from "@heedb/web-sdk";
34
+ * Heedb.init({ apiKey: "your_key", email: user.email, userHash: hash });
35
+ * ```
36
+ */
37
+ init(opts) {
38
+ var _a;
39
+ const host = (_a = opts.host) != null ? _a : DEFAULT_HOST;
40
+ if (!this._scriptLoaded) {
41
+ const script = document.createElement("script");
42
+ script.src = `${host}/widget.js`;
43
+ script.setAttribute("data-api-key", opts.apiKey);
44
+ script.setAttribute("data-host", host);
45
+ script.onload = () => {
46
+ this._scriptLoaded = true;
47
+ this._identify(opts);
48
+ };
49
+ document.body.appendChild(script);
50
+ } else {
51
+ this._identify(opts);
52
+ }
53
+ },
54
+ /** @internal */
55
+ _identify(opts) {
56
+ const w = window;
57
+ if (w.Heedb && typeof w.Heedb.init === "function") {
58
+ w.Heedb.init({
59
+ email: opts.email,
60
+ name: opts.name,
61
+ userHash: opts.userHash
62
+ });
63
+ }
64
+ }
65
+ };
@@ -0,0 +1,13 @@
1
+ export interface HeedbOptions {
2
+ apiKey: string;
3
+ host?: string;
4
+ email?: string;
5
+ name?: string;
6
+ userHash?: string;
7
+ }
8
+
9
+ export declare const Heedb: {
10
+ _scriptLoaded: boolean;
11
+ init(opts: HeedbOptions): void;
12
+ _identify(opts: HeedbOptions): void;
13
+ };
package/dist/index.js ADDED
@@ -0,0 +1,44 @@
1
+ // src/index.ts
2
+ var DEFAULT_HOST = "https://heedb.com";
3
+ var Heedb = {
4
+ _scriptLoaded: false,
5
+ /**
6
+ * Load the widget and optionally identify the user.
7
+ *
8
+ * ```ts
9
+ * import { Heedb } from "@heedb/web-sdk";
10
+ * Heedb.init({ apiKey: "your_key", email: user.email, userHash: hash });
11
+ * ```
12
+ */
13
+ init(opts) {
14
+ var _a;
15
+ const host = (_a = opts.host) != null ? _a : DEFAULT_HOST;
16
+ if (!this._scriptLoaded) {
17
+ const script = document.createElement("script");
18
+ script.src = `${host}/widget.js`;
19
+ script.setAttribute("data-api-key", opts.apiKey);
20
+ script.setAttribute("data-host", host);
21
+ script.onload = () => {
22
+ this._scriptLoaded = true;
23
+ this._identify(opts);
24
+ };
25
+ document.body.appendChild(script);
26
+ } else {
27
+ this._identify(opts);
28
+ }
29
+ },
30
+ /** @internal */
31
+ _identify(opts) {
32
+ const w = window;
33
+ if (w.Heedb && typeof w.Heedb.init === "function") {
34
+ w.Heedb.init({
35
+ email: opts.email,
36
+ name: opts.name,
37
+ userHash: opts.userHash
38
+ });
39
+ }
40
+ }
41
+ };
42
+ export {
43
+ Heedb
44
+ };
package/dist/widget.js ADDED
@@ -0,0 +1,84 @@
1
+ /* Heedb Widget — https://heedb.com */
2
+ "use strict";(()=>{(function(){var $,q,A,O,R;let u=document.currentScript||document.querySelector("script[data-api-key]"),w=($=u==null?void 0:u.getAttribute("data-api-key"))!=null?$:"",_=(q=u==null?void 0:u.src)!=null?q:"",H=(u==null?void 0:u.getAttribute("data-host"))||(_?new URL(_).origin:"");if(!w){console.warn("[Heedb] Missing data-api-key on <script> tag.");return}let k=!1,x="contact",v=(A=localStorage.getItem("heedb_name"))!=null?A:"",r=(O=localStorage.getItem("heedb_email"))!=null?O:"",d=(R=localStorage.getItem("heedb_token"))!=null?R:"",b=null,I=null;async function U(e,l){try{let a=await B("/api/threads/token",{api_key:w,email:e,userHash:l});if(a.ok){let t=await a.json();if(t.widgetToken){d=t.widgetToken,localStorage.setItem("heedb_token",t.widgetToken);return}}d="",localStorage.removeItem("heedb_token")}catch(a){d="",localStorage.removeItem("heedb_token")}}window.Heedb={init(e={}){if(e.name&&(v=e.name,localStorage.setItem("heedb_name",e.name)),e.email){let l=e.email!==r;r=e.email,localStorage.setItem("heedb_email",e.email),e.userHash?U(e.email,e.userHash).then(()=>{z(),k&&b&&y()}):(z(),k&&b&&y())}}};function B(e,l){return fetch(`${H}${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(l)})}function D(e){let l=new URL(`${H}/api/threads`);return l.searchParams.set("api_key",w),l.searchParams.set("email",e),d&&l.searchParams.set("token",d),fetch(l.toString()).then(a=>a.json())}let K=`
3
+ .llp-btn {
4
+ position: fixed; bottom: 24px; right: 24px; z-index: 999998;
5
+ width: 52px; height: 52px; border-radius: 50%; border: none;
6
+ background: #18181b; color: #fff; font-size: 22px; cursor: pointer;
7
+ box-shadow: 0 4px 16px rgba(0,0,0,.25);
8
+ display: flex; align-items: center; justify-content: center;
9
+ transition: transform .15s;
10
+ }
11
+ .llp-btn:hover { transform: scale(1.08); }
12
+ .llp-panel {
13
+ position: fixed; bottom: 88px; right: 24px; z-index: 999999;
14
+ width: 340px; max-height: 540px; border-radius: 16px;
15
+ background: #fff; box-shadow: 0 8px 32px rgba(0,0,0,.18);
16
+ display: flex; flex-direction: column; overflow: hidden;
17
+ font-family: system-ui, -apple-system, sans-serif; font-size: 14px;
18
+ transition: opacity .15s, transform .15s;
19
+ }
20
+ .llp-header {
21
+ background: #18181b; color: #fff; padding: 14px 16px;
22
+ display: flex; align-items: center; justify-content: space-between;
23
+ }
24
+ .llp-header h2 { margin: 0; font-size: 15px; font-weight: 600; }
25
+ .llp-close {
26
+ background: none; border: none; color: #fff; cursor: pointer;
27
+ font-size: 18px; line-height: 1; padding: 0;
28
+ }
29
+ .llp-tabs {
30
+ display: flex; border-bottom: 1px solid #e4e4e7;
31
+ }
32
+ .llp-tab {
33
+ flex: 1; padding: 10px 0; border: none; background: none;
34
+ cursor: pointer; font-size: 13px; font-weight: 500; color: #71717a;
35
+ border-bottom: 2px solid transparent; margin-bottom: -1px;
36
+ }
37
+ .llp-tab.active { color: #18181b; border-bottom-color: #18181b; }
38
+ .llp-body { padding: 16px; overflow-y: auto; flex: 1; }
39
+ .llp-label { display: block; font-size: 12px; font-weight: 600; color: #52525b; margin-bottom: 4px; }
40
+ .llp-input, .llp-textarea, .llp-select {
41
+ width: 100%; box-sizing: border-box; padding: 8px 10px;
42
+ border: 1px solid #d4d4d8; border-radius: 8px;
43
+ font-size: 13px; color: #18181b; margin-bottom: 10px;
44
+ font-family: inherit; background: #fff;
45
+ }
46
+ .llp-textarea { min-height: 80px; resize: vertical; }
47
+ .llp-input:focus, .llp-textarea:focus, .llp-select:focus {
48
+ outline: none; border-color: #18181b;
49
+ }
50
+ .llp-btn-submit {
51
+ width: 100%; padding: 10px; background: #18181b; color: #fff;
52
+ border: none; border-radius: 8px; font-size: 14px; font-weight: 600;
53
+ cursor: pointer; transition: opacity .15s;
54
+ }
55
+ .llp-btn-submit:hover { opacity: .85; }
56
+ .llp-btn-submit:disabled { opacity: .5; cursor: not-allowed; }
57
+ .llp-error { color: #ef4444; font-size: 12px; margin-bottom: 8px; }
58
+ .llp-success { text-align: center; padding: 24px 8px; }
59
+ .llp-success-icon { font-size: 36px; }
60
+ .llp-success h3 { margin: 12px 0 6px; font-size: 15px; color: #18181b; }
61
+ .llp-success p { margin: 0; color: #71717a; font-size: 13px; line-height: 1.5; }
62
+ .llp-thread-item {
63
+ padding: 10px 12px; border: 1px solid #e4e4e7; border-radius: 8px;
64
+ margin-bottom: 8px; text-decoration: none; display: block; color: inherit;
65
+ transition: background .1s;
66
+ }
67
+ .llp-thread-item:hover { background: #f4f4f5; }
68
+ .llp-thread-label { font-size: 12px; color: #71717a; margin-bottom: 2px; }
69
+ .llp-thread-text { font-size: 13px; color: #18181b; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
70
+ .llp-thread-meta { font-size: 11px; color: #a1a1aa; margin-top: 3px; }
71
+ .llp-badge { display: inline-block; padding: 1px 6px; border-radius: 9999px; font-size: 10px; font-weight: 600; }
72
+ .llp-badge-open { background: #d1fae5; color: #065f46; }
73
+ .llp-badge-replied { background: #dbeafe; color: #1e40af; }
74
+ .llp-new-msg { margin-top: 12px; padding-top: 12px; border-top: 1px solid #e4e4e7; }
75
+ .llp-new-msg button {
76
+ width: 100%; padding: 8px; background: none; border: 1px solid #d4d4d8;
77
+ border-radius: 8px; font-size: 13px; cursor: pointer; color: #52525b;
78
+ }
79
+ .llp-new-msg button:hover { background: #f4f4f5; }
80
+ `;function n(e,l={},a=[]){let t=document.createElement(e);for(let[o,s]of Object.entries(l))o==="class"?t.className=s:o==="style"?t.setAttribute("style",s):o.startsWith("on")&&typeof s=="function"?t.addEventListener(o.slice(2),s):t.setAttribute(o,String(s));for(let o of a)t.appendChild(typeof o=="string"?document.createTextNode(o):o);return t}function S(e,l,a=""){let t=document.createElement("input");return t.type=e,t.id=l,t.placeholder=a,t.className="llp-input",t}function F(e,l=""){let a=document.createElement("textarea");return a.id=e,a.placeholder=l,a.className="llp-textarea",a}function J(e,l){let a=document.createElement("select");a.id=e,a.className="llp-select";for(let t of l){let o=document.createElement("option");o.value=t.value,o.textContent=t.label,a.appendChild(o)}return a}function y(){if(!b)return;let e=b.querySelector(".llp-body");e.innerHTML="",x==="contact"?W(e):x==="privacy"?V(e):x==="threads"&&Y(e)}function W(e,l=""){let a=!!(v&&r),t=n("label",{class:"llp-label"},["Name"]),o=S("text","llp-name","Jane Smith");v&&(o.value=v);let s=n("label",{class:"llp-label"},["Email"]),m=S("email","llp-email","jane@example.com");(l||r)&&(m.value=l||r);let g=n("label",{class:"llp-label"},["Message"]),c=F("llp-message","How can we help?"),i=n("div",{class:"llp-error",style:"display:none"}),p=n("button",{class:"llp-btn-submit"},["Send message"]);p.onclick=async()=>{let f=o.value.trim(),h=m.value.trim(),E=c.value.trim();if(i.style.display="none",!f||!h||!E){i.textContent="Please fill in all fields.",i.style.display="block";return}p.disabled=!0,p.textContent="Sending\u2026";try{let C=await B("/api/contact",{api_key:w,name:f,email:h,message:E});if(C.ok)r=h,localStorage.setItem("heedb_email",h),z(),j(e,"Message sent!","We'll get back to you soon. Check your inbox for a confirmation email.");else{let G=await C.json();i.textContent=G.error||"Something went wrong.",i.style.display="block",p.disabled=!1,p.textContent="Send message"}}catch(C){i.textContent="Network error. Please try again.",i.style.display="block",p.disabled=!1,p.textContent="Send message"}},a?e.append(g,c,i,p):e.append(t,o,s,m,g,c,i,p)}function V(e){let l=!!(v&&r),a=n("label",{class:"llp-label"},["Name"]),t=S("text","llp-priv-name","Jane Smith");v&&(t.value=v);let o=n("label",{class:"llp-label"},["Email"]),s=S("email","llp-priv-email","jane@example.com");r&&(s.value=r);let m=n("label",{class:"llp-label"},["Request type"]),g=J("llp-priv-type",[{value:"deletion",label:"Delete my data"},{value:"access",label:"Access my data"},{value:"portability",label:"Export my data"},{value:"correction",label:"Correct my data"},{value:"restriction",label:"Restrict processing"},{value:"objection",label:"Object to processing"},{value:"other",label:"Other"}]),c=n("div",{class:"llp-error",style:"display:none"}),i=n("button",{class:"llp-btn-submit"},["Submit request"]);i.onclick=async()=>{let p=t.value.trim(),f=s.value.trim(),h=g.value;if(c.style.display="none",!p||!f){c.textContent="Please fill in all fields.",c.style.display="block";return}i.disabled=!0,i.textContent="Submitting\u2026";try{let E=await B("/api/privacy-request",{api_key:w,name:p,email:f,request_type:h});if(E.ok)r=f,localStorage.setItem("heedb_email",f),z(),j(e,"Request submitted!","Your privacy request has been received. We'll respond within 30 days.");else{let C=await E.json();c.textContent=C.error||"Something went wrong.",c.style.display="block",i.disabled=!1,i.textContent="Submit request"}}catch(E){c.textContent="Network error. Please try again.",c.style.display="block",i.disabled=!1,i.textContent="Submit request"}},l?e.append(m,g,c,i):e.append(a,t,o,s,m,g,c,i)}async function Y(e){var l;if(!d){e.innerHTML="";let a=n("p",{style:"color:#71717a;text-align:center;margin:8px 0;font-size:13px;line-height:1.5"},["Send a message below to view your message history."]),t=n("div",{class:"llp-new-msg"}),o=n("button",{},["+ Send a message"]);o.onclick=()=>{x="contact",L("contact"),y()},t.appendChild(o),e.append(a,t);return}e.innerHTML='<p style="color:#71717a;text-align:center">Loading\u2026</p>';try{let{threads:a}=await D(r);if(e.innerHTML="",a.length===0){let s=n("p",{style:"color:#71717a;text-align:center;margin:8px 0"},["No open messages yet."]);e.appendChild(s)}else for(let s of a){let m=n("span",{class:`llp-badge llp-badge-${s.status}`},[s.status]),g=n("div",{class:"llp-thread-label"},[s.type==="privacy"?"Privacy request":"Message"," \u2022 "]);g.appendChild(m);let c=n("div",{class:"llp-thread-text"},[(l=s.preview)!=null?l:""]),i=new Date(s.createdAt).toLocaleDateString(),p=n("div",{class:"llp-thread-meta"},[i]),f=`${H}/t/${encodeURIComponent(s.id)}?email=${encodeURIComponent(r)}&token=${encodeURIComponent(d)}&apiKey=${encodeURIComponent(w)}`,h=n("a",{class:"llp-thread-item",href:f,target:"_blank",rel:"noopener noreferrer"});h.append(g,c,p),e.appendChild(h)}let t=n("div",{class:"llp-new-msg"}),o=n("button",{},["+ Send a new message"]);o.onclick=()=>{x="contact",L("contact"),y()},t.appendChild(o),e.appendChild(t)}catch(a){e.innerHTML='<p style="color:#ef4444;text-align:center">Failed to load messages.</p>'}}function j(e,l,a){e.innerHTML="";let t=n("div",{class:"llp-success"});t.innerHTML=`
81
+ <div class="llp-success-icon">\u2705</div>
82
+ <h3>${l}</h3>
83
+ <p>${a}</p>
84
+ `;let o=n("a",{href:`${H}/app?email=${encodeURIComponent(r)}`,target:"_blank",style:"display:inline-block;margin-top:12px;color:#18181b;font-size:13px;font-weight:600"},["View your messages \u2192"]);t.appendChild(o),e.appendChild(t)}let M=null,T=null;function L(e){x=e,M&&M.querySelectorAll(".llp-tab").forEach(l=>{l.classList.toggle("active",l.dataset.mode===e)})}function z(){T&&(T.style.display=r&&d?"":"none")}function N(){let e=document.createElement("style");e.textContent=K,document.head.appendChild(e),I=n("button",{class:"llp-btn",title:"Contact us","aria-label":"Open contact widget"},["\u{1F4AC}"]),I.onclick=P,document.body.appendChild(I),b=n("div",{class:"llp-panel",style:"display:none"});let l=n("div",{class:"llp-header"}),a=n("h2",{},["Contact us"]),t=n("button",{class:"llp-close","aria-label":"Close"},["\u2715"]);t.onclick=P,l.append(a,t),M=n("div",{class:"llp-tabs"});let o=n("button",{class:"llp-tab active","data-mode":"contact"},["Message"]),s=n("button",{class:"llp-tab","data-mode":"privacy"},["Privacy"]);T=n("button",{class:"llp-tab","data-mode":"threads"},["Messages"]),T.style.display=r&&d?"":"none",o.onclick=()=>{L("contact"),y()},s.onclick=()=>{L("privacy"),y()},T.onclick=()=>{L("threads"),y()},M.append(o,s,T);let m=n("div",{class:"llp-body"});b.append(l,M,m),document.body.appendChild(b)}function P(){k=!k,b&&(b.style.display=k?"flex":"none",k&&(r&&d&&x!=="privacy"&&(x="threads",L("threads")),y()))}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",N):N()})();})();
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@heedb/web-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Drop-in feedback widget with email conversations for any website",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/TheAleSch/heedb.git",
9
+ "directory": "packages/web-sdk"
10
+ },
11
+ "homepage": "https://heedb.com",
12
+ "keywords": ["feedback", "widget", "support", "email", "customer-support", "sdk"],
13
+ "type": "module",
14
+ "main": "./dist/index.js",
15
+ "module": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.js",
21
+ "require": "./dist/index.cjs"
22
+ },
23
+ "./widget": "./dist/widget.js"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md"
28
+ ],
29
+ "scripts": {
30
+ "build": "node build.mjs"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "devDependencies": {
36
+ "esbuild": "0.27.3"
37
+ }
38
+ }