passline-ai 2.0.0 → 2.1.3

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.
@@ -1,5 +1,5 @@
1
1
  import { PasslineMessage, PasslineSession } from "../component/types";
2
- export declare function usePasslineSession(token: string, origin?: string, persistSession?: boolean, maxSessionAge?: number, maxStoredMessages?: number): {
2
+ export declare function usePasslineSession(token: string, persistSession?: boolean, maxSessionAge?: number, maxStoredMessages?: number, locale?: string, defaultGreeting?: string): {
3
3
  session: PasslineSession | null;
4
4
  messages: PasslineMessage[];
5
5
  isLoading: boolean;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,26 @@
1
1
  import * as react from 'react';
2
2
  import react__default, { ReactNode } from 'react';
3
3
 
4
+ interface PasslineChatLabels {
5
+ assistant: string;
6
+ offline: string;
7
+ unavailable: string;
8
+ connecting: string;
9
+ online: string;
10
+ typeMessage: string;
11
+ send: string;
12
+ openChat: string;
13
+ closeChat: string;
14
+ newChat: string;
15
+ newChatTitle: string;
16
+ typing: string;
17
+ panelAriaLabel: string;
18
+ organizationLogo: string;
19
+ defaultGreeting: string;
20
+ }
21
+ declare const DEFAULT_PASSLINE_CHAT_LABELS: PasslineChatLabels;
22
+ declare function mergePasslineChatLabels(labels?: Partial<PasslineChatLabels>): PasslineChatLabels;
23
+
4
24
  interface PasslineMessage {
5
25
  id?: string;
6
26
  role: "user" | "assistant";
@@ -48,7 +68,10 @@ interface PasslineTheme {
48
68
  }
49
69
  interface PasslineChatProps {
50
70
  token: string;
51
- origin?: string;
71
+ /** BCP 47 language tag sent when creating a session (e.g. `en`, `nb`, `fr`). */
72
+ locale?: string;
73
+ /** Overrides for widget chrome strings. Conversation copy comes from the API. */
74
+ labels?: Partial<PasslineChatLabels>;
52
75
  theme?: PasslineTheme;
53
76
  className?: string;
54
77
  buttonClassName?: string;
@@ -92,7 +115,8 @@ interface PasslineChatProps {
92
115
  declare const PasslineChat: react__default.FC<PasslineChatProps>;
93
116
 
94
117
  declare function usePasslineChat(token: string, options?: {
95
- origin?: string;
118
+ locale?: string;
119
+ defaultGreeting?: string;
96
120
  persistSession?: boolean;
97
121
  maxSessionAge?: number;
98
122
  maxStoredMessages?: number;
@@ -115,7 +139,7 @@ declare function usePasslineChat(token: string, options?: {
115
139
  newChat: () => Promise<void>;
116
140
  };
117
141
 
118
- declare function usePasslineSession(token: string, origin?: string, persistSession?: boolean, maxSessionAge?: number, maxStoredMessages?: number): {
142
+ declare function usePasslineSession(token: string, persistSession?: boolean, maxSessionAge?: number, maxStoredMessages?: number, locale?: string, defaultGreeting?: string): {
119
143
  session: PasslineSession | null;
120
144
  messages: PasslineMessage[];
121
145
  isLoading: boolean;
@@ -128,35 +152,5 @@ declare function usePasslineSession(token: string, origin?: string, persistSessi
128
152
  clearStoredSession: () => void;
129
153
  };
130
154
 
131
- type ChatbotSessionResponse = {
132
- sessionId: string;
133
- greeting: string;
134
- assistantName: string;
135
- companyName: string | null;
136
- logoUrl: string | null;
137
- };
138
- type ChatbotMessageResponse = {
139
- reply: string;
140
- };
141
- declare class PasslineApiError extends Error {
142
- readonly code?: string;
143
- readonly status?: number;
144
- constructor(message: string, options?: {
145
- code?: string;
146
- status?: number;
147
- });
148
- }
149
- declare function isOfflineError(err: unknown): boolean;
150
- declare function isSessionInvalidError(err: unknown): boolean;
151
- declare class PasslineApi {
152
- private token;
153
- private origin;
154
- constructor(token: string, origin?: string);
155
- private getHeaders;
156
- private request;
157
- createSession(): Promise<ChatbotSessionResponse>;
158
- sendMessage(sessionId: string, message: string): Promise<ChatbotMessageResponse>;
159
- }
160
-
161
- export { PasslineApi, PasslineApiError, PasslineChat, isOfflineError, isSessionInvalidError, usePasslineChat, usePasslineSession };
162
- export type { ChatbotMessageResponse, ChatbotSessionResponse, PasslineChatProps, PasslineMessage, PasslineSession, PasslineTheme };
155
+ export { DEFAULT_PASSLINE_CHAT_LABELS, PasslineChat, mergePasslineChatLabels, usePasslineChat, usePasslineSession };
156
+ export type { PasslineChatLabels, PasslineChatProps, PasslineMessage, PasslineSession, PasslineTheme };
package/dist/index.esm.js CHANGED
@@ -1,2 +1,2 @@
1
- import{jsx as e,jsxs as s}from"react/jsx-runtime";import{useState as n,useRef as a,useEffect as t,useCallback as r}from"react";class o extends Error{constructor(e,s){super(e),this.name="PasslineApiError",this.code=s?.code,this.status=s?.status}}function l(e){if(!e)return!1;if(e instanceof o&&"OFFLINE"===e.code)return!0;const s=e instanceof Error?e.message:"";return/offline|failed to fetch|networkerror|load failed|request failed \(5\d\d\)/i.test(s)}function i(e){const s=e instanceof Error?e.message.toLowerCase():"";return/session/.test(s)&&/(not found|expired|invalid)/.test(s)}class c{constructor(e,s){this.token=e,this.origin=(s||"https://api.passline.ai").replace(/\/$/,"")}getHeaders(){return{Accept:"application/json","Content-Type":"application/json","X-Passline-Embed-Token":this.token}}async request(e,s={}){const n=`${this.origin}/api/chatbot/${e.replace(/^\//,"")}`;let a;try{a=await fetch(n,{method:"POST",headers:this.getHeaders(),body:JSON.stringify(s),cache:"no-store"})}catch{throw new o("Offline",{code:"OFFLINE"})}const t=await a.json().catch(()=>null);if(!a.ok){if(a.status>=500||0===a.status)throw new o("Offline",{code:"OFFLINE",status:a.status});throw new o(t?.message||`Request failed (${a.status})`,{status:a.status})}return t}async createSession(){return this.request("session",{})}async sendMessage(e,s){return this.request(`session/${encodeURIComponent(e)}/messages`,{message:s})}}function u(e){return`${e}-${Date.now()}-${Math.random().toString(36).slice(2,8)}`}function d(e,s,o=!0,d=6048e5,m=200){const[g,p]=n(null),[f,h]=n(!1),[b,C]=n(!1),[w,y]=n(null),N=a(new c(e,s)),S=a(null),v=a(0),x=`passline:chat:${e}`;S.current=g,t(()=>{N.current=new c(e,s)},[e,s]);const I=r(()=>{if(!o||"undefined"==typeof window)return null;try{const e=window.localStorage.getItem(x);if(!e)return null;const s=JSON.parse(e);return s?.sessionId&&Array.isArray(s.messages)?Date.now()-(s.updatedAt||0)>d?(window.localStorage.removeItem(x),null):s:null}catch{return null}},[d,o,x]),O=r(e=>{if(o&&"undefined"!=typeof window)try{const s=e.messages.length>m?e.messages.slice(e.messages.length-m):e.messages;window.localStorage.setItem(x,JSON.stringify({...e,messages:s}))}catch{}},[m,o,x]),k=r(()=>{if(o&&"undefined"!=typeof window)try{window.localStorage.removeItem(x)}catch{}},[o,x]),E=r(e=>{O({sessionId:e.sessionId,assistantName:e.assistantName,companyName:e.companyName,logoUrl:e.logoUrl,messages:e.messages,updatedAt:Date.now()})},[O]),$=r(()=>{C(!0),p(null),S.current=null,y("Offline"),h(!1)},[]),A=r(()=>{const e=I();if(!e)return null;const s={sessionId:e.sessionId,assistantName:e.assistantName||"Assistant",companyName:e.companyName??null,logoUrl:e.logoUrl??null,messages:e.messages};return p(s),S.current=s,C(!1),y(null),h(!1),s},[I]),M=r(async(e=!1)=>{const s=++v.current;if(y(null),!e){const e=A();if(e)return e}try{h(!0);const e=await N.current.createSession();if(v.current!==s){const e=S.current;if(e)return e}const n=function(e,s=[]){return{sessionId:e.sessionId,assistantName:e.assistantName||"Assistant",companyName:e.companyName??null,logoUrl:e.logoUrl??null,greeting:e.greeting,messages:s}}(e,[{id:"greeting",role:"assistant",content:e.greeting||"Hi! How can I help?",timestamp:Date.now()}]);return p(n),S.current=n,C(!1),E(n),n}catch(e){if(v.current!==s){const e=S.current;if(e)return e}if(l(e))throw $(),new Error("Offline");const n=e instanceof Error?e.message:"Unable to start chat";throw y(n),p(null),S.current=null,new Error(n)}finally{v.current===s&&h(!1)}},[$,E,A]),B=r(async()=>{if(b)return null;if(S.current?.sessionId)return S.current;try{return await M(!1)}catch{return null}},[M,b]),U=r(async e=>{const s=S.current;if(!s?.sessionId)throw new Error("No active session");const n={id:u("u"),role:"user",content:e,timestamp:Date.now()},a={...s,messages:[...s.messages,n]};p(a),S.current=a,E(a);try{const n=await N.current.sendMessage(s.sessionId,e),t={id:u("a"),role:"assistant",content:n.reply||"",timestamp:Date.now()},r=S.current??a,o={...r,messages:[...r.messages,t]};return p(o),S.current=o,E(o),t}catch(e){if(l(e))throw $(),new Error("Offline");if(i(e))throw k(),p(null),S.current=null,await M(!0),new Error("Session expired. Please try your message again.");throw e}},[k,M,$,E]),R=r(async()=>(k(),p(null),S.current=null,C(!1),M(!0)),[k,M]);return t(()=>{v.current+=1,p(null),S.current=null,C(!1),y(null),h(!1),A()},[A,e]),{session:g,messages:g?.messages||[],isLoading:f,offline:b,error:w,sendMessage:U,newChat:R,createSession:M,ensureSession:B,clearStoredSession:k}}function m(e,s){const[o,l]=n(Boolean(s?.autoOpen)),[i,c]=n(!1),u=a(null),{session:m,messages:g,isLoading:p,offline:f,error:h,sendMessage:b,newChat:C,ensureSession:w}=d(e,s?.origin,s?.persistSession,s?.maxSessionAge,s?.maxStoredMessages);t(()=>{s?.autoOpen&&l(!0)},[s?.autoOpen]),t(()=>{u.current=null},[e]),t(()=>{o&&!f&&w()},[w,o,f,e]),t(()=>{m?.sessionId&&m.sessionId!==u.current&&(u.current=m.sessionId,s?.onSessionStart?.(m))},[s,m]),t(()=>{h&&s?.onError&&s.onError(new Error(h))},[h,s]);const y=r(async e=>{if(e.trim()&&!i&&!p&&!f)try{c(!0),s?.onMessageSend?.(e);const n=await b(e);s?.onMessageReceive?.(n)}catch(e){s?.onError?.(e instanceof Error?e:new Error("Failed to send message"))}finally{c(!1)}},[p,i,f,s,b]),N=r(async()=>{try{await C()}catch(e){s?.onError?.(e instanceof Error?e:new Error("Failed to start new chat"))}},[C,s]),S=r(()=>{l(e=>!e)},[]);return{isOpen:o,setIsOpen:l,toggleOpen:S,isSending:i,isLoading:p,offline:f,messages:g,session:m,error:h,sendMessage:y,newChat:N}}const g={primaryColor:"#0f766e",primaryHoverColor:"#0d9488",backgroundColor:"#f8fafc",textColor:"#0f172a",userBubbleColor:"#0f766e",userBubbleTextColor:"#ffffff",assistantBubbleColor:"#ffffff",assistantBubbleTextColor:"#1e293b",borderRadius:"16px",borderWidth:"1px",borderColor:"#e2e8f0",buttonSize:"medium",buttonBorderRadius:"999px",buttonBackgroundColor:"#0f766e",buttonHoverColor:"#0d9488",buttonIconColor:"#ffffff",panelWidth:"380px",panelHeight:"560px",panelShadow:"0 12px 40px rgba(0,0,0,.22)",fontFamily:"system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",fontSize:"14px",spacing:"12px",padding:"12px",className:"",buttonClassName:"",panelClassName:"",headerClassName:"",messagesClassName:"",inputClassName:""};function p(e){const s=String(e||"").trim().split(/\s+/).filter(Boolean);return 0===s.length?"A":1===s.length?s[0].slice(0,2).toUpperCase():(s[0].charAt(0)+s[1].charAt(0)).toUpperCase()}function f({logoUrl:s,label:a,fallback:r="initials",className:o,backgroundColor:l}){const[i,c]=n(!1);t(()=>{c(!1)},[s]);const u=Boolean(s)&&!i;return e("div",{className:`${o}${u?" passline-has-logo":""}`,style:u?void 0:{backgroundColor:l},children:u?e("img",{src:s??"",alt:a||"Organization logo",className:"passline-chat-avatar-img",onError:()=>c(!0)}):"icon"===r?e("svg",{viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",children:e("path",{d:"M4 4h16a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H9l-5 4v-4H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z"})}):p(a)})}const h=({token:o,origin:l,theme:i={},className:c="",buttonClassName:u="",panelClassName:d="",renderButton:h,renderHeader:b,renderMessage:C,renderInput:w,autoOpen:y=!1,persistSession:N=!0,maxSessionAge:S,maxStoredMessages:v,onOpen:x,onClose:I,onMessageSend:O,onMessageReceive:k,onError:E,onSessionStart:$,onNewChat:A})=>{const M={...g,...i},[B,U]=n(""),R=a(null),F=a(null),{isOpen:H,setIsOpen:z,toggleOpen:L,isSending:T,isLoading:D,offline:q,messages:P,session:j,error:W,sendMessage:J,newChat:V}=m(o,{origin:l,persistSession:N,maxSessionAge:S,maxStoredMessages:v,autoOpen:y,onMessageSend:O,onMessageReceive:k,onError:E,onSessionStart:$}),K=j?.companyName||j?.assistantName||"Assistant",X=D||q||!j?.sessionId,G=q?"Offline":W&&!j?"Unavailable":j?.companyName||(D?"Connecting…":"Online"),Q=q?"Offline":D?"Connecting…":j?.sessionId?"Type a message…":"Unavailable";t(()=>{y&&z(!0)},[y,z]),t(()=>{H?(x?.(),q||setTimeout(()=>{F.current?.focus()},100)):I?.()},[H,q,x,I]),t(()=>{R.current?.scrollIntoView({behavior:"smooth"})},[P,T,D]);const Y=r(()=>{!B.trim()||T||X||(J(B),U(""))},[X,B,T,J]),Z=r(e=>{"Enter"!==e.key||e.shiftKey||(e.preventDefault(),Y())},[Y]),_=r(()=>{V(),A?.()},[V,A]),ee="small"===M.buttonSize?"44px":"large"===M.buttonSize?"64px":"56px",se=e("button",{className:`passline-chat-button ${M.buttonClassName} ${u}`,onClick:L,style:{backgroundColor:j?.logoUrl?void 0:H?M.primaryHoverColor:M.buttonBackgroundColor,color:M.buttonIconColor,borderRadius:M.buttonBorderRadius,width:ee,height:ee},"aria-label":H?"Close chat":"Open chat",children:e(f,{logoUrl:j?.logoUrl,label:K,fallback:"icon",className:"passline-chat-button-mark"})}),ne=s("div",{className:`passline-chat-header ${M.headerClassName}`,children:[e(f,{logoUrl:j?.logoUrl,label:K,className:"passline-header-avatar",backgroundColor:M.primaryColor}),s("div",{className:"passline-header-info",children:[e("div",{className:"passline-header-title",children:j?.assistantName||"Assistant"}),e("div",{className:"passline-header-subtitle",children:G})]}),e("button",{className:"passline-header-newchat",onClick:_,title:"Start new chat","aria-label":"Start new chat",style:{color:M.textColor},children:e("svg",{viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",children:e("path",{d:"M17.65 6.35A7.95 7.95 0 0 0 12 4a8 8 0 1 0 7.75 10h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4z"})})})]}),ae=s("div",{className:`passline-chat-input-container ${M.inputClassName}`,children:[e("input",{ref:F,type:"text",className:"passline-chat-input",value:B,onChange:e=>U(e.target.value),onKeyDown:Z,placeholder:Q,disabled:X,autoComplete:"off",style:{borderColor:M.borderColor,borderRadius:M.borderRadius,backgroundColor:M.backgroundColor,color:M.textColor,fontFamily:M.fontFamily,fontSize:M.fontSize}}),e("button",{className:"passline-chat-send",onClick:Y,disabled:X||T||!B.trim(),style:{backgroundColor:M.primaryColor,borderRadius:M.borderRadius,color:"#ffffff"},children:"Send"})]}),te=s("div",{className:`passline-chat-panel ${M.panelClassName} ${d}`,role:"dialog","aria-label":"Passline chat",style:{width:M.panelWidth,height:M.panelHeight,boxShadow:M.panelShadow,backgroundColor:M.backgroundColor,borderRadius:M.borderRadius,fontFamily:M.fontFamily,fontSize:M.fontSize,color:M.textColor},children:[b?b({title:j?.assistantName||"Assistant",subtitle:G,avatar:p(K),onNewChat:_}):ne,s("div",{className:`passline-chat-messages ${M.messagesClassName}`,children:[D&&0===P.length?e("p",{className:"passline-chat-meta",children:"Connecting…"}):null,P.map((s,n)=>C?C({message:s,className:""}):((s,n)=>e("div",{className:"passline-message "+("user"===s.role?"message-user":"message-assistant"),style:{backgroundColor:"user"===s.role?M.userBubbleColor:M.assistantBubbleColor,color:"user"===s.role?M.userBubbleTextColor:M.assistantBubbleTextColor,borderRadius:M.borderRadius,border:"assistant"===s.role?`${M.borderWidth} solid ${M.borderColor}`:"none"},children:s.content},s.id||n))(s,n)),T?e("p",{className:"passline-chat-typing",children:"Assistant is typing…"}):null,W?e("p",{className:"passline-chat-error",children:W}):null,e("div",{ref:R})]}),w?w({value:B,onChange:U,onSend:Y,disabled:X||T,placeholder:Q,className:""}):ae]});return s("div",{className:`passline-chat-wrapper ${M.className} ${c}`,children:[H&&te,h?h({onClick:L,isOpen:H,className:""}):se]})};export{c as PasslineApi,o as PasslineApiError,h as PasslineChat,l as isOfflineError,i as isSessionInvalidError,m as usePasslineChat,d as usePasslineSession};
1
+ import{jsxs as e,jsx as n}from"react/jsx-runtime";import t,{useState as s,useRef as a,useEffect as r,useCallback as o,useMemo as i}from"react";const l={name:"ECDH",namedCurve:"P-256"},c={name:"AES-GCM",length:256};function u(e){if(e instanceof Uint8Array){const n=new Uint8Array(e.byteLength);return n.set(e),n}return new Uint8Array(e)}function d(e){return(new TextEncoder).encode(e)}function h(e){const n=u(e);let t="";for(let e=0;e<n.length;e+=1)t+=String.fromCharCode(n[e]);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function p(e){const n=e.replace(/-/g,"+").replace(/_/g,"/"),t=n.length%4==0?"":"=".repeat(4-n.length%4),s=atob(n+t),a=new Uint8Array(s.length);for(let e=0;e<s.length;e+=1)a[e]=s.charCodeAt(e);return a}async function f(e){return h(await crypto.subtle.exportKey("spki",e))}async function g(e,n){return async function(e,n){const t=await crypto.subtle.deriveBits({name:"ECDH",public:n},e,256),s=await crypto.subtle.importKey("raw",t,"HKDF",!1,["deriveKey"]);return crypto.subtle.deriveKey({name:"HKDF",hash:"SHA-256",salt:d("p-ch"),info:d("aes")},s,c,!1,["encrypt","decrypt"])}(e,n)}class m extends Error{constructor(e,n){super(e),this.name="PasslineApiError",this.code=n?.code,this.status=n?.status}}function y(e){if(!e)return!1;if(e instanceof m&&"OFFLINE"===e.code)return!0;const n=e instanceof Error?e.message:"";return/offline|failed to fetch|networkerror|load failed|request failed \(5\d\d\)/i.test(n)}class w{constructor(e){this.channel=null,this.handshake=null,this.token=e}gatewayUrl(){return"https://api.passline.ai/api/p"}async postJson(e){let n;try{n=await fetch(this.gatewayUrl(),{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e),cache:"no-store"})}catch{throw new m("Offline",{code:"OFFLINE"})}const t=await n.json().catch(()=>null);return{status:n.status,payload:t}}throwIfFailed(e,n){if(e>=200&&e<300)return;if(e>=500||0===e)throw new m("Offline",{code:"OFFLINE",status:e});const t=n&&"object"==typeof n&&"message"in n?String(n.message||""):"";throw new m(t||`Request failed (${e})`,{status:e})}async ensureChannel(){if(this.channel)return this.channel;if(this.handshake||(this.handshake=this.openChannel().finally(()=>{this.handshake=null})),await this.handshake,!this.channel)throw new m("Offline",{code:"OFFLINE"});return this.channel}async openChannel(){const e=await async function(){return crypto.subtle.generateKey(l,!0,["deriveBits"])}(),{status:n,payload:t}=await this.postJson({v:1,op:"h",c:await f(e.publicKey)});if(n>=500)throw new m("Offline",{code:"OFFLINE",status:n});const s=t;if(!s?.s||!s.t)throw new m("Unable to start chat",{status:n});const a=await async function(e){return crypto.subtle.importKey("spki",u(p(e)),l,!0,[])}(s.s);this.channel={key:await g(e.privateKey,a),ticket:s.t}}async request(e,n){const t=async()=>{const t=await this.ensureChannel(),s=await async function(e,n){const t=u(crypto.getRandomValues(new Uint8Array(12))),s=await crypto.subtle.encrypt({name:"AES-GCM",iv:t},e,d(JSON.stringify(n)));return{i:h(t),c:h(s)}}(t.key,{r:e,d:n,k:this.token}),{status:a,payload:r}=await this.postJson({v:1,op:"x",t:t.ticket,...s});if(400===a||409===a)throw this.channel=null,new m("retry",{status:a});const o=r;if(!o||!("i"in o)||!("c"in o))throw this.throwIfFailed(a,r),new m("Request failed",{status:a});const i=await async function(e,n,t){const s=await crypto.subtle.decrypt({name:"AES-GCM",iv:u(p(n))},e,u(p(t)));return JSON.parse((new TextDecoder).decode(s))}(t.key,o.i,o.c);return this.throwIfFailed(a,i),i};try{return await t()}catch(e){if(e instanceof m&&"retry"===e.message)return t();throw e}}async createSession(e){const n={},t=e?.locale?.trim();return t&&(n.locale=t),this.request("s",n)}async sendMessage(e,n){return this.request("m",{id:e,message:n})}}function b(e){return`${e}-${Date.now()}-${Math.random().toString(36).slice(2,8)}`}function C(e,n=!0,t=6048e5,i=200,l,c="Hi! How can I help?"){const[u,d]=s(null),[h,p]=s(!1),[f,g]=s(!1),[m,C]=s(null),N=a(new w(e)),S=a(null),v=a(0),x=l?`passline:chat:${e}:${l}`:`passline:chat:${e}`;S.current=u,r(()=>{N.current=new w(e)},[e]);const k=o(()=>{if(!n||"undefined"==typeof window)return null;try{const e=window.localStorage.getItem(x);if(!e)return null;const n=JSON.parse(e);return n?.sessionId&&Array.isArray(n.messages)?Date.now()-(n.updatedAt||0)>t?(window.localStorage.removeItem(x),null):n:null}catch{return null}},[t,n,x]),I=o(e=>{if(n&&"undefined"!=typeof window)try{const n=e.messages.length>i?e.messages.slice(e.messages.length-i):e.messages;window.localStorage.setItem(x,JSON.stringify({...e,messages:n}))}catch{}},[i,n,x]),O=o(()=>{if(n&&"undefined"!=typeof window)try{window.localStorage.removeItem(x)}catch{}},[n,x]),E=o(e=>{I({sessionId:e.sessionId,assistantName:e.assistantName,companyName:e.companyName,logoUrl:e.logoUrl,messages:e.messages,updatedAt:Date.now()})},[I]),A=o(()=>{g(!0),d(null),S.current=null,C("Offline"),p(!1)},[]),M=o(()=>{const e=k();if(!e)return null;const n={sessionId:e.sessionId,assistantName:e.assistantName||"Assistant",companyName:e.companyName??null,logoUrl:e.logoUrl??null,messages:e.messages};return d(n),S.current=n,g(!1),C(null),p(!1),n},[k]),U=o(async(e=!1)=>{const n=++v.current;if(C(null),!e){const e=M();if(e)return e}try{p(!0);const e=await N.current.createSession(l?{locale:l}:void 0);if(v.current!==n){const e=S.current;if(e)return e}const t=function(e,n=[]){return{sessionId:e.sessionId,assistantName:e.assistantName||"Assistant",companyName:e.companyName??null,logoUrl:e.logoUrl??null,greeting:e.greeting,messages:n}}(e,[{id:"greeting",role:"assistant",content:e.greeting||c,timestamp:Date.now()}]);return d(t),S.current=t,g(!1),E(t),t}catch(e){if(v.current!==n){const e=S.current;if(e)return e}if(y(e))throw A(),new Error("Offline");const t=e instanceof Error?e.message:"Unable to start chat";throw C(t),d(null),S.current=null,new Error(t)}finally{v.current===n&&p(!1)}},[c,A,l,E,M]),F=o(async()=>{if(f)return null;if(S.current?.sessionId)return S.current;try{return await U(!1)}catch{return null}},[U,f]),B=o(async e=>{const n=S.current;if(!n?.sessionId)throw new Error("No active session");const t={id:b("u"),role:"user",content:e,timestamp:Date.now()},s={...n,messages:[...n.messages,t]};d(s),S.current=s,E(s);try{const t=await N.current.sendMessage(n.sessionId,e),a={id:b("a"),role:"assistant",content:t.reply||"",timestamp:Date.now()},r=S.current??s,o={...r,messages:[...r.messages,a]};return d(o),S.current=o,E(o),a}catch(e){if(y(e))throw A(),new Error("Offline");if(function(e){const n=e instanceof Error?e.message.toLowerCase():"";return/session/.test(n)&&/(not found|expired|invalid)/.test(n)}(e))throw O(),d(null),S.current=null,await U(!0),new Error("Session expired. Please try your message again.");throw e}},[O,U,A,E]),$=o(async()=>(O(),d(null),S.current=null,g(!1),U(!0)),[O,U]);return r(()=>{v.current+=1,d(null),S.current=null,g(!1),C(null),p(!1),M()},[M,e,l]),{session:u,messages:u?.messages||[],isLoading:h,offline:f,error:m,sendMessage:B,newChat:$,createSession:U,ensureSession:F,clearStoredSession:O}}function N(e,n){const[t,i]=s(Boolean(n?.autoOpen)),[l,c]=s(!1),u=a(null),{session:d,messages:h,isLoading:p,offline:f,error:g,sendMessage:m,newChat:y,ensureSession:w}=C(e,n?.persistSession,n?.maxSessionAge,n?.maxStoredMessages,n?.locale,n?.defaultGreeting);r(()=>{n?.autoOpen&&i(!0)},[n?.autoOpen]),r(()=>{u.current=null},[e,n?.locale]),r(()=>{t&&!f&&w()},[w,t,f,e,n?.locale]),r(()=>{d?.sessionId&&d.sessionId!==u.current&&(u.current=d.sessionId,n?.onSessionStart?.(d))},[n,d]),r(()=>{g&&n?.onError&&n.onError(new Error(g))},[g,n]);const b=o(async e=>{if(e.trim()&&!l&&!p&&!f)try{c(!0),n?.onMessageSend?.(e);const t=await m(e);n?.onMessageReceive?.(t)}catch(e){n?.onError?.(e instanceof Error?e:new Error("Failed to send message"))}finally{c(!1)}},[p,l,f,n,m]),N=o(async()=>{try{await y()}catch(e){n?.onError?.(e instanceof Error?e:new Error("Failed to start new chat"))}},[y,n]),S=o(()=>{i(e=>!e)},[]);return{isOpen:t,setIsOpen:i,toggleOpen:S,isSending:l,isLoading:p,offline:f,messages:h,session:d,error:g,sendMessage:b,newChat:N}}const S={assistant:"Assistant",offline:"Offline",unavailable:"Unavailable",connecting:"Connecting…",online:"Online",typeMessage:"Type a message…",send:"Send",openChat:"Open chat",closeChat:"Close chat",newChat:"Start new chat",newChatTitle:"Start new chat",typing:"Assistant is typing…",panelAriaLabel:"Passline chat",organizationLogo:"Organization logo",defaultGreeting:"Hi! How can I help?"};function v(e){return{...S,...e}}const x={primaryColor:"#0f766e",primaryHoverColor:"#0d9488",backgroundColor:"#f8fafc",textColor:"#0f172a",userBubbleColor:"#0f766e",userBubbleTextColor:"#ffffff",assistantBubbleColor:"#ffffff",assistantBubbleTextColor:"#1e293b",borderRadius:"16px",borderWidth:"1px",borderColor:"#e2e8f0",buttonSize:"medium",buttonBorderRadius:"999px",buttonBackgroundColor:"#0f766e",buttonHoverColor:"#0d9488",buttonIconColor:"#ffffff",panelWidth:"380px",panelHeight:"560px",panelShadow:"0 12px 40px rgba(0,0,0,.22)",fontFamily:"system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",fontSize:"14px",spacing:"12px",padding:"12px",className:"",buttonClassName:"",panelClassName:"",headerClassName:"",messagesClassName:"",inputClassName:""};function k(e){const n=String(e||"").trim().split(/\s+/).filter(Boolean);return 0===n.length?"A":1===n.length?n[0].slice(0,2).toUpperCase():(n[0].charAt(0)+n[1].charAt(0)).toUpperCase()}function I({logoUrl:e,label:t,logoAlt:a="Organization logo",fallback:o="initials",className:i,backgroundColor:l}){const[c,u]=s(!1);r(()=>{u(!1)},[e]);const d=Boolean(e)&&!c;return n("div",{className:`${i}${d?" passline-has-logo":""}`,style:d?void 0:{backgroundColor:l},children:d?n("img",{src:e??"",alt:t||a,className:"passline-chat-avatar-img",onError:()=>u(!0)}):"icon"===o?n("svg",{viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",children:n("path",{d:"M4 4h16a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H9l-5 4v-4H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z"})}):k(t)})}const O=({token:l,locale:c,labels:u,theme:d={},className:h="",buttonClassName:p="",panelClassName:f="",renderButton:g,renderHeader:m,renderMessage:y,renderInput:w,autoOpen:b=!1,persistSession:C=!0,maxSessionAge:S,maxStoredMessages:O,onOpen:E,onClose:A,onMessageSend:M,onMessageReceive:U,onError:F,onSessionStart:B,onNewChat:$})=>{const R={...x,...d},H=i(()=>v(u),[u]),[L,z]=s(""),D=a(null),T=a(null),K=a(!1),{isOpen:J,setIsOpen:G,toggleOpen:q,isSending:j,isLoading:P,offline:V,messages:W,session:_,error:Q,sendMessage:X,newChat:Y}=N(l,{locale:c,defaultGreeting:H.defaultGreeting,persistSession:C,maxSessionAge:S,maxStoredMessages:O,autoOpen:b,onMessageSend:M,onMessageReceive:U,onError:F,onSessionStart:B}),Z=_?.companyName||_?.assistantName||H.assistant,ee=P||V||!_?.sessionId,ne=V?H.offline:Q&&!_?H.unavailable:_?.companyName||(P?H.connecting:H.online),te=V?H.offline:P?H.connecting:_?.sessionId?H.typeMessage:H.unavailable;r(()=>{b&&G(!0)},[b,G]),r(()=>{J!==K.current&&(K.current=J,J?E?.():A?.())},[J,E,A]),r(()=>{if(!J||V)return;const e=window.setTimeout(()=>{T.current?.focus()},100);return()=>window.clearTimeout(e)},[J,V]),r(()=>{D.current?.scrollIntoView({behavior:"smooth"})},[W,j,P]);const se=o(()=>{!L.trim()||j||ee||(X(L),z(""))},[ee,L,j,X]),ae=o(e=>{"Enter"!==e.key||e.shiftKey||(e.preventDefault(),se())},[se]),re=o(()=>{Y(),$?.()},[Y,$]),oe="small"===R.buttonSize?"44px":"large"===R.buttonSize?"64px":"56px",ie=_?.assistantName||H.assistant;return e("div",{className:`passline-chat-wrapper ${R.className} ${h}`,children:[J?e("div",{className:`passline-chat-panel ${R.panelClassName} ${f}`,role:"dialog","aria-label":H.panelAriaLabel,style:{width:R.panelWidth,height:R.panelHeight,boxShadow:R.panelShadow,backgroundColor:R.backgroundColor,borderRadius:R.borderRadius,fontFamily:R.fontFamily,fontSize:R.fontSize,color:R.textColor},children:[m?m({title:ie,subtitle:ne,avatar:k(Z),onNewChat:re}):e("div",{className:`passline-chat-header ${R.headerClassName}`,children:[n(I,{logoUrl:_?.logoUrl,label:Z,logoAlt:H.organizationLogo,className:"passline-header-avatar",backgroundColor:R.primaryColor}),e("div",{className:"passline-header-info",children:[n("div",{className:"passline-header-title",children:ie}),n("div",{className:"passline-header-subtitle",children:ne})]}),n("button",{className:"passline-header-newchat",onClick:re,title:H.newChatTitle,"aria-label":H.newChat,style:{color:R.textColor},type:"button",children:n("svg",{viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",children:n("path",{d:"M17.65 6.35A7.95 7.95 0 0 0 12 4a8 8 0 1 0 7.75 10h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4z"})})})]}),e("div",{className:`passline-chat-messages ${R.messagesClassName}`,children:[P&&0===W.length?n("p",{className:"passline-chat-meta",children:H.connecting}):null,W.map((e,s)=>n(t.Fragment,{children:y?y({message:e,className:""}):n("div",{className:"passline-message "+("user"===e.role?"message-user":"message-assistant"),style:{backgroundColor:"user"===e.role?R.userBubbleColor:R.assistantBubbleColor,color:"user"===e.role?R.userBubbleTextColor:R.assistantBubbleTextColor,borderRadius:R.borderRadius,border:"assistant"===e.role?`${R.borderWidth} solid ${R.borderColor}`:"none"},children:e.content})},e.id||s)),j?n("p",{className:"passline-chat-typing",children:H.typing}):null,Q?n("p",{className:"passline-chat-error",children:Q}):null,n("div",{ref:D})]}),w?w({value:L,onChange:z,onSend:se,disabled:ee||j,placeholder:te,className:""}):e("div",{className:`passline-chat-input-container ${R.inputClassName}`,children:[n("input",{ref:T,type:"text",className:"passline-chat-input",value:L,onChange:e=>z(e.target.value),onKeyDown:ae,placeholder:te,disabled:ee,autoComplete:"off",style:{borderColor:R.borderColor,borderRadius:R.borderRadius,backgroundColor:R.backgroundColor,color:R.textColor,fontFamily:R.fontFamily,fontSize:R.fontSize}}),n("button",{className:"passline-chat-send",onClick:se,disabled:ee||j||!L.trim(),style:{backgroundColor:R.primaryColor,borderRadius:R.borderRadius,color:"#ffffff"},type:"button",children:H.send})]})]}):null,g?g({onClick:q,isOpen:J,className:""}):n("button",{className:`passline-chat-button ${R.buttonClassName} ${p}`,onClick:q,style:{backgroundColor:_?.logoUrl?void 0:J?R.primaryHoverColor:R.buttonBackgroundColor,color:R.buttonIconColor,borderRadius:R.buttonBorderRadius,width:oe,height:oe},"aria-label":J?H.closeChat:H.openChat,type:"button",children:n(I,{logoUrl:_?.logoUrl,label:Z,logoAlt:H.organizationLogo,fallback:"icon",className:"passline-chat-button-mark"})})]})};export{S as DEFAULT_PASSLINE_CHAT_LABELS,O as PasslineChat,v as mergePasslineChatLabels,N as usePasslineChat,C as usePasslineSession};
2
2
  //# sourceMappingURL=index.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":["../src/api/passlineApi.ts","../src/hooks/usePasslineSession.ts","../src/hooks/usePasslineClient.ts","../src/component/PasslineChat.tsx"],"sourcesContent":["import { PasslineSession } from \"../component/types\";\n\nexport type ChatbotSessionResponse = {\n sessionId: string;\n greeting: string;\n assistantName: string;\n companyName: string | null;\n logoUrl: string | null;\n};\n\nexport type ChatbotMessageResponse = {\n reply: string;\n};\n\nexport class PasslineApiError extends Error {\n readonly code?: string;\n readonly status?: number;\n\n constructor(message: string, options?: { code?: string; status?: number }) {\n super(message);\n this.name = \"PasslineApiError\";\n this.code = options?.code;\n this.status = options?.status;\n }\n}\n\nexport function isOfflineError(err: unknown): boolean {\n if (!err) return false;\n if (err instanceof PasslineApiError && err.code === \"OFFLINE\") return true;\n const message = err instanceof Error ? err.message : \"\";\n return /offline|failed to fetch|networkerror|load failed|request failed \\(5\\d\\d\\)/i.test(\n message,\n );\n}\n\nexport function isSessionInvalidError(err: unknown): boolean {\n const message = err instanceof Error ? err.message.toLowerCase() : \"\";\n // Booking/calendar 404s (\"credentials not found\") must not wipe the chat.\n return /session/.test(message) && /(not found|expired|invalid)/.test(message);\n}\n\nexport class PasslineApi {\n private token: string;\n private origin: string;\n\n constructor(token: string, origin?: string) {\n this.token = token;\n this.origin = (origin || \"https://api.passline.ai\").replace(/\\/$/, \"\");\n }\n\n private getHeaders(): HeadersInit {\n return {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n \"X-Passline-Embed-Token\": this.token,\n };\n }\n\n private async request<T>(\n path: string,\n body: Record<string, unknown> = {},\n ): Promise<T> {\n const url = `${this.origin}/api/chatbot/${path.replace(/^\\//, \"\")}`;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"POST\",\n headers: this.getHeaders(),\n body: JSON.stringify(body),\n cache: \"no-store\",\n });\n } catch {\n throw new PasslineApiError(\"Offline\", { code: \"OFFLINE\" });\n }\n\n const payload = (await response.json().catch(() => null)) as\n | (T & { message?: string })\n | null;\n\n if (!response.ok) {\n if (response.status >= 500 || response.status === 0) {\n throw new PasslineApiError(\"Offline\", {\n code: \"OFFLINE\",\n status: response.status,\n });\n }\n throw new PasslineApiError(\n payload?.message || `Request failed (${response.status})`,\n { status: response.status },\n );\n }\n\n return payload as T;\n }\n\n async createSession(): Promise<ChatbotSessionResponse> {\n return this.request<ChatbotSessionResponse>(\"session\", {});\n }\n\n async sendMessage(\n sessionId: string,\n message: string,\n ): Promise<ChatbotMessageResponse> {\n return this.request<ChatbotMessageResponse>(\n `session/${encodeURIComponent(sessionId)}/messages`,\n { message },\n );\n }\n}\n\nexport function toPasslineSession(\n response: ChatbotSessionResponse,\n messages: PasslineSession[\"messages\"] = [],\n): PasslineSession {\n return {\n sessionId: response.sessionId,\n assistantName: response.assistantName || \"Assistant\",\n companyName: response.companyName ?? null,\n logoUrl: response.logoUrl ?? null,\n greeting: response.greeting,\n messages,\n };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { PasslineMessage, PasslineSession } from \"../component/types\";\nimport {\n isOfflineError,\n isSessionInvalidError,\n PasslineApi,\n toPasslineSession,\n} from \"../api/passlineApi\";\n\nconst DEFAULT_GREETING = \"Hi! How can I help?\";\n\ninterface StoredSession {\n sessionId: string;\n assistantName?: string;\n companyName?: string | null;\n logoUrl?: string | null;\n messages: PasslineMessage[];\n updatedAt: number;\n}\n\nfunction nextMessageId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nexport function usePasslineSession(\n token: string,\n origin?: string,\n persistSession: boolean = true,\n maxSessionAge: number = 7 * 24 * 60 * 60 * 1000,\n maxStoredMessages: number = 200,\n) {\n const [session, setSession] = useState<PasslineSession | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n const [offline, setOffline] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const apiRef = useRef(new PasslineApi(token, origin));\n const sessionRef = useRef<PasslineSession | null>(null);\n const bootIdRef = useRef(0);\n const storageKey = `passline:chat:${token}`;\n\n sessionRef.current = session;\n\n useEffect(() => {\n apiRef.current = new PasslineApi(token, origin);\n }, [token, origin]);\n\n const readStoredSession = useCallback((): StoredSession | null => {\n if (!persistSession || typeof window === \"undefined\") return null;\n\n try {\n const raw = window.localStorage.getItem(storageKey);\n if (!raw) return null;\n\n const parsed = JSON.parse(raw) as StoredSession;\n if (!parsed?.sessionId || !Array.isArray(parsed.messages)) {\n return null;\n }\n\n if (Date.now() - (parsed.updatedAt || 0) > maxSessionAge) {\n window.localStorage.removeItem(storageKey);\n return null;\n }\n\n return parsed;\n } catch {\n return null;\n }\n }, [maxSessionAge, persistSession, storageKey]);\n\n const writeStoredSession = useCallback(\n (data: StoredSession) => {\n if (!persistSession || typeof window === \"undefined\") return;\n\n try {\n const messages =\n data.messages.length > maxStoredMessages\n ? data.messages.slice(data.messages.length - maxStoredMessages)\n : data.messages;\n window.localStorage.setItem(\n storageKey,\n JSON.stringify({ ...data, messages }),\n );\n } catch {\n // Private browsing or quota exceeded — degrade to in-memory only.\n }\n },\n [maxStoredMessages, persistSession, storageKey],\n );\n\n const clearStoredSession = useCallback(() => {\n if (!persistSession || typeof window === \"undefined\") return;\n try {\n window.localStorage.removeItem(storageKey);\n } catch {\n // ignore\n }\n }, [persistSession, storageKey]);\n\n const persistFromSession = useCallback(\n (next: PasslineSession) => {\n writeStoredSession({\n sessionId: next.sessionId,\n assistantName: next.assistantName,\n companyName: next.companyName,\n logoUrl: next.logoUrl,\n messages: next.messages,\n updatedAt: Date.now(),\n });\n },\n [writeStoredSession],\n );\n\n const goOffline = useCallback(() => {\n setOffline(true);\n setSession(null);\n sessionRef.current = null;\n setError(\"Offline\");\n setIsLoading(false);\n }, []);\n\n const restoreCachedSession = useCallback((): PasslineSession | null => {\n const stored = readStoredSession();\n if (!stored) return null;\n\n const restored: PasslineSession = {\n sessionId: stored.sessionId,\n assistantName: stored.assistantName || \"Assistant\",\n companyName: stored.companyName ?? null,\n logoUrl: stored.logoUrl ?? null,\n messages: stored.messages,\n };\n setSession(restored);\n sessionRef.current = restored;\n setOffline(false);\n setError(null);\n setIsLoading(false);\n return restored;\n }, [readStoredSession]);\n\n const createSession = useCallback(\n async (forceNew: boolean = false): Promise<PasslineSession> => {\n const bootId = ++bootIdRef.current;\n setError(null);\n\n if (!forceNew) {\n const restored = restoreCachedSession();\n if (restored) return restored;\n }\n\n try {\n setIsLoading(true);\n const response = await apiRef.current.createSession();\n if (bootIdRef.current !== bootId) {\n const current = sessionRef.current;\n if (current) return current;\n }\n\n const greeting = response.greeting || DEFAULT_GREETING;\n const next = toPasslineSession(response, [\n {\n id: \"greeting\",\n role: \"assistant\",\n content: greeting,\n timestamp: Date.now(),\n },\n ]);\n\n setSession(next);\n sessionRef.current = next;\n setOffline(false);\n persistFromSession(next);\n return next;\n } catch (err) {\n if (bootIdRef.current !== bootId) {\n const current = sessionRef.current;\n if (current) return current;\n }\n if (isOfflineError(err)) {\n goOffline();\n throw new Error(\"Offline\");\n }\n const errorMessage =\n err instanceof Error ? err.message : \"Unable to start chat\";\n setError(errorMessage);\n setSession(null);\n sessionRef.current = null;\n throw new Error(errorMessage);\n } finally {\n if (bootIdRef.current === bootId) {\n setIsLoading(false);\n }\n }\n },\n [goOffline, persistFromSession, restoreCachedSession],\n );\n\n const ensureSession = useCallback(async (): Promise<PasslineSession | null> => {\n if (offline) return null;\n if (sessionRef.current?.sessionId) return sessionRef.current;\n try {\n return await createSession(false);\n } catch {\n return null;\n }\n }, [createSession, offline]);\n\n const sendMessage = useCallback(\n async (content: string): Promise<PasslineMessage> => {\n const active = sessionRef.current;\n if (!active?.sessionId) {\n throw new Error(\"No active session\");\n }\n\n const userMessage: PasslineMessage = {\n id: nextMessageId(\"u\"),\n role: \"user\",\n content,\n timestamp: Date.now(),\n };\n\n const withUser: PasslineSession = {\n ...active,\n messages: [...active.messages, userMessage],\n };\n setSession(withUser);\n sessionRef.current = withUser;\n persistFromSession(withUser);\n\n try {\n const response = await apiRef.current.sendMessage(\n active.sessionId,\n content,\n );\n\n const assistantMessage: PasslineMessage = {\n id: nextMessageId(\"a\"),\n role: \"assistant\",\n content: response.reply || \"\",\n timestamp: Date.now(),\n };\n\n const latest = sessionRef.current ?? withUser;\n const withAssistant: PasslineSession = {\n ...latest,\n messages: [...latest.messages, assistantMessage],\n };\n setSession(withAssistant);\n sessionRef.current = withAssistant;\n persistFromSession(withAssistant);\n return assistantMessage;\n } catch (err) {\n if (isOfflineError(err)) {\n goOffline();\n throw new Error(\"Offline\");\n }\n if (isSessionInvalidError(err)) {\n clearStoredSession();\n setSession(null);\n sessionRef.current = null;\n await createSession(true);\n throw new Error(\"Session expired. Please try your message again.\");\n }\n throw err;\n }\n },\n [clearStoredSession, createSession, goOffline, persistFromSession],\n );\n\n const newChat = useCallback(async () => {\n clearStoredSession();\n setSession(null);\n sessionRef.current = null;\n setOffline(false);\n return createSession(true);\n }, [clearStoredSession, createSession]);\n\n useEffect(() => {\n bootIdRef.current += 1;\n setSession(null);\n sessionRef.current = null;\n setOffline(false);\n setError(null);\n setIsLoading(false);\n restoreCachedSession();\n }, [restoreCachedSession, token]);\n\n return {\n session,\n messages: session?.messages || [],\n isLoading,\n offline,\n error,\n sendMessage,\n newChat,\n createSession,\n ensureSession,\n clearStoredSession,\n };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { usePasslineSession } from \"./usePasslineSession\";\nimport { PasslineMessage, PasslineSession } from \"../component/types\";\n\nexport function usePasslineChat(\n token: string,\n options?: {\n origin?: string;\n persistSession?: boolean;\n maxSessionAge?: number;\n maxStoredMessages?: number;\n autoOpen?: boolean;\n onMessageSend?: (message: string) => void;\n onMessageReceive?: (message: PasslineMessage) => void;\n onError?: (error: Error) => void;\n onSessionStart?: (session: PasslineSession) => void;\n },\n) {\n const [isOpen, setIsOpen] = useState(Boolean(options?.autoOpen));\n const [isSending, setIsSending] = useState(false);\n const lastSessionIdRef = useRef<string | null>(null);\n\n const {\n session,\n messages,\n isLoading,\n offline,\n error,\n sendMessage: sendMessageApi,\n newChat: newChatApi,\n ensureSession,\n } = usePasslineSession(\n token,\n options?.origin,\n options?.persistSession,\n options?.maxSessionAge,\n options?.maxStoredMessages,\n );\n\n useEffect(() => {\n if (options?.autoOpen) {\n setIsOpen(true);\n }\n }, [options?.autoOpen]);\n\n useEffect(() => {\n lastSessionIdRef.current = null;\n }, [token]);\n\n useEffect(() => {\n if (!isOpen || offline) {\n return;\n }\n void ensureSession();\n }, [ensureSession, isOpen, offline, token]);\n\n useEffect(() => {\n if (!session?.sessionId || session.sessionId === lastSessionIdRef.current) {\n return;\n }\n lastSessionIdRef.current = session.sessionId;\n options?.onSessionStart?.(session);\n }, [options, session]);\n\n useEffect(() => {\n if (error && options?.onError) {\n options.onError(new Error(error));\n }\n }, [error, options]);\n\n const sendMessage = useCallback(\n async (content: string) => {\n if (!content.trim() || isSending || isLoading || offline) return;\n\n try {\n setIsSending(true);\n options?.onMessageSend?.(content);\n const response = await sendMessageApi(content);\n options?.onMessageReceive?.(response);\n } catch (err) {\n options?.onError?.(\n err instanceof Error ? err : new Error(\"Failed to send message\"),\n );\n } finally {\n setIsSending(false);\n }\n },\n [isLoading, isSending, offline, options, sendMessageApi],\n );\n\n const newChat = useCallback(async () => {\n try {\n await newChatApi();\n } catch (err) {\n options?.onError?.(\n err instanceof Error ? err : new Error(\"Failed to start new chat\"),\n );\n }\n }, [newChatApi, options]);\n\n const toggleOpen = useCallback(() => {\n setIsOpen((prev) => !prev);\n }, []);\n\n return {\n isOpen,\n setIsOpen,\n toggleOpen,\n isSending,\n isLoading,\n offline,\n messages,\n session,\n error,\n sendMessage,\n newChat,\n };\n}\n","import React, { useState, useRef, useEffect, useCallback } from \"react\";\nimport { usePasslineChat } from \"../hooks/usePasslineClient\";\nimport { PasslineChatProps, PasslineMessage, PasslineTheme } from \"./types\";\nimport \"./PasslineChat.css\";\n\nconst CHAT_ICON =\n \"M4 4h16a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H9l-5 4v-4H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z\";\n\nconst defaultTheme: Required<PasslineTheme> = {\n primaryColor: \"#0f766e\",\n primaryHoverColor: \"#0d9488\",\n backgroundColor: \"#f8fafc\",\n textColor: \"#0f172a\",\n userBubbleColor: \"#0f766e\",\n userBubbleTextColor: \"#ffffff\",\n assistantBubbleColor: \"#ffffff\",\n assistantBubbleTextColor: \"#1e293b\",\n borderRadius: \"16px\",\n borderWidth: \"1px\",\n borderColor: \"#e2e8f0\",\n buttonSize: \"medium\",\n buttonBorderRadius: \"999px\",\n buttonBackgroundColor: \"#0f766e\",\n buttonHoverColor: \"#0d9488\",\n buttonIconColor: \"#ffffff\",\n panelWidth: \"380px\",\n panelHeight: \"560px\",\n panelShadow: \"0 12px 40px rgba(0,0,0,.22)\",\n fontFamily:\n \"system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif\",\n fontSize: \"14px\",\n spacing: \"12px\",\n padding: \"12px\",\n className: \"\",\n buttonClassName: \"\",\n panelClassName: \"\",\n headerClassName: \"\",\n messagesClassName: \"\",\n inputClassName: \"\",\n};\n\nfunction brandingInitials(label?: string | null): string {\n const parts = String(label || \"\")\n .trim()\n .split(/\\s+/)\n .filter(Boolean);\n if (parts.length === 0) return \"A\";\n if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();\n return (parts[0].charAt(0) + parts[1].charAt(0)).toUpperCase();\n}\n\nfunction BrandMark({\n logoUrl,\n label,\n fallback = \"initials\",\n className,\n backgroundColor,\n}: {\n logoUrl?: string | null;\n label: string;\n fallback?: \"initials\" | \"icon\";\n className: string;\n backgroundColor?: string;\n}) {\n const [broken, setBroken] = useState(false);\n\n useEffect(() => {\n setBroken(false);\n }, [logoUrl]);\n\n const showLogo = Boolean(logoUrl) && !broken;\n\n return (\n <div\n className={`${className}${showLogo ? \" passline-has-logo\" : \"\"}`}\n style={showLogo ? undefined : { backgroundColor }}\n >\n {showLogo ? (\n <img\n src={logoUrl ?? \"\"}\n alt={label || \"Organization logo\"}\n className=\"passline-chat-avatar-img\"\n onError={() => setBroken(true)}\n />\n ) : fallback === \"icon\" ? (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d={CHAT_ICON} />\n </svg>\n ) : (\n brandingInitials(label)\n )}\n </div>\n );\n}\n\nexport const PasslineChat: React.FC<PasslineChatProps> = ({\n token,\n origin,\n theme = {},\n className = \"\",\n buttonClassName = \"\",\n panelClassName = \"\",\n renderButton,\n renderHeader,\n renderMessage,\n renderInput,\n autoOpen = false,\n persistSession = true,\n maxSessionAge,\n maxStoredMessages,\n onOpen,\n onClose,\n onMessageSend,\n onMessageReceive,\n onError,\n onSessionStart,\n onNewChat,\n}) => {\n const mergedTheme = { ...defaultTheme, ...theme };\n const [inputValue, setInputValue] = useState(\"\");\n const messagesEndRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const {\n isOpen,\n setIsOpen,\n toggleOpen,\n isSending,\n isLoading,\n offline,\n messages,\n session,\n error,\n sendMessage,\n newChat,\n } = usePasslineChat(token, {\n origin,\n persistSession,\n maxSessionAge,\n maxStoredMessages,\n autoOpen,\n onMessageSend,\n onMessageReceive,\n onError,\n onSessionStart,\n });\n\n const brandLabel =\n session?.companyName || session?.assistantName || \"Assistant\";\n const inputLocked = isLoading || offline || !session?.sessionId;\n const subtitle = offline\n ? \"Offline\"\n : error && !session\n ? \"Unavailable\"\n : session?.companyName || (isLoading ? \"Connecting…\" : \"Online\");\n const placeholder = offline\n ? \"Offline\"\n : isLoading\n ? \"Connecting…\"\n : session?.sessionId\n ? \"Type a message…\"\n : \"Unavailable\";\n\n useEffect(() => {\n if (autoOpen) {\n setIsOpen(true);\n }\n }, [autoOpen, setIsOpen]);\n\n useEffect(() => {\n if (isOpen) {\n onOpen?.();\n if (!offline) {\n setTimeout(() => {\n inputRef.current?.focus();\n }, 100);\n }\n } else {\n onClose?.();\n }\n }, [isOpen, offline, onOpen, onClose]);\n\n useEffect(() => {\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }, [messages, isSending, isLoading]);\n\n const handleSend = useCallback(() => {\n if (inputValue.trim() && !isSending && !inputLocked) {\n sendMessage(inputValue);\n setInputValue(\"\");\n }\n }, [inputLocked, inputValue, isSending, sendMessage]);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" && !e.shiftKey) {\n e.preventDefault();\n handleSend();\n }\n },\n [handleSend],\n );\n\n const handleNewChat = useCallback(() => {\n newChat();\n onNewChat?.();\n }, [newChat, onNewChat]);\n\n const buttonSizePx =\n mergedTheme.buttonSize === \"small\"\n ? \"44px\"\n : mergedTheme.buttonSize === \"large\"\n ? \"64px\"\n : \"56px\";\n\n const defaultButton = (\n <button\n className={`passline-chat-button ${mergedTheme.buttonClassName} ${buttonClassName}`}\n onClick={toggleOpen}\n style={{\n backgroundColor: session?.logoUrl\n ? undefined\n : isOpen\n ? mergedTheme.primaryHoverColor\n : mergedTheme.buttonBackgroundColor,\n color: mergedTheme.buttonIconColor,\n borderRadius: mergedTheme.buttonBorderRadius,\n width: buttonSizePx,\n height: buttonSizePx,\n }}\n aria-label={isOpen ? \"Close chat\" : \"Open chat\"}\n >\n <BrandMark\n logoUrl={session?.logoUrl}\n label={brandLabel}\n fallback=\"icon\"\n className=\"passline-chat-button-mark\"\n />\n </button>\n );\n\n const defaultMessage = (message: PasslineMessage, index: number) => (\n <div\n key={message.id || index}\n className={`passline-message ${message.role === \"user\" ? \"message-user\" : \"message-assistant\"}`}\n style={{\n backgroundColor:\n message.role === \"user\"\n ? mergedTheme.userBubbleColor\n : mergedTheme.assistantBubbleColor,\n color:\n message.role === \"user\"\n ? mergedTheme.userBubbleTextColor\n : mergedTheme.assistantBubbleTextColor,\n borderRadius: mergedTheme.borderRadius,\n border:\n message.role === \"assistant\"\n ? `${mergedTheme.borderWidth} solid ${mergedTheme.borderColor}`\n : \"none\",\n }}\n >\n {message.content}\n </div>\n );\n\n const defaultHeader = (\n <div className={`passline-chat-header ${mergedTheme.headerClassName}`}>\n <BrandMark\n logoUrl={session?.logoUrl}\n label={brandLabel}\n className=\"passline-header-avatar\"\n backgroundColor={mergedTheme.primaryColor}\n />\n <div className=\"passline-header-info\">\n <div className=\"passline-header-title\">\n {session?.assistantName || \"Assistant\"}\n </div>\n <div className=\"passline-header-subtitle\">{subtitle}</div>\n </div>\n <button\n className=\"passline-header-newchat\"\n onClick={handleNewChat}\n title=\"Start new chat\"\n aria-label=\"Start new chat\"\n style={{\n color: mergedTheme.textColor,\n }}\n >\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M17.65 6.35A7.95 7.95 0 0 0 12 4a8 8 0 1 0 7.75 10h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4z\" />\n </svg>\n </button>\n </div>\n );\n\n const defaultInput = (\n <div\n className={`passline-chat-input-container ${mergedTheme.inputClassName}`}\n >\n <input\n ref={inputRef}\n type=\"text\"\n className=\"passline-chat-input\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n disabled={inputLocked}\n autoComplete=\"off\"\n style={{\n borderColor: mergedTheme.borderColor,\n borderRadius: mergedTheme.borderRadius,\n backgroundColor: mergedTheme.backgroundColor,\n color: mergedTheme.textColor,\n fontFamily: mergedTheme.fontFamily,\n fontSize: mergedTheme.fontSize,\n }}\n />\n <button\n className=\"passline-chat-send\"\n onClick={handleSend}\n disabled={inputLocked || isSending || !inputValue.trim()}\n style={{\n backgroundColor: mergedTheme.primaryColor,\n borderRadius: mergedTheme.borderRadius,\n color: \"#ffffff\",\n }}\n >\n Send\n </button>\n </div>\n );\n\n const defaultPanel = (\n <div\n className={`passline-chat-panel ${mergedTheme.panelClassName} ${panelClassName}`}\n role=\"dialog\"\n aria-label=\"Passline chat\"\n style={{\n width: mergedTheme.panelWidth,\n height: mergedTheme.panelHeight,\n boxShadow: mergedTheme.panelShadow,\n backgroundColor: mergedTheme.backgroundColor,\n borderRadius: mergedTheme.borderRadius,\n fontFamily: mergedTheme.fontFamily,\n fontSize: mergedTheme.fontSize,\n color: mergedTheme.textColor,\n }}\n >\n {renderHeader\n ? renderHeader({\n title: session?.assistantName || \"Assistant\",\n subtitle,\n avatar: brandingInitials(brandLabel),\n onNewChat: handleNewChat,\n })\n : defaultHeader}\n\n <div className={`passline-chat-messages ${mergedTheme.messagesClassName}`}>\n {isLoading && messages.length === 0 ? (\n <p className=\"passline-chat-meta\">Connecting…</p>\n ) : null}\n\n {messages.map((msg, index) =>\n renderMessage\n ? renderMessage({ message: msg, className: \"\" })\n : defaultMessage(msg, index),\n )}\n\n {isSending ? (\n <p className=\"passline-chat-typing\">Assistant is typing…</p>\n ) : null}\n\n {error ? <p className=\"passline-chat-error\">{error}</p> : null}\n <div ref={messagesEndRef} />\n </div>\n\n {renderInput\n ? renderInput({\n value: inputValue,\n onChange: setInputValue,\n onSend: handleSend,\n disabled: inputLocked || isSending,\n placeholder,\n className: \"\",\n })\n : defaultInput}\n </div>\n );\n\n return (\n <div\n className={`passline-chat-wrapper ${mergedTheme.className} ${className}`}\n >\n {isOpen && defaultPanel}\n {renderButton\n ? renderButton({ onClick: toggleOpen, isOpen, className: \"\" })\n : defaultButton}\n </div>\n );\n};\n"],"names":["PasslineApiError","Error","constructor","message","options","super","this","name","code","status","isOfflineError","err","test","isSessionInvalidError","toLowerCase","PasslineApi","token","origin","replace","getHeaders","Accept","request","path","body","url","response","fetch","method","headers","JSON","stringify","cache","payload","json","catch","ok","createSession","sendMessage","sessionId","encodeURIComponent","nextMessageId","prefix","Date","now","Math","random","toString","slice","usePasslineSession","persistSession","maxSessionAge","maxStoredMessages","session","setSession","useState","isLoading","setIsLoading","offline","setOffline","error","setError","apiRef","useRef","sessionRef","bootIdRef","storageKey","current","useEffect","readStoredSession","useCallback","window","raw","localStorage","getItem","parsed","parse","Array","isArray","messages","updatedAt","removeItem","writeStoredSession","data","length","setItem","clearStoredSession","persistFromSession","next","assistantName","companyName","logoUrl","goOffline","restoreCachedSession","stored","restored","async","forceNew","bootId","greeting","toPasslineSession","id","role","content","timestamp","errorMessage","ensureSession","active","userMessage","withUser","assistantMessage","reply","latest","withAssistant","newChat","usePasslineChat","isOpen","setIsOpen","Boolean","autoOpen","isSending","setIsSending","lastSessionIdRef","sendMessageApi","newChatApi","onSessionStart","onError","trim","onMessageSend","onMessageReceive","toggleOpen","prev","defaultTheme","primaryColor","primaryHoverColor","backgroundColor","textColor","userBubbleColor","userBubbleTextColor","assistantBubbleColor","assistantBubbleTextColor","borderRadius","borderWidth","borderColor","buttonSize","buttonBorderRadius","buttonBackgroundColor","buttonHoverColor","buttonIconColor","panelWidth","panelHeight","panelShadow","fontFamily","fontSize","spacing","padding","className","buttonClassName","panelClassName","headerClassName","messagesClassName","inputClassName","brandingInitials","label","parts","String","split","filter","toUpperCase","charAt","BrandMark","fallback","broken","setBroken","showLogo","_jsx","style","undefined","src","alt","viewBox","fill","d","PasslineChat","theme","renderButton","renderHeader","renderMessage","renderInput","onOpen","onClose","onNewChat","mergedTheme","inputValue","setInputValue","messagesEndRef","inputRef","brandLabel","inputLocked","subtitle","placeholder","setTimeout","focus","scrollIntoView","behavior","handleSend","handleKeyDown","e","key","shiftKey","preventDefault","handleNewChat","buttonSizePx","defaultButton","onClick","color","width","height","children","defaultHeader","_jsxs","title","defaultInput","ref","type","value","onChange","target","onKeyDown","disabled","autoComplete","defaultPanel","boxShadow","avatar","map","msg","index","border","defaultMessage","onSend"],"mappings":"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"}
1
+ {"version":3,"file":"index.esm.js","sources":["../src/crypto/envelope.ts","../src/api/passlineApi.ts","../src/hooks/usePasslineSession.ts","../src/hooks/usePasslineClient.ts","../src/component/labels.ts","../src/component/passlineChat.tsx"],"sourcesContent":["const ECDH: EcKeyGenParams = { name: \"ECDH\", namedCurve: \"P-256\" };\nconst AES: AesKeyGenParams = { name: \"AES-GCM\", length: 256 };\n\nexport const PROXY_PATH = \"/api/p\";\n/** Public widget gateway. This is the only host the npm package calls. */\nexport const PASSLINE_GATEWAY = \"https://api.passline.ai\";\n\nexport type HandshakeRequest = { v: 1; op: \"h\"; c: string };\nexport type HandshakeResponse = { v: 1; s: string; t: string };\nexport type RpcRequest = { v: 1; op: \"x\"; t: string; i: string; c: string };\nexport type RpcResponse = { v: 1; i: string; c: string };\n\nfunction bytesOf(data: ArrayBuffer | Uint8Array): Uint8Array<ArrayBuffer> {\n if (data instanceof Uint8Array) {\n const copy = new Uint8Array(data.byteLength);\n copy.set(data);\n return copy;\n }\n return new Uint8Array(data);\n}\n\nexport function utf8(value: string): Uint8Array<ArrayBuffer> {\n return new TextEncoder().encode(value);\n}\n\nexport function b64encode(data: ArrayBuffer | Uint8Array): string {\n const bytes = bytesOf(data);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i += 1) {\n binary += String.fromCharCode(bytes[i]);\n }\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/g, \"\");\n}\n\nexport function b64decode(value: string): Uint8Array<ArrayBuffer> {\n const padded = value.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const pad =\n padded.length % 4 === 0 ? \"\" : \"=\".repeat(4 - (padded.length % 4));\n const binary = atob(padded + pad);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i += 1) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes;\n}\n\nexport async function generateEcdhPair(): Promise<CryptoKeyPair> {\n return crypto.subtle.generateKey(ECDH, true, [\"deriveBits\"]);\n}\n\nexport async function exportPublicKey(key: CryptoKey): Promise<string> {\n return b64encode(await crypto.subtle.exportKey(\"spki\", key));\n}\n\nexport async function importPublicKey(spki: string): Promise<CryptoKey> {\n return crypto.subtle.importKey(\n \"spki\",\n bytesOf(b64decode(spki)),\n ECDH,\n true,\n [],\n );\n}\n\nasync function deriveAesKey(\n privateKey: CryptoKey,\n publicKey: CryptoKey,\n): Promise<CryptoKey> {\n const bits = await crypto.subtle.deriveBits(\n { name: \"ECDH\", public: publicKey },\n privateKey,\n 256,\n );\n const ikm = await crypto.subtle.importKey(\"raw\", bits, \"HKDF\", false, [\n \"deriveKey\",\n ]);\n return crypto.subtle.deriveKey(\n { name: \"HKDF\", hash: \"SHA-256\", salt: utf8(\"p-ch\"), info: utf8(\"aes\") },\n ikm,\n AES,\n false,\n [\"encrypt\", \"decrypt\"],\n );\n}\n\nexport async function sharedAesKey(\n privateKey: CryptoKey,\n publicKey: CryptoKey,\n): Promise<CryptoKey> {\n return deriveAesKey(privateKey, publicKey);\n}\n\nexport async function encryptJson(\n key: CryptoKey,\n payload: unknown,\n): Promise<{ i: string; c: string }> {\n const iv = bytesOf(crypto.getRandomValues(new Uint8Array(12)));\n const ct = await crypto.subtle.encrypt(\n { name: \"AES-GCM\", iv },\n key,\n utf8(JSON.stringify(payload)),\n );\n return { i: b64encode(iv), c: b64encode(ct) };\n}\n\nexport async function decryptJson<T>(\n key: CryptoKey,\n i: string,\n c: string,\n): Promise<T> {\n const pt = await crypto.subtle.decrypt(\n { name: \"AES-GCM\", iv: bytesOf(b64decode(i)) },\n key,\n bytesOf(b64decode(c)),\n );\n return JSON.parse(new TextDecoder().decode(pt)) as T;\n}\n\nasync function sealKey(secret: string): Promise<CryptoKey> {\n const ikm = await crypto.subtle.importKey(\n \"raw\",\n bytesOf(utf8(secret)),\n \"HKDF\",\n false,\n [\"deriveKey\"],\n );\n return crypto.subtle.deriveKey(\n { name: \"HKDF\", hash: \"SHA-256\", salt: utf8(\"p-tk\"), info: utf8(\"seal\") },\n ikm,\n AES,\n false,\n [\"encrypt\", \"decrypt\"],\n );\n}\n\nexport async function sealTicket(\n secret: string,\n sharedBits: ArrayBuffer,\n ttlMs = 2 * 60 * 60 * 1000,\n): Promise<string> {\n const key = await sealKey(secret);\n const iv = bytesOf(crypto.getRandomValues(new Uint8Array(12)));\n const body = utf8(\n JSON.stringify({\n s: b64encode(sharedBits),\n exp: Date.now() + ttlMs,\n }),\n );\n const ct = await crypto.subtle.encrypt({ name: \"AES-GCM\", iv }, key, body);\n const packed = new Uint8Array(iv.length + ct.byteLength);\n packed.set(iv, 0);\n packed.set(new Uint8Array(ct), iv.length);\n return b64encode(packed);\n}\n\nexport async function openTicket(\n secret: string,\n ticket: string,\n): Promise<CryptoKey> {\n const key = await sealKey(secret);\n const packed = b64decode(ticket);\n if (packed.byteLength < 13) {\n throw new Error(\"bad ticket\");\n }\n const iv = bytesOf(packed.slice(0, 12));\n const ct = bytesOf(packed.slice(12));\n const pt = await crypto.subtle.decrypt({ name: \"AES-GCM\", iv }, key, ct);\n const parsed = JSON.parse(new TextDecoder().decode(pt)) as {\n s?: string;\n exp?: number;\n };\n if (!parsed.s || !parsed.exp || Date.now() > parsed.exp) {\n throw new Error(\"expired ticket\");\n }\n const bits = bytesOf(b64decode(parsed.s));\n const ikm = await crypto.subtle.importKey(\"raw\", bits, \"HKDF\", false, [\n \"deriveKey\",\n ]);\n return crypto.subtle.deriveKey(\n { name: \"HKDF\", hash: \"SHA-256\", salt: utf8(\"p-ch\"), info: utf8(\"aes\") },\n ikm,\n AES,\n false,\n [\"encrypt\", \"decrypt\"],\n );\n}\n\nexport async function ecdhBits(\n privateKey: CryptoKey,\n publicKey: CryptoKey,\n): Promise<ArrayBuffer> {\n return crypto.subtle.deriveBits(\n { name: \"ECDH\", public: publicKey },\n privateKey,\n 256,\n );\n}\n","import { PasslineSession } from \"../component/types\";\nimport {\n decryptJson,\n encryptJson,\n exportPublicKey,\n generateEcdhPair,\n importPublicKey,\n PASSLINE_GATEWAY,\n PROXY_PATH,\n sharedAesKey,\n type HandshakeResponse,\n type RpcResponse,\n} from \"../crypto/envelope\";\n\nexport type ChatbotSessionResponse = {\n sessionId: string;\n greeting: string;\n assistantName: string;\n companyName: string | null;\n logoUrl: string | null;\n};\n\nexport type ChatbotMessageResponse = {\n reply: string;\n};\n\nexport class PasslineApiError extends Error {\n readonly code?: string;\n readonly status?: number;\n\n constructor(message: string, options?: { code?: string; status?: number }) {\n super(message);\n this.name = \"PasslineApiError\";\n this.code = options?.code;\n this.status = options?.status;\n }\n}\n\nexport function isOfflineError(err: unknown): boolean {\n if (!err) return false;\n if (err instanceof PasslineApiError && err.code === \"OFFLINE\") return true;\n const message = err instanceof Error ? err.message : \"\";\n return /offline|failed to fetch|networkerror|load failed|request failed \\(5\\d\\d\\)/i.test(\n message,\n );\n}\n\nexport function isSessionInvalidError(err: unknown): boolean {\n const message = err instanceof Error ? err.message.toLowerCase() : \"\";\n return /session/.test(message) && /(not found|expired|invalid)/.test(message);\n}\n\nexport class PasslineApi {\n private token: string;\n private channel: { key: CryptoKey; ticket: string } | null = null;\n private handshake: Promise<void> | null = null;\n\n constructor(token: string) {\n this.token = token;\n }\n\n private gatewayUrl(): string {\n return `${PASSLINE_GATEWAY}${PROXY_PATH}`;\n }\n\n private async postJson(\n body: unknown,\n ): Promise<{ status: number; payload: unknown }> {\n let response: Response;\n try {\n response = await fetch(this.gatewayUrl(), {\n method: \"POST\",\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n cache: \"no-store\",\n });\n } catch {\n throw new PasslineApiError(\"Offline\", { code: \"OFFLINE\" });\n }\n const payload = await response.json().catch(() => null);\n return { status: response.status, payload };\n }\n\n private throwIfFailed(status: number, payload: unknown): void {\n if (status >= 200 && status < 300) return;\n if (status >= 500 || status === 0) {\n throw new PasslineApiError(\"Offline\", { code: \"OFFLINE\", status });\n }\n const message =\n payload && typeof payload === \"object\" && \"message\" in payload\n ? String((payload as { message?: string }).message || \"\")\n : \"\";\n throw new PasslineApiError(message || `Request failed (${status})`, {\n status,\n });\n }\n\n private async ensureChannel(): Promise<{ key: CryptoKey; ticket: string }> {\n if (this.channel) return this.channel;\n if (!this.handshake) {\n this.handshake = this.openChannel().finally(() => {\n this.handshake = null;\n });\n }\n await this.handshake;\n if (!this.channel) {\n throw new PasslineApiError(\"Offline\", { code: \"OFFLINE\" });\n }\n return this.channel;\n }\n\n private async openChannel(): Promise<void> {\n const pair = await generateEcdhPair();\n const { status, payload } = await this.postJson({\n v: 1,\n op: \"h\",\n c: await exportPublicKey(pair.publicKey),\n });\n if (status >= 500) {\n throw new PasslineApiError(\"Offline\", { code: \"OFFLINE\", status });\n }\n const handshake = payload as HandshakeResponse | null;\n if (!handshake?.s || !handshake.t) {\n throw new PasslineApiError(\"Unable to start chat\", { status });\n }\n const serverPub = await importPublicKey(handshake.s);\n this.channel = {\n key: await sharedAesKey(pair.privateKey, serverPub),\n ticket: handshake.t,\n };\n }\n\n private async request<T>(\n route: \"s\" | \"m\",\n data: Record<string, unknown>,\n ): Promise<T> {\n const run = async (): Promise<T> => {\n const channel = await this.ensureChannel();\n const encrypted = await encryptJson(channel.key, {\n r: route,\n d: data,\n k: this.token,\n });\n const { status, payload } = await this.postJson({\n v: 1,\n op: \"x\",\n t: channel.ticket,\n ...encrypted,\n });\n if (status === 400 || status === 409) {\n this.channel = null;\n throw new PasslineApiError(\"retry\", { status });\n }\n const envelope = payload as RpcResponse | { message?: string } | null;\n if (!envelope || !(\"i\" in envelope) || !(\"c\" in envelope)) {\n this.throwIfFailed(status, payload);\n throw new PasslineApiError(\"Request failed\", { status });\n }\n const decoded = await decryptJson<T & { message?: string }>(\n channel.key,\n envelope.i,\n envelope.c,\n );\n this.throwIfFailed(status, decoded);\n return decoded;\n };\n\n try {\n return await run();\n } catch (error) {\n if (error instanceof PasslineApiError && error.message === \"retry\") {\n return run();\n }\n throw error;\n }\n }\n\n async createSession(options?: { locale?: string }): Promise<ChatbotSessionResponse> {\n const body: Record<string, unknown> = {};\n const locale = options?.locale?.trim();\n if (locale) body.locale = locale;\n return this.request<ChatbotSessionResponse>(\"s\", body);\n }\n\n async sendMessage(\n sessionId: string,\n message: string,\n ): Promise<ChatbotMessageResponse> {\n return this.request<ChatbotMessageResponse>(\"m\", {\n id: sessionId,\n message,\n });\n }\n}\n\nexport function toPasslineSession(\n response: ChatbotSessionResponse,\n messages: PasslineSession[\"messages\"] = [],\n): PasslineSession {\n return {\n sessionId: response.sessionId,\n assistantName: response.assistantName || \"Assistant\",\n companyName: response.companyName ?? null,\n logoUrl: response.logoUrl ?? null,\n greeting: response.greeting,\n messages,\n };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { PasslineMessage, PasslineSession } from \"../component/types\";\nimport {\n isOfflineError,\n isSessionInvalidError,\n PasslineApi,\n toPasslineSession,\n} from \"../api/passlineApi\";\n\ninterface StoredSession {\n sessionId: string;\n assistantName?: string;\n companyName?: string | null;\n logoUrl?: string | null;\n messages: PasslineMessage[];\n updatedAt: number;\n}\n\nfunction nextMessageId(prefix: string): string {\n return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n}\n\nexport function usePasslineSession(\n token: string,\n persistSession: boolean = true,\n maxSessionAge: number = 7 * 24 * 60 * 60 * 1000,\n maxStoredMessages: number = 200,\n locale?: string,\n defaultGreeting: string = \"Hi! How can I help?\",\n) {\n const [session, setSession] = useState<PasslineSession | null>(null);\n const [isLoading, setIsLoading] = useState(false);\n const [offline, setOffline] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const apiRef = useRef(new PasslineApi(token));\n const sessionRef = useRef<PasslineSession | null>(null);\n const bootIdRef = useRef(0);\n const storageKey = locale\n ? `passline:chat:${token}:${locale}`\n : `passline:chat:${token}`;\n\n sessionRef.current = session;\n\n useEffect(() => {\n apiRef.current = new PasslineApi(token);\n }, [token]);\n\n const readStoredSession = useCallback((): StoredSession | null => {\n if (!persistSession || typeof window === \"undefined\") return null;\n\n try {\n const raw = window.localStorage.getItem(storageKey);\n if (!raw) return null;\n\n const parsed = JSON.parse(raw) as StoredSession;\n if (!parsed?.sessionId || !Array.isArray(parsed.messages)) {\n return null;\n }\n\n if (Date.now() - (parsed.updatedAt || 0) > maxSessionAge) {\n window.localStorage.removeItem(storageKey);\n return null;\n }\n\n return parsed;\n } catch {\n return null;\n }\n }, [maxSessionAge, persistSession, storageKey]);\n\n const writeStoredSession = useCallback(\n (data: StoredSession) => {\n if (!persistSession || typeof window === \"undefined\") return;\n\n try {\n const messages =\n data.messages.length > maxStoredMessages\n ? data.messages.slice(data.messages.length - maxStoredMessages)\n : data.messages;\n window.localStorage.setItem(\n storageKey,\n JSON.stringify({ ...data, messages }),\n );\n } catch {\n // Private browsing or quota exceeded — degrade to in-memory only.\n }\n },\n [maxStoredMessages, persistSession, storageKey],\n );\n\n const clearStoredSession = useCallback(() => {\n if (!persistSession || typeof window === \"undefined\") return;\n try {\n window.localStorage.removeItem(storageKey);\n } catch {\n // ignore\n }\n }, [persistSession, storageKey]);\n\n const persistFromSession = useCallback(\n (next: PasslineSession) => {\n writeStoredSession({\n sessionId: next.sessionId,\n assistantName: next.assistantName,\n companyName: next.companyName,\n logoUrl: next.logoUrl,\n messages: next.messages,\n updatedAt: Date.now(),\n });\n },\n [writeStoredSession],\n );\n\n const goOffline = useCallback(() => {\n setOffline(true);\n setSession(null);\n sessionRef.current = null;\n setError(\"Offline\");\n setIsLoading(false);\n }, []);\n\n const restoreCachedSession = useCallback((): PasslineSession | null => {\n const stored = readStoredSession();\n if (!stored) return null;\n\n const restored: PasslineSession = {\n sessionId: stored.sessionId,\n assistantName: stored.assistantName || \"Assistant\",\n companyName: stored.companyName ?? null,\n logoUrl: stored.logoUrl ?? null,\n messages: stored.messages,\n };\n setSession(restored);\n sessionRef.current = restored;\n setOffline(false);\n setError(null);\n setIsLoading(false);\n return restored;\n }, [readStoredSession]);\n\n const createSession = useCallback(\n async (forceNew: boolean = false): Promise<PasslineSession> => {\n const bootId = ++bootIdRef.current;\n setError(null);\n\n if (!forceNew) {\n const restored = restoreCachedSession();\n if (restored) return restored;\n }\n\n try {\n setIsLoading(true);\n const response = await apiRef.current.createSession(\n locale ? { locale } : undefined,\n );\n if (bootIdRef.current !== bootId) {\n const current = sessionRef.current;\n if (current) return current;\n }\n\n const greeting = response.greeting || defaultGreeting;\n const next = toPasslineSession(response, [\n {\n id: \"greeting\",\n role: \"assistant\",\n content: greeting,\n timestamp: Date.now(),\n },\n ]);\n\n setSession(next);\n sessionRef.current = next;\n setOffline(false);\n persistFromSession(next);\n return next;\n } catch (err) {\n if (bootIdRef.current !== bootId) {\n const current = sessionRef.current;\n if (current) return current;\n }\n if (isOfflineError(err)) {\n goOffline();\n throw new Error(\"Offline\");\n }\n const errorMessage =\n err instanceof Error ? err.message : \"Unable to start chat\";\n setError(errorMessage);\n setSession(null);\n sessionRef.current = null;\n throw new Error(errorMessage);\n } finally {\n if (bootIdRef.current === bootId) {\n setIsLoading(false);\n }\n }\n },\n [defaultGreeting, goOffline, locale, persistFromSession, restoreCachedSession],\n );\n\n const ensureSession = useCallback(async (): Promise<PasslineSession | null> => {\n if (offline) return null;\n if (sessionRef.current?.sessionId) return sessionRef.current;\n try {\n return await createSession(false);\n } catch {\n return null;\n }\n }, [createSession, offline]);\n\n const sendMessage = useCallback(\n async (content: string): Promise<PasslineMessage> => {\n const active = sessionRef.current;\n if (!active?.sessionId) {\n throw new Error(\"No active session\");\n }\n\n const userMessage: PasslineMessage = {\n id: nextMessageId(\"u\"),\n role: \"user\",\n content,\n timestamp: Date.now(),\n };\n\n const withUser: PasslineSession = {\n ...active,\n messages: [...active.messages, userMessage],\n };\n setSession(withUser);\n sessionRef.current = withUser;\n persistFromSession(withUser);\n\n try {\n const response = await apiRef.current.sendMessage(\n active.sessionId,\n content,\n );\n\n const assistantMessage: PasslineMessage = {\n id: nextMessageId(\"a\"),\n role: \"assistant\",\n content: response.reply || \"\",\n timestamp: Date.now(),\n };\n\n const latest = sessionRef.current ?? withUser;\n const withAssistant: PasslineSession = {\n ...latest,\n messages: [...latest.messages, assistantMessage],\n };\n setSession(withAssistant);\n sessionRef.current = withAssistant;\n persistFromSession(withAssistant);\n return assistantMessage;\n } catch (err) {\n if (isOfflineError(err)) {\n goOffline();\n throw new Error(\"Offline\");\n }\n if (isSessionInvalidError(err)) {\n clearStoredSession();\n setSession(null);\n sessionRef.current = null;\n await createSession(true);\n throw new Error(\"Session expired. Please try your message again.\");\n }\n throw err;\n }\n },\n [clearStoredSession, createSession, goOffline, persistFromSession],\n );\n\n const newChat = useCallback(async () => {\n clearStoredSession();\n setSession(null);\n sessionRef.current = null;\n setOffline(false);\n return createSession(true);\n }, [clearStoredSession, createSession]);\n\n useEffect(() => {\n bootIdRef.current += 1;\n setSession(null);\n sessionRef.current = null;\n setOffline(false);\n setError(null);\n setIsLoading(false);\n restoreCachedSession();\n }, [restoreCachedSession, token, locale]);\n\n return {\n session,\n messages: session?.messages || [],\n isLoading,\n offline,\n error,\n sendMessage,\n newChat,\n createSession,\n ensureSession,\n clearStoredSession,\n };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { usePasslineSession } from \"./usePasslineSession\";\nimport { PasslineMessage, PasslineSession } from \"../component/types\";\n\nexport function usePasslineChat(\n token: string,\n options?: {\n locale?: string;\n defaultGreeting?: string;\n persistSession?: boolean;\n maxSessionAge?: number;\n maxStoredMessages?: number;\n autoOpen?: boolean;\n onMessageSend?: (message: string) => void;\n onMessageReceive?: (message: PasslineMessage) => void;\n onError?: (error: Error) => void;\n onSessionStart?: (session: PasslineSession) => void;\n },\n) {\n const [isOpen, setIsOpen] = useState(Boolean(options?.autoOpen));\n const [isSending, setIsSending] = useState(false);\n const lastSessionIdRef = useRef<string | null>(null);\n\n const {\n session,\n messages,\n isLoading,\n offline,\n error,\n sendMessage: sendMessageApi,\n newChat: newChatApi,\n ensureSession,\n } = usePasslineSession(\n token,\n options?.persistSession,\n options?.maxSessionAge,\n options?.maxStoredMessages,\n options?.locale,\n options?.defaultGreeting,\n );\n\n useEffect(() => {\n if (options?.autoOpen) {\n setIsOpen(true);\n }\n }, [options?.autoOpen]);\n\n useEffect(() => {\n lastSessionIdRef.current = null;\n }, [token, options?.locale]);\n\n useEffect(() => {\n if (!isOpen || offline) {\n return;\n }\n void ensureSession();\n }, [ensureSession, isOpen, offline, token, options?.locale]);\n\n useEffect(() => {\n if (!session?.sessionId || session.sessionId === lastSessionIdRef.current) {\n return;\n }\n lastSessionIdRef.current = session.sessionId;\n options?.onSessionStart?.(session);\n }, [options, session]);\n\n useEffect(() => {\n if (error && options?.onError) {\n options.onError(new Error(error));\n }\n }, [error, options]);\n\n const sendMessage = useCallback(\n async (content: string) => {\n if (!content.trim() || isSending || isLoading || offline) return;\n\n try {\n setIsSending(true);\n options?.onMessageSend?.(content);\n const response = await sendMessageApi(content);\n options?.onMessageReceive?.(response);\n } catch (err) {\n options?.onError?.(\n err instanceof Error ? err : new Error(\"Failed to send message\"),\n );\n } finally {\n setIsSending(false);\n }\n },\n [isLoading, isSending, offline, options, sendMessageApi],\n );\n\n const newChat = useCallback(async () => {\n try {\n await newChatApi();\n } catch (err) {\n options?.onError?.(\n err instanceof Error ? err : new Error(\"Failed to start new chat\"),\n );\n }\n }, [newChatApi, options]);\n\n const toggleOpen = useCallback(() => {\n setIsOpen((prev) => !prev);\n }, []);\n\n return {\n isOpen,\n setIsOpen,\n toggleOpen,\n isSending,\n isLoading,\n offline,\n messages,\n session,\n error,\n sendMessage,\n newChat,\n };\n}\n","export interface PasslineChatLabels {\n assistant: string;\n offline: string;\n unavailable: string;\n connecting: string;\n online: string;\n typeMessage: string;\n send: string;\n openChat: string;\n closeChat: string;\n newChat: string;\n newChatTitle: string;\n typing: string;\n panelAriaLabel: string;\n organizationLogo: string;\n defaultGreeting: string;\n}\n\nexport const DEFAULT_PASSLINE_CHAT_LABELS: PasslineChatLabels = {\n assistant: \"Assistant\",\n offline: \"Offline\",\n unavailable: \"Unavailable\",\n connecting: \"Connecting…\",\n online: \"Online\",\n typeMessage: \"Type a message…\",\n send: \"Send\",\n openChat: \"Open chat\",\n closeChat: \"Close chat\",\n newChat: \"Start new chat\",\n newChatTitle: \"Start new chat\",\n typing: \"Assistant is typing…\",\n panelAriaLabel: \"Passline chat\",\n organizationLogo: \"Organization logo\",\n defaultGreeting: \"Hi! How can I help?\",\n};\n\nexport function mergePasslineChatLabels(\n labels?: Partial<PasslineChatLabels>,\n): PasslineChatLabels {\n return { ...DEFAULT_PASSLINE_CHAT_LABELS, ...labels };\n}\n","/// <reference path=\"../css.d.ts\" />\nimport React, {\n useState,\n useRef,\n useEffect,\n useCallback,\n useMemo,\n} from \"react\";\nimport { usePasslineChat } from \"../hooks/usePasslineClient\";\nimport { PasslineChatProps, PasslineMessage, PasslineTheme } from \"./types\";\nimport { mergePasslineChatLabels } from \"./labels\";\nimport \"./PasslineChat.css\";\n\nconst CHAT_ICON =\n \"M4 4h16a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H9l-5 4v-4H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z\";\n\nconst defaultTheme: Required<PasslineTheme> = {\n primaryColor: \"#0f766e\",\n primaryHoverColor: \"#0d9488\",\n backgroundColor: \"#f8fafc\",\n textColor: \"#0f172a\",\n userBubbleColor: \"#0f766e\",\n userBubbleTextColor: \"#ffffff\",\n assistantBubbleColor: \"#ffffff\",\n assistantBubbleTextColor: \"#1e293b\",\n borderRadius: \"16px\",\n borderWidth: \"1px\",\n borderColor: \"#e2e8f0\",\n buttonSize: \"medium\",\n buttonBorderRadius: \"999px\",\n buttonBackgroundColor: \"#0f766e\",\n buttonHoverColor: \"#0d9488\",\n buttonIconColor: \"#ffffff\",\n panelWidth: \"380px\",\n panelHeight: \"560px\",\n panelShadow: \"0 12px 40px rgba(0,0,0,.22)\",\n fontFamily:\n \"system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif\",\n fontSize: \"14px\",\n spacing: \"12px\",\n padding: \"12px\",\n className: \"\",\n buttonClassName: \"\",\n panelClassName: \"\",\n headerClassName: \"\",\n messagesClassName: \"\",\n inputClassName: \"\",\n};\n\nfunction brandingInitials(label?: string | null): string {\n const parts = String(label || \"\")\n .trim()\n .split(/\\s+/)\n .filter(Boolean);\n if (parts.length === 0) return \"A\";\n if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();\n return (parts[0].charAt(0) + parts[1].charAt(0)).toUpperCase();\n}\n\nfunction BrandMark({\n logoUrl,\n label,\n logoAlt = \"Organization logo\",\n fallback = \"initials\",\n className,\n backgroundColor,\n}: {\n logoUrl?: string | null;\n label: string;\n logoAlt?: string;\n fallback?: \"initials\" | \"icon\";\n className: string;\n backgroundColor?: string;\n}) {\n const [broken, setBroken] = useState(false);\n\n useEffect(() => {\n setBroken(false);\n }, [logoUrl]);\n\n const showLogo = Boolean(logoUrl) && !broken;\n\n return (\n <div\n className={`${className}${showLogo ? \" passline-has-logo\" : \"\"}`}\n style={showLogo ? undefined : { backgroundColor }}\n >\n {showLogo ? (\n <img\n src={logoUrl ?? \"\"}\n alt={label || logoAlt}\n className=\"passline-chat-avatar-img\"\n onError={() => setBroken(true)}\n />\n ) : fallback === \"icon\" ? (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d={CHAT_ICON} />\n </svg>\n ) : (\n brandingInitials(label)\n )}\n </div>\n );\n}\n\nexport const PasslineChat: React.FC<PasslineChatProps> = ({\n token,\n locale,\n labels: labelsProp,\n theme = {},\n className = \"\",\n buttonClassName = \"\",\n panelClassName = \"\",\n renderButton,\n renderHeader,\n renderMessage,\n renderInput,\n autoOpen = false,\n persistSession = true,\n maxSessionAge,\n maxStoredMessages,\n onOpen,\n onClose,\n onMessageSend,\n onMessageReceive,\n onError,\n onSessionStart,\n onNewChat,\n}) => {\n const mergedTheme = { ...defaultTheme, ...theme };\n const labels = useMemo(\n () => mergePasslineChatLabels(labelsProp),\n [labelsProp],\n );\n const [inputValue, setInputValue] = useState(\"\");\n const messagesEndRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const wasOpenRef = useRef(false);\n\n const {\n isOpen,\n setIsOpen,\n toggleOpen,\n isSending,\n isLoading,\n offline,\n messages,\n session,\n error,\n sendMessage,\n newChat,\n } = usePasslineChat(token, {\n locale,\n defaultGreeting: labels.defaultGreeting,\n persistSession,\n maxSessionAge,\n maxStoredMessages,\n autoOpen,\n onMessageSend,\n onMessageReceive,\n onError,\n onSessionStart,\n });\n\n const brandLabel =\n session?.companyName || session?.assistantName || labels.assistant;\n const inputLocked = isLoading || offline || !session?.sessionId;\n const subtitle = offline\n ? labels.offline\n : error && !session\n ? labels.unavailable\n : session?.companyName || (isLoading ? labels.connecting : labels.online);\n const placeholder = offline\n ? labels.offline\n : isLoading\n ? labels.connecting\n : session?.sessionId\n ? labels.typeMessage\n : labels.unavailable;\n\n useEffect(() => {\n if (autoOpen) {\n setIsOpen(true);\n }\n }, [autoOpen, setIsOpen]);\n\n useEffect(() => {\n if (isOpen === wasOpenRef.current) {\n return;\n }\n wasOpenRef.current = isOpen;\n if (isOpen) {\n onOpen?.();\n } else {\n onClose?.();\n }\n }, [isOpen, onOpen, onClose]);\n\n useEffect(() => {\n if (!isOpen || offline) {\n return;\n }\n const focusTimer = window.setTimeout(() => {\n inputRef.current?.focus();\n }, 100);\n return () => window.clearTimeout(focusTimer);\n }, [isOpen, offline]);\n\n useEffect(() => {\n messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n }, [messages, isSending, isLoading]);\n\n const handleSend = useCallback(() => {\n if (inputValue.trim() && !isSending && !inputLocked) {\n sendMessage(inputValue);\n setInputValue(\"\");\n }\n }, [inputLocked, inputValue, isSending, sendMessage]);\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n handleSend();\n }\n },\n [handleSend],\n );\n\n const handleNewChat = useCallback(() => {\n void newChat();\n onNewChat?.();\n }, [newChat, onNewChat]);\n\n const buttonSizePx =\n mergedTheme.buttonSize === \"small\"\n ? \"44px\"\n : mergedTheme.buttonSize === \"large\"\n ? \"64px\"\n : \"56px\";\n\n const headerTitle = session?.assistantName || labels.assistant;\n\n return (\n <div\n className={`passline-chat-wrapper ${mergedTheme.className} ${className}`}\n >\n {isOpen ? (\n <div\n className={`passline-chat-panel ${mergedTheme.panelClassName} ${panelClassName}`}\n role=\"dialog\"\n aria-label={labels.panelAriaLabel}\n style={{\n width: mergedTheme.panelWidth,\n height: mergedTheme.panelHeight,\n boxShadow: mergedTheme.panelShadow,\n backgroundColor: mergedTheme.backgroundColor,\n borderRadius: mergedTheme.borderRadius,\n fontFamily: mergedTheme.fontFamily,\n fontSize: mergedTheme.fontSize,\n color: mergedTheme.textColor,\n }}\n >\n {renderHeader ? (\n renderHeader({\n title: headerTitle,\n subtitle,\n avatar: brandingInitials(brandLabel),\n onNewChat: handleNewChat,\n })\n ) : (\n <div\n className={`passline-chat-header ${mergedTheme.headerClassName}`}\n >\n <BrandMark\n logoUrl={session?.logoUrl}\n label={brandLabel}\n logoAlt={labels.organizationLogo}\n className=\"passline-header-avatar\"\n backgroundColor={mergedTheme.primaryColor}\n />\n <div className=\"passline-header-info\">\n <div className=\"passline-header-title\">{headerTitle}</div>\n <div className=\"passline-header-subtitle\">{subtitle}</div>\n </div>\n <button\n className=\"passline-header-newchat\"\n onClick={handleNewChat}\n title={labels.newChatTitle}\n aria-label={labels.newChat}\n style={{ color: mergedTheme.textColor }}\n type=\"button\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\">\n <path d=\"M17.65 6.35A7.95 7.95 0 0 0 12 4a8 8 0 1 0 7.75 10h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4z\" />\n </svg>\n </button>\n </div>\n )}\n\n <div\n className={`passline-chat-messages ${mergedTheme.messagesClassName}`}\n >\n {isLoading && messages.length === 0 ? (\n <p className=\"passline-chat-meta\">{labels.connecting}</p>\n ) : null}\n\n {messages.map((message: PasslineMessage, index: number) => (\n <React.Fragment key={message.id || index}>\n {renderMessage ? (\n renderMessage({ message, className: \"\" })\n ) : (\n <div\n className={`passline-message ${message.role === \"user\" ? \"message-user\" : \"message-assistant\"}`}\n style={{\n backgroundColor:\n message.role === \"user\"\n ? mergedTheme.userBubbleColor\n : mergedTheme.assistantBubbleColor,\n color:\n message.role === \"user\"\n ? mergedTheme.userBubbleTextColor\n : mergedTheme.assistantBubbleTextColor,\n borderRadius: mergedTheme.borderRadius,\n border:\n message.role === \"assistant\"\n ? `${mergedTheme.borderWidth} solid ${mergedTheme.borderColor}`\n : \"none\",\n }}\n >\n {message.content}\n </div>\n )}\n </React.Fragment>\n ))}\n\n {isSending ? (\n <p className=\"passline-chat-typing\">{labels.typing}</p>\n ) : null}\n\n {error ? <p className=\"passline-chat-error\">{error}</p> : null}\n <div ref={messagesEndRef} />\n </div>\n\n {renderInput ? (\n renderInput({\n value: inputValue,\n onChange: setInputValue,\n onSend: handleSend,\n disabled: inputLocked || isSending,\n placeholder,\n className: \"\",\n })\n ) : (\n <div\n className={`passline-chat-input-container ${mergedTheme.inputClassName}`}\n >\n <input\n ref={inputRef}\n type=\"text\"\n className=\"passline-chat-input\"\n value={inputValue}\n onChange={(event) => setInputValue(event.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n disabled={inputLocked}\n autoComplete=\"off\"\n style={{\n borderColor: mergedTheme.borderColor,\n borderRadius: mergedTheme.borderRadius,\n backgroundColor: mergedTheme.backgroundColor,\n color: mergedTheme.textColor,\n fontFamily: mergedTheme.fontFamily,\n fontSize: mergedTheme.fontSize,\n }}\n />\n <button\n className=\"passline-chat-send\"\n onClick={handleSend}\n disabled={inputLocked || isSending || !inputValue.trim()}\n style={{\n backgroundColor: mergedTheme.primaryColor,\n borderRadius: mergedTheme.borderRadius,\n color: \"#ffffff\",\n }}\n type=\"button\"\n >\n {labels.send}\n </button>\n </div>\n )}\n </div>\n ) : null}\n\n {renderButton ? (\n renderButton({ onClick: toggleOpen, isOpen, className: \"\" })\n ) : (\n <button\n className={`passline-chat-button ${mergedTheme.buttonClassName} ${buttonClassName}`}\n onClick={toggleOpen}\n style={{\n backgroundColor: session?.logoUrl\n ? undefined\n : isOpen\n ? mergedTheme.primaryHoverColor\n : mergedTheme.buttonBackgroundColor,\n color: mergedTheme.buttonIconColor,\n borderRadius: mergedTheme.buttonBorderRadius,\n width: buttonSizePx,\n height: buttonSizePx,\n }}\n aria-label={isOpen ? labels.closeChat : labels.openChat}\n type=\"button\"\n >\n <BrandMark\n logoUrl={session?.logoUrl}\n label={brandLabel}\n logoAlt={labels.organizationLogo}\n fallback=\"icon\"\n className=\"passline-chat-button-mark\"\n />\n </button>\n )}\n </div>\n );\n};\n"],"names":["ECDH","name","namedCurve","AES","length","bytesOf","data","Uint8Array","copy","byteLength","set","utf8","value","TextEncoder","encode","b64encode","bytes","binary","i","String","fromCharCode","btoa","replace","b64decode","padded","pad","repeat","atob","charCodeAt","async","exportPublicKey","key","crypto","subtle","exportKey","sharedAesKey","privateKey","publicKey","bits","deriveBits","public","ikm","importKey","deriveKey","hash","salt","info","deriveAesKey","PasslineApiError","Error","constructor","message","options","super","this","code","status","isOfflineError","err","test","PasslineApi","token","channel","handshake","gatewayUrl","postJson","body","response","fetch","method","headers","Accept","JSON","stringify","cache","payload","json","catch","throwIfFailed","ensureChannel","openChannel","finally","pair","generateKey","generateEcdhPair","v","op","c","s","t","serverPub","spki","importPublicKey","ticket","request","route","run","encrypted","iv","getRandomValues","ct","encrypt","encryptJson","r","d","k","envelope","decoded","pt","decrypt","parse","TextDecoder","decode","decryptJson","error","createSession","locale","trim","sendMessage","sessionId","id","nextMessageId","prefix","Date","now","Math","random","toString","slice","usePasslineSession","persistSession","maxSessionAge","maxStoredMessages","defaultGreeting","session","setSession","useState","isLoading","setIsLoading","offline","setOffline","setError","apiRef","useRef","sessionRef","bootIdRef","storageKey","current","useEffect","readStoredSession","useCallback","window","raw","localStorage","getItem","parsed","Array","isArray","messages","updatedAt","removeItem","writeStoredSession","setItem","clearStoredSession","persistFromSession","next","assistantName","companyName","logoUrl","goOffline","restoreCachedSession","stored","restored","forceNew","bootId","undefined","greeting","toPasslineSession","role","content","timestamp","errorMessage","ensureSession","active","userMessage","withUser","assistantMessage","reply","latest","withAssistant","toLowerCase","isSessionInvalidError","newChat","usePasslineChat","isOpen","setIsOpen","Boolean","autoOpen","isSending","setIsSending","lastSessionIdRef","sendMessageApi","newChatApi","onSessionStart","onError","onMessageSend","onMessageReceive","toggleOpen","prev","DEFAULT_PASSLINE_CHAT_LABELS","assistant","unavailable","connecting","online","typeMessage","send","openChat","closeChat","newChatTitle","typing","panelAriaLabel","organizationLogo","mergePasslineChatLabels","labels","defaultTheme","primaryColor","primaryHoverColor","backgroundColor","textColor","userBubbleColor","userBubbleTextColor","assistantBubbleColor","assistantBubbleTextColor","borderRadius","borderWidth","borderColor","buttonSize","buttonBorderRadius","buttonBackgroundColor","buttonHoverColor","buttonIconColor","panelWidth","panelHeight","panelShadow","fontFamily","fontSize","spacing","padding","className","buttonClassName","panelClassName","headerClassName","messagesClassName","inputClassName","brandingInitials","label","parts","split","filter","toUpperCase","charAt","BrandMark","logoAlt","fallback","broken","setBroken","showLogo","_jsx","style","src","alt","viewBox","fill","PasslineChat","labelsProp","theme","renderButton","renderHeader","renderMessage","renderInput","onOpen","onClose","onNewChat","mergedTheme","useMemo","inputValue","setInputValue","messagesEndRef","inputRef","wasOpenRef","brandLabel","inputLocked","subtitle","placeholder","focusTimer","setTimeout","focus","clearTimeout","scrollIntoView","behavior","handleSend","handleKeyDown","event","shiftKey","preventDefault","handleNewChat","buttonSizePx","headerTitle","_jsxs","children","width","height","boxShadow","color","title","avatar","onClick","type","map","index","React","Fragment","border","ref","onChange","onSend","disabled","target","onKeyDown","autoComplete"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var e=require("react/jsx-runtime"),s=require("react");class a extends Error{constructor(e,s){super(e),this.name="PasslineApiError",this.code=s?.code,this.status=s?.status}}function n(e){if(!e)return!1;if(e instanceof a&&"OFFLINE"===e.code)return!0;const s=e instanceof Error?e.message:"";return/offline|failed to fetch|networkerror|load failed|request failed \(5\d\d\)/i.test(s)}function t(e){const s=e instanceof Error?e.message.toLowerCase():"";return/session/.test(s)&&/(not found|expired|invalid)/.test(s)}class r{constructor(e,s){this.token=e,this.origin=(s||"https://api.passline.ai").replace(/\/$/,"")}getHeaders(){return{Accept:"application/json","Content-Type":"application/json","X-Passline-Embed-Token":this.token}}async request(e,s={}){const n=`${this.origin}/api/chatbot/${e.replace(/^\//,"")}`;let t;try{t=await fetch(n,{method:"POST",headers:this.getHeaders(),body:JSON.stringify(s),cache:"no-store"})}catch{throw new a("Offline",{code:"OFFLINE"})}const r=await t.json().catch(()=>null);if(!t.ok){if(t.status>=500||0===t.status)throw new a("Offline",{code:"OFFLINE",status:t.status});throw new a(r?.message||`Request failed (${t.status})`,{status:t.status})}return r}async createSession(){return this.request("session",{})}async sendMessage(e,s){return this.request(`session/${encodeURIComponent(e)}/messages`,{message:s})}}function o(e){return`${e}-${Date.now()}-${Math.random().toString(36).slice(2,8)}`}function l(e,a,l=!0,i=6048e5,c=200){const[u,d]=s.useState(null),[f,m]=s.useState(!1),[g,p]=s.useState(!1),[h,b]=s.useState(null),C=s.useRef(new r(e,a)),w=s.useRef(null),x=s.useRef(0),y=`passline:chat:${e}`;w.current=u,s.useEffect(()=>{C.current=new r(e,a)},[e,a]);const N=s.useCallback(()=>{if(!l||"undefined"==typeof window)return null;try{const e=window.localStorage.getItem(y);if(!e)return null;const s=JSON.parse(e);return s?.sessionId&&Array.isArray(s.messages)?Date.now()-(s.updatedAt||0)>i?(window.localStorage.removeItem(y),null):s:null}catch{return null}},[i,l,y]),S=s.useCallback(e=>{if(l&&"undefined"!=typeof window)try{const s=e.messages.length>c?e.messages.slice(e.messages.length-c):e.messages;window.localStorage.setItem(y,JSON.stringify({...e,messages:s}))}catch{}},[c,l,y]),k=s.useCallback(()=>{if(l&&"undefined"!=typeof window)try{window.localStorage.removeItem(y)}catch{}},[l,y]),v=s.useCallback(e=>{S({sessionId:e.sessionId,assistantName:e.assistantName,companyName:e.companyName,logoUrl:e.logoUrl,messages:e.messages,updatedAt:Date.now()})},[S]),E=s.useCallback(()=>{p(!0),d(null),w.current=null,b("Offline"),m(!1)},[]),I=s.useCallback(()=>{const e=N();if(!e)return null;const s={sessionId:e.sessionId,assistantName:e.assistantName||"Assistant",companyName:e.companyName??null,logoUrl:e.logoUrl??null,messages:e.messages};return d(s),w.current=s,p(!1),b(null),m(!1),s},[N]),O=s.useCallback(async(e=!1)=>{const s=++x.current;if(b(null),!e){const e=I();if(e)return e}try{m(!0);const e=await C.current.createSession();if(x.current!==s){const e=w.current;if(e)return e}const a=function(e,s=[]){return{sessionId:e.sessionId,assistantName:e.assistantName||"Assistant",companyName:e.companyName??null,logoUrl:e.logoUrl??null,greeting:e.greeting,messages:s}}(e,[{id:"greeting",role:"assistant",content:e.greeting||"Hi! How can I help?",timestamp:Date.now()}]);return d(a),w.current=a,p(!1),v(a),a}catch(e){if(x.current!==s){const e=w.current;if(e)return e}if(n(e))throw E(),new Error("Offline");const a=e instanceof Error?e.message:"Unable to start chat";throw b(a),d(null),w.current=null,new Error(a)}finally{x.current===s&&m(!1)}},[E,v,I]),j=s.useCallback(async()=>{if(g)return null;if(w.current?.sessionId)return w.current;try{return await O(!1)}catch{return null}},[O,g]),R=s.useCallback(async e=>{const s=w.current;if(!s?.sessionId)throw new Error("No active session");const a={id:o("u"),role:"user",content:e,timestamp:Date.now()},r={...s,messages:[...s.messages,a]};d(r),w.current=r,v(r);try{const a=await C.current.sendMessage(s.sessionId,e),n={id:o("a"),role:"assistant",content:a.reply||"",timestamp:Date.now()},t=w.current??r,l={...t,messages:[...t.messages,n]};return d(l),w.current=l,v(l),n}catch(e){if(n(e))throw E(),new Error("Offline");if(t(e))throw k(),d(null),w.current=null,await O(!0),new Error("Session expired. Please try your message again.");throw e}},[k,O,E,v]),A=s.useCallback(async()=>(k(),d(null),w.current=null,p(!1),O(!0)),[k,O]);return s.useEffect(()=>{x.current+=1,d(null),w.current=null,p(!1),b(null),m(!1),I()},[I,e]),{session:u,messages:u?.messages||[],isLoading:f,offline:g,error:h,sendMessage:R,newChat:A,createSession:O,ensureSession:j,clearStoredSession:k}}function i(e,a){const[n,t]=s.useState(Boolean(a?.autoOpen)),[r,o]=s.useState(!1),i=s.useRef(null),{session:c,messages:u,isLoading:d,offline:f,error:m,sendMessage:g,newChat:p,ensureSession:h}=l(e,a?.origin,a?.persistSession,a?.maxSessionAge,a?.maxStoredMessages);s.useEffect(()=>{a?.autoOpen&&t(!0)},[a?.autoOpen]),s.useEffect(()=>{i.current=null},[e]),s.useEffect(()=>{n&&!f&&h()},[h,n,f,e]),s.useEffect(()=>{c?.sessionId&&c.sessionId!==i.current&&(i.current=c.sessionId,a?.onSessionStart?.(c))},[a,c]),s.useEffect(()=>{m&&a?.onError&&a.onError(new Error(m))},[m,a]);const b=s.useCallback(async e=>{if(e.trim()&&!r&&!d&&!f)try{o(!0),a?.onMessageSend?.(e);const s=await g(e);a?.onMessageReceive?.(s)}catch(e){a?.onError?.(e instanceof Error?e:new Error("Failed to send message"))}finally{o(!1)}},[d,r,f,a,g]),C=s.useCallback(async()=>{try{await p()}catch(e){a?.onError?.(e instanceof Error?e:new Error("Failed to start new chat"))}},[p,a]),w=s.useCallback(()=>{t(e=>!e)},[]);return{isOpen:n,setIsOpen:t,toggleOpen:w,isSending:r,isLoading:d,offline:f,messages:u,session:c,error:m,sendMessage:b,newChat:C}}const c={primaryColor:"#0f766e",primaryHoverColor:"#0d9488",backgroundColor:"#f8fafc",textColor:"#0f172a",userBubbleColor:"#0f766e",userBubbleTextColor:"#ffffff",assistantBubbleColor:"#ffffff",assistantBubbleTextColor:"#1e293b",borderRadius:"16px",borderWidth:"1px",borderColor:"#e2e8f0",buttonSize:"medium",buttonBorderRadius:"999px",buttonBackgroundColor:"#0f766e",buttonHoverColor:"#0d9488",buttonIconColor:"#ffffff",panelWidth:"380px",panelHeight:"560px",panelShadow:"0 12px 40px rgba(0,0,0,.22)",fontFamily:"system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",fontSize:"14px",spacing:"12px",padding:"12px",className:"",buttonClassName:"",panelClassName:"",headerClassName:"",messagesClassName:"",inputClassName:""};function u(e){const s=String(e||"").trim().split(/\s+/).filter(Boolean);return 0===s.length?"A":1===s.length?s[0].slice(0,2).toUpperCase():(s[0].charAt(0)+s[1].charAt(0)).toUpperCase()}function d({logoUrl:a,label:n,fallback:t="initials",className:r,backgroundColor:o}){const[l,i]=s.useState(!1);s.useEffect(()=>{i(!1)},[a]);const c=Boolean(a)&&!l;return e.jsx("div",{className:`${r}${c?" passline-has-logo":""}`,style:c?void 0:{backgroundColor:o},children:c?e.jsx("img",{src:a??"",alt:n||"Organization logo",className:"passline-chat-avatar-img",onError:()=>i(!0)}):"icon"===t?e.jsx("svg",{viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",children:e.jsx("path",{d:"M4 4h16a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H9l-5 4v-4H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z"})}):u(n)})}exports.PasslineApi=r,exports.PasslineApiError=a,exports.PasslineChat=({token:a,origin:n,theme:t={},className:r="",buttonClassName:o="",panelClassName:l="",renderButton:f,renderHeader:m,renderMessage:g,renderInput:p,autoOpen:h=!1,persistSession:b=!0,maxSessionAge:C,maxStoredMessages:w,onOpen:x,onClose:y,onMessageSend:N,onMessageReceive:S,onError:k,onSessionStart:v,onNewChat:E})=>{const I={...c,...t},[O,j]=s.useState(""),R=s.useRef(null),A=s.useRef(null),{isOpen:$,setIsOpen:M,toggleOpen:B,isSending:U,isLoading:F,offline:H,messages:z,session:P,error:L,sendMessage:T,newChat:D}=i(a,{origin:n,persistSession:b,maxSessionAge:C,maxStoredMessages:w,autoOpen:h,onMessageSend:N,onMessageReceive:S,onError:k,onSessionStart:v}),q=P?.companyName||P?.assistantName||"Assistant",W=F||H||!P?.sessionId,J=H?"Offline":L&&!P?"Unavailable":P?.companyName||(F?"Connecting…":"Online"),V=H?"Offline":F?"Connecting…":P?.sessionId?"Type a message…":"Unavailable";s.useEffect(()=>{h&&M(!0)},[h,M]),s.useEffect(()=>{$?(x?.(),H||setTimeout(()=>{A.current?.focus()},100)):y?.()},[$,H,x,y]),s.useEffect(()=>{R.current?.scrollIntoView({behavior:"smooth"})},[z,U,F]);const K=s.useCallback(()=>{!O.trim()||U||W||(T(O),j(""))},[W,O,U,T]),X=s.useCallback(e=>{"Enter"!==e.key||e.shiftKey||(e.preventDefault(),K())},[K]),G=s.useCallback(()=>{D(),E?.()},[D,E]),Q="small"===I.buttonSize?"44px":"large"===I.buttonSize?"64px":"56px",Y=e.jsx("button",{className:`passline-chat-button ${I.buttonClassName} ${o}`,onClick:B,style:{backgroundColor:P?.logoUrl?void 0:$?I.primaryHoverColor:I.buttonBackgroundColor,color:I.buttonIconColor,borderRadius:I.buttonBorderRadius,width:Q,height:Q},"aria-label":$?"Close chat":"Open chat",children:e.jsx(d,{logoUrl:P?.logoUrl,label:q,fallback:"icon",className:"passline-chat-button-mark"})}),Z=e.jsxs("div",{className:`passline-chat-header ${I.headerClassName}`,children:[e.jsx(d,{logoUrl:P?.logoUrl,label:q,className:"passline-header-avatar",backgroundColor:I.primaryColor}),e.jsxs("div",{className:"passline-header-info",children:[e.jsx("div",{className:"passline-header-title",children:P?.assistantName||"Assistant"}),e.jsx("div",{className:"passline-header-subtitle",children:J})]}),e.jsx("button",{className:"passline-header-newchat",onClick:G,title:"Start new chat","aria-label":"Start new chat",style:{color:I.textColor},children:e.jsx("svg",{viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",children:e.jsx("path",{d:"M17.65 6.35A7.95 7.95 0 0 0 12 4a8 8 0 1 0 7.75 10h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4z"})})})]}),_=e.jsxs("div",{className:`passline-chat-input-container ${I.inputClassName}`,children:[e.jsx("input",{ref:A,type:"text",className:"passline-chat-input",value:O,onChange:e=>j(e.target.value),onKeyDown:X,placeholder:V,disabled:W,autoComplete:"off",style:{borderColor:I.borderColor,borderRadius:I.borderRadius,backgroundColor:I.backgroundColor,color:I.textColor,fontFamily:I.fontFamily,fontSize:I.fontSize}}),e.jsx("button",{className:"passline-chat-send",onClick:K,disabled:W||U||!O.trim(),style:{backgroundColor:I.primaryColor,borderRadius:I.borderRadius,color:"#ffffff"},children:"Send"})]}),ee=e.jsxs("div",{className:`passline-chat-panel ${I.panelClassName} ${l}`,role:"dialog","aria-label":"Passline chat",style:{width:I.panelWidth,height:I.panelHeight,boxShadow:I.panelShadow,backgroundColor:I.backgroundColor,borderRadius:I.borderRadius,fontFamily:I.fontFamily,fontSize:I.fontSize,color:I.textColor},children:[m?m({title:P?.assistantName||"Assistant",subtitle:J,avatar:u(q),onNewChat:G}):Z,e.jsxs("div",{className:`passline-chat-messages ${I.messagesClassName}`,children:[F&&0===z.length?e.jsx("p",{className:"passline-chat-meta",children:"Connecting…"}):null,z.map((s,a)=>g?g({message:s,className:""}):((s,a)=>e.jsx("div",{className:"passline-message "+("user"===s.role?"message-user":"message-assistant"),style:{backgroundColor:"user"===s.role?I.userBubbleColor:I.assistantBubbleColor,color:"user"===s.role?I.userBubbleTextColor:I.assistantBubbleTextColor,borderRadius:I.borderRadius,border:"assistant"===s.role?`${I.borderWidth} solid ${I.borderColor}`:"none"},children:s.content},s.id||a))(s,a)),U?e.jsx("p",{className:"passline-chat-typing",children:"Assistant is typing…"}):null,L?e.jsx("p",{className:"passline-chat-error",children:L}):null,e.jsx("div",{ref:R})]}),p?p({value:O,onChange:j,onSend:K,disabled:W||U,placeholder:V,className:""}):_]});return e.jsxs("div",{className:`passline-chat-wrapper ${I.className} ${r}`,children:[$&&ee,f?f({onClick:B,isOpen:$,className:""}):Y]})},exports.isOfflineError=n,exports.isSessionInvalidError=t,exports.usePasslineChat=i,exports.usePasslineSession=l;
1
+ "use strict";var e=require("react/jsx-runtime"),s=require("react");const t={name:"ECDH",namedCurve:"P-256"},n={name:"AES-GCM",length:256};function a(e){if(e instanceof Uint8Array){const s=new Uint8Array(e.byteLength);return s.set(e),s}return new Uint8Array(e)}function r(e){return(new TextEncoder).encode(e)}function o(e){const s=a(e);let t="";for(let e=0;e<s.length;e+=1)t+=String.fromCharCode(s[e]);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function l(e){const s=e.replace(/-/g,"+").replace(/_/g,"/"),t=s.length%4==0?"":"=".repeat(4-s.length%4),n=atob(s+t),a=new Uint8Array(n.length);for(let e=0;e<n.length;e+=1)a[e]=n.charCodeAt(e);return a}async function i(e){return o(await crypto.subtle.exportKey("spki",e))}async function c(e,s){return async function(e,s){const t=await crypto.subtle.deriveBits({name:"ECDH",public:s},e,256),a=await crypto.subtle.importKey("raw",t,"HKDF",!1,["deriveKey"]);return crypto.subtle.deriveKey({name:"HKDF",hash:"SHA-256",salt:r("p-ch"),info:r("aes")},a,n,!1,["encrypt","decrypt"])}(e,s)}class u extends Error{constructor(e,s){super(e),this.name="PasslineApiError",this.code=s?.code,this.status=s?.status}}function d(e){if(!e)return!1;if(e instanceof u&&"OFFLINE"===e.code)return!0;const s=e instanceof Error?e.message:"";return/offline|failed to fetch|networkerror|load failed|request failed \(5\d\d\)/i.test(s)}class f{constructor(e){this.channel=null,this.handshake=null,this.token=e}gatewayUrl(){return"https://api.passline.ai/api/p"}async postJson(e){let s;try{s=await fetch(this.gatewayUrl(),{method:"POST",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify(e),cache:"no-store"})}catch{throw new u("Offline",{code:"OFFLINE"})}const t=await s.json().catch(()=>null);return{status:s.status,payload:t}}throwIfFailed(e,s){if(e>=200&&e<300)return;if(e>=500||0===e)throw new u("Offline",{code:"OFFLINE",status:e});const t=s&&"object"==typeof s&&"message"in s?String(s.message||""):"";throw new u(t||`Request failed (${e})`,{status:e})}async ensureChannel(){if(this.channel)return this.channel;if(this.handshake||(this.handshake=this.openChannel().finally(()=>{this.handshake=null})),await this.handshake,!this.channel)throw new u("Offline",{code:"OFFLINE"});return this.channel}async openChannel(){const e=await async function(){return crypto.subtle.generateKey(t,!0,["deriveBits"])}(),{status:s,payload:n}=await this.postJson({v:1,op:"h",c:await i(e.publicKey)});if(s>=500)throw new u("Offline",{code:"OFFLINE",status:s});const r=n;if(!r?.s||!r.t)throw new u("Unable to start chat",{status:s});const o=await async function(e){return crypto.subtle.importKey("spki",a(l(e)),t,!0,[])}(r.s);this.channel={key:await c(e.privateKey,o),ticket:r.t}}async request(e,s){const t=async()=>{const t=await this.ensureChannel(),n=await async function(e,s){const t=a(crypto.getRandomValues(new Uint8Array(12))),n=await crypto.subtle.encrypt({name:"AES-GCM",iv:t},e,r(JSON.stringify(s)));return{i:o(t),c:o(n)}}(t.key,{r:e,d:s,k:this.token}),{status:i,payload:c}=await this.postJson({v:1,op:"x",t:t.ticket,...n});if(400===i||409===i)throw this.channel=null,new u("retry",{status:i});const d=c;if(!d||!("i"in d)||!("c"in d))throw this.throwIfFailed(i,c),new u("Request failed",{status:i});const f=await async function(e,s,t){const n=await crypto.subtle.decrypt({name:"AES-GCM",iv:a(l(s))},e,a(l(t)));return JSON.parse((new TextDecoder).decode(n))}(t.key,d.i,d.c);return this.throwIfFailed(i,f),f};try{return await t()}catch(e){if(e instanceof u&&"retry"===e.message)return t();throw e}}async createSession(e){const s={},t=e?.locale?.trim();return t&&(s.locale=t),this.request("s",s)}async sendMessage(e,s){return this.request("m",{id:e,message:s})}}function h(e){return`${e}-${Date.now()}-${Math.random().toString(36).slice(2,8)}`}function p(e,t=!0,n=6048e5,a=200,r,o="Hi! How can I help?"){const[l,i]=s.useState(null),[c,u]=s.useState(!1),[p,g]=s.useState(!1),[m,y]=s.useState(null),b=s.useRef(new f(e)),w=s.useRef(null),C=s.useRef(0),S=r?`passline:chat:${e}:${r}`:`passline:chat:${e}`;w.current=l,s.useEffect(()=>{b.current=new f(e)},[e]);const x=s.useCallback(()=>{if(!t||"undefined"==typeof window)return null;try{const e=window.localStorage.getItem(S);if(!e)return null;const s=JSON.parse(e);return s?.sessionId&&Array.isArray(s.messages)?Date.now()-(s.updatedAt||0)>n?(window.localStorage.removeItem(S),null):s:null}catch{return null}},[n,t,S]),N=s.useCallback(e=>{if(t&&"undefined"!=typeof window)try{const s=e.messages.length>a?e.messages.slice(e.messages.length-a):e.messages;window.localStorage.setItem(S,JSON.stringify({...e,messages:s}))}catch{}},[a,t,S]),k=s.useCallback(()=>{if(t&&"undefined"!=typeof window)try{window.localStorage.removeItem(S)}catch{}},[t,S]),v=s.useCallback(e=>{N({sessionId:e.sessionId,assistantName:e.assistantName,companyName:e.companyName,logoUrl:e.logoUrl,messages:e.messages,updatedAt:Date.now()})},[N]),E=s.useCallback(()=>{g(!0),i(null),w.current=null,y("Offline"),u(!1)},[]),A=s.useCallback(()=>{const e=x();if(!e)return null;const s={sessionId:e.sessionId,assistantName:e.assistantName||"Assistant",companyName:e.companyName??null,logoUrl:e.logoUrl??null,messages:e.messages};return i(s),w.current=s,g(!1),y(null),u(!1),s},[x]),I=s.useCallback(async(e=!1)=>{const s=++C.current;if(y(null),!e){const e=A();if(e)return e}try{u(!0);const e=await b.current.createSession(r?{locale:r}:void 0);if(C.current!==s){const e=w.current;if(e)return e}const t=function(e,s=[]){return{sessionId:e.sessionId,assistantName:e.assistantName||"Assistant",companyName:e.companyName??null,logoUrl:e.logoUrl??null,greeting:e.greeting,messages:s}}(e,[{id:"greeting",role:"assistant",content:e.greeting||o,timestamp:Date.now()}]);return i(t),w.current=t,g(!1),v(t),t}catch(e){if(C.current!==s){const e=w.current;if(e)return e}if(d(e))throw E(),new Error("Offline");const t=e instanceof Error?e.message:"Unable to start chat";throw y(t),i(null),w.current=null,new Error(t)}finally{C.current===s&&u(!1)}},[o,E,r,v,A]),O=s.useCallback(async()=>{if(p)return null;if(w.current?.sessionId)return w.current;try{return await I(!1)}catch{return null}},[I,p]),j=s.useCallback(async e=>{const s=w.current;if(!s?.sessionId)throw new Error("No active session");const t={id:h("u"),role:"user",content:e,timestamp:Date.now()},n={...s,messages:[...s.messages,t]};i(n),w.current=n,v(n);try{const t=await b.current.sendMessage(s.sessionId,e),a={id:h("a"),role:"assistant",content:t.reply||"",timestamp:Date.now()},r=w.current??n,o={...r,messages:[...r.messages,a]};return i(o),w.current=o,v(o),a}catch(e){if(d(e))throw E(),new Error("Offline");if(function(e){const s=e instanceof Error?e.message.toLowerCase():"";return/session/.test(s)&&/(not found|expired|invalid)/.test(s)}(e))throw k(),i(null),w.current=null,await I(!0),new Error("Session expired. Please try your message again.");throw e}},[k,I,E,v]),M=s.useCallback(async()=>(k(),i(null),w.current=null,g(!1),I(!0)),[k,I]);return s.useEffect(()=>{C.current+=1,i(null),w.current=null,g(!1),y(null),u(!1),A()},[A,e,r]),{session:l,messages:l?.messages||[],isLoading:c,offline:p,error:m,sendMessage:j,newChat:M,createSession:I,ensureSession:O,clearStoredSession:k}}function g(e,t){const[n,a]=s.useState(Boolean(t?.autoOpen)),[r,o]=s.useState(!1),l=s.useRef(null),{session:i,messages:c,isLoading:u,offline:d,error:f,sendMessage:h,newChat:g,ensureSession:m}=p(e,t?.persistSession,t?.maxSessionAge,t?.maxStoredMessages,t?.locale,t?.defaultGreeting);s.useEffect(()=>{t?.autoOpen&&a(!0)},[t?.autoOpen]),s.useEffect(()=>{l.current=null},[e,t?.locale]),s.useEffect(()=>{n&&!d&&m()},[m,n,d,e,t?.locale]),s.useEffect(()=>{i?.sessionId&&i.sessionId!==l.current&&(l.current=i.sessionId,t?.onSessionStart?.(i))},[t,i]),s.useEffect(()=>{f&&t?.onError&&t.onError(new Error(f))},[f,t]);const y=s.useCallback(async e=>{if(e.trim()&&!r&&!u&&!d)try{o(!0),t?.onMessageSend?.(e);const s=await h(e);t?.onMessageReceive?.(s)}catch(e){t?.onError?.(e instanceof Error?e:new Error("Failed to send message"))}finally{o(!1)}},[u,r,d,t,h]),b=s.useCallback(async()=>{try{await g()}catch(e){t?.onError?.(e instanceof Error?e:new Error("Failed to start new chat"))}},[g,t]),w=s.useCallback(()=>{a(e=>!e)},[]);return{isOpen:n,setIsOpen:a,toggleOpen:w,isSending:r,isLoading:u,offline:d,messages:c,session:i,error:f,sendMessage:y,newChat:b}}const m={assistant:"Assistant",offline:"Offline",unavailable:"Unavailable",connecting:"Connecting…",online:"Online",typeMessage:"Type a message…",send:"Send",openChat:"Open chat",closeChat:"Close chat",newChat:"Start new chat",newChatTitle:"Start new chat",typing:"Assistant is typing…",panelAriaLabel:"Passline chat",organizationLogo:"Organization logo",defaultGreeting:"Hi! How can I help?"};function y(e){return{...m,...e}}const b={primaryColor:"#0f766e",primaryHoverColor:"#0d9488",backgroundColor:"#f8fafc",textColor:"#0f172a",userBubbleColor:"#0f766e",userBubbleTextColor:"#ffffff",assistantBubbleColor:"#ffffff",assistantBubbleTextColor:"#1e293b",borderRadius:"16px",borderWidth:"1px",borderColor:"#e2e8f0",buttonSize:"medium",buttonBorderRadius:"999px",buttonBackgroundColor:"#0f766e",buttonHoverColor:"#0d9488",buttonIconColor:"#ffffff",panelWidth:"380px",panelHeight:"560px",panelShadow:"0 12px 40px rgba(0,0,0,.22)",fontFamily:"system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",fontSize:"14px",spacing:"12px",padding:"12px",className:"",buttonClassName:"",panelClassName:"",headerClassName:"",messagesClassName:"",inputClassName:""};function w(e){const s=String(e||"").trim().split(/\s+/).filter(Boolean);return 0===s.length?"A":1===s.length?s[0].slice(0,2).toUpperCase():(s[0].charAt(0)+s[1].charAt(0)).toUpperCase()}function C({logoUrl:t,label:n,logoAlt:a="Organization logo",fallback:r="initials",className:o,backgroundColor:l}){const[i,c]=s.useState(!1);s.useEffect(()=>{c(!1)},[t]);const u=Boolean(t)&&!i;return e.jsx("div",{className:`${o}${u?" passline-has-logo":""}`,style:u?void 0:{backgroundColor:l},children:u?e.jsx("img",{src:t??"",alt:n||a,className:"passline-chat-avatar-img",onError:()=>c(!0)}):"icon"===r?e.jsx("svg",{viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",children:e.jsx("path",{d:"M4 4h16a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H9l-5 4v-4H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z"})}):w(n)})}exports.DEFAULT_PASSLINE_CHAT_LABELS=m,exports.PasslineChat=({token:t,locale:n,labels:a,theme:r={},className:o="",buttonClassName:l="",panelClassName:i="",renderButton:c,renderHeader:u,renderMessage:d,renderInput:f,autoOpen:h=!1,persistSession:p=!0,maxSessionAge:m,maxStoredMessages:S,onOpen:x,onClose:N,onMessageSend:k,onMessageReceive:v,onError:E,onSessionStart:A,onNewChat:I})=>{const O={...b,...r},j=s.useMemo(()=>y(a),[a]),[M,U]=s.useState(""),F=s.useRef(null),R=s.useRef(null),B=s.useRef(!1),{isOpen:L,setIsOpen:$,toggleOpen:H,isSending:z,isLoading:T,offline:D,messages:K,session:P,error:q,sendMessage:J,newChat:G}=g(t,{locale:n,defaultGreeting:j.defaultGreeting,persistSession:p,maxSessionAge:m,maxStoredMessages:S,autoOpen:h,onMessageSend:k,onMessageReceive:v,onError:E,onSessionStart:A}),_=P?.companyName||P?.assistantName||j.assistant,V=T||D||!P?.sessionId,W=D?j.offline:q&&!P?j.unavailable:P?.companyName||(T?j.connecting:j.online),Q=D?j.offline:T?j.connecting:P?.sessionId?j.typeMessage:j.unavailable;s.useEffect(()=>{h&&$(!0)},[h,$]),s.useEffect(()=>{L!==B.current&&(B.current=L,L?x?.():N?.())},[L,x,N]),s.useEffect(()=>{if(!L||D)return;const e=window.setTimeout(()=>{R.current?.focus()},100);return()=>window.clearTimeout(e)},[L,D]),s.useEffect(()=>{F.current?.scrollIntoView({behavior:"smooth"})},[K,z,T]);const X=s.useCallback(()=>{!M.trim()||z||V||(J(M),U(""))},[V,M,z,J]),Y=s.useCallback(e=>{"Enter"!==e.key||e.shiftKey||(e.preventDefault(),X())},[X]),Z=s.useCallback(()=>{G(),I?.()},[G,I]),ee="small"===O.buttonSize?"44px":"large"===O.buttonSize?"64px":"56px",se=P?.assistantName||j.assistant;return e.jsxs("div",{className:`passline-chat-wrapper ${O.className} ${o}`,children:[L?e.jsxs("div",{className:`passline-chat-panel ${O.panelClassName} ${i}`,role:"dialog","aria-label":j.panelAriaLabel,style:{width:O.panelWidth,height:O.panelHeight,boxShadow:O.panelShadow,backgroundColor:O.backgroundColor,borderRadius:O.borderRadius,fontFamily:O.fontFamily,fontSize:O.fontSize,color:O.textColor},children:[u?u({title:se,subtitle:W,avatar:w(_),onNewChat:Z}):e.jsxs("div",{className:`passline-chat-header ${O.headerClassName}`,children:[e.jsx(C,{logoUrl:P?.logoUrl,label:_,logoAlt:j.organizationLogo,className:"passline-header-avatar",backgroundColor:O.primaryColor}),e.jsxs("div",{className:"passline-header-info",children:[e.jsx("div",{className:"passline-header-title",children:se}),e.jsx("div",{className:"passline-header-subtitle",children:W})]}),e.jsx("button",{className:"passline-header-newchat",onClick:Z,title:j.newChatTitle,"aria-label":j.newChat,style:{color:O.textColor},type:"button",children:e.jsx("svg",{viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",children:e.jsx("path",{d:"M17.65 6.35A7.95 7.95 0 0 0 12 4a8 8 0 1 0 7.75 10h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4z"})})})]}),e.jsxs("div",{className:`passline-chat-messages ${O.messagesClassName}`,children:[T&&0===K.length?e.jsx("p",{className:"passline-chat-meta",children:j.connecting}):null,K.map((t,n)=>e.jsx(s.Fragment,{children:d?d({message:t,className:""}):e.jsx("div",{className:"passline-message "+("user"===t.role?"message-user":"message-assistant"),style:{backgroundColor:"user"===t.role?O.userBubbleColor:O.assistantBubbleColor,color:"user"===t.role?O.userBubbleTextColor:O.assistantBubbleTextColor,borderRadius:O.borderRadius,border:"assistant"===t.role?`${O.borderWidth} solid ${O.borderColor}`:"none"},children:t.content})},t.id||n)),z?e.jsx("p",{className:"passline-chat-typing",children:j.typing}):null,q?e.jsx("p",{className:"passline-chat-error",children:q}):null,e.jsx("div",{ref:F})]}),f?f({value:M,onChange:U,onSend:X,disabled:V||z,placeholder:Q,className:""}):e.jsxs("div",{className:`passline-chat-input-container ${O.inputClassName}`,children:[e.jsx("input",{ref:R,type:"text",className:"passline-chat-input",value:M,onChange:e=>U(e.target.value),onKeyDown:Y,placeholder:Q,disabled:V,autoComplete:"off",style:{borderColor:O.borderColor,borderRadius:O.borderRadius,backgroundColor:O.backgroundColor,color:O.textColor,fontFamily:O.fontFamily,fontSize:O.fontSize}}),e.jsx("button",{className:"passline-chat-send",onClick:X,disabled:V||z||!M.trim(),style:{backgroundColor:O.primaryColor,borderRadius:O.borderRadius,color:"#ffffff"},type:"button",children:j.send})]})]}):null,c?c({onClick:H,isOpen:L,className:""}):e.jsx("button",{className:`passline-chat-button ${O.buttonClassName} ${l}`,onClick:H,style:{backgroundColor:P?.logoUrl?void 0:L?O.primaryHoverColor:O.buttonBackgroundColor,color:O.buttonIconColor,borderRadius:O.buttonBorderRadius,width:ee,height:ee},"aria-label":L?j.closeChat:j.openChat,type:"button",children:e.jsx(C,{logoUrl:P?.logoUrl,label:_,logoAlt:j.organizationLogo,fallback:"icon",className:"passline-chat-button-mark"})})]})},exports.mergePasslineChatLabels=y,exports.usePasslineChat=g,exports.usePasslineSession=p;
2
2
  //# sourceMappingURL=index.js.map