@harkly/js 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/dist/api.d.ts +9 -0
- package/dist/api.js +4 -0
- package/dist/api.js.map +10 -0
- package/dist/generated/api-types.d.ts +4794 -0
- package/dist/harkly.iife.js +21 -0
- package/dist/index.d.ts +93 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +12 -0
- package/dist/messenger.d.ts +27 -0
- package/dist/widgets.d.ts +32 -0
- package/package.json +57 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
(()=>{var M=Object.defineProperty;var N=(e,r)=>{for(var t in r)M(e,t,{get:r[t],enumerable:!0,configurable:!0,set:(y)=>r[t]=()=>y})};var E={};N(E,{updates:()=>A,unread:()=>K,reset:()=>s,messenger:()=>G,init:()=>Z,identify:()=>d,feedback:()=>T,HarklyClient:()=>L});function b(e,r,t,y){let a=new URL(`/widget/${r}`,e);for(let[k,m]of Object.entries(t))if(m)a.searchParams.set(k,m);let f=new URLSearchParams;if(y.jwt)f.set("jwt",y.jwt);if(y.anonymousId)f.set("anon",y.anonymousId);let h=f.toString();return h?`${a.toString()}#${h}`:a.toString()}function O(e){if(!e||typeof e!=="object")return null;let r=e;if(r.type==="harkly:close")return{type:"harkly:close"};if(r.type==="harkly:posted"&&typeof r.postId==="string")return{type:"harkly:posted",postId:r.postId};if(r.type==="harkly:height"&&typeof r.height==="number")return{type:"harkly:height",height:r.height};return null}var F="harkly-widget-style",V=`
|
|
2
|
+
.harkly-overlay{position:fixed;inset:0;z-index:2147483000;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;padding:16px;opacity:0;transition:opacity .18s ease}
|
|
3
|
+
.harkly-overlay[data-open]{opacity:1}
|
|
4
|
+
.harkly-frame{border:0;width:min(560px,100%);height:min(640px,100%);border-radius:14px;background:transparent;box-shadow:0 24px 64px rgba(0,0,0,.35);transform:translateY(8px) scale(.98);transition:transform .22s cubic-bezier(.22,1,.36,1)}
|
|
5
|
+
.harkly-overlay[data-open] .harkly-frame{transform:none}
|
|
6
|
+
.harkly-overlay[data-side] {justify-content:flex-end;padding:0}
|
|
7
|
+
.harkly-overlay[data-side] .harkly-frame{width:min(420px,100%);height:100%;border-radius:0;transform:translateX(24px)}
|
|
8
|
+
.harkly-overlay[data-side][data-open] .harkly-frame{transform:none}
|
|
9
|
+
@media (prefers-reduced-motion: reduce){.harkly-overlay,.harkly-frame{transition:none}}`;function q(e){if(e.getElementById(F))return;let r=e.createElement("style");r.id=F,r.textContent=V,e.head.appendChild(r)}function U(e,r,t,y){q(e);let a=e.createElement("div");if(a.className="harkly-overlay",y.side)a.dataset.side="";let f=e.createElement("iframe");f.className="harkly-frame",f.src=r,f.title="Harkly",f.setAttribute("allow","clipboard-write"),a.appendChild(f);let h=e.activeElement,k=()=>{e.defaultView?.removeEventListener("message",m),e.removeEventListener("keydown",n),a.removeAttribute("data-open"),setTimeout(()=>a.remove(),200),h?.focus?.(),y.onClose?.()},m=(g)=>{if(g.origin!==t||g.source!==f.contentWindow)return;let v=O(g.data);if(!v)return;if(v.type==="harkly:close")k();if(v.type==="harkly:posted")y.onPosted?.(v.postId)},n=(g)=>{if(g.key==="Escape")k()};return a.addEventListener("click",(g)=>{if(g.target===a)k()}),e.defaultView?.addEventListener("message",m),e.addEventListener("keydown",n),e.body.appendChild(a),requestAnimationFrame(()=>{a.dataset.open="",f.focus()}),{close:k}}function B(e){let r=e.replace(/[.+^${}()|[\]\\]/g,"\\$&").split("**").map((t)=>t.replace(/\*/g,"[^/]*")).join(".*");return new RegExp(`^${r}/?$`)}function $(e,r){if(!r||r.mode==="all"||r.patterns.length===0)return!0;let t=r.patterns.some((y)=>B(y).test(e));return r.mode==="include"?t:!t}function z(e,r){if(!r)return e;return{position:e.position??r.position,color:e.color??r.color??void 0,launcher:e.launcher??r.launcher,theme:e.theme}}var W="harkly-messenger-style",D=`
|
|
10
|
+
.harkly-launcher{position:fixed;bottom:20px;z-index:2147483000;width:52px;height:52px;border:0;border-radius:50%;cursor:pointer;color:#fff;background:var(--harkly-color,#111);box-shadow:0 8px 24px rgba(0,0,0,.22);display:flex;align-items:center;justify-content:center;transition:transform .18s ease}
|
|
11
|
+
.harkly-launcher:hover{transform:scale(1.05)}
|
|
12
|
+
.harkly-launcher[data-side=right]{right:20px}.harkly-launcher[data-side=left]{left:20px}
|
|
13
|
+
.harkly-launcher svg{width:24px;height:24px;transition:opacity .15s,transform .2s}
|
|
14
|
+
.harkly-launcher[data-open] .harkly-ic-chat{opacity:0;transform:scale(.6)}.harkly-launcher:not([data-open]) .harkly-ic-x{opacity:0;transform:scale(.6)}
|
|
15
|
+
.harkly-launcher .harkly-ic-x{position:absolute}
|
|
16
|
+
.harkly-badge{position:absolute;top:-2px;right:-2px;min-width:18px;height:18px;padding:0 5px;border-radius:9px;background:#e5484d;color:#fff;font:600 11px/18px system-ui,sans-serif;text-align:center}
|
|
17
|
+
.harkly-messenger{position:fixed;bottom:84px;z-index:2147483000;width:min(400px,calc(100vw - 32px));height:min(704px,calc(100vh - 104px));border:0;border-radius:16px;background:transparent;box-shadow:0 24px 64px rgba(0,0,0,.28);opacity:0;pointer-events:none;transform:translateY(12px) scale(.98);transform-origin:bottom right;transition:opacity .18s ease,transform .22s cubic-bezier(.22,1,.36,1)}
|
|
18
|
+
.harkly-messenger[data-side=right]{right:20px}.harkly-messenger[data-side=left]{left:20px;transform-origin:bottom left}
|
|
19
|
+
.harkly-messenger[data-open]{opacity:1;pointer-events:auto;transform:none}
|
|
20
|
+
@media (max-width:480px){.harkly-messenger[data-open]{inset:0;width:100%;height:100%;border-radius:0}}
|
|
21
|
+
@media (prefers-reduced-motion: reduce){.harkly-launcher,.harkly-messenger{transition:none}}`,R='<svg class="harkly-ic-chat" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/></svg>',S='<svg class="harkly-ic-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg>';function J(e){if(!e||typeof e!=="object")return null;let r=e;if(r.type==="harkly:close")return{type:"harkly:close"};if(r.type==="harkly:unread"&&typeof r.count==="number")return{type:"harkly:unread",count:r.count};return null}function I(e,r,t,y){if(!e.getElementById(W)){let l=e.createElement("style");l.id=W,l.textContent=D,e.head.appendChild(l)}let a=t.position??"right",f=new URL(r).origin,h=e.createElement("iframe");h.className="harkly-messenger",h.dataset.side=a,h.title="Messages",h.setAttribute("allow","clipboard-write");let k=e.createElement("button");if(k.type="button",k.className="harkly-launcher",k.dataset.side=a,k.setAttribute("aria-label","Messages"),t.color)k.style.setProperty("--harkly-color",t.color);k.innerHTML=R+S;let m=e.createElement("span");m.className="harkly-badge",m.hidden=!0,k.appendChild(m);let n=!1,g=!1,v=(l)=>{if(g&&!l)return;let w={theme:t.theme,c:l};h.src=b(r,"messenger",w,y),g=!0},i=(l)=>{if(n=l,n)v(),h.dataset.open="",k.dataset.open="",h.focus();else delete h.dataset.open,delete k.dataset.open;k.setAttribute("aria-expanded",String(n))},H=(l)=>{if(l.origin!==f||l.source!==h.contentWindow)return;let w=J(l.data);if(w?.type==="harkly:close")i(!1);if(w?.type==="harkly:unread")m.hidden=w.count===0,m.textContent=String(w.count)},C=(l)=>{if(l.key==="Escape"&&n)i(!1)};if(k.addEventListener("click",()=>i(!n)),e.defaultView?.addEventListener("message",H),e.addEventListener("keydown",C),e.body.appendChild(h),t.launcher!==!1)e.body.appendChild(k);return v(),{open:(l)=>{if(l)v(l);i(!0)},close:()=>i(!1),toggle:()=>i(!n),destroy:()=>{e.defaultView?.removeEventListener("message",H),e.removeEventListener("keydown",C),h.remove(),k.remove()},get isOpen(){return n}}}var u="harkly:anon";function P(){let e=new Uint8Array(16);return crypto.getRandomValues(e),Array.from(e,(r)=>r.toString(16).padStart(2,"0")).join("")}var Q={open:()=>{},close:()=>{},toggle:()=>{},destroy:()=>{},isOpen:!1};function X(){try{return typeof localStorage>"u"?void 0:localStorage}catch{return}}class L{publicKey;apiUrl;fetchImpl;storage;anonymousEnabled;currentUser;jwt;portal;messengerDefaults;configPromise;open;messengerHandle;constructor(e){if(!e.publicKey?.startsWith("hk_pub_"))throw Error("Harkly: publicKey must be your hk_pub_… key");if(this.publicKey=e.publicKey,this.apiUrl=(e.apiUrl??"https://api.harkly.app").replace(/\/$/,""),this.fetchImpl=e.fetch??((r,t)=>fetch(r,t)),this.storage=e.storage??X(),this.anonymousEnabled=e.anonymous??!1,e.portalUrl)this.portal=Promise.resolve(e.portalUrl.replace(/\/$/,""))}anonymousId(){if(!this.anonymousEnabled||!this.storage)return;let e=this.storage.getItem(u)??void 0;if(!e)e=P(),this.storage.setItem(u,e);return e}get user(){return this.currentUser}async identify(e,r={}){if(!e?.id)throw Error("Harkly: identify needs a user id");let t={"content-type":"application/json","x-harkly-public-key":this.publicKey};if(r.jwt)t.authorization=`Bearer ${r.jwt}`;let y=await this.fetchImpl(`${this.apiUrl}/identify`,{method:"POST",headers:t,body:JSON.stringify({user:e,company:r.company,anonymousId:this.anonymousId()}),keepalive:!0});if(!y.ok){let a=await y.text().catch(()=>"");throw Error(`Harkly: identify failed (${y.status}) ${a}`.trim())}return this.currentUser=e,this.jwt=r.jwt,await y.json()}reset(){this.currentUser=void 0,this.jwt=void 0,this.storage?.removeItem(u),this.messengerHandle?.destroy(),this.messengerHandle=void 0}async messenger(e={}){this.messengerHandle?.destroy();let[r,t]=await Promise.all([this.portalUrl(),this.defaults()]);if(!$(document.location.pathname,t?.pages))return Q;return this.messengerHandle=I(document,r,z(e,t),{jwt:this.jwt,anonymousId:this.anonymousId()}),this.messengerHandle}config(){return this.configPromise??=this.fetchImpl(`${this.apiUrl}/portal/config`,{headers:{"x-harkly-public-key":this.publicKey}}).then((e)=>e.json()),this.configPromise}defaults(){return this.messengerDefaults??=this.config().then((e)=>({...e.org.messenger,color:e.org.messenger?.color??e.org.brandColor})).catch(()=>{return}),this.messengerDefaults}async portalUrl(){return this.portal??=this.config().then((e)=>`https://${e.org.slug}.harkly.app`),this.portal}async show(e,r,t,y){this.open?.close();let a=await this.portalUrl(),f=b(a,e,r,{jwt:this.jwt,anonymousId:this.anonymousId()});return this.open=U(document,f,new URL(a).origin,{side:t,onPosted:y,onClose:()=>{this.open=void 0}}),this.open}feedback(e={}){let{onPosted:r,...t}=e;return this.show("feedback",t,!1,r)}updates(e={}){return this.show("updates",e,!0)}async unread(){let e={"x-harkly-public-key":this.publicKey};if(this.jwt)e.authorization=`Bearer ${this.jwt}`;let r=this.anonymousId();if(r)e["x-harkly-anonymous-id"]=r;let t=await this.fetchImpl(`${this.apiUrl}/portal/changelog/unread`,{headers:e});if(!t.ok)return 0;return(await t.json()).unread}}var j;function Z(e){return j=new L(e),j}function x(){if(!j)throw Error("Harkly: call init({ publicKey }) first");return j}var d=(e,r)=>x().identify(e,r),s=()=>x().reset(),T=(e)=>x().feedback(e),A=(e)=>x().updates(e),K=()=>x().unread(),G=(e)=>x().messenger(e);globalThis.Harkly=E;})();
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { type FeedbackOptions, type OpenHandle, type UpdatesOptions } from "./widgets";
|
|
2
|
+
import { type MessengerHandle, type MessengerOptions } from "./messenger";
|
|
3
|
+
export type { FeedbackOptions, UpdatesOptions, OpenHandle } from "./widgets";
|
|
4
|
+
export type { MessengerDefaults, MessengerHandle, MessengerOptions } from "./messenger";
|
|
5
|
+
export type Attributes = Record<string, string | number | boolean | null>;
|
|
6
|
+
export interface HarklyUser {
|
|
7
|
+
id: string;
|
|
8
|
+
email?: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
avatarUrl?: string;
|
|
11
|
+
plan?: string;
|
|
12
|
+
mrr?: number;
|
|
13
|
+
createdAt?: string;
|
|
14
|
+
custom?: Attributes;
|
|
15
|
+
}
|
|
16
|
+
export interface HarklyCompany {
|
|
17
|
+
id: string;
|
|
18
|
+
name?: string;
|
|
19
|
+
domain?: string;
|
|
20
|
+
plan?: string;
|
|
21
|
+
mrr?: number;
|
|
22
|
+
custom?: Attributes;
|
|
23
|
+
}
|
|
24
|
+
export interface InitOptions {
|
|
25
|
+
/** Your org's public key from Settings → SDK. Safe to ship in the browser. */
|
|
26
|
+
publicKey: string;
|
|
27
|
+
/** Defaults to https://api.harkly.app. */
|
|
28
|
+
apiUrl?: string;
|
|
29
|
+
/** Your portal, e.g. https://acme.harkly.app. Looked up from the public key when omitted. */
|
|
30
|
+
portalUrl?: string;
|
|
31
|
+
/** Keep an anonymous id in storage before identify so later votes can be attributed. Off by default: no cookie until you say so. */
|
|
32
|
+
anonymous?: boolean;
|
|
33
|
+
/** Override fetch (tests, SSR). */
|
|
34
|
+
fetch?: typeof fetch;
|
|
35
|
+
/** Override storage (tests, SSR). */
|
|
36
|
+
storage?: Pick<Storage, "getItem" | "setItem" | "removeItem">;
|
|
37
|
+
}
|
|
38
|
+
export interface IdentifyOptions {
|
|
39
|
+
/** A JWT signed with your SSO secret, `sub` = user id. Lets Harkly trust email changes. */
|
|
40
|
+
jwt?: string;
|
|
41
|
+
company?: HarklyCompany;
|
|
42
|
+
}
|
|
43
|
+
export interface IdentifyResult {
|
|
44
|
+
userId: string;
|
|
45
|
+
companyId: string | null;
|
|
46
|
+
created: boolean;
|
|
47
|
+
}
|
|
48
|
+
export declare class HarklyClient {
|
|
49
|
+
private readonly publicKey;
|
|
50
|
+
private readonly apiUrl;
|
|
51
|
+
private readonly fetchImpl;
|
|
52
|
+
private readonly storage?;
|
|
53
|
+
private readonly anonymousEnabled;
|
|
54
|
+
private currentUser?;
|
|
55
|
+
private jwt?;
|
|
56
|
+
private portal?;
|
|
57
|
+
private messengerDefaults?;
|
|
58
|
+
private configPromise?;
|
|
59
|
+
private open?;
|
|
60
|
+
private messengerHandle?;
|
|
61
|
+
constructor(options: InitOptions);
|
|
62
|
+
/** The anonymous id, created on first use when `anonymous` is on. */
|
|
63
|
+
anonymousId(): string | undefined;
|
|
64
|
+
get user(): HarklyUser | undefined;
|
|
65
|
+
identify(user: HarklyUser, options?: IdentifyOptions): Promise<IdentifyResult>;
|
|
66
|
+
/** Forget the current user and the anonymous id, e.g. on sign-out. */
|
|
67
|
+
reset(): void;
|
|
68
|
+
/**
|
|
69
|
+
* Put the messenger launcher on the page. Call again after identify to attach
|
|
70
|
+
* the user. Workspace settings (side, colour, page rules) apply unless the
|
|
71
|
+
* option is given here; on a page the rules exclude, nothing mounts.
|
|
72
|
+
*/
|
|
73
|
+
messenger(options?: MessengerOptions): Promise<MessengerHandle>;
|
|
74
|
+
private config;
|
|
75
|
+
private defaults;
|
|
76
|
+
private portalUrl;
|
|
77
|
+
private show;
|
|
78
|
+
/** Open the feedback modal. Resolves once it's on screen. */
|
|
79
|
+
feedback(options?: FeedbackOptions & {
|
|
80
|
+
onPosted?: (postId: string) => void;
|
|
81
|
+
}): Promise<OpenHandle>;
|
|
82
|
+
/** Open the "what's new" panel. */
|
|
83
|
+
updates(options?: UpdatesOptions): Promise<OpenHandle>;
|
|
84
|
+
/** Published updates the current user hasn't seen in the last 30 days. */
|
|
85
|
+
unread(): Promise<number>;
|
|
86
|
+
}
|
|
87
|
+
export declare function init(options: InitOptions): HarklyClient;
|
|
88
|
+
export declare const identify: (user: HarklyUser, options?: IdentifyOptions) => Promise<IdentifyResult>;
|
|
89
|
+
export declare const reset: () => void;
|
|
90
|
+
export declare const feedback: (options?: Parameters<HarklyClient["feedback"]>[0]) => Promise<OpenHandle>;
|
|
91
|
+
export declare const updates: (options?: UpdatesOptions) => Promise<OpenHandle>;
|
|
92
|
+
export declare const unread: () => Promise<number>;
|
|
93
|
+
export declare const messenger: (options?: MessengerOptions) => Promise<MessengerHandle>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
function b(e,r,t,k){let y=new URL(`/widget/${r}`,e);for(let[a,f]of Object.entries(t))if(f)y.searchParams.set(a,f);let m=new URLSearchParams;if(k.jwt)m.set("jwt",k.jwt);if(k.anonymousId)m.set("anon",k.anonymousId);let l=m.toString();return l?`${y.toString()}#${l}`:y.toString()}function M(e){if(!e||typeof e!=="object")return null;let r=e;if(r.type==="harkly:close")return{type:"harkly:close"};if(r.type==="harkly:posted"&&typeof r.postId==="string")return{type:"harkly:posted",postId:r.postId};if(r.type==="harkly:height"&&typeof r.height==="number")return{type:"harkly:height",height:r.height};return null}var C="harkly-widget-style",N=`
|
|
2
|
+
.harkly-overlay{position:fixed;inset:0;z-index:2147483000;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;padding:16px;opacity:0;transition:opacity .18s ease}
|
|
3
|
+
.harkly-overlay[data-open]{opacity:1}
|
|
4
|
+
.harkly-frame{border:0;width:min(560px,100%);height:min(640px,100%);border-radius:14px;background:transparent;box-shadow:0 24px 64px rgba(0,0,0,.35);transform:translateY(8px) scale(.98);transition:transform .22s cubic-bezier(.22,1,.36,1)}
|
|
5
|
+
.harkly-overlay[data-open] .harkly-frame{transform:none}
|
|
6
|
+
.harkly-overlay[data-side] {justify-content:flex-end;padding:0}
|
|
7
|
+
.harkly-overlay[data-side] .harkly-frame{width:min(420px,100%);height:100%;border-radius:0;transform:translateX(24px)}
|
|
8
|
+
.harkly-overlay[data-side][data-open] .harkly-frame{transform:none}
|
|
9
|
+
@media (prefers-reduced-motion: reduce){.harkly-overlay,.harkly-frame{transition:none}}`;function O(e){if(e.getElementById(C))return;let r=e.createElement("style");r.id=C,r.textContent=N,e.head.appendChild(r)}function F(e,r,t,k){O(e);let y=e.createElement("div");if(y.className="harkly-overlay",k.side)y.dataset.side="";let m=e.createElement("iframe");m.className="harkly-frame",m.src=r,m.title="Harkly",m.setAttribute("allow","clipboard-write"),y.appendChild(m);let l=e.activeElement,a=()=>{e.defaultView?.removeEventListener("message",f),e.removeEventListener("keydown",n),y.removeAttribute("data-open"),setTimeout(()=>y.remove(),200),l?.focus?.(),k.onClose?.()},f=(v)=>{if(v.origin!==t||v.source!==m.contentWindow)return;let g=M(v.data);if(!g)return;if(g.type==="harkly:close")a();if(g.type==="harkly:posted")k.onPosted?.(g.postId)},n=(v)=>{if(v.key==="Escape")a()};return y.addEventListener("click",(v)=>{if(v.target===y)a()}),e.defaultView?.addEventListener("message",f),e.addEventListener("keydown",n),e.body.appendChild(y),requestAnimationFrame(()=>{y.dataset.open="",m.focus()}),{close:a}}function V(e){let r=e.replace(/[.+^${}()|[\]\\]/g,"\\$&").split("**").map((t)=>t.replace(/\*/g,"[^/]*")).join(".*");return new RegExp(`^${r}/?$`)}function W(e,r){if(!r||r.mode==="all"||r.patterns.length===0)return!0;let t=r.patterns.some((k)=>V(k).test(e));return r.mode==="include"?t:!t}function $(e,r){if(!r)return e;return{position:e.position??r.position,color:e.color??r.color??void 0,launcher:e.launcher??r.launcher,theme:e.theme}}var U="harkly-messenger-style",q=`
|
|
10
|
+
.harkly-launcher{position:fixed;bottom:20px;z-index:2147483000;width:52px;height:52px;border:0;border-radius:50%;cursor:pointer;color:#fff;background:var(--harkly-color,#111);box-shadow:0 8px 24px rgba(0,0,0,.22);display:flex;align-items:center;justify-content:center;transition:transform .18s ease}
|
|
11
|
+
.harkly-launcher:hover{transform:scale(1.05)}
|
|
12
|
+
.harkly-launcher[data-side=right]{right:20px}.harkly-launcher[data-side=left]{left:20px}
|
|
13
|
+
.harkly-launcher svg{width:24px;height:24px;transition:opacity .15s,transform .2s}
|
|
14
|
+
.harkly-launcher[data-open] .harkly-ic-chat{opacity:0;transform:scale(.6)}.harkly-launcher:not([data-open]) .harkly-ic-x{opacity:0;transform:scale(.6)}
|
|
15
|
+
.harkly-launcher .harkly-ic-x{position:absolute}
|
|
16
|
+
.harkly-badge{position:absolute;top:-2px;right:-2px;min-width:18px;height:18px;padding:0 5px;border-radius:9px;background:#e5484d;color:#fff;font:600 11px/18px system-ui,sans-serif;text-align:center}
|
|
17
|
+
.harkly-messenger{position:fixed;bottom:84px;z-index:2147483000;width:min(400px,calc(100vw - 32px));height:min(704px,calc(100vh - 104px));border:0;border-radius:16px;background:transparent;box-shadow:0 24px 64px rgba(0,0,0,.28);opacity:0;pointer-events:none;transform:translateY(12px) scale(.98);transform-origin:bottom right;transition:opacity .18s ease,transform .22s cubic-bezier(.22,1,.36,1)}
|
|
18
|
+
.harkly-messenger[data-side=right]{right:20px}.harkly-messenger[data-side=left]{left:20px;transform-origin:bottom left}
|
|
19
|
+
.harkly-messenger[data-open]{opacity:1;pointer-events:auto;transform:none}
|
|
20
|
+
@media (max-width:480px){.harkly-messenger[data-open]{inset:0;width:100%;height:100%;border-radius:0}}
|
|
21
|
+
@media (prefers-reduced-motion: reduce){.harkly-launcher,.harkly-messenger{transition:none}}`,B='<svg class="harkly-ic-chat" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7.9 20A9 9 0 1 0 4 16.1L2 22Z"/></svg>',H='<svg class="harkly-ic-x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg>';function R(e){if(!e||typeof e!=="object")return null;let r=e;if(r.type==="harkly:close")return{type:"harkly:close"};if(r.type==="harkly:unread"&&typeof r.count==="number")return{type:"harkly:unread",count:r.count};return null}function z(e,r,t,k){if(!e.getElementById(U)){let h=e.createElement("style");h.id=U,h.textContent=q,e.head.appendChild(h)}let y=t.position??"right",m=new URL(r).origin,l=e.createElement("iframe");l.className="harkly-messenger",l.dataset.side=y,l.title="Messages",l.setAttribute("allow","clipboard-write");let a=e.createElement("button");if(a.type="button",a.className="harkly-launcher",a.dataset.side=y,a.setAttribute("aria-label","Messages"),t.color)a.style.setProperty("--harkly-color",t.color);a.innerHTML=B+H;let f=e.createElement("span");f.className="harkly-badge",f.hidden=!0,a.appendChild(f);let n=!1,v=!1,g=(h)=>{if(v&&!h)return;let i={theme:t.theme,c:h};l.src=b(r,"messenger",i,k),v=!0},x=(h)=>{if(n=h,n)g(),l.dataset.open="",a.dataset.open="",l.focus();else delete l.dataset.open,delete a.dataset.open;a.setAttribute("aria-expanded",String(n))},u=(h)=>{if(h.origin!==m||h.source!==l.contentWindow)return;let i=R(h.data);if(i?.type==="harkly:close")x(!1);if(i?.type==="harkly:unread")f.hidden=i.count===0,f.textContent=String(i.count)},E=(h)=>{if(h.key==="Escape"&&n)x(!1)};if(a.addEventListener("click",()=>x(!n)),e.defaultView?.addEventListener("message",u),e.addEventListener("keydown",E),e.body.appendChild(l),t.launcher!==!1)e.body.appendChild(a);return g(),{open:(h)=>{if(h)g(h);x(!0)},close:()=>x(!1),toggle:()=>x(!n),destroy:()=>{e.defaultView?.removeEventListener("message",u),e.removeEventListener("keydown",E),l.remove(),a.remove()},get isOpen(){return n}}}var L="harkly:anon";function D(){let e=new Uint8Array(16);return crypto.getRandomValues(e),Array.from(e,(r)=>r.toString(16).padStart(2,"0")).join("")}var J={open:()=>{},close:()=>{},toggle:()=>{},destroy:()=>{},isOpen:!1};function P(){try{return typeof localStorage>"u"?void 0:localStorage}catch{return}}class I{publicKey;apiUrl;fetchImpl;storage;anonymousEnabled;currentUser;jwt;portal;messengerDefaults;configPromise;open;messengerHandle;constructor(e){if(!e.publicKey?.startsWith("hk_pub_"))throw Error("Harkly: publicKey must be your hk_pub_… key");if(this.publicKey=e.publicKey,this.apiUrl=(e.apiUrl??"https://api.harkly.app").replace(/\/$/,""),this.fetchImpl=e.fetch??((r,t)=>fetch(r,t)),this.storage=e.storage??P(),this.anonymousEnabled=e.anonymous??!1,e.portalUrl)this.portal=Promise.resolve(e.portalUrl.replace(/\/$/,""))}anonymousId(){if(!this.anonymousEnabled||!this.storage)return;let e=this.storage.getItem(L)??void 0;if(!e)e=D(),this.storage.setItem(L,e);return e}get user(){return this.currentUser}async identify(e,r={}){if(!e?.id)throw Error("Harkly: identify needs a user id");let t={"content-type":"application/json","x-harkly-public-key":this.publicKey};if(r.jwt)t.authorization=`Bearer ${r.jwt}`;let k=await this.fetchImpl(`${this.apiUrl}/identify`,{method:"POST",headers:t,body:JSON.stringify({user:e,company:r.company,anonymousId:this.anonymousId()}),keepalive:!0});if(!k.ok){let y=await k.text().catch(()=>"");throw Error(`Harkly: identify failed (${k.status}) ${y}`.trim())}return this.currentUser=e,this.jwt=r.jwt,await k.json()}reset(){this.currentUser=void 0,this.jwt=void 0,this.storage?.removeItem(L),this.messengerHandle?.destroy(),this.messengerHandle=void 0}async messenger(e={}){this.messengerHandle?.destroy();let[r,t]=await Promise.all([this.portalUrl(),this.defaults()]);if(!W(document.location.pathname,t?.pages))return J;return this.messengerHandle=z(document,r,$(e,t),{jwt:this.jwt,anonymousId:this.anonymousId()}),this.messengerHandle}config(){return this.configPromise??=this.fetchImpl(`${this.apiUrl}/portal/config`,{headers:{"x-harkly-public-key":this.publicKey}}).then((e)=>e.json()),this.configPromise}defaults(){return this.messengerDefaults??=this.config().then((e)=>({...e.org.messenger,color:e.org.messenger?.color??e.org.brandColor})).catch(()=>{return}),this.messengerDefaults}async portalUrl(){return this.portal??=this.config().then((e)=>`https://${e.org.slug}.harkly.app`),this.portal}async show(e,r,t,k){this.open?.close();let y=await this.portalUrl(),m=b(y,e,r,{jwt:this.jwt,anonymousId:this.anonymousId()});return this.open=F(document,m,new URL(y).origin,{side:t,onPosted:k,onClose:()=>{this.open=void 0}}),this.open}feedback(e={}){let{onPosted:r,...t}=e;return this.show("feedback",t,!1,r)}updates(e={}){return this.show("updates",e,!0)}async unread(){let e={"x-harkly-public-key":this.publicKey};if(this.jwt)e.authorization=`Bearer ${this.jwt}`;let r=this.anonymousId();if(r)e["x-harkly-anonymous-id"]=r;let t=await this.fetchImpl(`${this.apiUrl}/portal/changelog/unread`,{headers:e});if(!t.ok)return 0;return(await t.json()).unread}}var j;function T(e){return j=new I(e),j}function w(){if(!j)throw Error("Harkly: call init({ publicKey }) first");return j}var d=(e,r)=>w().identify(e,r),G=()=>w().reset(),K=(e)=>w().feedback(e),Y=(e)=>w().updates(e),_=()=>w().unread(),c=(e)=>w().messenger(e);export{Y as updates,_ as unread,G as reset,c as messenger,T as init,d as identify,K as feedback,I as HarklyClient};
|
|
22
|
+
|
|
23
|
+
//# debugId=523707BF4C8E8CBE64756E2164756E21
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/widgets.ts", "../src/messenger.ts", "../src/index.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"// The feedback modal and the updates panel are the hosted portal in an\n// iframe. The page never sees the customer's session; identity travels in\n// the URL fragment, which browsers keep out of referrers and server logs.\nexport type WidgetIdentity = { jwt?: string; anonymousId?: string };\nexport type FeedbackOptions = { board?: string; title?: string; theme?: \"light\" | \"dark\" | \"system\" };\nexport type UpdatesOptions = { theme?: \"light\" | \"dark\" | \"system\" };\n\nexport function widgetUrl(portalUrl: string, kind: \"feedback\" | \"updates\" | \"messenger\", options: Record<string, string | undefined>, identity: WidgetIdentity): string {\n const url = new URL(`/widget/${kind}`, portalUrl);\n for (const [k, v] of Object.entries(options)) if (v) url.searchParams.set(k, v);\n const frag = new URLSearchParams();\n if (identity.jwt) frag.set(\"jwt\", identity.jwt);\n if (identity.anonymousId) frag.set(\"anon\", identity.anonymousId);\n const f = frag.toString();\n return f ? `${url.toString()}#${f}` : url.toString();\n}\n\ntype Message = { type: \"harkly:close\" } | { type: \"harkly:posted\"; postId: string } | { type: \"harkly:height\"; height: number };\n\nexport function parseMessage(data: unknown): Message | null {\n if (!data || typeof data !== \"object\") return null;\n const m = data as Record<string, unknown>;\n if (m.type === \"harkly:close\") return { type: \"harkly:close\" };\n if (m.type === \"harkly:posted\" && typeof m.postId === \"string\") return { type: \"harkly:posted\", postId: m.postId };\n if (m.type === \"harkly:height\" && typeof m.height === \"number\") return { type: \"harkly:height\", height: m.height };\n return null;\n}\n\nconst STYLE_ID = \"harkly-widget-style\";\nconst CSS = `\n.harkly-overlay{position:fixed;inset:0;z-index:2147483000;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;padding:16px;opacity:0;transition:opacity .18s ease}\n.harkly-overlay[data-open]{opacity:1}\n.harkly-frame{border:0;width:min(560px,100%);height:min(640px,100%);border-radius:14px;background:transparent;box-shadow:0 24px 64px rgba(0,0,0,.35);transform:translateY(8px) scale(.98);transition:transform .22s cubic-bezier(.22,1,.36,1)}\n.harkly-overlay[data-open] .harkly-frame{transform:none}\n.harkly-overlay[data-side] {justify-content:flex-end;padding:0}\n.harkly-overlay[data-side] .harkly-frame{width:min(420px,100%);height:100%;border-radius:0;transform:translateX(24px)}\n.harkly-overlay[data-side][data-open] .harkly-frame{transform:none}\n@media (prefers-reduced-motion: reduce){.harkly-overlay,.harkly-frame{transition:none}}`;\n\nfunction ensureStyle(doc: Document): void {\n if (doc.getElementById(STYLE_ID)) return;\n const style = doc.createElement(\"style\");\n style.id = STYLE_ID;\n style.textContent = CSS;\n doc.head.appendChild(style);\n}\n\nexport type OpenHandle = { close: () => void };\n\nexport function openFrame(doc: Document, src: string, origin: string, opts: { side?: boolean; onPosted?: (postId: string) => void; onClose?: () => void }): OpenHandle {\n ensureStyle(doc);\n const overlay = doc.createElement(\"div\");\n overlay.className = \"harkly-overlay\";\n if (opts.side) overlay.dataset.side = \"\";\n const frame = doc.createElement(\"iframe\");\n frame.className = \"harkly-frame\";\n frame.src = src;\n frame.title = \"Harkly\";\n frame.setAttribute(\"allow\", \"clipboard-write\");\n overlay.appendChild(frame);\n const previousFocus = doc.activeElement as HTMLElement | null;\n const close = () => {\n doc.defaultView?.removeEventListener(\"message\", onMessage);\n doc.removeEventListener(\"keydown\", onKey);\n overlay.removeAttribute(\"data-open\");\n setTimeout(() => overlay.remove(), 200);\n previousFocus?.focus?.();\n opts.onClose?.();\n };\n const onMessage = (e: MessageEvent) => {\n if (e.origin !== origin || e.source !== frame.contentWindow) return;\n const m = parseMessage(e.data);\n if (!m) return;\n if (m.type === \"harkly:close\") close();\n if (m.type === \"harkly:posted\") opts.onPosted?.(m.postId);\n };\n const onKey = (e: KeyboardEvent) => { if (e.key === \"Escape\") close(); };\n overlay.addEventListener(\"click\", (e) => { if (e.target === overlay) close(); });\n doc.defaultView?.addEventListener(\"message\", onMessage);\n doc.addEventListener(\"keydown\", onKey);\n doc.body.appendChild(overlay);\n requestAnimationFrame(() => { overlay.dataset.open = \"\"; frame.focus(); });\n return { close };\n}\n",
|
|
6
|
+
"// The messenger: a launcher in the corner and the hosted conversation UI in\n// an iframe that stays mounted once opened, so the live connection inside\n// it keeps the unread badge honest while it's hidden.\nimport { widgetUrl, type WidgetIdentity } from \"./widgets\";\n\nexport type MessengerOptions = { position?: \"right\" | \"left\"; launcher?: boolean; theme?: \"light\" | \"dark\" | \"system\"; color?: string };\n/** What the workspace configured in Settings; explicit options win over it. */\nexport type MessengerDefaults = { position?: \"right\" | \"left\"; color?: string | null; launcher?: boolean; pages?: { mode: \"all\" | \"include\" | \"exclude\"; patterns: string[] } };\n\n// Page rules are globs over the path: `/app/*` matches one segment, `/docs/**` any depth.\nfunction globToRegExp(pattern: string): RegExp {\n const escaped = pattern.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\").split(\"**\").map((part) => part.replace(/\\*/g, \"[^/]*\")).join(\".*\");\n return new RegExp(`^${escaped}/?$`);\n}\nexport function pageAllowed(pathname: string, pages: MessengerDefaults[\"pages\"] | undefined): boolean {\n if (!pages || pages.mode === \"all\" || pages.patterns.length === 0) return true;\n const hit = pages.patterns.some((p) => globToRegExp(p).test(pathname));\n return pages.mode === \"include\" ? hit : !hit;\n}\nexport function withDefaults(options: MessengerOptions, defaults: MessengerDefaults | undefined): MessengerOptions {\n if (!defaults) return options;\n return { position: options.position ?? defaults.position, color: options.color ?? defaults.color ?? undefined, launcher: options.launcher ?? defaults.launcher, theme: options.theme };\n}\nexport type MessengerHandle = { open: (conversationId?: string) => void; close: () => void; toggle: () => void; destroy: () => void; readonly isOpen: boolean };\n\nconst STYLE_ID = \"harkly-messenger-style\";\nconst CSS = `\n.harkly-launcher{position:fixed;bottom:20px;z-index:2147483000;width:52px;height:52px;border:0;border-radius:50%;cursor:pointer;color:#fff;background:var(--harkly-color,#111);box-shadow:0 8px 24px rgba(0,0,0,.22);display:flex;align-items:center;justify-content:center;transition:transform .18s ease}\n.harkly-launcher:hover{transform:scale(1.05)}\n.harkly-launcher[data-side=right]{right:20px}.harkly-launcher[data-side=left]{left:20px}\n.harkly-launcher svg{width:24px;height:24px;transition:opacity .15s,transform .2s}\n.harkly-launcher[data-open] .harkly-ic-chat{opacity:0;transform:scale(.6)}.harkly-launcher:not([data-open]) .harkly-ic-x{opacity:0;transform:scale(.6)}\n.harkly-launcher .harkly-ic-x{position:absolute}\n.harkly-badge{position:absolute;top:-2px;right:-2px;min-width:18px;height:18px;padding:0 5px;border-radius:9px;background:#e5484d;color:#fff;font:600 11px/18px system-ui,sans-serif;text-align:center}\n.harkly-messenger{position:fixed;bottom:84px;z-index:2147483000;width:min(400px,calc(100vw - 32px));height:min(704px,calc(100vh - 104px));border:0;border-radius:16px;background:transparent;box-shadow:0 24px 64px rgba(0,0,0,.28);opacity:0;pointer-events:none;transform:translateY(12px) scale(.98);transform-origin:bottom right;transition:opacity .18s ease,transform .22s cubic-bezier(.22,1,.36,1)}\n.harkly-messenger[data-side=right]{right:20px}.harkly-messenger[data-side=left]{left:20px;transform-origin:bottom left}\n.harkly-messenger[data-open]{opacity:1;pointer-events:auto;transform:none}\n@media (max-width:480px){.harkly-messenger[data-open]{inset:0;width:100%;height:100%;border-radius:0}}\n@media (prefers-reduced-motion: reduce){.harkly-launcher,.harkly-messenger{transition:none}}`;\n\nconst CHAT = '<svg class=\"harkly-ic-chat\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M7.9 20A9 9 0 1 0 4 16.1L2 22Z\"/></svg>';\nconst X = '<svg class=\"harkly-ic-x\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><path d=\"M18 6 6 18M6 6l12 12\"/></svg>';\n\ntype Message = { type: \"harkly:close\" } | { type: \"harkly:unread\"; count: number };\nfunction parse(data: unknown): Message | null {\n if (!data || typeof data !== \"object\") return null;\n const m = data as Record<string, unknown>;\n if (m.type === \"harkly:close\") return { type: \"harkly:close\" };\n if (m.type === \"harkly:unread\" && typeof m.count === \"number\") return { type: \"harkly:unread\", count: m.count };\n return null;\n}\n\nexport function mountMessenger(doc: Document, portal: string, options: MessengerOptions, identity: WidgetIdentity): MessengerHandle {\n if (!doc.getElementById(STYLE_ID)) {\n const style = doc.createElement(\"style\");\n style.id = STYLE_ID;\n style.textContent = CSS;\n doc.head.appendChild(style);\n }\n const side = options.position ?? \"right\";\n const origin = new URL(portal).origin;\n const frame = doc.createElement(\"iframe\");\n frame.className = \"harkly-messenger\";\n frame.dataset.side = side;\n frame.title = \"Messages\";\n frame.setAttribute(\"allow\", \"clipboard-write\");\n const launcher = doc.createElement(\"button\");\n launcher.type = \"button\";\n launcher.className = \"harkly-launcher\";\n launcher.dataset.side = side;\n launcher.setAttribute(\"aria-label\", \"Messages\");\n if (options.color) launcher.style.setProperty(\"--harkly-color\", options.color);\n launcher.innerHTML = CHAT + X;\n const badge = doc.createElement(\"span\");\n badge.className = \"harkly-badge\";\n badge.hidden = true;\n launcher.appendChild(badge);\n let open = false;\n let loaded = false;\n const load = (conversationId?: string) => {\n if (loaded && !conversationId) return;\n const opts: Record<string, string | undefined> = { theme: options.theme, c: conversationId };\n frame.src = widgetUrl(portal, \"messenger\", opts, identity);\n loaded = true;\n };\n const set = (next: boolean) => {\n open = next;\n if (open) { load(); frame.dataset.open = \"\"; launcher.dataset.open = \"\"; frame.focus(); }\n else { delete frame.dataset.open; delete launcher.dataset.open; }\n launcher.setAttribute(\"aria-expanded\", String(open));\n };\n const onMessage = (e: MessageEvent) => {\n if (e.origin !== origin || e.source !== frame.contentWindow) return;\n const m = parse(e.data);\n if (m?.type === \"harkly:close\") set(false);\n if (m?.type === \"harkly:unread\") {\n badge.hidden = m.count === 0;\n badge.textContent = String(m.count);\n }\n };\n const onKey = (e: KeyboardEvent) => { if (e.key === \"Escape\" && open) set(false); };\n launcher.addEventListener(\"click\", () => set(!open));\n doc.defaultView?.addEventListener(\"message\", onMessage);\n doc.addEventListener(\"keydown\", onKey);\n doc.body.appendChild(frame);\n if (options.launcher !== false) doc.body.appendChild(launcher);\n // the frame loads right away so unread counts arrive before the first click\n load();\n return {\n open: (conversationId) => {\n if (conversationId) load(conversationId);\n set(true);\n },\n close: () => set(false),\n toggle: () => set(!open),\n destroy: () => {\n doc.defaultView?.removeEventListener(\"message\", onMessage);\n doc.removeEventListener(\"keydown\", onKey);\n frame.remove();\n launcher.remove();\n },\n get isOpen() { return open; },\n };\n}\n",
|
|
7
|
+
"// Harkly browser SDK: tell Harkly who the current user is, open the feedback\n// modal, show what's new.\nimport { openFrame, widgetUrl, type FeedbackOptions, type OpenHandle, type UpdatesOptions } from \"./widgets\";\nimport { mountMessenger, pageAllowed, withDefaults, type MessengerDefaults, type MessengerHandle, type MessengerOptions } from \"./messenger\";\nexport type { FeedbackOptions, UpdatesOptions, OpenHandle } from \"./widgets\";\nexport type { MessengerDefaults, MessengerHandle, MessengerOptions } from \"./messenger\";\n\nexport type Attributes = Record<string, string | number | boolean | null>;\n\nexport interface HarklyUser {\n id: string;\n email?: string;\n name?: string;\n avatarUrl?: string;\n plan?: string;\n mrr?: number;\n createdAt?: string;\n custom?: Attributes;\n}\n\nexport interface HarklyCompany {\n id: string;\n name?: string;\n domain?: string;\n plan?: string;\n mrr?: number;\n custom?: Attributes;\n}\n\nexport interface InitOptions {\n /** Your org's public key from Settings → SDK. Safe to ship in the browser. */\n publicKey: string;\n /** Defaults to https://api.harkly.app. */\n apiUrl?: string;\n /** Your portal, e.g. https://acme.harkly.app. Looked up from the public key when omitted. */\n portalUrl?: string;\n /** Keep an anonymous id in storage before identify so later votes can be attributed. Off by default: no cookie until you say so. */\n anonymous?: boolean;\n /** Override fetch (tests, SSR). */\n fetch?: typeof fetch;\n /** Override storage (tests, SSR). */\n storage?: Pick<Storage, \"getItem\" | \"setItem\" | \"removeItem\">;\n}\n\nexport interface IdentifyOptions {\n /** A JWT signed with your SSO secret, `sub` = user id. Lets Harkly trust email changes. */\n jwt?: string;\n company?: HarklyCompany;\n}\n\nexport interface IdentifyResult { userId: string; companyId: string | null; created: boolean }\n\nconst STORAGE_KEY = \"harkly:anon\";\n\nfunction randomId(): string {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\ntype PortalConfig = { org: { slug: string; brandColor?: string | null; messenger?: MessengerDefaults } };\n// what messenger() hands back on a page the workspace's rules keep it off\nconst idleMessenger: MessengerHandle = { open: () => {}, close: () => {}, toggle: () => {}, destroy: () => {}, isOpen: false };\n\nfunction defaultStorage(): InitOptions[\"storage\"] | undefined {\n try {\n return typeof localStorage === \"undefined\" ? undefined : localStorage;\n } catch {\n return undefined;\n }\n}\n\nexport class HarklyClient {\n private readonly publicKey: string;\n private readonly apiUrl: string;\n private readonly fetchImpl: typeof fetch;\n private readonly storage?: InitOptions[\"storage\"];\n private readonly anonymousEnabled: boolean;\n private currentUser?: HarklyUser;\n private jwt?: string;\n private portal?: Promise<string>;\n private messengerDefaults?: Promise<MessengerDefaults | undefined>;\n private configPromise?: Promise<PortalConfig>;\n private open?: OpenHandle;\n private messengerHandle?: MessengerHandle;\n\n constructor(options: InitOptions) {\n if (!options.publicKey?.startsWith(\"hk_pub_\")) throw new Error(\"Harkly: publicKey must be your hk_pub_… key\");\n this.publicKey = options.publicKey;\n this.apiUrl = (options.apiUrl ?? \"https://api.harkly.app\").replace(/\\/$/, \"\");\n // bound, or the browser throws \"Illegal invocation\" when it's called off `this`\n this.fetchImpl = options.fetch ?? ((input: RequestInfo | URL, init?: RequestInit) => fetch(input, init)) as typeof fetch;\n this.storage = options.storage ?? defaultStorage();\n this.anonymousEnabled = options.anonymous ?? false;\n if (options.portalUrl) this.portal = Promise.resolve(options.portalUrl.replace(/\\/$/, \"\"));\n }\n\n /** The anonymous id, created on first use when `anonymous` is on. */\n anonymousId(): string | undefined {\n if (!this.anonymousEnabled || !this.storage) return undefined;\n let id = this.storage.getItem(STORAGE_KEY) ?? undefined;\n if (!id) {\n id = randomId();\n this.storage.setItem(STORAGE_KEY, id);\n }\n return id;\n }\n\n get user(): HarklyUser | undefined {\n return this.currentUser;\n }\n\n async identify(user: HarklyUser, options: IdentifyOptions = {}): Promise<IdentifyResult> {\n if (!user?.id) throw new Error(\"Harkly: identify needs a user id\");\n const headers: Record<string, string> = { \"content-type\": \"application/json\", \"x-harkly-public-key\": this.publicKey };\n if (options.jwt) headers.authorization = `Bearer ${options.jwt}`;\n const response = await this.fetchImpl(`${this.apiUrl}/identify`, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ user, company: options.company, anonymousId: this.anonymousId() }),\n keepalive: true,\n });\n if (!response.ok) {\n const detail = await response.text().catch(() => \"\");\n throw new Error(`Harkly: identify failed (${response.status}) ${detail}`.trim());\n }\n this.currentUser = user;\n this.jwt = options.jwt;\n return (await response.json()) as IdentifyResult;\n }\n\n /** Forget the current user and the anonymous id, e.g. on sign-out. */\n reset(): void {\n this.currentUser = undefined;\n this.jwt = undefined;\n this.storage?.removeItem(STORAGE_KEY);\n this.messengerHandle?.destroy();\n this.messengerHandle = undefined;\n }\n\n /**\n * Put the messenger launcher on the page. Call again after identify to attach\n * the user. Workspace settings (side, colour, page rules) apply unless the\n * option is given here; on a page the rules exclude, nothing mounts.\n */\n async messenger(options: MessengerOptions = {}): Promise<MessengerHandle> {\n this.messengerHandle?.destroy();\n const [portal, defaults] = await Promise.all([this.portalUrl(), this.defaults()]);\n if (!pageAllowed(document.location.pathname, defaults?.pages)) return idleMessenger;\n this.messengerHandle = mountMessenger(document, portal, withDefaults(options, defaults), { jwt: this.jwt, anonymousId: this.anonymousId() });\n return this.messengerHandle;\n }\n\n private config(): Promise<PortalConfig> {\n this.configPromise ??= this.fetchImpl(`${this.apiUrl}/portal/config`, { headers: { \"x-harkly-public-key\": this.publicKey } }).then((r) => r.json() as Promise<PortalConfig>);\n return this.configPromise;\n }\n private defaults(): Promise<MessengerDefaults | undefined> {\n // the launcher wears the brand colour unless the messenger settings say otherwise\n this.messengerDefaults ??= this.config().then((c) => ({ ...c.org.messenger, color: c.org.messenger?.color ?? c.org.brandColor })).catch(() => undefined);\n return this.messengerDefaults;\n }\n\n private async portalUrl(): Promise<string> {\n this.portal ??= this.config().then((c) => `https://${c.org.slug}.harkly.app`);\n return this.portal;\n }\n\n private async show(kind: \"feedback\" | \"updates\", options: Record<string, string | undefined>, side: boolean, onPosted?: (postId: string) => void): Promise<OpenHandle> {\n this.open?.close();\n const portal = await this.portalUrl();\n const src = widgetUrl(portal, kind, options, { jwt: this.jwt, anonymousId: this.anonymousId() });\n this.open = openFrame(document, src, new URL(portal).origin, { side, onPosted, onClose: () => { this.open = undefined; } });\n return this.open;\n }\n\n /** Open the feedback modal. Resolves once it's on screen. */\n feedback(options: FeedbackOptions & { onPosted?: (postId: string) => void } = {}): Promise<OpenHandle> {\n const { onPosted, ...rest } = options;\n return this.show(\"feedback\", rest, false, onPosted);\n }\n\n /** Open the \"what's new\" panel. */\n updates(options: UpdatesOptions = {}): Promise<OpenHandle> {\n return this.show(\"updates\", options, true);\n }\n\n /** Published updates the current user hasn't seen in the last 30 days. */\n async unread(): Promise<number> {\n const headers: Record<string, string> = { \"x-harkly-public-key\": this.publicKey };\n if (this.jwt) headers.authorization = `Bearer ${this.jwt}`;\n const anon = this.anonymousId();\n if (anon) headers[\"x-harkly-anonymous-id\"] = anon;\n const r = await this.fetchImpl(`${this.apiUrl}/portal/changelog/unread`, { headers });\n if (!r.ok) return 0;\n return ((await r.json()) as { unread: number }).unread;\n }\n}\n\nlet singleton: HarklyClient | undefined;\n\nexport function init(options: InitOptions): HarklyClient {\n singleton = new HarklyClient(options);\n return singleton;\n}\n\nfunction current(): HarklyClient {\n if (!singleton) throw new Error(\"Harkly: call init({ publicKey }) first\");\n return singleton;\n}\n\nexport const identify = (user: HarklyUser, options?: IdentifyOptions) => current().identify(user, options);\nexport const reset = () => current().reset();\nexport const feedback = (options?: Parameters<HarklyClient[\"feedback\"]>[0]) => current().feedback(options);\nexport const updates = (options?: UpdatesOptions) => current().updates(options);\nexport const unread = () => current().unread();\nexport const messenger = (options?: MessengerOptions) => current().messenger(options);\n\n"
|
|
8
|
+
],
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "523707BF4C8E8CBE64756E2164756E21",
|
|
11
|
+
"names": []
|
|
12
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type WidgetIdentity } from "./widgets";
|
|
2
|
+
export type MessengerOptions = {
|
|
3
|
+
position?: "right" | "left";
|
|
4
|
+
launcher?: boolean;
|
|
5
|
+
theme?: "light" | "dark" | "system";
|
|
6
|
+
color?: string;
|
|
7
|
+
};
|
|
8
|
+
/** What the workspace configured in Settings; explicit options win over it. */
|
|
9
|
+
export type MessengerDefaults = {
|
|
10
|
+
position?: "right" | "left";
|
|
11
|
+
color?: string | null;
|
|
12
|
+
launcher?: boolean;
|
|
13
|
+
pages?: {
|
|
14
|
+
mode: "all" | "include" | "exclude";
|
|
15
|
+
patterns: string[];
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export declare function pageAllowed(pathname: string, pages: MessengerDefaults["pages"] | undefined): boolean;
|
|
19
|
+
export declare function withDefaults(options: MessengerOptions, defaults: MessengerDefaults | undefined): MessengerOptions;
|
|
20
|
+
export type MessengerHandle = {
|
|
21
|
+
open: (conversationId?: string) => void;
|
|
22
|
+
close: () => void;
|
|
23
|
+
toggle: () => void;
|
|
24
|
+
destroy: () => void;
|
|
25
|
+
readonly isOpen: boolean;
|
|
26
|
+
};
|
|
27
|
+
export declare function mountMessenger(doc: Document, portal: string, options: MessengerOptions, identity: WidgetIdentity): MessengerHandle;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type WidgetIdentity = {
|
|
2
|
+
jwt?: string;
|
|
3
|
+
anonymousId?: string;
|
|
4
|
+
};
|
|
5
|
+
export type FeedbackOptions = {
|
|
6
|
+
board?: string;
|
|
7
|
+
title?: string;
|
|
8
|
+
theme?: "light" | "dark" | "system";
|
|
9
|
+
};
|
|
10
|
+
export type UpdatesOptions = {
|
|
11
|
+
theme?: "light" | "dark" | "system";
|
|
12
|
+
};
|
|
13
|
+
export declare function widgetUrl(portalUrl: string, kind: "feedback" | "updates" | "messenger", options: Record<string, string | undefined>, identity: WidgetIdentity): string;
|
|
14
|
+
type Message = {
|
|
15
|
+
type: "harkly:close";
|
|
16
|
+
} | {
|
|
17
|
+
type: "harkly:posted";
|
|
18
|
+
postId: string;
|
|
19
|
+
} | {
|
|
20
|
+
type: "harkly:height";
|
|
21
|
+
height: number;
|
|
22
|
+
};
|
|
23
|
+
export declare function parseMessage(data: unknown): Message | null;
|
|
24
|
+
export type OpenHandle = {
|
|
25
|
+
close: () => void;
|
|
26
|
+
};
|
|
27
|
+
export declare function openFrame(doc: Document, src: string, origin: string, opts: {
|
|
28
|
+
side?: boolean;
|
|
29
|
+
onPosted?: (postId: string) => void;
|
|
30
|
+
onClose?: () => void;
|
|
31
|
+
}): OpenHandle;
|
|
32
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@harkly/js",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Harkly browser SDK: identify your users so feedback, changelog and support know who they are.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./api": {
|
|
17
|
+
"types": "./dist/api.d.ts",
|
|
18
|
+
"import": "./dist/api.js",
|
|
19
|
+
"default": "./dist/api.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "bun run build.ts",
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"test": "bun test",
|
|
30
|
+
"api:types": "openapi-typescript openapi.json -o src/generated/api-types.ts",
|
|
31
|
+
"codegen": "bun run --cwd ../../apps/core api:spec && bun run api:types"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/bun": "latest",
|
|
35
|
+
"happy-dom": "^20.14.5",
|
|
36
|
+
"openapi-typescript": "^7.13.0"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"openapi-fetch": "^0.17.0"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://harkly.app",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/Rad-Soft/harkly.git",
|
|
45
|
+
"directory": "packages/sdk-js"
|
|
46
|
+
},
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"keywords": [
|
|
51
|
+
"harkly",
|
|
52
|
+
"feedback",
|
|
53
|
+
"support",
|
|
54
|
+
"messenger",
|
|
55
|
+
"widget"
|
|
56
|
+
]
|
|
57
|
+
}
|