Update Twilio SDK to 6.2.0 and fix compatibility issues
This commit is contained in:
parent
4aebc41fec
commit
720cbbad99
256
dist/_expo/static/js/web/index-88190fe3c7e11f155ccc52a72d821e73.js
vendored
Normal file
256
dist/_expo/static/js/web/index-88190fe3c7e11f155ccc52a72d821e73.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
dist/favicon.ico
vendored
Normal file
BIN
dist/favicon.ico
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
37
dist/index.html
vendored
Normal file
37
dist/index.html
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||||
|
<title>TwilioDemo</title>
|
||||||
|
<!-- The `react-native-web` recommended style reset: https://necolas.github.io/react-native-web/docs/setup/#root-element -->
|
||||||
|
<style id="expo-reset">
|
||||||
|
/* These styles make the body full-height */
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
/* These styles disable body scrolling if you are using <ScrollView> */
|
||||||
|
body {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
/* These styles make the root element full-height */
|
||||||
|
#root {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<link rel="icon" href="/favicon.ico" /></head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<!-- Use static rendering with Expo Router to support running without JavaScript. -->
|
||||||
|
<noscript>
|
||||||
|
You need to enable JavaScript to run this app.
|
||||||
|
</noscript>
|
||||||
|
<!-- The root element for your Expo app. -->
|
||||||
|
<div id="root"></div>
|
||||||
|
<script src="/_expo/static/js/web/index-88190fe3c7e11f155ccc52a72d821e73.js" defer></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1
dist/metadata.json
vendored
Normal file
1
dist/metadata.json
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"version":0,"bundler":"metro","fileMetadata":{}}
|
||||||
@ -31,6 +31,12 @@ if (useManagedAndroidSdkVersions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace "expo.modules.mynativemodule"
|
namespace "expo.modules.mynativemodule"
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
@ -43,6 +49,5 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// 添加Twilio AAR文件依赖
|
implementation 'com.twilio:conversations-android:6.2.0'
|
||||||
implementation(files("libs/twilio-conversations-android-6.0.4.aar"))
|
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@ -3,14 +3,32 @@ package expo.modules.mynativemodule
|
|||||||
import expo.modules.kotlin.modules.Module
|
import expo.modules.kotlin.modules.Module
|
||||||
import expo.modules.kotlin.modules.ModuleDefinition
|
import expo.modules.kotlin.modules.ModuleDefinition
|
||||||
import expo.modules.kotlin.Promise
|
import expo.modules.kotlin.Promise
|
||||||
import java.net.URL
|
import expo.modules.kotlin.AppContext
|
||||||
|
import android.content.Context
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
// Twilio SDK 6.2.0 导入
|
||||||
|
import com.twilio.conversations.ConversationsClient
|
||||||
|
import com.twilio.conversations.Conversation
|
||||||
|
import com.twilio.conversations.ErrorInfo
|
||||||
|
import com.twilio.conversations.CallbackListener
|
||||||
|
import com.twilio.conversations.ConversationsClientListener
|
||||||
|
import com.twilio.conversations.User
|
||||||
|
import com.twilio.conversations.StatusListener
|
||||||
|
|
||||||
class MyNativeModule : Module() {
|
class MyNativeModule : Module() {
|
||||||
|
|
||||||
// 模拟初始化状态
|
// Twilio Conversations 客户端
|
||||||
|
private var conversationsClient: ConversationsClient? = null
|
||||||
|
// 初始化状态
|
||||||
private var isInitialized = false
|
private var isInitialized = false
|
||||||
// 模拟对话列表
|
// 应用上下文
|
||||||
private val mockConversations = mutableListOf<Map<String, Any>>()
|
private val context: Context
|
||||||
|
get() = appContext.reactContext ?: throw IllegalStateException("React Application Context is not available")
|
||||||
|
// 协程作用域,用于异步操作
|
||||||
|
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
||||||
|
|
||||||
// Each module class must implement the definition function. The definition consists of components
|
// Each module class must implement the definition function. The definition consists of components
|
||||||
// that describes the module's functionality and behavior.
|
// that describes the module's functionality and behavior.
|
||||||
@ -43,78 +61,195 @@ class MyNativeModule : Module() {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模拟初始化方法
|
// 初始化 Twilio Conversations 客户端
|
||||||
AsyncFunction("initialize") { token: String, promise: Promise ->
|
AsyncFunction("initialize") { token: String, promise: Promise ->
|
||||||
try {
|
try {
|
||||||
// 模拟初始化延迟
|
if (token.isEmpty()) {
|
||||||
Thread.sleep(1000)
|
promise.reject("INVALID_TOKEN", "Token cannot be empty", null)
|
||||||
|
|
||||||
// 模拟初始化成功
|
|
||||||
isInitialized = true
|
|
||||||
|
|
||||||
// 添加一些模拟对话
|
|
||||||
mockConversations.clear()
|
|
||||||
mockConversations.add(mapOf(
|
|
||||||
"sid" to "CH001",
|
|
||||||
"friendlyName" to "示例对话1"
|
|
||||||
))
|
|
||||||
mockConversations.add(mapOf(
|
|
||||||
"sid" to "CH002",
|
|
||||||
"friendlyName" to "示例对话2"
|
|
||||||
))
|
|
||||||
|
|
||||||
promise.resolve("客户端已成功初始化(模拟)")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// 忽略异常
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模拟获取会话列表方法
|
|
||||||
AsyncFunction("getConversations") { promise: Promise ->
|
|
||||||
try {
|
|
||||||
if (!isInitialized) {
|
|
||||||
return@AsyncFunction
|
return@AsyncFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模拟获取对话延迟
|
// 使用 Twilio SDK 6.2.0 兼容的 API 创建客户端
|
||||||
Thread.sleep(500)
|
ConversationsClient.create(context, token, object : CallbackListener<ConversationsClient>() {
|
||||||
|
override fun onSuccess(client: ConversationsClient) {
|
||||||
// 返回模拟对话列表
|
conversationsClient = client
|
||||||
promise.resolve(mockConversations)
|
isInitialized = true
|
||||||
|
|
||||||
|
// 注册客户端监听器
|
||||||
|
client.addListener(object : ConversationsClientListener {
|
||||||
|
override fun onClientSynchronization(status: ConversationsClient.SynchronizationStatus) {
|
||||||
|
if (status == ConversationsClient.SynchronizationStatus.COMPLETED) {
|
||||||
|
sendEvent("onClientSynchronized", mapOf("status" to "synchronized"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(errorInfo: ErrorInfo) {
|
||||||
|
sendEvent("onClientFailed", mapOf(
|
||||||
|
"errorCode" to errorInfo.code,
|
||||||
|
"errorMessage" to errorInfo.message
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConversationAdded(conversation: Conversation) {
|
||||||
|
sendEvent("onConversationAdded", mapOf(
|
||||||
|
"sid" to conversation.sid,
|
||||||
|
"friendlyName" to (conversation.friendlyName ?: conversation.sid)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConversationUpdated(conversation: Conversation, reason: Conversation.UpdateReason) {
|
||||||
|
sendEvent("onConversationUpdated", mapOf(
|
||||||
|
"sid" to conversation.sid,
|
||||||
|
"friendlyName" to (conversation.friendlyName ?: conversation.sid)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConversationDeleted(conversation: Conversation) {
|
||||||
|
sendEvent("onConversationDeleted", mapOf("sid" to conversation.sid))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实现所有必需的接口方法
|
||||||
|
override fun onConversationSynchronizationChange(conversation: Conversation) {}
|
||||||
|
override fun onUserSubscribed(user: User) {}
|
||||||
|
override fun onUserUnsubscribed(user: User) {}
|
||||||
|
override fun onUserUpdated(user: User, reason: User.UpdateReason) {}
|
||||||
|
override fun onNewMessageNotification(conversationSid: String, messageSid: String, messageIndex: Long) {}
|
||||||
|
override fun onAddedToConversationNotification(conversationSid: String) {}
|
||||||
|
override fun onRemovedFromConversationNotification(conversationSid: String) {}
|
||||||
|
override fun onNotificationSubscribed() {}
|
||||||
|
override fun onNotificationFailed(errorInfo: ErrorInfo) {}
|
||||||
|
override fun onConnectionStateChange(state: ConversationsClient.ConnectionState) {}
|
||||||
|
override fun onTokenExpired() {}
|
||||||
|
override fun onTokenAboutToExpire() {}
|
||||||
|
})
|
||||||
|
|
||||||
|
promise.resolve("Twilio Conversations 客户端已成功初始化")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(errorInfo: ErrorInfo) {
|
||||||
|
// 初始化失败
|
||||||
|
promise.reject("INITIALIZATION_FAILED", "初始化失败: ${errorInfo.message}", null)
|
||||||
|
}
|
||||||
|
})
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// 忽略异常
|
promise.reject("INITIALIZATION_EXCEPTION", "初始化异常: ${e.message}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模拟创建对话方法
|
// 获取所有对话列表
|
||||||
|
AsyncFunction("getConversations") { promise: Promise ->
|
||||||
|
try {
|
||||||
|
if (!isInitialized || conversationsClient == null) {
|
||||||
|
promise.reject("NOT_INITIALIZED", "Twilio Conversations 客户端未初始化", null)
|
||||||
|
return@AsyncFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 Twilio SDK 6.2.0 兼容的 API 获取对话列表
|
||||||
|
conversationsClient!!.getConversations(object : CallbackListener<List<Conversation>>() {
|
||||||
|
override fun onSuccess(conversationList: List<Conversation>) {
|
||||||
|
val conversations = mutableListOf<Map<String, Any>>()
|
||||||
|
|
||||||
|
// 遍历对话列表并转换为可序列化的格式
|
||||||
|
for (conversation in conversationList) {
|
||||||
|
val convData = mapOf(
|
||||||
|
"sid" to conversation.sid,
|
||||||
|
"friendlyName" to (conversation.friendlyName ?: conversation.sid),
|
||||||
|
"dateCreated" to conversation.dateCreated.toString(),
|
||||||
|
"dateUpdated" to conversation.dateUpdated.toString()
|
||||||
|
)
|
||||||
|
conversations.add(convData)
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.resolve(conversations)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(errorInfo: ErrorInfo) {
|
||||||
|
promise.reject("GET_CONVERSATIONS_FAILED", "获取对话列表失败: ${errorInfo.message}", null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e: Exception) {
|
||||||
|
promise.reject("GET_CONVERSATIONS_EXCEPTION", "获取对话列表异常: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建对话方法
|
||||||
AsyncFunction("createConversation") { name: String, promise: Promise ->
|
AsyncFunction("createConversation") { name: String, promise: Promise ->
|
||||||
try {
|
try {
|
||||||
if (!isInitialized) {
|
if (!isInitialized || conversationsClient == null) {
|
||||||
|
promise.reject("NOT_INITIALIZED", "客户端未初始化", null)
|
||||||
return@AsyncFunction
|
return@AsyncFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name.trim().isEmpty()) {
|
if (name.trim().isEmpty()) {
|
||||||
|
promise.reject("INVALID_NAME", "对话名称不能为空", null)
|
||||||
return@AsyncFunction
|
return@AsyncFunction
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模拟创建对话延迟
|
// 使用 Twilio SDK 6.2.0 的兼容 API 创建对话
|
||||||
Thread.sleep(800)
|
conversationsClient!!.createConversation(name, object : CallbackListener<Conversation>() {
|
||||||
|
override fun onSuccess(conversation: Conversation) {
|
||||||
|
promise.resolve(mapOf(
|
||||||
|
"status" to "success",
|
||||||
|
"friendlyName" to name,
|
||||||
|
"sid" to conversation.sid
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(errorInfo: ErrorInfo) {
|
||||||
|
promise.reject("CREATE_CONVERSATION_ERROR", "创建对话失败: ${errorInfo.message}", null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e: Exception) {
|
||||||
|
promise.reject("CREATE_CONVERSATION_EXCEPTION", "创建对话异常: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取客户端连接状态
|
||||||
|
AsyncFunction("getClientStatus") { promise: Promise ->
|
||||||
|
try {
|
||||||
|
if (!isInitialized || conversationsClient == null) {
|
||||||
|
promise.reject("NOT_INITIALIZED", "Twilio Conversations 客户端未初始化", null)
|
||||||
|
return@AsyncFunction
|
||||||
|
}
|
||||||
|
|
||||||
// 生成随机SID
|
// 使用 Twilio SDK 6.2.0 兼容的 API 获取连接状态
|
||||||
val newSid = "CH${System.currentTimeMillis()}"
|
val connectionState = conversationsClient!!.connectionState
|
||||||
|
val status = when (connectionState) {
|
||||||
|
ConversationsClient.ConnectionState.CONNECTING -> "connecting"
|
||||||
|
ConversationsClient.ConnectionState.CONNECTED -> "connected"
|
||||||
|
ConversationsClient.ConnectionState.DISCONNECTING -> "disconnecting"
|
||||||
|
ConversationsClient.ConnectionState.DISCONNECTED -> "disconnected"
|
||||||
|
ConversationsClient.ConnectionState.ERROR -> "error"
|
||||||
|
else -> "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
// 创建新对话
|
val result = mapOf(
|
||||||
val newConversation = mapOf(
|
"status" to status,
|
||||||
"sid" to newSid,
|
"connectionState" to connectionState.toString()
|
||||||
"friendlyName" to name
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 添加到模拟对话列表
|
promise.resolve(result)
|
||||||
mockConversations.add(newConversation)
|
|
||||||
|
|
||||||
promise.resolve(newConversation)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// 忽略异常
|
promise.reject("GET_CLIENT_STATUS_EXCEPTION", "获取客户端状态异常: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 断开客户端连接
|
||||||
|
AsyncFunction("disconnect") { promise: Promise ->
|
||||||
|
try {
|
||||||
|
if (!isInitialized || conversationsClient == null) {
|
||||||
|
promise.resolve("already_disconnected")
|
||||||
|
return@AsyncFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
// 断开连接
|
||||||
|
conversationsClient!!.shutdown()
|
||||||
|
isInitialized = false
|
||||||
|
conversationsClient = null
|
||||||
|
|
||||||
|
promise.resolve("disconnected")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
promise.reject("DISCONNECT_EXCEPTION", "断开连接异常: ${e.message}", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,12 +4,6 @@
|
|||||||
"modules": ["MyNativeModule"]
|
"modules": ["MyNativeModule"]
|
||||||
},
|
},
|
||||||
"android": {
|
"android": {
|
||||||
"gradleAarProjects": [
|
|
||||||
{
|
|
||||||
"name": "twilio-conversations-android",
|
|
||||||
"aarFilePath": "android/libs/twilio-conversations-android-6.0.4.aar"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"modules": ["expo.modules.mynativemodule.MyNativeModule"]
|
"modules": ["expo.modules.mynativemodule.MyNativeModule"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,41 +1,205 @@
|
|||||||
import ExpoModulesCore
|
import ExpoModulesCore
|
||||||
|
|
||||||
public class MyNativeModule: Module {
|
// 导入Twilio Conversations SDK
|
||||||
// Each module class must implement the definition function. The definition consists of components
|
import TwilioConversationsClient
|
||||||
// that describes the module's functionality and behavior.
|
|
||||||
// See https://docs.expo.dev/modules/module-api for more details about available components.
|
public class MyNativeModule: Module, TwilioConversationsClientDelegate {
|
||||||
|
// 存储Twilio客户端实例
|
||||||
|
private var conversationsClient: TwilioConversationsClient?
|
||||||
|
|
||||||
|
// 初始化时创建的Promise解析器,用于异步操作
|
||||||
|
private var initCompletion: PromiseCompletionBlock?
|
||||||
|
private var createConversationCompletion: PromiseCompletionBlock?
|
||||||
|
|
||||||
|
// Each module class must implement the definition function.
|
||||||
public func definition() -> ModuleDefinition {
|
public func definition() -> ModuleDefinition {
|
||||||
// Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
|
// 设置模块名称
|
||||||
// Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
|
|
||||||
// The module will be accessible from `requireNativeModule('MyNativeModule')` in JavaScript.
|
|
||||||
Name("MyNativeModule")
|
Name("MyNativeModule")
|
||||||
|
|
||||||
// Defines constant property on the module.
|
// 定义常量
|
||||||
Constant("PI") {
|
Constant("PI") {
|
||||||
Double.pi
|
Double.pi
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defines event names that the module can send to JavaScript.
|
// 定义模块可以发送到JavaScript的事件
|
||||||
Events("onChange")
|
Events(
|
||||||
|
"onChange",
|
||||||
|
"onClientSynchronized",
|
||||||
|
"onClientError",
|
||||||
|
"onConnectionStateChanged",
|
||||||
|
"onConversationAdded",
|
||||||
|
"onConversationUpdated",
|
||||||
|
"onConversationDeleted",
|
||||||
|
"onMessageAdded",
|
||||||
|
"onMessageUpdated",
|
||||||
|
"onMessageDeleted",
|
||||||
|
"onParticipantAdded",
|
||||||
|
"onParticipantUpdated",
|
||||||
|
"onParticipantDeleted"
|
||||||
|
)
|
||||||
|
|
||||||
// Defines a JavaScript synchronous function that runs the native code on the JavaScript thread.
|
// 同步函数
|
||||||
Function("hello") {
|
Function("hello") {
|
||||||
return "Hello world! 👋"
|
return "Hello world! 👋"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defines a JavaScript function that always returns a Promise and whose native code
|
// 原有异步函数
|
||||||
// is by default dispatched on the different thread than the JavaScript runtime runs on.
|
|
||||||
AsyncFunction("setValueAsync") { (value: String) in
|
AsyncFunction("setValueAsync") { (value: String) in
|
||||||
// Send an event to JavaScript.
|
// Send an event to JavaScript.
|
||||||
self.sendEvent("onChange", [
|
self.sendEvent("onChange", [
|
||||||
"value": value
|
"value": value
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Twilio Conversations 客户端初始化
|
||||||
|
AsyncFunction("initialize") { (token: String, completion: @escaping PromiseCompletionBlock) -> Void in
|
||||||
|
if token.isEmpty {
|
||||||
|
completion(["error": "Token cannot be empty"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存完成回调
|
||||||
|
self.initCompletion = completion
|
||||||
|
|
||||||
|
// 创建Twilio客户端实例
|
||||||
|
TwilioConversationsClient.conversationsClient(withToken: token, properties: nil, delegate: self) {
|
||||||
|
result, error in
|
||||||
|
|
||||||
|
if let error = error {
|
||||||
|
self.sendEvent("onClientError", ["error": error.localizedDescription])
|
||||||
|
self.initCompletion?(nil, error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let client = result {
|
||||||
|
self.conversationsClient = client
|
||||||
|
|
||||||
|
// 发送成功消息
|
||||||
|
self.initCompletion?("Twilio Conversations 客户端已成功初始化", nil)
|
||||||
|
} else {
|
||||||
|
self.initCompletion?(["error": "Failed to initialize client"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取所有对话列表
|
||||||
|
AsyncFunction("getConversations") { (completion: @escaping PromiseCompletionBlock) -> Void in
|
||||||
|
guard let client = self.conversationsClient else {
|
||||||
|
completion(["error": "客户端未初始化"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取对话列表
|
||||||
|
client.conversations() { result, error in
|
||||||
|
if let error = error {
|
||||||
|
completion(nil, error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let conversations = result {
|
||||||
|
// 转换为JS友好的格式
|
||||||
|
let jsConversations = conversations.map { conversation -> [String: Any] in
|
||||||
|
var conversationDict: [String: Any] = [
|
||||||
|
"sid": conversation.sid,
|
||||||
|
"friendlyName": conversation.friendlyName ?? "未命名对话"
|
||||||
|
]
|
||||||
|
|
||||||
|
// 添加时间戳信息(如果可用)
|
||||||
|
if let dateCreated = conversation.dateCreated {
|
||||||
|
conversationDict["dateCreated"] = Int(dateCreated.timeIntervalSince1970 * 1000)
|
||||||
|
}
|
||||||
|
if let dateUpdated = conversation.dateUpdated {
|
||||||
|
conversationDict["dateUpdated"] = Int(dateUpdated.timeIntervalSince1970 * 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conversationDict
|
||||||
|
}
|
||||||
|
|
||||||
|
completion(jsConversations, nil)
|
||||||
|
} else {
|
||||||
|
completion([], nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新对话
|
||||||
|
AsyncFunction("createConversation") { (friendlyName: String, completion: @escaping PromiseCompletionBlock) -> Void in
|
||||||
|
guard let client = self.conversationsClient else {
|
||||||
|
completion(["error": "客户端未初始化"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if friendlyName.isEmpty {
|
||||||
|
completion(["error": "对话名称不能为空"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存完成回调
|
||||||
|
self.createConversationCompletion = completion
|
||||||
|
|
||||||
|
// 创建对话
|
||||||
|
client.createConversation(options: ["friendlyName": friendlyName]) { result, error in
|
||||||
|
if let error = error {
|
||||||
|
completion(nil, error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let conversation = result {
|
||||||
|
// 返回创建状态和名称
|
||||||
|
completion(["status": "creating", "friendlyName": conversation.friendlyName ?? friendlyName], nil)
|
||||||
|
|
||||||
|
// 发送事件通知
|
||||||
|
self.sendEvent("onConversationAdded", [
|
||||||
|
"sid": conversation.sid,
|
||||||
|
"friendlyName": conversation.friendlyName ?? friendlyName
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
completion(["error": "创建对话失败"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取客户端状态
|
||||||
|
AsyncFunction("getClientStatus") { (completion: @escaping PromiseCompletionBlock) -> Void in
|
||||||
|
guard let client = self.conversationsClient else {
|
||||||
|
completion("not_initialized")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据连接状态返回相应的状态字符串
|
||||||
|
switch client.connectionState {
|
||||||
|
case .connecting:
|
||||||
|
completion("connecting")
|
||||||
|
case .connected:
|
||||||
|
completion("connected")
|
||||||
|
case .disconnected:
|
||||||
|
completion("disconnected")
|
||||||
|
case .denied:
|
||||||
|
completion("denied")
|
||||||
|
case .error:
|
||||||
|
completion("error")
|
||||||
|
@unknown default:
|
||||||
|
completion("unknown")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 断开客户端连接
|
||||||
|
AsyncFunction("disconnect") { (completion: @escaping PromiseCompletionBlock) -> Void in
|
||||||
|
guard let client = self.conversationsClient else {
|
||||||
|
completion("already_disconnected")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 断开连接
|
||||||
|
client.shutdown()
|
||||||
|
self.conversationsClient = nil
|
||||||
|
|
||||||
|
completion("disconnected")
|
||||||
|
}
|
||||||
|
|
||||||
// Enables the module to be used as a native view. Definition components that are accepted as part of the
|
// 原生视图定义
|
||||||
// view definition: Prop, Events.
|
|
||||||
View(MyNativeModuleView.self) {
|
View(MyNativeModuleView.self) {
|
||||||
// Defines a setter for the `url` prop.
|
// 定义url属性的setter
|
||||||
Prop("url") { (view: MyNativeModuleView, url: URL) in
|
Prop("url") { (view: MyNativeModuleView, url: URL) in
|
||||||
if view.webView.url != url {
|
if view.webView.url != url {
|
||||||
view.webView.load(URLRequest(url: url))
|
view.webView.load(URLRequest(url: url))
|
||||||
@ -45,4 +209,121 @@ public class MyNativeModule: Module {
|
|||||||
Events("onLoad")
|
Events("onLoad")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - TwilioConversationsClientDelegate方法
|
||||||
|
|
||||||
|
// 客户端同步完成
|
||||||
|
public func conversationsClientDidSynchronize(_ client: TwilioConversationsClient) {
|
||||||
|
sendEvent("onClientSynchronized", ["status": "synchronized"])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端连接状态变化
|
||||||
|
public func conversationsClient(_ client: TwilioConversationsClient, connectionStateChanged state: TwilioConversationsClient.ConnectionState) {
|
||||||
|
var stateString: String
|
||||||
|
|
||||||
|
switch state {
|
||||||
|
case .connecting:
|
||||||
|
stateString = "connecting"
|
||||||
|
case .connected:
|
||||||
|
stateString = "connected"
|
||||||
|
case .disconnected:
|
||||||
|
stateString = "disconnected"
|
||||||
|
case .denied:
|
||||||
|
stateString = "denied"
|
||||||
|
case .error:
|
||||||
|
stateString = "error"
|
||||||
|
@unknown default:
|
||||||
|
stateString = "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
sendEvent("onConnectionStateChanged", ["state": stateString])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对话添加
|
||||||
|
public func conversationsClient(_ client: TwilioConversationsClient, conversationAdded conversation: TCHConversation) {
|
||||||
|
sendEvent("onConversationAdded", [
|
||||||
|
"sid": conversation.sid,
|
||||||
|
"friendlyName": conversation.friendlyName ?? "未命名对话"
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对话更新
|
||||||
|
public func conversationsClient(_ client: TwilioConversationsClient, conversationUpdated conversation: TCHConversation) {
|
||||||
|
sendEvent("onConversationUpdated", [
|
||||||
|
"sid": conversation.sid,
|
||||||
|
"friendlyName": conversation.friendlyName ?? "未命名对话"
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对话删除
|
||||||
|
public func conversationsClient(_ client: TwilioConversationsClient, conversationDeleted conversation: TCHConversation) {
|
||||||
|
sendEvent("onConversationDeleted", ["sid": conversation.sid])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 消息添加
|
||||||
|
public func conversationsClient(_ client: TwilioConversationsClient, conversation: TCHConversation, messageAdded message: TCHMessage) {
|
||||||
|
sendEvent("onMessageAdded", [
|
||||||
|
"conversationSid": conversation.sid,
|
||||||
|
"messageSid": message.sid
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 消息更新
|
||||||
|
public func conversationsClient(_ client: TwilioConversationsClient, conversation: TCHConversation, messageUpdated message: TCHMessage) {
|
||||||
|
sendEvent("onMessageUpdated", [
|
||||||
|
"conversationSid": conversation.sid,
|
||||||
|
"messageSid": message.sid
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 消息删除
|
||||||
|
public func conversationsClient(_ client: TwilioConversationsClient, conversation: TCHConversation, messageDeleted message: TCHMessage) {
|
||||||
|
sendEvent("onMessageDeleted", [
|
||||||
|
"conversationSid": conversation.sid,
|
||||||
|
"messageSid": message.sid
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 参与者添加
|
||||||
|
public func conversationsClient(_ client: TwilioConversationsClient, conversation: TCHConversation, participantAdded participant: TCHParticipant) {
|
||||||
|
sendEvent("onParticipantAdded", [
|
||||||
|
"conversationSid": conversation.sid,
|
||||||
|
"participantIdentity": participant.identity ?? ""
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 参与者更新
|
||||||
|
public func conversationsClient(_ client: TwilioConversationsClient, conversation: TCHConversation, participantUpdated participant: TCHParticipant) {
|
||||||
|
sendEvent("onParticipantUpdated", [
|
||||||
|
"conversationSid": conversation.sid,
|
||||||
|
"participantIdentity": participant.identity ?? ""
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 参与者删除
|
||||||
|
public func conversationsClient(_ client: TwilioConversationsClient, conversation: TCHConversation, participantDeleted participant: TCHParticipant) {
|
||||||
|
sendEvent("onParticipantDeleted", [
|
||||||
|
"conversationSid": conversation.sid,
|
||||||
|
"participantIdentity": participant.identity ?? ""
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户端错误
|
||||||
|
public func conversationsClient(_ client: TwilioConversationsClient, synchronizationStatusChanged status: TCHClientSynchronizationStatus) {
|
||||||
|
// 同步状态变化事件处理
|
||||||
|
var statusString: String
|
||||||
|
|
||||||
|
switch status {
|
||||||
|
case .started:
|
||||||
|
statusString = "started"
|
||||||
|
case .completed:
|
||||||
|
statusString = "completed"
|
||||||
|
case .failed:
|
||||||
|
statusString = "failed"
|
||||||
|
@unknown default:
|
||||||
|
statusString = "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
sendEvent("onClientSynchronized", ["status": statusString])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { NativeModule, requireNativeModule } from 'expo';
|
import { NativeModule, requireNativeModule } from 'expo';
|
||||||
|
|
||||||
import { MyNativeModuleEvents, ConversationOptions, Conversation, TwilioConversationsOptions } from './MyNativeModule.types';
|
import { MyNativeModuleEvents, ConversationOptions, Conversation, TwilioConversationsOptions, CreateConversationResponse, ClientStatus } from './MyNativeModule.types';
|
||||||
|
|
||||||
declare class MyNativeModule extends NativeModule<MyNativeModuleEvents> {
|
declare class MyNativeModule extends NativeModule<MyNativeModuleEvents> {
|
||||||
PI: number;
|
PI: number;
|
||||||
@ -11,19 +11,34 @@ declare class MyNativeModule extends NativeModule<MyNativeModuleEvents> {
|
|||||||
/**
|
/**
|
||||||
* 初始化Twilio Conversations客户端
|
* 初始化Twilio Conversations客户端
|
||||||
* @param token Twilio访问令牌
|
* @param token Twilio访问令牌
|
||||||
|
* @returns 初始化结果消息
|
||||||
*/
|
*/
|
||||||
initialize(token: string): Promise<{ status: string }>;
|
initialize(token: string): Promise<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建新对话
|
* 创建新对话
|
||||||
* @param friendlyName 对话的友好名称
|
* @param friendlyName 对话的友好名称
|
||||||
|
* @returns 创建对话的响应对象
|
||||||
*/
|
*/
|
||||||
createConversation(friendlyName: string): Promise<Conversation>;
|
createConversation(friendlyName: string): Promise<CreateConversationResponse>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有对话
|
* 获取所有对话
|
||||||
|
* @returns 对话列表
|
||||||
*/
|
*/
|
||||||
getConversations(): Promise<Conversation[]>;
|
getConversations(): Promise<Conversation[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取客户端连接状态
|
||||||
|
* @returns 客户端状态
|
||||||
|
*/
|
||||||
|
getClientStatus(): Promise<ClientStatus>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 断开客户端连接
|
||||||
|
* @returns 断开连接结果
|
||||||
|
*/
|
||||||
|
disconnect(): Promise<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This call loads the native module object from the JSI.
|
// This call loads the native module object from the JSI.
|
||||||
|
|||||||
@ -4,8 +4,46 @@ export type OnLoadEventPayload = {
|
|||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Twilio Conversations相关事件类型
|
||||||
export type MyNativeModuleEvents = {
|
export type MyNativeModuleEvents = {
|
||||||
onChange: (params: ChangeEventPayload) => void;
|
onChange: (params: ChangeEventPayload) => void;
|
||||||
|
onClientSynchronized: (params: { status: string }) => void;
|
||||||
|
onClientFailed: (params: { errorCode: number; errorMessage: string }) => void;
|
||||||
|
onConversationAdded: (params: { sid: string; friendlyName: string }) => void;
|
||||||
|
onConversationUpdated: (params: { sid: string; friendlyName: string }) => void;
|
||||||
|
onConversationDeleted: (params: { sid: string }) => void;
|
||||||
|
onNotificationNewMessage: (params: {
|
||||||
|
conversationSid: string;
|
||||||
|
messageSid: string;
|
||||||
|
author: string;
|
||||||
|
body: string;
|
||||||
|
}) => void;
|
||||||
|
onNotificationAddedToConversation: (params: {
|
||||||
|
sid: string;
|
||||||
|
friendlyName: string;
|
||||||
|
}) => void;
|
||||||
|
onNotificationRemovedFromConversation: (params: {
|
||||||
|
sid: string;
|
||||||
|
friendlyName: string;
|
||||||
|
}) => void;
|
||||||
|
onNotificationInvitedToConversation: (params: {
|
||||||
|
sid: string;
|
||||||
|
friendlyName: string;
|
||||||
|
}) => void;
|
||||||
|
onNotificationParticipantJoined: (params: {
|
||||||
|
conversationSid: string;
|
||||||
|
participantSid: string;
|
||||||
|
identity: string;
|
||||||
|
}) => void;
|
||||||
|
onNotificationParticipantLeft: (params: {
|
||||||
|
conversationSid: string;
|
||||||
|
participantSid: string;
|
||||||
|
identity: string;
|
||||||
|
}) => void;
|
||||||
|
onUserUpdated: (params: {
|
||||||
|
identity: string;
|
||||||
|
friendlyName: string;
|
||||||
|
}) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ChangeEventPayload = {
|
export type ChangeEventPayload = {
|
||||||
@ -26,8 +64,17 @@ export interface ConversationOptions {
|
|||||||
export interface Conversation {
|
export interface Conversation {
|
||||||
sid: string;
|
sid: string;
|
||||||
friendlyName: string;
|
friendlyName: string;
|
||||||
|
dateCreated?: number;
|
||||||
|
dateUpdated?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CreateConversationResponse {
|
||||||
|
status: string;
|
||||||
|
friendlyName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ClientStatus = 'connected' | 'connecting' | 'disconnected' | 'disconnecting' | 'not_initialized' | 'unknown';
|
||||||
|
|
||||||
export interface TwilioConversationsOptions {
|
export interface TwilioConversationsOptions {
|
||||||
// 未来可能的配置选项
|
// 未来可能的配置选项
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,19 +1,109 @@
|
|||||||
import { registerWebModule, NativeModule } from 'expo';
|
import { registerWebModule, NativeModule } from 'expo';
|
||||||
|
|
||||||
import { ChangeEventPayload } from './MyNativeModule.types';
|
import { MyNativeModuleEvents, Conversation, CreateConversationResponse, ClientStatus } from './MyNativeModule.types';
|
||||||
|
|
||||||
type MyNativeModuleEvents = {
|
|
||||||
onChange: (params: ChangeEventPayload) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyNativeModule extends NativeModule<MyNativeModuleEvents> {
|
class MyNativeModule extends NativeModule<MyNativeModuleEvents> {
|
||||||
PI = Math.PI;
|
PI = Math.PI;
|
||||||
|
private isInitialized = false;
|
||||||
|
private mockConversations: Conversation[] = [];
|
||||||
|
|
||||||
async setValueAsync(value: string): Promise<void> {
|
async setValueAsync(value: string): Promise<void> {
|
||||||
this.emit('onChange', { value });
|
this.emit('onChange', { value });
|
||||||
}
|
}
|
||||||
|
|
||||||
hello() {
|
hello() {
|
||||||
return 'Hello world! 👋';
|
return 'Hello world! 👋';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Twilio Conversations相关方法的Web模拟实现
|
||||||
|
async initialize(token: string): Promise<string> {
|
||||||
|
if (!token || token.trim() === '') {
|
||||||
|
throw new Error('Token cannot be empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟初始化延迟
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
|
||||||
|
// 模拟初始化成功
|
||||||
|
this.isInitialized = true;
|
||||||
|
|
||||||
|
// 添加一些模拟对话
|
||||||
|
this.mockConversations = [
|
||||||
|
{ sid: 'CH001', friendlyName: '示例对话1', dateCreated: Date.now() - 86400000, dateUpdated: Date.now() - 3600000 },
|
||||||
|
{ sid: 'CH002', friendlyName: '示例对话2', dateCreated: Date.now() - 172800000, dateUpdated: Date.now() - 7200000 }
|
||||||
|
];
|
||||||
|
|
||||||
|
// 模拟事件
|
||||||
|
setTimeout(() => {
|
||||||
|
this.emit('onClientSynchronized', { status: 'synchronized' });
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
return 'Twilio Conversations 客户端已成功初始化';
|
||||||
|
}
|
||||||
|
|
||||||
|
async createConversation(friendlyName: string): Promise<CreateConversationResponse> {
|
||||||
|
if (!this.isInitialized) {
|
||||||
|
throw new Error('客户端未初始化');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!friendlyName || friendlyName.trim() === '') {
|
||||||
|
throw new Error('对话名称不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟创建对话延迟
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 800));
|
||||||
|
|
||||||
|
// 模拟创建对话
|
||||||
|
const newConversation: Conversation = {
|
||||||
|
sid: `CH${Date.now()}`,
|
||||||
|
friendlyName,
|
||||||
|
dateCreated: Date.now(),
|
||||||
|
dateUpdated: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加到模拟对话列表
|
||||||
|
this.mockConversations.push(newConversation);
|
||||||
|
|
||||||
|
// 模拟事件
|
||||||
|
setTimeout(() => {
|
||||||
|
this.emit('onConversationAdded', {
|
||||||
|
sid: newConversation.sid,
|
||||||
|
friendlyName: newConversation.friendlyName
|
||||||
|
});
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
return { status: 'creating', friendlyName };
|
||||||
|
}
|
||||||
|
|
||||||
|
async getConversations(): Promise<Conversation[]> {
|
||||||
|
if (!this.isInitialized) {
|
||||||
|
throw new Error('客户端未初始化');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟获取对话延迟
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
|
||||||
|
return [...this.mockConversations];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getClientStatus(): Promise<ClientStatus> {
|
||||||
|
if (!this.isInitialized) {
|
||||||
|
return 'not_initialized';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'connected';
|
||||||
|
}
|
||||||
|
|
||||||
|
async disconnect(): Promise<string> {
|
||||||
|
if (!this.isInitialized) {
|
||||||
|
return 'already_disconnected';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isInitialized = false;
|
||||||
|
this.mockConversations = [];
|
||||||
|
|
||||||
|
return 'disconnected';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default registerWebModule(MyNativeModule, 'MyNativeModule');
|
export default registerWebModule(MyNativeModule, 'MyNativeModule');
|
||||||
|
|||||||
197
package-lock.json
generated
197
package-lock.json
generated
@ -13,7 +13,9 @@
|
|||||||
"expo-status-bar": "~3.0.8",
|
"expo-status-bar": "~3.0.8",
|
||||||
"my-native-module": "file:./modules/my-native-module",
|
"my-native-module": "file:./modules/my-native-module",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-native": "0.81.5"
|
"react-dom": "19.1.0",
|
||||||
|
"react-native": "0.81.5",
|
||||||
|
"react-native-web": "^0.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "~19.1.0",
|
"@types/react": "~19.1.0",
|
||||||
@ -4197,6 +4199,15 @@
|
|||||||
"url": "https://opencollective.com/core-js"
|
"url": "https://opencollective.com/core-js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cross-fetch": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": "^2.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
@ -4220,6 +4231,15 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/css-in-js-utils": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"hyphenate-style-name": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/csstype": {
|
"node_modules/csstype": {
|
||||||
"version": "3.2.2",
|
"version": "3.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.2.tgz",
|
||||||
@ -5103,6 +5123,36 @@
|
|||||||
"bser": "2.1.1"
|
"bser": "2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fbjs": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cross-fetch": "^3.1.5",
|
||||||
|
"fbjs-css-vars": "^1.0.0",
|
||||||
|
"loose-envify": "^1.0.0",
|
||||||
|
"object-assign": "^4.1.0",
|
||||||
|
"promise": "^7.1.1",
|
||||||
|
"setimmediate": "^1.0.5",
|
||||||
|
"ua-parser-js": "^1.0.35"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/fbjs-css-vars": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/fbjs/node_modules/promise": {
|
||||||
|
"version": "7.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||||
|
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asap": "~2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
@ -5402,6 +5452,12 @@
|
|||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hyphenate-style-name": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/ieee754": {
|
"node_modules/ieee754": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
@ -5478,6 +5534,15 @@
|
|||||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/inline-style-prefixer": {
|
||||||
|
"version": "7.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz",
|
||||||
|
"integrity": "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"css-in-js-utils": "^3.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/invariant": {
|
"node_modules/invariant": {
|
||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||||
@ -6973,6 +7038,26 @@
|
|||||||
"integrity": "sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==",
|
"integrity": "sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/node-fetch": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"whatwg-url": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"encoding": "^0.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"encoding": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-forge": {
|
"node_modules/node-forge": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
||||||
@ -7352,6 +7437,12 @@
|
|||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postcss-value-parser": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/pretty-bytes": {
|
"node_modules/pretty-bytes": {
|
||||||
"version": "5.6.0",
|
"version": "5.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
|
||||||
@ -7499,6 +7590,18 @@
|
|||||||
"ws": "^7"
|
"ws": "^7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-dom": {
|
||||||
|
"version": "19.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||||
|
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"scheduler": "^0.26.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^19.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||||
@ -7572,6 +7675,38 @@
|
|||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-native-web": {
|
||||||
|
"version": "0.21.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz",
|
||||||
|
"integrity": "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.18.6",
|
||||||
|
"@react-native/normalize-colors": "^0.74.1",
|
||||||
|
"fbjs": "^3.0.4",
|
||||||
|
"inline-style-prefixer": "^7.0.1",
|
||||||
|
"memoize-one": "^6.0.0",
|
||||||
|
"nullthrows": "^1.1.1",
|
||||||
|
"postcss-value-parser": "^4.2.0",
|
||||||
|
"styleq": "^0.1.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0 || ^19.0.0",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-native-web/node_modules/@react-native/normalize-colors": {
|
||||||
|
"version": "0.74.89",
|
||||||
|
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.89.tgz",
|
||||||
|
"integrity": "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/react-native-web/node_modules/memoize-one": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/react-native/node_modules/@react-native/virtualized-lists": {
|
"node_modules/react-native/node_modules/@react-native/virtualized-lists": {
|
||||||
"version": "0.81.5",
|
"version": "0.81.5",
|
||||||
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.81.5.tgz",
|
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.81.5.tgz",
|
||||||
@ -8035,6 +8170,12 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/setimmediate": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/setprototypeof": {
|
"node_modules/setprototypeof": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||||
@ -8326,6 +8467,12 @@
|
|||||||
"integrity": "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==",
|
"integrity": "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/styleq": {
|
||||||
|
"version": "0.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/styleq/-/styleq-0.1.3.tgz",
|
||||||
|
"integrity": "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/sucrase": {
|
"node_modules/sucrase": {
|
||||||
"version": "3.35.0",
|
"version": "3.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
||||||
@ -8600,6 +8747,12 @@
|
|||||||
"node": ">=0.6"
|
"node": ">=0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tr46": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ts-interface-checker": {
|
"node_modules/ts-interface-checker": {
|
||||||
"version": "0.1.13",
|
"version": "0.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
||||||
@ -8638,6 +8791,32 @@
|
|||||||
"node": ">=14.17"
|
"node": ">=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ua-parser-js": {
|
||||||
|
"version": "1.0.41",
|
||||||
|
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.41.tgz",
|
||||||
|
"integrity": "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/ua-parser-js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "paypal",
|
||||||
|
"url": "https://paypal.me/faisalman"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/faisalman"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"ua-parser-js": "script/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/undici": {
|
"node_modules/undici": {
|
||||||
"version": "6.22.0",
|
"version": "6.22.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz",
|
||||||
@ -8819,6 +8998,16 @@
|
|||||||
"integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==",
|
"integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/whatwg-url": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tr46": "~0.0.3",
|
||||||
|
"webidl-conversions": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/whatwg-url-without-unicode": {
|
"node_modules/whatwg-url-without-unicode": {
|
||||||
"version": "8.0.0-3",
|
"version": "8.0.0-3",
|
||||||
"resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz",
|
"resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz",
|
||||||
@ -8833,6 +9022,12 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/whatwg-url/node_modules/webidl-conversions": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||||
|
"license": "BSD-2-Clause"
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
|||||||
@ -14,7 +14,9 @@
|
|||||||
"expo-status-bar": "~3.0.8",
|
"expo-status-bar": "~3.0.8",
|
||||||
"my-native-module": "file:./modules/my-native-module",
|
"my-native-module": "file:./modules/my-native-module",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-native": "0.81.5"
|
"react-dom": "19.1.0",
|
||||||
|
"react-native": "0.81.5",
|
||||||
|
"react-native-web": "^0.21.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "~19.1.0",
|
"@types/react": "~19.1.0",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user