@chativa/connector-directline 0.7.3 → 0.8.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/chativa-directline.global.js +1 -1
- package/dist/chativa-directline.global.js.map +1 -1
- package/dist/index.cjs +4 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.js +32 -23
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("botframework-directlinejs"),f=Symbol("typing"),
|
|
2
|
-
`),buttons:n}}function
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("botframework-directlinejs"),f=Symbol("typing"),y="application/vnd.microsoft.card.hero",k="application/vnd.microsoft.card.thumbnail",m="application/vnd.microsoft.card.adaptive",x="application/vnd.microsoft.card.signin",H="application/vnd.microsoft.card.oauth",A="application/vnd.microsoft.card.receipt",D="application/vnd.microsoft.card.audio",E="application/vnd.microsoft.card.video",L="application/vnd.microsoft.card.animation",b="application/vnd.microsoft.card.flex";function v(i,t){if(i.from.id===t)return null;if(i.type==="typing")return f;if(i.type!=="message")return null;const e=i,n=e.id??`dl-${Date.now()}-${Math.random().toString(36).slice(2,7)}`,s=e.timestamp?new Date(e.timestamp).getTime():Date.now(),r=e.channelData,o=R(e);if(e.attachments&&e.attachments.length>0){const a=$(e,n,s);return a&&(o.length>0&&(a.actions=o),r&&(a.data.channelData=r)),a}return o.length>0?{id:n,type:"quick-reply",from:"bot",data:{text:e.text??"",actions:o,...r?{channelData:r}:{}},timestamp:s}:e.text?{id:n,type:"text",from:"bot",data:{text:e.text,...r?{channelData:r}:{}},timestamp:s}:null}function $(i,t,e){const n=i.attachments,s=i.attachmentLayout,r=[y,k,b,m];return n.every(a=>r.includes(a.contentType))&&(n.length>1||s==="carousel")?{id:t,type:"carousel",from:"bot",data:{cards:n.map(a=>a.contentType===m?U(a.content):T(a.content))},timestamp:e}:N(n[0],i,t,e)}function N(i,t,e,n){const s=i.contentType;if(s===y||s===k||s===b){const r=i.content;return{id:e,type:"card",from:"bot",data:T(r),timestamp:n}}if(s===m)return P(i.content,e,n);if(s===x||s===H){const r=i.content;return{id:e,type:"buttons",from:"bot",data:{text:r.text??"Please sign in",buttons:I(r.buttons)},timestamp:n}}if(s===A)return _(i.content,e,n);if(s===E){const r=i.content,o=r.media?.[0]?.url;return o?{id:e,type:"video",from:"bot",data:{src:o,poster:r.image?.url,caption:r.title??t.text},timestamp:n}:null}if(s===D){const r=i.content,o=r.media?.[0]?.url;return o?{id:e,type:"file",from:"bot",data:{url:o,name:r.title??"audio",mimeType:"audio/mpeg"},timestamp:n}:null}if(s===L){const r=i.content,o=r.media?.[0]?.url;return o?{id:e,type:"image",from:"bot",data:{src:o,caption:r.title??t.text},timestamp:n}:null}return s.startsWith("image/")&&"contentUrl"in i?{id:e,type:"image",from:"bot",data:{src:i.contentUrl,alt:i.name,caption:t.text},timestamp:n}:s.startsWith("video/")&&"contentUrl"in i?{id:e,type:"video",from:"bot",data:{src:i.contentUrl,caption:t.text},timestamp:n}:"contentUrl"in i?{id:e,type:"file",from:"bot",data:{url:i.contentUrl,name:i.name??"file",mimeType:s},timestamp:n}:null}function T(i){return{image:i.images?.[0]?.url,title:i.title??"",subtitle:i.subtitle??i.text,buttons:I(i.buttons)}}function I(i){return!i||i.length===0?[]:i.map(t=>{const e=("title"in t?t.title:void 0)??String(t.value??"");switch(t.type){case"openUrl":case"signin":return{label:e,url:String(t.value??"")};case"call":return{label:e,url:String(t.value??"")};default:return{label:e,value:String(t.value??e)}}})}function R(i){const t=i.suggestedActions?.actions;return!t||t.length===0?[]:t.map(e=>{const n=("title"in e?e.title:void 0)??String(e.value??"");return e.type==="openUrl"?{label:n,url:String(e.value??"")}:{label:n,value:String(e.value??n)}})}function d(i,t,e,n){if(i)for(const s of i)switch(s.type){case"TextBlock":s.text&&t.push(s.text);break;case"Image":s.url&&n(s.url);break;case"ActionSet":s.actions&&w(s.actions,e);break;case"Container":d(s.items,t,e,n);break;case"ColumnSet":for(const r of s.columns??[])d(r.items,t,e,n);break;case"Column":d(s.items,t,e,n);break}}function C(i){const t=[];let e;const n=[];return d(i.body,t,n,s=>{e=e??s}),Array.isArray(i.actions)&&w(i.actions,n),{texts:t,image:e,buttons:n}}function U(i){const{texts:t,image:e,buttons:n}=C(i);return{image:e,title:t[0]??"",subtitle:t.slice(1).join(`
|
|
2
|
+
`),buttons:n}}function P(i,t,e){const{texts:n,image:s,buttons:r}=C(i),o=i.fallbackText??i.speak??n.join(`
|
|
3
3
|
`);return s?{id:t,type:"card",from:"bot",data:{image:s,title:n[0]??"",subtitle:n.slice(1).join(`
|
|
4
|
-
`),buttons:r},timestamp:e}:r.length>0?{id:t,type:"buttons",from:"bot",data:{text:o||"Adaptive Card",buttons:r},timestamp:e}:{id:t,type:"text",from:"bot",data:{text:o||"[Adaptive Card]"},timestamp:e}}function w(i,t){for(const e of i){const n=e.title??"Action";e.type==="Action.OpenUrl"&&e.url?t.push({label:n,url:e.url}):e.type==="Action.Submit"?t.push({label:n,value:typeof e.data=="string"?e.data:n}):t.push({label:n,value:n})}}function
|
|
5
|
-
`)},timestamp:e}}const y="chativa_directline_userId",h="chativa_directline_conversation";function S(){return Math.random().toString(36).slice(2,15)+Math.random().toString(36).slice(2,15)}function P(){try{const t=localStorage.getItem(y);if(t)return t}catch{}const i=S();try{localStorage.setItem(y,i)}catch{}return i}function B(i){try{const t=JSON.parse(atob(i.split(".")[1]));return typeof t.exp=="number"?t.exp*1e3:null}catch{return null}}async function j(i,t){const e=await fetch("https://directline.botframework.com/v3/directline/tokens/generate",{method:"POST",headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json"},body:JSON.stringify({user:{id:t,name:t}})});if(!e.ok)throw new Error(`DirectLine token fetch failed: ${e.status}`);const n=await e.json();return{token:n.token,conversationId:n.conversationId}}async function F(i){const t=await fetch(i,{method:"POST"});if(!t.ok)throw new Error(`Token generator failed: ${t.status}`);return await t.json()}async function u(i,t){const n=await fetch(`${t??"https://directline.botframework.com/v3/directline"}/tokens/refresh`,{method:"POST",headers:{Authorization:`Bearer ${i}`}});if(!n.ok)throw new Error(`Token refresh failed: ${n.status}`);return await n.json()}class M{constructor(t){this.name="directline",this.addSentToHistory=!0,this.chativaCtx=null,this.messageHandler=null,this.connectHandler=null,this.disconnectHandler=null,this.typingHandler=null,this.messageStatusHandler=null,this.activitySub=null,this.connectionSub=null,this.typingTimeout=null,this.refreshTimer=null,this.pendingIds=[],this.hasConnectedBefore=!1,this.resolveReady=null,this._skipNextJoin=!1,this.options=t}setContext(t){this.chativaCtx=t}addEventHandler(t,e){this.options.eventHandlers??={},this.options.eventHandlers[t]=e}removeEventHandler(t){return this.options.eventHandlers?.[t]?(delete this.options.eventHandlers[t],!0):!1}hasEventHandler(t){return!!this.options.eventHandlers?.[t]}getEventHandlerNames(){return Object.keys(this.options.eventHandlers??{})}async connect(){this.userId=this.options.userId??(this.options.resumeConversation?P():S()),this.userName=this.options.userName;let t=!1;if(this.options.resumeConversation){const n=this.loadPersistedConversation();if(n)try{const s=await u(n.token,this.options.domain);this.token=s.token,this.conversationId=n.conversationId,this.watermark=n.watermark,this.userId=n.userId,t=!0}catch{this.clearPersistedConversation()}}if(!t)if(this.options.tokenGeneratorUrl){const n=await F(this.options.tokenGeneratorUrl);this.token=n.token,n.conversationId&&(this.conversationId=n.conversationId),n.userId&&(this.userId=n.userId)}else if(this.options.token)this.token=this.options.token;else if(this.options.secret){const n=await j(this.options.secret,this.userId);this.token=n.token,this.conversationId=n.conversationId}else throw new Error("DirectLineConnector: provide token, secret, or tokenGeneratorUrl.");this.scheduleTokenRefresh(),this.directLine=new c.DirectLine({token:this.token,domain:this.options.domain,...t?{conversationId:this.conversationId,watermark:this.watermark}:{}}),t&&(this.hasConnectedBefore=!0,this._skipNextJoin=!0);const e=new Promise(n=>{this.resolveReady=n});this.startListening(t),await e,this.connectHandler?.(),this.options.resumeConversation&&this.persistConversation()}async disconnect(){this.activitySub?.unsubscribe(),this.activitySub=null,this.connectionSub?.unsubscribe(),this.connectionSub=null,this.clearTypingTimeout(),this.clearRefreshTimer();try{this.directLine?.end()}catch{}this.messageHandler=null,this.connectHandler=null,this.disconnectHandler=null,this.typingHandler=null,this.messageStatusHandler=null}clearConversation(){this.watermark=void 0,this.clearPersistedConversation()}async sendMessage(t){return this.pendingIds.push(t.id),this.messageStatusHandler?.(t.id,"sent"),new Promise((e,n)=>{this.directLine.postActivity({type:"message",from:{id:this.userId,name:this.userName??this.userId},text:t.data.text??"",conversation:{id:this.conversationId},channelId:"directline",...this.options.locale?{locale:this.options.locale}:{},timestamp:new Date().toISOString(),id:t.id}).subscribe({next:()=>e(),error:s=>n(s)})})}async sendFile(t){const n=`${this.options.domain??"https://directline.botframework.com/v3/directline"}/conversations/${this.conversationId}/upload?userId=${encodeURIComponent(this.userId)}`,s=new FormData;s.append("file",t,t.name);const r=await fetch(n,{method:"POST",headers:{Authorization:`Bearer ${this.token}`},body:s});if(!r.ok)throw new Error(`DirectLine file upload failed: ${r.status}`)}async sendSurvey(t){const e=typeof t.kind=="number"?t.kind:t.kind!==void 0?Number(t.kind):1,n=Number.isFinite(e)?e:1;return new Promise((s,r)=>{this.directLine.postActivity({type:"event",name:"webchat/customerfeedback",from:{id:this.userId,name:this.userId},value:{rating:t.rating,comment:t.comment??"",type:n}}).subscribe({next:()=>s(),error:o=>r(o)})})}async sendFeedback(t,e){const s=this.chativaCtx?.messages.getAll().find(o=>o.id===t)?.data?.channelData?.correlationId;if(!s){console.warn("[DirectLineConnector] sendFeedback: no correlationId found for message",t);return}const r=e==="like"?0:1;return new Promise((o,a)=>{this.directLine.postActivity({type:"event",name:"webchat/messageFeedback",from:{id:this.userId,name:this.userId},value:{correlationId:s,feedbackType:r}}).subscribe({next:()=>o(),error:l=>a(l)})})}async loadHistory(t){const e=this.options.domain??"https://directline.botframework.com/v3/directline",n=t?`${e}/conversations/${this.conversationId}/activities?watermark=${encodeURIComponent(t)}`:`${e}/conversations/${this.conversationId}/activities`,s=await fetch(n,{headers:{Authorization:`Bearer ${this.token}`}});if(!s.ok)throw new Error(`DirectLine history fetch failed: ${s.status}`);const r=await s.json(),o=[];for(const a of r.activities){if(a.from.id===this.userId&&a.type==="message"){const p=a;p.text&&o.push({id:a.id??`dl-${Date.now()}-${Math.random().toString(36).slice(2,7)}`,type:"text",from:"user",data:{text:p.text},timestamp:a.timestamp?new Date(a.timestamp).getTime():Date.now()});continue}const l=v(a,this.userId);l!==null&&l!==f&&o.push(l)}return{messages:o,hasMore:!1,cursor:r.watermark}}onMessage(t){this.messageHandler=t}onConnect(t){this.connectHandler=t}onDisconnect(t){this.disconnectHandler=t}onTyping(t){this.typingHandler=t}onMessageStatus(t){this.messageStatusHandler=t}startListening(t){this.connectionSub=this.directLine.connectionStatus$.subscribe(e=>{switch(e){case c.ConnectionStatus.Online:this.sendJoinEvent(),t&&this.resolveReady&&(this.resolveReady(),this.resolveReady=null);break;case c.ConnectionStatus.ExpiredToken:this.handleExpiredToken();break;case c.ConnectionStatus.FailedToConnect:this.options.resumeConversation&&this.clearPersistedConversation(),this.disconnectHandler?.("Failed to connect");break;case c.ConnectionStatus.Ended:this.disconnectHandler?.("Connection ended");break}}),this.activitySub=this.directLine.activity$.subscribe(e=>{try{if(e.id&&(this.watermark=e.id),!this.conversationId&&e.conversation?.id&&(this.conversationId=e.conversation.id),e.from.id===this.userId){if(e.type==="message"){const s=this.pendingIds.shift();s&&this.messageStatusHandler?.(s,"read")}return}if(e.type==="event"&&e.name){if(e.name==="DisableFeedbackButton"&&e.value){const r=e.value;r.CorrelationId&&this.handleDisableFeedback(r.CorrelationId,r.FeedbackType)}const s=this.options.eventHandlers?.[e.name];s&&s(this.createEventContext(e));return}const n=v(e,this.userId);if(n===f){this.handleTyping();return}n!==null&&(this.clearTypingTimeout(),this.typingHandler?.(!1),this.resolveReady&&(this.resolveReady(),this.resolveReady=null),this.messageHandler?.(n),this.options.resumeConversation&&this.persistConversation())}catch(n){console.warn("[DirectLineConnector] Activity mapping error:",n)}})}createEventContext(t){const e={id:this.userId,name:this.userName??this.userId};return{activity:t,userId:this.userId,userName:this.userName??this.userId,postEvent:(n,s)=>{this.directLine.postActivity({type:"event",name:n,from:e,...this.options.locale?{locale:this.options.locale}:{},value:s}).subscribe()},chativa:this.chativaCtx}}sendJoinEvent(){const t=this.options.locale,e={id:this.userId,name:this.userName??this.userId};if(this.hasConnectedBefore,this._skipNextJoin){this._skipNextJoin=!1,this.hasConnectedBefore=!0,this.directLine.postActivity({type:"event",name:"webchat/rejoin",from:e,...t?{locale:t}:{},value:{...t?{language:t}:{}}}).subscribe();return}this.directLine.postActivity({type:"event",name:"webchat/join",from:e,...t?{locale:t}:{},value:{...t?{language:t}:{},...this.options.joinParameters}}).subscribe()}handleDisableFeedback(t,e){const n=this.chativaCtx?.messages.getAll();if(!n)return;const s=n.find(r=>r.data?.channelData?.correlationId===t);s&&this.chativaCtx.messages.update(s.id,{data:{...s.data,feedbackDisabled:!0,feedbackType:e}})}handleTyping(){if(this.clearTypingTimeout(),this.typingHandler?.(!0),this.options.typingUntilMessage)return;const t=this.options.typingTimeoutMs??3e3;this.typingTimeout=setTimeout(()=>{this.typingHandler?.(!1),this.typingTimeout=null},t)}clearTypingTimeout(){this.typingTimeout!==null&&(clearTimeout(this.typingTimeout),this.typingTimeout=null)}scheduleTokenRefresh(){this.clearRefreshTimer();const t=B(this.token);if(!t)return;const e=t-Date.now()-6e4;if(e<=0){this.refreshTokenNow();return}this.refreshTimer=setTimeout(()=>this.refreshTokenNow(),e)}async refreshTokenNow(){try{const t=await u(this.token,this.options.domain);this.token=t.token,this.scheduleTokenRefresh(),this.options.resumeConversation&&this.persistConversation()}catch(t){console.warn("[DirectLineConnector] Token refresh failed:",t)}}async handleExpiredToken(){try{const t=await u(this.token,this.options.domain);this.token=t.token,this.scheduleTokenRefresh()}catch{this.options.resumeConversation&&this.clearPersistedConversation(),this.disconnectHandler?.("Token expired and refresh failed");return}this.activitySub?.unsubscribe(),this.connectionSub?.unsubscribe(),this.clearTypingTimeout();try{this.directLine.end()}catch{}this.directLine=new c.DirectLine({token:this.token,domain:this.options.domain,conversationId:this.conversationId,watermark:this.watermark}),this.startListening(!0),this.options.resumeConversation&&this.persistConversation()}clearRefreshTimer(){this.refreshTimer!==null&&(clearTimeout(this.refreshTimer),this.refreshTimer=null)}persistConversation(){if(!(!this.conversationId||!this.token))try{const t={conversationId:this.conversationId,token:this.token,watermark:this.watermark,userId:this.userId};localStorage.setItem(h,JSON.stringify(t))}catch{}}loadPersistedConversation(){try{const t=localStorage.getItem(h);return t?JSON.parse(t):null}catch{return null}}clearPersistedConversation(){try{localStorage.removeItem(h)}catch{}}}exports.DirectLineConnector=M;
|
|
4
|
+
`),buttons:r},timestamp:e}:r.length>0?{id:t,type:"buttons",from:"bot",data:{text:o||"Adaptive Card",buttons:r},timestamp:e}:{id:t,type:"text",from:"bot",data:{text:o||"[Adaptive Card]"},timestamp:e}}function w(i,t){for(const e of i){const n=e.title??"Action";e.type==="Action.OpenUrl"&&e.url?t.push({label:n,url:e.url}):e.type==="Action.Submit"?t.push({label:n,value:typeof e.data=="string"?e.data:n}):t.push({label:n,value:n})}}function _(i,t,e){const n=[];if(i.title&&n.push(`**${i.title}**`),i.facts)for(const s of i.facts)n.push(`${s.key}: ${s.value}`);if(i.items)for(const s of i.items){const r=s.price?` — ${s.price}`:"";n.push(`- ${s.title??"Item"}${r}`)}return i.tax&&n.push(`Tax: ${i.tax}`),i.total&&n.push(`**Total: ${i.total}**`),{id:t,type:"text",from:"bot",data:{text:n.join(`
|
|
5
|
+
`)},timestamp:e}}const g="chativa_directline_userId",h="chativa_directline_conversation";function S(){return Math.random().toString(36).slice(2,15)+Math.random().toString(36).slice(2,15)}function O(){try{const t=localStorage.getItem(g);if(t)return t}catch{}const i=S();try{localStorage.setItem(g,i)}catch{}return i}function B(i){try{const t=JSON.parse(atob(i.split(".")[1]));return typeof t.exp=="number"?t.exp*1e3:null}catch{return null}}async function j(i,t){const e=await fetch("https://directline.botframework.com/v3/directline/tokens/generate",{method:"POST",headers:{Authorization:`Bearer ${i}`,"Content-Type":"application/json"},body:JSON.stringify({user:{id:t,name:t}})});if(!e.ok)throw new Error(`DirectLine token fetch failed: ${e.status}`);const n=await e.json();return{token:n.token,conversationId:n.conversationId}}async function F(i){const t=await fetch(i,{method:"POST"});if(!t.ok)throw new Error(`Token generator failed: ${t.status}`);return await t.json()}async function u(i,t){const n=await fetch(`${t??"https://directline.botframework.com/v3/directline"}/tokens/refresh`,{method:"POST",headers:{Authorization:`Bearer ${i}`}});if(!n.ok)throw new Error(`Token refresh failed: ${n.status}`);return await n.json()}class M{constructor(t){this.name="directline",this.addSentToHistory=!0,this.chativaCtx=null,this.messageHandler=null,this.connectHandler=null,this.disconnectHandler=null,this.typingHandler=null,this.messageStatusHandler=null,this.activitySub=null,this.connectionSub=null,this.typingTimeout=null,this.refreshTimer=null,this.pendingIds=[],this.hasConnectedBefore=!1,this.resolveReady=null,this._skipNextJoin=!1,this.options=t}setContext(t){this.chativaCtx=t}addEventHandler(t,e){this.options.eventHandlers??={},this.options.eventHandlers[t]=e}removeEventHandler(t){return this.options.eventHandlers?.[t]?(delete this.options.eventHandlers[t],!0):!1}hasEventHandler(t){return!!this.options.eventHandlers?.[t]}getEventHandlerNames(){return Object.keys(this.options.eventHandlers??{})}async connect(){this.userId=this.options.userId??(this.options.resumeConversation?O():S()),this.userName=this.options.userName;let t=!1;if(this.options.resumeConversation){const n=this.loadPersistedConversation();if(n)try{const s=await u(n.token,this.options.domain);this.token=s.token,this.conversationId=n.conversationId,this.watermark=n.watermark,this.userId=n.userId,t=!0}catch{this.clearPersistedConversation()}}if(!t)if(this.options.tokenGeneratorUrl){const n=await F(this.options.tokenGeneratorUrl);this.token=n.token,n.conversationId&&(this.conversationId=n.conversationId),n.userId&&(this.userId=n.userId)}else if(this.options.token)this.token=this.options.token;else if(this.options.secret){const n=await j(this.options.secret,this.userId);this.token=n.token,this.conversationId=n.conversationId}else throw new Error("DirectLineConnector: provide token, secret, or tokenGeneratorUrl.");this.scheduleTokenRefresh(),this.directLine=new c.DirectLine({token:this.token,domain:this.options.domain,...t?{conversationId:this.conversationId,watermark:this.watermark}:{}}),t&&(this.hasConnectedBefore=!0,this._skipNextJoin=!0);const e=new Promise(n=>{this.resolveReady=n});this.startListening(t),await e,this.connectHandler?.(),this.options.resumeConversation&&this.persistConversation()}async disconnect(){this.activitySub?.unsubscribe(),this.activitySub=null,this.connectionSub?.unsubscribe(),this.connectionSub=null,this.clearTypingTimeout(),this.clearRefreshTimer();try{this.directLine?.end()}catch{}this.messageHandler=null,this.connectHandler=null,this.disconnectHandler=null,this.typingHandler=null,this.messageStatusHandler=null}clearConversation(){this.watermark=void 0,this.clearPersistedConversation()}async sendMessage(t){return this.pendingIds.push(t.id),this.messageStatusHandler?.(t.id,"sent"),new Promise((e,n)=>{this.directLine.postActivity({type:"message",from:{id:this.userId,name:this.userName??this.userId},text:t.data.text??"",conversation:{id:this.conversationId},channelId:"directline",...this.options.locale?{locale:this.options.locale}:{},timestamp:new Date().toISOString(),id:t.id}).subscribe({next:()=>e(),error:s=>n(s)})})}async sendFile(t){const n=`${this.options.domain??"https://directline.botframework.com/v3/directline"}/conversations/${this.conversationId}/upload?userId=${encodeURIComponent(this.userId)}`,s=new FormData;s.append("file",t,t.name);const r=await fetch(n,{method:"POST",headers:{Authorization:`Bearer ${this.token}`},body:s});if(!r.ok)throw new Error(`DirectLine file upload failed: ${r.status}`)}async sendSurvey(t){const e=typeof t.kind=="number"?t.kind:t.kind!==void 0?Number(t.kind):1,n=Number.isFinite(e)?e:1;return new Promise((s,r)=>{this.directLine.postActivity({type:"event",name:"webchat/customerfeedback",from:{id:this.userId,name:this.userId},value:{rating:t.rating,comment:t.comment??"",type:n}}).subscribe({next:()=>s(),error:o=>r(o)})})}async sendFeedback(t,e){const s=this.chativaCtx?.messages.getAll().find(o=>o.id===t)?.data?.channelData?.correlationId;if(!s){console.warn("[DirectLineConnector] sendFeedback: no correlationId found for message",t);return}const r=e==="like"?0:1;return new Promise((o,a)=>{this.directLine.postActivity({type:"event",name:"webchat/messageFeedback",from:{id:this.userId,name:this.userId},value:{correlationId:s,feedbackType:r}}).subscribe({next:()=>o(),error:l=>a(l)})})}async loadHistory(t){const e=this.options.domain??"https://directline.botframework.com/v3/directline",n=t?`${e}/conversations/${this.conversationId}/activities?watermark=${encodeURIComponent(t)}`:`${e}/conversations/${this.conversationId}/activities`,s=await fetch(n,{headers:{Authorization:`Bearer ${this.token}`}});if(!s.ok)throw new Error(`DirectLine history fetch failed: ${s.status}`);const r=await s.json(),o=[];for(const a of r.activities){if(a.from.id===this.userId&&a.type==="message"){const p=a;p.text&&o.push({id:a.id??`dl-${Date.now()}-${Math.random().toString(36).slice(2,7)}`,type:"text",from:"user",data:{text:p.text},timestamp:a.timestamp?new Date(a.timestamp).getTime():Date.now()});continue}const l=v(a,this.userId);l!==null&&l!==f&&o.push(l)}return{messages:o,hasMore:!1,cursor:r.watermark}}onMessage(t){this.messageHandler=t}onConnect(t){this.connectHandler=t}onDisconnect(t){this.disconnectHandler=t}onTyping(t){this.typingHandler=t}onMessageStatus(t){this.messageStatusHandler=t}startListening(t){this.connectionSub=this.directLine.connectionStatus$.subscribe(e=>{switch(e){case c.ConnectionStatus.Online:this.sendJoinEvent(),t&&this.resolveReady&&(this.resolveReady(),this.resolveReady=null);break;case c.ConnectionStatus.ExpiredToken:this.handleExpiredToken();break;case c.ConnectionStatus.FailedToConnect:this.options.resumeConversation&&this.clearPersistedConversation(),this.disconnectHandler?.("Failed to connect");break;case c.ConnectionStatus.Ended:this.disconnectHandler?.("Connection ended");break}}),this.activitySub=this.directLine.activity$.subscribe(e=>{try{if(e.id&&(this.watermark=e.id),!this.conversationId&&e.conversation?.id&&(this.conversationId=e.conversation.id),e.from.id===this.userId)return;if(e.type==="event"&&e.name){if(e.name==="DisableFeedbackButton"&&e.value){const r=e.value;r.CorrelationId&&this.handleDisableFeedback(r.CorrelationId,r.FeedbackType)}const s=this.options.eventHandlers?.[e.name];s&&s(this.createEventContext(e));return}const n=v(e,this.userId);if(n===f){this.handleTyping();return}n!==null&&(this.clearTypingTimeout(),this.typingHandler?.(!1),this.resolveReady&&(this.resolveReady(),this.resolveReady=null),this.flushPendingToRead(),this.messageHandler?.(n),this.options.resumeConversation&&this.persistConversation())}catch(n){console.warn("[DirectLineConnector] Activity mapping error:",n)}})}createEventContext(t){const e={id:this.userId,name:this.userName??this.userId};return{activity:t,userId:this.userId,userName:this.userName??this.userId,postEvent:(n,s)=>{this.directLine.postActivity({type:"event",name:n,from:e,...this.options.locale?{locale:this.options.locale}:{},value:s}).subscribe()},chativa:this.chativaCtx}}sendJoinEvent(){const t=this.options.locale,e={id:this.userId,name:this.userName??this.userId};if(this.hasConnectedBefore,this._skipNextJoin){this._skipNextJoin=!1,this.hasConnectedBefore=!0,this.directLine.postActivity({type:"event",name:"webchat/rejoin",from:e,...t?{locale:t}:{},value:{...t?{language:t}:{}}}).subscribe();return}this.directLine.postActivity({type:"event",name:"webchat/join",from:e,...t?{locale:t}:{},value:{...t?{language:t}:{},...this.options.joinParameters}}).subscribe()}handleDisableFeedback(t,e){const n=this.chativaCtx?.messages.getAll();if(!n)return;const s=n.find(r=>r.data?.channelData?.correlationId===t);s&&this.chativaCtx.messages.update(s.id,{data:{...s.data,feedbackDisabled:!0,feedbackType:e}})}flushPendingToRead(){if(!(!this.messageStatusHandler||this.pendingIds.length===0)){for(const t of this.pendingIds)this.messageStatusHandler(t,"read");this.pendingIds=[]}}handleTyping(){if(this.clearTypingTimeout(),this.typingHandler?.(!0),this.options.typingUntilMessage)return;const t=this.options.typingTimeoutMs??3e3;this.typingTimeout=setTimeout(()=>{this.typingHandler?.(!1),this.typingTimeout=null},t)}clearTypingTimeout(){this.typingTimeout!==null&&(clearTimeout(this.typingTimeout),this.typingTimeout=null)}scheduleTokenRefresh(){this.clearRefreshTimer();const t=B(this.token);if(!t)return;const e=t-Date.now()-6e4;if(e<=0){this.refreshTokenNow();return}this.refreshTimer=setTimeout(()=>this.refreshTokenNow(),e)}async refreshTokenNow(){try{const t=await u(this.token,this.options.domain);this.token=t.token,this.scheduleTokenRefresh(),this.options.resumeConversation&&this.persistConversation()}catch(t){console.warn("[DirectLineConnector] Token refresh failed:",t)}}async handleExpiredToken(){try{const t=await u(this.token,this.options.domain);this.token=t.token,this.scheduleTokenRefresh()}catch{this.options.resumeConversation&&this.clearPersistedConversation(),this.disconnectHandler?.("Token expired and refresh failed");return}this.activitySub?.unsubscribe(),this.connectionSub?.unsubscribe(),this.clearTypingTimeout();try{this.directLine.end()}catch{}this.directLine=new c.DirectLine({token:this.token,domain:this.options.domain,conversationId:this.conversationId,watermark:this.watermark}),this.startListening(!0),this.options.resumeConversation&&this.persistConversation()}clearRefreshTimer(){this.refreshTimer!==null&&(clearTimeout(this.refreshTimer),this.refreshTimer=null)}persistConversation(){if(!(!this.conversationId||!this.token))try{const t={conversationId:this.conversationId,token:this.token,watermark:this.watermark,userId:this.userId};localStorage.setItem(h,JSON.stringify(t))}catch{}}loadPersistedConversation(){try{const t=localStorage.getItem(h);return t?JSON.parse(t):null}catch{return null}}clearPersistedConversation(){try{localStorage.removeItem(h)}catch{}}}exports.DirectLineConnector=M;
|
|
6
6
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/mapActivity.ts","../src/DirectLineConnector.ts"],"sourcesContent":["/**\n * Pure mapping functions that convert DirectLine activities\n * into Chativa IncomingMessage structures.\n *\n * No side-effects — all functions are stateless and easily testable.\n */\n\nimport type {\n Activity,\n Message,\n CardAction,\n Attachment,\n HeroCard,\n Thumbnail,\n Signin,\n Receipt,\n AudioCard,\n VideoCard,\n AnimationCard,\n AdaptiveCard,\n FlexCard,\n} from \"botframework-directlinejs\";\nimport type { IncomingMessage, MessageAction } from \"@chativa/core\";\n\n/* ------------------------------------------------------------------ */\n/* Sentinel returned for typing activities */\n/* ------------------------------------------------------------------ */\n\nexport const TYPING_SENTINEL = Symbol(\"typing\");\nexport type MapResult = IncomingMessage | typeof TYPING_SENTINEL | null;\n\n/* ------------------------------------------------------------------ */\n/* Content-type constants */\n/* ------------------------------------------------------------------ */\n\nconst CT_HERO = \"application/vnd.microsoft.card.hero\";\nconst CT_THUMBNAIL = \"application/vnd.microsoft.card.thumbnail\";\nconst CT_ADAPTIVE = \"application/vnd.microsoft.card.adaptive\";\nconst CT_SIGNIN = \"application/vnd.microsoft.card.signin\";\nconst CT_OAUTH = \"application/vnd.microsoft.card.oauth\";\nconst CT_RECEIPT = \"application/vnd.microsoft.card.receipt\";\nconst CT_AUDIO = \"application/vnd.microsoft.card.audio\";\nconst CT_VIDEO = \"application/vnd.microsoft.card.video\";\nconst CT_ANIMATION = \"application/vnd.microsoft.card.animation\";\nconst CT_FLEX = \"application/vnd.microsoft.card.flex\";\n\n/* ------------------------------------------------------------------ */\n/* Main entry point */\n/* ------------------------------------------------------------------ */\n\n/**\n * Convert a DirectLine Activity into a Chativa IncomingMessage.\n *\n * Returns:\n * - `IncomingMessage` for renderable messages\n * - `TYPING_SENTINEL` for typing indicators (handled by the connector)\n * - `null` for activities that should be ignored (echo, events, etc.)\n */\nexport function mapActivityToMessage(\n activity: Activity,\n userId: string,\n): MapResult {\n // Filter out echoed user messages\n if (activity.from.id === userId) return null;\n\n // Typing indicator\n if (activity.type === \"typing\") return TYPING_SENTINEL;\n\n // Only process message activities\n if (activity.type !== \"message\") return null;\n\n const msg = activity as Message;\n const id = msg.id ?? `dl-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;\n const timestamp = msg.timestamp ? new Date(msg.timestamp).getTime() : Date.now();\n\n // Preserve channelData (e.g. correlationId used for feedback)\n const channelData = (msg as unknown as { channelData?: Record<string, unknown> }).channelData;\n\n // Map suggested actions (may be used as top-level actions or as quick-reply)\n const suggestedActions = mapSuggestedActions(msg);\n\n // If there are attachments, process them\n if (msg.attachments && msg.attachments.length > 0) {\n const result = mapAttachments(msg, id, timestamp);\n if (result) {\n if (suggestedActions.length > 0) result.actions = suggestedActions;\n if (channelData) result.data.channelData = channelData;\n }\n return result;\n }\n\n // Text + suggested actions → quick-reply\n if (suggestedActions.length > 0) {\n return {\n id,\n type: \"quick-reply\",\n from: \"bot\",\n data: { text: msg.text ?? \"\", actions: suggestedActions, ...(channelData ? { channelData } : {}) },\n timestamp,\n };\n }\n\n // Plain text\n if (msg.text) {\n return {\n id,\n type: \"text\",\n from: \"bot\",\n data: { text: msg.text, ...(channelData ? { channelData } : {}) },\n timestamp,\n };\n }\n\n // Nothing meaningful\n return null;\n}\n\n/* ------------------------------------------------------------------ */\n/* Attachment mapping */\n/* ------------------------------------------------------------------ */\n\nfunction mapAttachments(\n msg: Message,\n id: string,\n timestamp: number,\n): IncomingMessage | null {\n const attachments = msg.attachments!;\n const layout = msg.attachmentLayout;\n\n // Multiple cards with carousel layout\n const cardTypes = [CT_HERO, CT_THUMBNAIL, CT_FLEX, CT_ADAPTIVE];\n const allCards = attachments.every((a) => cardTypes.includes(a.contentType));\n\n if (allCards && (attachments.length > 1 || layout === \"carousel\")) {\n return {\n id,\n type: \"carousel\",\n from: \"bot\",\n data: {\n cards: attachments.map((a) => {\n if (a.contentType === CT_ADAPTIVE) {\n return adaptiveCardToCardData(\n (a as AdaptiveCard).content as Record<string, unknown>,\n );\n }\n return mapHeroLikeCard((a as HeroCard | Thumbnail | FlexCard).content);\n }),\n },\n timestamp,\n };\n }\n\n // Single attachment\n return mapSingleAttachment(attachments[0], msg, id, timestamp);\n}\n\nfunction mapSingleAttachment(\n att: Attachment,\n msg: Message,\n id: string,\n timestamp: number,\n): IncomingMessage | null {\n const ct = att.contentType;\n\n // Hero / Thumbnail / Flex card\n if (ct === CT_HERO || ct === CT_THUMBNAIL || ct === CT_FLEX) {\n const content = (att as HeroCard | Thumbnail | FlexCard).content;\n return {\n id,\n type: \"card\",\n from: \"bot\",\n data: mapHeroLikeCard(content),\n timestamp,\n };\n }\n\n // Adaptive card — simple parse\n if (ct === CT_ADAPTIVE) {\n return mapAdaptiveCard((att as AdaptiveCard).content, id, timestamp);\n }\n\n // Signin / OAuth card\n if (ct === CT_SIGNIN || ct === CT_OAUTH) {\n const content = (att as Signin).content;\n return {\n id,\n type: \"buttons\",\n from: \"bot\",\n data: {\n text: content.text ?? \"Please sign in\",\n buttons: mapCardButtons(content.buttons),\n },\n timestamp,\n };\n }\n\n // Receipt card\n if (ct === CT_RECEIPT) {\n return mapReceiptCard((att as Receipt).content, id, timestamp);\n }\n\n // Video card\n if (ct === CT_VIDEO) {\n const content = (att as VideoCard).content;\n const mediaUrl = content.media?.[0]?.url;\n if (!mediaUrl) return null;\n return {\n id,\n type: \"video\",\n from: \"bot\",\n data: {\n src: mediaUrl,\n poster: content.image?.url,\n caption: content.title ?? msg.text,\n },\n timestamp,\n };\n }\n\n // Audio card\n if (ct === CT_AUDIO) {\n const content = (att as AudioCard).content;\n const mediaUrl = content.media?.[0]?.url;\n if (!mediaUrl) return null;\n return {\n id,\n type: \"file\",\n from: \"bot\",\n data: {\n url: mediaUrl,\n name: content.title ?? \"audio\",\n mimeType: \"audio/mpeg\",\n },\n timestamp,\n };\n }\n\n // Animation card (typically GIFs)\n if (ct === CT_ANIMATION) {\n const content = (att as AnimationCard).content;\n const mediaUrl = content.media?.[0]?.url;\n if (!mediaUrl) return null;\n return {\n id,\n type: \"image\",\n from: \"bot\",\n data: {\n src: mediaUrl,\n caption: content.title ?? msg.text,\n },\n timestamp,\n };\n }\n\n // Image attachment (contentType starts with \"image/\")\n if (ct.startsWith(\"image/\") && \"contentUrl\" in att) {\n return {\n id,\n type: \"image\",\n from: \"bot\",\n data: {\n src: att.contentUrl,\n alt: att.name,\n caption: msg.text,\n },\n timestamp,\n };\n }\n\n // Video file attachment\n if (ct.startsWith(\"video/\") && \"contentUrl\" in att) {\n return {\n id,\n type: \"video\",\n from: \"bot\",\n data: {\n src: att.contentUrl,\n caption: msg.text,\n },\n timestamp,\n };\n }\n\n // Generic file attachment\n if (\"contentUrl\" in att) {\n return {\n id,\n type: \"file\",\n from: \"bot\",\n data: {\n url: att.contentUrl,\n name: att.name ?? \"file\",\n mimeType: ct,\n },\n timestamp,\n };\n }\n\n return null;\n}\n\n/* ------------------------------------------------------------------ */\n/* Hero-like card mapping (hero, thumbnail, flex share same shape) */\n/* ------------------------------------------------------------------ */\n\nfunction mapHeroLikeCard(content: HeroCard[\"content\"]): Record<string, unknown> {\n return {\n image: content.images?.[0]?.url,\n title: content.title ?? \"\",\n subtitle: content.subtitle ?? content.text,\n buttons: mapCardButtons(content.buttons),\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Card button / action mapping */\n/* ------------------------------------------------------------------ */\n\nexport function mapCardButtons(\n buttons?: CardAction[],\n): MessageAction[] {\n if (!buttons || buttons.length === 0) return [];\n\n return buttons.map((btn): MessageAction => {\n const label = (\"title\" in btn ? btn.title : undefined) ?? String(btn.value ?? \"\");\n\n switch (btn.type) {\n case \"openUrl\":\n case \"signin\":\n return { label, url: String(btn.value ?? \"\") };\n\n case \"call\":\n return { label, url: String(btn.value ?? \"\") };\n\n case \"imBack\":\n case \"postBack\":\n case \"messageBack\":\n default:\n return { label, value: String(btn.value ?? label) };\n }\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* Suggested actions mapping */\n/* ------------------------------------------------------------------ */\n\nfunction mapSuggestedActions(msg: Message): MessageAction[] {\n const actions = msg.suggestedActions?.actions;\n if (!actions || actions.length === 0) return [];\n\n return actions.map((a): MessageAction => {\n const label = (\"title\" in a ? a.title : undefined) ?? String(a.value ?? \"\");\n if (a.type === \"openUrl\") {\n return { label, url: String(a.value ?? \"\") };\n }\n return { label, value: String(a.value ?? label) };\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* Adaptive Card — simple parse */\n/* ------------------------------------------------------------------ */\n\ninterface AdaptiveElement {\n type: string;\n text?: string;\n url?: string;\n body?: AdaptiveElement[];\n items?: AdaptiveElement[];\n columns?: Array<{ items?: AdaptiveElement[] }>;\n actions?: Array<{ type: string; title?: string; url?: string; data?: unknown }>;\n [key: string]: unknown;\n}\n\n/** Recursively walk Adaptive Card elements and collect texts, the first image, and buttons. */\nfunction walkAdaptiveElements(\n elements: AdaptiveElement[] | undefined,\n texts: string[],\n buttons: MessageAction[],\n onImage: (url: string) => void,\n) {\n if (!elements) return;\n for (const el of elements) {\n switch (el.type) {\n case \"TextBlock\":\n if (el.text) texts.push(el.text);\n break;\n case \"Image\":\n if (el.url) onImage(el.url);\n break;\n case \"ActionSet\":\n if (el.actions) mapAdaptiveActions(el.actions, buttons);\n break;\n case \"Container\":\n walkAdaptiveElements(el.items, texts, buttons, onImage);\n break;\n case \"ColumnSet\":\n for (const col of el.columns ?? []) {\n walkAdaptiveElements(col.items, texts, buttons, onImage);\n }\n break;\n case \"Column\":\n walkAdaptiveElements(el.items, texts, buttons, onImage);\n break;\n }\n }\n}\n\n/** Parse an Adaptive Card body into { image, title, subtitle, buttons }. */\nfunction parseAdaptiveCardBody(content: Record<string, unknown>) {\n const texts: string[] = [];\n let image: string | undefined;\n const buttons: MessageAction[] = [];\n\n walkAdaptiveElements(\n content.body as AdaptiveElement[] | undefined,\n texts,\n buttons,\n (url) => { image = image ?? url; },\n );\n\n if (Array.isArray(content.actions)) {\n mapAdaptiveActions(content.actions as AdaptiveElement[\"actions\"] & object, buttons);\n }\n\n return { texts, image, buttons };\n}\n\n/**\n * Extract card-like data from an Adaptive Card — used inside carousels.\n */\nfunction adaptiveCardToCardData(\n content: Record<string, unknown>,\n): Record<string, unknown> {\n const { texts, image, buttons } = parseAdaptiveCardBody(content);\n return {\n image,\n title: texts[0] ?? \"\",\n subtitle: texts.slice(1).join(\"\\n\"),\n buttons,\n };\n}\n\nfunction mapAdaptiveCard(\n content: Record<string, unknown>,\n id: string,\n timestamp: number,\n): IncomingMessage {\n const { texts, image, buttons } = parseAdaptiveCardBody(content);\n\n // Fallback text\n const fallback =\n (content.fallbackText as string) ??\n (content.speak as string) ??\n texts.join(\"\\n\");\n\n // If we have an image and a title, render as card\n if (image) {\n return {\n id,\n type: \"card\",\n from: \"bot\",\n data: {\n image,\n title: texts[0] ?? \"\",\n subtitle: texts.slice(1).join(\"\\n\"),\n buttons,\n },\n timestamp,\n };\n }\n\n // If we have buttons, render as buttons message\n if (buttons.length > 0) {\n return {\n id,\n type: \"buttons\",\n from: \"bot\",\n data: {\n text: fallback || \"Adaptive Card\",\n buttons,\n },\n timestamp,\n };\n }\n\n // Plain text fallback\n return {\n id,\n type: \"text\",\n from: \"bot\",\n data: { text: fallback || \"[Adaptive Card]\" },\n timestamp,\n };\n}\n\nfunction mapAdaptiveActions(\n actions: Array<{ type: string; title?: string; url?: string; data?: unknown }>,\n out: MessageAction[],\n) {\n for (const a of actions) {\n const label = a.title ?? \"Action\";\n if (a.type === \"Action.OpenUrl\" && a.url) {\n out.push({ label, url: a.url });\n } else if (a.type === \"Action.Submit\") {\n out.push({ label, value: typeof a.data === \"string\" ? a.data : label });\n } else {\n out.push({ label, value: label });\n }\n }\n}\n\n/* ------------------------------------------------------------------ */\n/* Receipt card mapping */\n/* ------------------------------------------------------------------ */\n\nfunction mapReceiptCard(\n content: Receipt[\"content\"],\n id: string,\n timestamp: number,\n): IncomingMessage {\n const lines: string[] = [];\n if (content.title) lines.push(`**${content.title}**`);\n\n if (content.facts) {\n for (const fact of content.facts) {\n lines.push(`${fact.key}: ${fact.value}`);\n }\n }\n\n if (content.items) {\n for (const item of content.items) {\n const price = item.price ? ` — ${item.price}` : \"\";\n lines.push(`- ${item.title ?? \"Item\"}${price}`);\n }\n }\n\n if (content.tax) lines.push(`Tax: ${content.tax}`);\n if (content.total) lines.push(`**Total: ${content.total}**`);\n\n return {\n id,\n type: \"text\",\n from: \"bot\",\n data: { text: lines.join(\"\\n\") },\n timestamp,\n };\n}\n","import type {\n IConnector,\n MessageHandler,\n ConnectHandler,\n DisconnectHandler,\n TypingHandler,\n MessageStatusHandler,\n ChativaContext,\n SurveyPayload,\n} from \"@chativa/core\";\nimport type {\n OutgoingMessage,\n HistoryResult,\n IncomingMessage,\n} from \"@chativa/core\";\nimport { DirectLine, ConnectionStatus } from \"botframework-directlinejs\";\nimport type { Activity } from \"botframework-directlinejs\";\nimport { mapActivityToMessage, TYPING_SENTINEL } from \"./mapActivity\";\n\n/** Minimal subscription interface returned by RxJS5 Observable.subscribe(). */\ninterface Unsubscribable {\n unsubscribe(): void;\n}\n\n/**\n * Context passed to custom event handlers — provides the info and methods\n * needed to respond to a bot-initiated event activity.\n */\nexport interface EventHandlerContext {\n /** The raw DirectLine event activity. */\n activity: Activity;\n /** The user ID for this session. */\n userId: string;\n /** The user display name for this session. */\n userName: string;\n /** Send an event activity back to the bot. */\n postEvent(name: string, value?: unknown): void;\n /**\n * Full Chativa context — access messages, chat UI, theme, and event bus.\n * Available when the connector is used via ChatEngine (which injects it).\n */\n chativa: ChativaContext;\n}\n\n/**\n * Handler for a bot-initiated event activity.\n * Keyed by event name (e.g. `\"LocationRequest\"`).\n */\nexport type EventHandler = (ctx: EventHandlerContext) => void;\n\nexport interface DirectLineConnectorOptions {\n /** DirectLine channel secret (server-side only — generates a token). */\n secret?: string;\n /** Pre-fetched DirectLine token. */\n token?: string;\n /** URL of a custom token-generating endpoint (POST, returns { token, conversationId? }). */\n tokenGeneratorUrl?: string;\n /**\n * Override the user ID. When omitted, a random per-instance ID is\n * generated — unless `resumeConversation` is true, in which case the ID\n * is persisted to localStorage so reloads can rejoin the same\n * conversation.\n */\n userId?: string;\n /** Display name sent with activities. Defaults to the (resolved) userId. */\n userName?: string;\n /** Sovereign-cloud DirectLine endpoint (e.g. government, china). */\n domain?: string;\n /** BCP-47 locale sent with the webchat/join event and outgoing activities (e.g. \"tr-TR\"). */\n locale?: string;\n /**\n * Extra key-value pairs merged into the `webchat/join` event's `value` payload.\n * Example: `{ language: \"tr\", tenant: \"galataport\" }`\n */\n joinParameters?: Record<string, unknown>;\n /**\n * Custom handlers for bot-initiated event activities, keyed by event name.\n * Example:\n * ```ts\n * eventHandlers: {\n * LocationRequest: (ctx) => {\n * navigator.geolocation.getCurrentPosition((pos) => {\n * ctx.postEvent(\"webchat/location\", {\n * latitude: pos.coords.latitude,\n * longitude: pos.coords.longitude,\n * });\n * });\n * },\n * }\n * ```\n */\n eventHandlers?: Record<string, EventHandler>;\n /**\n * When true, persist conversation state (conversationId, token, watermark)\n * and the auto-generated userId to localStorage so the conversation can\n * be resumed across page reloads. Default: `false` — each new connector\n * instance gets a fresh userId and opens a new conversation.\n */\n resumeConversation?: boolean;\n /**\n * How long (ms) to keep the typing indicator visible after a bot typing\n * signal before auto-clearing it. Each new typing signal resets the timer.\n * Default: 3000. Ignored when `typingUntilMessage` is true.\n */\n typingTimeoutMs?: number;\n /**\n * When true, the typing indicator stays on until the next bot message\n * arrives (no auto-clear timeout).\n */\n typingUntilMessage?: boolean;\n}\n\n/* ── Constants ────────────────────────────────────────────────────── */\n\nconst USER_ID_KEY = \"chativa_directline_userId\";\nconst CONVERSATION_KEY = \"chativa_directline_conversation\";\n\ninterface PersistedConversation {\n conversationId: string;\n token: string;\n watermark?: string;\n userId: string;\n}\n\n/* ── Module-level helpers ─────────────────────────────────────────── */\n\nfunction randomUserId(): string {\n return (\n Math.random().toString(36).slice(2, 15) +\n Math.random().toString(36).slice(2, 15)\n );\n}\n\n/** Read a persisted userId for conversation-resume mode, generating and\n * storing one on first use. Only called when `resumeConversation` is true. */\nfunction getOrCreateUserId(): string {\n try {\n const stored = localStorage.getItem(USER_ID_KEY);\n if (stored) return stored;\n } catch {\n // localStorage unavailable (e.g. incognito, SSR)\n }\n const id = randomUserId();\n try {\n localStorage.setItem(USER_ID_KEY, id);\n } catch {\n // best-effort\n }\n return id;\n}\n\n/** Extract expiry time (ms since epoch) from a JWT token. */\nfunction getTokenExpiry(token: string): number | null {\n try {\n const payload = JSON.parse(atob(token.split(\".\")[1]));\n return typeof payload.exp === \"number\" ? payload.exp * 1000 : null;\n } catch {\n return null;\n }\n}\n\nasync function fetchToken(\n secret: string,\n userId: string,\n): Promise<{ token: string; conversationId: string }> {\n const res = await fetch(\n \"https://directline.botframework.com/v3/directline/tokens/generate\",\n {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${secret}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ user: { id: userId, name: userId } }),\n },\n );\n if (!res.ok) {\n throw new Error(`DirectLine token fetch failed: ${res.status}`);\n }\n const data = (await res.json()) as {\n token: string;\n conversationId: string;\n };\n return { token: data.token, conversationId: data.conversationId };\n}\n\nasync function fetchTokenFromUrl(\n url: string,\n): Promise<{ token: string; conversationId?: string; userId?: string }> {\n const res = await fetch(url, { method: \"POST\" });\n if (!res.ok) {\n throw new Error(`Token generator failed: ${res.status}`);\n }\n return (await res.json()) as {\n token: string;\n conversationId?: string;\n userId?: string;\n };\n}\n\n/** Refresh an existing DirectLine token via the REST API. */\nasync function refreshDirectLineToken(\n currentToken: string,\n domain?: string,\n): Promise<{ token: string; conversationId: string }> {\n const base = domain ?? \"https://directline.botframework.com/v3/directline\";\n const res = await fetch(`${base}/tokens/refresh`, {\n method: \"POST\",\n headers: { Authorization: `Bearer ${currentToken}` },\n });\n if (!res.ok) {\n throw new Error(`Token refresh failed: ${res.status}`);\n }\n return (await res.json()) as { token: string; conversationId: string };\n}\n\n/**\n * DirectLineConnector — Azure Bot Framework DirectLine v3 adapter.\n *\n * Maps all Bot Framework activity types (hero cards, carousels,\n * suggested actions, images, videos, files, adaptive cards, etc.)\n * into Chativa's native message types.\n */\nexport class DirectLineConnector implements IConnector {\n readonly name = \"directline\";\n readonly addSentToHistory = true;\n\n private directLine!: DirectLine;\n private conversationId!: string;\n private userId!: string;\n private userName: string | undefined;\n private token!: string;\n private options: DirectLineConnectorOptions;\n private chativaCtx: ChativaContext | null = null;\n\n private messageHandler: MessageHandler | null = null;\n private connectHandler: ConnectHandler | null = null;\n private disconnectHandler: DisconnectHandler | null = null;\n private typingHandler: TypingHandler | null = null;\n private messageStatusHandler: MessageStatusHandler | null = null;\n\n private activitySub: Unsubscribable | null = null;\n private connectionSub: Unsubscribable | null = null;\n private typingTimeout: ReturnType<typeof setTimeout> | null = null;\n private refreshTimer: ReturnType<typeof setTimeout> | null = null;\n /** Queue of sent message IDs awaiting echo confirmation. */\n private pendingIds: string[] = [];\n /** True after the first successful connection (reserved for future join-vs-rejoin logic). */\n private hasConnectedBefore = false;\n /** Resolves the connect() promise once the bot sends its first message after join. */\n private resolveReady: (() => void) | null = null;\n /** Last known activity watermark — used for conversation resume. */\n private watermark: string | undefined;\n /** When true, skip the next join/rejoin event (used during resume). */\n private _skipNextJoin = false;\n\n constructor(options: DirectLineConnectorOptions) {\n this.options = options;\n }\n\n setContext(ctx: ChativaContext): void {\n this.chativaCtx = ctx;\n }\n\n /**\n * Register (or replace) an event handler for a bot-initiated event activity.\n * Can be called before or after `connect()`.\n *\n * @example\n * ```ts\n * connector.addEventHandler(\"LocationRequest\", (ctx) => {\n * navigator.geolocation.getCurrentPosition((pos) => {\n * ctx.postEvent(\"webchat/location\", { latitude: pos.coords.latitude, ... });\n * });\n * });\n * ```\n */\n addEventHandler(name: string, handler: EventHandler): void {\n this.options.eventHandlers ??= {};\n this.options.eventHandlers[name] = handler;\n }\n\n /** Remove a previously registered event handler by name. */\n removeEventHandler(name: string): boolean {\n if (!this.options.eventHandlers?.[name]) return false;\n delete this.options.eventHandlers[name];\n return true;\n }\n\n /** Check whether an event handler is registered for the given name. */\n hasEventHandler(name: string): boolean {\n return !!this.options.eventHandlers?.[name];\n }\n\n /** Return the names of all registered event handlers. */\n getEventHandlerNames(): string[] {\n return Object.keys(this.options.eventHandlers ?? {});\n }\n\n async connect(): Promise<void> {\n this.userId =\n this.options.userId ??\n (this.options.resumeConversation\n ? getOrCreateUserId()\n : randomUserId());\n this.userName = this.options.userName;\n\n // ── Try to resume a persisted conversation ────────────────────\n let isResuming = false;\n if (this.options.resumeConversation) {\n const persisted = this.loadPersistedConversation();\n if (persisted) {\n try {\n const refreshed = await refreshDirectLineToken(\n persisted.token,\n this.options.domain,\n );\n this.token = refreshed.token;\n this.conversationId = persisted.conversationId;\n this.watermark = persisted.watermark;\n this.userId = persisted.userId;\n isResuming = true;\n } catch {\n // Persisted token expired or invalid — start fresh\n this.clearPersistedConversation();\n }\n }\n }\n\n // ── Acquire token (fresh conversation) ────────────────────────\n if (!isResuming) {\n if (this.options.tokenGeneratorUrl) {\n const result = await fetchTokenFromUrl(\n this.options.tokenGeneratorUrl,\n );\n this.token = result.token;\n if (result.conversationId)\n this.conversationId = result.conversationId;\n if (result.userId) this.userId = result.userId;\n } else if (this.options.token) {\n this.token = this.options.token;\n } else if (this.options.secret) {\n const result = await fetchToken(\n this.options.secret,\n this.userId,\n );\n this.token = result.token;\n this.conversationId = result.conversationId;\n } else {\n throw new Error(\n \"DirectLineConnector: provide token, secret, or tokenGeneratorUrl.\",\n );\n }\n }\n\n // ── Schedule automatic token refresh ──────────────────────────\n this.scheduleTokenRefresh();\n\n // ── Create DirectLine instance ────────────────────────────────\n this.directLine = new DirectLine({\n token: this.token,\n domain: this.options.domain,\n ...(isResuming\n ? {\n conversationId: this.conversationId,\n watermark: this.watermark,\n }\n : {}),\n });\n\n if (isResuming) {\n this.hasConnectedBefore = true;\n this._skipNextJoin = true;\n }\n\n // ── Wait for connection ───────────────────────────────────────\n const ready = new Promise<void>((resolve) => {\n this.resolveReady = resolve;\n });\n\n this.startListening(isResuming);\n\n await ready;\n this.connectHandler?.();\n\n if (this.options.resumeConversation) {\n this.persistConversation();\n }\n }\n\n async disconnect(): Promise<void> {\n this.activitySub?.unsubscribe();\n this.activitySub = null;\n this.connectionSub?.unsubscribe();\n this.connectionSub = null;\n this.clearTypingTimeout();\n this.clearRefreshTimer();\n\n try {\n this.directLine?.end();\n } catch {\n // DirectLine.end() may throw if already ended\n }\n\n this.messageHandler = null;\n this.connectHandler = null;\n this.disconnectHandler = null;\n this.typingHandler = null;\n this.messageStatusHandler = null;\n }\n\n /** Clear persisted conversation state and reset watermark. */\n clearConversation(): void {\n this.watermark = undefined;\n this.clearPersistedConversation();\n }\n\n async sendMessage(message: OutgoingMessage): Promise<void> {\n this.pendingIds.push(message.id);\n // Mark as \"sent\" immediately so it shows before the echo arrives\n this.messageStatusHandler?.(message.id, \"sent\");\n return new Promise<void>((resolve, reject) => {\n this.directLine\n .postActivity({\n type: \"message\",\n from: {\n id: this.userId,\n name: this.userName ?? this.userId,\n },\n text: (message.data as { text?: string }).text ?? \"\",\n conversation: { id: this.conversationId },\n channelId: \"directline\",\n ...(this.options.locale\n ? { locale: this.options.locale }\n : {}),\n timestamp: new Date().toISOString(),\n id: message.id,\n })\n .subscribe({\n next: () => resolve(),\n error: (err: unknown) => reject(err),\n });\n });\n }\n\n async sendFile(file: File): Promise<void> {\n const domain =\n this.options.domain ??\n \"https://directline.botframework.com/v3/directline\";\n const url = `${domain}/conversations/${this.conversationId}/upload?userId=${encodeURIComponent(this.userId)}`;\n\n const formData = new FormData();\n formData.append(\"file\", file, file.name);\n\n const res = await fetch(url, {\n method: \"POST\",\n headers: { Authorization: `Bearer ${this.token}` },\n body: formData,\n });\n\n if (!res.ok) {\n throw new Error(`DirectLine file upload failed: ${res.status}`);\n }\n }\n\n /**\n * Submit an end-of-conversation survey as a DirectLine event activity.\n * Matches the legacy `webchat/customerfeedback` event shape so existing\n * bot flows keep working:\n * value: { rating, comment, type }\n * where `type` is `kind` coerced to a number (defaulting to 1) to preserve\n * compatibility with bots that switch on numeric survey types.\n */\n async sendSurvey(payload: SurveyPayload): Promise<void> {\n const rawType =\n typeof payload.kind === \"number\"\n ? payload.kind\n : payload.kind !== undefined\n ? Number(payload.kind)\n : 1;\n const type = Number.isFinite(rawType) ? rawType : 1;\n\n return new Promise<void>((resolve, reject) => {\n this.directLine\n .postActivity({\n type: \"event\",\n name: \"webchat/customerfeedback\",\n from: { id: this.userId, name: this.userId },\n value: {\n rating: payload.rating,\n comment: payload.comment ?? \"\",\n type,\n },\n })\n .subscribe({\n next: () => resolve(),\n error: (err: unknown) => reject(err),\n });\n });\n }\n\n async sendFeedback(\n messageId: string,\n feedback: \"like\" | \"dislike\",\n ): Promise<void> {\n // Look up the correlationId from the stored message's channelData\n const msg = this.chativaCtx?.messages\n .getAll()\n .find((m) => m.id === messageId);\n const correlationId = (\n msg?.data?.channelData as Record<string, unknown> | undefined\n )?.correlationId;\n\n if (!correlationId) {\n console.warn(\n \"[DirectLineConnector] sendFeedback: no correlationId found for message\",\n messageId,\n );\n return;\n }\n\n // Bot expects numeric: 0 = like, 1 = dislike\n const feedbackType = feedback === \"like\" ? 0 : 1;\n\n return new Promise<void>((resolve, reject) => {\n this.directLine\n .postActivity({\n type: \"event\",\n name: \"webchat/messageFeedback\",\n from: { id: this.userId, name: this.userId },\n value: { correlationId, feedbackType },\n })\n .subscribe({\n next: () => resolve(),\n error: (err: unknown) => reject(err),\n });\n });\n }\n\n async loadHistory(cursor?: string): Promise<HistoryResult> {\n const domain =\n this.options.domain ??\n \"https://directline.botframework.com/v3/directline\";\n const url = cursor\n ? `${domain}/conversations/${this.conversationId}/activities?watermark=${encodeURIComponent(cursor)}`\n : `${domain}/conversations/${this.conversationId}/activities`;\n\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${this.token}` },\n });\n\n if (!res.ok) {\n throw new Error(`DirectLine history fetch failed: ${res.status}`);\n }\n\n const data = (await res.json()) as {\n activities: Activity[];\n watermark: string;\n };\n\n const messages: IncomingMessage[] = [];\n for (const activity of data.activities) {\n // User's own messages — mapActivityToMessage filters these out (live dedup),\n // but history needs them.\n if (\n activity.from.id === this.userId &&\n activity.type === \"message\"\n ) {\n const msg = activity as Activity & { text?: string };\n if (msg.text) {\n messages.push({\n id:\n activity.id ??\n `dl-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,\n type: \"text\",\n from: \"user\",\n data: { text: msg.text },\n timestamp: activity.timestamp\n ? new Date(activity.timestamp).getTime()\n : Date.now(),\n });\n }\n continue;\n }\n\n const result = mapActivityToMessage(activity, this.userId);\n if (result !== null && result !== TYPING_SENTINEL) {\n messages.push(result);\n }\n }\n\n return {\n messages,\n hasMore: false,\n cursor: data.watermark,\n };\n }\n\n onMessage(callback: MessageHandler): void {\n this.messageHandler = callback;\n }\n\n onConnect(callback: ConnectHandler): void {\n this.connectHandler = callback;\n }\n\n onDisconnect(callback: DisconnectHandler): void {\n this.disconnectHandler = callback;\n }\n\n onTyping(callback: TypingHandler): void {\n this.typingHandler = callback;\n }\n\n onMessageStatus(callback: MessageStatusHandler): void {\n this.messageStatusHandler = callback;\n }\n\n /* ── Private helpers ───────────────────────────────────────────── */\n\n /**\n * Subscribe to DirectLine connectionStatus$ and activity$ observables.\n * Extracted so it can be re-used after an ExpiredToken reconnect.\n */\n private startListening(resolveOnOnline: boolean) {\n this.connectionSub = this.directLine.connectionStatus$.subscribe(\n (status: ConnectionStatus) => {\n switch (status) {\n case ConnectionStatus.Online:\n this.sendJoinEvent();\n // Resumed / reconnected conversations resolve immediately\n if (resolveOnOnline && this.resolveReady) {\n this.resolveReady();\n this.resolveReady = null;\n }\n break;\n case ConnectionStatus.ExpiredToken:\n this.handleExpiredToken();\n break;\n case ConnectionStatus.FailedToConnect:\n if (this.options.resumeConversation) {\n this.clearPersistedConversation();\n }\n this.disconnectHandler?.(\"Failed to connect\");\n break;\n case ConnectionStatus.Ended:\n this.disconnectHandler?.(\"Connection ended\");\n break;\n }\n },\n );\n\n this.activitySub = this.directLine.activity$.subscribe((activity) => {\n try {\n // Track watermark and conversationId from every activity\n if (activity.id) this.watermark = activity.id;\n if (!this.conversationId && activity.conversation?.id) {\n this.conversationId = activity.conversation.id;\n }\n\n // Skip own echoed activities\n if (activity.from.id === this.userId) {\n // Echoed user message → mark the original as \"read\"\n if (activity.type === \"message\") {\n const pendingId = this.pendingIds.shift();\n if (pendingId) {\n this.messageStatusHandler?.(pendingId, \"read\");\n }\n }\n return;\n }\n\n // Bot-initiated event\n if (activity.type === \"event\" && activity.name) {\n // Built-in: DisableFeedbackButton — mark the message as feedback-sent\n if (\n activity.name === \"DisableFeedbackButton\" &&\n activity.value\n ) {\n const val = activity.value as {\n CorrelationId?: string;\n FeedbackType?: number;\n };\n if (val.CorrelationId) {\n this.handleDisableFeedback(\n val.CorrelationId,\n val.FeedbackType,\n );\n }\n }\n\n // Dispatch to custom event handler (if registered)\n const handler = this.options.eventHandlers?.[activity.name];\n if (handler) {\n handler(this.createEventContext(activity));\n }\n return;\n }\n\n const result = mapActivityToMessage(activity, this.userId);\n\n if (result === TYPING_SENTINEL) {\n this.handleTyping();\n return;\n }\n\n if (result !== null) {\n // Clear typing indicator when a message arrives\n this.clearTypingTimeout();\n this.typingHandler?.(false);\n\n // First bot message → resolve connect() and signal \"connected\"\n if (this.resolveReady) {\n this.resolveReady();\n this.resolveReady = null;\n }\n\n this.messageHandler?.(result);\n\n // Persist state after delivering a message\n if (this.options.resumeConversation) {\n this.persistConversation();\n }\n }\n } catch (err) {\n console.warn(\n \"[DirectLineConnector] Activity mapping error:\",\n err,\n );\n }\n });\n }\n\n /** Build the context object passed to custom event handlers. */\n private createEventContext(activity: Activity): EventHandlerContext {\n const from = { id: this.userId, name: this.userName ?? this.userId };\n return {\n activity,\n userId: this.userId,\n userName: this.userName ?? this.userId,\n postEvent: (name: string, value?: unknown) => {\n this.directLine\n .postActivity({\n type: \"event\",\n name,\n from,\n ...(this.options.locale\n ? { locale: this.options.locale }\n : {}),\n value,\n })\n .subscribe();\n },\n chativa: this.chativaCtx!,\n };\n }\n\n /** Send webchat/join (first connect) or webchat/rejoin (reconnect) event. */\n private sendJoinEvent() {\n const locale = this.options.locale;\n const from = { id: this.userId, name: this.userName ?? this.userId };\n // Read reserved for future join-vs-rejoin branching; keeps the field alive.\n void this.hasConnectedBefore;\n\n // Skip join/rejoin when resuming a persisted conversation\n if (this._skipNextJoin) {\n this._skipNextJoin = false;\n this.hasConnectedBefore = true;\n this.directLine\n .postActivity({\n type: \"event\",\n name: \"webchat/rejoin\",\n from,\n ...(locale ? { locale } : {}),\n value: { ...(locale ? { language: locale } : {}) },\n })\n .subscribe();\n return;\n }\n this.directLine\n .postActivity({\n type: \"event\",\n name: \"webchat/join\",\n from,\n ...(locale ? { locale } : {}),\n value: {\n ...(locale ? { language: locale } : {}),\n ...this.options.joinParameters,\n },\n })\n .subscribe();\n }\n\n /** Handle DisableFeedbackButton event — find the message by correlationId and patch it. */\n private handleDisableFeedback(\n correlationId: string,\n feedbackType?: number,\n ) {\n const messages = this.chativaCtx?.messages.getAll();\n if (!messages) return;\n const msg = messages.find(\n (m) =>\n (m.data?.channelData as Record<string, unknown> | undefined)\n ?.correlationId === correlationId,\n );\n if (msg) {\n this.chativaCtx!.messages.update(msg.id, {\n data: { ...msg.data, feedbackDisabled: true, feedbackType },\n });\n }\n }\n\n private handleTyping() {\n this.clearTypingTimeout();\n this.typingHandler?.(true);\n if (this.options.typingUntilMessage) {\n // No timer — rely on next bot message (handled by ChatEngine) to clear.\n return;\n }\n const ms = this.options.typingTimeoutMs ?? 3000;\n this.typingTimeout = setTimeout(() => {\n this.typingHandler?.(false);\n this.typingTimeout = null;\n }, ms);\n }\n\n private clearTypingTimeout() {\n if (this.typingTimeout !== null) {\n clearTimeout(this.typingTimeout);\n this.typingTimeout = null;\n }\n }\n\n /* ── Token refresh ──────────────────────────────────────────────── */\n\n /** Schedule a token refresh 60 seconds before expiry. */\n private scheduleTokenRefresh() {\n this.clearRefreshTimer();\n const expiry = getTokenExpiry(this.token);\n if (!expiry) return;\n\n const delay = expiry - Date.now() - 60_000;\n if (delay <= 0) {\n // Token is already about to expire — refresh immediately\n this.refreshTokenNow();\n return;\n }\n this.refreshTimer = setTimeout(() => this.refreshTokenNow(), delay);\n }\n\n /** Pre-emptively refresh the token before it expires. */\n private async refreshTokenNow() {\n try {\n const result = await refreshDirectLineToken(\n this.token,\n this.options.domain,\n );\n this.token = result.token;\n this.scheduleTokenRefresh();\n if (this.options.resumeConversation) {\n this.persistConversation();\n }\n } catch (err) {\n console.warn(\"[DirectLineConnector] Token refresh failed:\", err);\n }\n }\n\n /**\n * Called when DirectLine emits ExpiredToken status.\n * Refreshes the token and recreates the DirectLine connection.\n */\n private async handleExpiredToken() {\n try {\n const result = await refreshDirectLineToken(\n this.token,\n this.options.domain,\n );\n this.token = result.token;\n this.scheduleTokenRefresh();\n } catch {\n if (this.options.resumeConversation) {\n this.clearPersistedConversation();\n }\n this.disconnectHandler?.(\"Token expired and refresh failed\");\n return;\n }\n\n // Tear down old DirectLine\n this.activitySub?.unsubscribe();\n this.connectionSub?.unsubscribe();\n this.clearTypingTimeout();\n try {\n this.directLine.end();\n } catch {\n /* already ended */\n }\n\n // Recreate with new token, preserving conversation\n this.directLine = new DirectLine({\n token: this.token,\n domain: this.options.domain,\n conversationId: this.conversationId,\n watermark: this.watermark,\n });\n\n this.startListening(true);\n\n if (this.options.resumeConversation) {\n this.persistConversation();\n }\n }\n\n private clearRefreshTimer() {\n if (this.refreshTimer !== null) {\n clearTimeout(this.refreshTimer);\n this.refreshTimer = null;\n }\n }\n\n /* ── Conversation persistence ───────────────────────────────────── */\n\n private persistConversation() {\n if (!this.conversationId || !this.token) return;\n try {\n const data: PersistedConversation = {\n conversationId: this.conversationId,\n token: this.token,\n watermark: this.watermark,\n userId: this.userId,\n };\n localStorage.setItem(CONVERSATION_KEY, JSON.stringify(data));\n } catch {\n // localStorage unavailable\n }\n }\n\n private loadPersistedConversation(): PersistedConversation | null {\n try {\n const raw = localStorage.getItem(CONVERSATION_KEY);\n if (!raw) return null;\n return JSON.parse(raw) as PersistedConversation;\n } catch {\n return null;\n }\n }\n\n private clearPersistedConversation() {\n try {\n localStorage.removeItem(CONVERSATION_KEY);\n } catch {\n // best-effort\n }\n }\n}\n"],"names":["TYPING_SENTINEL","CT_HERO","CT_THUMBNAIL","CT_ADAPTIVE","CT_SIGNIN","CT_OAUTH","CT_RECEIPT","CT_AUDIO","CT_VIDEO","CT_ANIMATION","CT_FLEX","mapActivityToMessage","activity","userId","msg","id","timestamp","channelData","suggestedActions","mapSuggestedActions","result","mapAttachments","attachments","layout","cardTypes","adaptiveCardToCardData","mapHeroLikeCard","mapSingleAttachment","att","ct","content","mapAdaptiveCard","mapCardButtons","mapReceiptCard","mediaUrl","buttons","btn","label","actions","a","walkAdaptiveElements","elements","texts","onImage","el","mapAdaptiveActions","col","parseAdaptiveCardBody","image","url","fallback","out","lines","fact","item","price","USER_ID_KEY","CONVERSATION_KEY","randomUserId","getOrCreateUserId","stored","getTokenExpiry","token","payload","fetchToken","secret","res","data","fetchTokenFromUrl","refreshDirectLineToken","currentToken","domain","DirectLineConnector","options","ctx","name","handler","isResuming","persisted","refreshed","DirectLine","ready","resolve","message","reject","err","file","formData","rawType","type","messageId","feedback","correlationId","m","feedbackType","cursor","messages","callback","resolveOnOnline","status","ConnectionStatus","pendingId","val","from","value","locale","ms","expiry","delay","raw"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/mapActivity.ts","../src/DirectLineConnector.ts"],"sourcesContent":["/**\n * Pure mapping functions that convert DirectLine activities\n * into Chativa IncomingMessage structures.\n *\n * No side-effects — all functions are stateless and easily testable.\n */\n\nimport type {\n Activity,\n Message,\n CardAction,\n Attachment,\n HeroCard,\n Thumbnail,\n Signin,\n Receipt,\n AudioCard,\n VideoCard,\n AnimationCard,\n AdaptiveCard,\n FlexCard,\n} from \"botframework-directlinejs\";\nimport type { IncomingMessage, MessageAction } from \"@chativa/core\";\n\n/* ------------------------------------------------------------------ */\n/* Sentinel returned for typing activities */\n/* ------------------------------------------------------------------ */\n\nexport const TYPING_SENTINEL = Symbol(\"typing\");\nexport type MapResult = IncomingMessage | typeof TYPING_SENTINEL | null;\n\n/* ------------------------------------------------------------------ */\n/* Content-type constants */\n/* ------------------------------------------------------------------ */\n\nconst CT_HERO = \"application/vnd.microsoft.card.hero\";\nconst CT_THUMBNAIL = \"application/vnd.microsoft.card.thumbnail\";\nconst CT_ADAPTIVE = \"application/vnd.microsoft.card.adaptive\";\nconst CT_SIGNIN = \"application/vnd.microsoft.card.signin\";\nconst CT_OAUTH = \"application/vnd.microsoft.card.oauth\";\nconst CT_RECEIPT = \"application/vnd.microsoft.card.receipt\";\nconst CT_AUDIO = \"application/vnd.microsoft.card.audio\";\nconst CT_VIDEO = \"application/vnd.microsoft.card.video\";\nconst CT_ANIMATION = \"application/vnd.microsoft.card.animation\";\nconst CT_FLEX = \"application/vnd.microsoft.card.flex\";\n\n/* ------------------------------------------------------------------ */\n/* Main entry point */\n/* ------------------------------------------------------------------ */\n\n/**\n * Convert a DirectLine Activity into a Chativa IncomingMessage.\n *\n * Returns:\n * - `IncomingMessage` for renderable messages\n * - `TYPING_SENTINEL` for typing indicators (handled by the connector)\n * - `null` for activities that should be ignored (echo, events, etc.)\n */\nexport function mapActivityToMessage(\n activity: Activity,\n userId: string,\n): MapResult {\n // Filter out echoed user messages\n if (activity.from.id === userId) return null;\n\n // Typing indicator\n if (activity.type === \"typing\") return TYPING_SENTINEL;\n\n // Only process message activities\n if (activity.type !== \"message\") return null;\n\n const msg = activity as Message;\n const id = msg.id ?? `dl-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;\n const timestamp = msg.timestamp ? new Date(msg.timestamp).getTime() : Date.now();\n\n // Preserve channelData (e.g. correlationId used for feedback)\n const channelData = (msg as unknown as { channelData?: Record<string, unknown> }).channelData;\n\n // Map suggested actions (may be used as top-level actions or as quick-reply)\n const suggestedActions = mapSuggestedActions(msg);\n\n // If there are attachments, process them\n if (msg.attachments && msg.attachments.length > 0) {\n const result = mapAttachments(msg, id, timestamp);\n if (result) {\n if (suggestedActions.length > 0) result.actions = suggestedActions;\n if (channelData) result.data.channelData = channelData;\n }\n return result;\n }\n\n // Text + suggested actions → quick-reply\n if (suggestedActions.length > 0) {\n return {\n id,\n type: \"quick-reply\",\n from: \"bot\",\n data: { text: msg.text ?? \"\", actions: suggestedActions, ...(channelData ? { channelData } : {}) },\n timestamp,\n };\n }\n\n // Plain text\n if (msg.text) {\n return {\n id,\n type: \"text\",\n from: \"bot\",\n data: { text: msg.text, ...(channelData ? { channelData } : {}) },\n timestamp,\n };\n }\n\n // Nothing meaningful\n return null;\n}\n\n/* ------------------------------------------------------------------ */\n/* Attachment mapping */\n/* ------------------------------------------------------------------ */\n\nfunction mapAttachments(\n msg: Message,\n id: string,\n timestamp: number,\n): IncomingMessage | null {\n const attachments = msg.attachments!;\n const layout = msg.attachmentLayout;\n\n // Multiple cards with carousel layout\n const cardTypes = [CT_HERO, CT_THUMBNAIL, CT_FLEX, CT_ADAPTIVE];\n const allCards = attachments.every((a) => cardTypes.includes(a.contentType));\n\n if (allCards && (attachments.length > 1 || layout === \"carousel\")) {\n return {\n id,\n type: \"carousel\",\n from: \"bot\",\n data: {\n cards: attachments.map((a) => {\n if (a.contentType === CT_ADAPTIVE) {\n return adaptiveCardToCardData(\n (a as AdaptiveCard).content as Record<string, unknown>,\n );\n }\n return mapHeroLikeCard((a as HeroCard | Thumbnail | FlexCard).content);\n }),\n },\n timestamp,\n };\n }\n\n // Single attachment\n return mapSingleAttachment(attachments[0], msg, id, timestamp);\n}\n\nfunction mapSingleAttachment(\n att: Attachment,\n msg: Message,\n id: string,\n timestamp: number,\n): IncomingMessage | null {\n const ct = att.contentType;\n\n // Hero / Thumbnail / Flex card\n if (ct === CT_HERO || ct === CT_THUMBNAIL || ct === CT_FLEX) {\n const content = (att as HeroCard | Thumbnail | FlexCard).content;\n return {\n id,\n type: \"card\",\n from: \"bot\",\n data: mapHeroLikeCard(content),\n timestamp,\n };\n }\n\n // Adaptive card — simple parse\n if (ct === CT_ADAPTIVE) {\n return mapAdaptiveCard((att as AdaptiveCard).content, id, timestamp);\n }\n\n // Signin / OAuth card\n if (ct === CT_SIGNIN || ct === CT_OAUTH) {\n const content = (att as Signin).content;\n return {\n id,\n type: \"buttons\",\n from: \"bot\",\n data: {\n text: content.text ?? \"Please sign in\",\n buttons: mapCardButtons(content.buttons),\n },\n timestamp,\n };\n }\n\n // Receipt card\n if (ct === CT_RECEIPT) {\n return mapReceiptCard((att as Receipt).content, id, timestamp);\n }\n\n // Video card\n if (ct === CT_VIDEO) {\n const content = (att as VideoCard).content;\n const mediaUrl = content.media?.[0]?.url;\n if (!mediaUrl) return null;\n return {\n id,\n type: \"video\",\n from: \"bot\",\n data: {\n src: mediaUrl,\n poster: content.image?.url,\n caption: content.title ?? msg.text,\n },\n timestamp,\n };\n }\n\n // Audio card\n if (ct === CT_AUDIO) {\n const content = (att as AudioCard).content;\n const mediaUrl = content.media?.[0]?.url;\n if (!mediaUrl) return null;\n return {\n id,\n type: \"file\",\n from: \"bot\",\n data: {\n url: mediaUrl,\n name: content.title ?? \"audio\",\n mimeType: \"audio/mpeg\",\n },\n timestamp,\n };\n }\n\n // Animation card (typically GIFs)\n if (ct === CT_ANIMATION) {\n const content = (att as AnimationCard).content;\n const mediaUrl = content.media?.[0]?.url;\n if (!mediaUrl) return null;\n return {\n id,\n type: \"image\",\n from: \"bot\",\n data: {\n src: mediaUrl,\n caption: content.title ?? msg.text,\n },\n timestamp,\n };\n }\n\n // Image attachment (contentType starts with \"image/\")\n if (ct.startsWith(\"image/\") && \"contentUrl\" in att) {\n return {\n id,\n type: \"image\",\n from: \"bot\",\n data: {\n src: att.contentUrl,\n alt: att.name,\n caption: msg.text,\n },\n timestamp,\n };\n }\n\n // Video file attachment\n if (ct.startsWith(\"video/\") && \"contentUrl\" in att) {\n return {\n id,\n type: \"video\",\n from: \"bot\",\n data: {\n src: att.contentUrl,\n caption: msg.text,\n },\n timestamp,\n };\n }\n\n // Generic file attachment\n if (\"contentUrl\" in att) {\n return {\n id,\n type: \"file\",\n from: \"bot\",\n data: {\n url: att.contentUrl,\n name: att.name ?? \"file\",\n mimeType: ct,\n },\n timestamp,\n };\n }\n\n return null;\n}\n\n/* ------------------------------------------------------------------ */\n/* Hero-like card mapping (hero, thumbnail, flex share same shape) */\n/* ------------------------------------------------------------------ */\n\nfunction mapHeroLikeCard(content: HeroCard[\"content\"]): Record<string, unknown> {\n return {\n image: content.images?.[0]?.url,\n title: content.title ?? \"\",\n subtitle: content.subtitle ?? content.text,\n buttons: mapCardButtons(content.buttons),\n };\n}\n\n/* ------------------------------------------------------------------ */\n/* Card button / action mapping */\n/* ------------------------------------------------------------------ */\n\nexport function mapCardButtons(\n buttons?: CardAction[],\n): MessageAction[] {\n if (!buttons || buttons.length === 0) return [];\n\n return buttons.map((btn): MessageAction => {\n const label = (\"title\" in btn ? btn.title : undefined) ?? String(btn.value ?? \"\");\n\n switch (btn.type) {\n case \"openUrl\":\n case \"signin\":\n return { label, url: String(btn.value ?? \"\") };\n\n case \"call\":\n return { label, url: String(btn.value ?? \"\") };\n\n case \"imBack\":\n case \"postBack\":\n case \"messageBack\":\n default:\n return { label, value: String(btn.value ?? label) };\n }\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* Suggested actions mapping */\n/* ------------------------------------------------------------------ */\n\nfunction mapSuggestedActions(msg: Message): MessageAction[] {\n const actions = msg.suggestedActions?.actions;\n if (!actions || actions.length === 0) return [];\n\n return actions.map((a): MessageAction => {\n const label = (\"title\" in a ? a.title : undefined) ?? String(a.value ?? \"\");\n if (a.type === \"openUrl\") {\n return { label, url: String(a.value ?? \"\") };\n }\n return { label, value: String(a.value ?? label) };\n });\n}\n\n/* ------------------------------------------------------------------ */\n/* Adaptive Card — simple parse */\n/* ------------------------------------------------------------------ */\n\ninterface AdaptiveElement {\n type: string;\n text?: string;\n url?: string;\n body?: AdaptiveElement[];\n items?: AdaptiveElement[];\n columns?: Array<{ items?: AdaptiveElement[] }>;\n actions?: Array<{ type: string; title?: string; url?: string; data?: unknown }>;\n [key: string]: unknown;\n}\n\n/** Recursively walk Adaptive Card elements and collect texts, the first image, and buttons. */\nfunction walkAdaptiveElements(\n elements: AdaptiveElement[] | undefined,\n texts: string[],\n buttons: MessageAction[],\n onImage: (url: string) => void,\n) {\n if (!elements) return;\n for (const el of elements) {\n switch (el.type) {\n case \"TextBlock\":\n if (el.text) texts.push(el.text);\n break;\n case \"Image\":\n if (el.url) onImage(el.url);\n break;\n case \"ActionSet\":\n if (el.actions) mapAdaptiveActions(el.actions, buttons);\n break;\n case \"Container\":\n walkAdaptiveElements(el.items, texts, buttons, onImage);\n break;\n case \"ColumnSet\":\n for (const col of el.columns ?? []) {\n walkAdaptiveElements(col.items, texts, buttons, onImage);\n }\n break;\n case \"Column\":\n walkAdaptiveElements(el.items, texts, buttons, onImage);\n break;\n }\n }\n}\n\n/** Parse an Adaptive Card body into { image, title, subtitle, buttons }. */\nfunction parseAdaptiveCardBody(content: Record<string, unknown>) {\n const texts: string[] = [];\n let image: string | undefined;\n const buttons: MessageAction[] = [];\n\n walkAdaptiveElements(\n content.body as AdaptiveElement[] | undefined,\n texts,\n buttons,\n (url) => { image = image ?? url; },\n );\n\n if (Array.isArray(content.actions)) {\n mapAdaptiveActions(content.actions as AdaptiveElement[\"actions\"] & object, buttons);\n }\n\n return { texts, image, buttons };\n}\n\n/**\n * Extract card-like data from an Adaptive Card — used inside carousels.\n */\nfunction adaptiveCardToCardData(\n content: Record<string, unknown>,\n): Record<string, unknown> {\n const { texts, image, buttons } = parseAdaptiveCardBody(content);\n return {\n image,\n title: texts[0] ?? \"\",\n subtitle: texts.slice(1).join(\"\\n\"),\n buttons,\n };\n}\n\nfunction mapAdaptiveCard(\n content: Record<string, unknown>,\n id: string,\n timestamp: number,\n): IncomingMessage {\n const { texts, image, buttons } = parseAdaptiveCardBody(content);\n\n // Fallback text\n const fallback =\n (content.fallbackText as string) ??\n (content.speak as string) ??\n texts.join(\"\\n\");\n\n // If we have an image and a title, render as card\n if (image) {\n return {\n id,\n type: \"card\",\n from: \"bot\",\n data: {\n image,\n title: texts[0] ?? \"\",\n subtitle: texts.slice(1).join(\"\\n\"),\n buttons,\n },\n timestamp,\n };\n }\n\n // If we have buttons, render as buttons message\n if (buttons.length > 0) {\n return {\n id,\n type: \"buttons\",\n from: \"bot\",\n data: {\n text: fallback || \"Adaptive Card\",\n buttons,\n },\n timestamp,\n };\n }\n\n // Plain text fallback\n return {\n id,\n type: \"text\",\n from: \"bot\",\n data: { text: fallback || \"[Adaptive Card]\" },\n timestamp,\n };\n}\n\nfunction mapAdaptiveActions(\n actions: Array<{ type: string; title?: string; url?: string; data?: unknown }>,\n out: MessageAction[],\n) {\n for (const a of actions) {\n const label = a.title ?? \"Action\";\n if (a.type === \"Action.OpenUrl\" && a.url) {\n out.push({ label, url: a.url });\n } else if (a.type === \"Action.Submit\") {\n out.push({ label, value: typeof a.data === \"string\" ? a.data : label });\n } else {\n out.push({ label, value: label });\n }\n }\n}\n\n/* ------------------------------------------------------------------ */\n/* Receipt card mapping */\n/* ------------------------------------------------------------------ */\n\nfunction mapReceiptCard(\n content: Receipt[\"content\"],\n id: string,\n timestamp: number,\n): IncomingMessage {\n const lines: string[] = [];\n if (content.title) lines.push(`**${content.title}**`);\n\n if (content.facts) {\n for (const fact of content.facts) {\n lines.push(`${fact.key}: ${fact.value}`);\n }\n }\n\n if (content.items) {\n for (const item of content.items) {\n const price = item.price ? ` — ${item.price}` : \"\";\n lines.push(`- ${item.title ?? \"Item\"}${price}`);\n }\n }\n\n if (content.tax) lines.push(`Tax: ${content.tax}`);\n if (content.total) lines.push(`**Total: ${content.total}**`);\n\n return {\n id,\n type: \"text\",\n from: \"bot\",\n data: { text: lines.join(\"\\n\") },\n timestamp,\n };\n}\n","import type {\n IConnector,\n MessageHandler,\n ConnectHandler,\n DisconnectHandler,\n TypingHandler,\n MessageStatusHandler,\n ChativaContext,\n SurveyPayload,\n} from \"@chativa/core\";\nimport type {\n OutgoingMessage,\n HistoryResult,\n IncomingMessage,\n} from \"@chativa/core\";\nimport { DirectLine, ConnectionStatus } from \"botframework-directlinejs\";\nimport type { Activity } from \"botframework-directlinejs\";\nimport { mapActivityToMessage, TYPING_SENTINEL } from \"./mapActivity\";\n\n/** Minimal subscription interface returned by RxJS5 Observable.subscribe(). */\ninterface Unsubscribable {\n unsubscribe(): void;\n}\n\n/**\n * Context passed to custom event handlers — provides the info and methods\n * needed to respond to a bot-initiated event activity.\n */\nexport interface EventHandlerContext {\n /** The raw DirectLine event activity. */\n activity: Activity;\n /** The user ID for this session. */\n userId: string;\n /** The user display name for this session. */\n userName: string;\n /** Send an event activity back to the bot. */\n postEvent(name: string, value?: unknown): void;\n /**\n * Full Chativa context — access messages, chat UI, theme, and event bus.\n * Available when the connector is used via ChatEngine (which injects it).\n */\n chativa: ChativaContext;\n}\n\n/**\n * Handler for a bot-initiated event activity.\n * Keyed by event name (e.g. `\"LocationRequest\"`).\n */\nexport type EventHandler = (ctx: EventHandlerContext) => void;\n\nexport interface DirectLineConnectorOptions {\n /** DirectLine channel secret (server-side only — generates a token). */\n secret?: string;\n /** Pre-fetched DirectLine token. */\n token?: string;\n /** URL of a custom token-generating endpoint (POST, returns { token, conversationId? }). */\n tokenGeneratorUrl?: string;\n /**\n * Override the user ID. When omitted, a random per-instance ID is\n * generated — unless `resumeConversation` is true, in which case the ID\n * is persisted to localStorage so reloads can rejoin the same\n * conversation.\n */\n userId?: string;\n /** Display name sent with activities. Defaults to the (resolved) userId. */\n userName?: string;\n /** Sovereign-cloud DirectLine endpoint (e.g. government, china). */\n domain?: string;\n /** BCP-47 locale sent with the webchat/join event and outgoing activities (e.g. \"tr-TR\"). */\n locale?: string;\n /**\n * Extra key-value pairs merged into the `webchat/join` event's `value` payload.\n * Example: `{ language: \"tr\", tenant: \"galataport\" }`\n */\n joinParameters?: Record<string, unknown>;\n /**\n * Custom handlers for bot-initiated event activities, keyed by event name.\n * Example:\n * ```ts\n * eventHandlers: {\n * LocationRequest: (ctx) => {\n * navigator.geolocation.getCurrentPosition((pos) => {\n * ctx.postEvent(\"webchat/location\", {\n * latitude: pos.coords.latitude,\n * longitude: pos.coords.longitude,\n * });\n * });\n * },\n * }\n * ```\n */\n eventHandlers?: Record<string, EventHandler>;\n /**\n * When true, persist conversation state (conversationId, token, watermark)\n * and the auto-generated userId to localStorage so the conversation can\n * be resumed across page reloads. Default: `false` — each new connector\n * instance gets a fresh userId and opens a new conversation.\n */\n resumeConversation?: boolean;\n /**\n * How long (ms) to keep the typing indicator visible after a bot typing\n * signal before auto-clearing it. Each new typing signal resets the timer.\n * Default: 3000. Ignored when `typingUntilMessage` is true.\n */\n typingTimeoutMs?: number;\n /**\n * When true, the typing indicator stays on until the next bot message\n * arrives (no auto-clear timeout).\n */\n typingUntilMessage?: boolean;\n}\n\n/* ── Constants ────────────────────────────────────────────────────── */\n\nconst USER_ID_KEY = \"chativa_directline_userId\";\nconst CONVERSATION_KEY = \"chativa_directline_conversation\";\n\ninterface PersistedConversation {\n conversationId: string;\n token: string;\n watermark?: string;\n userId: string;\n}\n\n/* ── Module-level helpers ─────────────────────────────────────────── */\n\nfunction randomUserId(): string {\n return (\n Math.random().toString(36).slice(2, 15) +\n Math.random().toString(36).slice(2, 15)\n );\n}\n\n/** Read a persisted userId for conversation-resume mode, generating and\n * storing one on first use. Only called when `resumeConversation` is true. */\nfunction getOrCreateUserId(): string {\n try {\n const stored = localStorage.getItem(USER_ID_KEY);\n if (stored) return stored;\n } catch {\n // localStorage unavailable (e.g. incognito, SSR)\n }\n const id = randomUserId();\n try {\n localStorage.setItem(USER_ID_KEY, id);\n } catch {\n // best-effort\n }\n return id;\n}\n\n/** Extract expiry time (ms since epoch) from a JWT token. */\nfunction getTokenExpiry(token: string): number | null {\n try {\n const payload = JSON.parse(atob(token.split(\".\")[1]));\n return typeof payload.exp === \"number\" ? payload.exp * 1000 : null;\n } catch {\n return null;\n }\n}\n\nasync function fetchToken(\n secret: string,\n userId: string,\n): Promise<{ token: string; conversationId: string }> {\n const res = await fetch(\n \"https://directline.botframework.com/v3/directline/tokens/generate\",\n {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${secret}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ user: { id: userId, name: userId } }),\n },\n );\n if (!res.ok) {\n throw new Error(`DirectLine token fetch failed: ${res.status}`);\n }\n const data = (await res.json()) as {\n token: string;\n conversationId: string;\n };\n return { token: data.token, conversationId: data.conversationId };\n}\n\nasync function fetchTokenFromUrl(\n url: string,\n): Promise<{ token: string; conversationId?: string; userId?: string }> {\n const res = await fetch(url, { method: \"POST\" });\n if (!res.ok) {\n throw new Error(`Token generator failed: ${res.status}`);\n }\n return (await res.json()) as {\n token: string;\n conversationId?: string;\n userId?: string;\n };\n}\n\n/** Refresh an existing DirectLine token via the REST API. */\nasync function refreshDirectLineToken(\n currentToken: string,\n domain?: string,\n): Promise<{ token: string; conversationId: string }> {\n const base = domain ?? \"https://directline.botframework.com/v3/directline\";\n const res = await fetch(`${base}/tokens/refresh`, {\n method: \"POST\",\n headers: { Authorization: `Bearer ${currentToken}` },\n });\n if (!res.ok) {\n throw new Error(`Token refresh failed: ${res.status}`);\n }\n return (await res.json()) as { token: string; conversationId: string };\n}\n\n/**\n * DirectLineConnector — Azure Bot Framework DirectLine v3 adapter.\n *\n * Maps all Bot Framework activity types (hero cards, carousels,\n * suggested actions, images, videos, files, adaptive cards, etc.)\n * into Chativa's native message types.\n */\nexport class DirectLineConnector implements IConnector {\n readonly name = \"directline\";\n readonly addSentToHistory = true;\n\n private directLine!: DirectLine;\n private conversationId!: string;\n private userId!: string;\n private userName: string | undefined;\n private token!: string;\n private options: DirectLineConnectorOptions;\n private chativaCtx: ChativaContext | null = null;\n\n private messageHandler: MessageHandler | null = null;\n private connectHandler: ConnectHandler | null = null;\n private disconnectHandler: DisconnectHandler | null = null;\n private typingHandler: TypingHandler | null = null;\n private messageStatusHandler: MessageStatusHandler | null = null;\n\n private activitySub: Unsubscribable | null = null;\n private connectionSub: Unsubscribable | null = null;\n private typingTimeout: ReturnType<typeof setTimeout> | null = null;\n private refreshTimer: ReturnType<typeof setTimeout> | null = null;\n /** Queue of sent message IDs awaiting echo confirmation. */\n private pendingIds: string[] = [];\n /** True after the first successful connection (reserved for future join-vs-rejoin logic). */\n private hasConnectedBefore = false;\n /** Resolves the connect() promise once the bot sends its first message after join. */\n private resolveReady: (() => void) | null = null;\n /** Last known activity watermark — used for conversation resume. */\n private watermark: string | undefined;\n /** When true, skip the next join/rejoin event (used during resume). */\n private _skipNextJoin = false;\n\n constructor(options: DirectLineConnectorOptions) {\n this.options = options;\n }\n\n setContext(ctx: ChativaContext): void {\n this.chativaCtx = ctx;\n }\n\n /**\n * Register (or replace) an event handler for a bot-initiated event activity.\n * Can be called before or after `connect()`.\n *\n * @example\n * ```ts\n * connector.addEventHandler(\"LocationRequest\", (ctx) => {\n * navigator.geolocation.getCurrentPosition((pos) => {\n * ctx.postEvent(\"webchat/location\", { latitude: pos.coords.latitude, ... });\n * });\n * });\n * ```\n */\n addEventHandler(name: string, handler: EventHandler): void {\n this.options.eventHandlers ??= {};\n this.options.eventHandlers[name] = handler;\n }\n\n /** Remove a previously registered event handler by name. */\n removeEventHandler(name: string): boolean {\n if (!this.options.eventHandlers?.[name]) return false;\n delete this.options.eventHandlers[name];\n return true;\n }\n\n /** Check whether an event handler is registered for the given name. */\n hasEventHandler(name: string): boolean {\n return !!this.options.eventHandlers?.[name];\n }\n\n /** Return the names of all registered event handlers. */\n getEventHandlerNames(): string[] {\n return Object.keys(this.options.eventHandlers ?? {});\n }\n\n async connect(): Promise<void> {\n this.userId =\n this.options.userId ??\n (this.options.resumeConversation\n ? getOrCreateUserId()\n : randomUserId());\n this.userName = this.options.userName;\n\n // ── Try to resume a persisted conversation ────────────────────\n let isResuming = false;\n if (this.options.resumeConversation) {\n const persisted = this.loadPersistedConversation();\n if (persisted) {\n try {\n const refreshed = await refreshDirectLineToken(\n persisted.token,\n this.options.domain,\n );\n this.token = refreshed.token;\n this.conversationId = persisted.conversationId;\n this.watermark = persisted.watermark;\n this.userId = persisted.userId;\n isResuming = true;\n } catch {\n // Persisted token expired or invalid — start fresh\n this.clearPersistedConversation();\n }\n }\n }\n\n // ── Acquire token (fresh conversation) ────────────────────────\n if (!isResuming) {\n if (this.options.tokenGeneratorUrl) {\n const result = await fetchTokenFromUrl(\n this.options.tokenGeneratorUrl,\n );\n this.token = result.token;\n if (result.conversationId)\n this.conversationId = result.conversationId;\n if (result.userId) this.userId = result.userId;\n } else if (this.options.token) {\n this.token = this.options.token;\n } else if (this.options.secret) {\n const result = await fetchToken(\n this.options.secret,\n this.userId,\n );\n this.token = result.token;\n this.conversationId = result.conversationId;\n } else {\n throw new Error(\n \"DirectLineConnector: provide token, secret, or tokenGeneratorUrl.\",\n );\n }\n }\n\n // ── Schedule automatic token refresh ──────────────────────────\n this.scheduleTokenRefresh();\n\n // ── Create DirectLine instance ────────────────────────────────\n this.directLine = new DirectLine({\n token: this.token,\n domain: this.options.domain,\n ...(isResuming\n ? {\n conversationId: this.conversationId,\n watermark: this.watermark,\n }\n : {}),\n });\n\n if (isResuming) {\n this.hasConnectedBefore = true;\n this._skipNextJoin = true;\n }\n\n // ── Wait for connection ───────────────────────────────────────\n const ready = new Promise<void>((resolve) => {\n this.resolveReady = resolve;\n });\n\n this.startListening(isResuming);\n\n await ready;\n this.connectHandler?.();\n\n if (this.options.resumeConversation) {\n this.persistConversation();\n }\n }\n\n async disconnect(): Promise<void> {\n this.activitySub?.unsubscribe();\n this.activitySub = null;\n this.connectionSub?.unsubscribe();\n this.connectionSub = null;\n this.clearTypingTimeout();\n this.clearRefreshTimer();\n\n try {\n this.directLine?.end();\n } catch {\n // DirectLine.end() may throw if already ended\n }\n\n this.messageHandler = null;\n this.connectHandler = null;\n this.disconnectHandler = null;\n this.typingHandler = null;\n this.messageStatusHandler = null;\n }\n\n /** Clear persisted conversation state and reset watermark. */\n clearConversation(): void {\n this.watermark = undefined;\n this.clearPersistedConversation();\n }\n\n async sendMessage(message: OutgoingMessage): Promise<void> {\n this.pendingIds.push(message.id);\n // Flip to \"sent\" (single-tick) right away — postActivity's network\n // round-trip can take 200–500 ms and the user expects instant\n // feedback. The bot's actual reply will flush this pending id to\n // \"read\" (double-tick) via flushPendingToRead().\n this.messageStatusHandler?.(message.id, \"sent\");\n return new Promise<void>((resolve, reject) => {\n this.directLine\n .postActivity({\n type: \"message\",\n from: {\n id: this.userId,\n name: this.userName ?? this.userId,\n },\n text: (message.data as { text?: string }).text ?? \"\",\n conversation: { id: this.conversationId },\n channelId: \"directline\",\n ...(this.options.locale\n ? { locale: this.options.locale }\n : {}),\n timestamp: new Date().toISOString(),\n id: message.id,\n })\n .subscribe({\n next: () => resolve(),\n error: (err: unknown) => reject(err),\n });\n });\n }\n\n async sendFile(file: File): Promise<void> {\n const domain =\n this.options.domain ??\n \"https://directline.botframework.com/v3/directline\";\n const url = `${domain}/conversations/${this.conversationId}/upload?userId=${encodeURIComponent(this.userId)}`;\n\n const formData = new FormData();\n formData.append(\"file\", file, file.name);\n\n const res = await fetch(url, {\n method: \"POST\",\n headers: { Authorization: `Bearer ${this.token}` },\n body: formData,\n });\n\n if (!res.ok) {\n throw new Error(`DirectLine file upload failed: ${res.status}`);\n }\n }\n\n /**\n * Submit an end-of-conversation survey as a DirectLine event activity.\n * Matches the legacy `webchat/customerfeedback` event shape so existing\n * bot flows keep working:\n * value: { rating, comment, type }\n * where `type` is `kind` coerced to a number (defaulting to 1) to preserve\n * compatibility with bots that switch on numeric survey types.\n */\n async sendSurvey(payload: SurveyPayload): Promise<void> {\n const rawType =\n typeof payload.kind === \"number\"\n ? payload.kind\n : payload.kind !== undefined\n ? Number(payload.kind)\n : 1;\n const type = Number.isFinite(rawType) ? rawType : 1;\n\n return new Promise<void>((resolve, reject) => {\n this.directLine\n .postActivity({\n type: \"event\",\n name: \"webchat/customerfeedback\",\n from: { id: this.userId, name: this.userId },\n value: {\n rating: payload.rating,\n comment: payload.comment ?? \"\",\n type,\n },\n })\n .subscribe({\n next: () => resolve(),\n error: (err: unknown) => reject(err),\n });\n });\n }\n\n async sendFeedback(\n messageId: string,\n feedback: \"like\" | \"dislike\",\n ): Promise<void> {\n // Look up the correlationId from the stored message's channelData\n const msg = this.chativaCtx?.messages\n .getAll()\n .find((m) => m.id === messageId);\n const correlationId = (\n msg?.data?.channelData as Record<string, unknown> | undefined\n )?.correlationId;\n\n if (!correlationId) {\n console.warn(\n \"[DirectLineConnector] sendFeedback: no correlationId found for message\",\n messageId,\n );\n return;\n }\n\n // Bot expects numeric: 0 = like, 1 = dislike\n const feedbackType = feedback === \"like\" ? 0 : 1;\n\n return new Promise<void>((resolve, reject) => {\n this.directLine\n .postActivity({\n type: \"event\",\n name: \"webchat/messageFeedback\",\n from: { id: this.userId, name: this.userId },\n value: { correlationId, feedbackType },\n })\n .subscribe({\n next: () => resolve(),\n error: (err: unknown) => reject(err),\n });\n });\n }\n\n async loadHistory(cursor?: string): Promise<HistoryResult> {\n const domain =\n this.options.domain ??\n \"https://directline.botframework.com/v3/directline\";\n const url = cursor\n ? `${domain}/conversations/${this.conversationId}/activities?watermark=${encodeURIComponent(cursor)}`\n : `${domain}/conversations/${this.conversationId}/activities`;\n\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${this.token}` },\n });\n\n if (!res.ok) {\n throw new Error(`DirectLine history fetch failed: ${res.status}`);\n }\n\n const data = (await res.json()) as {\n activities: Activity[];\n watermark: string;\n };\n\n const messages: IncomingMessage[] = [];\n for (const activity of data.activities) {\n // User's own messages — mapActivityToMessage filters these out (live dedup),\n // but history needs them.\n if (\n activity.from.id === this.userId &&\n activity.type === \"message\"\n ) {\n const msg = activity as Activity & { text?: string };\n if (msg.text) {\n messages.push({\n id:\n activity.id ??\n `dl-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,\n type: \"text\",\n from: \"user\",\n data: { text: msg.text },\n timestamp: activity.timestamp\n ? new Date(activity.timestamp).getTime()\n : Date.now(),\n });\n }\n continue;\n }\n\n const result = mapActivityToMessage(activity, this.userId);\n if (result !== null && result !== TYPING_SENTINEL) {\n messages.push(result);\n }\n }\n\n return {\n messages,\n hasMore: false,\n cursor: data.watermark,\n };\n }\n\n onMessage(callback: MessageHandler): void {\n this.messageHandler = callback;\n }\n\n onConnect(callback: ConnectHandler): void {\n this.connectHandler = callback;\n }\n\n onDisconnect(callback: DisconnectHandler): void {\n this.disconnectHandler = callback;\n }\n\n onTyping(callback: TypingHandler): void {\n this.typingHandler = callback;\n }\n\n onMessageStatus(callback: MessageStatusHandler): void {\n this.messageStatusHandler = callback;\n }\n\n /* ── Private helpers ───────────────────────────────────────────── */\n\n /**\n * Subscribe to DirectLine connectionStatus$ and activity$ observables.\n * Extracted so it can be re-used after an ExpiredToken reconnect.\n */\n private startListening(resolveOnOnline: boolean) {\n this.connectionSub = this.directLine.connectionStatus$.subscribe(\n (status: ConnectionStatus) => {\n switch (status) {\n case ConnectionStatus.Online:\n this.sendJoinEvent();\n // Resumed / reconnected conversations resolve immediately\n if (resolveOnOnline && this.resolveReady) {\n this.resolveReady();\n this.resolveReady = null;\n }\n break;\n case ConnectionStatus.ExpiredToken:\n this.handleExpiredToken();\n break;\n case ConnectionStatus.FailedToConnect:\n if (this.options.resumeConversation) {\n this.clearPersistedConversation();\n }\n this.disconnectHandler?.(\"Failed to connect\");\n break;\n case ConnectionStatus.Ended:\n this.disconnectHandler?.(\"Connection ended\");\n break;\n }\n },\n );\n\n this.activitySub = this.directLine.activity$.subscribe((activity) => {\n try {\n // Track watermark and conversationId from every activity\n if (activity.id) this.watermark = activity.id;\n if (!this.conversationId && activity.conversation?.id) {\n this.conversationId = activity.conversation.id;\n }\n\n // Skip own echoed activities. The echo just means DirectLine\n // routed our message back through the conversation channel —\n // it does NOT mean the bot has read or processed it. We\n // intentionally do **not** touch `pendingIds` here: the\n // echo arrives over the WebSocket in ~50 ms, well before\n // the bot's actual reply (which can take seconds). If we\n // shifted ids off the queue on echo, the bot-message\n // branch below would find an empty queue and never flip\n // anything to \"read\". `flushPendingToRead()` clears the\n // queue in one shot when an actual bot message lands.\n if (activity.from.id === this.userId) {\n return;\n }\n\n // Bot-initiated event\n if (activity.type === \"event\" && activity.name) {\n // Built-in: DisableFeedbackButton — mark the message as feedback-sent\n if (\n activity.name === \"DisableFeedbackButton\" &&\n activity.value\n ) {\n const val = activity.value as {\n CorrelationId?: string;\n FeedbackType?: number;\n };\n if (val.CorrelationId) {\n this.handleDisableFeedback(\n val.CorrelationId,\n val.FeedbackType,\n );\n }\n }\n\n // Dispatch to custom event handler (if registered)\n const handler = this.options.eventHandlers?.[activity.name];\n if (handler) {\n handler(this.createEventContext(activity));\n }\n return;\n }\n\n const result = mapActivityToMessage(activity, this.userId);\n\n if (result === TYPING_SENTINEL) {\n this.handleTyping();\n return;\n }\n\n if (result !== null) {\n // Clear typing indicator when a message arrives\n this.clearTypingTimeout();\n this.typingHandler?.(false);\n\n // First bot message → resolve connect() and signal \"connected\"\n if (this.resolveReady) {\n this.resolveReady();\n this.resolveReady = null;\n }\n\n // The bot just replied — flip every still-pending user\n // message to \"read\" (double-tick). Only an actual incoming\n // message counts; typing indicators and events do not.\n this.flushPendingToRead();\n\n this.messageHandler?.(result);\n\n // Persist state after delivering a message\n if (this.options.resumeConversation) {\n this.persistConversation();\n }\n }\n } catch (err) {\n console.warn(\n \"[DirectLineConnector] Activity mapping error:\",\n err,\n );\n }\n });\n }\n\n /** Build the context object passed to custom event handlers. */\n private createEventContext(activity: Activity): EventHandlerContext {\n const from = { id: this.userId, name: this.userName ?? this.userId };\n return {\n activity,\n userId: this.userId,\n userName: this.userName ?? this.userId,\n postEvent: (name: string, value?: unknown) => {\n this.directLine\n .postActivity({\n type: \"event\",\n name,\n from,\n ...(this.options.locale\n ? { locale: this.options.locale }\n : {}),\n value,\n })\n .subscribe();\n },\n chativa: this.chativaCtx!,\n };\n }\n\n /** Send webchat/join (first connect) or webchat/rejoin (reconnect) event. */\n private sendJoinEvent() {\n const locale = this.options.locale;\n const from = { id: this.userId, name: this.userName ?? this.userId };\n // Read reserved for future join-vs-rejoin branching; keeps the field alive.\n void this.hasConnectedBefore;\n\n // Skip join/rejoin when resuming a persisted conversation\n if (this._skipNextJoin) {\n this._skipNextJoin = false;\n this.hasConnectedBefore = true;\n this.directLine\n .postActivity({\n type: \"event\",\n name: \"webchat/rejoin\",\n from,\n ...(locale ? { locale } : {}),\n value: { ...(locale ? { language: locale } : {}) },\n })\n .subscribe();\n return;\n }\n this.directLine\n .postActivity({\n type: \"event\",\n name: \"webchat/join\",\n from,\n ...(locale ? { locale } : {}),\n value: {\n ...(locale ? { language: locale } : {}),\n ...this.options.joinParameters,\n },\n })\n .subscribe();\n }\n\n /** Handle DisableFeedbackButton event — find the message by correlationId and patch it. */\n private handleDisableFeedback(\n correlationId: string,\n feedbackType?: number,\n ) {\n const messages = this.chativaCtx?.messages.getAll();\n if (!messages) return;\n const msg = messages.find(\n (m) =>\n (m.data?.channelData as Record<string, unknown> | undefined)\n ?.correlationId === correlationId,\n );\n if (msg) {\n this.chativaCtx!.messages.update(msg.id, {\n data: { ...msg.data, feedbackDisabled: true, feedbackType },\n });\n }\n }\n\n /**\n * Mark every still-pending user message as \"read\" (double-tick).\n * Called only when an actual bot **message** arrives — typing\n * indicators, events, and the WebSocket echo of our own activity\n * do not flush, because none of them are visible \"the bot replied\"\n * signals from a user perspective.\n */\n private flushPendingToRead() {\n if (!this.messageStatusHandler || this.pendingIds.length === 0) return;\n for (const id of this.pendingIds) {\n this.messageStatusHandler(id, \"read\");\n }\n this.pendingIds = [];\n }\n\n private handleTyping() {\n this.clearTypingTimeout();\n this.typingHandler?.(true);\n if (this.options.typingUntilMessage) {\n // No timer — rely on next bot message (handled by ChatEngine) to clear.\n return;\n }\n const ms = this.options.typingTimeoutMs ?? 3000;\n this.typingTimeout = setTimeout(() => {\n this.typingHandler?.(false);\n this.typingTimeout = null;\n }, ms);\n }\n\n private clearTypingTimeout() {\n if (this.typingTimeout !== null) {\n clearTimeout(this.typingTimeout);\n this.typingTimeout = null;\n }\n }\n\n /* ── Token refresh ──────────────────────────────────────────────── */\n\n /** Schedule a token refresh 60 seconds before expiry. */\n private scheduleTokenRefresh() {\n this.clearRefreshTimer();\n const expiry = getTokenExpiry(this.token);\n if (!expiry) return;\n\n const delay = expiry - Date.now() - 60_000;\n if (delay <= 0) {\n // Token is already about to expire — refresh immediately\n this.refreshTokenNow();\n return;\n }\n this.refreshTimer = setTimeout(() => this.refreshTokenNow(), delay);\n }\n\n /** Pre-emptively refresh the token before it expires. */\n private async refreshTokenNow() {\n try {\n const result = await refreshDirectLineToken(\n this.token,\n this.options.domain,\n );\n this.token = result.token;\n this.scheduleTokenRefresh();\n if (this.options.resumeConversation) {\n this.persistConversation();\n }\n } catch (err) {\n console.warn(\"[DirectLineConnector] Token refresh failed:\", err);\n }\n }\n\n /**\n * Called when DirectLine emits ExpiredToken status.\n * Refreshes the token and recreates the DirectLine connection.\n */\n private async handleExpiredToken() {\n try {\n const result = await refreshDirectLineToken(\n this.token,\n this.options.domain,\n );\n this.token = result.token;\n this.scheduleTokenRefresh();\n } catch {\n if (this.options.resumeConversation) {\n this.clearPersistedConversation();\n }\n this.disconnectHandler?.(\"Token expired and refresh failed\");\n return;\n }\n\n // Tear down old DirectLine\n this.activitySub?.unsubscribe();\n this.connectionSub?.unsubscribe();\n this.clearTypingTimeout();\n try {\n this.directLine.end();\n } catch {\n /* already ended */\n }\n\n // Recreate with new token, preserving conversation\n this.directLine = new DirectLine({\n token: this.token,\n domain: this.options.domain,\n conversationId: this.conversationId,\n watermark: this.watermark,\n });\n\n this.startListening(true);\n\n if (this.options.resumeConversation) {\n this.persistConversation();\n }\n }\n\n private clearRefreshTimer() {\n if (this.refreshTimer !== null) {\n clearTimeout(this.refreshTimer);\n this.refreshTimer = null;\n }\n }\n\n /* ── Conversation persistence ───────────────────────────────────── */\n\n private persistConversation() {\n if (!this.conversationId || !this.token) return;\n try {\n const data: PersistedConversation = {\n conversationId: this.conversationId,\n token: this.token,\n watermark: this.watermark,\n userId: this.userId,\n };\n localStorage.setItem(CONVERSATION_KEY, JSON.stringify(data));\n } catch {\n // localStorage unavailable\n }\n }\n\n private loadPersistedConversation(): PersistedConversation | null {\n try {\n const raw = localStorage.getItem(CONVERSATION_KEY);\n if (!raw) return null;\n return JSON.parse(raw) as PersistedConversation;\n } catch {\n return null;\n }\n }\n\n private clearPersistedConversation() {\n try {\n localStorage.removeItem(CONVERSATION_KEY);\n } catch {\n // best-effort\n }\n }\n}\n"],"names":["TYPING_SENTINEL","CT_HERO","CT_THUMBNAIL","CT_ADAPTIVE","CT_SIGNIN","CT_OAUTH","CT_RECEIPT","CT_AUDIO","CT_VIDEO","CT_ANIMATION","CT_FLEX","mapActivityToMessage","activity","userId","msg","id","timestamp","channelData","suggestedActions","mapSuggestedActions","result","mapAttachments","attachments","layout","cardTypes","adaptiveCardToCardData","mapHeroLikeCard","mapSingleAttachment","att","ct","content","mapAdaptiveCard","mapCardButtons","mapReceiptCard","mediaUrl","buttons","btn","label","actions","a","walkAdaptiveElements","elements","texts","onImage","el","mapAdaptiveActions","col","parseAdaptiveCardBody","image","url","fallback","out","lines","fact","item","price","USER_ID_KEY","CONVERSATION_KEY","randomUserId","getOrCreateUserId","stored","getTokenExpiry","token","payload","fetchToken","secret","res","data","fetchTokenFromUrl","refreshDirectLineToken","currentToken","domain","DirectLineConnector","options","ctx","name","handler","isResuming","persisted","refreshed","DirectLine","ready","resolve","message","reject","err","file","formData","rawType","type","messageId","feedback","correlationId","m","feedbackType","cursor","messages","callback","resolveOnOnline","status","ConnectionStatus","val","from","value","locale","ms","expiry","delay","raw"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -101,6 +101,14 @@ export declare class DirectLineConnector implements IConnector {
|
|
|
101
101
|
private sendJoinEvent;
|
|
102
102
|
/** Handle DisableFeedbackButton event — find the message by correlationId and patch it. */
|
|
103
103
|
private handleDisableFeedback;
|
|
104
|
+
/**
|
|
105
|
+
* Mark every still-pending user message as "read" (double-tick).
|
|
106
|
+
* Called only when an actual bot **message** arrives — typing
|
|
107
|
+
* indicators, events, and the WebSocket echo of our own activity
|
|
108
|
+
* do not flush, because none of them are visible "the bot replied"
|
|
109
|
+
* signals from a user perspective.
|
|
110
|
+
*/
|
|
111
|
+
private flushPendingToRead;
|
|
104
112
|
private handleTyping;
|
|
105
113
|
private clearTypingTimeout;
|
|
106
114
|
/** Schedule a token refresh 60 seconds before expiry. */
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { DirectLine as v, ConnectionStatus as l } from "botframework-directlinejs";
|
|
2
2
|
const f = /* @__PURE__ */ Symbol("typing"), k = "application/vnd.microsoft.card.hero", b = "application/vnd.microsoft.card.thumbnail", m = "application/vnd.microsoft.card.adaptive", H = "application/vnd.microsoft.card.signin", A = "application/vnd.microsoft.card.oauth", D = "application/vnd.microsoft.card.receipt", E = "application/vnd.microsoft.card.audio", $ = "application/vnd.microsoft.card.video", N = "application/vnd.microsoft.card.animation", T = "application/vnd.microsoft.card.flex";
|
|
3
|
-
function
|
|
3
|
+
function g(i, t) {
|
|
4
4
|
if (i.from.id === t) return null;
|
|
5
5
|
if (i.type === "typing") return f;
|
|
6
6
|
if (i.type !== "message") return null;
|
|
7
|
-
const e = i, n = e.id ?? `dl-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, s = e.timestamp ? new Date(e.timestamp).getTime() : Date.now(), r = e.channelData, o =
|
|
7
|
+
const e = i, n = e.id ?? `dl-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`, s = e.timestamp ? new Date(e.timestamp).getTime() : Date.now(), r = e.channelData, o = U(e);
|
|
8
8
|
if (e.attachments && e.attachments.length > 0) {
|
|
9
9
|
const a = L(e, n, s);
|
|
10
10
|
return a && (o.length > 0 && (a.actions = o), r && (a.data.channelData = r)), a;
|
|
@@ -30,14 +30,14 @@ function L(i, t, e) {
|
|
|
30
30
|
type: "carousel",
|
|
31
31
|
from: "bot",
|
|
32
32
|
data: {
|
|
33
|
-
cards: n.map((a) => a.contentType === m ?
|
|
33
|
+
cards: n.map((a) => a.contentType === m ? P(
|
|
34
34
|
a.content
|
|
35
35
|
) : I(a.content))
|
|
36
36
|
},
|
|
37
37
|
timestamp: e
|
|
38
|
-
} :
|
|
38
|
+
} : R(n[0], i, t, e);
|
|
39
39
|
}
|
|
40
|
-
function
|
|
40
|
+
function R(i, t, e, n) {
|
|
41
41
|
const s = i.contentType;
|
|
42
42
|
if (s === k || s === b || s === T) {
|
|
43
43
|
const r = i.content;
|
|
@@ -50,7 +50,7 @@ function U(i, t, e, n) {
|
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
if (s === m)
|
|
53
|
-
return
|
|
53
|
+
return _(i.content, e, n);
|
|
54
54
|
if (s === H || s === A) {
|
|
55
55
|
const r = i.content;
|
|
56
56
|
return {
|
|
@@ -65,7 +65,7 @@ function U(i, t, e, n) {
|
|
|
65
65
|
};
|
|
66
66
|
}
|
|
67
67
|
if (s === D)
|
|
68
|
-
return
|
|
68
|
+
return O(i.content, e, n);
|
|
69
69
|
if (s === $) {
|
|
70
70
|
const r = i.content, o = r.media?.[0]?.url;
|
|
71
71
|
return o ? {
|
|
@@ -160,7 +160,7 @@ function C(i) {
|
|
|
160
160
|
}
|
|
161
161
|
});
|
|
162
162
|
}
|
|
163
|
-
function
|
|
163
|
+
function U(i) {
|
|
164
164
|
const t = i.suggestedActions?.actions;
|
|
165
165
|
return !t || t.length === 0 ? [] : t.map((e) => {
|
|
166
166
|
const n = ("title" in e ? e.title : void 0) ?? String(e.value ?? "");
|
|
@@ -205,7 +205,7 @@ function w(i) {
|
|
|
205
205
|
}
|
|
206
206
|
), Array.isArray(i.actions) && x(i.actions, n), { texts: t, image: e, buttons: n };
|
|
207
207
|
}
|
|
208
|
-
function
|
|
208
|
+
function P(i) {
|
|
209
209
|
const { texts: t, image: e, buttons: n } = w(i);
|
|
210
210
|
return {
|
|
211
211
|
image: e,
|
|
@@ -215,7 +215,7 @@ function _(i) {
|
|
|
215
215
|
buttons: n
|
|
216
216
|
};
|
|
217
217
|
}
|
|
218
|
-
function
|
|
218
|
+
function _(i, t, e) {
|
|
219
219
|
const { texts: n, image: s, buttons: r } = w(i), o = i.fallbackText ?? i.speak ?? n.join(`
|
|
220
220
|
`);
|
|
221
221
|
return s ? {
|
|
@@ -253,7 +253,7 @@ function x(i, t) {
|
|
|
253
253
|
e.type === "Action.OpenUrl" && e.url ? t.push({ label: n, url: e.url }) : e.type === "Action.Submit" ? t.push({ label: n, value: typeof e.data == "string" ? e.data : n }) : t.push({ label: n, value: n });
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
|
-
function
|
|
256
|
+
function O(i, t, e) {
|
|
257
257
|
const n = [];
|
|
258
258
|
if (i.title && n.push(`**${i.title}**`), i.facts)
|
|
259
259
|
for (const s of i.facts)
|
|
@@ -272,19 +272,19 @@ function P(i, t, e) {
|
|
|
272
272
|
timestamp: e
|
|
273
273
|
};
|
|
274
274
|
}
|
|
275
|
-
const
|
|
275
|
+
const y = "chativa_directline_userId", h = "chativa_directline_conversation";
|
|
276
276
|
function S() {
|
|
277
277
|
return Math.random().toString(36).slice(2, 15) + Math.random().toString(36).slice(2, 15);
|
|
278
278
|
}
|
|
279
279
|
function B() {
|
|
280
280
|
try {
|
|
281
|
-
const t = localStorage.getItem(
|
|
281
|
+
const t = localStorage.getItem(y);
|
|
282
282
|
if (t) return t;
|
|
283
283
|
} catch {
|
|
284
284
|
}
|
|
285
285
|
const i = S();
|
|
286
286
|
try {
|
|
287
|
-
localStorage.setItem(
|
|
287
|
+
localStorage.setItem(y, i);
|
|
288
288
|
} catch {
|
|
289
289
|
}
|
|
290
290
|
return i;
|
|
@@ -521,7 +521,7 @@ class G {
|
|
|
521
521
|
});
|
|
522
522
|
continue;
|
|
523
523
|
}
|
|
524
|
-
const c =
|
|
524
|
+
const c = g(a, this.userId);
|
|
525
525
|
c !== null && c !== f && o.push(c);
|
|
526
526
|
}
|
|
527
527
|
return {
|
|
@@ -570,13 +570,8 @@ class G {
|
|
|
570
570
|
}
|
|
571
571
|
), this.activitySub = this.directLine.activity$.subscribe((e) => {
|
|
572
572
|
try {
|
|
573
|
-
if (e.id && (this.watermark = e.id), !this.conversationId && e.conversation?.id && (this.conversationId = e.conversation.id), e.from.id === this.userId)
|
|
574
|
-
if (e.type === "message") {
|
|
575
|
-
const s = this.pendingIds.shift();
|
|
576
|
-
s && this.messageStatusHandler?.(s, "read");
|
|
577
|
-
}
|
|
573
|
+
if (e.id && (this.watermark = e.id), !this.conversationId && e.conversation?.id && (this.conversationId = e.conversation.id), e.from.id === this.userId)
|
|
578
574
|
return;
|
|
579
|
-
}
|
|
580
575
|
if (e.type === "event" && e.name) {
|
|
581
576
|
if (e.name === "DisableFeedbackButton" && e.value) {
|
|
582
577
|
const r = e.value;
|
|
@@ -589,12 +584,12 @@ class G {
|
|
|
589
584
|
s && s(this.createEventContext(e));
|
|
590
585
|
return;
|
|
591
586
|
}
|
|
592
|
-
const n =
|
|
587
|
+
const n = g(e, this.userId);
|
|
593
588
|
if (n === f) {
|
|
594
589
|
this.handleTyping();
|
|
595
590
|
return;
|
|
596
591
|
}
|
|
597
|
-
n !== null && (this.clearTypingTimeout(), this.typingHandler?.(!1), this.resolveReady && (this.resolveReady(), this.resolveReady = null), this.messageHandler?.(n), this.options.resumeConversation && this.persistConversation());
|
|
592
|
+
n !== null && (this.clearTypingTimeout(), this.typingHandler?.(!1), this.resolveReady && (this.resolveReady(), this.resolveReady = null), this.flushPendingToRead(), this.messageHandler?.(n), this.options.resumeConversation && this.persistConversation());
|
|
598
593
|
} catch (n) {
|
|
599
594
|
console.warn(
|
|
600
595
|
"[DirectLineConnector] Activity mapping error:",
|
|
@@ -657,6 +652,20 @@ class G {
|
|
|
657
652
|
data: { ...s.data, feedbackDisabled: !0, feedbackType: e }
|
|
658
653
|
});
|
|
659
654
|
}
|
|
655
|
+
/**
|
|
656
|
+
* Mark every still-pending user message as "read" (double-tick).
|
|
657
|
+
* Called only when an actual bot **message** arrives — typing
|
|
658
|
+
* indicators, events, and the WebSocket echo of our own activity
|
|
659
|
+
* do not flush, because none of them are visible "the bot replied"
|
|
660
|
+
* signals from a user perspective.
|
|
661
|
+
*/
|
|
662
|
+
flushPendingToRead() {
|
|
663
|
+
if (!(!this.messageStatusHandler || this.pendingIds.length === 0)) {
|
|
664
|
+
for (const t of this.pendingIds)
|
|
665
|
+
this.messageStatusHandler(t, "read");
|
|
666
|
+
this.pendingIds = [];
|
|
667
|
+
}
|
|
668
|
+
}
|
|
660
669
|
handleTyping() {
|
|
661
670
|
if (this.clearTypingTimeout(), this.typingHandler?.(!0), this.options.typingUntilMessage)
|
|
662
671
|
return;
|