birdcash-chat-sdk-alpha 1.0.2 → 1.0.4

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/README.md CHANGED
@@ -70,6 +70,42 @@ await chat.sendMiniAppMessage(msgID, {
70
70
 
71
71
  // Web link preview
72
72
  await chat.sendWebLinkMessage(msgID, 'https://example.com')
73
+
74
+ // Choices prompt (ai_choice_prompt) — returns the new message id
75
+ const choiceMsgID = await chat.sendChoicesMessage(msgID, {
76
+ prompt: 'Which shipping option do you want?',
77
+ choices: [
78
+ { id: 'standard', label: 'Standard (5–7 days)' },
79
+ { id: 'express', label: 'Express (2 days)' },
80
+ ],
81
+ // selectionMode: 'single', // optional, defaults to 'single'
82
+ })
83
+
84
+ // Official-account tweet card — returns the new message id
85
+ await chat.sendOfficialAccountTweetMessage(msgID, {
86
+ title: 'Release notes',
87
+ content: 'What shipped this week…',
88
+ link: 'https://example.com/blog',
89
+ version: 1,
90
+ })
91
+ ```
92
+
93
+ ## Editing & deleting
94
+
95
+ Send with a known `replyMsgID`, then edit that message in place (or delete it):
96
+
97
+ ```ts
98
+ const editable = crypto.randomUUID()
99
+ await chat.sendMessage(msgID, 'Working on it…', editable)
100
+ await chat.editTextMessage(editable, 'Done ✅')
101
+
102
+ // Stickers and images edit in place too
103
+ await chat.sendStickerMessage(msgID, 'capoo', 'capoo_1', editable)
104
+ await chat.editStickerMessage(editable, 'capoo', 'capoo_2')
105
+ await chat.editImageMessage(imgMsgID, [upload_id])
106
+
107
+ // Delete a message you sent
108
+ await chat.deleteMessage(editable)
73
109
  ```
74
110
 
75
111
  ## Streaming (typewriter effect)
@@ -98,6 +134,28 @@ const token = await getAccessToken({
98
134
  // token.access_token, token.expires_in, …
99
135
  ```
100
136
 
137
+ ## Verifying webhooks
138
+
139
+ Validate incoming webhook requests before trusting them. The signature scheme
140
+ matches the server: `HMAC-SHA256(secret, "${timestamp}.${rawBody}")`, sent in the
141
+ `X-Webhook-Signature` (optionally `sha256=`-prefixed) and `X-Webhook-Timestamp`
142
+ headers. Pass the **raw** body text, read before JSON-parsing.
143
+
144
+ ```ts
145
+ import { verifyWebhookSignature } from 'birdcash-chat-sdk-alpha'
146
+
147
+ const raw = await request.text()
148
+ const { valid, error } = await verifyWebhookSignature(request, env.WEBHOOK_SECRET, raw)
149
+ if (!valid) return new Response(error ?? 'bad signature', { status: 401 })
150
+
151
+ const event = JSON.parse(raw)
152
+ // …handle event
153
+ ```
154
+
155
+ It rejects missing headers, bad signatures (constant-time compare), and stale
156
+ timestamps (replay protection, default 300s — override with `{ toleranceSec }`).
157
+ Pass `{ logger: console }` for diagnostics.
158
+
101
159
  ## Lower-level exports
102
160
 
103
161
  The element builders and helpers are also exported if you want to assemble
package/dist/main.cjs.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e="https://chat-api2-3rnt.onrender.com",t="web_link";async function s(t){var s;const{clientId:n,clientSecret:r}=t;if(!n||!r)throw new Error("getAccessToken: clientId and clientSecret are required");const i=e.replace(/\/$/,""),o=null!==(s=t.scope)&&void 0!==s?s:"chat:write uploads:write";if("undefined"==typeof fetch)throw new Error("getAccessToken: global fetch is not available");const a=await fetch(`${i}/v1/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"client_credentials",client_id:n,client_secret:r,scope:o})});if(!a.ok)throw new Error(`Token request failed: ${a.status} ${a.statusText}`);return await a.json()}var n;function r(e){return{elemType:exports.ElemType.Text,priority:0,textElem:{text:e.trim()}}}function i(e){return{elemType:exports.ElemType.Image,priority:0,imageElem:{upload_ids:e}}}function o(e){return{elemType:exports.ElemType.File,priority:0,fileElem:{upload_ids:e}}}function a(e,t=0){return{elemType:exports.ElemType.Sound,priority:0,soundElem:{uploadID:e,duration:t}}}function l(e,t){return{elemType:exports.ElemType.Sticker,priority:0,stickerElem:{packID:e,stickerID:t}}}function u(e){return{elemType:exports.ElemType.Custom,priority:0,customElem:{data:e}}}function p(e){return{elemType:exports.ElemType.MiniApp,priority:0,miniAppElem:e}}function c(e){const t=new Uint8Array(e.byteLength);return t.set(e),t}function d(e){const t=(new TextEncoder).encode(e);let s="";for(const e of t)s+=String.fromCharCode(e);return btoa(s)}function h(e,t=10){if(e.length<=t)return[e];const s=[];let n="";for(const r of e.split(" "))n.length+r.length+1>t&&n.length>0?(s.push(n.trim()+" "),n=r):n+=(n?" ":"")+r;return n&&s.push(n.trim()),s.length>0?s:[e]}exports.ElemType=void 0,(n=exports.ElemType||(exports.ElemType={}))[n.None=0]="None",n[n.Text=1]="Text",n[n.Image=2]="Image",n[n.Sound=3]="Sound",n[n.Video=4]="Video",n[n.File=5]="File",n[n.Sticker=6]="Sticker",n[n.GroupTips=7]="GroupTips",n[n.Merger=8]="Merger",n[n.Custom=9]="Custom",n[n.Location=10]="Location",n[n.GroupAnnouncement=11]="GroupAnnouncement",n[n.Quote=12]="Quote",n[n.InputStatus=14]="InputStatus",n[n.TypingStatus=15]="TypingStatus",n[n.MiniApp=16]="MiniApp",n[n.Order=17]="Order",n[n.Transfer=18]="Transfer";class g{constructor(t){var s,n;if(!(null==t?void 0:t.token))throw new Error("ChatClient: token is required");this.token=t.token,this.baseUrl=(null!==(s=t.baseUrl)&&void 0!==s?s:e).replace(/\/$/,""),this.logger=t.logger;const r=null!==(n=t.fetch)&&void 0!==n?n:"undefined"!=typeof fetch?fetch:void 0;if(!r)throw new Error("ChatClient: no fetch implementation available; pass `fetch` in options");this.fetchImpl=r.bind(globalThis)}static async fromCredentials(e){const{access_token:t}=await s({clientId:e.clientId,clientSecret:e.clientSecret,scope:e.scope});return new g({token:t,baseUrl:e.baseUrl,logger:e.logger,fetch:e.fetch})}get authHeaders(){return{"Content-Type":"application/json",Authorization:`Bearer ${this.token}`}}replyUrl(e){return`${this.baseUrl}/v1/chat/message/${e}/reply`}async postElements(e,t,s="POST"){return await this.fetchImpl(this.replyUrl(e),{method:s,headers:this.authHeaders,body:JSON.stringify(t)})}async uploadImage(e,t,s="image/png"){var n;const r=new FormData;r.append("file",new Blob([c(e)],{type:s}),t);const i=await this.fetchImpl(`${this.baseUrl}/v1/upload/image`,{method:"POST",headers:{Authorization:`Bearer ${this.token}`},body:r}),o=await i.json();if(!i.ok)throw new Error(`uploadImage failed: ${i.status} ${JSON.stringify(o)}`);if(!o.upload_id)throw new Error(`uploadImage: missing upload_id: ${JSON.stringify(o)}`);return{upload_id:o.upload_id,url:null!==(n=o.url)&&void 0!==n?n:""}}async uploadFile(e,t,s="application/octet-stream"){var n;const r=new FormData;r.append("file",new Blob([c(e)],{type:s}),t);const i=await this.fetchImpl(`${this.baseUrl}/v1/upload/file`,{method:"POST",headers:{Authorization:`Bearer ${this.token}`},body:r}),o=await i.json();if(!i.ok)throw new Error(`uploadFile failed: ${i.status} ${JSON.stringify(o)}`);if(!o.upload_id)throw new Error(`uploadFile: missing upload_id: ${JSON.stringify(o)}`);return{upload_id:o.upload_id,url:null!==(n=o.url)&&void 0!==n?n:""}}async sendMessage(e,t){var s;const n=Array.isArray(t)?t.map((e=>e.trim())).filter(Boolean).map(r):[r(t)];if(0===n.length)return;const i=await this.postElements(e,n),o=await i.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] message sent",{status:i.status,elements:n.length}),!i.ok)throw new Error(`sendMessage failed: ${i.status} ${o}`)}async sendImageMessage(e,t){var s;if(!(null==t?void 0:t.length))return;const n=await this.postElements(e,[i(t)]),r=await n.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] image message sent",{status:n.status}),!n.ok)throw new Error(`sendImageMessage failed: ${n.status} ${r}`)}async sendFileMessage(e,t){var s;if(!(null==t?void 0:t.length))return;const n=await this.postElements(e,[o(t)]),r=await n.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] file message sent",{status:n.status}),!n.ok)throw new Error(`sendFileMessage failed: ${n.status} ${r}`)}async sendSoundMessage(e,t,s=0){var n;if(!(null==t?void 0:t.trim()))return;const r=await this.postElements(e,[a(t,s)]),i=await r.text();if(null===(n=this.logger)||void 0===n||n.log("[chat-sdk] sound message sent",{status:r.status}),!r.ok)throw new Error(`sendSoundMessage failed: ${r.status} ${i.slice(0,500)}`)}async sendStickerMessage(e,t,s){var n;if(!(null==t?void 0:t.trim())||!(null==s?void 0:s.trim()))return;const r=await this.postElements(e,[l(t,s)]),i=await r.text();if(null===(n=this.logger)||void 0===n||n.log("[chat-sdk] sticker message sent",{status:r.status}),!r.ok)throw new Error(`sendStickerMessage failed: ${r.status} ${i}`)}async sendTypingStatus(e,t){var s,n;const r={businessID:"user_typing_status",typingStatus:t?1:0,version:1,userAction:14,actionParam:t?"EIMAMSG_InputStatus_Ing":"EIMAMSG_InputStatus_End"};try{await this.postElements(e,[u(d(JSON.stringify(r)))])}catch(e){null===(s=this.logger)||void 0===s||s.error("[chat-sdk] typing status failed",null!==(n=null==e?void 0:e.message)&&void 0!==n?n:String(e))}}async sendStreamingChunk(e,t,s,n){const r={businessID:"chatbotPlugin",src:0,chunks:t,isFinished:s,TMessageCell_Name:"ChatbotMessageCell_Minimalist",TMessageCell_Data_Name:"ChatbotMessageCellData"},i={msgID:e,...u(d(JSON.stringify(r)))},o=await this.postElements(e,[i],n?"POST":"PATCH");if(!o.ok)throw new Error(`sendStreamingChunk failed: ${o.statusText}`);await o.text()}async sendMiniAppMessage(e,t){var s;const n=await this.postElements(e,[p(t)]),r=await n.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] miniapp card sent",{status:n.status}),!n.ok)throw new Error(`sendMiniAppMessage failed: ${n.status} ${r}`)}async sendWebLinkMessage(e,s){var n;const r={businessID:t,url:s},i=await this.postElements(e,[u(d(JSON.stringify(r)))]),o=await i.text();if(null===(n=this.logger)||void 0===n||n.log("[chat-sdk] web link sent",{status:i.status}),!i.ok)throw new Error(`sendWebLinkMessage failed: ${i.status} ${o.slice(0,500)}`)}async streamMessage(e,t,s=10){const n=h(t,s),r=[];for(let t=0;t<n.length;t++)r.push(n[t]),await this.sendStreamingChunk(e,r,t===n.length-1,0===t)}}g.ElemType=exports.ElemType,exports.ChatClient=g,exports.DEFAULT_BASE_URL=e,exports.WEB_LINK_BUSINESS_ID=t,exports.arrayBufferToBase64=function(e){let t="";const s=new Uint8Array(e),n=s.byteLength;for(let e=0;e<n;e++)t+=String.fromCharCode(s[e]);return btoa(t)},exports.base64ToArrayBuffer=function(e){const t=atob(e),s=t.length,n=new Uint8Array(s);for(let e=0;e<s;e++)n[e]=t.charCodeAt(e);return n.buffer},exports.customElem=u,exports.fileElem=o,exports.getAccessToken=s,exports.imageElem=i,exports.miniAppElem=p,exports.soundElem=a,exports.splitIntoChunks=h,exports.stickerElem=l,exports.textElem=r,exports.toBase64=d;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e="https://chat-api2-3rnt.onrender.com",t="web_link",s="ai_choice_prompt",r="official_account_tweet";async function a(t){var s;const{clientId:r,clientSecret:a}=t;if(!r||!a)throw new Error("getAccessToken: clientId and clientSecret are required");const i=e.replace(/\/$/,""),o=null!==(s=t.scope)&&void 0!==s?s:"chat:write uploads:write";if("undefined"==typeof fetch)throw new Error("getAccessToken: global fetch is not available");const n=await fetch(`${i}/v1/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"client_credentials",client_id:r,client_secret:a,scope:o})});if(!n.ok)throw new Error(`Token request failed: ${n.status} ${n.statusText}`);return await n.json()}var i;function o(e){return{elemType:exports.ElemType.Text,priority:0,textElem:{text:e.trim()}}}function n(e){return{elemType:exports.ElemType.Image,priority:0,imageElem:{upload_ids:e}}}function l(e){return{elemType:exports.ElemType.File,priority:0,fileElem:{upload_ids:e}}}function u(e,t=0){return{elemType:exports.ElemType.Sound,priority:0,soundElem:{uploadID:e,duration:t}}}function c(e,t){return{elemType:exports.ElemType.Sticker,priority:0,stickerElem:{packID:e,stickerID:t}}}function d(e){return{elemType:exports.ElemType.Custom,priority:0,customElem:{data:e}}}function h(e){return{elemType:exports.ElemType.MiniApp,priority:0,miniAppElem:e}}function g(e){const t=new Uint8Array(e.byteLength);return t.set(e),t}function p(e){const t=(new TextEncoder).encode(e);let s="";for(const e of t)s+=String.fromCharCode(e);return btoa(s)}function m(e,t=10){if(e.length<=t)return[e];const s=[];let r="";for(const a of e.split(" "))r.length+a.length+1>t&&r.length>0?(s.push(r.trim()+" "),r=a):r+=(r?" ":"")+a;return r&&s.push(r.trim()),s.length>0?s:[e]}exports.ElemType=void 0,(i=exports.ElemType||(exports.ElemType={}))[i.None=0]="None",i[i.Text=1]="Text",i[i.Image=2]="Image",i[i.Sound=3]="Sound",i[i.Video=4]="Video",i[i.File=5]="File",i[i.Sticker=6]="Sticker",i[i.GroupTips=7]="GroupTips",i[i.Merger=8]="Merger",i[i.Custom=9]="Custom",i[i.Location=10]="Location",i[i.GroupAnnouncement=11]="GroupAnnouncement",i[i.Quote=12]="Quote",i[i.InputStatus=14]="InputStatus",i[i.TypingStatus=15]="TypingStatus",i[i.MiniApp=16]="MiniApp",i[i.Order=17]="Order",i[i.Transfer=18]="Transfer";class f{constructor(t){var s,r;if(!(null==t?void 0:t.token))throw new Error("ChatClient: token is required");this.token=t.token,this.baseUrl=(null!==(s=t.baseUrl)&&void 0!==s?s:e).replace(/\/$/,""),this.logger=t.logger;const a=null!==(r=t.fetch)&&void 0!==r?r:"undefined"!=typeof fetch?fetch:void 0;if(!a)throw new Error("ChatClient: no fetch implementation available; pass `fetch` in options");this.fetchImpl=a.bind(globalThis)}static async fromCredentials(e){const{access_token:t}=await a({clientId:e.clientId,clientSecret:e.clientSecret,scope:e.scope});return new f({token:t,baseUrl:e.baseUrl,logger:e.logger,fetch:e.fetch})}get authHeaders(){return{"Content-Type":"application/json",Authorization:`Bearer ${this.token}`}}replyUrl(e){return`${this.baseUrl}/v1/chat/message/${e}/reply`}newMsgID(){return globalThis.crypto.randomUUID()}async postElements(e,t,s="POST"){return await this.fetchImpl(this.replyUrl(e),{method:s,headers:this.authHeaders,body:JSON.stringify(t)})}async uploadImage(e,t,s="image/png"){var r;const a=new FormData;a.append("file",new Blob([g(e)],{type:s}),t);const i=await this.fetchImpl(`${this.baseUrl}/v1/upload/image`,{method:"POST",headers:{Authorization:`Bearer ${this.token}`},body:a}),o=await i.json();if(!i.ok)throw new Error(`uploadImage failed: ${i.status} ${JSON.stringify(o)}`);if(!o.upload_id)throw new Error(`uploadImage: missing upload_id: ${JSON.stringify(o)}`);return{upload_id:o.upload_id,url:null!==(r=o.url)&&void 0!==r?r:""}}async uploadFile(e,t,s="application/octet-stream"){var r;const a=new FormData;a.append("file",new Blob([g(e)],{type:s}),t);const i=await this.fetchImpl(`${this.baseUrl}/v1/upload/file`,{method:"POST",headers:{Authorization:`Bearer ${this.token}`},body:a}),o=await i.json();if(!i.ok)throw new Error(`uploadFile failed: ${i.status} ${JSON.stringify(o)}`);if(!o.upload_id)throw new Error(`uploadFile: missing upload_id: ${JSON.stringify(o)}`);return{upload_id:o.upload_id,url:null!==(r=o.url)&&void 0!==r?r:""}}async sendMessage(e,t,s){var r;let a=Array.isArray(t)?t.map((e=>e.trim())).filter(Boolean).map(o):[o(t)];if(0===a.length)return;s&&(a=a.map((e=>({...e,msgID:s}))));const i=await this.postElements(e,a),n=await i.text();if(null===(r=this.logger)||void 0===r||r.log("[chat-sdk] message sent",{status:i.status,elements:a.length}),!i.ok)throw new Error(`sendMessage failed: ${i.status} ${n}`)}async sendImageMessage(e,t){var s;if(!(null==t?void 0:t.length))return;const r=await this.postElements(e,[n(t)]),a=await r.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] image message sent",{status:r.status}),!r.ok)throw new Error(`sendImageMessage failed: ${r.status} ${a}`)}async sendFileMessage(e,t){var s;if(!(null==t?void 0:t.length))return;const r=await this.postElements(e,[l(t)]),a=await r.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] file message sent",{status:r.status}),!r.ok)throw new Error(`sendFileMessage failed: ${r.status} ${a}`)}async sendSoundMessage(e,t,s=0){var r;if(!(null==t?void 0:t.trim()))return;const a=await this.postElements(e,[u(t,s)]),i=await a.text();if(null===(r=this.logger)||void 0===r||r.log("[chat-sdk] sound message sent",{status:a.status}),!a.ok)throw new Error(`sendSoundMessage failed: ${a.status} ${i.slice(0,500)}`)}async sendStickerMessage(e,t,s,r){var a;if(!(null==t?void 0:t.trim())||!(null==s?void 0:s.trim()))return;const i=c(t,s),o=await this.postElements(e,[r?{...i,msgID:r}:i]),n=await o.text();if(null===(a=this.logger)||void 0===a||a.log("[chat-sdk] sticker message sent",{status:o.status}),!o.ok)throw new Error(`sendStickerMessage failed: ${o.status} ${n}`)}async editTextMessage(e,t){var s;const r=await this.postElements(e,[{msgID:e,...o(t)}]),a=await r.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] text message edited",{status:r.status}),!r.ok)throw new Error(`editTextMessage failed: ${r.status} ${a.slice(0,500)}`)}async editStickerMessage(e,t,s){var r;const a=await this.postElements(e,[{msgID:e,...c(t,s)}]),i=await a.text();if(null===(r=this.logger)||void 0===r||r.log("[chat-sdk] sticker message edited",{status:a.status}),!a.ok)throw new Error(`editStickerMessage failed: ${a.status} ${i.slice(0,500)}`)}async editImageMessage(e,t){var s;const r=await this.postElements(e,[{msgID:e,...n(t)}]),a=await r.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] image message edited",{status:r.status}),!r.ok)throw new Error(`editImageMessage failed: ${r.status} ${a.slice(0,500)}`)}async deleteMessage(e){var t;const s=await this.fetchImpl(`${this.baseUrl}/v1/chat/message/${e}`,{method:"DELETE",headers:this.authHeaders});if(null===(t=this.logger)||void 0===t||t.log("[chat-sdk] message deleted",{status:s.status}),!s.ok){const e=await s.text();throw new Error(`deleteMessage failed: ${s.status} ${e.slice(0,500)}`)}}async sendTypingStatus(e,t){var s,r;const a={businessID:"user_typing_status",typingStatus:t?1:0,version:1,userAction:14,actionParam:t?"EIMAMSG_InputStatus_Ing":"EIMAMSG_InputStatus_End"};try{await this.postElements(e,[d(p(JSON.stringify(a)))])}catch(e){null===(s=this.logger)||void 0===s||s.error("[chat-sdk] typing status failed",null!==(r=null==e?void 0:e.message)&&void 0!==r?r:String(e))}}async sendStreamingChunk(e,t,s,r){const a={businessID:"chatbotPlugin",src:0,chunks:t,isFinished:s,TMessageCell_Name:"ChatbotMessageCell_Minimalist",TMessageCell_Data_Name:"ChatbotMessageCellData"},i={msgID:e,...d(p(JSON.stringify(a)))},o=await this.postElements(e,[i],r?"POST":"PATCH");if(!o.ok)throw new Error(`sendStreamingChunk failed: ${o.statusText}`);await o.text()}async sendMiniAppMessage(e,t){var s;const r=await this.postElements(e,[h(t)]),a=await r.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] miniapp card sent",{status:r.status}),!r.ok)throw new Error(`sendMiniAppMessage failed: ${r.status} ${a}`)}async sendWebLinkMessage(e,s){var r;const a={businessID:t,url:s},i=await this.postElements(e,[d(p(JSON.stringify(a)))]),o=await i.text();if(null===(r=this.logger)||void 0===r||r.log("[chat-sdk] web link sent",{status:i.status}),!i.ok)throw new Error(`sendWebLinkMessage failed: ${i.status} ${o.slice(0,500)}`)}async sendChoicesMessage(e,t){var r,a,i;const o=this.newMsgID(),n={businessID:s,promptId:null!==(r=t.promptId)&&void 0!==r?r:this.newMsgID(),prompt:t.prompt,selectionMode:null!==(a=t.selectionMode)&&void 0!==a?a:"single",choices:t.choices},l=await this.postElements(e,[{msgID:o,...d(p(JSON.stringify(n)))}]),u=await l.text();if(null===(i=this.logger)||void 0===i||i.log("[chat-sdk] choices message sent",{status:l.status}),!l.ok)throw new Error(`sendChoicesMessage failed: ${l.status} ${u.slice(0,500)}`);return o}async sendOfficialAccountTweetMessage(e,t){var s;const a=this.newMsgID(),i={businessID:r,...t},o=await this.postElements(e,[{msgID:a,...d(p(JSON.stringify(i)))}]),n=await o.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] official account tweet sent",{status:o.status}),!o.ok)throw new Error(`sendOfficialAccountTweetMessage failed: ${o.status} ${n.slice(0,500)}`);return a}async streamMessage(e,t,s=10){const r=m(t,s),a=[];for(let t=0;t<r.length;t++)a.push(r[t]),await this.sendStreamingChunk(e,a,t===r.length-1,0===t)}}f.ElemType=exports.ElemType,exports.AI_CHOICE_PROMPT_BUSINESS_ID=s,exports.ChatClient=f,exports.DEFAULT_BASE_URL=e,exports.OFFICIAL_ACCOUNT_TWEET_BUSINESS_ID=r,exports.WEB_LINK_BUSINESS_ID=t,exports.arrayBufferToBase64=function(e){let t="";const s=new Uint8Array(e),r=s.byteLength;for(let e=0;e<r;e++)t+=String.fromCharCode(s[e]);return btoa(t)},exports.base64ToArrayBuffer=function(e){const t=atob(e),s=t.length,r=new Uint8Array(s);for(let e=0;e<s;e++)r[e]=t.charCodeAt(e);return r.buffer},exports.customElem=d,exports.fileElem=l,exports.getAccessToken=a,exports.imageElem=n,exports.miniAppElem=h,exports.soundElem=u,exports.splitIntoChunks=m,exports.stickerElem=c,exports.textElem=o,exports.toBase64=p,exports.verifyWebhookSignature=async function(e,t,s,r={}){const{toleranceSec:a=300,logger:i}=r,o=e.headers.get("X-Webhook-Signature");if(!o)return null==i||i.error("[chat-sdk] webhook missing X-Webhook-Signature header"),{valid:!1,error:"Missing X-Webhook-Signature header"};const n=e.headers.get("X-Webhook-Timestamp");if(!n)return null==i||i.error("[chat-sdk] webhook missing X-Webhook-Timestamp header"),{valid:!1,error:"Missing X-Webhook-Timestamp header"};const l=o.startsWith("sha256=")?o.slice(7):o,u=await async function(e,t){const s=new TextEncoder,r=await crypto.subtle.importKey("raw",s.encode(e),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),a=await crypto.subtle.sign("HMAC",r,s.encode(t));return[...new Uint8Array(a)].map((e=>e.toString(16).padStart(2,"0"))).join("")}(t,`${n}.${s}`);if(!function(e,t){if(e.length!==t.length)return!1;let s=0;for(let r=0;r<e.length;r++)s|=e.charCodeAt(r)^t.charCodeAt(r);return 0===s}(l,u))return null==i||i.error("[chat-sdk] webhook invalid signature"),{valid:!1,error:"Invalid signature"};const c=parseInt(n,10),d=Math.floor(Date.now()/1e3);return!Number.isFinite(c)||Math.abs(d-c)>a?(null==i||i.error("[chat-sdk] webhook timestamp out of range"),{valid:!1,error:"Request timestamp too old or too far in future"}):(null==i||i.log("[chat-sdk] webhook signature valid"),{valid:!0})};
2
2
  //# sourceMappingURL=main.cjs.js.map
package/dist/main.esm.js CHANGED
@@ -1,2 +1,2 @@
1
- const t="https://chat-api2-3rnt.onrender.com",e="web_link";async function s(e){var s;const{clientId:n,clientSecret:i}=e;if(!n||!i)throw new Error("getAccessToken: clientId and clientSecret are required");const r=t.replace(/\/$/,""),a=null!==(s=e.scope)&&void 0!==s?s:"chat:write uploads:write";if("undefined"==typeof fetch)throw new Error("getAccessToken: global fetch is not available");const o=await fetch(`${r}/v1/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"client_credentials",client_id:n,client_secret:i,scope:a})});if(!o.ok)throw new Error(`Token request failed: ${o.status} ${o.statusText}`);return await o.json()}var n;function i(t){return{elemType:n.Text,priority:0,textElem:{text:t.trim()}}}function r(t){return{elemType:n.Image,priority:0,imageElem:{upload_ids:t}}}function a(t){return{elemType:n.File,priority:0,fileElem:{upload_ids:t}}}function o(t,e=0){return{elemType:n.Sound,priority:0,soundElem:{uploadID:t,duration:e}}}function l(t,e){return{elemType:n.Sticker,priority:0,stickerElem:{packID:t,stickerID:e}}}function u(t){return{elemType:n.Custom,priority:0,customElem:{data:t}}}function c(t){return{elemType:n.MiniApp,priority:0,miniAppElem:t}}function d(t){const e=new Uint8Array(t.byteLength);return e.set(t),e}function h(t){let e="";const s=new Uint8Array(t),n=s.byteLength;for(let t=0;t<n;t++)e+=String.fromCharCode(s[t]);return btoa(e)}function p(t){const e=atob(t),s=e.length,n=new Uint8Array(s);for(let t=0;t<s;t++)n[t]=e.charCodeAt(t);return n.buffer}function g(t){const e=(new TextEncoder).encode(t);let s="";for(const t of e)s+=String.fromCharCode(t);return btoa(s)}function f(t,e=10){if(t.length<=e)return[t];const s=[];let n="";for(const i of t.split(" "))n.length+i.length+1>e&&n.length>0?(s.push(n.trim()+" "),n=i):n+=(n?" ":"")+i;return n&&s.push(n.trim()),s.length>0?s:[t]}!function(t){t[t.None=0]="None",t[t.Text=1]="Text",t[t.Image=2]="Image",t[t.Sound=3]="Sound",t[t.Video=4]="Video",t[t.File=5]="File",t[t.Sticker=6]="Sticker",t[t.GroupTips=7]="GroupTips",t[t.Merger=8]="Merger",t[t.Custom=9]="Custom",t[t.Location=10]="Location",t[t.GroupAnnouncement=11]="GroupAnnouncement",t[t.Quote=12]="Quote",t[t.InputStatus=14]="InputStatus",t[t.TypingStatus=15]="TypingStatus",t[t.MiniApp=16]="MiniApp",t[t.Order=17]="Order",t[t.Transfer=18]="Transfer"}(n||(n={}));class m{constructor(e){var s,n;if(!(null==e?void 0:e.token))throw new Error("ChatClient: token is required");this.token=e.token,this.baseUrl=(null!==(s=e.baseUrl)&&void 0!==s?s:t).replace(/\/$/,""),this.logger=e.logger;const i=null!==(n=e.fetch)&&void 0!==n?n:"undefined"!=typeof fetch?fetch:void 0;if(!i)throw new Error("ChatClient: no fetch implementation available; pass `fetch` in options");this.fetchImpl=i.bind(globalThis)}static async fromCredentials(t){const{access_token:e}=await s({clientId:t.clientId,clientSecret:t.clientSecret,scope:t.scope});return new m({token:e,baseUrl:t.baseUrl,logger:t.logger,fetch:t.fetch})}get authHeaders(){return{"Content-Type":"application/json",Authorization:`Bearer ${this.token}`}}replyUrl(t){return`${this.baseUrl}/v1/chat/message/${t}/reply`}async postElements(t,e,s="POST"){return await this.fetchImpl(this.replyUrl(t),{method:s,headers:this.authHeaders,body:JSON.stringify(e)})}async uploadImage(t,e,s="image/png"){var n;const i=new FormData;i.append("file",new Blob([d(t)],{type:s}),e);const r=await this.fetchImpl(`${this.baseUrl}/v1/upload/image`,{method:"POST",headers:{Authorization:`Bearer ${this.token}`},body:i}),a=await r.json();if(!r.ok)throw new Error(`uploadImage failed: ${r.status} ${JSON.stringify(a)}`);if(!a.upload_id)throw new Error(`uploadImage: missing upload_id: ${JSON.stringify(a)}`);return{upload_id:a.upload_id,url:null!==(n=a.url)&&void 0!==n?n:""}}async uploadFile(t,e,s="application/octet-stream"){var n;const i=new FormData;i.append("file",new Blob([d(t)],{type:s}),e);const r=await this.fetchImpl(`${this.baseUrl}/v1/upload/file`,{method:"POST",headers:{Authorization:`Bearer ${this.token}`},body:i}),a=await r.json();if(!r.ok)throw new Error(`uploadFile failed: ${r.status} ${JSON.stringify(a)}`);if(!a.upload_id)throw new Error(`uploadFile: missing upload_id: ${JSON.stringify(a)}`);return{upload_id:a.upload_id,url:null!==(n=a.url)&&void 0!==n?n:""}}async sendMessage(t,e){var s;const n=Array.isArray(e)?e.map((t=>t.trim())).filter(Boolean).map(i):[i(e)];if(0===n.length)return;const r=await this.postElements(t,n),a=await r.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] message sent",{status:r.status,elements:n.length}),!r.ok)throw new Error(`sendMessage failed: ${r.status} ${a}`)}async sendImageMessage(t,e){var s;if(!(null==e?void 0:e.length))return;const n=await this.postElements(t,[r(e)]),i=await n.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] image message sent",{status:n.status}),!n.ok)throw new Error(`sendImageMessage failed: ${n.status} ${i}`)}async sendFileMessage(t,e){var s;if(!(null==e?void 0:e.length))return;const n=await this.postElements(t,[a(e)]),i=await n.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] file message sent",{status:n.status}),!n.ok)throw new Error(`sendFileMessage failed: ${n.status} ${i}`)}async sendSoundMessage(t,e,s=0){var n;if(!(null==e?void 0:e.trim()))return;const i=await this.postElements(t,[o(e,s)]),r=await i.text();if(null===(n=this.logger)||void 0===n||n.log("[chat-sdk] sound message sent",{status:i.status}),!i.ok)throw new Error(`sendSoundMessage failed: ${i.status} ${r.slice(0,500)}`)}async sendStickerMessage(t,e,s){var n;if(!(null==e?void 0:e.trim())||!(null==s?void 0:s.trim()))return;const i=await this.postElements(t,[l(e,s)]),r=await i.text();if(null===(n=this.logger)||void 0===n||n.log("[chat-sdk] sticker message sent",{status:i.status}),!i.ok)throw new Error(`sendStickerMessage failed: ${i.status} ${r}`)}async sendTypingStatus(t,e){var s,n;const i={businessID:"user_typing_status",typingStatus:e?1:0,version:1,userAction:14,actionParam:e?"EIMAMSG_InputStatus_Ing":"EIMAMSG_InputStatus_End"};try{await this.postElements(t,[u(g(JSON.stringify(i)))])}catch(t){null===(s=this.logger)||void 0===s||s.error("[chat-sdk] typing status failed",null!==(n=null==t?void 0:t.message)&&void 0!==n?n:String(t))}}async sendStreamingChunk(t,e,s,n){const i={businessID:"chatbotPlugin",src:0,chunks:e,isFinished:s,TMessageCell_Name:"ChatbotMessageCell_Minimalist",TMessageCell_Data_Name:"ChatbotMessageCellData"},r={msgID:t,...u(g(JSON.stringify(i)))},a=await this.postElements(t,[r],n?"POST":"PATCH");if(!a.ok)throw new Error(`sendStreamingChunk failed: ${a.statusText}`);await a.text()}async sendMiniAppMessage(t,e){var s;const n=await this.postElements(t,[c(e)]),i=await n.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] miniapp card sent",{status:n.status}),!n.ok)throw new Error(`sendMiniAppMessage failed: ${n.status} ${i}`)}async sendWebLinkMessage(t,s){var n;const i={businessID:e,url:s},r=await this.postElements(t,[u(g(JSON.stringify(i)))]),a=await r.text();if(null===(n=this.logger)||void 0===n||n.log("[chat-sdk] web link sent",{status:r.status}),!r.ok)throw new Error(`sendWebLinkMessage failed: ${r.status} ${a.slice(0,500)}`)}async streamMessage(t,e,s=10){const n=f(e,s),i=[];for(let e=0;e<n.length;e++)i.push(n[e]),await this.sendStreamingChunk(t,i,e===n.length-1,0===e)}}m.ElemType=n;export{m as ChatClient,t as DEFAULT_BASE_URL,n as ElemType,e as WEB_LINK_BUSINESS_ID,h as arrayBufferToBase64,p as base64ToArrayBuffer,u as customElem,a as fileElem,s as getAccessToken,r as imageElem,c as miniAppElem,o as soundElem,f as splitIntoChunks,l as stickerElem,i as textElem,g as toBase64};
1
+ const t="https://chat-api2-3rnt.onrender.com",e="web_link",s="ai_choice_prompt",a="official_account_tweet";async function i(e){var s;const{clientId:a,clientSecret:i}=e;if(!a||!i)throw new Error("getAccessToken: clientId and clientSecret are required");const n=t.replace(/\/$/,""),r=null!==(s=e.scope)&&void 0!==s?s:"chat:write uploads:write";if("undefined"==typeof fetch)throw new Error("getAccessToken: global fetch is not available");const o=await fetch(`${n}/v1/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"client_credentials",client_id:a,client_secret:i,scope:r})});if(!o.ok)throw new Error(`Token request failed: ${o.status} ${o.statusText}`);return await o.json()}var n;function r(t){return{elemType:n.Text,priority:0,textElem:{text:t.trim()}}}function o(t){return{elemType:n.Image,priority:0,imageElem:{upload_ids:t}}}function l(t){return{elemType:n.File,priority:0,fileElem:{upload_ids:t}}}function u(t,e=0){return{elemType:n.Sound,priority:0,soundElem:{uploadID:t,duration:e}}}function c(t,e){return{elemType:n.Sticker,priority:0,stickerElem:{packID:t,stickerID:e}}}function d(t){return{elemType:n.Custom,priority:0,customElem:{data:t}}}function h(t){return{elemType:n.MiniApp,priority:0,miniAppElem:t}}function g(t){const e=new Uint8Array(t.byteLength);return e.set(t),e}function p(t){let e="";const s=new Uint8Array(t),a=s.byteLength;for(let t=0;t<a;t++)e+=String.fromCharCode(s[t]);return btoa(e)}function m(t){const e=atob(t),s=e.length,a=new Uint8Array(s);for(let t=0;t<s;t++)a[t]=e.charCodeAt(t);return a.buffer}function f(t){const e=(new TextEncoder).encode(t);let s="";for(const t of e)s+=String.fromCharCode(t);return btoa(s)}function w(t,e=10){if(t.length<=e)return[t];const s=[];let a="";for(const i of t.split(" "))a.length+i.length+1>e&&a.length>0?(s.push(a.trim()+" "),a=i):a+=(a?" ":"")+i;return a&&s.push(a.trim()),s.length>0?s:[t]}!function(t){t[t.None=0]="None",t[t.Text=1]="Text",t[t.Image=2]="Image",t[t.Sound=3]="Sound",t[t.Video=4]="Video",t[t.File=5]="File",t[t.Sticker=6]="Sticker",t[t.GroupTips=7]="GroupTips",t[t.Merger=8]="Merger",t[t.Custom=9]="Custom",t[t.Location=10]="Location",t[t.GroupAnnouncement=11]="GroupAnnouncement",t[t.Quote=12]="Quote",t[t.InputStatus=14]="InputStatus",t[t.TypingStatus=15]="TypingStatus",t[t.MiniApp=16]="MiniApp",t[t.Order=17]="Order",t[t.Transfer=18]="Transfer"}(n||(n={}));class y{constructor(e){var s,a;if(!(null==e?void 0:e.token))throw new Error("ChatClient: token is required");this.token=e.token,this.baseUrl=(null!==(s=e.baseUrl)&&void 0!==s?s:t).replace(/\/$/,""),this.logger=e.logger;const i=null!==(a=e.fetch)&&void 0!==a?a:"undefined"!=typeof fetch?fetch:void 0;if(!i)throw new Error("ChatClient: no fetch implementation available; pass `fetch` in options");this.fetchImpl=i.bind(globalThis)}static async fromCredentials(t){const{access_token:e}=await i({clientId:t.clientId,clientSecret:t.clientSecret,scope:t.scope});return new y({token:e,baseUrl:t.baseUrl,logger:t.logger,fetch:t.fetch})}get authHeaders(){return{"Content-Type":"application/json",Authorization:`Bearer ${this.token}`}}replyUrl(t){return`${this.baseUrl}/v1/chat/message/${t}/reply`}newMsgID(){return globalThis.crypto.randomUUID()}async postElements(t,e,s="POST"){return await this.fetchImpl(this.replyUrl(t),{method:s,headers:this.authHeaders,body:JSON.stringify(e)})}async uploadImage(t,e,s="image/png"){var a;const i=new FormData;i.append("file",new Blob([g(t)],{type:s}),e);const n=await this.fetchImpl(`${this.baseUrl}/v1/upload/image`,{method:"POST",headers:{Authorization:`Bearer ${this.token}`},body:i}),r=await n.json();if(!n.ok)throw new Error(`uploadImage failed: ${n.status} ${JSON.stringify(r)}`);if(!r.upload_id)throw new Error(`uploadImage: missing upload_id: ${JSON.stringify(r)}`);return{upload_id:r.upload_id,url:null!==(a=r.url)&&void 0!==a?a:""}}async uploadFile(t,e,s="application/octet-stream"){var a;const i=new FormData;i.append("file",new Blob([g(t)],{type:s}),e);const n=await this.fetchImpl(`${this.baseUrl}/v1/upload/file`,{method:"POST",headers:{Authorization:`Bearer ${this.token}`},body:i}),r=await n.json();if(!n.ok)throw new Error(`uploadFile failed: ${n.status} ${JSON.stringify(r)}`);if(!r.upload_id)throw new Error(`uploadFile: missing upload_id: ${JSON.stringify(r)}`);return{upload_id:r.upload_id,url:null!==(a=r.url)&&void 0!==a?a:""}}async sendMessage(t,e,s){var a;let i=Array.isArray(e)?e.map((t=>t.trim())).filter(Boolean).map(r):[r(e)];if(0===i.length)return;s&&(i=i.map((t=>({...t,msgID:s}))));const n=await this.postElements(t,i),o=await n.text();if(null===(a=this.logger)||void 0===a||a.log("[chat-sdk] message sent",{status:n.status,elements:i.length}),!n.ok)throw new Error(`sendMessage failed: ${n.status} ${o}`)}async sendImageMessage(t,e){var s;if(!(null==e?void 0:e.length))return;const a=await this.postElements(t,[o(e)]),i=await a.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] image message sent",{status:a.status}),!a.ok)throw new Error(`sendImageMessage failed: ${a.status} ${i}`)}async sendFileMessage(t,e){var s;if(!(null==e?void 0:e.length))return;const a=await this.postElements(t,[l(e)]),i=await a.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] file message sent",{status:a.status}),!a.ok)throw new Error(`sendFileMessage failed: ${a.status} ${i}`)}async sendSoundMessage(t,e,s=0){var a;if(!(null==e?void 0:e.trim()))return;const i=await this.postElements(t,[u(e,s)]),n=await i.text();if(null===(a=this.logger)||void 0===a||a.log("[chat-sdk] sound message sent",{status:i.status}),!i.ok)throw new Error(`sendSoundMessage failed: ${i.status} ${n.slice(0,500)}`)}async sendStickerMessage(t,e,s,a){var i;if(!(null==e?void 0:e.trim())||!(null==s?void 0:s.trim()))return;const n=c(e,s),r=await this.postElements(t,[a?{...n,msgID:a}:n]),o=await r.text();if(null===(i=this.logger)||void 0===i||i.log("[chat-sdk] sticker message sent",{status:r.status}),!r.ok)throw new Error(`sendStickerMessage failed: ${r.status} ${o}`)}async editTextMessage(t,e){var s;const a=await this.postElements(t,[{msgID:t,...r(e)}]),i=await a.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] text message edited",{status:a.status}),!a.ok)throw new Error(`editTextMessage failed: ${a.status} ${i.slice(0,500)}`)}async editStickerMessage(t,e,s){var a;const i=await this.postElements(t,[{msgID:t,...c(e,s)}]),n=await i.text();if(null===(a=this.logger)||void 0===a||a.log("[chat-sdk] sticker message edited",{status:i.status}),!i.ok)throw new Error(`editStickerMessage failed: ${i.status} ${n.slice(0,500)}`)}async editImageMessage(t,e){var s;const a=await this.postElements(t,[{msgID:t,...o(e)}]),i=await a.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] image message edited",{status:a.status}),!a.ok)throw new Error(`editImageMessage failed: ${a.status} ${i.slice(0,500)}`)}async deleteMessage(t){var e;const s=await this.fetchImpl(`${this.baseUrl}/v1/chat/message/${t}`,{method:"DELETE",headers:this.authHeaders});if(null===(e=this.logger)||void 0===e||e.log("[chat-sdk] message deleted",{status:s.status}),!s.ok){const t=await s.text();throw new Error(`deleteMessage failed: ${s.status} ${t.slice(0,500)}`)}}async sendTypingStatus(t,e){var s,a;const i={businessID:"user_typing_status",typingStatus:e?1:0,version:1,userAction:14,actionParam:e?"EIMAMSG_InputStatus_Ing":"EIMAMSG_InputStatus_End"};try{await this.postElements(t,[d(f(JSON.stringify(i)))])}catch(t){null===(s=this.logger)||void 0===s||s.error("[chat-sdk] typing status failed",null!==(a=null==t?void 0:t.message)&&void 0!==a?a:String(t))}}async sendStreamingChunk(t,e,s,a){const i={businessID:"chatbotPlugin",src:0,chunks:e,isFinished:s,TMessageCell_Name:"ChatbotMessageCell_Minimalist",TMessageCell_Data_Name:"ChatbotMessageCellData"},n={msgID:t,...d(f(JSON.stringify(i)))},r=await this.postElements(t,[n],a?"POST":"PATCH");if(!r.ok)throw new Error(`sendStreamingChunk failed: ${r.statusText}`);await r.text()}async sendMiniAppMessage(t,e){var s;const a=await this.postElements(t,[h(e)]),i=await a.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] miniapp card sent",{status:a.status}),!a.ok)throw new Error(`sendMiniAppMessage failed: ${a.status} ${i}`)}async sendWebLinkMessage(t,s){var a;const i={businessID:e,url:s},n=await this.postElements(t,[d(f(JSON.stringify(i)))]),r=await n.text();if(null===(a=this.logger)||void 0===a||a.log("[chat-sdk] web link sent",{status:n.status}),!n.ok)throw new Error(`sendWebLinkMessage failed: ${n.status} ${r.slice(0,500)}`)}async sendChoicesMessage(t,e){var a,i,n;const r=this.newMsgID(),o={businessID:s,promptId:null!==(a=e.promptId)&&void 0!==a?a:this.newMsgID(),prompt:e.prompt,selectionMode:null!==(i=e.selectionMode)&&void 0!==i?i:"single",choices:e.choices},l=await this.postElements(t,[{msgID:r,...d(f(JSON.stringify(o)))}]),u=await l.text();if(null===(n=this.logger)||void 0===n||n.log("[chat-sdk] choices message sent",{status:l.status}),!l.ok)throw new Error(`sendChoicesMessage failed: ${l.status} ${u.slice(0,500)}`);return r}async sendOfficialAccountTweetMessage(t,e){var s;const i=this.newMsgID(),n={businessID:a,...e},r=await this.postElements(t,[{msgID:i,...d(f(JSON.stringify(n)))}]),o=await r.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] official account tweet sent",{status:r.status}),!r.ok)throw new Error(`sendOfficialAccountTweetMessage failed: ${r.status} ${o.slice(0,500)}`);return i}async streamMessage(t,e,s=10){const a=w(e,s),i=[];for(let e=0;e<a.length;e++)i.push(a[e]),await this.sendStreamingChunk(t,i,e===a.length-1,0===e)}}async function k(t,e,s,a={}){const{toleranceSec:i=300,logger:n}=a,r=t.headers.get("X-Webhook-Signature");if(!r)return null==n||n.error("[chat-sdk] webhook missing X-Webhook-Signature header"),{valid:!1,error:"Missing X-Webhook-Signature header"};const o=t.headers.get("X-Webhook-Timestamp");if(!o)return null==n||n.error("[chat-sdk] webhook missing X-Webhook-Timestamp header"),{valid:!1,error:"Missing X-Webhook-Timestamp header"};const l=r.startsWith("sha256=")?r.slice(7):r,u=await async function(t,e){const s=new TextEncoder,a=await crypto.subtle.importKey("raw",s.encode(t),{name:"HMAC",hash:"SHA-256"},!1,["sign"]),i=await crypto.subtle.sign("HMAC",a,s.encode(e));return[...new Uint8Array(i)].map((t=>t.toString(16).padStart(2,"0"))).join("")}(e,`${o}.${s}`);if(!function(t,e){if(t.length!==e.length)return!1;let s=0;for(let a=0;a<t.length;a++)s|=t.charCodeAt(a)^e.charCodeAt(a);return 0===s}(l,u))return null==n||n.error("[chat-sdk] webhook invalid signature"),{valid:!1,error:"Invalid signature"};const c=parseInt(o,10),d=Math.floor(Date.now()/1e3);return!Number.isFinite(c)||Math.abs(d-c)>i?(null==n||n.error("[chat-sdk] webhook timestamp out of range"),{valid:!1,error:"Request timestamp too old or too far in future"}):(null==n||n.log("[chat-sdk] webhook signature valid"),{valid:!0})}y.ElemType=n;export{s as AI_CHOICE_PROMPT_BUSINESS_ID,y as ChatClient,t as DEFAULT_BASE_URL,n as ElemType,a as OFFICIAL_ACCOUNT_TWEET_BUSINESS_ID,e as WEB_LINK_BUSINESS_ID,p as arrayBufferToBase64,m as base64ToArrayBuffer,d as customElem,l as fileElem,i as getAccessToken,o as imageElem,h as miniAppElem,u as soundElem,w as splitIntoChunks,c as stickerElem,r as textElem,f as toBase64,k as verifyWebhookSignature};
2
2
  //# sourceMappingURL=main.esm.js.map
@@ -1,4 +1,4 @@
1
- import { ChatClientOptions, ElemType, Logger, MiniAppElem, UploadV1FileResponse, UploadV1ImageResponse } from "./types";
1
+ import { ChatClientOptions, ChoicesPrompt, ElemType, Logger, MiniAppElem, OfficialAccountTweet, UploadV1FileResponse, UploadV1ImageResponse } from "./types";
2
2
  /**
3
3
  * ChatClient wraps the chat API reply/upload endpoints. Construct once with a
4
4
  * token (and optionally a baseUrl / logger), then call the message helpers.
@@ -31,6 +31,8 @@ export declare class ChatClient {
31
31
  }): Promise<ChatClient>;
32
32
  private get authHeaders();
33
33
  private replyUrl;
34
+ /** Generate a fresh message id (so a sent message can later be edited/streamed). */
35
+ private newMsgID;
34
36
  /** POST an array of message elements to a message's reply endpoint. */
35
37
  private postElements;
36
38
  /** Upload a binary image (POST /v1/upload/image) and return { upload_id, url }. */
@@ -40,16 +42,30 @@ export declare class ChatClient {
40
42
  /**
41
43
  * Send one or more text messages as a reply. A string sends a single element;
42
44
  * an array sends one request with multiple elements (one per line/paragraph).
45
+ *
46
+ * Pass `replyMsgID` to assign the outgoing message a known id (so it can later
47
+ * be edited via {@link editTextMessage}); it is applied to every element sent.
43
48
  */
44
- sendMessage(msgID: string, message: string | string[]): Promise<void>;
49
+ sendMessage(msgID: string, message: string | string[], replyMsgID?: string): Promise<void>;
45
50
  /** Send an image message by upload_id(s) (use uploadImage first). */
46
51
  sendImageMessage(msgID: string, uploadIDs: string[]): Promise<void>;
47
52
  /** Send a file message by upload_id(s) (use uploadFile first). */
48
53
  sendFileMessage(msgID: string, uploadIDs: string[]): Promise<void>;
49
54
  /** Send a sound message by uploadID (duration in seconds). */
50
55
  sendSoundMessage(msgID: string, uploadID: string, durationSec?: number): Promise<void>;
51
- /** Send a sticker message by pack + sticker identifiers. */
52
- sendStickerMessage(msgID: string, packID: string, stickerID: string): Promise<void>;
56
+ /**
57
+ * Send a sticker message by pack + sticker identifiers. Pass `replyMsgID` to
58
+ * assign a known id so it can later be edited via {@link editStickerMessage}.
59
+ */
60
+ sendStickerMessage(msgID: string, packID: string, stickerID: string, replyMsgID?: string): Promise<void>;
61
+ /** Edit a text message in place (`event.message.modified`). */
62
+ editTextMessage(msgID: string, message: string): Promise<void>;
63
+ /** Edit a sticker message in place (`event.message.modified`). */
64
+ editStickerMessage(msgID: string, packID: string, stickerID: string): Promise<void>;
65
+ /** Replace the image variants on an existing image message in place. */
66
+ editImageMessage(msgID: string, uploadIDs: string[]): Promise<void>;
67
+ /** Delete a business message (`event.message.deleted`). */
68
+ deleteMessage(msgID: string): Promise<void>;
53
69
  /** Send typing status (best effort — never throws). */
54
70
  sendTypingStatus(msgID: string, isTyping: boolean): Promise<void>;
55
71
  /**
@@ -61,6 +77,16 @@ export declare class ChatClient {
61
77
  sendMiniAppMessage(msgID: string, miniApp: MiniAppElem): Promise<void>;
62
78
  /** Send a web link preview custom message (matches iOS `webLinkPreviewMessage`). */
63
79
  sendWebLinkMessage(msgID: string, url: string): Promise<void>;
80
+ /**
81
+ * Send a choices prompt message (iOS `ai_choice_prompt`). Returns the new
82
+ * message's id so it can later be edited (e.g. to mark a selection).
83
+ */
84
+ sendChoicesMessage(msgID: string, prompt: ChoicesPrompt): Promise<string>;
85
+ /**
86
+ * Send an official-account tweet card (iOS `TUIOfficialAccountMessage`).
87
+ * Returns the new message's id.
88
+ */
89
+ sendOfficialAccountTweetMessage(msgID: string, tweet: OfficialAccountTweet): Promise<string>;
64
90
  /**
65
91
  * Stream a message in chunks for a typewriter effect. Splits `text`, then
66
92
  * sends each chunk via sendStreamingChunk (POST first, PATCH after).
@@ -69,3 +95,5 @@ export declare class ChatClient {
69
95
  /** Re-export of the element types for convenience. */
70
96
  static ElemType: typeof ElemType;
71
97
  }
98
+ /** Build a one-off SDK client bound to a bearer token. */
99
+ export declare function chatClient(token: string): ChatClient;
@@ -2,3 +2,7 @@
2
2
  export declare const DEFAULT_BASE_URL = "https://chat-api2-3rnt.onrender.com";
3
3
  /** Must match iOS `WebLinkMessageCellData.businessID`. */
4
4
  export declare const WEB_LINK_BUSINESS_ID = "web_link";
5
+ /** Must match iOS `AIChoiceMessageCellData` (`businessID: ai_choice_prompt`). */
6
+ export declare const AI_CHOICE_PROMPT_BUSINESS_ID = "ai_choice_prompt";
7
+ /** Must match iOS `OfficialAccountMessageBusinessID`. */
8
+ export declare const OFFICIAL_ACCOUNT_TWEET_BUSINESS_ID = "official_account_tweet";
@@ -1,6 +1,8 @@
1
1
  export { ChatClient } from './client';
2
2
  export { getAccessToken } from './auth';
3
3
  export type { GetAccessTokenOptions } from './auth';
4
+ export { verifyWebhookSignature } from './webhook';
5
+ export type { VerifyWebhookOptions, WebhookVerificationResult } from './webhook';
4
6
  export * from './types';
5
7
  export * from './elements';
6
8
  export * from './constants';
@@ -41,6 +41,33 @@ export type StickerElem = {
41
41
  packID: string;
42
42
  stickerID: string;
43
43
  };
44
+ /** A single choice in an `ai_choice_prompt` message. */
45
+ export type Choice = {
46
+ id: string;
47
+ label: string;
48
+ };
49
+ /** Payload for a choices prompt message (iOS `AIChoiceMessageCellData`). */
50
+ export type ChoicesPrompt = {
51
+ prompt: string;
52
+ choices: Choice[];
53
+ /** Defaults to 'single'. */
54
+ selectionMode?: 'single' | 'multiple';
55
+ /** Defaults to a generated UUID. */
56
+ promptId?: string;
57
+ };
58
+ export type OfficialAccountTweetImageInfo = {
59
+ url: string;
60
+ width: number;
61
+ height: number;
62
+ };
63
+ /** Payload for an official-account tweet card (iOS `TUIOfficialAccountMessage`). */
64
+ export type OfficialAccountTweet = {
65
+ title: string;
66
+ content: string;
67
+ link: string;
68
+ version: number;
69
+ imageInfo?: OfficialAccountTweetImageInfo;
70
+ };
44
71
  /** OAuth client_credentials token response (POST /v1/oauth/token). */
45
72
  export type TokenResponse = {
46
73
  access_token: string;
@@ -0,0 +1,28 @@
1
+ import { Logger } from './types';
2
+ export interface VerifyWebhookOptions {
3
+ /** Max allowed clock skew in seconds (replay protection). Defaults to 300. */
4
+ toleranceSec?: number;
5
+ /** Optional logger for diagnostics. Omit for silence. */
6
+ logger?: Logger;
7
+ }
8
+ export interface WebhookVerificationResult {
9
+ valid: boolean;
10
+ error?: string;
11
+ }
12
+ /**
13
+ * Verify a chat-API webhook signature.
14
+ *
15
+ * Scheme (matches the Go webhook manager): the request carries an
16
+ * `X-Webhook-Signature` header (optionally `sha256=`-prefixed) and an
17
+ * `X-Webhook-Timestamp` header, where the signature is
18
+ * `HMAC-SHA256(secret, "${timestamp}.${rawBody}")`. Pass `bodyText` as the raw
19
+ * request body exactly as received (read it before JSON-parsing).
20
+ *
21
+ * ```ts
22
+ * const raw = await request.text()
23
+ * const { valid } = await verifyWebhookSignature(request, env.WEBHOOK_SECRET, raw)
24
+ * if (!valid) return new Response('bad signature', { status: 401 })
25
+ * const event = JSON.parse(raw)
26
+ * ```
27
+ */
28
+ export declare function verifyWebhookSignature(request: Request, secret: string, bodyText: string, options?: VerifyWebhookOptions): Promise<WebhookVerificationResult>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "birdcash-chat-sdk-alpha",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "TypeScript SDK for sending messages through the chat API (text, images, files, miniapp cards, streaming).",
5
5
  "main": "dist/main.cjs.js",
6
6
  "module": "dist/main.esm.js",