@zellio/chat-react-native 0.0.0-stage → 1.0.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.
Files changed (58) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +239 -2
  3. package/ZellioChatReactNative.podspec +30 -0
  4. package/android/build.gradle +62 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/java/ai/zellio/chat/reactnative/ZellioChatModule.kt +214 -0
  7. package/android/src/main/java/ai/zellio/chat/reactnative/ZellioChatPackage.kt +25 -0
  8. package/app.plugin.js +4 -0
  9. package/ios/RNZellioChat.h +5 -0
  10. package/ios/RNZellioChat.mm +116 -0
  11. package/ios/ZCRNBridge.swift +217 -0
  12. package/ios/vendor/resources/PrivacyInfo.xcprivacy +99 -0
  13. package/ios/vendor/swift/ZellioChat/Model/ChatModel.swift +261 -0
  14. package/ios/vendor/swift/ZellioChat/Platform/Platform.swift +124 -0
  15. package/ios/vendor/swift/ZellioChat/Platform/URLSessionTransport.swift +175 -0
  16. package/ios/vendor/swift/ZellioChat/Preview/Previews.swift +62 -0
  17. package/ios/vendor/swift/ZellioChat/Public/InAppBanner.swift +155 -0
  18. package/ios/vendor/swift/ZellioChat/Public/PublicTypes.swift +141 -0
  19. package/ios/vendor/swift/ZellioChat/Public/Runtime.swift +406 -0
  20. package/ios/vendor/swift/ZellioChat/Public/ZellioChat.swift +209 -0
  21. package/ios/vendor/swift/ZellioChat/Theme/ChatTheme.swift +185 -0
  22. package/ios/vendor/swift/ZellioChat/Thread/AttachmentPicker.swift +354 -0
  23. package/ios/vendor/swift/ZellioChat/Thread/ComposerBar.swift +355 -0
  24. package/ios/vendor/swift/ZellioChat/Thread/ThreadCells.swift +776 -0
  25. package/ios/vendor/swift/ZellioChat/Thread/ThreadViewController.swift +703 -0
  26. package/ios/vendor/swift/ZellioChat/Views/ChatRootView.swift +154 -0
  27. package/ios/vendor/swift/ZellioChat/Views/Components.swift +335 -0
  28. package/ios/vendor/swift/ZellioChat/Views/HomeView.swift +413 -0
  29. package/ios/vendor/swift/ZellioChatCore/ChatModels.swift +373 -0
  30. package/ios/vendor/swift/ZellioChatCore/ConfigModels.swift +389 -0
  31. package/ios/vendor/swift/ZellioChatCore/Engine.swift +325 -0
  32. package/ios/vendor/swift/ZellioChatCore/Errors.swift +158 -0
  33. package/ios/vendor/swift/ZellioChatCore/Generated/ChatStringTable.swift +125 -0
  34. package/ios/vendor/swift/ZellioChatCore/Outbox.swift +223 -0
  35. package/ios/vendor/swift/ZellioChatCore/Push.swift +74 -0
  36. package/ios/vendor/swift/ZellioChatCore/Realtime.swift +328 -0
  37. package/ios/vendor/swift/ZellioChatCore/Session.swift +210 -0
  38. package/ios/vendor/swift/ZellioChatCore/Storage.swift +201 -0
  39. package/ios/vendor/swift/ZellioChatCore/Strings.swift +104 -0
  40. package/ios/vendor/swift/ZellioChatCore/Theme.swift +238 -0
  41. package/ios/vendor/swift/ZellioChatCore/ThreadController.swift +319 -0
  42. package/ios/vendor/swift/ZellioChatCore/Timeline.swift +157 -0
  43. package/ios/vendor/swift/ZellioChatCore/Transport.swift +236 -0
  44. package/ios/vendor/swift/ZellioChatCore/Util.swift +137 -0
  45. package/lib/NativeZellioChat.d.ts +34 -0
  46. package/lib/NativeZellioChat.js +4 -0
  47. package/lib/index.d.ts +37 -0
  48. package/lib/index.js +241 -0
  49. package/lib/types.d.ts +54 -0
  50. package/lib/types.js +2 -0
  51. package/lib/version.d.ts +4 -0
  52. package/lib/version.js +7 -0
  53. package/package.json +75 -4
  54. package/plugin/build/index.js +73 -0
  55. package/src/NativeZellioChat.ts +30 -0
  56. package/src/index.ts +237 -0
  57. package/src/types.ts +56 -0
  58. package/src/version.ts +5 -0
@@ -0,0 +1,373 @@
1
+ import Foundation
2
+
3
+ public enum MessageSender: String, Codable, Equatable, Sendable {
4
+ case visitor
5
+ case agent
6
+ case unknown
7
+
8
+ public init(from decoder: Decoder) throws {
9
+ let value = (try? decoder.singleValueContainer().decode(String.self)) ?? ""
10
+ self = MessageSender(rawValue: value) ?? .unknown
11
+ }
12
+ }
13
+
14
+ public enum DeliveryState: String, Codable, Equatable, Sendable {
15
+ case sent
16
+ case sending
17
+ case waiting // queued, offline
18
+ case failed
19
+
20
+ public init(from decoder: Decoder) throws {
21
+ let value = (try? decoder.singleValueContainer().decode(String.self)) ?? ""
22
+ self = DeliveryState(rawValue: value) ?? .sent
23
+ }
24
+ }
25
+
26
+ public struct Attachment: Codable, Equatable, Sendable {
27
+ public var id: String?
28
+ public var kind: String // "image" or "file"
29
+ public var url: String
30
+ public var name: String
31
+ public var mimeType: String
32
+ public var size: Int?
33
+ public var width: Int?
34
+ public var height: Int?
35
+
36
+ public init(id: String? = nil, kind: String, url: String, name: String, mimeType: String, size: Int? = nil, width: Int? = nil, height: Int? = nil) {
37
+ self.id = id
38
+ self.kind = kind
39
+ self.url = url
40
+ self.name = name
41
+ self.mimeType = mimeType
42
+ self.size = size
43
+ self.width = width
44
+ self.height = height
45
+ }
46
+
47
+ public var isImage: Bool { kind == "image" || mimeType.hasPrefix("image/") }
48
+
49
+ private enum CodingKeys: String, CodingKey { case id, kind, url, name, mimeType, size, width, height }
50
+
51
+ public init(from decoder: Decoder) throws {
52
+ let c = try decoder.container(keyedBy: CodingKeys.self)
53
+ id = c.lenient(String.self, .id)
54
+ let mime = c.lenient(String.self, .mimeType) ?? ""
55
+ mimeType = mime
56
+ kind = c.lenient(String.self, .kind) ?? (mime.hasPrefix("image/") ? "image" : "file")
57
+ url = c.lenient(String.self, .url) ?? ""
58
+ name = c.lenient(String.self, .name) ?? ""
59
+ size = c.lenient(Int.self, .size)
60
+ width = c.lenient(Int.self, .width)
61
+ height = c.lenient(Int.self, .height)
62
+ }
63
+ }
64
+
65
+ /// The page the customer is on. Sent with messages. Strings are at most 200 characters, `url` must be https.
66
+ public struct ChatContext: Codable, Equatable, Sendable {
67
+ public var screen: String?
68
+ public var itemType: String?
69
+ public var itemId: String?
70
+ public var itemTitle: String?
71
+ public var url: String?
72
+
73
+ public init(screen: String? = nil, itemType: String? = nil, itemId: String? = nil, itemTitle: String? = nil, url: String? = nil) {
74
+ self.screen = screen
75
+ self.itemType = itemType
76
+ self.itemId = itemId
77
+ self.itemTitle = itemTitle
78
+ self.url = url
79
+ }
80
+
81
+ public var isEmpty: Bool { screen == nil && itemType == nil && itemId == nil && itemTitle == nil && url == nil }
82
+
83
+ /// Trims every string to 200 UTF-16 units and drops a url that does not start with lowercase https://. Nil when nothing is left.
84
+ public func sanitized() -> ChatContext? {
85
+ func clip(_ value: String?) -> String? {
86
+ guard let value = value, !value.isEmpty else { return nil }
87
+ return value.clipped(toUTF16: 200)
88
+ }
89
+ var safeURL = clip(url)
90
+ if let candidate = safeURL, !candidate.hasPrefix("https://") { safeURL = nil }
91
+ let result = ChatContext(screen: clip(screen), itemType: clip(itemType), itemId: clip(itemId), itemTitle: clip(itemTitle), url: safeURL)
92
+ return result.isEmpty ? nil : result
93
+ }
94
+ }
95
+
96
+ public struct ChatMessage: Codable, Equatable, Sendable, Identifiable {
97
+ public var serverId: String?
98
+ public var clientMessageId: String?
99
+ public var from: MessageSender
100
+ public var text: String
101
+ public var attachments: [Attachment]
102
+ public var createdAt: String
103
+ public var agentName: String?
104
+ public var agentAvatarUrl: String?
105
+ public var delivery: DeliveryState
106
+ public var failureReason: String?
107
+
108
+ public init(serverId: String? = nil, clientMessageId: String? = nil, from: MessageSender, text: String, attachments: [Attachment] = [],
109
+ createdAt: String, agentName: String? = nil, agentAvatarUrl: String? = nil, delivery: DeliveryState = .sent, failureReason: String? = nil) {
110
+ self.serverId = serverId
111
+ self.clientMessageId = clientMessageId
112
+ self.from = from
113
+ self.text = text
114
+ self.attachments = attachments
115
+ self.createdAt = createdAt
116
+ self.agentName = agentName
117
+ self.agentAvatarUrl = agentAvatarUrl
118
+ self.delivery = delivery
119
+ self.failureReason = failureReason
120
+ }
121
+
122
+ private enum CodingKeys: String, CodingKey {
123
+ case serverId = "id"
124
+ case clientMessageId, from, text, attachments, createdAt, agentName, agentAvatarUrl, delivery, failureReason
125
+ }
126
+
127
+ public init(from decoder: Decoder) throws {
128
+ let c = try decoder.container(keyedBy: CodingKeys.self)
129
+ serverId = c.lenient(String.self, .serverId)
130
+ clientMessageId = c.lenient(String.self, .clientMessageId)
131
+ from = c.lenient(MessageSender.self, .from) ?? .unknown
132
+ text = c.lenient(String.self, .text) ?? ""
133
+ attachments = c.lenient(LossyArray<Attachment>.self, .attachments)?.items ?? []
134
+ createdAt = c.lenient(String.self, .createdAt) ?? ""
135
+ agentName = c.lenient(String.self, .agentName)
136
+ agentAvatarUrl = c.lenient(String.self, .agentAvatarUrl)
137
+ delivery = c.lenient(DeliveryState.self, .delivery) ?? .sent
138
+ failureReason = c.lenient(String.self, .failureReason)
139
+ }
140
+
141
+ /// Stable for the whole life of a message: the same value before and after the server confirms it.
142
+ public var stableId: String { clientMessageId ?? serverId ?? createdAt }
143
+ public var id: String { stableId }
144
+ public var date: Date { ISODate.parse(createdAt) ?? Date(timeIntervalSince1970: 0) }
145
+ public var isUsable: Bool { from != .unknown && (serverId != nil || clientMessageId != nil) }
146
+ }
147
+
148
+ public struct MessagesPage: Equatable, Sendable {
149
+ public var conversationId: String?
150
+ public var messages: [ChatMessage]
151
+ public var nextBefore: String?
152
+ public var latest: String?
153
+
154
+ public init(conversationId: String? = nil, messages: [ChatMessage] = [], nextBefore: String? = nil, latest: String? = nil) {
155
+ self.conversationId = conversationId
156
+ self.messages = messages
157
+ self.nextBefore = nextBefore
158
+ self.latest = latest
159
+ }
160
+ }
161
+
162
+ extension MessagesPage: Decodable {
163
+ private enum CodingKeys: String, CodingKey { case conversationId, messages, nextBefore, latest }
164
+
165
+ public init(from decoder: Decoder) throws {
166
+ let c = try decoder.container(keyedBy: CodingKeys.self)
167
+ conversationId = c.lenient(String.self, .conversationId)
168
+ // Unknown `from` and items without an id are skipped.
169
+ messages = (c.lenient(LossyArray<ChatMessage>.self, .messages)?.items ?? []).filter { $0.isUsable }
170
+ nextBefore = c.lenient(String.self, .nextBefore)
171
+ latest = c.lenient(String.self, .latest)
172
+ }
173
+ }
174
+
175
+ public struct LastMessage: Codable, Equatable, Sendable {
176
+ public var from: MessageSender
177
+ public var text: String
178
+ public var hasAttachment: Bool
179
+ /// "image", "file" or nil. A value the SDK does not know is treated as a file.
180
+ public var attachmentKind: String?
181
+
182
+ public init(from: MessageSender, text: String, hasAttachment: Bool, attachmentKind: String? = nil) {
183
+ self.from = from
184
+ self.text = text
185
+ self.hasAttachment = hasAttachment
186
+ self.attachmentKind = attachmentKind
187
+ }
188
+
189
+ private enum CodingKeys: String, CodingKey { case from, text, hasAttachment, attachmentKind }
190
+
191
+ public init(from decoder: Decoder) throws {
192
+ let c = try decoder.container(keyedBy: CodingKeys.self)
193
+ from = c.lenient(MessageSender.self, .from) ?? .unknown
194
+ text = c.lenient(String.self, .text) ?? ""
195
+ hasAttachment = c.lenient(Bool.self, .hasAttachment) ?? false
196
+ attachmentKind = c.lenient(String.self, .attachmentKind)
197
+ }
198
+ }
199
+
200
+ public struct ConversationSummary: Codable, Equatable, Sendable, Identifiable {
201
+ public var id: String
202
+ public var status: String
203
+ public var lastMessageAt: String?
204
+ public var lastMessage: LastMessage?
205
+ public var agentName: String?
206
+ public var agentAvatarUrl: String?
207
+ public var unread: Int
208
+
209
+ public init(id: String, status: String = "open", lastMessageAt: String? = nil, lastMessage: LastMessage? = nil, agentName: String? = nil, agentAvatarUrl: String? = nil, unread: Int = 0) {
210
+ self.id = id
211
+ self.status = status
212
+ self.lastMessageAt = lastMessageAt
213
+ self.lastMessage = lastMessage
214
+ self.agentName = agentName
215
+ self.agentAvatarUrl = agentAvatarUrl
216
+ self.unread = unread
217
+ }
218
+
219
+ private enum CodingKeys: String, CodingKey { case id, status, lastMessageAt, lastMessage, agentName, agentAvatarUrl, unread }
220
+
221
+ public init(from decoder: Decoder) throws {
222
+ let c = try decoder.container(keyedBy: CodingKeys.self)
223
+ guard let id = c.lenient(String.self, .id), !id.isEmpty else {
224
+ throw DecodingError.keyNotFound(CodingKeys.id, DecodingError.Context(codingPath: [], debugDescription: "id"))
225
+ }
226
+ self.id = id
227
+ status = c.lenient(String.self, .status) ?? "open"
228
+ lastMessageAt = c.lenient(String.self, .lastMessageAt)
229
+ lastMessage = c.lenient(LastMessage.self, .lastMessage)
230
+ agentName = c.lenient(String.self, .agentName)
231
+ agentAvatarUrl = c.lenient(String.self, .agentAvatarUrl)
232
+ unread = c.lenient(Int.self, .unread) ?? 0
233
+ }
234
+
235
+ public var lastDate: Date? { lastMessageAt.flatMap { ISODate.parse($0) } }
236
+ }
237
+
238
+ public struct ConversationsPage: Codable, Equatable, Sendable {
239
+ public var conversations: [ConversationSummary]
240
+ public var nextCursor: String?
241
+ public var unreadTotal: Int
242
+
243
+ public init(conversations: [ConversationSummary] = [], nextCursor: String? = nil, unreadTotal: Int = 0) {
244
+ self.conversations = conversations
245
+ self.nextCursor = nextCursor
246
+ self.unreadTotal = unreadTotal
247
+ }
248
+
249
+ private enum CodingKeys: String, CodingKey { case conversations, nextCursor, unreadTotal }
250
+
251
+ public init(from decoder: Decoder) throws {
252
+ let c = try decoder.container(keyedBy: CodingKeys.self)
253
+ conversations = c.lenient(LossyArray<ConversationSummary>.self, .conversations)?.items ?? []
254
+ nextCursor = c.lenient(String.self, .nextCursor)
255
+ unreadTotal = c.lenient(Int.self, .unreadTotal) ?? 0
256
+ }
257
+ }
258
+
259
+ public struct SessionInfo: Codable, Equatable, Sendable {
260
+ public var sessionToken: String
261
+ public var expiresAt: String?
262
+ public var identified: Bool
263
+ public var customerName: String?
264
+ public var versionId: String?
265
+
266
+ public init(sessionToken: String, expiresAt: String? = nil, identified: Bool = false, customerName: String? = nil, versionId: String? = nil) {
267
+ self.sessionToken = sessionToken
268
+ self.expiresAt = expiresAt
269
+ self.identified = identified
270
+ self.customerName = customerName
271
+ self.versionId = versionId
272
+ }
273
+
274
+ private enum CodingKeys: String, CodingKey { case sessionToken, expiresAt, customer, versionId }
275
+ private enum CustomerKeys: String, CodingKey { case identified, name }
276
+
277
+ public init(from decoder: Decoder) throws {
278
+ let c = try decoder.container(keyedBy: CodingKeys.self)
279
+ guard let token = c.lenient(String.self, .sessionToken), !token.isEmpty else {
280
+ throw DecodingError.keyNotFound(CodingKeys.sessionToken, DecodingError.Context(codingPath: [], debugDescription: "sessionToken"))
281
+ }
282
+ sessionToken = token
283
+ expiresAt = c.lenient(String.self, .expiresAt)
284
+ versionId = c.lenient(String.self, .versionId)
285
+ if let customer = try? c.nestedContainer(keyedBy: CustomerKeys.self, forKey: .customer) {
286
+ identified = customer.lenient(Bool.self, .identified) ?? false
287
+ customerName = customer.lenient(String.self, .name)
288
+ } else {
289
+ identified = false
290
+ }
291
+ }
292
+
293
+ public func encode(to encoder: Encoder) throws {
294
+ var c = encoder.container(keyedBy: CodingKeys.self)
295
+ try c.encode(sessionToken, forKey: .sessionToken)
296
+ try c.encodeIfPresent(expiresAt, forKey: .expiresAt)
297
+ try c.encodeIfPresent(versionId, forKey: .versionId)
298
+ var customer = c.nestedContainer(keyedBy: CustomerKeys.self, forKey: .customer)
299
+ try customer.encode(identified, forKey: .identified)
300
+ try customer.encodeIfPresent(customerName, forKey: .name)
301
+ }
302
+ }
303
+
304
+ public struct SendReceipt: Decodable, Equatable, Sendable {
305
+ public var clientMessageId: String?
306
+ public var accepted: Bool
307
+ public var conversationId: String?
308
+
309
+ private enum CodingKeys: String, CodingKey { case clientMessageId, accepted, conversationId }
310
+
311
+ public init(clientMessageId: String?, accepted: Bool, conversationId: String?) {
312
+ self.clientMessageId = clientMessageId
313
+ self.accepted = accepted
314
+ self.conversationId = conversationId
315
+ }
316
+
317
+ public init(from decoder: Decoder) throws {
318
+ let c = try decoder.container(keyedBy: CodingKeys.self)
319
+ clientMessageId = c.lenient(String.self, .clientMessageId)
320
+ accepted = c.lenient(Bool.self, .accepted) ?? true
321
+ conversationId = c.lenient(String.self, .conversationId)
322
+ }
323
+ }
324
+
325
+ /// One event from the SSE stream.
326
+ public enum RealtimeEvent: Equatable, Sendable {
327
+ case ready(conversationId: String?)
328
+ case message(ChatMessage, conversationId: String?)
329
+ case typing(active: Bool, agentName: String?)
330
+ case agentJoined(agentName: String?, avatarUrl: String?)
331
+ case unread(Int)
332
+ }
333
+
334
+ public enum RealtimeEventDecoder {
335
+ /// Nil for an unknown event name or bad data. Never throws.
336
+ public static func decode(_ event: SSEEvent) -> RealtimeEvent? {
337
+ guard let name = event.name, let data = event.data.data(using: .utf8) else { return nil }
338
+ guard let object = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] else { return nil }
339
+ switch name {
340
+ case "ready":
341
+ return .ready(conversationId: object["conversationId"] as? String)
342
+ case "message":
343
+ guard let message = try? JSONDecoder().decode(ChatMessage.self, from: data), message.isUsable else { return nil }
344
+ return .message(message, conversationId: object["conversationId"] as? String)
345
+ case "typing":
346
+ return .typing(active: (object["active"] as? Bool) ?? false, agentName: object["agentName"] as? String)
347
+ case "agent_joined":
348
+ return .agentJoined(agentName: object["agentName"] as? String, avatarUrl: object["agentAvatarUrl"] as? String)
349
+ case "unread":
350
+ guard let total = object["unreadTotal"] as? Int else { return nil }
351
+ return .unread(total)
352
+ default:
353
+ return nil
354
+ }
355
+ }
356
+ }
357
+
358
+ extension String {
359
+ /// The longest start of the string that fits in `limit` UTF-16 units (the server counts these). Never cuts a character in two.
360
+ func clipped(toUTF16 limit: Int) -> String {
361
+ guard limit > 0 else { return "" }
362
+ if utf16.count <= limit { return self }
363
+ var used = 0
364
+ var result = ""
365
+ for character in self {
366
+ let size = character.utf16.count
367
+ if used + size > limit { break }
368
+ used += size
369
+ result.append(character)
370
+ }
371
+ return result
372
+ }
373
+ }