react-native-newinstance-chat 0.1.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.
- package/LICENSE +20 -0
- package/NewinstanceChat.podspec +23 -0
- package/README.md +106 -0
- package/android/build.gradle +79 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/newinstancechat/NewinstanceChatModule.kt +200 -0
- package/android/src/main/java/com/newinstancechat/NewinstanceChatPackage.kt +31 -0
- package/ios/NewinstanceChat.h +12 -0
- package/ios/NewinstanceChat.mm +103 -0
- package/ios/NewinstanceChatImpl.swift +239 -0
- package/lib/module/NativeNewinstanceChat.js +17 -0
- package/lib/module/NativeNewinstanceChat.js.map +1 -0
- package/lib/module/index.js +88 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeNewinstanceChat.d.ts +27 -0
- package/lib/typescript/src/NativeNewinstanceChat.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +52 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +69 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +166 -0
- package/src/NativeNewinstanceChat.ts +30 -0
- package/src/index.tsx +120 -0
- package/src/types.ts +86 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 lawrence
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "NewinstanceChat"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "https://github.com/talktothelaw/react-native-newinstance-chat.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
|
|
17
|
+
s.private_header_files = "ios/**/*.h"
|
|
18
|
+
|
|
19
|
+
# Native chat SDK we wrap, published on CocoaPods Trunk.
|
|
20
|
+
s.dependency "LiveAndAiChat", "~> 0.1"
|
|
21
|
+
|
|
22
|
+
install_modules_dependencies(s)
|
|
23
|
+
end
|
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# react-native-newinstance-chat
|
|
2
|
+
|
|
3
|
+
React Native bindings for [LiveAndAiChat](https://newinstance.cloud) — drops a
|
|
4
|
+
complete AI + live-agent chat experience into your iOS and Android RN app
|
|
5
|
+
via native module bridges.
|
|
6
|
+
|
|
7
|
+
The chat UI, transport, attachments, image cache, typing indicators, and
|
|
8
|
+
silent gap-fill resync all come from the native iOS / Android SDKs. This
|
|
9
|
+
package is a thin TurboModule surface over them — your React Native code
|
|
10
|
+
stays small.
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
- React Native 0.74+ (TurboModule support — recommended 0.85+)
|
|
15
|
+
- iOS 15.1+
|
|
16
|
+
- Android API 24+, JDK 17
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm install react-native-newinstance-chat
|
|
22
|
+
# or
|
|
23
|
+
yarn add react-native-newinstance-chat
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### iOS
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
cd ios && pod install
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Autolink pulls the `NewinstanceChat` podspec, which transitively depends
|
|
33
|
+
on `LiveAndAiChat` (the native SDK pod, published on CocoaPods Trunk).
|
|
34
|
+
|
|
35
|
+
### Android
|
|
36
|
+
|
|
37
|
+
Autolink registers `NewinstanceChatPackage`. The underlying
|
|
38
|
+
`cloud.newinstance:liveandaichat` AAR is resolved from Maven Central —
|
|
39
|
+
ensure `mavenCentral()` is in `settings.gradle.kts`.
|
|
40
|
+
|
|
41
|
+
## Quick start
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { NewinstanceChat } from 'react-native-newinstance-chat';
|
|
45
|
+
|
|
46
|
+
const sdk = new NewinstanceChat({
|
|
47
|
+
apiKey: 'sk_live_…',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
sdk.setUser({
|
|
51
|
+
customerName: 'Ada Lovelace',
|
|
52
|
+
customerEmail: 'ada@example.com',
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await sdk.initialize();
|
|
56
|
+
|
|
57
|
+
// Later, in response to a host button tap:
|
|
58
|
+
sdk.openChat();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The chat screen renders natively — there is no React component to mount.
|
|
62
|
+
Your RN UI is whatever button / banner / FAB triggers `openChat()`.
|
|
63
|
+
|
|
64
|
+
### Subscribing to events
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
const unsub = sdk.addListener('messageReceived', (m) => {
|
|
68
|
+
console.log('rx <-', m.content);
|
|
69
|
+
});
|
|
70
|
+
// Later:
|
|
71
|
+
unsub();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Event names: `messageReceived`, `messageSent`, `agentTypingChanged`,
|
|
75
|
+
`connectionStateChanged`, `unreadCountChanged`, `error`. See `src/types.ts`
|
|
76
|
+
for payload shapes.
|
|
77
|
+
|
|
78
|
+
### Sending messages programmatically
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
sdk.sendMessage('Hi, I have a question.');
|
|
82
|
+
sdk.attachFile({ uri: 'file:///…', name: 'screenshot.png', mimeType: 'image/png' });
|
|
83
|
+
sdk.retryMessage(failedMessageId);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Handoff to a live agent and typing indicators are driven automatically
|
|
87
|
+
by the native chat screen — there's nothing to wire up from the host.
|
|
88
|
+
|
|
89
|
+
### Tear-down
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
sdk.destroy();
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Call this when your host component unmounts. After `destroy()` the
|
|
96
|
+
instance is unusable — construct a new one if you need to chat again.
|
|
97
|
+
|
|
98
|
+
## Contributing
|
|
99
|
+
|
|
100
|
+
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
|
101
|
+
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
|
|
102
|
+
- [Code of conduct](CODE_OF_CONDUCT.md)
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.NewinstanceChat = [
|
|
3
|
+
kotlinVersion: "2.0.21",
|
|
4
|
+
minSdkVersion: 24,
|
|
5
|
+
compileSdkVersion: 36,
|
|
6
|
+
targetSdkVersion: 36
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
ext.getExtOrDefault = { prop ->
|
|
10
|
+
if (rootProject.ext.has(prop)) {
|
|
11
|
+
return rootProject.ext.get(prop)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return NewinstanceChat[prop]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
repositories {
|
|
18
|
+
google()
|
|
19
|
+
mavenCentral()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
dependencies {
|
|
23
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
24
|
+
// noinspection DifferentKotlinGradleVersion
|
|
25
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
apply plugin: "com.android.library"
|
|
31
|
+
apply plugin: "kotlin-android"
|
|
32
|
+
|
|
33
|
+
apply plugin: "com.facebook.react"
|
|
34
|
+
|
|
35
|
+
android {
|
|
36
|
+
namespace "com.newinstancechat"
|
|
37
|
+
|
|
38
|
+
compileSdkVersion getExtOrDefault("compileSdkVersion")
|
|
39
|
+
|
|
40
|
+
defaultConfig {
|
|
41
|
+
minSdkVersion getExtOrDefault("minSdkVersion")
|
|
42
|
+
targetSdkVersion getExtOrDefault("targetSdkVersion")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
buildFeatures {
|
|
46
|
+
buildConfig true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
buildTypes {
|
|
50
|
+
release {
|
|
51
|
+
minifyEnabled false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
lint {
|
|
56
|
+
disable "GradleCompatible"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
compileOptions {
|
|
60
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
61
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
repositories {
|
|
66
|
+
google()
|
|
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.
|
|
72
|
+
mavenLocal()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
dependencies {
|
|
76
|
+
implementation "com.facebook.react:react-android"
|
|
77
|
+
// Native chat SDK the bridge wraps. Published under cloud.newinstance.
|
|
78
|
+
implementation "cloud.newinstance:liveandaichat:0.1.0"
|
|
79
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
package com.newinstancechat
|
|
2
|
+
|
|
3
|
+
import cloud.newinstance.liveandaichat.ConnectionState
|
|
4
|
+
import cloud.newinstance.liveandaichat.LiveAndAiChat
|
|
5
|
+
import cloud.newinstance.liveandaichat.LiveAndAiChatConfig
|
|
6
|
+
import cloud.newinstance.liveandaichat.LiveAndAiChatError
|
|
7
|
+
import cloud.newinstance.liveandaichat.ChatUser
|
|
8
|
+
import cloud.newinstance.liveandaichat.model.ChatMessage
|
|
9
|
+
import cloud.newinstance.liveandaichat.model.TransportMode
|
|
10
|
+
import com.facebook.react.bridge.Arguments
|
|
11
|
+
import com.facebook.react.bridge.Promise
|
|
12
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
13
|
+
import com.facebook.react.bridge.ReadableMap
|
|
14
|
+
import com.facebook.react.bridge.WritableMap
|
|
15
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* React Native ↔ LiveAndAiChat Android SDK bridge (TurboModule).
|
|
19
|
+
*
|
|
20
|
+
* Holds a single underlying `LiveAndAiChat` instance built lazily on
|
|
21
|
+
* `configure(...)`. Repeated `configure(...)` calls tear down and
|
|
22
|
+
* rebuild — hosts almost never want concurrent chat sessions.
|
|
23
|
+
*
|
|
24
|
+
* Events emitted to JS via `RCTDeviceEventEmitter`:
|
|
25
|
+
* - `messageReceived` (ChatMessage map)
|
|
26
|
+
* - `messageSent` (ChatMessage map)
|
|
27
|
+
* - `agentTypingChanged` (Boolean)
|
|
28
|
+
* - `connectionStateChanged` (lowercased connection state name)
|
|
29
|
+
* - `unreadCountChanged` (Int)
|
|
30
|
+
* - `error` ({type, message, recoverable})
|
|
31
|
+
*/
|
|
32
|
+
class NewinstanceChatModule(reactContext: ReactApplicationContext) :
|
|
33
|
+
NativeNewinstanceChatSpec(reactContext), LiveAndAiChat.Listener {
|
|
34
|
+
|
|
35
|
+
private var sdk: LiveAndAiChat? = null
|
|
36
|
+
private var pendingUser: ChatUser? = null
|
|
37
|
+
|
|
38
|
+
override fun getName(): String = NAME
|
|
39
|
+
|
|
40
|
+
private fun emit(event: String, body: Any?) {
|
|
41
|
+
reactApplicationContext
|
|
42
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
43
|
+
.emit(event, body)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// MARK: - configure / setUser / initialize
|
|
47
|
+
|
|
48
|
+
override fun configure(config: ReadableMap, promise: Promise) {
|
|
49
|
+
val apiKey = config.getString("apiKey")
|
|
50
|
+
if (apiKey.isNullOrBlank()) {
|
|
51
|
+
promise.reject("E_CONFIG", "apiKey is required")
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
val baseUrl = if (config.hasKey("baseUrl")) config.getString("baseUrl") else null
|
|
55
|
+
val initialMessage =
|
|
56
|
+
if (config.hasKey("initialMessage")) config.getString("initialMessage") else null
|
|
57
|
+
val transport: TransportMode? = if (config.hasKey("transport")) {
|
|
58
|
+
when (config.getString("transport")) {
|
|
59
|
+
"sse" -> TransportMode.SSE
|
|
60
|
+
"ws" -> TransportMode.WS
|
|
61
|
+
else -> null
|
|
62
|
+
}
|
|
63
|
+
} else null
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
val cfg = LiveAndAiChatConfig(
|
|
67
|
+
apiKey = apiKey,
|
|
68
|
+
baseUrl = baseUrl ?: LiveAndAiChatConfig.DEFAULT_BASE_URL,
|
|
69
|
+
transport = transport,
|
|
70
|
+
initialMessage = initialMessage,
|
|
71
|
+
)
|
|
72
|
+
sdk?.destroy()
|
|
73
|
+
val builder = LiveAndAiChat.Builder(reactApplicationContext).config(cfg)
|
|
74
|
+
pendingUser?.let { builder.user(it) }
|
|
75
|
+
val instance = builder.build()
|
|
76
|
+
instance.addListener(this)
|
|
77
|
+
sdk = instance
|
|
78
|
+
pendingUser = null
|
|
79
|
+
promise.resolve(null)
|
|
80
|
+
} catch (e: Exception) {
|
|
81
|
+
promise.reject("E_BUILD", e.message, e)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
override fun setUser(user: ReadableMap) {
|
|
86
|
+
val name = user.getString("customerName") ?: return
|
|
87
|
+
if (name.isBlank()) return
|
|
88
|
+
val chatUser = ChatUser(
|
|
89
|
+
customerName = name,
|
|
90
|
+
customerId = if (user.hasKey("customerId")) user.getString("customerId") else null,
|
|
91
|
+
customerEmail = if (user.hasKey("customerEmail")) user.getString("customerEmail") else null,
|
|
92
|
+
)
|
|
93
|
+
val current = sdk
|
|
94
|
+
if (current != null) {
|
|
95
|
+
current.setUser(chatUser)
|
|
96
|
+
} else {
|
|
97
|
+
pendingUser = chatUser
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
override fun initialize(promise: Promise) {
|
|
102
|
+
val current = sdk
|
|
103
|
+
if (current == null) {
|
|
104
|
+
promise.reject("E_NOT_CONFIGURED", "configure() must be called before initialize()")
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
current.initialize()
|
|
108
|
+
promise.resolve(null)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// MARK: - Native chat presentation
|
|
112
|
+
|
|
113
|
+
override fun openChat() { sdk?.openChat() }
|
|
114
|
+
override fun closeChat() { sdk?.closeChat() }
|
|
115
|
+
|
|
116
|
+
// MARK: - Programmatic actions
|
|
117
|
+
|
|
118
|
+
override fun sendMessage(text: String) { sdk?.sendMessage(text) }
|
|
119
|
+
override fun retryMessage(messageId: String) { sdk?.retryMessage(messageId) }
|
|
120
|
+
|
|
121
|
+
override fun attachFile(opts: ReadableMap, promise: Promise) {
|
|
122
|
+
val current = sdk
|
|
123
|
+
if (current == null) {
|
|
124
|
+
promise.reject("E_NOT_CONFIGURED", "configure() must be called first")
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
val uri = opts.getString("uri")
|
|
128
|
+
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
|
+
return
|
|
133
|
+
}
|
|
134
|
+
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)
|
|
140
|
+
} catch (e: Exception) {
|
|
141
|
+
promise.reject("E_READ", e.message, e)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
override fun destroy() {
|
|
146
|
+
sdk?.destroy()
|
|
147
|
+
sdk = null
|
|
148
|
+
pendingUser = null
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// RN NativeEventEmitter listener tracking — required by Codegen even
|
|
152
|
+
// though the actual emit path is RCTDeviceEventEmitter (above).
|
|
153
|
+
override fun addListener(eventName: String) { /* no-op */ }
|
|
154
|
+
override fun removeListeners(count: Double) { /* no-op */ }
|
|
155
|
+
|
|
156
|
+
// MARK: - LiveAndAiChat.Listener
|
|
157
|
+
|
|
158
|
+
override fun onMessageReceived(message: ChatMessage) {
|
|
159
|
+
emit("messageReceived", chatMessageToMap(message))
|
|
160
|
+
}
|
|
161
|
+
override fun onMessageSent(message: ChatMessage) {
|
|
162
|
+
emit("messageSent", chatMessageToMap(message))
|
|
163
|
+
}
|
|
164
|
+
override fun onTyping(isTyping: Boolean) {
|
|
165
|
+
emit("agentTypingChanged", isTyping)
|
|
166
|
+
}
|
|
167
|
+
override fun onConnectionChange(state: ConnectionState) {
|
|
168
|
+
emit("connectionStateChanged", state.name.lowercase())
|
|
169
|
+
}
|
|
170
|
+
override fun onUnreadCountChange(count: Int) {
|
|
171
|
+
emit("unreadCountChanged", count)
|
|
172
|
+
}
|
|
173
|
+
override fun onError(error: LiveAndAiChatError) {
|
|
174
|
+
val map = Arguments.createMap()
|
|
175
|
+
map.putString("type", error.type.name.lowercase())
|
|
176
|
+
map.putString("message", error.message)
|
|
177
|
+
map.putBoolean("recoverable", error.recoverable)
|
|
178
|
+
emit("error", map)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// MARK: - Helpers
|
|
182
|
+
|
|
183
|
+
private fun chatMessageToMap(m: ChatMessage): WritableMap {
|
|
184
|
+
val map = Arguments.createMap()
|
|
185
|
+
map.putString("id", m.id)
|
|
186
|
+
map.putString("clientId", m.clientId)
|
|
187
|
+
map.putString("conversationId", m.conversationId)
|
|
188
|
+
map.putString("content", m.content)
|
|
189
|
+
map.putString("type", m.type.name.lowercase())
|
|
190
|
+
map.putString("status", m.status.name.lowercase())
|
|
191
|
+
m.seq?.let { map.putDouble("seq", it.toDouble()) }
|
|
192
|
+
m.sentAt?.let { map.putString("sentAt", it) }
|
|
193
|
+
m.totalMessages?.let { map.putInt("totalMessages", it) }
|
|
194
|
+
return map
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
companion object {
|
|
198
|
+
const val NAME = NativeNewinstanceChatSpec.NAME
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
package com.newinstancechat
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
import java.util.HashMap
|
|
9
|
+
|
|
10
|
+
class NewinstanceChatPackage : BaseReactPackage() {
|
|
11
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
12
|
+
return if (name == NewinstanceChatModule.NAME) {
|
|
13
|
+
NewinstanceChatModule(reactContext)
|
|
14
|
+
} else {
|
|
15
|
+
null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
|
|
20
|
+
mapOf(
|
|
21
|
+
NewinstanceChatModule.NAME to ReactModuleInfo(
|
|
22
|
+
name = NewinstanceChatModule.NAME,
|
|
23
|
+
className = NewinstanceChatModule.NAME,
|
|
24
|
+
canOverrideExistingModule = false,
|
|
25
|
+
needsEagerInit = false,
|
|
26
|
+
isCxxModule = false,
|
|
27
|
+
isTurboModule = true
|
|
28
|
+
)
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#import <NewinstanceChatSpec/NewinstanceChatSpec.h>
|
|
2
|
+
|
|
3
|
+
@class NewinstanceChatImpl;
|
|
4
|
+
|
|
5
|
+
@interface NewinstanceChat : NSObject <NativeNewinstanceChatSpec>
|
|
6
|
+
|
|
7
|
+
/// Emit a JS-side `RCTDeviceEventEmitter` event. Called by
|
|
8
|
+
/// NewinstanceChatImpl (Swift) when the underlying LiveAndAiChat SDK
|
|
9
|
+
/// surfaces a delegate callback.
|
|
10
|
+
- (void)emitEvent:(NSString *)name body:(id)body;
|
|
11
|
+
|
|
12
|
+
@end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#import "NewinstanceChat.h"
|
|
2
|
+
#import <React/RCTBridge.h>
|
|
3
|
+
#import <React/RCTEventDispatcherProtocol.h>
|
|
4
|
+
|
|
5
|
+
// Auto-generated Swift bridging header for this pod (created at build
|
|
6
|
+
// time by Xcode/CocoaPods when the pod mixes Obj-C and Swift). The pod
|
|
7
|
+
// name is `NewinstanceChat`, hence the header name.
|
|
8
|
+
#import "NewinstanceChat-Swift.h"
|
|
9
|
+
|
|
10
|
+
// Declare conformance to NewinstanceChatEmitter (defined in
|
|
11
|
+
// NewinstanceChatImpl.swift) so the Swift impl can hold a typed weak
|
|
12
|
+
// reference to us. The auto-generated NewinstanceChat-Swift.h imported
|
|
13
|
+
// above provides the protocol declaration.
|
|
14
|
+
@interface NewinstanceChat () <NewinstanceChatEmitter>
|
|
15
|
+
@property (nonatomic, strong) NewinstanceChatImpl *impl;
|
|
16
|
+
@end
|
|
17
|
+
|
|
18
|
+
@implementation NewinstanceChat
|
|
19
|
+
|
|
20
|
+
@synthesize bridge = _bridge;
|
|
21
|
+
|
|
22
|
+
RCT_EXPORT_MODULE()
|
|
23
|
+
|
|
24
|
+
- (instancetype)init
|
|
25
|
+
{
|
|
26
|
+
if ((self = [super init])) {
|
|
27
|
+
_impl = [[NewinstanceChatImpl alloc] init];
|
|
28
|
+
_impl.emitter = self;
|
|
29
|
+
}
|
|
30
|
+
return self;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
+ (BOOL)requiresMainQueueSetup
|
|
34
|
+
{
|
|
35
|
+
return YES;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// MARK: - Spec methods (delegated to Swift)
|
|
39
|
+
|
|
40
|
+
- (void)configure:(NSDictionary *)config
|
|
41
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
42
|
+
reject:(RCTPromiseRejectBlock)reject
|
|
43
|
+
{
|
|
44
|
+
[_impl configure:config resolver:resolve rejecter:reject];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
- (void)setUser:(NSDictionary *)user
|
|
48
|
+
{
|
|
49
|
+
[_impl setUser:user];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
- (void)initialize:(RCTPromiseResolveBlock)resolve
|
|
53
|
+
reject:(RCTPromiseRejectBlock)reject
|
|
54
|
+
{
|
|
55
|
+
[_impl initializeWithResolver:resolve rejecter:reject];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
- (void)openChat { [_impl openChat]; }
|
|
59
|
+
- (void)closeChat { [_impl closeChat]; }
|
|
60
|
+
- (void)destroy { [_impl destroy]; }
|
|
61
|
+
|
|
62
|
+
- (void)sendMessage:(NSString *)text { [_impl sendMessage:text]; }
|
|
63
|
+
- (void)retryMessage:(NSString *)messageId { [_impl retryMessage:messageId]; }
|
|
64
|
+
|
|
65
|
+
- (void)attachFile:(NSDictionary *)opts
|
|
66
|
+
resolve:(RCTPromiseResolveBlock)resolve
|
|
67
|
+
reject:(RCTPromiseRejectBlock)reject
|
|
68
|
+
{
|
|
69
|
+
[_impl attachFile:opts resolver:resolve rejecter:reject];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// NativeEventEmitter wires these so it can track subscriber counts. The
|
|
73
|
+
// actual event payloads flow through RCTDeviceEventEmitter, so these are
|
|
74
|
+
// intentional no-ops.
|
|
75
|
+
- (void)addListener:(NSString *)eventName { }
|
|
76
|
+
- (void)removeListeners:(double)count { }
|
|
77
|
+
|
|
78
|
+
// MARK: - Event emission helper (called from Swift)
|
|
79
|
+
|
|
80
|
+
- (void)emitEvent:(NSString *)name body:(id)body
|
|
81
|
+
{
|
|
82
|
+
// RCTDeviceEventEmitter is registered on every RN bridge instance.
|
|
83
|
+
// `enqueueJSCall:method:args:completion:` works in both bridge mode
|
|
84
|
+
// and bridgeless mode (the new arch shims it). The `bridge` property
|
|
85
|
+
// is synthesised by RCT_EXPORT_MODULE; nil in unusual bridgeless
|
|
86
|
+
// setups where the module is constructed without an attached bridge.
|
|
87
|
+
if (!self.bridge) return;
|
|
88
|
+
id payload = body ?: [NSNull null];
|
|
89
|
+
[self.bridge enqueueJSCall:@"RCTDeviceEventEmitter"
|
|
90
|
+
method:@"emit"
|
|
91
|
+
args:@[name, payload]
|
|
92
|
+
completion:NULL];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// MARK: - TurboModule registration
|
|
96
|
+
|
|
97
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
98
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
99
|
+
{
|
|
100
|
+
return std::make_shared<facebook::react::NativeNewinstanceChatSpecJSI>(params);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@end
|