react-native-newinstance-chat 0.2.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.
@@ -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.2.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) — drops a
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 — your React Native code
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 — recommended 0.85+)
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,26 +54,212 @@ 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
- apiKey: 'sk_live_…',
57
+ customerToken: tokenFromYourBackend,
58
+ });
59
+
60
+ await sdk.ready;
61
+ await sdk.initialize();
62
+
63
+ sdk.openChat();
64
+ ```
65
+
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
74
+
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.
80
+
81
+ ### JWT mode (`customerToken`), for apps you ship to the public
82
+
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.
88
+
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:
92
+
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:
122
+
123
+ ```ts
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
+ });
144
+ ```
145
+
146
+ Hand `token` to the app and pass it in the config:
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();
156
+ ```
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
+ });
171
+ ```
172
+
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`.
178
+
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.
185
+
186
+ In JWT mode `setUser` is accepted but ignored for identity: the name, email
187
+ and customer id always come from the token.
188
+
189
+ ### Key mode (`apiKey`), for trusted internal apps only
190
+
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.
195
+
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`.
200
+
201
+ ```ts
202
+ const sdk = new NewinstanceChat({
203
+ apiKey: keyFromYourBuildConfig,
58
204
  });
59
205
 
60
206
  sdk.setUser({
61
207
  customerName: 'Ada Lovelace',
62
208
  customerEmail: 'ada@example.com',
209
+ customerId: 'usr_123',
63
210
  });
64
211
 
212
+ await sdk.ready;
65
213
  await sdk.initialize();
214
+ ```
66
215
 
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
- await sdk.ready;
216
+ Load the key from your build configuration or an environment variable, and
217
+ never commit it to source control.
70
218
 
71
- // Later, in response to a host button tap:
72
- sdk.openChat();
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.
234
+
235
+ ### Theming
236
+
237
+ The chat resolves colours from three layers:
238
+
239
+ ```
240
+ dashboard theme > your local theme > the SDK's built-in theme
241
+ ```
242
+
243
+ The merchant's dashboard configuration wins, your local theme fills in what
244
+ the dashboard did not set, and the built-in palette fills in the rest, so
245
+ every colour always has a value, even with no network. Merging is per
246
+ token, so setting one colour does not discard the others.
247
+
248
+ ```ts
249
+ const sdk = new NewinstanceChat({
250
+ customerToken: tokenFromYourBackend,
251
+ theme: { mode: 'dark', sentBubble: '#16A34A' },
252
+ });
73
253
  ```
74
254
 
75
- The chat screen renders natively — there is no React component to mount.
76
- Your RN UI is whatever button / banner / FAB triggers `openChat()`.
255
+ Every `ChatThemeOverride` field is optional; colours are hex strings. An
256
+ unparseable value is ignored rather than throwing. `mode` is only a
257
+ preference: the dashboard's explicit light/dark wins, and its `auto` defers
258
+ to yours, then to the device.
259
+
260
+ If remote configuration cannot be fetched, your local theme still applies
261
+ and the chat stays fully usable; you get a `CONFIG_FETCH_FAILED` warning on
262
+ the error event.
77
263
 
78
264
  ### Subscribing to events
79
265
 
@@ -89,6 +275,47 @@ Event names: `messageReceived`, `messageSent`, `agentTypingChanged`,
89
275
  `connectionStateChanged`, `unreadCountChanged`, `error`,
90
276
  `attachmentUpdated`, `chatClosed`. See `src/types.ts` for payload shapes.
91
277
 
278
+ ### The error event
279
+
280
+ `error` is the channel for "why is my chat not working". It never surfaces
281
+ in the chat interface the customer sees, and it never carries the API key,
282
+ the secret, the customer token, or customer data.
283
+
284
+ ```ts
285
+ sdk.addListener('error', (e) => {
286
+ if (e.code === 'INVALID_IDENTITY_TOKEN') return refreshChatToken();
287
+ if (!e.recoverable) reportToYourMonitoring(e.code, e.message);
288
+ });
289
+ ```
290
+
291
+ | `code` | What went wrong |
292
+ |---|---|
293
+ | `MISSING_WIDGET_KEY` | No API key was supplied |
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 |
296
+ | `CHAT_DISABLED` | Chat is switched off in the dashboard |
297
+ | `CONFIG_FETCH_FAILED` | Remote configuration could not be loaded; the local/built-in theme is in use |
298
+ | `INVALID_IDENTITY_TOKEN` | JWT mode: the token was malformed, expired, revoked, or signed for another organization |
299
+ | `NETWORK_ERROR` | The backend could not be reached |
300
+ | `TRANSPORT_ERROR` | The realtime connection dropped |
301
+ | `CHAT_INITIALIZATION_FAILED` | The conversation could not be started |
302
+ | `MESSAGE_SEND_FAILED` | A message could not be delivered |
303
+ | `ATTACHMENT_FAILED` | An attachment could not be uploaded |
304
+ | `SERVER_ERROR` | The backend returned something unusable |
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
+
310
+ The same codes are emitted by the web, Android, iOS and Flutter SDKs.
311
+ `type` (`network` / `validation` / `auth` / `system`) is still present for
312
+ handlers written against 0.2.x; `code` is `undefined` only when the
313
+ underlying native SDK predates coded errors.
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
+
92
319
  ### The chat-close event
93
320
 
94
321
  `chatClosed` fires **exactly once per chat presentation**, however the
@@ -127,8 +354,8 @@ sdk.retryMessage(failedMessageId);
127
354
  ### Programmatic attachments
128
355
 
129
356
  `attachFile` accepts base64, data URIs, local file paths,
130
- `content://` / `file://` URIs (picker outputs), and — when the native
131
- SDK is configured with `allowRemoteAttachmentUrls` — remote URLs.
357
+ `content://` / `file://` URIs (picker outputs), and, when the native
358
+ SDK is configured with `allowRemoteAttachmentUrls`, remote URLs.
132
359
  `sourceType: 'auto'` (the default) safely detects string inputs.
133
360
 
134
361
  ```ts
@@ -155,8 +382,8 @@ await sdk.attachFile({ source: dataUri, name: 'chart.png' });
155
382
 
156
383
  The returned promise resolves with the attachment **id** as soon as the
157
384
  request is accepted and queued. Validation and upload run in the
158
- background and never block the UI thread — failures surface as
159
- `attachmentUpdated` with `status: 'failed'` plus an `error` event — and
385
+ background and never block the UI thread (failures surface as
386
+ `attachmentUpdated` with `status: 'failed'` plus an `error` event), and
160
387
  the message is never sent until the upload succeeds (send drains only
161
388
  uploaded attachments). Track progress and results via
162
389
  `attachmentUpdated`:
@@ -182,7 +409,7 @@ Allowed MIME types and the 25 MB cap mirror the web widget; keys and
182
409
  limits are enforced again server-side.
183
410
 
184
411
  Handoff to a live agent and typing indicators are driven automatically
185
- by the native chat screen — there's nothing to wire up from the host.
412
+ by the native chat screen; there's nothing to wire up from the host.
186
413
 
187
414
  ### Tear-down
188
415
 
@@ -191,7 +418,25 @@ sdk.destroy();
191
418
  ```
192
419
 
193
420
  Call this when your host component unmounts. After `destroy()` the
194
- instance is unusable — construct a new one if you need to chat again.
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.
195
440
 
196
441
  ## Contributing
197
442
 
@@ -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.2.0"
76
+ implementation "cloud.newinstance:liveandaichat:0.4.0"
77
77
  }
@@ -1,10 +1,14 @@
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
6
7
  import cloud.newinstance.liveandaichat.LiveAndAiChatError
8
+ import cloud.newinstance.liveandaichat.ChatSdkError
9
+ import cloud.newinstance.liveandaichat.ChatThemeOverride
7
10
  import cloud.newinstance.liveandaichat.ChatUser
11
+ import cloud.newinstance.liveandaichat.ErrorType
8
12
  import cloud.newinstance.liveandaichat.model.AttachmentRequest
9
13
  import cloud.newinstance.liveandaichat.model.AttachmentSource
10
14
  import cloud.newinstance.liveandaichat.model.ChatCloseEvent
@@ -15,6 +19,7 @@ import com.facebook.react.bridge.Arguments
15
19
  import com.facebook.react.bridge.Promise
16
20
  import com.facebook.react.bridge.ReactApplicationContext
17
21
  import com.facebook.react.bridge.ReadableMap
22
+ import com.facebook.react.bridge.ReadableType
18
23
  import com.facebook.react.bridge.WritableMap
19
24
  import com.facebook.react.modules.core.DeviceEventManagerModule
20
25
 
@@ -39,6 +44,8 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
39
44
  private var sdk: LiveAndAiChat? = null
40
45
  private var pendingUser: ChatUser? = null
41
46
 
47
+ private var usingIdentityToken: Boolean = false
48
+
42
49
  override fun getName(): String = NAME
43
50
 
44
51
  private fun emit(event: String, body: Any?) {
@@ -50,9 +57,10 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
50
57
  // MARK: - configure / setUser / initialize
51
58
 
52
59
  override fun configure(config: ReadableMap, promise: Promise) {
53
- val apiKey = config.getString("apiKey")
54
- if (apiKey.isNullOrBlank()) {
55
- promise.reject("E_CONFIG", "apiKey is required")
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")
56
64
  return
57
65
  }
58
66
  val baseUrl = if (config.hasKey("baseUrl")) config.getString("baseUrl") else null
@@ -66,12 +74,26 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
66
74
  }
67
75
  } else null
68
76
 
77
+ val themeOverride = if (config.hasKey("theme")) {
78
+ config.getMap("theme")?.let { map ->
79
+ val values = mutableMapOf<String, String?>()
80
+ val it = map.keySetIterator()
81
+ while (it.hasNextKey()) {
82
+ val key = it.nextKey()
83
+ if (map.getType(key) == ReadableType.String) values[key] = map.getString(key)
84
+ }
85
+ ChatThemeOverride.fromMap(values)
86
+ }
87
+ } else null
88
+
69
89
  try {
70
90
  val cfg = LiveAndAiChatConfig(
71
91
  apiKey = apiKey,
92
+ customerToken = customerToken,
72
93
  baseUrl = baseUrl ?: LiveAndAiChatConfig.DEFAULT_BASE_URL,
73
94
  transport = transport,
74
95
  initialMessage = initialMessage,
96
+ theme = themeOverride,
75
97
  )
76
98
  sdk?.destroy()
77
99
  val builder = LiveAndAiChat.Builder(reactApplicationContext).config(cfg)
@@ -79,16 +101,18 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
79
101
  val instance = builder.build()
80
102
  instance.addListener(this)
81
103
  sdk = instance
104
+ usingIdentityToken = cfg.authMode == ChatAuthMode.CUSTOMER_TOKEN
82
105
  pendingUser = null
83
106
  promise.resolve(null)
107
+ } catch (e: LiveAndAiChatError) {
108
+ promise.reject(e.code ?: "E_BUILD", e.message, e)
84
109
  } catch (e: Exception) {
85
110
  promise.reject("E_BUILD", e.message, e)
86
111
  }
87
112
  }
88
113
 
89
114
  override fun setUser(user: ReadableMap) {
90
- val name = user.getString("customerName") ?: return
91
- if (name.isBlank()) return
115
+ val name = user.getString("customerName")?.takeIf { it.isNotBlank() } ?: return
92
116
  val chatUser = ChatUser(
93
117
  customerName = name,
94
118
  customerId = if (user.hasKey("customerId")) user.getString("customerId") else null,
@@ -102,6 +126,22 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
102
126
  }
103
127
  }
104
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
+
105
145
  override fun initialize(promise: Promise) {
106
146
  val current = sdk
107
147
  if (current == null) {
@@ -143,7 +183,7 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
143
183
  if (source == null) {
144
184
  promise.reject(
145
185
  "E_SOURCE",
146
- if (sourceType == "auto") "Could not detect the attachment source type — pass sourceType explicitly"
186
+ if (sourceType == "auto") "Could not detect the attachment source type: pass sourceType explicitly"
147
187
  else "Unknown sourceType: $sourceType",
148
188
  )
149
189
  return
@@ -240,12 +280,39 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
240
280
 
241
281
  override fun onError(error: LiveAndAiChatError) {
242
282
  val map = Arguments.createMap()
283
+ map.putString("code", sdkErrorCode(error))
243
284
  map.putString("type", error.type.name.lowercase())
244
285
  map.putString("message", error.message)
245
286
  map.putBoolean("recoverable", error.recoverable)
246
287
  emit("error", map)
247
288
  }
248
289
 
290
+ private fun sdkErrorCode(error: LiveAndAiChatError): String {
291
+ val known = setOf(
292
+ ChatSdkError.CHAT_CONFIG_UNAVAILABLE,
293
+ ChatSdkError.CHAT_DISABLED,
294
+ ChatSdkError.INVALID_PUBLIC_KEY,
295
+ ChatSdkError.MISSING_WIDGET_KEY,
296
+ ChatSdkError.CONFIG_FETCH_FAILED,
297
+ ChatSdkError.CHAT_INITIALIZATION_FAILED,
298
+ ChatSdkError.NETWORK_ERROR,
299
+ ChatSdkError.SERVER_ERROR,
300
+ ChatSdkError.INVALID_IDENTITY_TOKEN,
301
+ ChatSdkError.TRANSPORT_ERROR,
302
+ ChatSdkError.MESSAGE_SEND_FAILED,
303
+ ChatSdkError.ATTACHMENT_FAILED,
304
+ )
305
+ error.code?.let { if (it in known) return it }
306
+ return when (error.type) {
307
+ ErrorType.AUTH ->
308
+ if (usingIdentityToken) ChatSdkError.INVALID_IDENTITY_TOKEN else ChatSdkError.INVALID_PUBLIC_KEY
309
+ ErrorType.UNAVAILABLE -> ChatSdkError.CHAT_CONFIG_UNAVAILABLE
310
+ ErrorType.NETWORK -> ChatSdkError.NETWORK_ERROR
311
+ ErrorType.VALIDATION -> ChatSdkError.SERVER_ERROR
312
+ ErrorType.SYSTEM -> ChatSdkError.CHAT_INITIALIZATION_FAILED
313
+ }
314
+ }
315
+
249
316
  // MARK: - Helpers
250
317
 
251
318
  private fun chatMessageToMap(m: ChatMessage): WritableMap {
@@ -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,14 +29,18 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
29
29
  private var sdk: LiveAndAiChat?
30
30
  private var pendingUser: ChatUser?
31
31
 
32
+ private var usingIdentityToken: Bool = false
33
+
32
34
  // MARK: - configure / setUser / initialize
33
35
 
34
36
  @objc(configure:resolver:rejecter:)
35
37
  public func configure(_ configDict: NSDictionary,
36
38
  resolver: @escaping RCTPromiseResolveBlock,
37
39
  rejecter: @escaping RCTPromiseRejectBlock) {
38
- guard let apiKey = configDict["apiKey"] as? String, !apiKey.isEmpty else {
39
- rejecter("E_CONFIG", "apiKey is required", nil)
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)
40
44
  return
41
45
  }
42
46
  let baseUrl = configDict["baseUrl"] as? String
@@ -49,14 +53,25 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
49
53
  }
50
54
  }()
51
55
 
56
+ let themeOverride: ChatThemeOverride? = {
57
+ guard let raw = configDict["theme"] as? NSDictionary else { return nil }
58
+ var values: [String: String] = [:]
59
+ for (key, value) in raw {
60
+ if let k = key as? String, let v = value as? String { values[k] = v }
61
+ }
62
+ return values.isEmpty ? nil : ChatThemeOverride.fromDictionary(values)
63
+ }()
64
+
52
65
  DispatchQueue.main.async { [weak self] in
53
66
  guard let self = self else { return }
54
67
  do {
55
68
  let cfg = try LiveAndAiChatConfig(
56
69
  apiKey: apiKey,
70
+ customerToken: customerToken,
57
71
  baseUrl: baseUrl ?? LiveAndAiChatConfig.defaultBaseUrl,
58
72
  transport: transport,
59
- initialMessage: initialMessage
73
+ initialMessage: initialMessage,
74
+ theme: themeOverride
60
75
  )
61
76
  let builder = LiveAndAiChat.Builder().config(cfg)
62
77
  if let user = self.pendingUser { _ = builder.user(user) }
@@ -64,10 +79,11 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
64
79
  let instance = try builder.build()
65
80
  instance.addDelegate(self)
66
81
  self.sdk = instance
82
+ self.usingIdentityToken = cfg.authMode == .customerToken
67
83
  self.pendingUser = nil
68
84
  resolver(nil)
69
85
  } catch let e as LiveAndAiChatError {
70
- rejecter("E_BUILD", e.message, e)
86
+ rejecter(e.code ?? "E_BUILD", e.message, e)
71
87
  } catch {
72
88
  rejecter("E_BUILD", error.localizedDescription, error)
73
89
  }
@@ -76,7 +92,7 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
76
92
 
77
93
  @objc(setUser:)
78
94
  public func setUser(_ userDict: NSDictionary) {
79
- guard let name = userDict["customerName"] as? String, !name.isEmpty else { return }
95
+ guard let name = (userDict["customerName"] as? String).flatMap({ $0.isEmpty ? nil : $0 }) else { return }
80
96
  let user = ChatUser(
81
97
  customerName: name,
82
98
  customerId: userDict["customerId"] as? String,
@@ -92,6 +108,26 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
92
108
  }
93
109
  }
94
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
+
95
131
  @objc(initializeWithResolver:rejecter:)
96
132
  public func initialize(resolver: @escaping RCTPromiseResolveBlock,
97
133
  rejecter: @escaping RCTPromiseRejectBlock) {
@@ -153,7 +189,7 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
153
189
  rejecter(
154
190
  "E_SOURCE",
155
191
  sourceType == "auto"
156
- ? "Could not detect the attachment source type — pass sourceType explicitly"
192
+ ? "Could not detect the attachment source type: pass sourceType explicitly"
157
193
  : "Unknown sourceType: \(sourceType)",
158
194
  nil
159
195
  )
@@ -247,6 +283,7 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
247
283
  }
248
284
  public func didEncounterError(_ error: LiveAndAiChatError) {
249
285
  emitter?.emitEvent("error", body: [
286
+ "code": error.sdkCode(usingIdentityToken: usingIdentityToken),
250
287
  "type": Self.errorTypeName(error.type),
251
288
  "message": error.message,
252
289
  "recoverable": error.recoverable,
@@ -255,6 +292,11 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
255
292
 
256
293
  // MARK: - Helpers
257
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
+
258
300
  private static func chatMessageToDict(_ m: ChatMessage) -> [String: Any?] {
259
301
  return [
260
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;;AAoBA,eAAeA,mBAAmB,CAACC,YAAY,CAAO,iBAAiB,CAAC","ignoreList":[]}
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":[]}
@@ -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,7 +43,22 @@ export class NewinstanceChat {
22
43
  /** Resolves once the native SDK is configured. */
23
44
 
24
45
  constructor(config) {
25
- this.ready = NativeNewinstanceChat.configure(config);
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
  /**
@@ -31,8 +67,19 @@ export class NewinstanceChat {
31
67
  * once the native instance exists.
32
68
  */
33
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
+ }
34
73
  NativeNewinstanceChat.setUser(user);
35
74
  }
75
+ setUserToken(customerToken) {
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);
82
+ }
36
83
 
37
84
  /**
38
85
  * Fetch org config, restore the saved conversation, and connect the
@@ -40,7 +87,7 @@ export class NewinstanceChat {
40
87
  * the same in-flight init.
41
88
  */
42
89
  initialize() {
43
- return NativeNewinstanceChat.initialize();
90
+ return this.ready.then(() => NativeNewinstanceChat.initialize());
44
91
  }
45
92
 
46
93
  /** Present the native chat screen modally over the current RN host. */
@@ -1 +1 @@
1
- {"version":3,"names":["NativeEventEmitter","NativeModules","NativeNewinstanceChat","emitterSource","NewinstanceChat","emitter","constructor","config","ready","configure","setUser","user","initialize","openChat","closeChat","sendMessage","text","retryMessage","messageId","attachFile","opts","source","uri","Promise","reject","Error","removeAttachment","id","clearAttachments","addListener","event","handler","sub","remove","destroy"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SACEA,kBAAkB,EAClBC,aAAa,QAER,cAAc;AAErB,OAAOC,qBAAqB,MAAM,4BAAyB;AAqB3D;AACA;AACA;AACA,MAAMC,aAAkB,GACtBF,aAAa,CAACG,eAAe,IAAKF,qBAAiC;AACrE,MAAMG,OAAO,GAAG,IAAIL,kBAAkB,CAACG,aAAa,CAAC;;AAErD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,eAAe,CAAC;EAC3B;;EAGAE,WAAWA,CAACC,MAA6B,EAAE;IACzC,IAAI,CAACC,KAAK,GAAGN,qBAAqB,CAACO,SAAS,CAACF,MAAM,CAAC;EACtD;;EAEA;AACF;AACA;AACA;AACA;EACEG,OAAOA,CAACC,IAAc,EAAQ;IAC5BT,qBAAqB,CAACQ,OAAO,CAACC,IAAI,CAAC;EACrC;;EAEA;AACF;AACA;AACA;AACA;EACEC,UAAUA,CAAA,EAAkB;IAC1B,OAAOV,qBAAqB,CAACU,UAAU,CAAC,CAAC;EAC3C;;EAEA;EACAC,QAAQA,CAAA,EAAS;IACfX,qBAAqB,CAACW,QAAQ,CAAC,CAAC;EAClC;;EAEA;EACAC,SAASA,CAAA,EAAS;IAChBZ,qBAAqB,CAACY,SAAS,CAAC,CAAC;EACnC;EAEAC,WAAWA,CAACC,IAAY,EAAQ;IAC9Bd,qBAAqB,CAACa,WAAW,CAACC,IAAI,CAAC;EACzC;EAEAC,YAAYA,CAACC,SAAiB,EAAQ;IACpChB,qBAAqB,CAACe,YAAY,CAACC,SAAS,CAAC;EAC/C;;EAEA;AACF;AACA;AACA;AACA;EACEC,UAAUA,CAACC,IAAqB,EAAmB;IACjD,IAAI,CAACA,IAAI,CAACC,MAAM,IAAI,CAACD,IAAI,CAACE,GAAG,EAAE;MAC7B,OAAOC,OAAO,CAACC,MAAM,CAAC,IAAIC,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACxF;IACA,OAAOvB,qBAAqB,CAACiB,UAAU,CAACC,IAAI,CAAC;EAC/C;;EAEA;EACAM,gBAAgBA,CAACC,EAAU,EAAQ;IACjCzB,qBAAqB,CAACwB,gBAAgB,CAACC,EAAE,CAAC;EAC5C;;EAEA;EACAC,gBAAgBA,CAAA,EAAS;IACvB1B,qBAAqB,CAAC0B,gBAAgB,CAAC,CAAC;EAC1C;;EAEA;AACF;AACA;AACA;EACEC,WAAWA,CACTC,KAAQ,EACRC,OAAwB,EACZ;IACZ,MAAMC,GAAwB,GAAG3B,OAAO,CAACwB,WAAW,CAClDC,KAAK,EACLC,OACF,CAAC;IACD,OAAO,MAAMC,GAAG,CAACC,MAAM,CAAC,CAAC;EAC3B;;EAEA;AACF;AACA;AACA;EACEC,OAAOA,CAAA,EAAS;IACdhC,qBAAqB,CAACgC,OAAO,CAAC,CAAC;EACjC;AACF","ignoreList":[]}
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"}
@@ -1,5 +1,5 @@
1
1
  import type { AttachmentInput, ChatUser, EventHandler, EventName, NewinstanceChatConfig } from './types';
2
- export type { AttachmentInput, ChatMessage, ChatUser, ConnectionState, EventMap, EventName, NewinstanceChatConfig, NewinstanceChatError, NewinstanceChatErrorType, } from './types';
2
+ export type { AttachmentInput, AttachmentUpdate, ChatClosedEvent, ChatMessage, ChatThemeOverride, ChatUser, ConnectionState, EventMap, EventName, NewinstanceChatConfig, NewinstanceChatError, NewinstanceChatErrorCode, NewinstanceChatErrorType, } from './types';
3
3
  /**
4
4
  * React Native binding for the native LiveAndAiChat iOS / Android SDKs.
5
5
  *
@@ -13,6 +13,7 @@ export type { AttachmentInput, ChatMessage, ChatUser, ConnectionState, EventMap,
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
@@ -20,6 +21,7 @@ export declare class NewinstanceChat {
20
21
  * once the native instance exists.
21
22
  */
22
23
  setUser(user: ChatUser): void;
24
+ setUserToken(customerToken: string): Promise<void>;
23
25
  /**
24
26
  * Fetch org config, restore the saved conversation, and connect the
25
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,WAAW,EACX,QAAQ,EACR,eAAe,EACf,QAAQ,EACR,SAAS,EACT,qBAAqB,EACrB,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,SAAS,CAAC;AASjB;;;;;;;;;GASG;AACH,qBAAa,eAAe;IAC1B,kDAAkD;IAClD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;gBAElB,MAAM,EAAE,qBAAqB;IAIzC;;;;OAIG;IACH,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI;IAI7B;;;;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;IAOlD,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"}
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,14 +6,43 @@
6
6
  * are lowercased string enums, not platform-specific enum values).
7
7
  */
8
8
  export interface NewinstanceChatConfig {
9
- /** API key id from your newinstance.cloud dashboard. Never embed the secret half. */
10
- apiKey: string;
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
+ theme?: ChatThemeOverride;
18
+ }
19
+ export interface ChatThemeOverride {
20
+ mode?: 'light' | 'dark';
21
+ chatBackground?: string;
22
+ headerBackground?: string;
23
+ headerPrimaryText?: string;
24
+ headerSecondaryText?: string;
25
+ headerIcon?: string;
26
+ closeButton?: string;
27
+ receivedBubble?: string;
28
+ receivedText?: string;
29
+ receivedTimestamp?: string;
30
+ sentBubble?: string;
31
+ sentText?: string;
32
+ sentTimestamp?: string;
33
+ systemMessageText?: string;
34
+ daySeparatorBackground?: string;
35
+ daySeparatorText?: string;
36
+ footerContainer?: string;
37
+ chatInputBackground?: string;
38
+ chatInputText?: string;
39
+ chatInputPlaceholder?: string;
40
+ chatInputBorder?: string;
41
+ sendButtonBackground?: string;
42
+ sendButtonIcon?: string;
43
+ attachmentButton?: string;
44
+ unreadBadgeBackground?: string;
45
+ unreadBadgeText?: string;
17
46
  }
18
47
  export interface ChatUser {
19
48
  customerName: string;
@@ -101,9 +130,12 @@ export interface ChatMessage {
101
130
  totalMessages?: number;
102
131
  }
103
132
  export type NewinstanceChatErrorType = 'network' | 'validation' | 'auth' | 'system';
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';
104
134
  export interface NewinstanceChatError {
135
+ code?: NewinstanceChatErrorCode;
105
136
  type: NewinstanceChatErrorType;
106
137
  message: string;
138
+ developerMessage?: string;
107
139
  recoverable: boolean;
108
140
  }
109
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":"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"}
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.2.0",
4
- "description": "New instance LiveAndAiChat React Native SDK \u2014 AI + live-agent chat, drop-in native chat screen for iOS and Android.",
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
@@ -15,13 +15,17 @@ import type {
15
15
 
16
16
  export type {
17
17
  AttachmentInput,
18
+ AttachmentUpdate,
19
+ ChatClosedEvent,
18
20
  ChatMessage,
21
+ ChatThemeOverride,
19
22
  ChatUser,
20
23
  ConnectionState,
21
24
  EventMap,
22
25
  EventName,
23
26
  NewinstanceChatConfig,
24
27
  NewinstanceChatError,
28
+ NewinstanceChatErrorCode,
25
29
  NewinstanceChatErrorType,
26
30
  } from './types';
27
31
 
@@ -32,6 +36,34 @@ const emitterSource: any =
32
36
  NativeModules.NewinstanceChat ?? (NativeNewinstanceChat as unknown);
33
37
  const emitter = new NativeEventEmitter(emitterSource);
34
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
+
35
67
  /**
36
68
  * React Native binding for the native LiveAndAiChat iOS / Android SDKs.
37
69
  *
@@ -45,9 +77,19 @@ const emitter = new NativeEventEmitter(emitterSource);
45
77
  export class NewinstanceChat {
46
78
  /** Resolves once the native SDK is configured. */
47
79
  readonly ready: Promise<void>;
80
+ private readonly customerTokenMode: boolean;
48
81
 
49
82
  constructor(config: NewinstanceChatConfig) {
50
- this.ready = NativeNewinstanceChat.configure(config);
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
+ );
51
93
  }
52
94
 
53
95
  /**
@@ -56,16 +98,32 @@ export class NewinstanceChat {
56
98
  * once the native instance exists.
57
99
  */
58
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
+ }
59
106
  NativeNewinstanceChat.setUser(user);
60
107
  }
61
108
 
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);
118
+ }
119
+
62
120
  /**
63
121
  * Fetch org config, restore the saved conversation, and connect the
64
122
  * subscription transport in the background. Concurrent callers share
65
123
  * the same in-flight init.
66
124
  */
67
125
  initialize(): Promise<void> {
68
- return NativeNewinstanceChat.initialize();
126
+ return this.ready.then(() => NativeNewinstanceChat.initialize());
69
127
  }
70
128
 
71
129
  /** Present the native chat screen modally over the current RN host. */
@@ -93,7 +151,9 @@ export class NewinstanceChat {
93
151
  */
94
152
  attachFile(opts: AttachmentInput): Promise<string> {
95
153
  if (!opts.source && !opts.uri) {
96
- return Promise.reject(new Error('attachFile requires `source` (or the legacy `uri`)'));
154
+ return Promise.reject(
155
+ new Error('attachFile requires `source` (or the legacy `uri`)')
156
+ );
97
157
  }
98
158
  return NativeNewinstanceChat.attachFile(opts);
99
159
  }
package/src/types.ts CHANGED
@@ -7,14 +7,44 @@
7
7
  */
8
8
 
9
9
  export interface NewinstanceChatConfig {
10
- /** API key id from your newinstance.cloud dashboard. Never embed the secret half. */
11
- apiKey: string;
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
+ theme?: ChatThemeOverride;
19
+ }
20
+
21
+ export interface ChatThemeOverride {
22
+ mode?: 'light' | 'dark';
23
+ chatBackground?: string;
24
+ headerBackground?: string;
25
+ headerPrimaryText?: string;
26
+ headerSecondaryText?: string;
27
+ headerIcon?: string;
28
+ closeButton?: string;
29
+ receivedBubble?: string;
30
+ receivedText?: string;
31
+ receivedTimestamp?: string;
32
+ sentBubble?: string;
33
+ sentText?: string;
34
+ sentTimestamp?: string;
35
+ systemMessageText?: string;
36
+ daySeparatorBackground?: string;
37
+ daySeparatorText?: string;
38
+ footerContainer?: string;
39
+ chatInputBackground?: string;
40
+ chatInputText?: string;
41
+ chatInputPlaceholder?: string;
42
+ chatInputBorder?: string;
43
+ sendButtonBackground?: string;
44
+ sendButtonIcon?: string;
45
+ attachmentButton?: string;
46
+ unreadBadgeBackground?: string;
47
+ unreadBadgeText?: string;
18
48
  }
19
49
 
20
50
  export interface ChatUser {
@@ -139,9 +169,25 @@ export type NewinstanceChatErrorType =
139
169
  | 'auth'
140
170
  | 'system';
141
171
 
172
+ export type NewinstanceChatErrorCode =
173
+ | 'INVALID_PUBLIC_KEY'
174
+ | 'MISSING_WIDGET_KEY'
175
+ | 'CHAT_CONFIG_UNAVAILABLE'
176
+ | 'CHAT_DISABLED'
177
+ | 'CONFIG_FETCH_FAILED'
178
+ | 'INVALID_IDENTITY_TOKEN'
179
+ | 'NETWORK_ERROR'
180
+ | 'TRANSPORT_ERROR'
181
+ | 'CHAT_INITIALIZATION_FAILED'
182
+ | 'MESSAGE_SEND_FAILED'
183
+ | 'ATTACHMENT_FAILED'
184
+ | 'SERVER_ERROR';
185
+
142
186
  export interface NewinstanceChatError {
187
+ code?: NewinstanceChatErrorCode;
143
188
  type: NewinstanceChatErrorType;
144
189
  message: string;
190
+ developerMessage?: string;
145
191
  recoverable: boolean;
146
192
  }
147
193