react-native-newinstance-chat 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NewinstanceChat.podspec +1 -1
- package/README.md +192 -70
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/newinstancechat/NewinstanceChatModule.kt +27 -25
- package/ios/NewinstanceChat.mm +7 -0
- package/ios/NewinstanceChatImpl.swift +35 -17
- package/lib/module/NativeNewinstanceChat.js.map +1 -1
- package/lib/module/index.js +47 -16
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/NativeNewinstanceChat.d.ts +1 -0
- package/lib/typescript/src/NativeNewinstanceChat.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -10
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +3 -71
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/NativeNewinstanceChat.ts +1 -0
- package/src/index.tsx +57 -14
- package/src/types.ts +2 -58
package/NewinstanceChat.podspec
CHANGED
|
@@ -18,7 +18,7 @@ Pod::Spec.new do |s|
|
|
|
18
18
|
s.pod_target_xcconfig = { "DEFINES_MODULE" => "YES" }
|
|
19
19
|
|
|
20
20
|
# Native chat SDK we wrap, published on CocoaPods Trunk.
|
|
21
|
-
s.dependency "LiveAndAiChat", "~> 0.
|
|
21
|
+
s.dependency "LiveAndAiChat", "~> 0.4.0"
|
|
22
22
|
|
|
23
23
|
install_modules_dependencies(s)
|
|
24
24
|
end
|
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# react-native-newinstance-chat
|
|
2
2
|
|
|
3
|
-
React Native bindings for [LiveAndAiChat](https://newinstance.cloud)
|
|
3
|
+
React Native bindings for [LiveAndAiChat](https://newinstance.cloud). Drops a
|
|
4
4
|
complete AI + live-agent chat experience into your iOS and Android RN app
|
|
5
5
|
via native module bridges.
|
|
6
6
|
|
|
7
7
|
The chat UI, transport, attachments, image cache, typing indicators, and
|
|
8
8
|
silent gap-fill resync all come from the native iOS / Android SDKs. This
|
|
9
|
-
package is a thin TurboModule surface over them
|
|
9
|
+
package is a thin TurboModule surface over them, so your React Native code
|
|
10
10
|
stays small.
|
|
11
11
|
|
|
12
12
|
## Requirements
|
|
13
13
|
|
|
14
|
-
- React Native 0.74+ (TurboModule support
|
|
14
|
+
- React Native 0.74+ (TurboModule support; 0.85+ recommended)
|
|
15
15
|
- iOS 15.1+
|
|
16
16
|
- Android API 24+, JDK 17
|
|
17
17
|
|
|
@@ -45,7 +45,7 @@ cd ios && pod install
|
|
|
45
45
|
### Android
|
|
46
46
|
|
|
47
47
|
Autolink registers `NewinstanceChatPackage`. The underlying
|
|
48
|
-
`cloud.newinstance:liveandaichat` AAR is resolved from Maven Central
|
|
48
|
+
`cloud.newinstance:liveandaichat` AAR is resolved from Maven Central, so
|
|
49
49
|
ensure `mavenCentral()` is in `settings.gradle.kts`.
|
|
50
50
|
|
|
51
51
|
## Quick start
|
|
@@ -54,87 +54,183 @@ ensure `mavenCentral()` is in `settings.gradle.kts`.
|
|
|
54
54
|
import { NewinstanceChat } from 'react-native-newinstance-chat';
|
|
55
55
|
|
|
56
56
|
const sdk = new NewinstanceChat({
|
|
57
|
-
|
|
57
|
+
customerToken: tokenFromYourBackend,
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
sdk.setUser({
|
|
61
|
-
customerName: 'Ada Lovelace',
|
|
62
|
-
customerEmail: 'ada@example.com',
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
await sdk.initialize();
|
|
66
|
-
|
|
67
|
-
// `sdk.ready` is a Promise that resolves once the native module is configured -
|
|
68
|
-
// await it anywhere you need to know setup completed.
|
|
69
60
|
await sdk.ready;
|
|
61
|
+
await sdk.initialize();
|
|
70
62
|
|
|
71
|
-
// Later, in response to a host button tap:
|
|
72
63
|
sdk.openChat();
|
|
73
64
|
```
|
|
74
65
|
|
|
75
|
-
|
|
76
|
-
|
|
66
|
+
`sdk.ready` resolves once the native module is configured. It rejects with
|
|
67
|
+
an error whose `code` is `E_CONFIG` when the configuration is invalid, and
|
|
68
|
+
`initialize()` rejects with the same error.
|
|
69
|
+
|
|
70
|
+
The chat screen renders natively, so there is no React component to mount.
|
|
71
|
+
Your RN UI is whatever button, banner or FAB triggers `openChat()`.
|
|
72
|
+
|
|
73
|
+
## Authentication
|
|
77
74
|
|
|
78
|
-
|
|
75
|
+
The SDK runs in exactly one of two modes. Pass `customerToken` **or**
|
|
76
|
+
`apiKey`, never both. Passing both, or neither, rejects `ready` with
|
|
77
|
+
`E_CONFIG` and the message
|
|
78
|
+
`pass either apiKey (keyId:secret) or customerToken`. Blank strings count
|
|
79
|
+
as absent.
|
|
79
80
|
|
|
80
|
-
`
|
|
81
|
-
form, which belongs on your server only.
|
|
81
|
+
### JWT mode (`customerToken`), for apps you ship to the public
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
Your backend mints a short-lived chat token for the signed-in customer and
|
|
84
|
+
hands it to the app. The native SDK sends it as `Authorization: Bearer <token>`
|
|
85
|
+
on every request. The customer's identity comes only from the token's signed
|
|
86
|
+
claims, so a tampered app cannot claim to be someone else, and your API key
|
|
87
|
+
never leaves your server.
|
|
84
88
|
|
|
85
|
-
|
|
89
|
+
Mint the token on your backend, on the REST API host
|
|
90
|
+
(`baseUrl` below is `https://api.newinstance.cloud`), with your full
|
|
91
|
+
`keyId:secret` key. Server side only:
|
|
86
92
|
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
```
|
|
94
|
+
POST {baseUrl}/api/v1/chat/sessions
|
|
95
|
+
x-api-key: sk_live_abc123:YOUR_SECRET_KEY
|
|
96
|
+
Content-Type: application/json
|
|
97
|
+
|
|
98
|
+
{
|
|
99
|
+
"customerId": "usr_123",
|
|
100
|
+
"customerName": "Ada Lovelace",
|
|
101
|
+
"customerEmail": "ada@example.com",
|
|
102
|
+
"customerPhone": "+44 20 7946 0958",
|
|
103
|
+
"metadata": { "plan": "enterprise" },
|
|
104
|
+
"expiresInSeconds": 3600
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`customerId` and `customerName` are required; `customerEmail`,
|
|
109
|
+
`customerPhone`, `metadata` and `expiresInSeconds` are optional. The
|
|
110
|
+
response is:
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"token": "eyJhbGciOi...",
|
|
115
|
+
"expiresAt": "2026-09-29T13:00:00.000Z",
|
|
116
|
+
"expiresInSeconds": 3600,
|
|
117
|
+
"session": { "sessionId": "...", "customerId": "usr_123", "status": "active" }
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
For example, a Node backend route the app calls after sign-in:
|
|
89
122
|
|
|
90
123
|
```ts
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
124
|
+
app.post('/chat-token', requireSignedInUser, async (req, res) => {
|
|
125
|
+
const response = await fetch(
|
|
126
|
+
'https://api.newinstance.cloud/api/v1/chat/sessions',
|
|
127
|
+
{
|
|
128
|
+
method: 'POST',
|
|
129
|
+
headers: {
|
|
130
|
+
'content-type': 'application/json',
|
|
131
|
+
'x-api-key': process.env.NEWINSTANCE_API_KEY,
|
|
132
|
+
},
|
|
133
|
+
body: JSON.stringify({
|
|
134
|
+
customerId: req.user.id,
|
|
135
|
+
customerName: req.user.name,
|
|
136
|
+
customerEmail: req.user.email,
|
|
137
|
+
expiresInSeconds: 3600,
|
|
138
|
+
}),
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
const { token, expiresAt } = await response.json();
|
|
142
|
+
res.json({ token, expiresAt });
|
|
143
|
+
});
|
|
100
144
|
```
|
|
101
145
|
|
|
102
|
-
|
|
103
|
-
session already knows the customer before the app says anything:
|
|
146
|
+
Hand `token` to the app and pass it in the config:
|
|
104
147
|
|
|
148
|
+
```ts
|
|
149
|
+
const { token } = await fetch('https://your-backend.example.com/chat-token', {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
}).then((r) => r.json());
|
|
152
|
+
|
|
153
|
+
const sdk = new NewinstanceChat({ customerToken: token });
|
|
154
|
+
await sdk.ready;
|
|
155
|
+
await sdk.initialize();
|
|
105
156
|
```
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
157
|
+
|
|
158
|
+
Tokens expire (one hour by default, 60 seconds to 24 hours via
|
|
159
|
+
`expiresInSeconds`). When the token expires or is revoked, the server
|
|
160
|
+
rejects it and the `error` event fires with `type: 'auth'` and
|
|
161
|
+
`code: 'INVALID_IDENTITY_TOKEN'`. The SDK stops instead of reconnecting with
|
|
162
|
+
the same token. Fetch a fresh token from your backend and swap it in with
|
|
163
|
+
`setUserToken`. The SDK keeps running and keeps the conversation:
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
sdk.addListener('error', async (e) => {
|
|
167
|
+
if (e.code === 'INVALID_IDENTITY_TOKEN') {
|
|
168
|
+
await sdk.setUserToken(await fetchFreshTokenFromYourBackend());
|
|
169
|
+
}
|
|
170
|
+
});
|
|
111
171
|
```
|
|
112
172
|
|
|
113
|
-
|
|
114
|
-
|
|
173
|
+
`setUserToken` updates the credential for every later request, rebuilds the
|
|
174
|
+
realtime connection with the new token, and restarts initialization if the
|
|
175
|
+
auth failure stopped it. The stored conversation is kept unless the new
|
|
176
|
+
token belongs to a different customer. A blank token throws `E_CONFIG` with
|
|
177
|
+
the message `customerToken must not be blank`.
|
|
115
178
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
179
|
+
A customer holds **one** live session at a time, so minting while their
|
|
180
|
+
token is still valid returns `409` with the existing session. To rotate a
|
|
181
|
+
token before it expires, your backend must first revoke the session
|
|
182
|
+
(`DELETE /api/v1/chat/sessions?customerId=...`, also the sign-out call) and
|
|
183
|
+
then mint a new one. Revocation takes effect on the next request, so an
|
|
184
|
+
invalidated token stops working immediately.
|
|
120
185
|
|
|
121
|
-
|
|
122
|
-
|
|
186
|
+
In JWT mode `setUser` is accepted but ignored for identity: the name, email
|
|
187
|
+
and customer id always come from the token.
|
|
123
188
|
|
|
124
|
-
|
|
125
|
-
`DELETE /api/v1/chat/sessions?customerId=...` on sign-out. You choose the
|
|
126
|
-
lifetime (60 seconds to 24 hours, default 1 hour) with `expiresInSeconds`.
|
|
189
|
+
### Key mode (`apiKey`), for trusted internal apps only
|
|
127
190
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
is still valid.
|
|
191
|
+
**Key mode ships your secret key inside the app.** Anyone who has the app
|
|
192
|
+
binary can extract it. Use key mode only for trusted internal apps (staff
|
|
193
|
+
tools, kiosks, test builds), never for apps distributed to the public. Use
|
|
194
|
+
JWT mode for those.
|
|
133
195
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
196
|
+
`apiKey` is the full `keyId:secret` key from your dashboard. The native SDK
|
|
197
|
+
sends it whole as `x-api-key` on every request. A key ID without its secret
|
|
198
|
+
rejects `ready` with `E_CONFIG` and the message
|
|
199
|
+
`apiKey must be keyId:secret, or use customerToken`.
|
|
137
200
|
|
|
201
|
+
```ts
|
|
202
|
+
const sdk = new NewinstanceChat({
|
|
203
|
+
apiKey: keyFromYourBuildConfig,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
sdk.setUser({
|
|
207
|
+
customerName: 'Ada Lovelace',
|
|
208
|
+
customerEmail: 'ada@example.com',
|
|
209
|
+
customerId: 'usr_123',
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
await sdk.ready;
|
|
213
|
+
await sdk.initialize();
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Load the key from your build configuration or an environment variable, and
|
|
217
|
+
never commit it to source control.
|
|
218
|
+
|
|
219
|
+
In key mode `setUser` supplies the customer. `setUserToken` is not
|
|
220
|
+
available and throws synchronously with `E_CONFIG` and the message
|
|
221
|
+
`setUserToken is only available when the SDK is configured with customerToken`.
|
|
222
|
+
A rejected key arrives on the error event as `INVALID_PUBLIC_KEY`.
|
|
223
|
+
|
|
224
|
+
### Migrating from 0.3.x
|
|
225
|
+
|
|
226
|
+
- `ChatUser.customerToken` is gone. Passing `customerToken` to `setUser`
|
|
227
|
+
throws `E_CONFIG` with the message
|
|
228
|
+
`customerToken is not a setUser field; pass it in the config or call setUserToken`.
|
|
229
|
+
Put the token in the config (`customerToken`) and use `setUserToken` to
|
|
230
|
+
swap it.
|
|
231
|
+
- `apiKey` is now the full `keyId:secret` and is sent whole. A bare key ID
|
|
232
|
+
is rejected. Prefer JWT mode for public apps.
|
|
233
|
+
- `setUserToken` returns a Promise and is only available in JWT mode.
|
|
138
234
|
|
|
139
235
|
### Theming
|
|
140
236
|
|
|
@@ -151,7 +247,7 @@ token, so setting one colour does not discard the others.
|
|
|
151
247
|
|
|
152
248
|
```ts
|
|
153
249
|
const sdk = new NewinstanceChat({
|
|
154
|
-
|
|
250
|
+
customerToken: tokenFromYourBackend,
|
|
155
251
|
theme: { mode: 'dark', sentBubble: '#16A34A' },
|
|
156
252
|
});
|
|
157
253
|
```
|
|
@@ -183,7 +279,7 @@ Event names: `messageReceived`, `messageSent`, `agentTypingChanged`,
|
|
|
183
279
|
|
|
184
280
|
`error` is the channel for "why is my chat not working". It never surfaces
|
|
185
281
|
in the chat interface the customer sees, and it never carries the API key,
|
|
186
|
-
the
|
|
282
|
+
the secret, the customer token, or customer data.
|
|
187
283
|
|
|
188
284
|
```ts
|
|
189
285
|
sdk.addListener('error', (e) => {
|
|
@@ -195,11 +291,11 @@ sdk.addListener('error', (e) => {
|
|
|
195
291
|
| `code` | What went wrong |
|
|
196
292
|
|---|---|
|
|
197
293
|
| `MISSING_WIDGET_KEY` | No API key was supplied |
|
|
198
|
-
| `INVALID_PUBLIC_KEY` |
|
|
199
|
-
| `CHAT_CONFIG_UNAVAILABLE` | The
|
|
294
|
+
| `INVALID_PUBLIC_KEY` | Key mode: the key was rejected (wrong key, wrong environment, revoked, expired) |
|
|
295
|
+
| `CHAT_CONFIG_UNAVAILABLE` | The credential is fine, but the organization has no chat configuration |
|
|
200
296
|
| `CHAT_DISABLED` | Chat is switched off in the dashboard |
|
|
201
297
|
| `CONFIG_FETCH_FAILED` | Remote configuration could not be loaded; the local/built-in theme is in use |
|
|
202
|
-
| `INVALID_IDENTITY_TOKEN` |
|
|
298
|
+
| `INVALID_IDENTITY_TOKEN` | JWT mode: the token was malformed, expired, revoked, or signed for another organization |
|
|
203
299
|
| `NETWORK_ERROR` | The backend could not be reached |
|
|
204
300
|
| `TRANSPORT_ERROR` | The realtime connection dropped |
|
|
205
301
|
| `CHAT_INITIALIZATION_FAILED` | The conversation could not be started |
|
|
@@ -207,11 +303,19 @@ sdk.addListener('error', (e) => {
|
|
|
207
303
|
| `ATTACHMENT_FAILED` | An attachment could not be uploaded |
|
|
208
304
|
| `SERVER_ERROR` | The backend returned something unusable |
|
|
209
305
|
|
|
306
|
+
Auth failures (`type: 'auth'`) report `INVALID_IDENTITY_TOKEN` in JWT mode
|
|
307
|
+
and `INVALID_PUBLIC_KEY` in key mode. After an auth failure the SDK does not
|
|
308
|
+
reconnect with the same credential.
|
|
309
|
+
|
|
210
310
|
The same codes are emitted by the web, Android, iOS and Flutter SDKs.
|
|
211
311
|
`type` (`network` / `validation` / `auth` / `system`) is still present for
|
|
212
312
|
handlers written against 0.2.x; `code` is `undefined` only when the
|
|
213
313
|
underlying native SDK predates coded errors.
|
|
214
314
|
|
|
315
|
+
Configuration mistakes are not emitted on the error event. They are thrown
|
|
316
|
+
(or reject `ready`) as an `Error` with `code: 'E_CONFIG'`,
|
|
317
|
+
`type: 'validation'` and `recoverable: false`.
|
|
318
|
+
|
|
215
319
|
### The chat-close event
|
|
216
320
|
|
|
217
321
|
`chatClosed` fires **exactly once per chat presentation**, however the
|
|
@@ -250,8 +354,8 @@ sdk.retryMessage(failedMessageId);
|
|
|
250
354
|
### Programmatic attachments
|
|
251
355
|
|
|
252
356
|
`attachFile` accepts base64, data URIs, local file paths,
|
|
253
|
-
`content://` / `file://` URIs (picker outputs), and
|
|
254
|
-
SDK is configured with `allowRemoteAttachmentUrls
|
|
357
|
+
`content://` / `file://` URIs (picker outputs), and, when the native
|
|
358
|
+
SDK is configured with `allowRemoteAttachmentUrls`, remote URLs.
|
|
255
359
|
`sourceType: 'auto'` (the default) safely detects string inputs.
|
|
256
360
|
|
|
257
361
|
```ts
|
|
@@ -278,8 +382,8 @@ await sdk.attachFile({ source: dataUri, name: 'chart.png' });
|
|
|
278
382
|
|
|
279
383
|
The returned promise resolves with the attachment **id** as soon as the
|
|
280
384
|
request is accepted and queued. Validation and upload run in the
|
|
281
|
-
background and never block the UI thread
|
|
282
|
-
`attachmentUpdated` with `status: 'failed'` plus an `error` event
|
|
385
|
+
background and never block the UI thread (failures surface as
|
|
386
|
+
`attachmentUpdated` with `status: 'failed'` plus an `error` event), and
|
|
283
387
|
the message is never sent until the upload succeeds (send drains only
|
|
284
388
|
uploaded attachments). Track progress and results via
|
|
285
389
|
`attachmentUpdated`:
|
|
@@ -305,7 +409,7 @@ Allowed MIME types and the 25 MB cap mirror the web widget; keys and
|
|
|
305
409
|
limits are enforced again server-side.
|
|
306
410
|
|
|
307
411
|
Handoff to a live agent and typing indicators are driven automatically
|
|
308
|
-
by the native chat screen
|
|
412
|
+
by the native chat screen; there's nothing to wire up from the host.
|
|
309
413
|
|
|
310
414
|
### Tear-down
|
|
311
415
|
|
|
@@ -314,7 +418,25 @@ sdk.destroy();
|
|
|
314
418
|
```
|
|
315
419
|
|
|
316
420
|
Call this when your host component unmounts. After `destroy()` the
|
|
317
|
-
instance is unusable
|
|
421
|
+
instance is unusable; construct a new one if you need to chat again.
|
|
422
|
+
|
|
423
|
+
## Example app
|
|
424
|
+
|
|
425
|
+
The example app in `example/` runs in JWT mode by default and fetches a
|
|
426
|
+
token from the placeholder URL `https://your-backend.example.com/chat-token`
|
|
427
|
+
in `example/src/App.tsx`. Point it at your own backend route.
|
|
428
|
+
|
|
429
|
+
To try key mode on a trusted development build instead, set the key in the
|
|
430
|
+
environment before starting Metro. Nothing is committed to the repository:
|
|
431
|
+
|
|
432
|
+
```sh
|
|
433
|
+
NEWINSTANCE_API_KEY='sk_test_abc123:YOUR_SECRET_KEY' yarn example start --reset-cache
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
`NEWINSTANCE_BASE_URL` optionally points a debug build at another backend.
|
|
437
|
+
Both variables are empty by default and are inlined by
|
|
438
|
+
`example/babel.config.js`, so restart Metro with `--reset-cache` after
|
|
439
|
+
changing them.
|
|
318
440
|
|
|
319
441
|
## Contributing
|
|
320
442
|
|
package/android/build.gradle
CHANGED
|
@@ -73,5 +73,5 @@ repositories {
|
|
|
73
73
|
dependencies {
|
|
74
74
|
implementation "com.facebook.react:react-android"
|
|
75
75
|
// Native chat SDK the bridge wraps. Published under cloud.newinstance.
|
|
76
|
-
implementation "cloud.newinstance:liveandaichat:0.
|
|
76
|
+
implementation "cloud.newinstance:liveandaichat:0.4.0"
|
|
77
77
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package com.newinstancechat
|
|
2
2
|
|
|
3
|
+
import cloud.newinstance.liveandaichat.ChatAuthMode
|
|
3
4
|
import cloud.newinstance.liveandaichat.ConnectionState
|
|
4
5
|
import cloud.newinstance.liveandaichat.LiveAndAiChat
|
|
5
6
|
import cloud.newinstance.liveandaichat.LiveAndAiChatConfig
|
|
@@ -43,11 +44,6 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
|
|
|
43
44
|
private var sdk: LiveAndAiChat? = null
|
|
44
45
|
private var pendingUser: ChatUser? = null
|
|
45
46
|
|
|
46
|
-
/**
|
|
47
|
-
* Whether the host identified the customer with a chat identity token.
|
|
48
|
-
* Used only to tell an API-key rejection apart from a token rejection when
|
|
49
|
-
* reporting an auth failure.
|
|
50
|
-
*/
|
|
51
47
|
private var usingIdentityToken: Boolean = false
|
|
52
48
|
|
|
53
49
|
override fun getName(): String = NAME
|
|
@@ -61,9 +57,10 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
|
|
|
61
57
|
// MARK: - configure / setUser / initialize
|
|
62
58
|
|
|
63
59
|
override fun configure(config: ReadableMap, promise: Promise) {
|
|
64
|
-
val apiKey = config.getString("apiKey")
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
val apiKey = config.getString("apiKey")?.trim()?.takeIf { it.isNotEmpty() }
|
|
61
|
+
val customerToken = config.getString("customerToken")?.trim()?.takeIf { it.isNotEmpty() }
|
|
62
|
+
if ((apiKey == null) == (customerToken == null)) {
|
|
63
|
+
promise.reject("E_CONFIG", "pass either apiKey (keyId:secret) or customerToken")
|
|
67
64
|
return
|
|
68
65
|
}
|
|
69
66
|
val baseUrl = if (config.hasKey("baseUrl")) config.getString("baseUrl") else null
|
|
@@ -77,8 +74,6 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
|
|
|
77
74
|
}
|
|
78
75
|
} else null
|
|
79
76
|
|
|
80
|
-
// Local theme arrives as a flat string map; unknown keys are ignored by
|
|
81
|
-
// ChatThemeOverride.fromMap so a newer JS SDK cannot break this bridge.
|
|
82
77
|
val themeOverride = if (config.hasKey("theme")) {
|
|
83
78
|
config.getMap("theme")?.let { map ->
|
|
84
79
|
val values = mutableMapOf<String, String?>()
|
|
@@ -94,6 +89,7 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
|
|
|
94
89
|
try {
|
|
95
90
|
val cfg = LiveAndAiChatConfig(
|
|
96
91
|
apiKey = apiKey,
|
|
92
|
+
customerToken = customerToken,
|
|
97
93
|
baseUrl = baseUrl ?: LiveAndAiChatConfig.DEFAULT_BASE_URL,
|
|
98
94
|
transport = transport,
|
|
99
95
|
initialMessage = initialMessage,
|
|
@@ -105,26 +101,23 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
|
|
|
105
101
|
val instance = builder.build()
|
|
106
102
|
instance.addListener(this)
|
|
107
103
|
sdk = instance
|
|
104
|
+
usingIdentityToken = cfg.authMode == ChatAuthMode.CUSTOMER_TOKEN
|
|
108
105
|
pendingUser = null
|
|
109
106
|
promise.resolve(null)
|
|
107
|
+
} catch (e: LiveAndAiChatError) {
|
|
108
|
+
promise.reject(e.code ?: "E_BUILD", e.message, e)
|
|
110
109
|
} catch (e: Exception) {
|
|
111
110
|
promise.reject("E_BUILD", e.message, e)
|
|
112
111
|
}
|
|
113
112
|
}
|
|
114
113
|
|
|
115
114
|
override fun setUser(user: ReadableMap) {
|
|
116
|
-
val
|
|
117
|
-
// With a token the display name is irrelevant: the backend replaces the
|
|
118
|
-
// whole identity from the token's signed claims.
|
|
119
|
-
val name = user.getString("customerName")?.takeIf { it.isNotBlank() }
|
|
120
|
-
?: (if (!token.isNullOrBlank()) "Customer" else return)
|
|
115
|
+
val name = user.getString("customerName")?.takeIf { it.isNotBlank() } ?: return
|
|
121
116
|
val chatUser = ChatUser(
|
|
122
117
|
customerName = name,
|
|
123
118
|
customerId = if (user.hasKey("customerId")) user.getString("customerId") else null,
|
|
124
119
|
customerEmail = if (user.hasKey("customerEmail")) user.getString("customerEmail") else null,
|
|
125
|
-
customerToken = token,
|
|
126
120
|
)
|
|
127
|
-
usingIdentityToken = !token.isNullOrBlank()
|
|
128
121
|
val current = sdk
|
|
129
122
|
if (current != null) {
|
|
130
123
|
current.setUser(chatUser)
|
|
@@ -133,6 +126,22 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
|
|
|
133
126
|
}
|
|
134
127
|
}
|
|
135
128
|
|
|
129
|
+
override fun setUserToken(token: String, promise: Promise) {
|
|
130
|
+
val current = sdk
|
|
131
|
+
if (current == null) {
|
|
132
|
+
promise.reject("E_NOT_CONFIGURED", "configure() must be called first")
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
current.setUserToken(token)
|
|
137
|
+
promise.resolve(null)
|
|
138
|
+
} catch (e: LiveAndAiChatError) {
|
|
139
|
+
promise.reject(e.code ?: "E_CONFIG", e.message, e)
|
|
140
|
+
} catch (e: Exception) {
|
|
141
|
+
promise.reject("E_CONFIG", e.message, e)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
136
145
|
override fun initialize(promise: Promise) {
|
|
137
146
|
val current = sdk
|
|
138
147
|
if (current == null) {
|
|
@@ -174,7 +183,7 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
|
|
|
174
183
|
if (source == null) {
|
|
175
184
|
promise.reject(
|
|
176
185
|
"E_SOURCE",
|
|
177
|
-
if (sourceType == "auto") "Could not detect the attachment source type
|
|
186
|
+
if (sourceType == "auto") "Could not detect the attachment source type: pass sourceType explicitly"
|
|
178
187
|
else "Unknown sourceType: $sourceType",
|
|
179
188
|
)
|
|
180
189
|
return
|
|
@@ -278,13 +287,6 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
|
|
|
278
287
|
emit("error", map)
|
|
279
288
|
}
|
|
280
289
|
|
|
281
|
-
/**
|
|
282
|
-
* Normalise onto the cross-platform code vocabulary. `error.code` carries
|
|
283
|
-
* the raw GraphQL/transport code when the native SDK has one; otherwise the
|
|
284
|
-
* broad type decides. An auth failure while a chat identity token is in use
|
|
285
|
-
* is reported as a token problem, which is the difference between
|
|
286
|
-
* "rotate your key" and "mint a fresh token".
|
|
287
|
-
*/
|
|
288
290
|
private fun sdkErrorCode(error: LiveAndAiChatError): String {
|
|
289
291
|
val known = setOf(
|
|
290
292
|
ChatSdkError.CHAT_CONFIG_UNAVAILABLE,
|
package/ios/NewinstanceChat.mm
CHANGED
|
@@ -56,6 +56,13 @@ RCT_EXPORT_MODULE()
|
|
|
56
56
|
[_impl setUser:user];
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
- (void)setUserToken:(NSString *)token
|
|
60
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
61
|
+
reject:(RCTPromiseRejectBlock)reject
|
|
62
|
+
{
|
|
63
|
+
[_impl setUserToken:token resolver:resolve rejecter:reject];
|
|
64
|
+
}
|
|
65
|
+
|
|
59
66
|
- (void)initialize:(RCTPromiseResolveBlock)resolve
|
|
60
67
|
reject:(RCTPromiseRejectBlock)reject
|
|
61
68
|
{
|
|
@@ -29,9 +29,6 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
|
|
|
29
29
|
private var sdk: LiveAndAiChat?
|
|
30
30
|
private var pendingUser: ChatUser?
|
|
31
31
|
|
|
32
|
-
/// Whether the host identified the customer with a chat identity token.
|
|
33
|
-
/// Used only to tell an API-key rejection apart from a token rejection when
|
|
34
|
-
/// reporting an auth failure.
|
|
35
32
|
private var usingIdentityToken: Bool = false
|
|
36
33
|
|
|
37
34
|
// MARK: - configure / setUser / initialize
|
|
@@ -40,8 +37,10 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
|
|
|
40
37
|
public func configure(_ configDict: NSDictionary,
|
|
41
38
|
resolver: @escaping RCTPromiseResolveBlock,
|
|
42
39
|
rejecter: @escaping RCTPromiseRejectBlock) {
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
let apiKey = Self.nonBlank(configDict["apiKey"])
|
|
41
|
+
let customerToken = Self.nonBlank(configDict["customerToken"])
|
|
42
|
+
guard (apiKey == nil) != (customerToken == nil) else {
|
|
43
|
+
rejecter("E_CONFIG", "pass either apiKey (keyId:secret) or customerToken", nil)
|
|
45
44
|
return
|
|
46
45
|
}
|
|
47
46
|
let baseUrl = configDict["baseUrl"] as? String
|
|
@@ -54,8 +53,6 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
|
|
|
54
53
|
}
|
|
55
54
|
}()
|
|
56
55
|
|
|
57
|
-
// Local theme arrives as a flat string dictionary; unknown keys are
|
|
58
|
-
// ignored by fromDictionary so a newer JS SDK cannot break this bridge.
|
|
59
56
|
let themeOverride: ChatThemeOverride? = {
|
|
60
57
|
guard let raw = configDict["theme"] as? NSDictionary else { return nil }
|
|
61
58
|
var values: [String: String] = [:]
|
|
@@ -70,6 +67,7 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
|
|
|
70
67
|
do {
|
|
71
68
|
let cfg = try LiveAndAiChatConfig(
|
|
72
69
|
apiKey: apiKey,
|
|
70
|
+
customerToken: customerToken,
|
|
73
71
|
baseUrl: baseUrl ?? LiveAndAiChatConfig.defaultBaseUrl,
|
|
74
72
|
transport: transport,
|
|
75
73
|
initialMessage: initialMessage,
|
|
@@ -81,10 +79,11 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
|
|
|
81
79
|
let instance = try builder.build()
|
|
82
80
|
instance.addDelegate(self)
|
|
83
81
|
self.sdk = instance
|
|
82
|
+
self.usingIdentityToken = cfg.authMode == .customerToken
|
|
84
83
|
self.pendingUser = nil
|
|
85
84
|
resolver(nil)
|
|
86
85
|
} catch let e as LiveAndAiChatError {
|
|
87
|
-
rejecter("E_BUILD", e.message, e)
|
|
86
|
+
rejecter(e.code ?? "E_BUILD", e.message, e)
|
|
88
87
|
} catch {
|
|
89
88
|
rejecter("E_BUILD", error.localizedDescription, error)
|
|
90
89
|
}
|
|
@@ -93,20 +92,14 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
|
|
|
93
92
|
|
|
94
93
|
@objc(setUser:)
|
|
95
94
|
public func setUser(_ userDict: NSDictionary) {
|
|
96
|
-
let
|
|
97
|
-
// With a token the display name is irrelevant: the backend replaces the
|
|
98
|
-
// whole identity from the token's signed claims.
|
|
99
|
-
let providedName = (userDict["customerName"] as? String).flatMap { $0.isEmpty ? nil : $0 }
|
|
100
|
-
guard let name = providedName ?? (token != nil ? "Customer" : nil) else { return }
|
|
95
|
+
guard let name = (userDict["customerName"] as? String).flatMap({ $0.isEmpty ? nil : $0 }) else { return }
|
|
101
96
|
let user = ChatUser(
|
|
102
97
|
customerName: name,
|
|
103
98
|
customerId: userDict["customerId"] as? String,
|
|
104
|
-
customerEmail: userDict["customerEmail"] as? String
|
|
105
|
-
customerToken: token
|
|
99
|
+
customerEmail: userDict["customerEmail"] as? String
|
|
106
100
|
)
|
|
107
101
|
DispatchQueue.main.async { [weak self] in
|
|
108
102
|
guard let self = self else { return }
|
|
109
|
-
self.usingIdentityToken = token != nil
|
|
110
103
|
if let sdk = self.sdk {
|
|
111
104
|
sdk.setUser(user)
|
|
112
105
|
} else {
|
|
@@ -115,6 +108,26 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
|
|
|
115
108
|
}
|
|
116
109
|
}
|
|
117
110
|
|
|
111
|
+
@objc(setUserToken:resolver:rejecter:)
|
|
112
|
+
public func setUserToken(_ token: String,
|
|
113
|
+
resolver: @escaping RCTPromiseResolveBlock,
|
|
114
|
+
rejecter: @escaping RCTPromiseRejectBlock) {
|
|
115
|
+
DispatchQueue.main.async { [weak self] in
|
|
116
|
+
guard let sdk = self?.sdk else {
|
|
117
|
+
rejecter("E_NOT_CONFIGURED", "configure() must be called first", nil)
|
|
118
|
+
return
|
|
119
|
+
}
|
|
120
|
+
do {
|
|
121
|
+
try sdk.setUserToken(token)
|
|
122
|
+
resolver(nil)
|
|
123
|
+
} catch let e as LiveAndAiChatError {
|
|
124
|
+
rejecter(e.code ?? "E_CONFIG", e.message, e)
|
|
125
|
+
} catch {
|
|
126
|
+
rejecter("E_CONFIG", error.localizedDescription, error)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
118
131
|
@objc(initializeWithResolver:rejecter:)
|
|
119
132
|
public func initialize(resolver: @escaping RCTPromiseResolveBlock,
|
|
120
133
|
rejecter: @escaping RCTPromiseRejectBlock) {
|
|
@@ -176,7 +189,7 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
|
|
|
176
189
|
rejecter(
|
|
177
190
|
"E_SOURCE",
|
|
178
191
|
sourceType == "auto"
|
|
179
|
-
? "Could not detect the attachment source type
|
|
192
|
+
? "Could not detect the attachment source type: pass sourceType explicitly"
|
|
180
193
|
: "Unknown sourceType: \(sourceType)",
|
|
181
194
|
nil
|
|
182
195
|
)
|
|
@@ -279,6 +292,11 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
|
|
|
279
292
|
|
|
280
293
|
// MARK: - Helpers
|
|
281
294
|
|
|
295
|
+
private static func nonBlank(_ value: Any?) -> String? {
|
|
296
|
+
guard let text = (value as? String)?.trimmingCharacters(in: .whitespacesAndNewlines), !text.isEmpty else { return nil }
|
|
297
|
+
return text
|
|
298
|
+
}
|
|
299
|
+
|
|
282
300
|
private static func chatMessageToDict(_ m: ChatMessage) -> [String: Any?] {
|
|
283
301
|
return [
|
|
284
302
|
"id": m.id,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeNewinstanceChat.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;;AAEpE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;
|
|
1
|
+
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeNewinstanceChat.ts"],"mappings":";;AAAA,SAASA,mBAAmB,QAA0B,cAAc;;AAEpE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAqBA,eAAeA,mBAAmB,CAACC,YAAY,CAAO,iBAAiB,CAAC","ignoreList":[]}
|
package/lib/module/index.js
CHANGED
|
@@ -7,6 +7,27 @@ import NativeNewinstanceChat from "./NativeNewinstanceChat.js";
|
|
|
7
7
|
// we feed it the legacy handle so listener counts get tracked.
|
|
8
8
|
const emitterSource = NativeModules.NewinstanceChat ?? NativeNewinstanceChat;
|
|
9
9
|
const emitter = new NativeEventEmitter(emitterSource);
|
|
10
|
+
function trimmed(value) {
|
|
11
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
12
|
+
}
|
|
13
|
+
function configProblem(apiKey, customerToken) {
|
|
14
|
+
if (!apiKey === !customerToken) {
|
|
15
|
+
return 'pass either apiKey (keyId:secret) or customerToken';
|
|
16
|
+
}
|
|
17
|
+
if (customerToken) return null;
|
|
18
|
+
const separator = apiKey.indexOf(':');
|
|
19
|
+
if (separator < 0 || !apiKey.slice(0, separator).trim() || !apiKey.slice(separator + 1).trim()) {
|
|
20
|
+
return 'apiKey must be keyId:secret, or use customerToken';
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
function configError(message) {
|
|
25
|
+
return Object.assign(new Error(message), {
|
|
26
|
+
code: 'E_CONFIG',
|
|
27
|
+
type: 'validation',
|
|
28
|
+
recoverable: false
|
|
29
|
+
});
|
|
30
|
+
}
|
|
10
31
|
|
|
11
32
|
/**
|
|
12
33
|
* React Native binding for the native LiveAndAiChat iOS / Android SDKs.
|
|
@@ -22,32 +43,42 @@ export class NewinstanceChat {
|
|
|
22
43
|
/** Resolves once the native SDK is configured. */
|
|
23
44
|
|
|
24
45
|
constructor(config) {
|
|
25
|
-
|
|
46
|
+
const {
|
|
47
|
+
apiKey,
|
|
48
|
+
customerToken,
|
|
49
|
+
...rest
|
|
50
|
+
} = config;
|
|
51
|
+
const key = trimmed(apiKey);
|
|
52
|
+
const token = trimmed(customerToken);
|
|
53
|
+
this.customerTokenMode = !key && !!token;
|
|
54
|
+
const problem = configProblem(key, token);
|
|
55
|
+
this.ready = problem ? Promise.reject(configError(problem)) : NativeNewinstanceChat.configure(token ? {
|
|
56
|
+
...rest,
|
|
57
|
+
customerToken: token
|
|
58
|
+
} : {
|
|
59
|
+
...rest,
|
|
60
|
+
apiKey: key
|
|
61
|
+
});
|
|
26
62
|
}
|
|
27
63
|
|
|
28
64
|
/**
|
|
29
65
|
* Identify the customer. Safe to call before or after `initialize()`. If
|
|
30
66
|
* called before `configure()` resolves, the user is queued and applied
|
|
31
67
|
* once the native instance exists.
|
|
32
|
-
*
|
|
33
|
-
* Prefer `customerToken`: it is minted by your backend, so the server can
|
|
34
|
-
* verify who the customer is. `customerName` / `customerEmail` /
|
|
35
|
-
* `customerId` alone are client-asserted and are not trusted as identity.
|
|
36
68
|
*/
|
|
37
69
|
setUser(user) {
|
|
70
|
+
if ('customerToken' in user) {
|
|
71
|
+
throw configError('customerToken is not a setUser field; pass it in the config or call setUserToken');
|
|
72
|
+
}
|
|
38
73
|
NativeNewinstanceChat.setUser(user);
|
|
39
74
|
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Identify the customer with a backend-minted chat identity token only.
|
|
43
|
-
* Nothing else needs to be supplied: the server derives the name, email and
|
|
44
|
-
* customer id from the token's signed claims.
|
|
45
|
-
*/
|
|
46
75
|
setUserToken(customerToken) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
76
|
+
if (!this.customerTokenMode) {
|
|
77
|
+
throw configError('setUserToken is only available when the SDK is configured with customerToken');
|
|
78
|
+
}
|
|
79
|
+
const token = trimmed(customerToken);
|
|
80
|
+
if (!token) throw configError('customerToken must not be blank');
|
|
81
|
+
return NativeNewinstanceChat.setUserToken(token);
|
|
51
82
|
}
|
|
52
83
|
|
|
53
84
|
/**
|
|
@@ -56,7 +87,7 @@ export class NewinstanceChat {
|
|
|
56
87
|
* the same in-flight init.
|
|
57
88
|
*/
|
|
58
89
|
initialize() {
|
|
59
|
-
return NativeNewinstanceChat.initialize();
|
|
90
|
+
return this.ready.then(() => NativeNewinstanceChat.initialize());
|
|
60
91
|
}
|
|
61
92
|
|
|
62
93
|
/** Present the native chat screen modally over the current RN host. */
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["NativeEventEmitter","NativeModules","NativeNewinstanceChat","emitterSource","NewinstanceChat","emitter","constructor","config","ready","configure","setUser","user","setUserToken","
|
|
1
|
+
{"version":3,"names":["NativeEventEmitter","NativeModules","NativeNewinstanceChat","emitterSource","NewinstanceChat","emitter","trimmed","value","trim","configProblem","apiKey","customerToken","separator","indexOf","slice","configError","message","Object","assign","Error","code","type","recoverable","constructor","config","rest","key","token","customerTokenMode","problem","ready","Promise","reject","configure","setUser","user","setUserToken","initialize","then","openChat","closeChat","sendMessage","text","retryMessage","messageId","attachFile","opts","source","uri","removeAttachment","id","clearAttachments","addListener","event","handler","sub","remove","destroy"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -12,6 +12,7 @@ import { type TurboModule } from 'react-native';
|
|
|
12
12
|
export interface Spec extends TurboModule {
|
|
13
13
|
configure(config: Object): Promise<void>;
|
|
14
14
|
setUser(user: Object): void;
|
|
15
|
+
setUserToken(token: string): Promise<void>;
|
|
15
16
|
initialize(): Promise<void>;
|
|
16
17
|
openChat(): void;
|
|
17
18
|
closeChat(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NativeNewinstanceChat.d.ts","sourceRoot":"","sources":["../../../src/NativeNewinstanceChat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAErE;;;;;;;;;GASG;AACH,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,QAAQ,IAAI,IAAI,CAAC;IACjB,SAAS,IAAI,IAAI,CAAC;IAClB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,gBAAgB,IAAI,IAAI,CAAC;IACzB,OAAO,IAAI,IAAI,CAAC;IAIhB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;;AAED,wBAAyE"}
|
|
1
|
+
{"version":3,"file":"NativeNewinstanceChat.d.ts","sourceRoot":"","sources":["../../../src/NativeNewinstanceChat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAErE;;;;;;;;;GASG;AACH,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,QAAQ,IAAI,IAAI,CAAC;IACjB,SAAS,IAAI,IAAI,CAAC;IAClB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,gBAAgB,IAAI,IAAI,CAAC;IACzB,OAAO,IAAI,IAAI,CAAC;IAIhB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtC;;AAED,wBAAyE"}
|
|
@@ -13,23 +13,15 @@ export type { AttachmentInput, AttachmentUpdate, ChatClosedEvent, ChatMessage, C
|
|
|
13
13
|
export declare class NewinstanceChat {
|
|
14
14
|
/** Resolves once the native SDK is configured. */
|
|
15
15
|
readonly ready: Promise<void>;
|
|
16
|
+
private readonly customerTokenMode;
|
|
16
17
|
constructor(config: NewinstanceChatConfig);
|
|
17
18
|
/**
|
|
18
19
|
* Identify the customer. Safe to call before or after `initialize()`. If
|
|
19
20
|
* called before `configure()` resolves, the user is queued and applied
|
|
20
21
|
* once the native instance exists.
|
|
21
|
-
*
|
|
22
|
-
* Prefer `customerToken`: it is minted by your backend, so the server can
|
|
23
|
-
* verify who the customer is. `customerName` / `customerEmail` /
|
|
24
|
-
* `customerId` alone are client-asserted and are not trusted as identity.
|
|
25
22
|
*/
|
|
26
23
|
setUser(user: ChatUser): void;
|
|
27
|
-
|
|
28
|
-
* Identify the customer with a backend-minted chat identity token only.
|
|
29
|
-
* Nothing else needs to be supplied: the server derives the name, email and
|
|
30
|
-
* customer id from the token's signed claims.
|
|
31
|
-
*/
|
|
32
|
-
setUserToken(customerToken: string): void;
|
|
24
|
+
setUserToken(customerToken: string): Promise<void>;
|
|
33
25
|
/**
|
|
34
26
|
* Fetch org config, restore the saved conversation, and connect the
|
|
35
27
|
* subscription transport in the background. Concurrent callers share
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,eAAe,EACf,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,qBAAqB,EACtB,MAAM,SAAS,CAAC;AAEjB,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,QAAQ,EACR,eAAe,EACf,QAAQ,EACR,SAAS,EACT,qBAAqB,EACrB,oBAAoB,EACpB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,eAAe,EACf,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,qBAAqB,EACtB,MAAM,SAAS,CAAC;AAEjB,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,QAAQ,EACR,eAAe,EACf,QAAQ,EACR,SAAS,EACT,qBAAqB,EACrB,oBAAoB,EACpB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,SAAS,CAAC;AAqCjB;;;;;;;;;GASG;AACH,qBAAa,eAAe;IAC1B,kDAAkD;IAClD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAU;gBAEhC,MAAM,EAAE,qBAAqB;IAazC;;;;OAIG;IACH,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI;IAS7B,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWlD;;;;OAIG;IACH,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,uEAAuE;IACvE,QAAQ,IAAI,IAAI;IAIhB,sCAAsC;IACtC,SAAS,IAAI,IAAI;IAIjB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI/B,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIrC;;;;OAIG;IACH,UAAU,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC;IASlD,yEAAyE;IACzE,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAIlC,mEAAmE;IACnE,gBAAgB,IAAI,IAAI;IAIxB;;;OAGG;IACH,WAAW,CAAC,CAAC,SAAS,SAAS,EAC7B,KAAK,EAAE,CAAC,EACR,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,GACvB,MAAM,IAAI;IAQb;;;OAGG;IACH,OAAO,IAAI,IAAI;CAGhB"}
|
|
@@ -6,31 +6,17 @@
|
|
|
6
6
|
* are lowercased string enums, not platform-specific enum values).
|
|
7
7
|
*/
|
|
8
8
|
export interface NewinstanceChatConfig {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
apiKey?: string;
|
|
10
|
+
customerToken?: string;
|
|
11
11
|
/** Backend base URL. Defaults to `https://service.newinstance.cloud`. */
|
|
12
12
|
baseUrl?: string;
|
|
13
13
|
/** Subscription transport. `'sse'` (default in production), `'ws'`, or omit to let the server choose. */
|
|
14
14
|
transport?: 'sse' | 'ws';
|
|
15
15
|
/** Optional message pre-seeded as the first outbound message. */
|
|
16
16
|
initialMessage?: string;
|
|
17
|
-
/**
|
|
18
|
-
* Local theme. Ranked BELOW whatever the merchant configured in the
|
|
19
|
-
* dashboard and ABOVE the SDK's built-in palette, merged per property, so a
|
|
20
|
-
* token you set here applies only where the dashboard did not set one.
|
|
21
|
-
*/
|
|
22
17
|
theme?: ChatThemeOverride;
|
|
23
18
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Local theme tokens. Every field is optional; colours are hex strings
|
|
26
|
-
* (`#RRGGBB` or `#AARRGGBB`). An unparseable value is ignored rather than
|
|
27
|
-
* throwing, so a bad colour can never crash the app.
|
|
28
|
-
*/
|
|
29
19
|
export interface ChatThemeOverride {
|
|
30
|
-
/**
|
|
31
|
-
* Force light or dark. Omit for "no opinion": the dashboard's mode wins if
|
|
32
|
-
* it set one, otherwise the device setting.
|
|
33
|
-
*/
|
|
34
20
|
mode?: 'light' | 'dark';
|
|
35
21
|
chatBackground?: string;
|
|
36
22
|
headerBackground?: string;
|
|
@@ -61,19 +47,7 @@ export interface ChatThemeOverride {
|
|
|
61
47
|
export interface ChatUser {
|
|
62
48
|
customerName: string;
|
|
63
49
|
customerEmail?: string;
|
|
64
|
-
/**
|
|
65
|
-
* Your reference for this customer. Sent for agent context but NOT trusted
|
|
66
|
-
* as identity: an id asserted by app code cannot be verified. For a trusted
|
|
67
|
-
* identity use `customerToken`.
|
|
68
|
-
*/
|
|
69
50
|
customerId?: string;
|
|
70
|
-
/**
|
|
71
|
-
* Chat identity token minted by YOUR backend with `createCsCustomerToken`
|
|
72
|
-
* and handed to the app. When set it is the authoritative identity: the
|
|
73
|
-
* server derives name, email and customer id from its signed claims, so a
|
|
74
|
-
* tampered app cannot impersonate another customer.
|
|
75
|
-
*/
|
|
76
|
-
customerToken?: string;
|
|
77
51
|
}
|
|
78
52
|
export type AttachmentSourceType = 'auto' | 'base64' | 'dataUri' | 'file' | 'contentUri' | 'url';
|
|
79
53
|
/**
|
|
@@ -156,54 +130,12 @@ export interface ChatMessage {
|
|
|
156
130
|
totalMessages?: number;
|
|
157
131
|
}
|
|
158
132
|
export type NewinstanceChatErrorType = 'network' | 'validation' | 'auth' | 'system';
|
|
159
|
-
|
|
160
|
-
* Stable, cross-platform diagnostic codes. The same strings are emitted by the
|
|
161
|
-
* web, Android, iOS and Flutter SDKs, so `code` is the value to branch on and
|
|
162
|
-
* to quote in a support request.
|
|
163
|
-
*/
|
|
164
|
-
export type NewinstanceChatErrorCode =
|
|
165
|
-
/** The API key was rejected: wrong key, wrong environment, revoked, or expired. */
|
|
166
|
-
'INVALID_PUBLIC_KEY'
|
|
167
|
-
/** No API key was supplied. */
|
|
168
|
-
| 'MISSING_WIDGET_KEY'
|
|
169
|
-
/** The key is valid but no chat configuration exists for the organization. */
|
|
170
|
-
| 'CHAT_CONFIG_UNAVAILABLE'
|
|
171
|
-
/** The key and config are fine, but chat is switched off in the dashboard. */
|
|
172
|
-
| 'CHAT_DISABLED'
|
|
173
|
-
/** Remote configuration could not be fetched; the built-in theme is in use. */
|
|
174
|
-
| 'CONFIG_FETCH_FAILED'
|
|
175
|
-
/** The chat identity token was malformed, expired, or signed for another organization. */
|
|
176
|
-
| 'INVALID_IDENTITY_TOKEN'
|
|
177
|
-
/** A request could not reach the backend at all. */
|
|
178
|
-
| 'NETWORK_ERROR'
|
|
179
|
-
/** The realtime transport dropped and could not be re-established. */
|
|
180
|
-
| 'TRANSPORT_ERROR'
|
|
181
|
-
/** Starting a conversation failed. */
|
|
182
|
-
| 'CHAT_INITIALIZATION_FAILED'
|
|
183
|
-
/** A message could not be delivered. */
|
|
184
|
-
| 'MESSAGE_SEND_FAILED'
|
|
185
|
-
/** An attachment could not be uploaded. */
|
|
186
|
-
| 'ATTACHMENT_FAILED'
|
|
187
|
-
/** The backend answered, but not with anything the SDK could use. */
|
|
188
|
-
| 'SERVER_ERROR';
|
|
189
|
-
/**
|
|
190
|
-
* Delivered on the `error` event. This is the channel for "why is my chat not
|
|
191
|
-
* working": it is never rendered inside the chat interface, and it never
|
|
192
|
-
* carries the API key, the identity token, or customer personal data.
|
|
193
|
-
*/
|
|
133
|
+
export type NewinstanceChatErrorCode = 'INVALID_PUBLIC_KEY' | 'MISSING_WIDGET_KEY' | 'CHAT_CONFIG_UNAVAILABLE' | 'CHAT_DISABLED' | 'CONFIG_FETCH_FAILED' | 'INVALID_IDENTITY_TOKEN' | 'NETWORK_ERROR' | 'TRANSPORT_ERROR' | 'CHAT_INITIALIZATION_FAILED' | 'MESSAGE_SEND_FAILED' | 'ATTACHMENT_FAILED' | 'SERVER_ERROR';
|
|
194
134
|
export interface NewinstanceChatError {
|
|
195
|
-
/** Stable code to branch on. Undefined only from SDK versions older than 0.3.0. */
|
|
196
135
|
code?: NewinstanceChatErrorCode;
|
|
197
|
-
/** Broad category, kept for backwards compatibility with 0.2.x handlers. */
|
|
198
136
|
type: NewinstanceChatErrorType;
|
|
199
|
-
/** Safe to log and to show a developer. Not written for end users. */
|
|
200
137
|
message: string;
|
|
201
|
-
/**
|
|
202
|
-
* Extra detail for developers, when the SDK had any that was safe to
|
|
203
|
-
* expose. Never a raw server response body.
|
|
204
|
-
*/
|
|
205
138
|
developerMessage?: string;
|
|
206
|
-
/** False when the SDK has given up until the host fixes something. */
|
|
207
139
|
recoverable: boolean;
|
|
208
140
|
}
|
|
209
141
|
/** Native event names. Subscribe with `sdk.addListener(event, handler)`. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,qBAAqB;IACpC,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-newinstance-chat",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "New instance LiveAndAiChat React Native SDK
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "New instance LiveAndAiChat React Native SDK: AI + live-agent chat, drop-in native chat screen for iOS and Android.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -13,6 +13,7 @@ import { TurboModuleRegistry, type TurboModule } from 'react-native';
|
|
|
13
13
|
export interface Spec extends TurboModule {
|
|
14
14
|
configure(config: Object): Promise<void>;
|
|
15
15
|
setUser(user: Object): void;
|
|
16
|
+
setUserToken(token: string): Promise<void>;
|
|
16
17
|
initialize(): Promise<void>;
|
|
17
18
|
openChat(): void;
|
|
18
19
|
closeChat(): void;
|
package/src/index.tsx
CHANGED
|
@@ -36,6 +36,34 @@ const emitterSource: any =
|
|
|
36
36
|
NativeModules.NewinstanceChat ?? (NativeNewinstanceChat as unknown);
|
|
37
37
|
const emitter = new NativeEventEmitter(emitterSource);
|
|
38
38
|
|
|
39
|
+
function trimmed(value: unknown): string {
|
|
40
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function configProblem(apiKey: string, customerToken: string): string | null {
|
|
44
|
+
if (!apiKey === !customerToken) {
|
|
45
|
+
return 'pass either apiKey (keyId:secret) or customerToken';
|
|
46
|
+
}
|
|
47
|
+
if (customerToken) return null;
|
|
48
|
+
const separator = apiKey.indexOf(':');
|
|
49
|
+
if (
|
|
50
|
+
separator < 0 ||
|
|
51
|
+
!apiKey.slice(0, separator).trim() ||
|
|
52
|
+
!apiKey.slice(separator + 1).trim()
|
|
53
|
+
) {
|
|
54
|
+
return 'apiKey must be keyId:secret, or use customerToken';
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function configError(message: string) {
|
|
60
|
+
return Object.assign(new Error(message), {
|
|
61
|
+
code: 'E_CONFIG' as const,
|
|
62
|
+
type: 'validation' as const,
|
|
63
|
+
recoverable: false,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
39
67
|
/**
|
|
40
68
|
* React Native binding for the native LiveAndAiChat iOS / Android SDKs.
|
|
41
69
|
*
|
|
@@ -49,31 +77,44 @@ const emitter = new NativeEventEmitter(emitterSource);
|
|
|
49
77
|
export class NewinstanceChat {
|
|
50
78
|
/** Resolves once the native SDK is configured. */
|
|
51
79
|
readonly ready: Promise<void>;
|
|
80
|
+
private readonly customerTokenMode: boolean;
|
|
52
81
|
|
|
53
82
|
constructor(config: NewinstanceChatConfig) {
|
|
54
|
-
|
|
83
|
+
const { apiKey, customerToken, ...rest } = config;
|
|
84
|
+
const key = trimmed(apiKey);
|
|
85
|
+
const token = trimmed(customerToken);
|
|
86
|
+
this.customerTokenMode = !key && !!token;
|
|
87
|
+
const problem = configProblem(key, token);
|
|
88
|
+
this.ready = problem
|
|
89
|
+
? Promise.reject(configError(problem))
|
|
90
|
+
: NativeNewinstanceChat.configure(
|
|
91
|
+
token ? { ...rest, customerToken: token } : { ...rest, apiKey: key }
|
|
92
|
+
);
|
|
55
93
|
}
|
|
56
94
|
|
|
57
95
|
/**
|
|
58
96
|
* Identify the customer. Safe to call before or after `initialize()`. If
|
|
59
97
|
* called before `configure()` resolves, the user is queued and applied
|
|
60
98
|
* once the native instance exists.
|
|
61
|
-
*
|
|
62
|
-
* Prefer `customerToken`: it is minted by your backend, so the server can
|
|
63
|
-
* verify who the customer is. `customerName` / `customerEmail` /
|
|
64
|
-
* `customerId` alone are client-asserted and are not trusted as identity.
|
|
65
99
|
*/
|
|
66
100
|
setUser(user: ChatUser): void {
|
|
101
|
+
if ('customerToken' in user) {
|
|
102
|
+
throw configError(
|
|
103
|
+
'customerToken is not a setUser field; pass it in the config or call setUserToken'
|
|
104
|
+
);
|
|
105
|
+
}
|
|
67
106
|
NativeNewinstanceChat.setUser(user);
|
|
68
107
|
}
|
|
69
108
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
109
|
+
setUserToken(customerToken: string): Promise<void> {
|
|
110
|
+
if (!this.customerTokenMode) {
|
|
111
|
+
throw configError(
|
|
112
|
+
'setUserToken is only available when the SDK is configured with customerToken'
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
const token = trimmed(customerToken);
|
|
116
|
+
if (!token) throw configError('customerToken must not be blank');
|
|
117
|
+
return NativeNewinstanceChat.setUserToken(token);
|
|
77
118
|
}
|
|
78
119
|
|
|
79
120
|
/**
|
|
@@ -82,7 +123,7 @@ export class NewinstanceChat {
|
|
|
82
123
|
* the same in-flight init.
|
|
83
124
|
*/
|
|
84
125
|
initialize(): Promise<void> {
|
|
85
|
-
return NativeNewinstanceChat.initialize();
|
|
126
|
+
return this.ready.then(() => NativeNewinstanceChat.initialize());
|
|
86
127
|
}
|
|
87
128
|
|
|
88
129
|
/** Present the native chat screen modally over the current RN host. */
|
|
@@ -110,7 +151,9 @@ export class NewinstanceChat {
|
|
|
110
151
|
*/
|
|
111
152
|
attachFile(opts: AttachmentInput): Promise<string> {
|
|
112
153
|
if (!opts.source && !opts.uri) {
|
|
113
|
-
return Promise.reject(
|
|
154
|
+
return Promise.reject(
|
|
155
|
+
new Error('attachFile requires `source` (or the legacy `uri`)')
|
|
156
|
+
);
|
|
114
157
|
}
|
|
115
158
|
return NativeNewinstanceChat.attachFile(opts);
|
|
116
159
|
}
|
package/src/types.ts
CHANGED
|
@@ -7,32 +7,18 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
export interface NewinstanceChatConfig {
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
apiKey?: string;
|
|
11
|
+
customerToken?: string;
|
|
12
12
|
/** Backend base URL. Defaults to `https://service.newinstance.cloud`. */
|
|
13
13
|
baseUrl?: string;
|
|
14
14
|
/** Subscription transport. `'sse'` (default in production), `'ws'`, or omit to let the server choose. */
|
|
15
15
|
transport?: 'sse' | 'ws';
|
|
16
16
|
/** Optional message pre-seeded as the first outbound message. */
|
|
17
17
|
initialMessage?: string;
|
|
18
|
-
/**
|
|
19
|
-
* Local theme. Ranked BELOW whatever the merchant configured in the
|
|
20
|
-
* dashboard and ABOVE the SDK's built-in palette, merged per property, so a
|
|
21
|
-
* token you set here applies only where the dashboard did not set one.
|
|
22
|
-
*/
|
|
23
18
|
theme?: ChatThemeOverride;
|
|
24
19
|
}
|
|
25
20
|
|
|
26
|
-
/**
|
|
27
|
-
* Local theme tokens. Every field is optional; colours are hex strings
|
|
28
|
-
* (`#RRGGBB` or `#AARRGGBB`). An unparseable value is ignored rather than
|
|
29
|
-
* throwing, so a bad colour can never crash the app.
|
|
30
|
-
*/
|
|
31
21
|
export interface ChatThemeOverride {
|
|
32
|
-
/**
|
|
33
|
-
* Force light or dark. Omit for "no opinion": the dashboard's mode wins if
|
|
34
|
-
* it set one, otherwise the device setting.
|
|
35
|
-
*/
|
|
36
22
|
mode?: 'light' | 'dark';
|
|
37
23
|
chatBackground?: string;
|
|
38
24
|
headerBackground?: string;
|
|
@@ -64,19 +50,7 @@ export interface ChatThemeOverride {
|
|
|
64
50
|
export interface ChatUser {
|
|
65
51
|
customerName: string;
|
|
66
52
|
customerEmail?: string;
|
|
67
|
-
/**
|
|
68
|
-
* Your reference for this customer. Sent for agent context but NOT trusted
|
|
69
|
-
* as identity: an id asserted by app code cannot be verified. For a trusted
|
|
70
|
-
* identity use `customerToken`.
|
|
71
|
-
*/
|
|
72
53
|
customerId?: string;
|
|
73
|
-
/**
|
|
74
|
-
* Chat identity token minted by YOUR backend with `createCsCustomerToken`
|
|
75
|
-
* and handed to the app. When set it is the authoritative identity: the
|
|
76
|
-
* server derives name, email and customer id from its signed claims, so a
|
|
77
|
-
* tampered app cannot impersonate another customer.
|
|
78
|
-
*/
|
|
79
|
-
customerToken?: string;
|
|
80
54
|
}
|
|
81
55
|
|
|
82
56
|
export type AttachmentSourceType =
|
|
@@ -195,55 +169,25 @@ export type NewinstanceChatErrorType =
|
|
|
195
169
|
| 'auth'
|
|
196
170
|
| 'system';
|
|
197
171
|
|
|
198
|
-
/**
|
|
199
|
-
* Stable, cross-platform diagnostic codes. The same strings are emitted by the
|
|
200
|
-
* web, Android, iOS and Flutter SDKs, so `code` is the value to branch on and
|
|
201
|
-
* to quote in a support request.
|
|
202
|
-
*/
|
|
203
172
|
export type NewinstanceChatErrorCode =
|
|
204
|
-
/** The API key was rejected: wrong key, wrong environment, revoked, or expired. */
|
|
205
173
|
| 'INVALID_PUBLIC_KEY'
|
|
206
|
-
/** No API key was supplied. */
|
|
207
174
|
| 'MISSING_WIDGET_KEY'
|
|
208
|
-
/** The key is valid but no chat configuration exists for the organization. */
|
|
209
175
|
| 'CHAT_CONFIG_UNAVAILABLE'
|
|
210
|
-
/** The key and config are fine, but chat is switched off in the dashboard. */
|
|
211
176
|
| 'CHAT_DISABLED'
|
|
212
|
-
/** Remote configuration could not be fetched; the built-in theme is in use. */
|
|
213
177
|
| 'CONFIG_FETCH_FAILED'
|
|
214
|
-
/** The chat identity token was malformed, expired, or signed for another organization. */
|
|
215
178
|
| 'INVALID_IDENTITY_TOKEN'
|
|
216
|
-
/** A request could not reach the backend at all. */
|
|
217
179
|
| 'NETWORK_ERROR'
|
|
218
|
-
/** The realtime transport dropped and could not be re-established. */
|
|
219
180
|
| 'TRANSPORT_ERROR'
|
|
220
|
-
/** Starting a conversation failed. */
|
|
221
181
|
| 'CHAT_INITIALIZATION_FAILED'
|
|
222
|
-
/** A message could not be delivered. */
|
|
223
182
|
| 'MESSAGE_SEND_FAILED'
|
|
224
|
-
/** An attachment could not be uploaded. */
|
|
225
183
|
| 'ATTACHMENT_FAILED'
|
|
226
|
-
/** The backend answered, but not with anything the SDK could use. */
|
|
227
184
|
| 'SERVER_ERROR';
|
|
228
185
|
|
|
229
|
-
/**
|
|
230
|
-
* Delivered on the `error` event. This is the channel for "why is my chat not
|
|
231
|
-
* working": it is never rendered inside the chat interface, and it never
|
|
232
|
-
* carries the API key, the identity token, or customer personal data.
|
|
233
|
-
*/
|
|
234
186
|
export interface NewinstanceChatError {
|
|
235
|
-
/** Stable code to branch on. Undefined only from SDK versions older than 0.3.0. */
|
|
236
187
|
code?: NewinstanceChatErrorCode;
|
|
237
|
-
/** Broad category, kept for backwards compatibility with 0.2.x handlers. */
|
|
238
188
|
type: NewinstanceChatErrorType;
|
|
239
|
-
/** Safe to log and to show a developer. Not written for end users. */
|
|
240
189
|
message: string;
|
|
241
|
-
/**
|
|
242
|
-
* Extra detail for developers, when the SDK had any that was safe to
|
|
243
|
-
* expose. Never a raw server response body.
|
|
244
|
-
*/
|
|
245
190
|
developerMessage?: string;
|
|
246
|
-
/** False when the SDK has given up until the host fixes something. */
|
|
247
191
|
recoverable: boolean;
|
|
248
192
|
}
|
|
249
193
|
|