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.
@@ -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.3.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,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
- apiKey: 'sk_live_…',
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
- The chat screen renders natively — there is no React component to mount.
76
- Your RN UI is whatever button / banner / FAB triggers `openChat()`.
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
- ### Authenticating the customer
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
- `apiKey` is the publishable widget key **ID**, never the `keyId:secret`
81
- form, which belongs on your server only.
81
+ ### JWT mode (`customerToken`), for apps you ship to the public
82
82
 
83
- There are two ways to say who the customer is.
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
- **Anonymous.** Supply nothing. The chat collects whatever it needs.
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
- **Verified.** Have your backend mint a chat identity token and hand it to
88
- the app. The token is the identity, so no other customer fields are needed:
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
- // Your backend, with the SECRET key, typically at sign-in:
92
- // POST https://api.newinstance.cloud/api/v1/chat/sessions
93
- // x-api-key: sk_live_abc123:YOUR_SECRET_KEY
94
- // { "customerId": "usr_123", "customerName": "Ada Lovelace",
95
- // "customerEmail": "ada@example.com", "customerPhone": "+44 20 7946 0958",
96
- // "metadata": { "plan": "enterprise" }, "expiresInSeconds": 21600 }
97
- // -> { token, expiresAt, session: { sessionId, ... } }
98
-
99
- sdk.setUserToken(tokenFromYourBackend);
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
- Everything you put in that payload is signed into the token, so the chat
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
- customerId -> the verified customer id
107
- customerName -> the name on the conversation and on every message
108
- customerEmail -> also satisfies the merchant's "require email" setting
109
- customerPhone -> shown to the agent
110
- metadata -> any extra context your agents should see
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
- The client then passes **one string**. It never repeats the name, never
114
- repeats the email, and is never shown a pre-chat form.
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
- The server verifies the signature and derives the name, email and customer
117
- id from the token's claims, so a tampered app cannot claim to be someone
118
- else. `customerId` passed *without* a token is recorded for agent context
119
- only and is not treated as identity.
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
- Tokens expire (one hour by default). A rejected token arrives on the error
122
- event as `INVALID_IDENTITY_TOKEN`; mint a fresh one.
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
- Sessions are managed over REST: `POST /api/v1/chat/sessions` to create,
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
- A customer holds **one** live session at a time. Creating another while
129
- theirs is valid returns `409` with the existing session; mint again once it
130
- expires or after you revoke it. Revocation takes effect on the next request,
131
- so an invalidated token stops working immediately even though its signature
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
- The token is the session: the identity is signed into it, and the platform
135
- stores no copy. Decode the token if you need the name, email, phone or
136
- metadata back.
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
- apiKey: 'sk_live_…',
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 identity token, or customer data.
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` | The key was rejected: wrong key, wrong environment, revoked, expired |
199
- | `CHAT_CONFIG_UNAVAILABLE` | The key is fine, but the organization has no chat configuration |
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` | The token was malformed, expired, or signed for another organization |
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 — when the native
254
- 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.
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 — failures surface as
282
- `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
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 — there's nothing to wire up from the host.
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 — 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.
318
440
 
319
441
  ## Contributing
320
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.3.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
- if (apiKey.isNullOrBlank()) {
66
- 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")
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 token = if (user.hasKey("customerToken")) user.getString("customerToken") else null
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 — pass sourceType explicitly"
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,
@@ -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
- guard let apiKey = configDict["apiKey"] as? String, !apiKey.isEmpty else {
44
- 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)
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 token = (userDict["customerToken"] as? String).flatMap { $0.isEmpty ? nil : $0 }
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 — pass sourceType explicitly"
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;;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,32 +43,42 @@ 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
  /**
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
- NativeNewinstanceChat.setUser({
48
- customerName: 'Customer',
49
- customerToken
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. */
@@ -1 +1 @@
1
- {"version":3,"names":["NativeEventEmitter","NativeModules","NativeNewinstanceChat","emitterSource","NewinstanceChat","emitter","constructor","config","ready","configure","setUser","user","setUserToken","customerToken","customerName","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;AAyB3D;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;AACA;AACA;AACA;AACA;EACEG,OAAOA,CAACC,IAAc,EAAQ;IAC5BT,qBAAqB,CAACQ,OAAO,CAACC,IAAI,CAAC;EACrC;;EAEA;AACF;AACA;AACA;AACA;EACEC,YAAYA,CAACC,aAAqB,EAAQ;IACxCX,qBAAqB,CAACQ,OAAO,CAAC;MAAEI,YAAY,EAAE,UAAU;MAAED;IAAc,CAAC,CAAC;EAC5E;;EAEA;AACF;AACA;AACA;AACA;EACEE,UAAUA,CAAA,EAAkB;IAC1B,OAAOb,qBAAqB,CAACa,UAAU,CAAC,CAAC;EAC3C;;EAEA;EACAC,QAAQA,CAAA,EAAS;IACfd,qBAAqB,CAACc,QAAQ,CAAC,CAAC;EAClC;;EAEA;EACAC,SAASA,CAAA,EAAS;IAChBf,qBAAqB,CAACe,SAAS,CAAC,CAAC;EACnC;EAEAC,WAAWA,CAACC,IAAY,EAAQ;IAC9BjB,qBAAqB,CAACgB,WAAW,CAACC,IAAI,CAAC;EACzC;EAEAC,YAAYA,CAACC,SAAiB,EAAQ;IACpCnB,qBAAqB,CAACkB,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,OAAO1B,qBAAqB,CAACoB,UAAU,CAACC,IAAI,CAAC;EAC/C;;EAEA;EACAM,gBAAgBA,CAACC,EAAU,EAAQ;IACjC5B,qBAAqB,CAAC2B,gBAAgB,CAACC,EAAE,CAAC;EAC5C;;EAEA;EACAC,gBAAgBA,CAAA,EAAS;IACvB7B,qBAAqB,CAAC6B,gBAAgB,CAAC,CAAC;EAC1C;;EAEA;AACF;AACA;AACA;EACEC,WAAWA,CACTC,KAAQ,EACRC,OAAwB,EACZ;IACZ,MAAMC,GAAwB,GAAG9B,OAAO,CAAC2B,WAAW,CAClDC,KAAK,EACLC,OACF,CAAC;IACD,OAAO,MAAMC,GAAG,CAACC,MAAM,CAAC,CAAC;EAC3B;;EAEA;AACF;AACA;AACA;EACEC,OAAOA,CAAA,EAAS;IACdnC,qBAAqB,CAACmC,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"}
@@ -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;AASjB;;;;;;;;;GASG;AACH,qBAAa,eAAe;IAC1B,kDAAkD;IAClD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;gBAElB,MAAM,EAAE,qBAAqB;IAIzC;;;;;;;;OAQG;IACH,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI;IAI7B;;;;OAIG;IACH,YAAY,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI;IAIzC;;;;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,31 +6,17 @@
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
- /**
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":"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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.3.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
@@ -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
- 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
+ );
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
- * Identify the customer with a backend-minted chat identity token only.
72
- * Nothing else needs to be supplied: the server derives the name, email and
73
- * customer id from the token's signed claims.
74
- */
75
- setUserToken(customerToken: string): void {
76
- NativeNewinstanceChat.setUser({ customerName: 'Customer', customerToken });
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(new Error('attachFile requires `source` (or the legacy `uri`)'));
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
- /** 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
- /**
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