## Client API - FFI bindings for Client lifecycle and operations ## Uses the {.ffi.} pragma for async request handling import std/json import chronicles import chronos import ffi import src/chat import src/chat/delivery/waku_client import library/utils logScope: topics = "chat ffi client" ################################################# # Client Creation Request (for chat_new) ################################################# type ChatCallbacks* = object onNewMessage*: MessageCallback onNewConversation*: NewConvoCallback onDeliveryAck*: DeliveryAckCallback proc createChatClient( configJson: cstring, chatCallbacks: ChatCallbacks ): Future[Result[ChatClient, string]] {.async.} = try: let config = parseJson($configJson) # Parse identity name let name = config.getOrDefault("name").getStr("anonymous") # Parse Waku configuration or use defaults var wakuCfg = DefaultConfig() if config.hasKey("port"): wakuCfg.port = config["port"].getInt().uint16 if config.hasKey("clusterId"): wakuCfg.clusterId = config["clusterId"].getInt().uint16 if config.hasKey("shardId"): wakuCfg.shardId = @[config["shardId"].getInt().uint16] if config.hasKey("clusterId") or config.hasKey("shardId"): wakuCfg.pubsubTopic = "/waku/2/rs/" & $wakuCfg.clusterId & "/" & $wakuCfg.shardId[0] if config.hasKey("staticPeers"): wakuCfg.staticPeers = @[] for peer in config["staticPeers"]: wakuCfg.staticPeers.add(peer.getStr()) if config.hasKey("mixEnabled"): wakuCfg.mixEnabled = config["mixEnabled"].getBool(false) if config.hasKey("mixNodes"): wakuCfg.mixNodes = @[] for node in config["mixNodes"]: wakuCfg.mixNodes.add(node.getStr()) if config.hasKey("destPeerAddr"): wakuCfg.destPeerAddr = config["destPeerAddr"].getStr("") if config.hasKey("minMixPoolSize"): wakuCfg.minMixPoolSize = config["minMixPoolSize"].getInt(4) if config.hasKey("gifterNodeAddr"): wakuCfg.gifterNodeAddr = config["gifterNodeAddr"].getStr("") if config.hasKey("gifterAuthKey"): wakuCfg.gifterAuthKey = config["gifterAuthKey"].getStr("") # Create Waku client let wakuClient = initWakuClient(wakuCfg) # Create Chat client let client = ?newClient(wakuClient, installation_name = name) # Register event handlers client.onNewMessage(chatCallbacks.onNewMessage) client.onNewConversation(chatCallbacks.onNewConversation) client.onDeliveryAck(chatCallbacks.onDeliveryAck) notice "Chat client created", name = name return ok(client) except CatchableError as e: return err("failed to create client: " & e.msg) registerReqFFI(CreateClientRequest, ctx: ptr FFIContext[ChatClient]): proc( configJson: cstring, chatCallbacks: ChatCallbacks ): Future[Result[string, string]] {.async.} = ctx.myLib[] = (await createChatClient(configJson, chatCallbacks)).valueOr: error "CreateClientRequest failed", error = error return err($error) return ok("") ################################################# # ChatClient Lifecycle Operations ################################################# proc chat_start( ctx: ptr FFIContext[ChatClient], callback: FFICallBack, userData: pointer ) {.ffi.} = try: await ctx.myLib[].start() return ok("") except CatchableError as e: error "chat_start failed", error = e.msg return err("failed to start client: " & e.msg) proc chat_stop( ctx: ptr FFIContext[ChatClient], callback: FFICallBack, userData: pointer ) {.ffi.} = try: await ctx.myLib[].stop() return ok("") except CatchableError as e: error "chat_stop failed", error = e.msg return err("failed to stop client: " & e.msg) ################################################# # ChatClient Info Operations ################################################# proc chat_get_id( ctx: ptr FFIContext[ChatClient], callback: FFICallBack, userData: pointer ) {.ffi.} = ## Get the client's identifier let clientId = ctx.myLib[].getId() return ok(clientId) ################################################# # Mix Protocol Status ################################################# proc chat_get_mix_status( ctx: ptr FFIContext[ChatClient], callback: FFICallBack, userData: pointer ) {.ffi.} = let client = ctx.myLib[] let mixEnabled = client.ds.cfg.mixEnabled var poolSize = 0 if mixEnabled: poolSize = client.ds.getMixPoolSize() let status = %*{ "mixEnabled": mixEnabled, "mixReady": client.ds.mixReady, "mixPoolSize": poolSize, "minPoolSize": client.ds.cfg.minMixPoolSize } return ok($status) ################################################# # Conversation List Operations ################################################# proc chat_list_conversations( ctx: ptr FFIContext[ChatClient], callback: FFICallBack, userData: pointer ) {.ffi.} = ## List all conversations as JSON array let convos = ctx.myLib[].listConversations() var convoList = newJArray() for convo in convos: convoList.add(%*{"id": convo.id()}) return ok($convoList) proc chat_get_conversation( ctx: ptr FFIContext[ChatClient], callback: FFICallBack, userData: pointer, convoId: cstring ) {.ffi.} = ## Get a specific conversation by ID let convo = ctx.myLib[].getConversation($convoId) return ok($(%*{"id": convo.id()}))