2025-11-17 16:32:44 +08:00
|
|
|
|
import ExpoModulesCore
|
|
|
|
|
|
|
2025-11-17 17:28:44 +08:00
|
|
|
|
// 导入Twilio Conversations SDK
|
|
|
|
|
|
import TwilioConversationsClient
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
2025-11-17 16:32:44 +08:00
|
|
|
|
public func definition() -> ModuleDefinition {
|
2025-11-17 17:28:44 +08:00
|
|
|
|
// 设置模块名称
|
2025-11-17 16:32:44 +08:00
|
|
|
|
Name("MyNativeModule")
|
|
|
|
|
|
|
2025-11-17 17:28:44 +08:00
|
|
|
|
// 定义常量
|
2025-11-17 16:32:44 +08:00
|
|
|
|
Constant("PI") {
|
|
|
|
|
|
Double.pi
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-17 17:28:44 +08:00
|
|
|
|
// 定义模块可以发送到JavaScript的事件
|
|
|
|
|
|
Events(
|
|
|
|
|
|
"onChange",
|
|
|
|
|
|
"onClientSynchronized",
|
|
|
|
|
|
"onClientError",
|
|
|
|
|
|
"onConnectionStateChanged",
|
|
|
|
|
|
"onConversationAdded",
|
|
|
|
|
|
"onConversationUpdated",
|
|
|
|
|
|
"onConversationDeleted",
|
|
|
|
|
|
"onMessageAdded",
|
|
|
|
|
|
"onMessageUpdated",
|
|
|
|
|
|
"onMessageDeleted",
|
|
|
|
|
|
"onParticipantAdded",
|
|
|
|
|
|
"onParticipantUpdated",
|
|
|
|
|
|
"onParticipantDeleted"
|
|
|
|
|
|
)
|
2025-11-17 16:32:44 +08:00
|
|
|
|
|
2025-11-17 17:28:44 +08:00
|
|
|
|
// 同步函数
|
2025-11-17 16:32:44 +08:00
|
|
|
|
Function("hello") {
|
|
|
|
|
|
return "Hello world! 👋"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-17 17:28:44 +08:00
|
|
|
|
// 原有异步函数
|
2025-11-17 16:32:44 +08:00
|
|
|
|
AsyncFunction("setValueAsync") { (value: String) in
|
|
|
|
|
|
// Send an event to JavaScript.
|
|
|
|
|
|
self.sendEvent("onChange", [
|
|
|
|
|
|
"value": value
|
|
|
|
|
|
])
|
|
|
|
|
|
}
|
2025-11-17 17:28:44 +08:00
|
|
|
|
|
|
|
|
|
|
// 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")
|
|
|
|
|
|
}
|
2025-11-17 16:32:44 +08:00
|
|
|
|
|
2025-11-17 17:28:44 +08:00
|
|
|
|
// 原生视图定义
|
2025-11-17 16:32:44 +08:00
|
|
|
|
View(MyNativeModuleView.self) {
|
2025-11-17 17:28:44 +08:00
|
|
|
|
// 定义url属性的setter
|
2025-11-17 16:32:44 +08:00
|
|
|
|
Prop("url") { (view: MyNativeModuleView, url: URL) in
|
|
|
|
|
|
if view.webView.url != url {
|
|
|
|
|
|
view.webView.load(URLRequest(url: url))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Events("onLoad")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-17 17:28:44 +08:00
|
|
|
|
|
|
|
|
|
|
// 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])
|
|
|
|
|
|
}
|
2025-11-17 16:32:44 +08:00
|
|
|
|
}
|