react-native-newinstance-chat 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,9 +15,10 @@ Pod::Spec.new do |s|
15
15
 
16
16
  s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
17
17
  s.private_header_files = "ios/**/*.h"
18
+ s.pod_target_xcconfig = { "DEFINES_MODULE" => "YES" }
18
19
 
19
20
  # Native chat SDK we wrap, published on CocoaPods Trunk.
20
- s.dependency "LiveAndAiChat", "~> 0.1"
21
+ s.dependency "LiveAndAiChat", "~> 0.2.0"
21
22
 
22
23
  install_modules_dependencies(s)
23
24
  end
package/README.md CHANGED
@@ -32,6 +32,16 @@ cd ios && pod install
32
32
  Autolink pulls the `NewinstanceChat` podspec, which transitively depends
33
33
  on `LiveAndAiChat` (the native SDK pod, published on CocoaPods Trunk).
34
34
 
35
+ Both default static-library Podfiles and `use_frameworks!` (any linkage)
36
+ are supported. If you see `'NewinstanceChat-Swift.h' file not found` you
37
+ are on 0.1.0, which only resolved the generated Swift header in
38
+ static-library builds. Upgrade to 0.1.1 or later, then reinstall pods:
39
+
40
+ ```sh
41
+ npm install react-native-newinstance-chat@latest
42
+ cd ios && pod install
43
+ ```
44
+
35
45
  ### Android
36
46
 
37
47
  Autolink registers `NewinstanceChatPackage`. The underlying
@@ -54,6 +64,10 @@ sdk.setUser({
54
64
 
55
65
  await sdk.initialize();
56
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
+ await sdk.ready;
70
+
57
71
  // Later, in response to a host button tap:
58
72
  sdk.openChat();
59
73
  ```
@@ -72,17 +86,101 @@ unsub();
72
86
  ```
73
87
 
74
88
  Event names: `messageReceived`, `messageSent`, `agentTypingChanged`,
75
- `connectionStateChanged`, `unreadCountChanged`, `error`. See `src/types.ts`
76
- for payload shapes.
89
+ `connectionStateChanged`, `unreadCountChanged`, `error`,
90
+ `attachmentUpdated`, `chatClosed`. See `src/types.ts` for payload shapes.
91
+
92
+ ### The chat-close event
93
+
94
+ `chatClosed` fires **exactly once per chat presentation**, however the
95
+ screen went away: the in-chat close button, Android back navigation
96
+ (button or gesture), an iOS dismissal gesture, `closeChat()`,
97
+ `destroy()` (reason `session_ended`), or the host app navigating the
98
+ screen away. Rerenders and lifecycle churn never produce duplicates.
99
+
100
+ ```ts
101
+ useEffect(() => {
102
+ const unsub = sdk.addListener('chatClosed', (e) => {
103
+ // e.reason: 'close_button' | 'back_navigation' | 'gesture' |
104
+ // 'programmatic' | 'session_ended' | 'host_navigation'
105
+ // e.initiator: 'user' | 'host' | 'sdk'
106
+ // plus: timestamp, conversationId, assignmentId, channel,
107
+ // previousState, unreadCount, hasDraft,
108
+ // pendingAttachmentCount, metadata
109
+ analytics.track('chat_closed', { reason: e.reason });
110
+ });
111
+ return unsub; // always clean up on unmount
112
+ }, []);
113
+ ```
114
+
115
+ Ordering: `chatClosed` is emitted after the SDK has left the open
116
+ state (`unreadCount` and `pendingAttachmentCount` reflect the moment of
117
+ dismissal). The event never contains message contents, credentials, or
118
+ attachment bytes.
77
119
 
78
120
  ### Sending messages programmatically
79
121
 
80
122
  ```ts
81
123
  sdk.sendMessage('Hi, I have a question.');
82
- sdk.attachFile({ uri: 'file:///…', name: 'screenshot.png', mimeType: 'image/png' });
83
124
  sdk.retryMessage(failedMessageId);
84
125
  ```
85
126
 
127
+ ### Programmatic attachments
128
+
129
+ `attachFile` accepts base64, data URIs, local file paths,
130
+ `content://` / `file://` URIs (picker outputs), and — when the native
131
+ SDK is configured with `allowRemoteAttachmentUrls` — remote URLs.
132
+ `sourceType: 'auto'` (the default) safely detects string inputs.
133
+
134
+ ```ts
135
+ // Base64 (e.g. from a camera or canvas):
136
+ const id = await sdk.attachFile({
137
+ source: pngBase64,
138
+ sourceType: 'base64',
139
+ name: 'photo.png',
140
+ mimeType: 'image/png',
141
+ });
142
+
143
+ // React Native document picker result:
144
+ const [doc] = await pick(); // e.g. @react-native-documents/picker
145
+ await sdk.attachFile({
146
+ source: doc.uri, // content:// on Android, file:// on iOS
147
+ name: doc.name ?? 'document.pdf',
148
+ mimeType: doc.type ?? 'application/pdf',
149
+ size: doc.size ?? undefined,
150
+ });
151
+
152
+ // Data URI:
153
+ await sdk.attachFile({ source: dataUri, name: 'chart.png' });
154
+ ```
155
+
156
+ The returned promise resolves with the attachment **id** as soon as the
157
+ request is accepted and queued. Validation and upload run in the
158
+ background and never block the UI thread — failures surface as
159
+ `attachmentUpdated` with `status: 'failed'` plus an `error` event — and
160
+ the message is never sent until the upload succeeds (send drains only
161
+ uploaded attachments). Track progress and results via
162
+ `attachmentUpdated`:
163
+
164
+ ```ts
165
+ sdk.addListener('attachmentUpdated', (a) => {
166
+ // a.status: 'queued' | 'uploading' | 'uploaded' | 'failed' | 'cancelled'
167
+ // a.progress: 0..1 real byte progress
168
+ if (a.status === 'failed') console.warn(a.error);
169
+ });
170
+
171
+ sdk.removeAttachment(id); // cancels an in-flight upload
172
+ sdk.clearAttachments();
173
+ ```
174
+
175
+ Validation happens before anything is queued: base64 must decode, URIs
176
+ must be readable, files must be non-empty and at most 25 MB, the MIME
177
+ type must be one of png/jpeg/webp/gif/pdf, a mismatched `size` is
178
+ rejected as corruption, and the extension must agree with the MIME
179
+ type. Attachment contents are never written to logs.
180
+
181
+ Allowed MIME types and the 25 MB cap mirror the web widget; keys and
182
+ limits are enforced again server-side.
183
+
86
184
  Handoff to a live agent and typing indicators are driven automatically
87
185
  by the native chat screen — there's nothing to wire up from the host.
88
186
 
@@ -65,15 +65,13 @@ android {
65
65
  repositories {
66
66
  google()
67
67
  mavenCentral()
68
- // Dev fallback: pick up `cloud.newinstance:liveandaichat:0.1.0` from
69
- // ~/.m2/repository if the developer ran `./gradlew :sdk:publishToMavenLocal`
70
- // in liveAndAiChat-android/ but the Maven Central publish hasn't
71
- // landed yet. Remove this once the SDK is live on Maven Central.
68
+ // Dev fallback for local native SDK work after publishing
69
+ // liveAndAiChat-android with `./gradlew :sdk:publishToMavenLocal`.
72
70
  mavenLocal()
73
71
  }
74
72
 
75
73
  dependencies {
76
74
  implementation "com.facebook.react:react-android"
77
75
  // Native chat SDK the bridge wraps. Published under cloud.newinstance.
78
- implementation "cloud.newinstance:liveandaichat:0.1.0"
76
+ implementation "cloud.newinstance:liveandaichat:0.2.0"
79
77
  }
@@ -5,8 +5,12 @@ import cloud.newinstance.liveandaichat.LiveAndAiChat
5
5
  import cloud.newinstance.liveandaichat.LiveAndAiChatConfig
6
6
  import cloud.newinstance.liveandaichat.LiveAndAiChatError
7
7
  import cloud.newinstance.liveandaichat.ChatUser
8
+ import cloud.newinstance.liveandaichat.model.AttachmentRequest
9
+ import cloud.newinstance.liveandaichat.model.AttachmentSource
10
+ import cloud.newinstance.liveandaichat.model.ChatCloseEvent
8
11
  import cloud.newinstance.liveandaichat.model.ChatMessage
9
12
  import cloud.newinstance.liveandaichat.model.TransportMode
13
+ import cloud.newinstance.liveandaichat.upload.QueuedAttachment
10
14
  import com.facebook.react.bridge.Arguments
11
15
  import com.facebook.react.bridge.Promise
12
16
  import com.facebook.react.bridge.ReactApplicationContext
@@ -124,24 +128,57 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
124
128
  promise.reject("E_NOT_CONFIGURED", "configure() must be called first")
125
129
  return
126
130
  }
127
- val uri = opts.getString("uri")
128
131
  val name = opts.getString("name")
129
- val mimeType = opts.getString("mimeType")
130
- if (uri == null || name == null || mimeType == null) {
131
- promise.reject("E_ARGS", "uri, name, mimeType are required")
132
+ if (name.isNullOrBlank()) {
133
+ promise.reject("E_ARGS", "name is required")
134
+ return
135
+ }
136
+ val sourceValue = opts.getString("source") ?: opts.getString("uri")
137
+ if (sourceValue.isNullOrBlank()) {
138
+ promise.reject("E_ARGS", "source (or legacy uri) is required")
139
+ return
140
+ }
141
+ val sourceType = opts.getString("sourceType") ?: "auto"
142
+ val source = AttachmentSource.fromTypeName(sourceType, sourceValue)
143
+ if (source == null) {
144
+ promise.reject(
145
+ "E_SOURCE",
146
+ if (sourceType == "auto") "Could not detect the attachment source type — pass sourceType explicitly"
147
+ else "Unknown sourceType: $sourceType",
148
+ )
132
149
  return
133
150
  }
151
+ val mimeType = opts.getString("mimeType")
152
+ val declaredSize = if (opts.hasKey("size") && !opts.isNull("size")) opts.getDouble("size").toLong() else null
153
+ val metadata = mutableMapOf<String, String>()
154
+ if (opts.hasKey("metadata") && !opts.isNull("metadata")) {
155
+ val map = opts.getMap("metadata")
156
+ map?.entryIterator?.forEach { (key, value) -> metadata[key] = value.toString() }
157
+ }
134
158
  try {
135
- val bytes = reactApplicationContext.contentResolver
136
- .openInputStream(android.net.Uri.parse(uri))?.use { it.readBytes() }
137
- ?: throw java.io.IOException("Could not open content URI: $uri")
138
- current.attachFile(bytes, name, mimeType, uri)
139
- promise.resolve(null)
159
+ val id = current.attach(
160
+ AttachmentRequest(
161
+ source = source,
162
+ name = name,
163
+ mimeType = mimeType,
164
+ declaredSize = declaredSize,
165
+ metadata = metadata,
166
+ ),
167
+ )
168
+ promise.resolve(id)
140
169
  } catch (e: Exception) {
141
- promise.reject("E_READ", e.message, e)
170
+ promise.reject("E_ATTACH", e.message, e)
142
171
  }
143
172
  }
144
173
 
174
+ override fun removeAttachment(id: String) {
175
+ sdk?.removeAttachment(id)
176
+ }
177
+
178
+ override fun clearAttachments() {
179
+ sdk?.clearAttachments()
180
+ }
181
+
145
182
  override fun destroy() {
146
183
  sdk?.destroy()
147
184
  sdk = null
@@ -170,6 +207,37 @@ class NewinstanceChatModule(reactContext: ReactApplicationContext) :
170
207
  override fun onUnreadCountChange(count: Int) {
171
208
  emit("unreadCountChanged", count)
172
209
  }
210
+ override fun onAttachmentUpdate(attachment: QueuedAttachment) {
211
+ val map = Arguments.createMap()
212
+ map.putString("id", attachment.id)
213
+ map.putString("name", attachment.name)
214
+ map.putString("mimeType", attachment.mimeType)
215
+ map.putDouble("size", attachment.size.toDouble())
216
+ map.putString("status", attachment.status.name.lowercase())
217
+ map.putDouble("progress", attachment.progress.toDouble())
218
+ attachment.publicUrl?.let { map.putString("url", it) }
219
+ attachment.errorReason?.let { map.putString("error", it) }
220
+ emit("attachmentUpdated", map)
221
+ }
222
+
223
+ override fun onChatClosed(event: ChatCloseEvent) {
224
+ val map = Arguments.createMap()
225
+ map.putString("reason", event.reason.wire)
226
+ map.putString("initiator", event.initiator.wire)
227
+ map.putDouble("timestamp", event.timestamp.toDouble())
228
+ event.conversationId?.let { map.putString("conversationId", it) }
229
+ event.assignmentId?.let { map.putString("assignmentId", it) }
230
+ map.putString("channel", event.channel)
231
+ map.putString("previousState", event.previousState)
232
+ map.putInt("unreadCount", event.unreadCount)
233
+ map.putBoolean("hasDraft", event.hasDraft)
234
+ map.putInt("pendingAttachmentCount", event.pendingAttachmentCount)
235
+ val meta = Arguments.createMap()
236
+ event.metadata.forEach { (k, v) -> meta.putString(k, v) }
237
+ map.putMap("metadata", meta)
238
+ emit("chatClosed", map)
239
+ }
240
+
173
241
  override fun onError(error: LiveAndAiChatError) {
174
242
  val map = Arguments.createMap()
175
243
  map.putString("type", error.type.name.lowercase())
@@ -4,8 +4,15 @@
4
4
 
5
5
  // Auto-generated Swift bridging header for this pod (created at build
6
6
  // time by Xcode/CocoaPods when the pod mixes Obj-C and Swift). The pod
7
- // name is `NewinstanceChat`, hence the header name.
7
+ // name is `NewinstanceChat`, hence the header name. Framework builds
8
+ // (`use_frameworks!`) place it inside the framework, reachable only via
9
+ // the angle-bracket path; static-library builds emit it to derived
10
+ // sources, reachable only via the quote form, so probe for both.
11
+ #if __has_include(<NewinstanceChat/NewinstanceChat-Swift.h>)
12
+ #import <NewinstanceChat/NewinstanceChat-Swift.h>
13
+ #else
8
14
  #import "NewinstanceChat-Swift.h"
15
+ #endif
9
16
 
10
17
  // Declare conformance to NewinstanceChatEmitter (defined in
11
18
  // NewinstanceChatImpl.swift) so the Swift impl can hold a typed weak
@@ -69,6 +76,16 @@ RCT_EXPORT_MODULE()
69
76
  [_impl attachFile:opts resolver:resolve rejecter:reject];
70
77
  }
71
78
 
79
+ - (void)removeAttachment:(NSString *)attachmentId
80
+ {
81
+ [_impl removeAttachment:attachmentId];
82
+ }
83
+
84
+ - (void)clearAttachments
85
+ {
86
+ [_impl clearAttachments];
87
+ }
88
+
72
89
  // NativeEventEmitter wires these so it can track subscriber counts. The
73
90
  // actual event payloads flow through RCTDeviceEventEmitter, so these are
74
91
  // intentional no-ops.
@@ -140,24 +140,51 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
140
140
  rejecter("E_NOT_CONFIGURED", "configure() must be called first", nil)
141
141
  return
142
142
  }
143
- guard let uri = opts["uri"] as? String,
144
- let name = opts["name"] as? String,
145
- let mimeType = opts["mimeType"] as? String,
146
- let url = URL(string: uri) else {
147
- rejecter("E_ARGS", "uri, name, mimeType are required", nil)
143
+ guard let name = opts["name"] as? String, !name.isEmpty else {
144
+ rejecter("E_ARGS", "name is required", nil)
148
145
  return
149
146
  }
150
- DispatchQueue.global(qos: .userInitiated).async {
151
- do {
152
- let data = try Data(contentsOf: url)
153
- DispatchQueue.main.async {
154
- sdk.attachFile(data: data, name: name, mimeType: mimeType, previewUri: uri)
155
- resolver(nil)
156
- }
157
- } catch {
158
- rejecter("E_READ", "Failed to read file: \(error.localizedDescription)", error)
159
- }
147
+ guard let sourceValue = (opts["source"] as? String) ?? (opts["uri"] as? String), !sourceValue.isEmpty else {
148
+ rejecter("E_ARGS", "source (or legacy uri) is required", nil)
149
+ return
150
+ }
151
+ let sourceType = (opts["sourceType"] as? String) ?? "auto"
152
+ guard let source = AttachmentSource.fromTypeName(sourceType, value: sourceValue) else {
153
+ rejecter(
154
+ "E_SOURCE",
155
+ sourceType == "auto"
156
+ ? "Could not detect the attachment source type — pass sourceType explicitly"
157
+ : "Unknown sourceType: \(sourceType)",
158
+ nil
159
+ )
160
+ return
160
161
  }
162
+ let mimeType = opts["mimeType"] as? String
163
+ let declaredSize = (opts["size"] as? NSNumber)?.int64Value
164
+ var metadata: [String: String] = [:]
165
+ if let raw = opts["metadata"] as? [String: Any] {
166
+ for (key, value) in raw { metadata[key] = String(describing: value) }
167
+ }
168
+ let request = AttachmentRequest(
169
+ source: source,
170
+ name: name,
171
+ mimeType: mimeType,
172
+ declaredSize: declaredSize,
173
+ metadata: metadata
174
+ )
175
+ DispatchQueue.main.async {
176
+ let id = sdk.attach(request)
177
+ resolver(id)
178
+ }
179
+ }
180
+
181
+ @objc(removeAttachment:)
182
+ public func removeAttachment(_ id: String) {
183
+ DispatchQueue.main.async { [weak self] in self?.sdk?.removeAttachment(id: id) }
184
+ }
185
+
186
+ @objc public func clearAttachments() {
187
+ DispatchQueue.main.async { [weak self] in self?.sdk?.clearAttachments() }
161
188
  }
162
189
 
163
190
  @objc public func destroy() {
@@ -171,6 +198,41 @@ public final class NewinstanceChatImpl: NSObject, LiveAndAiChatDelegate {
171
198
 
172
199
  // MARK: - LiveAndAiChatDelegate
173
200
 
201
+ public func unreadCountDidChange(_ count: Int) {
202
+ emitter?.emitEvent("unreadCountChanged", body: count)
203
+ }
204
+
205
+ public func attachmentDidUpdate(_ attachment: QueuedAttachment) {
206
+ var body: [String: Any] = [
207
+ "id": attachment.id,
208
+ "name": attachment.name,
209
+ "mimeType": attachment.mimeType,
210
+ "size": attachment.size,
211
+ "status": attachment.status.rawValue,
212
+ "progress": attachment.progress,
213
+ ]
214
+ if let url = attachment.publicUrl { body["url"] = url }
215
+ if let error = attachment.errorReason { body["error"] = error }
216
+ emitter?.emitEvent("attachmentUpdated", body: body)
217
+ }
218
+
219
+ public func chatDidClose(_ event: ChatCloseEvent) {
220
+ var body: [String: Any] = [
221
+ "reason": event.reason.rawValue,
222
+ "initiator": event.initiator.rawValue,
223
+ "timestamp": event.timestamp,
224
+ "channel": event.channel,
225
+ "previousState": event.previousState,
226
+ "unreadCount": event.unreadCount,
227
+ "hasDraft": event.hasDraft,
228
+ "pendingAttachmentCount": event.pendingAttachmentCount,
229
+ "metadata": event.metadata,
230
+ ]
231
+ if let id = event.conversationId { body["conversationId"] = id }
232
+ if let id = event.assignmentId { body["assignmentId"] = id }
233
+ emitter?.emitEvent("chatClosed", body: body)
234
+ }
235
+
174
236
  public func didReceiveMessage(_ message: ChatMessage) {
175
237
  emitter?.emitEvent("messageReceived", body: Self.chatMessageToDict(message))
176
238
  }
@@ -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;;AAkBA,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;;AAoBA,eAAeA,mBAAmB,CAACC,YAAY,CAAO,iBAAiB,CAAC","ignoreList":[]}
@@ -65,9 +65,22 @@ export class NewinstanceChat {
65
65
  * uploaded reference into the next `sendMessage()`.
66
66
  */
67
67
  attachFile(opts) {
68
+ if (!opts.source && !opts.uri) {
69
+ return Promise.reject(new Error('attachFile requires `source` (or the legacy `uri`)'));
70
+ }
68
71
  return NativeNewinstanceChat.attachFile(opts);
69
72
  }
70
73
 
74
+ /** Remove a queued attachment; cancels its upload if still in flight. */
75
+ removeAttachment(id) {
76
+ NativeNewinstanceChat.removeAttachment(id);
77
+ }
78
+
79
+ /** Remove every queued attachment and cancel in-flight uploads. */
80
+ clearAttachments() {
81
+ NativeNewinstanceChat.clearAttachments();
82
+ }
83
+
71
84
  /**
72
85
  * Subscribe to a native event. Returns an unsubscribe function — call it
73
86
  * when your component unmounts to avoid leaks.
@@ -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","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,EAAiB;IAC/C,OAAOlB,qBAAqB,CAACiB,UAAU,CAACC,IAAI,CAAC;EAC/C;;EAEA;AACF;AACA;AACA;EACEC,WAAWA,CACTC,KAAQ,EACRC,OAAwB,EACZ;IACZ,MAAMC,GAAwB,GAAGnB,OAAO,CAACgB,WAAW,CAClDC,KAAK,EACLC,OACF,CAAC;IACD,OAAO,MAAMC,GAAG,CAACC,MAAM,CAAC,CAAC;EAC3B;;EAEA;AACF;AACA;AACA;EACEC,OAAOA,CAAA,EAAS;IACdxB,qBAAqB,CAACwB,OAAO,CAAC,CAAC;EACjC;AACF","ignoreList":[]}
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":[]}
@@ -17,7 +17,9 @@ export interface Spec extends TurboModule {
17
17
  closeChat(): void;
18
18
  sendMessage(text: string): void;
19
19
  retryMessage(messageId: string): void;
20
- attachFile(opts: Object): Promise<void>;
20
+ attachFile(opts: Object): Promise<string>;
21
+ removeAttachment(id: string): void;
22
+ clearAttachments(): void;
21
23
  destroy(): void;
22
24
  addListener(eventName: string): void;
23
25
  removeListeners(count: number): 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,IAAI,CAAC,CAAC;IACxC,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,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"}
@@ -37,7 +37,11 @@ export declare class NewinstanceChat {
37
37
  * `uri`, pushes them through the SDK's upload pipeline, and bundles the
38
38
  * uploaded reference into the next `sendMessage()`.
39
39
  */
40
- attachFile(opts: AttachmentInput): Promise<void>;
40
+ attachFile(opts: AttachmentInput): Promise<string>;
41
+ /** Remove a queued attachment; cancels its upload if still in flight. */
42
+ removeAttachment(id: string): void;
43
+ /** Remove every queued attachment and cancel in-flight uploads. */
44
+ clearAttachments(): void;
41
45
  /**
42
46
  * Subscribe to a native event. Returns an unsubscribe function — call it
43
47
  * when your component unmounts to avoid leaks.
@@ -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,IAAI,CAAC;IAIhD;;;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,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"}
@@ -20,12 +20,63 @@ export interface ChatUser {
20
20
  customerEmail?: string;
21
21
  customerId?: string;
22
22
  }
23
- /** A local file the native side reads bytes from. */
23
+ export type AttachmentSourceType = 'auto' | 'base64' | 'dataUri' | 'file' | 'contentUri' | 'url';
24
+ /**
25
+ * A programmatic attachment. Provide `source` + optional `sourceType`
26
+ * (`'auto'` detects data URIs, `content://`/`file://` URIs, absolute
27
+ * paths, http(s) URLs — remote URLs upload only when the native SDK is
28
+ * configured with `allowRemoteAttachmentUrls` — and raw base64).
29
+ * The legacy `uri` field keeps working and behaves like
30
+ * `source` + `sourceType: 'auto'`.
31
+ */
24
32
  export interface AttachmentInput {
25
- /** Local file URI: `file://...` (iOS) or `content://...` / `file://...` (Android). */
26
- uri: string;
33
+ /** Attachment data or locator, interpreted per `sourceType`. */
34
+ source?: string;
35
+ sourceType?: AttachmentSourceType;
36
+ /** @deprecated Use `source`. Local file URI, still fully supported. */
37
+ uri?: string;
38
+ name: string;
39
+ /** Required unless the source itself carries one (data URIs, content URIs) or the extension resolves it. */
40
+ mimeType?: string;
41
+ /** Expected byte size — validated against the resolved data as a corruption guard. */
42
+ size?: number;
43
+ /** Free-form string metadata carried on the queued attachment. */
44
+ metadata?: Record<string, string>;
45
+ }
46
+ export type AttachmentStatus = 'queued' | 'uploading' | 'uploaded' | 'failed' | 'cancelled';
47
+ /** Streamed on `attachmentUpdated` for every attachment state/progress change. */
48
+ export interface AttachmentUpdate {
49
+ id: string;
27
50
  name: string;
28
51
  mimeType: string;
52
+ size: number;
53
+ status: AttachmentStatus;
54
+ /** 0..1 — real byte progress while uploading. */
55
+ progress: number;
56
+ /** Public URL once `status === 'uploaded'`. */
57
+ url?: string;
58
+ /** Failure reason once `status === 'failed'`. */
59
+ error?: string;
60
+ }
61
+ export type ChatCloseReason = 'close_button' | 'back_navigation' | 'gesture' | 'programmatic' | 'session_ended' | 'host_navigation';
62
+ /**
63
+ * Fired exactly once per chat presentation when the chat screen goes
64
+ * away, however it was dismissed. Never contains message contents,
65
+ * credentials, or attachment data.
66
+ */
67
+ export interface ChatClosedEvent {
68
+ reason: ChatCloseReason;
69
+ initiator: 'user' | 'host' | 'sdk';
70
+ /** Unix milliseconds. */
71
+ timestamp: number;
72
+ conversationId?: string;
73
+ assignmentId?: string;
74
+ channel: string;
75
+ previousState: string;
76
+ unreadCount: number;
77
+ hasDraft: boolean;
78
+ pendingAttachmentCount: number;
79
+ metadata: Record<string, string>;
29
80
  }
30
81
  export type ConnectionState = 'idle' | 'connecting' | 'connected' | 'disconnected' | 'offline';
31
82
  export interface ChatMessage {
@@ -63,6 +114,8 @@ export interface EventMap {
63
114
  connectionStateChanged: (state: ConnectionState) => void;
64
115
  unreadCountChanged: (count: number) => void;
65
116
  error: (e: NewinstanceChatError) => void;
117
+ attachmentUpdated: (attachment: AttachmentUpdate) => void;
118
+ chatClosed: (event: ChatClosedEvent) => void;
66
119
  }
67
120
  export type EventName = keyof EventMap;
68
121
  export type EventHandler<K extends EventName> = EventMap[K];
@@ -1 +1 @@
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;CACzB;AAED,MAAM,WAAW,QAAQ;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qDAAqD;AACrD,MAAM,WAAW,eAAe;IAC9B,sFAAsF;IACtF,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;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,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,wBAAwB,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,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;CAC1C;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC;AACvC,MAAM,MAAM,YAAY,CAAC,CAAC,SAAS,SAAS,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC"}
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.1.0",
4
- "description": "New instance LiveAndAiChat React Native SDK — AI + live-agent chat, drop-in native chat screen for iOS and Android.",
3
+ "version": "0.2.0",
4
+ "description": "New instance LiveAndAiChat React Native SDK \u2014 AI + live-agent chat, drop-in native chat screen for iOS and Android.",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
7
7
  "exports": {
@@ -130,7 +130,9 @@
130
130
  "preset": "@react-native/jest-preset",
131
131
  "modulePathIgnorePatterns": [
132
132
  "<rootDir>/example/node_modules",
133
- "<rootDir>/lib/"
133
+ "<rootDir>/lib/",
134
+ "<rootDir>/example/ios",
135
+ "<rootDir>/example/android"
134
136
  ]
135
137
  },
136
138
  "release-it": {
@@ -162,5 +164,9 @@
162
164
  "vite"
163
165
  ],
164
166
  "version": "0.62.0"
167
+ },
168
+ "resolutions": {
169
+ "jest-environment-node": "^30.4.1",
170
+ "jest-mock": "^30.4.1"
165
171
  }
166
172
  }
@@ -18,7 +18,9 @@ export interface Spec extends TurboModule {
18
18
  closeChat(): void;
19
19
  sendMessage(text: string): void;
20
20
  retryMessage(messageId: string): void;
21
- attachFile(opts: Object): Promise<void>;
21
+ attachFile(opts: Object): Promise<string>;
22
+ removeAttachment(id: string): void;
23
+ clearAttachments(): void;
22
24
  destroy(): void;
23
25
 
24
26
  // NativeEventEmitter on iOS calls these to track listener counts.
package/src/index.tsx CHANGED
@@ -91,10 +91,23 @@ export class NewinstanceChat {
91
91
  * `uri`, pushes them through the SDK's upload pipeline, and bundles the
92
92
  * uploaded reference into the next `sendMessage()`.
93
93
  */
94
- attachFile(opts: AttachmentInput): Promise<void> {
94
+ attachFile(opts: AttachmentInput): Promise<string> {
95
+ if (!opts.source && !opts.uri) {
96
+ return Promise.reject(new Error('attachFile requires `source` (or the legacy `uri`)'));
97
+ }
95
98
  return NativeNewinstanceChat.attachFile(opts);
96
99
  }
97
100
 
101
+ /** Remove a queued attachment; cancels its upload if still in flight. */
102
+ removeAttachment(id: string): void {
103
+ NativeNewinstanceChat.removeAttachment(id);
104
+ }
105
+
106
+ /** Remove every queued attachment and cancel in-flight uploads. */
107
+ clearAttachments(): void {
108
+ NativeNewinstanceChat.clearAttachments();
109
+ }
110
+
98
111
  /**
99
112
  * Subscribe to a native event. Returns an unsubscribe function — call it
100
113
  * when your component unmounts to avoid leaks.
package/src/types.ts CHANGED
@@ -23,12 +23,85 @@ export interface ChatUser {
23
23
  customerId?: string;
24
24
  }
25
25
 
26
- /** A local file the native side reads bytes from. */
26
+ export type AttachmentSourceType =
27
+ | 'auto'
28
+ | 'base64'
29
+ | 'dataUri'
30
+ | 'file'
31
+ | 'contentUri'
32
+ | 'url';
33
+
34
+ /**
35
+ * A programmatic attachment. Provide `source` + optional `sourceType`
36
+ * (`'auto'` detects data URIs, `content://`/`file://` URIs, absolute
37
+ * paths, http(s) URLs — remote URLs upload only when the native SDK is
38
+ * configured with `allowRemoteAttachmentUrls` — and raw base64).
39
+ * The legacy `uri` field keeps working and behaves like
40
+ * `source` + `sourceType: 'auto'`.
41
+ */
27
42
  export interface AttachmentInput {
28
- /** Local file URI: `file://...` (iOS) or `content://...` / `file://...` (Android). */
29
- uri: string;
43
+ /** Attachment data or locator, interpreted per `sourceType`. */
44
+ source?: string;
45
+ sourceType?: AttachmentSourceType;
46
+ /** @deprecated Use `source`. Local file URI, still fully supported. */
47
+ uri?: string;
48
+ name: string;
49
+ /** Required unless the source itself carries one (data URIs, content URIs) or the extension resolves it. */
50
+ mimeType?: string;
51
+ /** Expected byte size — validated against the resolved data as a corruption guard. */
52
+ size?: number;
53
+ /** Free-form string metadata carried on the queued attachment. */
54
+ metadata?: Record<string, string>;
55
+ }
56
+
57
+ export type AttachmentStatus =
58
+ | 'queued'
59
+ | 'uploading'
60
+ | 'uploaded'
61
+ | 'failed'
62
+ | 'cancelled';
63
+
64
+ /** Streamed on `attachmentUpdated` for every attachment state/progress change. */
65
+ export interface AttachmentUpdate {
66
+ id: string;
30
67
  name: string;
31
68
  mimeType: string;
69
+ size: number;
70
+ status: AttachmentStatus;
71
+ /** 0..1 — real byte progress while uploading. */
72
+ progress: number;
73
+ /** Public URL once `status === 'uploaded'`. */
74
+ url?: string;
75
+ /** Failure reason once `status === 'failed'`. */
76
+ error?: string;
77
+ }
78
+
79
+ export type ChatCloseReason =
80
+ | 'close_button'
81
+ | 'back_navigation'
82
+ | 'gesture'
83
+ | 'programmatic'
84
+ | 'session_ended'
85
+ | 'host_navigation';
86
+
87
+ /**
88
+ * Fired exactly once per chat presentation when the chat screen goes
89
+ * away, however it was dismissed. Never contains message contents,
90
+ * credentials, or attachment data.
91
+ */
92
+ export interface ChatClosedEvent {
93
+ reason: ChatCloseReason;
94
+ initiator: 'user' | 'host' | 'sdk';
95
+ /** Unix milliseconds. */
96
+ timestamp: number;
97
+ conversationId?: string;
98
+ assignmentId?: string;
99
+ channel: string;
100
+ previousState: string;
101
+ unreadCount: number;
102
+ hasDraft: boolean;
103
+ pendingAttachmentCount: number;
104
+ metadata: Record<string, string>;
32
105
  }
33
106
 
34
107
  export type ConnectionState =
@@ -80,6 +153,8 @@ export interface EventMap {
80
153
  connectionStateChanged: (state: ConnectionState) => void;
81
154
  unreadCountChanged: (count: number) => void;
82
155
  error: (e: NewinstanceChatError) => void;
156
+ attachmentUpdated: (attachment: AttachmentUpdate) => void;
157
+ chatClosed: (event: ChatClosedEvent) => void;
83
158
  }
84
159
 
85
160
  export type EventName = keyof EventMap;