@chativa/connector-directline 0.1.0-beta.8 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +5 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +153 -4
- package/dist/index.js +700 -39
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("botframework-directlinejs"),f=Symbol("typing"),g="application/vnd.microsoft.card.hero",k="application/vnd.microsoft.card.thumbnail",p="application/vnd.microsoft.card.adaptive",S="application/vnd.microsoft.card.signin",x="application/vnd.microsoft.card.oauth",H="application/vnd.microsoft.card.receipt",A="application/vnd.microsoft.card.audio",D="application/vnd.microsoft.card.video",E="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=N(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=[g,k,b,p];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===p?R(a.content):T(a.content))},timestamp:e}:L(n[0],i,t,e)}function L(i,t,e,n){const s=i.contentType;if(s===g||s===k||s===b){const r=i.content;return{id:e,type:"card",from:"bot",data:T(r),timestamp:n}}if(s===p)return U(i.content,e,n);if(s===S||s===x){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===H)return _(i.content,e,n);if(s===D){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===A){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===E){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 N(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 R(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 U(i,t,e){const{texts:n,image:s,buttons:r}=C(i),o=i.fallbackText??i.speak??n.join(`
|
|
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 _(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 y="chativa_directline_userId",h="chativa_directline_conversation";function O(){try{const t=localStorage.getItem(y);if(t)return t}catch{}const i=Math.random().toString(36).slice(2,15)+Math.random().toString(36).slice(2,15);try{localStorage.setItem(y,i)}catch{}return i}function P(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 B(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 F{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??O(),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 B(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 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 m=a;m.text&&o.push({id:a.id??`dl-${Date.now()}-${Math.random().toString(36).slice(2,7)}`,type:"text",from:"user",data:{text:m.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._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(){this.clearTypingTimeout(),this.typingHandler?.(!0),this.typingTimeout=setTimeout(()=>{this.typingHandler?.(!1),this.typingTimeout=null},3e3)}clearTypingTimeout(){this.typingTimeout!==null&&(clearTimeout(this.typingTimeout),this.typingTimeout=null)}scheduleTokenRefresh(){this.clearRefreshTimer();const t=P(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=F;
|
|
2
6
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/DirectLineConnector.ts"],"sourcesContent":["import type { IConnector, MessageHandler } from \"@chativa/core\";\nimport type { OutgoingMessage } from \"@chativa/core\";\nimport { DirectLine } from \"botframework-directlinejs\";\n\nexport interface DirectLineConnectorOptions {\n secret?: string;\n token?: string;\n tokenGeneratorUrl?: string;\n}\n\nfunction createUserId(): string {\n return (\n Math.random().toString(36).slice(2, 15) +\n Math.random().toString(36).slice(2, 15)\n );\n}\n\nasync function fetchToken(\n secret: string\n): Promise<{ token: string; conversationId: string; userId: string }> {\n const userId = createUserId();\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 { token, conversationId } = (await res.json()) as {\n token: string;\n conversationId: string;\n };\n return { token, conversationId, userId };\n}\n\n/**\n * DirectLineConnector — Azure Bot Framework DirectLine v3 adapter.\n *\n * The bot echoes messages back, so we set addSentToHistory = false\n * to prevent user messages appearing twice.\n */\nexport class DirectLineConnector implements IConnector {\n readonly name = \"directline\";\n readonly addSentToHistory = false;\n\n private directLine!: DirectLine;\n private conversationId!: string;\n private userId!: string;\n private messageHandler: MessageHandler | null = null;\n private options: DirectLineConnectorOptions;\n\n constructor(options: DirectLineConnectorOptions) {\n this.options = options;\n }\n\n async connect(): Promise<void> {\n let token: string;\n\n if (this.options.token) {\n token = this.options.token;\n this.userId = createUserId();\n } else if (this.options.secret) {\n const result = await fetchToken(this.options.secret);\n token = result.token;\n this.conversationId = result.conversationId;\n this.userId = result.userId;\n } else {\n throw new Error(\"DirectLineConnector: provide either token or secret.\");\n }\n\n this.directLine = new DirectLine({ token });\n\n this.directLine.activity$\n .filter((a) => a.type === \"message\")\n .subscribe((activity) => {\n this.messageHandler?.({\n id: activity.id ?? `dl-${Date.now()}`,\n type: \"text\",\n data: { text: activity.text ?? \"\" },\n timestamp: Date.now(),\n });\n });\n }\n\n async disconnect(): Promise<void> {\n // DirectLine SDK doesn't expose an explicit close method\n this.messageHandler = null;\n }\n\n async sendMessage(message: OutgoingMessage): Promise<void> {\n this.directLine\n .postActivity({\n type: \"message\",\n from: { id: this.userId },\n text: (message.data as { text?: string }).text ?? \"\",\n conversation: { id: this.conversationId },\n channelId: \"directline\",\n timestamp: new Date().toISOString(),\n id: message.id,\n })\n .subscribe();\n }\n\n onMessage(callback: MessageHandler): void {\n this.messageHandler = callback;\n }\n}\n"],"names":["createUserId","fetchToken","secret","userId","res","token","conversationId","DirectLineConnector","options","result","DirectLine","a","activity","message","callback"],"mappings":"6HAUA,SAASA,GAAuB,CAC9B,OACE,KAAK,SAAS,SAAS,EAAE,EAAE,MAAM,EAAG,EAAE,EACtC,KAAK,SAAS,SAAS,EAAE,EAAE,MAAM,EAAG,EAAE,CAE1C,CAEA,eAAeC,EACbC,EACoE,CACpE,MAAMC,EAASH,EAAA,EACTI,EAAM,MAAM,MAChB,oEACA,CACE,OAAQ,OACR,QAAS,CACP,cAAe,UAAUF,CAAM,GAC/B,eAAgB,kBAAA,EAElB,KAAM,KAAK,UAAU,CAAE,KAAM,CAAE,GAAIC,EAAQ,KAAMA,EAAO,CAAG,CAAA,CAC7D,EAEF,GAAI,CAACC,EAAI,GACP,MAAM,IAAI,MAAM,kCAAkCA,EAAI,MAAM,EAAE,EAEhE,KAAM,CAAE,MAAAC,EAAO,eAAAC,CAAA,EAAoB,MAAMF,EAAI,KAAA,EAI7C,MAAO,CAAE,MAAAC,EAAO,eAAAC,EAAgB,OAAAH,CAAA,CAClC,CAQO,MAAMI,CAA0C,CAUrD,YAAYC,EAAqC,CATjD,KAAS,KAAO,aAChB,KAAS,iBAAmB,GAK5B,KAAQ,eAAwC,KAI9C,KAAK,QAAUA,CACjB,CAEA,MAAM,SAAyB,CAC7B,IAAIH,EAEJ,GAAI,KAAK,QAAQ,MACfA,EAAQ,KAAK,QAAQ,MACrB,KAAK,OAASL,EAAA,UACL,KAAK,QAAQ,OAAQ,CAC9B,MAAMS,EAAS,MAAMR,EAAW,KAAK,QAAQ,MAAM,EACnDI,EAAQI,EAAO,MACf,KAAK,eAAiBA,EAAO,eAC7B,KAAK,OAASA,EAAO,MACvB,KACE,OAAM,IAAI,MAAM,sDAAsD,EAGxE,KAAK,WAAa,IAAIC,aAAW,CAAE,MAAAL,EAAO,EAE1C,KAAK,WAAW,UACb,OAAQM,GAAMA,EAAE,OAAS,SAAS,EAClC,UAAWC,GAAa,CACvB,KAAK,iBAAiB,CACpB,GAAIA,EAAS,IAAM,MAAM,KAAK,KAAK,GACnC,KAAM,OACN,KAAM,CAAE,KAAMA,EAAS,MAAQ,EAAA,EAC/B,UAAW,KAAK,IAAA,CAAI,CACrB,CACH,CAAC,CACL,CAEA,MAAM,YAA4B,CAEhC,KAAK,eAAiB,IACxB,CAEA,MAAM,YAAYC,EAAyC,CACzD,KAAK,WACF,aAAa,CACZ,KAAM,UACN,KAAM,CAAE,GAAI,KAAK,MAAA,EACjB,KAAOA,EAAQ,KAA2B,MAAQ,GAClD,aAAc,CAAE,GAAI,KAAK,cAAA,EACzB,UAAW,aACX,UAAW,IAAI,KAAA,EAAO,YAAA,EACtB,GAAIA,EAAQ,EAAA,CACb,EACA,UAAA,CACL,CAEA,UAAUC,EAAgC,CACxC,KAAK,eAAiBA,CACxB,CACF"}
|
|
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} 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 /** Override the user ID instead of auto-generating one. */\n userId?: string;\n /** Display name sent with activities. */\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 * to localStorage so the conversation can be resumed across page reloads.\n */\n resumeConversation?: 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 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 =\n Math.random().toString(36).slice(2, 15) +\n Math.random().toString(36).slice(2, 15);\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 (used for join vs rejoin). */\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 = this.options.userId ?? getOrCreateUserId();\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 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\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 // Auto-clear typing after 3 seconds (bot may not send a stop signal)\n this.typingTimeout = setTimeout(() => {\n this.typingHandler?.(false);\n this.typingTimeout = null;\n }, 3000);\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","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","messageId","feedback","correlationId","m","feedbackType","cursor","messages","callback","resolveOnOnline","status","ConnectionStatus","pendingId","val","from","value","locale","expiry","delay","raw"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,32 +1,181 @@
|
|
|
1
|
+
import { Activity } from 'botframework-directlinejs';
|
|
2
|
+
import { ChativaContext } from '../../core/src/index.ts';
|
|
3
|
+
import { ConnectHandler } from '../../core/src/index.ts';
|
|
4
|
+
import { DisconnectHandler } from '../../core/src/index.ts';
|
|
5
|
+
import { HistoryResult } from '../../core/src/index.ts';
|
|
1
6
|
import { IConnector } from '../../core/src/index.ts';
|
|
2
7
|
import { MessageHandler } from '../../core/src/index.ts';
|
|
8
|
+
import { MessageStatusHandler } from '../../core/src/index.ts';
|
|
3
9
|
import { OutgoingMessage } from '../../core/src/index.ts';
|
|
10
|
+
import { TypingHandler } from '../../core/src/index.ts';
|
|
4
11
|
|
|
5
12
|
/**
|
|
6
13
|
* DirectLineConnector — Azure Bot Framework DirectLine v3 adapter.
|
|
7
14
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
15
|
+
* Maps all Bot Framework activity types (hero cards, carousels,
|
|
16
|
+
* suggested actions, images, videos, files, adaptive cards, etc.)
|
|
17
|
+
* into Chativa's native message types.
|
|
10
18
|
*/
|
|
11
19
|
export declare class DirectLineConnector implements IConnector {
|
|
12
20
|
readonly name = "directline";
|
|
13
|
-
readonly addSentToHistory =
|
|
21
|
+
readonly addSentToHistory = true;
|
|
14
22
|
private directLine;
|
|
15
23
|
private conversationId;
|
|
16
24
|
private userId;
|
|
17
|
-
private
|
|
25
|
+
private userName;
|
|
26
|
+
private token;
|
|
18
27
|
private options;
|
|
28
|
+
private chativaCtx;
|
|
29
|
+
private messageHandler;
|
|
30
|
+
private connectHandler;
|
|
31
|
+
private disconnectHandler;
|
|
32
|
+
private typingHandler;
|
|
33
|
+
private messageStatusHandler;
|
|
34
|
+
private activitySub;
|
|
35
|
+
private connectionSub;
|
|
36
|
+
private typingTimeout;
|
|
37
|
+
private refreshTimer;
|
|
38
|
+
/** Queue of sent message IDs awaiting echo confirmation. */
|
|
39
|
+
private pendingIds;
|
|
40
|
+
/** True after the first successful connection (used for join vs rejoin). */
|
|
41
|
+
private hasConnectedBefore;
|
|
42
|
+
/** Resolves the connect() promise once the bot sends its first message after join. */
|
|
43
|
+
private resolveReady;
|
|
44
|
+
/** Last known activity watermark — used for conversation resume. */
|
|
45
|
+
private watermark;
|
|
46
|
+
/** When true, skip the next join/rejoin event (used during resume). */
|
|
47
|
+
private _skipNextJoin;
|
|
19
48
|
constructor(options: DirectLineConnectorOptions);
|
|
49
|
+
setContext(ctx: ChativaContext): void;
|
|
50
|
+
/**
|
|
51
|
+
* Register (or replace) an event handler for a bot-initiated event activity.
|
|
52
|
+
* Can be called before or after `connect()`.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* connector.addEventHandler("LocationRequest", (ctx) => {
|
|
57
|
+
* navigator.geolocation.getCurrentPosition((pos) => {
|
|
58
|
+
* ctx.postEvent("webchat/location", { latitude: pos.coords.latitude, ... });
|
|
59
|
+
* });
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
addEventHandler(name: string, handler: EventHandler): void;
|
|
64
|
+
/** Remove a previously registered event handler by name. */
|
|
65
|
+
removeEventHandler(name: string): boolean;
|
|
66
|
+
/** Check whether an event handler is registered for the given name. */
|
|
67
|
+
hasEventHandler(name: string): boolean;
|
|
68
|
+
/** Return the names of all registered event handlers. */
|
|
69
|
+
getEventHandlerNames(): string[];
|
|
20
70
|
connect(): Promise<void>;
|
|
21
71
|
disconnect(): Promise<void>;
|
|
72
|
+
/** Clear persisted conversation state and reset watermark. */
|
|
73
|
+
clearConversation(): void;
|
|
22
74
|
sendMessage(message: OutgoingMessage): Promise<void>;
|
|
75
|
+
sendFile(file: File): Promise<void>;
|
|
76
|
+
sendFeedback(messageId: string, feedback: "like" | "dislike"): Promise<void>;
|
|
77
|
+
loadHistory(cursor?: string): Promise<HistoryResult>;
|
|
23
78
|
onMessage(callback: MessageHandler): void;
|
|
79
|
+
onConnect(callback: ConnectHandler): void;
|
|
80
|
+
onDisconnect(callback: DisconnectHandler): void;
|
|
81
|
+
onTyping(callback: TypingHandler): void;
|
|
82
|
+
onMessageStatus(callback: MessageStatusHandler): void;
|
|
83
|
+
/**
|
|
84
|
+
* Subscribe to DirectLine connectionStatus$ and activity$ observables.
|
|
85
|
+
* Extracted so it can be re-used after an ExpiredToken reconnect.
|
|
86
|
+
*/
|
|
87
|
+
private startListening;
|
|
88
|
+
/** Build the context object passed to custom event handlers. */
|
|
89
|
+
private createEventContext;
|
|
90
|
+
/** Send webchat/join (first connect) or webchat/rejoin (reconnect) event. */
|
|
91
|
+
private sendJoinEvent;
|
|
92
|
+
/** Handle DisableFeedbackButton event — find the message by correlationId and patch it. */
|
|
93
|
+
private handleDisableFeedback;
|
|
94
|
+
private handleTyping;
|
|
95
|
+
private clearTypingTimeout;
|
|
96
|
+
/** Schedule a token refresh 60 seconds before expiry. */
|
|
97
|
+
private scheduleTokenRefresh;
|
|
98
|
+
/** Pre-emptively refresh the token before it expires. */
|
|
99
|
+
private refreshTokenNow;
|
|
100
|
+
/**
|
|
101
|
+
* Called when DirectLine emits ExpiredToken status.
|
|
102
|
+
* Refreshes the token and recreates the DirectLine connection.
|
|
103
|
+
*/
|
|
104
|
+
private handleExpiredToken;
|
|
105
|
+
private clearRefreshTimer;
|
|
106
|
+
private persistConversation;
|
|
107
|
+
private loadPersistedConversation;
|
|
108
|
+
private clearPersistedConversation;
|
|
24
109
|
}
|
|
25
110
|
|
|
26
111
|
export declare interface DirectLineConnectorOptions {
|
|
112
|
+
/** DirectLine channel secret (server-side only — generates a token). */
|
|
27
113
|
secret?: string;
|
|
114
|
+
/** Pre-fetched DirectLine token. */
|
|
28
115
|
token?: string;
|
|
116
|
+
/** URL of a custom token-generating endpoint (POST, returns { token, conversationId? }). */
|
|
29
117
|
tokenGeneratorUrl?: string;
|
|
118
|
+
/** Override the user ID instead of auto-generating one. */
|
|
119
|
+
userId?: string;
|
|
120
|
+
/** Display name sent with activities. */
|
|
121
|
+
userName?: string;
|
|
122
|
+
/** Sovereign-cloud DirectLine endpoint (e.g. government, china). */
|
|
123
|
+
domain?: string;
|
|
124
|
+
/** BCP-47 locale sent with the webchat/join event and outgoing activities (e.g. "tr-TR"). */
|
|
125
|
+
locale?: string;
|
|
126
|
+
/**
|
|
127
|
+
* Extra key-value pairs merged into the `webchat/join` event's `value` payload.
|
|
128
|
+
* Example: `{ language: "tr", tenant: "galataport" }`
|
|
129
|
+
*/
|
|
130
|
+
joinParameters?: Record<string, unknown>;
|
|
131
|
+
/**
|
|
132
|
+
* Custom handlers for bot-initiated event activities, keyed by event name.
|
|
133
|
+
* Example:
|
|
134
|
+
* ```ts
|
|
135
|
+
* eventHandlers: {
|
|
136
|
+
* LocationRequest: (ctx) => {
|
|
137
|
+
* navigator.geolocation.getCurrentPosition((pos) => {
|
|
138
|
+
* ctx.postEvent("webchat/location", {
|
|
139
|
+
* latitude: pos.coords.latitude,
|
|
140
|
+
* longitude: pos.coords.longitude,
|
|
141
|
+
* });
|
|
142
|
+
* });
|
|
143
|
+
* },
|
|
144
|
+
* }
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
eventHandlers?: Record<string, EventHandler>;
|
|
148
|
+
/**
|
|
149
|
+
* When true, persist conversation state (conversationId, token, watermark)
|
|
150
|
+
* to localStorage so the conversation can be resumed across page reloads.
|
|
151
|
+
*/
|
|
152
|
+
resumeConversation?: boolean;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Handler for a bot-initiated event activity.
|
|
157
|
+
* Keyed by event name (e.g. `"LocationRequest"`).
|
|
158
|
+
*/
|
|
159
|
+
export declare type EventHandler = (ctx: EventHandlerContext) => void;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Context passed to custom event handlers — provides the info and methods
|
|
163
|
+
* needed to respond to a bot-initiated event activity.
|
|
164
|
+
*/
|
|
165
|
+
export declare interface EventHandlerContext {
|
|
166
|
+
/** The raw DirectLine event activity. */
|
|
167
|
+
activity: Activity;
|
|
168
|
+
/** The user ID for this session. */
|
|
169
|
+
userId: string;
|
|
170
|
+
/** The user display name for this session. */
|
|
171
|
+
userName: string;
|
|
172
|
+
/** Send an event activity back to the bot. */
|
|
173
|
+
postEvent(name: string, value?: unknown): void;
|
|
174
|
+
/**
|
|
175
|
+
* Full Chativa context — access messages, chat UI, theme, and event bus.
|
|
176
|
+
* Available when the connector is used via ChatEngine (which injects it).
|
|
177
|
+
*/
|
|
178
|
+
chativa: ChativaContext;
|
|
30
179
|
}
|
|
31
180
|
|
|
32
181
|
export { }
|