birdcash-chat-sdk-alpha 1.0.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/README.md +105 -0
- package/dist/main.cjs.js +2 -0
- package/dist/main.cjs.js.map +1 -0
- package/dist/main.esm.js +2 -0
- package/dist/main.esm.js.map +1 -0
- package/dist/types/auth.d.ts +16 -0
- package/dist/types/client.d.ts +69 -0
- package/dist/types/constants.d.ts +4 -0
- package/dist/types/elements.d.ts +13 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/types.d.ts +82 -0
- package/dist/types/utils.d.ts +10 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# birdcash-chat-sdk
|
|
2
|
+
|
|
3
|
+
A small TypeScript SDK for sending messages through the chat API. It wraps the
|
|
4
|
+
`/v1/chat/message/:msgID/reply` and `/v1/upload/*` endpoints so you can send
|
|
5
|
+
text, images, files, miniapp cards, web-link previews, typing status, and
|
|
6
|
+
streaming (typewriter) responses.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install birdcash-chat-sdk-alpha
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { ChatClient } from 'birdcash-chat-sdk-alpha'
|
|
18
|
+
|
|
19
|
+
const chat = new ChatClient({
|
|
20
|
+
token: '<bearer-token>',
|
|
21
|
+
// baseUrl: 'https://your-chat-api', // optional, defaults to the hosted API
|
|
22
|
+
// logger: console, // optional, omit for silence
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
// …or exchange OAuth client credentials for a token automatically:
|
|
26
|
+
const chat2 = await ChatClient.fromCredentials({ clientId, clientSecret })
|
|
27
|
+
|
|
28
|
+
// Send text (string = one element; array = one request, one element per item)
|
|
29
|
+
await chat.sendMessage(msgID, 'Hello there!')
|
|
30
|
+
await chat.sendMessage(msgID, ['First line', 'Second line'])
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Options
|
|
34
|
+
|
|
35
|
+
| Option | Type | Default | Description |
|
|
36
|
+
| --------- | ---------------- | ---------------- | -------------------------------------------- |
|
|
37
|
+
| `token` | `string` | — (required) | Bearer token sent with every request. |
|
|
38
|
+
| `baseUrl` | `string` | hosted endpoint | Base URL of the chat API. |
|
|
39
|
+
| `logger` | `Logger` | `undefined` | Pass `console` to log; omit for no output. |
|
|
40
|
+
| `fetch` | `typeof fetch` | global `fetch` | Custom fetch (Node < 18, tests, etc.). |
|
|
41
|
+
|
|
42
|
+
## Sending messages
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// Images: upload first, then send by upload_id
|
|
46
|
+
const { upload_id } = await chat.uploadImage(bytes, 'photo.png', 'image/png')
|
|
47
|
+
await chat.sendImageMessage(msgID, [upload_id])
|
|
48
|
+
|
|
49
|
+
// Files
|
|
50
|
+
const file = await chat.uploadFile(bytes, 'report.pdf', 'application/pdf')
|
|
51
|
+
await chat.sendFileMessage(msgID, [file.upload_id])
|
|
52
|
+
|
|
53
|
+
// Sound (uploaded via uploadFile; duration in seconds)
|
|
54
|
+
const audio = await chat.uploadFile(bytes, 'clip.m4a', 'audio/m4a')
|
|
55
|
+
await chat.sendSoundMessage(msgID, audio.upload_id, 12)
|
|
56
|
+
|
|
57
|
+
// Typing indicator (best effort — never throws)
|
|
58
|
+
await chat.sendTypingStatus(msgID, true)
|
|
59
|
+
|
|
60
|
+
// Miniapp card
|
|
61
|
+
await chat.sendMiniAppMessage(msgID, {
|
|
62
|
+
appID: 'app123',
|
|
63
|
+
title: 'Open the app',
|
|
64
|
+
path: '/home',
|
|
65
|
+
imageURL: 'https://…/cover.png',
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
// Web link preview
|
|
69
|
+
await chat.sendWebLinkMessage(msgID, 'https://example.com')
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Streaming (typewriter effect)
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
// High-level: chunk the text and stream it for you (POST first, PATCH rest)
|
|
76
|
+
await chat.streamMessage(msgID, 'A long streamed reply…')
|
|
77
|
+
|
|
78
|
+
// Low-level: drive the chunks yourself
|
|
79
|
+
await chat.sendStreamingChunk(msgID, ['Hel'], false, true)
|
|
80
|
+
await chat.sendStreamingChunk(msgID, ['Hel', 'lo'], true, false)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## OAuth
|
|
84
|
+
|
|
85
|
+
If you don't already have a bearer token, exchange client credentials for one:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import { getAccessToken } from 'birdcash-chat-sdk-alpha'
|
|
89
|
+
|
|
90
|
+
const token = await getAccessToken({
|
|
91
|
+
clientId,
|
|
92
|
+
clientSecret,
|
|
93
|
+
scope: 'chat:write uploads:write', // default
|
|
94
|
+
})
|
|
95
|
+
// token.access_token, token.expires_in, …
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Lower-level exports
|
|
99
|
+
|
|
100
|
+
The element builders and helpers are also exported if you want to assemble
|
|
101
|
+
requests yourself:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { ElemType, textElem, imageElem, splitIntoChunks, toBase64 } from 'birdcash-chat-sdk-alpha'
|
|
105
|
+
```
|
package/dist/main.cjs.js
ADDED
|
@@ -0,0 +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,n,r;const{clientId:o,clientSecret:i}=t;if(!o||!i)throw new Error("getAccessToken: clientId and clientSecret are required");const a=(null!==(s=t.baseUrl)&&void 0!==s?s:e).replace(/\/$/,""),l=null!==(n=t.scope)&&void 0!==n?n:"chat:write uploads:write",u=null!==(r=t.fetch)&&void 0!==r?r:"undefined"!=typeof fetch?fetch.bind(globalThis):void 0;if(!u)throw new Error("getAccessToken: no fetch implementation available; pass `fetch` in options");const p=await u(`${a}/v1/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"client_credentials",client_id:o,client_secret:i,scope:l})});if(!p.ok)throw new Error(`Token request failed: ${p.status} ${p.statusText}`);return await p.json()}var n;function r(e){return{elemType:exports.ElemType.Text,priority:0,textElem:{text:e.trim()}}}function o(e){return{elemType:exports.ElemType.Image,priority:0,imageElem:{upload_ids:e}}}function i(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){return{elemType:exports.ElemType.Custom,priority:0,customElem:{data:e}}}function u(e){return{elemType:exports.ElemType.MiniApp,priority:0,miniAppElem:e}}function p(e){const t=new Uint8Array(e.byteLength);return t.set(e),t}function c(e){const t=(new TextEncoder).encode(e);let s="";for(const e of t)s+=String.fromCharCode(e);return btoa(s)}function d(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 h{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,baseUrl:e.baseUrl,fetch:e.fetch});return new h({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([p(e)],{type:s}),t);const o=await this.fetchImpl(`${this.baseUrl}/v1/upload/image`,{method:"POST",headers:{Authorization:`Bearer ${this.token}`},body:r}),i=await o.json();if(!o.ok)throw new Error(`uploadImage failed: ${o.status} ${JSON.stringify(i)}`);if(!i.upload_id)throw new Error(`uploadImage: missing upload_id: ${JSON.stringify(i)}`);return{upload_id:i.upload_id,url:null!==(n=i.url)&&void 0!==n?n:""}}async uploadFile(e,t,s="application/octet-stream"){var n;const r=new FormData;r.append("file",new Blob([p(e)],{type:s}),t);const o=await this.fetchImpl(`${this.baseUrl}/v1/upload/file`,{method:"POST",headers:{Authorization:`Bearer ${this.token}`},body:r}),i=await o.json();if(!o.ok)throw new Error(`uploadFile failed: ${o.status} ${JSON.stringify(i)}`);if(!i.upload_id)throw new Error(`uploadFile: missing upload_id: ${JSON.stringify(i)}`);return{upload_id:i.upload_id,url:null!==(n=i.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 o=await this.postElements(e,n),i=await o.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] message sent",{status:o.status,elements:n.length}),!o.ok)throw new Error(`sendMessage failed: ${o.status} ${i}`)}async sendImageMessage(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] 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,[i(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)]),o=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} ${o.slice(0,500)}`)}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,[l(c(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"},o={msgID:e,...l(c(JSON.stringify(r)))},i=await this.postElements(e,[o],n?"POST":"PATCH");if(!i.ok)throw new Error(`sendStreamingChunk failed: ${i.statusText}`);await i.text()}async sendMiniAppMessage(e,t){var s;const n=await this.postElements(e,[u(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},o=await this.postElements(e,[l(c(JSON.stringify(r)))]),i=await o.text();if(null===(n=this.logger)||void 0===n||n.log("[chat-sdk] web link sent",{status:o.status}),!o.ok)throw new Error(`sendWebLinkMessage failed: ${o.status} ${i.slice(0,500)}`)}async streamMessage(e,t,s=10){const n=d(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)}}h.ElemType=exports.ElemType,exports.ChatClient=h,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=l,exports.fileElem=i,exports.getAccessToken=s,exports.imageElem=o,exports.miniAppElem=u,exports.soundElem=a,exports.splitIntoChunks=d,exports.textElem=r,exports.toBase64=c;
|
|
2
|
+
//# sourceMappingURL=main.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
package/dist/main.esm.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const t="https://chat-api2-3rnt.onrender.com",e="web_link";async function n(e){var n,s,i;const{clientId:a,clientSecret:r}=e;if(!a||!r)throw new Error("getAccessToken: clientId and clientSecret are required");const o=(null!==(n=e.baseUrl)&&void 0!==n?n:t).replace(/\/$/,""),l=null!==(s=e.scope)&&void 0!==s?s:"chat:write uploads:write",u=null!==(i=e.fetch)&&void 0!==i?i:"undefined"!=typeof fetch?fetch.bind(globalThis):void 0;if(!u)throw new Error("getAccessToken: no fetch implementation available; pass `fetch` in options");const c=await u(`${o}/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:r,scope:l})});if(!c.ok)throw new Error(`Token request failed: ${c.status} ${c.statusText}`);return await c.json()}var s;function i(t){return{elemType:s.Text,priority:0,textElem:{text:t.trim()}}}function a(t){return{elemType:s.Image,priority:0,imageElem:{upload_ids:t}}}function r(t){return{elemType:s.File,priority:0,fileElem:{upload_ids:t}}}function o(t,e=0){return{elemType:s.Sound,priority:0,soundElem:{uploadID:t,duration:e}}}function l(t){return{elemType:s.Custom,priority:0,customElem:{data:t}}}function u(t){return{elemType:s.MiniApp,priority:0,miniAppElem:t}}function c(t){const e=new Uint8Array(t.byteLength);return e.set(t),e}function d(t){let e="";const n=new Uint8Array(t),s=n.byteLength;for(let t=0;t<s;t++)e+=String.fromCharCode(n[t]);return btoa(e)}function h(t){const e=atob(t),n=e.length,s=new Uint8Array(n);for(let t=0;t<n;t++)s[t]=e.charCodeAt(t);return s.buffer}function p(t){const e=(new TextEncoder).encode(t);let n="";for(const t of e)n+=String.fromCharCode(t);return btoa(n)}function g(t,e=10){if(t.length<=e)return[t];const n=[];let s="";for(const i of t.split(" "))s.length+i.length+1>e&&s.length>0?(n.push(s.trim()+" "),s=i):s+=(s?" ":"")+i;return s&&n.push(s.trim()),n.length>0?n:[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"}(s||(s={}));class f{constructor(e){var n,s;if(!(null==e?void 0:e.token))throw new Error("ChatClient: token is required");this.token=e.token,this.baseUrl=(null!==(n=e.baseUrl)&&void 0!==n?n:t).replace(/\/$/,""),this.logger=e.logger;const i=null!==(s=e.fetch)&&void 0!==s?s:"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 n({clientId:t.clientId,clientSecret:t.clientSecret,scope:t.scope,baseUrl:t.baseUrl,fetch:t.fetch});return new f({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,n="POST"){return await this.fetchImpl(this.replyUrl(t),{method:n,headers:this.authHeaders,body:JSON.stringify(e)})}async uploadImage(t,e,n="image/png"){var s;const i=new FormData;i.append("file",new Blob([c(t)],{type:n}),e);const a=await this.fetchImpl(`${this.baseUrl}/v1/upload/image`,{method:"POST",headers:{Authorization:`Bearer ${this.token}`},body:i}),r=await a.json();if(!a.ok)throw new Error(`uploadImage failed: ${a.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!==(s=r.url)&&void 0!==s?s:""}}async uploadFile(t,e,n="application/octet-stream"){var s;const i=new FormData;i.append("file",new Blob([c(t)],{type:n}),e);const a=await this.fetchImpl(`${this.baseUrl}/v1/upload/file`,{method:"POST",headers:{Authorization:`Bearer ${this.token}`},body:i}),r=await a.json();if(!a.ok)throw new Error(`uploadFile failed: ${a.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!==(s=r.url)&&void 0!==s?s:""}}async sendMessage(t,e){var n;const s=Array.isArray(e)?e.map((t=>t.trim())).filter(Boolean).map(i):[i(e)];if(0===s.length)return;const a=await this.postElements(t,s),r=await a.text();if(null===(n=this.logger)||void 0===n||n.log("[chat-sdk] message sent",{status:a.status,elements:s.length}),!a.ok)throw new Error(`sendMessage failed: ${a.status} ${r}`)}async sendImageMessage(t,e){var n;if(!(null==e?void 0:e.length))return;const s=await this.postElements(t,[a(e)]),i=await s.text();if(null===(n=this.logger)||void 0===n||n.log("[chat-sdk] image message sent",{status:s.status}),!s.ok)throw new Error(`sendImageMessage failed: ${s.status} ${i}`)}async sendFileMessage(t,e){var n;if(!(null==e?void 0:e.length))return;const s=await this.postElements(t,[r(e)]),i=await s.text();if(null===(n=this.logger)||void 0===n||n.log("[chat-sdk] file message sent",{status:s.status}),!s.ok)throw new Error(`sendFileMessage failed: ${s.status} ${i}`)}async sendSoundMessage(t,e,n=0){var s;if(!(null==e?void 0:e.trim()))return;const i=await this.postElements(t,[o(e,n)]),a=await i.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] sound message sent",{status:i.status}),!i.ok)throw new Error(`sendSoundMessage failed: ${i.status} ${a.slice(0,500)}`)}async sendTypingStatus(t,e){var n,s;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,[l(p(JSON.stringify(i)))])}catch(t){null===(n=this.logger)||void 0===n||n.error("[chat-sdk] typing status failed",null!==(s=null==t?void 0:t.message)&&void 0!==s?s:String(t))}}async sendStreamingChunk(t,e,n,s){const i={businessID:"chatbotPlugin",src:0,chunks:e,isFinished:n,TMessageCell_Name:"ChatbotMessageCell_Minimalist",TMessageCell_Data_Name:"ChatbotMessageCellData"},a={msgID:t,...l(p(JSON.stringify(i)))},r=await this.postElements(t,[a],s?"POST":"PATCH");if(!r.ok)throw new Error(`sendStreamingChunk failed: ${r.statusText}`);await r.text()}async sendMiniAppMessage(t,e){var n;const s=await this.postElements(t,[u(e)]),i=await s.text();if(null===(n=this.logger)||void 0===n||n.log("[chat-sdk] miniapp card sent",{status:s.status}),!s.ok)throw new Error(`sendMiniAppMessage failed: ${s.status} ${i}`)}async sendWebLinkMessage(t,n){var s;const i={businessID:e,url:n},a=await this.postElements(t,[l(p(JSON.stringify(i)))]),r=await a.text();if(null===(s=this.logger)||void 0===s||s.log("[chat-sdk] web link sent",{status:a.status}),!a.ok)throw new Error(`sendWebLinkMessage failed: ${a.status} ${r.slice(0,500)}`)}async streamMessage(t,e,n=10){const s=g(e,n),i=[];for(let e=0;e<s.length;e++)i.push(s[e]),await this.sendStreamingChunk(t,i,e===s.length-1,0===e)}}f.ElemType=s;export{f as ChatClient,t as DEFAULT_BASE_URL,s as ElemType,e as WEB_LINK_BUSINESS_ID,d as arrayBufferToBase64,h as base64ToArrayBuffer,l as customElem,r as fileElem,n as getAccessToken,a as imageElem,u as miniAppElem,o as soundElem,g as splitIntoChunks,i as textElem,p as toBase64};
|
|
2
|
+
//# sourceMappingURL=main.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { TokenResponse } from './types';
|
|
2
|
+
export interface GetAccessTokenOptions {
|
|
3
|
+
clientId: string;
|
|
4
|
+
clientSecret: string;
|
|
5
|
+
/** Space-separated scopes. Defaults to 'chat:write uploads:write'. */
|
|
6
|
+
scope?: string;
|
|
7
|
+
/** Base URL of the chat API. Defaults to the hosted endpoint. */
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
/** Optional custom fetch implementation. */
|
|
10
|
+
fetch?: typeof fetch;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Exchange OAuth client credentials for an access token
|
|
14
|
+
* (POST /v1/oauth/token, grant_type=client_credentials).
|
|
15
|
+
*/
|
|
16
|
+
export declare function getAccessToken(options: GetAccessTokenOptions): Promise<TokenResponse>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { ChatClientOptions, ElemType, Logger, MiniAppElem, UploadV1FileResponse, UploadV1ImageResponse } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* ChatClient wraps the chat API reply/upload endpoints. Construct once with a
|
|
4
|
+
* token (and optionally a baseUrl / logger), then call the message helpers.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* const chat = new ChatClient({ token })
|
|
8
|
+
* await chat.sendMessage(msgID, 'Hello!')
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export declare class ChatClient {
|
|
12
|
+
private readonly token;
|
|
13
|
+
private readonly baseUrl;
|
|
14
|
+
private readonly logger?;
|
|
15
|
+
private readonly fetchImpl;
|
|
16
|
+
constructor(options: ChatClientOptions);
|
|
17
|
+
/**
|
|
18
|
+
* Build a ChatClient by first exchanging OAuth client credentials for a token.
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* const chat = await ChatClient.fromCredentials({ clientId, clientSecret })
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
static fromCredentials(options: {
|
|
25
|
+
clientId: string;
|
|
26
|
+
clientSecret: string;
|
|
27
|
+
scope?: string;
|
|
28
|
+
baseUrl?: string;
|
|
29
|
+
logger?: Logger;
|
|
30
|
+
fetch?: typeof fetch;
|
|
31
|
+
}): Promise<ChatClient>;
|
|
32
|
+
private get authHeaders();
|
|
33
|
+
private replyUrl;
|
|
34
|
+
/** POST an array of message elements to a message's reply endpoint. */
|
|
35
|
+
private postElements;
|
|
36
|
+
/** Upload a binary image (POST /v1/upload/image) and return { upload_id, url }. */
|
|
37
|
+
uploadImage(file: Uint8Array, filename: string, mimeType?: string): Promise<UploadV1ImageResponse>;
|
|
38
|
+
/** Upload a binary file (POST /v1/upload/file) and return { upload_id, url }. */
|
|
39
|
+
uploadFile(file: Uint8Array, filename: string, mimeType?: string): Promise<UploadV1FileResponse>;
|
|
40
|
+
/**
|
|
41
|
+
* Send one or more text messages as a reply. A string sends a single element;
|
|
42
|
+
* an array sends one request with multiple elements (one per line/paragraph).
|
|
43
|
+
*/
|
|
44
|
+
sendMessage(msgID: string, message: string | string[]): Promise<void>;
|
|
45
|
+
/** Send an image message by upload_id(s) (use uploadImage first). */
|
|
46
|
+
sendImageMessage(msgID: string, uploadIDs: string[]): Promise<void>;
|
|
47
|
+
/** Send a file message by upload_id(s) (use uploadFile first). */
|
|
48
|
+
sendFileMessage(msgID: string, uploadIDs: string[]): Promise<void>;
|
|
49
|
+
/** Send a sound message by uploadID (duration in seconds). */
|
|
50
|
+
sendSoundMessage(msgID: string, uploadID: string, durationSec?: number): Promise<void>;
|
|
51
|
+
/** Send typing status (best effort — never throws). */
|
|
52
|
+
sendTypingStatus(msgID: string, isTyping: boolean): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Send a streaming chunk (chatbotPlugin format). Use POST for the first chunk
|
|
55
|
+
* and PATCH for the rest (`isFirstChunk` controls the method).
|
|
56
|
+
*/
|
|
57
|
+
sendStreamingChunk(msgID: string, chunks: string[], isFinished: boolean, isFirstChunk: boolean): Promise<void>;
|
|
58
|
+
/** Send a miniapp card message. */
|
|
59
|
+
sendMiniAppMessage(msgID: string, miniApp: MiniAppElem): Promise<void>;
|
|
60
|
+
/** Send a web link preview custom message (matches iOS `webLinkPreviewMessage`). */
|
|
61
|
+
sendWebLinkMessage(msgID: string, url: string): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Stream a message in chunks for a typewriter effect. Splits `text`, then
|
|
64
|
+
* sends each chunk via sendStreamingChunk (POST first, PATCH after).
|
|
65
|
+
*/
|
|
66
|
+
streamMessage(msgID: string, text: string, minChunkSize?: number): Promise<void>;
|
|
67
|
+
/** Re-export of the element types for convenience. */
|
|
68
|
+
static ElemType: typeof ElemType;
|
|
69
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Default hosted chat API endpoint. Override via ChatClient `baseUrl`. */
|
|
2
|
+
export declare const DEFAULT_BASE_URL = "https://chat-api2-3rnt.onrender.com";
|
|
3
|
+
/** Must match iOS `WebLinkMessageCellData.businessID`. */
|
|
4
|
+
export declare const WEB_LINK_BUSINESS_ID = "web_link";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { MessageElem, MiniAppElem } from './types';
|
|
2
|
+
/** Single text message element for the chat API body. */
|
|
3
|
+
export declare function textElem(text: string): MessageElem;
|
|
4
|
+
/** Single image message element (requires upload_ids from uploadImage). */
|
|
5
|
+
export declare function imageElem(uploadIDs: string[]): MessageElem;
|
|
6
|
+
/** Single file message element (requires upload_ids from uploadFile). */
|
|
7
|
+
export declare function fileElem(uploadIDs: string[]): MessageElem;
|
|
8
|
+
/** Single sound message element (requires an uploadID; duration in seconds). */
|
|
9
|
+
export declare function soundElem(uploadID: string, durationSec?: number): MessageElem;
|
|
10
|
+
/** Single custom message element carrying a base64-encoded payload. */
|
|
11
|
+
export declare function customElem(dataBase64: string): MessageElem;
|
|
12
|
+
/** Single miniapp card message element. */
|
|
13
|
+
export declare function miniAppElem(miniApp: MiniAppElem): MessageElem;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { ChatClient } from './client';
|
|
2
|
+
export { getAccessToken } from './auth';
|
|
3
|
+
export type { GetAccessTokenOptions } from './auth';
|
|
4
|
+
export * from './types';
|
|
5
|
+
export * from './elements';
|
|
6
|
+
export * from './constants';
|
|
7
|
+
export { toBase64, splitIntoChunks, arrayBufferToBase64, base64ToArrayBuffer, } from './utils';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** Mirrors datastore/database/message.go ElemType constants. */
|
|
2
|
+
export declare enum ElemType {
|
|
3
|
+
None = 0,
|
|
4
|
+
Text = 1,
|
|
5
|
+
Image = 2,
|
|
6
|
+
Sound = 3,
|
|
7
|
+
Video = 4,
|
|
8
|
+
File = 5,
|
|
9
|
+
Sticker = 6,
|
|
10
|
+
GroupTips = 7,
|
|
11
|
+
Merger = 8,
|
|
12
|
+
Custom = 9,
|
|
13
|
+
Location = 10,
|
|
14
|
+
GroupAnnouncement = 11,
|
|
15
|
+
Quote = 12,
|
|
16
|
+
InputStatus = 14,
|
|
17
|
+
TypingStatus = 15,
|
|
18
|
+
MiniApp = 16,
|
|
19
|
+
Order = 17,
|
|
20
|
+
Transfer = 18
|
|
21
|
+
}
|
|
22
|
+
export type UploadV1ImageResponse = {
|
|
23
|
+
upload_id: string;
|
|
24
|
+
url: string;
|
|
25
|
+
};
|
|
26
|
+
export type UploadV1FileResponse = {
|
|
27
|
+
upload_id: string;
|
|
28
|
+
url: string;
|
|
29
|
+
};
|
|
30
|
+
export type MiniAppElem = {
|
|
31
|
+
appID: string;
|
|
32
|
+
title: string;
|
|
33
|
+
path: string;
|
|
34
|
+
imageURL: string;
|
|
35
|
+
};
|
|
36
|
+
export type SoundElem = {
|
|
37
|
+
uploadID: string;
|
|
38
|
+
duration: number;
|
|
39
|
+
};
|
|
40
|
+
/** OAuth client_credentials token response (POST /v1/oauth/token). */
|
|
41
|
+
export type TokenResponse = {
|
|
42
|
+
access_token: string;
|
|
43
|
+
expires_in: number;
|
|
44
|
+
refresh_token: string;
|
|
45
|
+
scope: string;
|
|
46
|
+
token_type: string;
|
|
47
|
+
};
|
|
48
|
+
/** A single message element as accepted by the chat API reply endpoint. */
|
|
49
|
+
export type MessageElem = {
|
|
50
|
+
elemType: ElemType;
|
|
51
|
+
priority: number;
|
|
52
|
+
msgID?: string;
|
|
53
|
+
textElem?: {
|
|
54
|
+
text: string;
|
|
55
|
+
};
|
|
56
|
+
imageElem?: {
|
|
57
|
+
upload_ids: string[];
|
|
58
|
+
};
|
|
59
|
+
fileElem?: {
|
|
60
|
+
upload_ids: string[];
|
|
61
|
+
};
|
|
62
|
+
soundElem?: SoundElem;
|
|
63
|
+
customElem?: {
|
|
64
|
+
data: string;
|
|
65
|
+
};
|
|
66
|
+
miniAppElem?: MiniAppElem;
|
|
67
|
+
};
|
|
68
|
+
/** Minimal logger interface — pass `console` to enable logging, omit for silence. */
|
|
69
|
+
export interface Logger {
|
|
70
|
+
log: (...args: any[]) => void;
|
|
71
|
+
error: (...args: any[]) => void;
|
|
72
|
+
}
|
|
73
|
+
export interface ChatClientOptions {
|
|
74
|
+
/** Bearer token used to authenticate every request. */
|
|
75
|
+
token: string;
|
|
76
|
+
/** Base URL of the chat API. Defaults to the hosted endpoint. */
|
|
77
|
+
baseUrl?: string;
|
|
78
|
+
/** Optional logger. Omit (or pass undefined) for no logging. */
|
|
79
|
+
logger?: Logger;
|
|
80
|
+
/** Optional custom fetch implementation (e.g. for Node < 18 or testing). */
|
|
81
|
+
fetch?: typeof fetch;
|
|
82
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Copy bytes into an ArrayBuffer-backed view for Blob / FormData APIs. */
|
|
2
|
+
export declare function bytesToBlobPart(bytes: Uint8Array): BlobPart;
|
|
3
|
+
export declare function arrayBufferToBase64(buffer: ArrayBuffer): string;
|
|
4
|
+
export declare function base64ToArrayBuffer(base64: string): ArrayBuffer;
|
|
5
|
+
/** UTF-8 safe base64 encoding (browser/Workers style). */
|
|
6
|
+
export declare function toBase64(str: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Splits text into chunks for a streaming effect (word boundaries, min chunk size).
|
|
9
|
+
*/
|
|
10
|
+
export declare function splitIntoChunks(text: string, minChunkSize?: number): string[];
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "birdcash-chat-sdk-alpha",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "TypeScript SDK for sending messages through the chat API (text, images, files, miniapp cards, streaming).",
|
|
5
|
+
"main": "dist/main.cjs.js",
|
|
6
|
+
"module": "dist/main.esm.js",
|
|
7
|
+
"types": "dist/types/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"test": "jest",
|
|
10
|
+
"build": "rm -rf dist && rollup -c",
|
|
11
|
+
"prepublishOnly": "npm run build"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"author": "andy",
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@rollup/plugin-commonjs": "^26.0.1",
|
|
20
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
21
|
+
"@types/jest": "^29.5.14",
|
|
22
|
+
"jest": "^29.7.0",
|
|
23
|
+
"jest-environment-jsdom": "^29.7.0",
|
|
24
|
+
"rollup": "^2.79.1",
|
|
25
|
+
"rollup-plugin-terser": "^7.0.2",
|
|
26
|
+
"rollup-plugin-typescript2": "^0.36.0",
|
|
27
|
+
"ts-jest": "^29.2.5",
|
|
28
|
+
"typescript": "^4.9.5"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"tslib": "^2.7.0"
|
|
32
|
+
}
|
|
33
|
+
}
|