react-native-newinstance-chat 0.2.0 → 0.3.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.3.0"
22
22
 
23
23
  install_modules_dependencies(s)
24
24
  end
package/README.md CHANGED
@@ -75,6 +75,96 @@ sdk.openChat();
75
75
  The chat screen renders natively — there is no React component to mount.
76
76
  Your RN UI is whatever button / banner / FAB triggers `openChat()`.
77
77
 
78
+ ### Authenticating the customer
79
+
80
+ `apiKey` is the publishable widget key **ID**, never the `keyId:secret`
81
+ form, which belongs on your server only.
82
+
83
+ There are two ways to say who the customer is.
84
+
85
+ **Anonymous.** Supply nothing. The chat collects whatever it needs.
86
+
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:
89
+
90
+ ```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);
100
+ ```
101
+
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:
104
+
105
+ ```
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
111
+ ```
112
+
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.
115
+
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.
120
+
121
+ Tokens expire (one hour by default). A rejected token arrives on the error
122
+ event as `INVALID_IDENTITY_TOKEN`; mint a fresh one.
123
+
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`.
127
+
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.
133
+
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.
137
+
138
+
139
+ ### Theming
140
+
141
+ The chat resolves colours from three layers:
142
+
143
+ ```
144
+ dashboard theme > your local theme > the SDK's built-in theme
145
+ ```
146
+
147
+ The merchant's dashboard configuration wins, your local theme fills in what
148
+ the dashboard did not set, and the built-in palette fills in the rest, so
149
+ every colour always has a value, even with no network. Merging is per
150
+ token, so setting one colour does not discard the others.
151
+
152
+ ```ts
153
+ const sdk = new NewinstanceChat({
154
+ apiKey: 'sk_live_…',
155
+ theme: { mode: 'dark', sentBubble: '#16A34A' },
156
+ });
157
+ ```
158
+
159
+ Every `ChatThemeOverride` field is optional; colours are hex strings. An
160
+ unparseable value is ignored rather than throwing. `mode` is only a
161
+ preference: the dashboard's explicit light/dark wins, and its `auto` defers
162
+ to yours, then to the device.
163
+
164
+ If remote configuration cannot be fetched, your local theme still applies
165
+ and the chat stays fully usable; you get a `CONFIG_FETCH_FAILED` warning on
166
+ the error event.
167
+
78
168
  ### Subscribing to events
79
169
 
80
170
  ```ts
@@ -89,6 +179,39 @@ Event names: `messageReceived`, `messageSent`, `agentTypingChanged`,
89
179
  `connectionStateChanged`, `unreadCountChanged`, `error`,
90
180
  `attachmentUpdated`, `chatClosed`. See `src/types.ts` for payload shapes.
91
181
 
182
+ ### The error event
183
+
184
+ `error` is the channel for "why is my chat not working". It never surfaces
185
+ in the chat interface the customer sees, and it never carries the API key,
186
+ the identity token, or customer data.
187
+
188
+ ```ts
189
+ sdk.addListener('error', (e) => {
190
+ if (e.code === 'INVALID_IDENTITY_TOKEN') return refreshChatToken();
191
+ if (!e.recoverable) reportToYourMonitoring(e.code, e.message);
192
+ });
193
+ ```
194
+
195
+ | `code` | What went wrong |
196
+ |---|---|
197
+ | `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 |
200
+ | `CHAT_DISABLED` | Chat is switched off in the dashboard |
201
+ | `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 |
203
+ | `NETWORK_ERROR` | The backend could not be reached |
204
+ | `TRANSPORT_ERROR` | The realtime connection dropped |
205
+ | `CHAT_INITIALIZATION_FAILED` | The conversation could not be started |
206
+ | `MESSAGE_SEND_FAILED` | A message could not be delivered |
207
+ | `ATTACHMENT_FAILED` | An attachment could not be uploaded |
208
+ | `SERVER_ERROR` | The backend returned something unusable |
209
+
210
+ The same codes are emitted by the web, Android, iOS and Flutter SDKs.
211
+ `type` (`network` / `validation` / `auth` / `system`) is still present for
212
+ handlers written against 0.2.x; `code` is `undefined` only when the
213
+ underlying native SDK predates coded errors.
214
+
92
215
  ### The chat-close event
93
216
 
94
217
  `chatClosed` fires **exactly once per chat presentation**, however the
@@ -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.3.0"
77
77
  }
@@ -4,7 +4,10 @@ import cloud.newinstance.liveandaichat.ConnectionState
4
4
  import cloud.newinstance.liveandaichat.LiveAndAiChat
5
5
  import cloud.newinstance.liveandaichat.LiveAndAiChatConfig
6
6
  import cloud.newinstance.liveandaichat.LiveAndAiChatError
7
+ import cloud.newinstance.liveandaichat.ChatSdkError
8
+ import cloud.newinstance.liveandaichat.ChatThemeOverride
7
9
  import cloud.newinstance.liveandaichat.ChatUser
10
+ import cloud.newinstance.liveandaichat.ErrorType
8
11
  import cloud.newinstance.liveandaichat.model.AttachmentRequest
9
12
  import cloud.newinstance.liveandaichat.model.AttachmentSource
10
13
  import cloud.newinstance.liveandaichat.model.ChatCloseEvent
@@ -15,6 +18,7 @@ import com.facebook.react.bridge.Arguments
15
18
  import com.facebook.react.bridge.Promise
16
19
  import com.facebook.react.bridge.ReactApplicationContext
17
20
  import com.facebook.react.bridge.ReadableMap
21
+ import com.facebook.react.bridge.ReadableType
18
22
  import com.facebook.react.bridge.WritableMap
19
23
  import com.facebook.react.modules.core.DeviceEventManagerModule
20
24
 
@@ -39,6 +43,13 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
39
43
  private var sdk: LiveAndAiChat? = null
40
44
  private var pendingUser: ChatUser? = null
41
45
 
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
+ private var usingIdentityToken: Boolean = false
52
+
42
53
  override fun getName(): String = NAME
43
54
 
44
55
  private fun emit(event: String, body: Any?) {
@@ -66,12 +77,27 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
66
77
  }
67
78
  } else null
68
79
 
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
+ val themeOverride = if (config.hasKey("theme")) {
83
+ config.getMap("theme")?.let { map ->
84
+ val values = mutableMapOf<String, String?>()
85
+ val it = map.keySetIterator()
86
+ while (it.hasNextKey()) {
87
+ val key = it.nextKey()
88
+ if (map.getType(key) == ReadableType.String) values[key] = map.getString(key)
89
+ }
90
+ ChatThemeOverride.fromMap(values)
91
+ }
92
+ } else null
93
+
69
94
  try {
70
95
  val cfg = LiveAndAiChatConfig(
71
96
  apiKey = apiKey,
72
97
  baseUrl = baseUrl ?: LiveAndAiChatConfig.DEFAULT_BASE_URL,
73
98
  transport = transport,
74
99
  initialMessage = initialMessage,
100
+ theme = themeOverride,
75
101
  )
76
102
  sdk?.destroy()
77
103
  val builder = LiveAndAiChat.Builder(reactApplicationContext).config(cfg)
@@ -87,13 +113,18 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
87
113
  }
88
114
 
89
115
  override fun setUser(user: ReadableMap) {
90
- val name = user.getString("customerName") ?: return
91
- if (name.isBlank()) return
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)
92
121
  val chatUser = ChatUser(
93
122
  customerName = name,
94
123
  customerId = if (user.hasKey("customerId")) user.getString("customerId") else null,
95
124
  customerEmail = if (user.hasKey("customerEmail")) user.getString("customerEmail") else null,
125
+ customerToken = token,
96
126
  )
127
+ usingIdentityToken = !token.isNullOrBlank()
97
128
  val current = sdk
98
129
  if (current != null) {
99
130
  current.setUser(chatUser)
@@ -240,12 +271,46 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
240
271
 
241
272
  override fun onError(error: LiveAndAiChatError) {
242
273
  val map = Arguments.createMap()
274
+ map.putString("code", sdkErrorCode(error))
243
275
  map.putString("type", error.type.name.lowercase())
244
276
  map.putString("message", error.message)
245
277
  map.putBoolean("recoverable", error.recoverable)
246
278
  emit("error", map)
247
279
  }
248
280
 
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
+ private fun sdkErrorCode(error: LiveAndAiChatError): String {
289
+ val known = setOf(
290
+ ChatSdkError.CHAT_CONFIG_UNAVAILABLE,
291
+ ChatSdkError.CHAT_DISABLED,
292
+ ChatSdkError.INVALID_PUBLIC_KEY,
293
+ ChatSdkError.MISSING_WIDGET_KEY,
294
+ ChatSdkError.CONFIG_FETCH_FAILED,
295
+ ChatSdkError.CHAT_INITIALIZATION_FAILED,
296
+ ChatSdkError.NETWORK_ERROR,
297
+ ChatSdkError.SERVER_ERROR,
298
+ ChatSdkError.INVALID_IDENTITY_TOKEN,
299
+ ChatSdkError.TRANSPORT_ERROR,
300
+ ChatSdkError.MESSAGE_SEND_FAILED,
301
+ ChatSdkError.ATTACHMENT_FAILED,
302
+ )
303
+ error.code?.let { if (it in known) return it }
304
+ return when (error.type) {
305
+ ErrorType.AUTH ->
306
+ if (usingIdentityToken) ChatSdkError.INVALID_IDENTITY_TOKEN else ChatSdkError.INVALID_PUBLIC_KEY
307
+ ErrorType.UNAVAILABLE -> ChatSdkError.CHAT_CONFIG_UNAVAILABLE
308
+ ErrorType.NETWORK -> ChatSdkError.NETWORK_ERROR
309
+ ErrorType.VALIDATION -> ChatSdkError.SERVER_ERROR
310
+ ErrorType.SYSTEM -> ChatSdkError.CHAT_INITIALIZATION_FAILED
311
+ }
312
+ }
313
+
249
314
  // MARK: - Helpers
250
315
 
251
316
  private fun chatMessageToMap(m: ChatMessage): WritableMap {
@@ -29,6 +29,11 @@ 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
+ private var usingIdentityToken: Bool = false
36
+
32
37
  // MARK: - configure / setUser / initialize
33
38
 
34
39
  @objc(configure:resolver:rejecter:)
@@ -49,6 +54,17 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
49
54
  }
50
55
  }()
51
56
 
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
+ let themeOverride: ChatThemeOverride? = {
60
+ guard let raw = configDict["theme"] as? NSDictionary else { return nil }
61
+ var values: [String: String] = [:]
62
+ for (key, value) in raw {
63
+ if let k = key as? String, let v = value as? String { values[k] = v }
64
+ }
65
+ return values.isEmpty ? nil : ChatThemeOverride.fromDictionary(values)
66
+ }()
67
+
52
68
  DispatchQueue.main.async { [weak self] in
53
69
  guard let self = self else { return }
54
70
  do {
@@ -56,7 +72,8 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
56
72
  apiKey: apiKey,
57
73
  baseUrl: baseUrl ?? LiveAndAiChatConfig.defaultBaseUrl,
58
74
  transport: transport,
59
- initialMessage: initialMessage
75
+ initialMessage: initialMessage,
76
+ theme: themeOverride
60
77
  )
61
78
  let builder = LiveAndAiChat.Builder().config(cfg)
62
79
  if let user = self.pendingUser { _ = builder.user(user) }
@@ -76,14 +93,20 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
76
93
 
77
94
  @objc(setUser:)
78
95
  public func setUser(_ userDict: NSDictionary) {
79
- guard let name = userDict["customerName"] as? String, !name.isEmpty else { return }
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 }
80
101
  let user = ChatUser(
81
102
  customerName: name,
82
103
  customerId: userDict["customerId"] as? String,
83
- customerEmail: userDict["customerEmail"] as? String
104
+ customerEmail: userDict["customerEmail"] as? String,
105
+ customerToken: token
84
106
  )
85
107
  DispatchQueue.main.async { [weak self] in
86
108
  guard let self = self else { return }
109
+ self.usingIdentityToken = token != nil
87
110
  if let sdk = self.sdk {
88
111
  sdk.setUser(user)
89
112
  } else {
@@ -247,6 +270,7 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
247
270
  }
248
271
  public func didEncounterError(_ error: LiveAndAiChatError) {
249
272
  emitter?.emitEvent("error", body: [
273
+ "code": error.sdkCode(usingIdentityToken: usingIdentityToken),
250
274
  "type": Self.errorTypeName(error.type),
251
275
  "message": error.message,
252
276
  "recoverable": error.recoverable,
@@ -29,11 +29,27 @@ export class NewinstanceChat {
29
29
  * Identify the customer. Safe to call before or after `initialize()`. If
30
30
  * called before `configure()` resolves, the user is queued and applied
31
31
  * 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.
32
36
  */
33
37
  setUser(user) {
34
38
  NativeNewinstanceChat.setUser(user);
35
39
  }
36
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
+ setUserToken(customerToken) {
47
+ NativeNewinstanceChat.setUser({
48
+ customerName: 'Customer',
49
+ customerToken
50
+ });
51
+ }
52
+
37
53
  /**
38
54
  * Fetch org config, restore the saved conversation, and connect the
39
55
  * subscription transport in the background. Concurrent callers share
@@ -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","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,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
  *
@@ -18,8 +18,18 @@ export declare class NewinstanceChat {
18
18
  * Identify the customer. Safe to call before or after `initialize()`. If
19
19
  * called before `configure()` resolves, the user is queued and applied
20
20
  * 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.
21
25
  */
22
26
  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;
23
33
  /**
24
34
  * Fetch org config, restore the saved conversation, and connect the
25
35
  * 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;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"}
@@ -14,11 +14,66 @@ export interface NewinstanceChatConfig {
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
+ theme?: ChatThemeOverride;
23
+ }
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
+ 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
+ mode?: 'light' | 'dark';
35
+ chatBackground?: string;
36
+ headerBackground?: string;
37
+ headerPrimaryText?: string;
38
+ headerSecondaryText?: string;
39
+ headerIcon?: string;
40
+ closeButton?: string;
41
+ receivedBubble?: string;
42
+ receivedText?: string;
43
+ receivedTimestamp?: string;
44
+ sentBubble?: string;
45
+ sentText?: string;
46
+ sentTimestamp?: string;
47
+ systemMessageText?: string;
48
+ daySeparatorBackground?: string;
49
+ daySeparatorText?: string;
50
+ footerContainer?: string;
51
+ chatInputBackground?: string;
52
+ chatInputText?: string;
53
+ chatInputPlaceholder?: string;
54
+ chatInputBorder?: string;
55
+ sendButtonBackground?: string;
56
+ sendButtonIcon?: string;
57
+ attachmentButton?: string;
58
+ unreadBadgeBackground?: string;
59
+ unreadBadgeText?: string;
17
60
  }
18
61
  export interface ChatUser {
19
62
  customerName: string;
20
63
  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
+ */
21
69
  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;
22
77
  }
23
78
  export type AttachmentSourceType = 'auto' | 'base64' | 'dataUri' | 'file' | 'contentUri' | 'url';
24
79
  /**
@@ -101,9 +156,54 @@ export interface ChatMessage {
101
156
  totalMessages?: number;
102
157
  }
103
158
  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
+ */
104
194
  export interface NewinstanceChatError {
195
+ /** Stable code to branch on. Undefined only from SDK versions older than 0.3.0. */
196
+ code?: NewinstanceChatErrorCode;
197
+ /** Broad category, kept for backwards compatibility with 0.2.x handlers. */
105
198
  type: NewinstanceChatErrorType;
199
+ /** Safe to log and to show a developer. Not written for end users. */
106
200
  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
+ developerMessage?: string;
206
+ /** False when the SDK has given up until the host fixes something. */
107
207
  recoverable: boolean;
108
208
  }
109
209
  /** 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":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,qBAAqB;IACpC,qFAAqF;IACrF,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yGAAyG;IACzG,SAAS,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACzB,iEAAiE;IACjE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,KAAK,CAAC,EAAE,iBAAiB,CAAC;CAC3B;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,QAAQ;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,oBAAoB,GAC5B,MAAM,GACN,QAAQ,GACR,SAAS,GACT,MAAM,GACN,YAAY,GACZ,KAAK,CAAC;AAEV;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,oBAAoB,CAAC;IAClC,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,4GAA4G;IAC5G,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,MAAM,MAAM,gBAAgB,GACxB,QAAQ,GACR,WAAW,GACX,UAAU,GACV,QAAQ,GACR,WAAW,CAAC;AAEhB,kFAAkF;AAClF,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,gBAAgB,CAAC;IACzB,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,eAAe,GACvB,cAAc,GACd,iBAAiB,GACjB,SAAS,GACT,cAAc,GACd,eAAe,GACf,iBAAiB,CAAC;AAEtB;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,eAAe,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;IACnC,yBAAyB;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,MAAM,MAAM,eAAe,GACvB,MAAM,GACN,YAAY,GACZ,WAAW,GACX,cAAc,GACd,SAAS,CAAC;AAEd,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;IACf,8DAA8D;IAC9D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,wBAAwB,GAChC,SAAS,GACT,YAAY,GACZ,MAAM,GACN,QAAQ,CAAC;AAEb;;;;GAIG;AACH,MAAM,MAAM,wBAAwB;AAClC,mFAAmF;AACjF,oBAAoB;AACtB,+BAA+B;GAC7B,oBAAoB;AACtB,8EAA8E;GAC5E,yBAAyB;AAC3B,8EAA8E;GAC5E,eAAe;AACjB,+EAA+E;GAC7E,qBAAqB;AACvB,0FAA0F;GACxF,wBAAwB;AAC1B,oDAAoD;GAClD,eAAe;AACjB,sEAAsE;GACpE,iBAAiB;AACnB,sCAAsC;GACpC,4BAA4B;AAC9B,wCAAwC;GACtC,qBAAqB;AACvB,2CAA2C;GACzC,mBAAmB;AACrB,qEAAqE;GACnE,cAAc,CAAC;AAEnB;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,mFAAmF;IACnF,IAAI,CAAC,EAAE,wBAAwB,CAAC;IAChC,4EAA4E;IAC5E,IAAI,EAAE,wBAAwB,CAAC;IAC/B,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sEAAsE;IACtE,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,4EAA4E;AAC5E,MAAM,WAAW,QAAQ;IACvB,eAAe,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAChD,WAAW,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAC5C,kBAAkB,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,sBAAsB,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;IACzD,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,KAAK,EAAE,CAAC,CAAC,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACzC,iBAAiB,EAAE,CAAC,UAAU,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC1D,UAAU,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC;CAC9C;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC;AACvC,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,SAAS,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-newinstance-chat",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "New instance LiveAndAiChat React Native SDK \u2014 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",
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
 
@@ -54,11 +58,24 @@ export class NewinstanceChat {
54
58
  * Identify the customer. Safe to call before or after `initialize()`. If
55
59
  * called before `configure()` resolves, the user is queued and applied
56
60
  * 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.
57
65
  */
58
66
  setUser(user: ChatUser): void {
59
67
  NativeNewinstanceChat.setUser(user);
60
68
  }
61
69
 
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 });
77
+ }
78
+
62
79
  /**
63
80
  * Fetch org config, restore the saved conversation, and connect the
64
81
  * subscription transport in the background. Concurrent callers share
package/src/types.ts CHANGED
@@ -15,12 +15,68 @@ export interface NewinstanceChatConfig {
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
+ theme?: ChatThemeOverride;
24
+ }
25
+
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
+ 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
+ mode?: 'light' | 'dark';
37
+ chatBackground?: string;
38
+ headerBackground?: string;
39
+ headerPrimaryText?: string;
40
+ headerSecondaryText?: string;
41
+ headerIcon?: string;
42
+ closeButton?: string;
43
+ receivedBubble?: string;
44
+ receivedText?: string;
45
+ receivedTimestamp?: string;
46
+ sentBubble?: string;
47
+ sentText?: string;
48
+ sentTimestamp?: string;
49
+ systemMessageText?: string;
50
+ daySeparatorBackground?: string;
51
+ daySeparatorText?: string;
52
+ footerContainer?: string;
53
+ chatInputBackground?: string;
54
+ chatInputText?: string;
55
+ chatInputPlaceholder?: string;
56
+ chatInputBorder?: string;
57
+ sendButtonBackground?: string;
58
+ sendButtonIcon?: string;
59
+ attachmentButton?: string;
60
+ unreadBadgeBackground?: string;
61
+ unreadBadgeText?: string;
18
62
  }
19
63
 
20
64
  export interface ChatUser {
21
65
  customerName: string;
22
66
  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
+ */
23
72
  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;
24
80
  }
25
81
 
26
82
  export type AttachmentSourceType =
@@ -139,9 +195,55 @@ export type NewinstanceChatErrorType =
139
195
  | 'auth'
140
196
  | 'system';
141
197
 
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
+ export type NewinstanceChatErrorCode =
204
+ /** The API key was rejected: wrong key, wrong environment, revoked, or expired. */
205
+ | 'INVALID_PUBLIC_KEY'
206
+ /** No API key was supplied. */
207
+ | 'MISSING_WIDGET_KEY'
208
+ /** The key is valid but no chat configuration exists for the organization. */
209
+ | 'CHAT_CONFIG_UNAVAILABLE'
210
+ /** The key and config are fine, but chat is switched off in the dashboard. */
211
+ | 'CHAT_DISABLED'
212
+ /** Remote configuration could not be fetched; the built-in theme is in use. */
213
+ | 'CONFIG_FETCH_FAILED'
214
+ /** The chat identity token was malformed, expired, or signed for another organization. */
215
+ | 'INVALID_IDENTITY_TOKEN'
216
+ /** A request could not reach the backend at all. */
217
+ | 'NETWORK_ERROR'
218
+ /** The realtime transport dropped and could not be re-established. */
219
+ | 'TRANSPORT_ERROR'
220
+ /** Starting a conversation failed. */
221
+ | 'CHAT_INITIALIZATION_FAILED'
222
+ /** A message could not be delivered. */
223
+ | 'MESSAGE_SEND_FAILED'
224
+ /** An attachment could not be uploaded. */
225
+ | 'ATTACHMENT_FAILED'
226
+ /** The backend answered, but not with anything the SDK could use. */
227
+ | 'SERVER_ERROR';
228
+
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
+ */
142
234
  export interface NewinstanceChatError {
235
+ /** Stable code to branch on. Undefined only from SDK versions older than 0.3.0. */
236
+ code?: NewinstanceChatErrorCode;
237
+ /** Broad category, kept for backwards compatibility with 0.2.x handlers. */
143
238
  type: NewinstanceChatErrorType;
239
+ /** Safe to log and to show a developer. Not written for end users. */
144
240
  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
+ developerMessage?: string;
246
+ /** False when the SDK has given up until the host fixes something. */
145
247
  recoverable: boolean;
146
248
  }
147
249