botwire-js 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/botwire.js +7 -7
- package/dist/index.cjs +6 -1
- package/dist/index.js +6 -1
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
package/dist/botwire.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"use strict";(()=>{var m="/support",o=class extends Error{constructor(e,i,s){super(i);this.status=e;this.httpStatus=s;this.name="BotWireError"}},
|
|
2
|
-
`))!==-1;){let
|
|
1
|
+
"use strict";(()=>{var m="/support",o=class extends Error{constructor(e,i,s){super(i);this.status=e;this.httpStatus=s;this.name="BotWireError"}},c=class{constructor(t={}){this._sessionToken=null;this.endpoint=x(t.endpoint??m),this.publicKey=t.publicKey;let e=t.fetch??globalThis.fetch;if(!e)throw new Error("BotWireClient: no global fetch available \u2014 pass config.fetch");this._fetch=e.bind(globalThis)}getSessionToken(){return this._sessionToken}setSessionToken(t){this._sessionToken=t}async initSession(t){let e=await this.post(`${this.endpoint}/session`,{},t);if(!e.ok)throw await this.toError(e);let i=await e.json();return this._sessionToken=i.sessionToken,{sessionToken:i.sessionToken,needsName:i.needsName??!1,errorMessage:i.errorMessage}}async chat(t,e={}){await this.ensureSession(e.signal);let i=await this.post(`${this.endpoint}/chat`,this.body(t,e),e.signal);await this.staleSession(i)&&(await this.initSession(e.signal),i=await this.post(`${this.endpoint}/chat`,this.body(t,e),e.signal));let s;try{s=await i.json()}catch{throw await this.toError(i)}return s.sessionToken&&(this._sessionToken=s.sessionToken),s}async*streamChat(t,e={}){await this.ensureSession(e.signal);let i=await this.post(`${this.endpoint}/chat/stream`,this.body(t,e),e.signal);if(await this.staleSession(i)&&(await this.initSession(e.signal),i=await this.post(`${this.endpoint}/chat/stream`,this.body(t,e),e.signal)),!i.ok||!i.body)throw await this.toError(i);yield*this.parseSse(i.body)}async ensureSession(t){this._sessionToken||await this.initSession(t)}body(t,e){let i={message:t,sessionToken:this._sessionToken};return e.contactEmail&&(i.contactEmail=e.contactEmail),i}async staleSession(t){if(t.status!==400)return!1;try{if((await t.clone().json()).status==="InvalidSession")return this._sessionToken=null,!0}catch{}return!1}async*parseSse(t){let e=t.getReader(),i=new TextDecoder,s="";try{for(;;){let{value:a,done:r}=await e.read();if(r)break;s+=i.decode(a,{stream:!0});let l;for(;(l=s.indexOf(`
|
|
2
|
+
`))!==-1;){let d=s.slice(0,l);if(s=s.slice(l+1),!d.startsWith("data: "))continue;let u=d.slice(6);if(u==="[DONE]"){yield{type:"done"};return}let g=v(u);g&&(yield g)}}}finally{e.releaseLock()}}post(t,e,i){let s={"Content-Type":"application/json"};return this.publicKey&&(s["X-BotWire-Key"]=this.publicKey),this._fetch(t,{method:"POST",headers:s,body:JSON.stringify(e),signal:i})}async toError(t){let e="Error",i=`BotWire request failed (HTTP ${t.status})`;try{let s=await t.clone().json();s.status&&(e=s.status),s.message&&(i=s.message)}catch{}return new o(e,i,t.status)}};function x(n){let t=n.length;for(;t>0&&n.charCodeAt(t-1)===47;)t--;return n.slice(0,t)}function v(n){let t;try{t=JSON.parse(n)}catch{return null}switch(t.type){case"token":return{type:"delta",delta:t.value??""};case"collect_contact":return{type:"collect_contact"};case"escalated":return{type:"escalated",ticketId:t.ticketId??"",message:t.message??""};case"blocked":return{type:"blocked",reason:t.reason??""};default:return null}}var h="botwire_session",f={en:{title:"Support",greeting:"How can we help you today?",placeholder:"Type a message\u2026",sendLabel:"Send",contactPrompt:"Please leave your email address so our team can follow up with you.",emailPlaceholder:"your@email.com",submitLabel:"Submit",cancelLabel:"Cancel",cancelMessage:"You have ended this conversation."},"zh-CN":{title:"\u5728\u7EBF\u5BA2\u670D",greeting:"\u8BF7\u95EE\u6709\u4EC0\u4E48\u53EF\u4EE5\u5E2E\u60A8\uFF1F",placeholder:"\u8F93\u5165\u6D88\u606F\u2026",sendLabel:"\u53D1\u9001",contactPrompt:"\u8BF7\u7559\u4E0B\u60A8\u7684\u90AE\u7BB1\uFF0C\u65B9\u4FBF\u6211\u4EEC\u7684\u56E2\u961F\u8DDF\u8FDB\u3002",emailPlaceholder:"your@email.com",submitLabel:"\u63D0\u4EA4",cancelLabel:"\u53D6\u6D88",cancelMessage:"\u60A8\u5DF2\u7ED3\u675F\u672C\u6B21\u4F1A\u8BDD\u3002"},ja:{title:"\u30B5\u30DD\u30FC\u30C8",greeting:"\u3054\u7528\u4EF6\u3092\u304A\u77E5\u3089\u305B\u304F\u3060\u3055\u3044\u3002",placeholder:"\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u5165\u529B\u2026",sendLabel:"\u9001\u4FE1",contactPrompt:"\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u3054\u8A18\u5165\u3044\u305F\u3060\u3051\u308C\u3070\u3001\u62C5\u5F53\u8005\u3088\u308A\u3054\u9023\u7D61\u3044\u305F\u3057\u307E\u3059\u3002",emailPlaceholder:"your@email.com",submitLabel:"\u9001\u4FE1\u3059\u308B",cancelLabel:"\u30AD\u30E3\u30F3\u30BB\u30EB",cancelMessage:"\u3053\u306E\u4F1A\u8A71\u3092\u7D42\u4E86\u3057\u307E\u3057\u305F\u3002"}};function y(n){if(!n)return"en";let t=n.toLowerCase();return t==="zh"||t.startsWith("zh-")||t.startsWith("zh_")?"zh-CN":t==="ja"||t.startsWith("ja-")||t.startsWith("ja_")?"ja":"en"}function w(n){return n.replace(/\\/g,"\\\\").replace(/'/g,"\\'")}function k(n,t,e){let s=t==="bottom-left"?"left":"right";return`
|
|
3
3
|
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
4
4
|
|
|
5
5
|
#bubble{
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
scroll-behavior:smooth;
|
|
48
48
|
}
|
|
49
49
|
#messages:empty::before{
|
|
50
|
-
content:'${
|
|
50
|
+
content:'${w(e)}';
|
|
51
51
|
color:#94a3b8;font-size:13px;text-align:center;
|
|
52
52
|
margin:auto;padding:32px 16px;
|
|
53
53
|
}
|
|
@@ -146,14 +146,14 @@
|
|
|
146
146
|
border-radius:0;border-top-left-radius:16px;border-top-right-radius:16px}
|
|
147
147
|
#bubble{bottom:16px;${s}:16px}
|
|
148
148
|
}
|
|
149
|
-
`}var b='<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M20 2H4a2 2 0 00-2 2v18l4-4h14a2 2 0 002-2V4a2 2 0 00-2-2z"/></svg>',
|
|
150
|
-
<style>${
|
|
149
|
+
`}var b='<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M20 2H4a2 2 0 00-2 2v18l4-4h14a2 2 0 002-2V4a2 2 0 00-2-2z"/></svg>',E='<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M20 2H4a2 2 0 00-2 2v18l4-4h14a2 2 0 002-2V4a2 2 0 00-2-2zM6 10h12v2H6v-2zm0-4h12v2H6V6zm8 8H6v-2h8v2z"/></svg>',T='<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M17.65 6.35A7.96 7.96 0 0012 4a8 8 0 108 8h-2a6 6 0 11-1.76-4.24L13 11h7V4l-2.35 2.35z"/></svg>',p=class extends HTMLElement{constructor(){super();this.streaming=!1;this.streamAbort=null;this.awaitingEmail=!1;this.ticketCreated=!1;this.errorOccurred=!1;this.errorMessage="Something went wrong. Please try again.";this.shadow=this.attachShadow({mode:"open"})}get endpoint(){return this.dataset.endpoint??"/support"}get primary(){return this.dataset.primaryColor??"#6366f1"}get position(){return this.dataset.position??"bottom-right"}get publicKey(){return this.dataset.publicKey}get offtopicMessage(){return this.dataset.offtopicMessage}get resetEnabled(){return this.dataset.reset!=="false"}get resetConfirm(){return this.dataset.resetConfirm!=="false"}get langKey(){return y(this.dataset.lang)}t(e){let i=this.dataset[e];return i!==void 0?i:f[this.langKey]?.[e]??f.en[e]}get widgetTitle(){return this.t("title")}get placeholder(){return this.t("placeholder")}get contactPrompt(){return this.t("contactPrompt")}get emailPlaceholder(){return this.t("emailPlaceholder")}get sendLabel(){return this.t("sendLabel")}get submitLabel(){return this.t("submitLabel")}get cancelLabel(){return this.t("cancelLabel")}get cancelMessage(){return this.t("cancelMessage")}get greeting(){return this.t("greeting")}get starters(){return(this.dataset.starters??"").split("|").map(e=>e.trim()).filter(e=>e.length>0)}connectedCallback(){this.mount(),this.client=new c({endpoint:this.endpoint,publicKey:this.publicKey}),this.client.setSessionToken(sessionStorage.getItem(h)),this.client.getSessionToken()||this.initSession()}mount(){this.shadow.innerHTML=`
|
|
150
|
+
<style>${k(this.primary,this.position,this.greeting)}</style>
|
|
151
151
|
<button id="bubble" aria-label="Open support chat" aria-expanded="false">${b}</button>
|
|
152
152
|
<div id="panel" hidden role="dialog" aria-label="${this.esc(this.widgetTitle)} support chat">
|
|
153
153
|
<div id="header">
|
|
154
154
|
<span id="header-title">${this.esc(this.widgetTitle)}</span>
|
|
155
155
|
<div id="header-actions">
|
|
156
|
-
<button id="reset" type="button" hidden aria-label="Reset conversation">${
|
|
156
|
+
<button id="reset" type="button" hidden aria-label="Reset conversation">${T}</button>
|
|
157
157
|
<button id="close" aria-label="Close chat">\u2715</button>
|
|
158
158
|
</div>
|
|
159
159
|
</div>
|
|
@@ -173,4 +173,4 @@
|
|
|
173
173
|
</div>
|
|
174
174
|
</form>
|
|
175
175
|
<div id="ticket-card" hidden role="status"></div>
|
|
176
|
-
</div>`,this.panel=this.q("#panel"),this.bubble=this.q("#bubble"),this.messages=this.q("#messages"),this.startersBox=this.q("#starters"),this.resetBtn=this.q("#reset"),this.typing=this.q("#typing"),this.inputArea=this.q("#input-area"),this.input=this.q("#input"),this.sendBtn=this.q("#send"),this.contact=this.q("#contact-form"),this.emailIn=this.q("#email-input"),this.cancelBtn=this.q("#contact-cancel"),this.ticket=this.q("#ticket-card"),this.bubble.addEventListener("click",()=>this.toggle()),this.q("#close").addEventListener("click",()=>this.close()),this.resetBtn.addEventListener("click",()=>this.handleReset()),this.sendBtn.addEventListener("click",()=>this.handleSend()),this.input.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),this.handleSend())}),this.input.addEventListener("input",()=>this.autoResize()),this.contact.addEventListener("submit",e=>{e.preventDefault(),this.handleContactSubmit()}),this.cancelBtn.addEventListener("click",()=>this.handleContactCancel()),this.resetBtn.hidden=!this.resetEnabled,this.renderStarters()}async initSession(){try{let e=await this.client.initSession();sessionStorage.setItem(
|
|
176
|
+
</div>`,this.panel=this.q("#panel"),this.bubble=this.q("#bubble"),this.messages=this.q("#messages"),this.startersBox=this.q("#starters"),this.resetBtn=this.q("#reset"),this.typing=this.q("#typing"),this.inputArea=this.q("#input-area"),this.input=this.q("#input"),this.sendBtn=this.q("#send"),this.contact=this.q("#contact-form"),this.emailIn=this.q("#email-input"),this.cancelBtn=this.q("#contact-cancel"),this.ticket=this.q("#ticket-card"),this.bubble.addEventListener("click",()=>this.toggle()),this.q("#close").addEventListener("click",()=>this.close()),this.resetBtn.addEventListener("click",()=>this.handleReset()),this.sendBtn.addEventListener("click",()=>this.handleSend()),this.input.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),this.handleSend())}),this.input.addEventListener("input",()=>this.autoResize()),this.contact.addEventListener("submit",e=>{e.preventDefault(),this.handleContactSubmit()}),this.cancelBtn.addEventListener("click",()=>this.handleContactCancel()),this.resetBtn.hidden=!this.resetEnabled,this.renderStarters()}async initSession(){try{let e=await this.client.initSession();sessionStorage.setItem(h,e.sessionToken),e.errorMessage&&(this.errorMessage=e.errorMessage)}catch{}}toggle(){this.panel.hidden?this.open():this.ticketCreated?(this.resetConversation(),this.input.focus()):this.close()}open(){this.ticketCreated&&this.resetConversation(),this.panel.hidden=!1,this.bubble.innerHTML=E,this.bubble.setAttribute("aria-expanded","true"),this.awaitingEmail?this.emailIn.focus():this.input.focus()}resetConversation(){this.streamAbort?.abort(),this.streamAbort=null,this.messages.innerHTML="",this.ticketCreated=!1,this.awaitingEmail=!1,this.streaming=!1,this.errorOccurred=!1,this.contact.hidden=!0,this.ticket.hidden=!0,this.inputArea.hidden=!1,this.sendBtn.disabled=!1,this.emailIn.value="",this.client.setSessionToken(null),sessionStorage.removeItem(h),this.renderStarters(),this.initSession()}handleReset(){if(this.resetConfirm){let e=this.dataset.resetConfirmMessage??"Start a new conversation?";if(typeof confirm=="function"&&!confirm(e))return}this.resetConversation(),!this.panel.hidden&&!this.awaitingEmail&&this.input.focus()}renderStarters(){this.startersBox.innerHTML="";let e=this.starters;if(e.length===0){this.startersBox.hidden=!0;return}for(let i of e){let s=document.createElement("button");s.type="button",s.className="starter",s.textContent=i,s.addEventListener("click",()=>{this.input.value=i,this.handleSend()}),this.startersBox.appendChild(s)}this.startersBox.hidden=!1}close(){this.errorOccurred&&this.resetConversation(),this.panel.hidden=!0,this.bubble.innerHTML=b,this.bubble.setAttribute("aria-expanded","false")}handleSend(){if(this.streaming||this.awaitingEmail)return;let e=this.input.value.trim();e&&(this.input.value="",this.autoResize(),this.startersBox.hidden=!0,this.appendMessage("user",e),this.stream(e))}handleContactSubmit(){let e=this.emailIn.value.trim();e&&(this.contact.hidden=!0,this.awaitingEmail=!1,this.stream("",e))}handleContactCancel(){this.contact.hidden=!0,this.awaitingEmail=!1,this.ticketCreated=!0,this.appendMessage("sys",this.cancelMessage)}async stream(e,i){if(this.streaming)return;this.streaming=!0,this.sendBtn.disabled=!0,this.typing.hidden=!1;let s=new AbortController;this.streamAbort=s;let a=null;try{for await(let r of this.client.streamChat(e,{contactEmail:i,signal:s.signal}))switch(this.typing.hidden=!0,r.type){case"delta":a||(a=this.appendMessage("bot","")),a.textContent+=r.delta,this.scrollBottom();break;case"collect_contact":this.inputArea.hidden=!0,this.contact.hidden=!1,this.awaitingEmail=!0,requestAnimationFrame(()=>{this.scrollBottom(),this.emailIn.focus()});break;case"escalated":this.ticketCreated=!0,this.ticket.hidden=!1,this.ticket.textContent=r.message,this.inputArea.hidden=!0;break;case"blocked":this.appendMessage("bot",this.offtopicMessage??r.reason);break;case"done":break}}catch(r){if(s.signal.aborted)return;if(this.typing.hidden=!0,!(r instanceof DOMException&&r.name==="AbortError"))if(r instanceof o){this.errorOccurred=!0;let d=["PiiBlocked","Blocked","RateLimited"].includes(r.status)&&r.message?r.message:this.errorMessage;this.appendMessage("sys",d)}else this.appendMessage("sys","Connection error. Please try again.")}finally{if(this.streamAbort===s){this.streamAbort=null;let r=this.client.getSessionToken();r&&sessionStorage.setItem(h,r),this.typing.hidden=!0,this.streaming=!1,!this.awaitingEmail&&!this.ticketCreated&&(this.sendBtn.disabled=!1,this.panel.hidden||this.input.focus())}}}appendMessage(e,i){let s=document.createElement("div");return s.className=`msg msg-${e}`,s.textContent=i,this.messages.appendChild(s),this.scrollBottom(),s}scrollBottom(){this.messages.scrollTop=this.messages.scrollHeight}autoResize(){this.input.style.height="auto",this.input.style.height=`${Math.min(this.input.scrollHeight,100)}px`}esc(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}q(e){return this.shadow.querySelector(e)}};customElements.define("botwire-widget",p);})();
|
package/dist/index.cjs
CHANGED
|
@@ -38,7 +38,7 @@ var BotWireError = class extends Error {
|
|
|
38
38
|
var BotWireClient = class {
|
|
39
39
|
constructor(config = {}) {
|
|
40
40
|
this._sessionToken = null;
|
|
41
|
-
this.endpoint = (config.endpoint ?? DEFAULT_ENDPOINT)
|
|
41
|
+
this.endpoint = stripTrailingSlashes(config.endpoint ?? DEFAULT_ENDPOINT);
|
|
42
42
|
this.publicKey = config.publicKey;
|
|
43
43
|
const f = config.fetch ?? globalThis.fetch;
|
|
44
44
|
if (!f) throw new Error("BotWireClient: no global fetch available \u2014 pass config.fetch");
|
|
@@ -165,6 +165,11 @@ var BotWireClient = class {
|
|
|
165
165
|
return new BotWireError(status, message, resp.status);
|
|
166
166
|
}
|
|
167
167
|
};
|
|
168
|
+
function stripTrailingSlashes(s) {
|
|
169
|
+
let end = s.length;
|
|
170
|
+
while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
|
|
171
|
+
return s.slice(0, end);
|
|
172
|
+
}
|
|
168
173
|
function mapWireEvent(data) {
|
|
169
174
|
let w;
|
|
170
175
|
try {
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ var BotWireError = class extends Error {
|
|
|
11
11
|
var BotWireClient = class {
|
|
12
12
|
constructor(config = {}) {
|
|
13
13
|
this._sessionToken = null;
|
|
14
|
-
this.endpoint = (config.endpoint ?? DEFAULT_ENDPOINT)
|
|
14
|
+
this.endpoint = stripTrailingSlashes(config.endpoint ?? DEFAULT_ENDPOINT);
|
|
15
15
|
this.publicKey = config.publicKey;
|
|
16
16
|
const f = config.fetch ?? globalThis.fetch;
|
|
17
17
|
if (!f) throw new Error("BotWireClient: no global fetch available \u2014 pass config.fetch");
|
|
@@ -138,6 +138,11 @@ var BotWireClient = class {
|
|
|
138
138
|
return new BotWireError(status, message, resp.status);
|
|
139
139
|
}
|
|
140
140
|
};
|
|
141
|
+
function stripTrailingSlashes(s) {
|
|
142
|
+
let end = s.length;
|
|
143
|
+
while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
|
|
144
|
+
return s.slice(0, end);
|
|
145
|
+
}
|
|
141
146
|
function mapWireEvent(data) {
|
|
142
147
|
let w;
|
|
143
148
|
try {
|
package/dist/types.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface BotWireConfig {
|
|
|
16
16
|
}
|
|
17
17
|
/** Result of a non-streaming {@link BotWireClient.chat} call. */
|
|
18
18
|
export interface BotWireResponse {
|
|
19
|
-
/** Server status, e.g. `Answered`, `NeedHuman`, `TicketCreated`, `Blocked`, `RateLimited`. */
|
|
19
|
+
/** Server status, e.g. `Answered`, `NeedHuman`, `TicketCreated`, `Blocked`, `PiiBlocked`, `RateLimited`. */
|
|
20
20
|
status: string;
|
|
21
21
|
/** The assistant message (or status explanation). */
|
|
22
22
|
message: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "botwire-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Framework-agnostic JS/TS client for the BotWire support API — chat, SSE streaming, and session management with zero DOM dependencies.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"author": "Object IT Limited",
|