@ecodrix/erix-api 3.1.2 → 3.2.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 +69 -1
- package/dist/cli.js +3 -3
- package/dist/ts/cjs/index.cjs +1 -1
- package/dist/ts/cjs/visitor.cjs +1 -0
- package/dist/ts/esm/chunk-XPXDFZWU.js +1 -0
- package/dist/ts/esm/error-DuNpKP1d.d.ts +158 -0
- package/dist/ts/esm/index.d.ts +77 -70
- package/dist/ts/esm/index.js +1 -1
- package/dist/ts/esm/visitor.d.ts +147 -0
- package/dist/ts/esm/visitor.js +1 -0
- package/package.json +6 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { V as VisitorMessage, d as VisitorTyping, A as APIError, e as WebchatSettings } from './error-DuNpKP1d.js';
|
|
2
|
+
export { W as WebchatSettingsPatch } from './error-DuNpKP1d.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @module visitor
|
|
6
|
+
* @responsibility `@ecodrix/erix-api/visitor` — the anonymous website-chat
|
|
7
|
+
* visitor client. No API key and no workspace data: a visitor reads and
|
|
8
|
+
* writes only their own thread, identified by their session id. Uses `fetch`
|
|
9
|
+
* for HTTP and loads `socket.io-client` only when `connect()` is called, so a
|
|
10
|
+
* chat widget doesn't ship the full SDK (no axios on this path).
|
|
11
|
+
*
|
|
12
|
+
* Every import here must stay free of axios: the subpath's whole point is a
|
|
13
|
+
* small bundle (checked by `visitor.test.ts` and `scripts/smoke-dist.mjs`).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** The widget's look, as a visitor reads it (no domains, no staff-name switch). */
|
|
17
|
+
type WebchatWidget = Omit<WebchatSettings, "allowedDomains" | "showStaffNames">;
|
|
18
|
+
/**
|
|
19
|
+
* Why a visitor call failed. Server codes, plus `NETWORK_ERROR` when no
|
|
20
|
+
* answer came back (offline, timed out): only that one and `INTERNAL_ERROR`
|
|
21
|
+
* are worth an immediate "Try again".
|
|
22
|
+
*/
|
|
23
|
+
type WebchatErrorCode = "VALIDATION_ERROR" | "NOT_FOUND" | "ORIGIN_NOT_ALLOWED" | "RATE_LIMITED" | "INTERNAL_ERROR" | "MALICIOUS_INPUT" | "NETWORK_ERROR";
|
|
24
|
+
/**
|
|
25
|
+
* A failed visitor call or a refused live connection. `status` is the HTTP
|
|
26
|
+
* status (absent for network and socket errors); `field` names the input a
|
|
27
|
+
* `VALIDATION_ERROR` is about (`text`, `name`, `phone`, `email`, …) and
|
|
28
|
+
* `message` is copy the visitor can read.
|
|
29
|
+
*/
|
|
30
|
+
declare class WebchatVisitorError extends APIError {
|
|
31
|
+
code: WebchatErrorCode;
|
|
32
|
+
field?: string;
|
|
33
|
+
constructor(message: string, code: WebchatErrorCode, status?: number, field?: string);
|
|
34
|
+
}
|
|
35
|
+
interface WebchatVisitorOptions {
|
|
36
|
+
/** API host, scheme and domain only. Default `https://api.ecodrix.com`. */
|
|
37
|
+
apiUrl?: string;
|
|
38
|
+
/** The workspace's public client code (from the embed snippet). */
|
|
39
|
+
clientCode: string;
|
|
40
|
+
/**
|
|
41
|
+
* The visitor's id and only secret: random, 20–80 characters of
|
|
42
|
+
* `A-Z a-z 0-9 _ -` (e.g. `crypto.randomUUID()`). Keep it for the visitor's
|
|
43
|
+
* next visit to continue the same thread.
|
|
44
|
+
*/
|
|
45
|
+
sessionId: string;
|
|
46
|
+
/** The origin of the site the chat runs on, checked against the workspace's allowed domains. */
|
|
47
|
+
parent: string;
|
|
48
|
+
}
|
|
49
|
+
/** The page the visitor is on. Query string and hash are dropped before sending. */
|
|
50
|
+
interface VisitorPage {
|
|
51
|
+
/** Starts with `/`, up to 200 characters. */
|
|
52
|
+
path: string;
|
|
53
|
+
/** Up to 120 characters. */
|
|
54
|
+
title?: string;
|
|
55
|
+
}
|
|
56
|
+
interface VisitorSendOptions {
|
|
57
|
+
/**
|
|
58
|
+
* Your id for this message, 8–64 characters of `A-Z a-z 0-9 _ -`. Resend
|
|
59
|
+
* with the same id after a failure: it's stored (and answered) once.
|
|
60
|
+
*/
|
|
61
|
+
messageId?: string;
|
|
62
|
+
/** The visitor's name, if known. */
|
|
63
|
+
name?: string;
|
|
64
|
+
page?: VisitorPage;
|
|
65
|
+
}
|
|
66
|
+
/** The visitor's details (name plus a phone or an email). */
|
|
67
|
+
interface VisitorContact {
|
|
68
|
+
name: string;
|
|
69
|
+
phone?: string;
|
|
70
|
+
email?: string;
|
|
71
|
+
/** Ticked the consent box (shown only when `messages()` returned `consentNotice`). */
|
|
72
|
+
consent?: boolean;
|
|
73
|
+
}
|
|
74
|
+
/** `messages()`: the thread, the widget's look, and the consent notice when the workspace has a privacy link. */
|
|
75
|
+
interface VisitorThread {
|
|
76
|
+
success: boolean;
|
|
77
|
+
data: VisitorMessage[];
|
|
78
|
+
widget: WebchatWidget;
|
|
79
|
+
consentNotice?: string;
|
|
80
|
+
privacyPolicyUrl?: string;
|
|
81
|
+
}
|
|
82
|
+
/** `send()`: the stored message, or `{ duplicate: true }` when a resent `messageId` was already stored. */
|
|
83
|
+
interface VisitorSendResult {
|
|
84
|
+
success: boolean;
|
|
85
|
+
data: VisitorMessage | {
|
|
86
|
+
duplicate: true;
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
interface VisitorLiveHandlers {
|
|
90
|
+
/** A new message on the thread (team, AI or system; also the visitor's own). */
|
|
91
|
+
onMessage?: (message: VisitorMessage) => void;
|
|
92
|
+
onTyping?: (typing: VisitorTyping) => void;
|
|
93
|
+
/**
|
|
94
|
+
* The live connection failed or was refused. Fall back to polling
|
|
95
|
+
* `messages()` until `onReconnect`. Network drops retry on their own;
|
|
96
|
+
* `RATE_LIMITED` and `INTERNAL_ERROR` refusals retry after a minute; other
|
|
97
|
+
* refusals (`ORIGIN_NOT_ALLOWED`, `NOT_FOUND`, `VALIDATION_ERROR`) don't.
|
|
98
|
+
*/
|
|
99
|
+
onError?: (error: WebchatVisitorError) => void;
|
|
100
|
+
/**
|
|
101
|
+
* Live again after a drop or a refusal. Nothing missed in between is
|
|
102
|
+
* replayed: call `messages()` now.
|
|
103
|
+
*/
|
|
104
|
+
onReconnect?: () => void;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* One website visitor's chat with a workspace: send, read, leave contact
|
|
108
|
+
* details, and get replies live.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* import { WebchatVisitor } from "@ecodrix/erix-api/visitor";
|
|
113
|
+
*
|
|
114
|
+
* const chat = new WebchatVisitor({ clientCode: "ERIX_CLNT_XXXXXX", sessionId, parent: location.origin });
|
|
115
|
+
* const { data: thread, widget } = await chat.messages();
|
|
116
|
+
* await chat.send("Do you open on Sunday?", { messageId: crypto.randomUUID() });
|
|
117
|
+
* chat.connect({ onMessage: (m) => render(m), onReconnect: () => chat.messages().then(redraw) });
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
declare class WebchatVisitor {
|
|
121
|
+
private readonly options;
|
|
122
|
+
private readonly host;
|
|
123
|
+
private readonly base;
|
|
124
|
+
private socket;
|
|
125
|
+
private opening;
|
|
126
|
+
private retry;
|
|
127
|
+
constructor(options: WebchatVisitorOptions);
|
|
128
|
+
/** Send the visitor's message. */
|
|
129
|
+
send(text: string, opts?: VisitorSendOptions): Promise<VisitorSendResult>;
|
|
130
|
+
/** The visitor's thread, oldest first, with the widget's look. */
|
|
131
|
+
messages(): Promise<VisitorThread>;
|
|
132
|
+
/** Save the visitor's details and link the thread to a contact. */
|
|
133
|
+
contact(details: VisitorContact): Promise<{
|
|
134
|
+
success: boolean;
|
|
135
|
+
}>;
|
|
136
|
+
/**
|
|
137
|
+
* Get replies and typing live over the `/visitor` socket. Only works inside
|
|
138
|
+
* the ERIX widget frame: the server refuses sockets opened from other
|
|
139
|
+
* origins, so a chat UI on your own page polls `messages()` instead.
|
|
140
|
+
*/
|
|
141
|
+
connect(handlers: VisitorLiveHandlers): Promise<void>;
|
|
142
|
+
/** Close the live connection. `connect()` again to reopen. */
|
|
143
|
+
disconnect(): void;
|
|
144
|
+
private call;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export { type VisitorContact, type VisitorLiveHandlers, VisitorMessage, type VisitorPage, type VisitorSendOptions, type VisitorSendResult, type VisitorThread, VisitorTyping, type WebchatErrorCode, WebchatSettings, WebchatVisitor, WebchatVisitorError, type WebchatVisitorOptions, type WebchatWidget };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as l,b as h,c as m,d as a,g as d,h as O,j as f}from"./chunk-XPXDFZWU.js";var R=class extends f{constructor(i,n,p,o){super(i,p,n);a(this,"field");this.name="WebchatVisitorError",this.field=o}},_=3e4,N=6e4,V=new Set(["RATE_LIMITED","INTERNAL_ERROR"]),S={400:"VALIDATION_ERROR",403:"ORIGIN_NOT_ALLOWED",404:"NOT_FOUND",429:"RATE_LIMITED"},I=class{constructor(e){a(this,"options",e);a(this,"host");a(this,"base");a(this,"socket",null);a(this,"opening",null);a(this,"retry");var i;this.host=((i=e.apiUrl)!=null?i:d).replace(/\/+$/,""),this.base=`${O("v1",this.host)}/infra/connect/webhooks/webchat/${encodeURIComponent(e.clientCode)}`}send(e,i={}){let{sessionId:n,parent:p}=this.options,t=i,{page:o}=t,c=m(t,["page"]);return this.call("/message",l(l({sessionId:n,text:e,parent:p},c),o&&{page:h(l({},o),{path:o.path.split(/[?#]/)[0]})}))}messages(){let{sessionId:e,parent:i}=this.options;return this.call(`/messages?${new URLSearchParams({sessionId:e,parent:i})}`)}contact(e){let{sessionId:i,parent:n}=this.options;return this.call("/contact",l({sessionId:i,parent:n},e))}connect(e){if(this.opening)return this.opening;let{clientCode:i,sessionId:n,parent:p}=this.options,o=import("socket.io-client").then(({io:c})=>{if(this.opening!==o)return;let t=c(`${this.host}/visitor`,{auth:{clientCode:i,sessionId:n,parent:p}});this.socket=t;let g=!1;t.on("connect",()=>{var r;g&&((r=e.onReconnect)==null||r.call(e)),g=!1}),t.on("disconnect",()=>{g=!0}),t.on("connect_error",r=>{var u,E,T;g=!0;let s=(E=(u=r.data)==null?void 0:u.code)!=null?E:"NETWORK_ERROR";(T=e.onError)==null||T.call(e,new R(r.message,s)),!t.active&&V.has(s)&&(clearTimeout(this.retry),this.retry=setTimeout(()=>{this.socket===t&&t.connect()},N))}),t.on("webchat.message",r=>{var s;return(s=e.onMessage)==null?void 0:s.call(e,r)}),t.on("webchat.typing",r=>{var s;return(s=e.onTyping)==null?void 0:s.call(e,r)})},c=>{var t;this.opening===o&&(this.opening=null,(t=e.onError)==null||t.call(e,new R((c==null?void 0:c.message)||"Network error","NETWORK_ERROR")))});return this.opening=o,o}disconnect(){var e;clearTimeout(this.retry),(e=this.socket)==null||e.disconnect(),this.socket=null,this.opening=null}async call(e,i){var g,r;let n=new AbortController,p=setTimeout(()=>n.abort(),_),o,c;try{o=await fetch(`${this.base}${e}`,{method:i?"POST":"GET",headers:i?{"content-type":"application/json"}:void 0,body:i?JSON.stringify(i):void 0,signal:n.signal}),c=await o.text()}catch(s){throw new R((s==null?void 0:s.message)||"Network error","NETWORK_ERROR")}finally{clearTimeout(p)}let t=null;try{t=JSON.parse(c)}catch(s){}if(o.ok&&(t!=null&&t.success))return t;throw new R((t==null?void 0:t.message)||(t==null?void 0:t.error)||`Request failed (${o.status})`,(r=(g=t==null?void 0:t.code)!=null?g:S[o.status])!=null?r:"INTERNAL_ERROR",o.status,t==null?void 0:t.field)}};export{I as WebchatVisitor,R as WebchatVisitorError};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecodrix/erix-api",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"author": "ECODrIx Team <contact@ecodrix.com>",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Official TypeScript SDK for the ERIX API by ECODrIx: CRM, Flow automations, one inbox across WhatsApp, Instagram and email, GST invoices and payments. Node and browser (proxy mode).",
|
|
@@ -33,6 +33,11 @@
|
|
|
33
33
|
"types": "./dist/ts/esm/index.d.ts",
|
|
34
34
|
"import": "./dist/ts/esm/index.js",
|
|
35
35
|
"require": "./dist/ts/cjs/index.cjs"
|
|
36
|
+
},
|
|
37
|
+
"./visitor": {
|
|
38
|
+
"types": "./dist/ts/esm/visitor.d.ts",
|
|
39
|
+
"import": "./dist/ts/esm/visitor.js",
|
|
40
|
+
"require": "./dist/ts/cjs/visitor.cjs"
|
|
36
41
|
}
|
|
37
42
|
},
|
|
38
43
|
"files": [
|