OpenIM JSSDK (四) Wasm

目录结构

❯ tree wasm ws_wrapper
wasm
├── cmd
│   ├── Makefile
│   ├── main.go
│   └── static
│       └── wasm_exec.js
├── event_listener
│   ├── callback_writer.go
│   ├── caller.go
│   └── listener.go
├── exec
│   └── executor.go
├── indexdb
│   ├── black_model.go
│   ├── cache_message.go
│   ├── chat_log_model.go
│   ├── chat_log_reaction_extension_model.go
│   ├── conversation_model.go
│   ├── conversation_unread_message_model.go
│   ├── friend_model.go
│   ├── friend_request_model.go
│   ├── group_member_model.go
│   ├── group_model.go
│   ├── group_request.model.go
│   ├── init.go
│   ├── notification_model.go
│   ├── stranger_model.go
│   ├── super_group_chat_log_model.go
│   ├── super_group_model.go
│   ├── temp_struct
│   │   └── struct.go
│   ├── upload_model.go
│   └── user_model.go
└── wasm_wrapper
    ├── wasm_conversation_msg.go
    ├── wasm_friend.go
    ├── wasm_group.go
    ├── wasm_init_login.go
    ├── wasm_signaling.go
    ├── wasm_third.go
    └── wasm_user.go
ws_wrapper
├── cmd
│   ├── Makefile
│   ├── open_im_sdk_electron.go
│   ├── open_im_sdk_my.go
│   └── open_im_sdk_server.go
├── logs
│   └── sdk.all.2023-10-30
├── test
│   ├── client
│   │   └── client.go
│   ├── cmd
│   │   └── main.go
│   └── simulationClient.go
├── utils
│   ├── platform_number_id_to_name.go
│   └── strings.go
└── ws_local_server
    ├── constant.go
    ├── handle_func.go
    ├── ws_conversation_msg.go
    ├── ws_friend.go
    ├── ws_group.go
    ├── ws_handle.go
    ├── ws_init_login.go
    ├── ws_organization.go
    ├── ws_server.go
    ├── ws_signaling.go
    ├── ws_user.go
    └── ws_work_moments.go

代码模块解析

1. wasm

看起来这是一个WebAssembly (WASM)相关的代码库。WebAssembly是一种为Web设计的新的代码格式,能够以接近于本地速度在浏览器中运行。

  • cmd: 命令行工具或应用程序的主要文件。
    • Makefile: 构建脚本,通常用于编译和打包代码。
    • main.go: Go应用程序的入口点。
    • static/wasm_exec.js: WebAssembly的Go运行时所需的JavaScript代码。
  • event_listener: 监听并处理特定的事件或回调。
    • callback_writer.go, caller.go, listener.go: 这些文件可能与事件监听和回调执行相关。
  • exec: 执行或运行代码的模块。
    • executor.go: 可能用于执行某些操作或函数的代码。
  • indexdb: 看起来是与IndexedDB相关的模块,IndexedDB是一种在浏览器中存储结构化数据的方式。
    • 各种_model.go文件:定义数据库模型或结构的地方。
    • init.go: 初始化数据库或模型的地方。
    • temp_struct/struct.go: 临时的结构或模型定义。
  • wasm_wrapper: 提供了与WebAssembly交互的方法或函数。
    • 各种wasm_*.go文件:可能为不同的功能提供WASM封装或接口。

2. ws_wrapper

这似乎是一个WebSocket的封装或库,用于实现实时通信。

  • cmd: 命令行工具或应用程序的主要文件。
    • Makefile: 构建脚本。
    • open_im_sdk_*.go文件:各种版本的SDK的实现。
  • logs: 存储日志文件。
  • test: 包含测试客户端和模拟客户端的测试代码。
  • utils: 工具或帮助函数。
  • ws_local_server: WebSocket的本地服务器实现。
    • 各种ws_*.go文件:为不同的功能或模块提供WebSocket实现。

学习建议:

  1. 了解背景知识:首先,您需要对WebAssembly, Go, WebSocket, IndexedDB有一定的了解。这将帮助您更好地理解代码的意图。
  2. 开始于“main.go”:通常,main.go是应用程序的入口点,从这里开始可以帮助您了解代码的执行流程。
  3. 深入研究核心模块wasmws_wrapper 是两个核心模块。您应该重点关注这两个模块,尤其是与数据交互和通信的部分。
  4. 阅读测试代码test目录通常包含了如何使用该库或模块的示例。这可以帮助您了解如何实际使用它。
  5. 实践:尝试运行代码,进行一些基本的操作,如发送WebSocket消息,或使用WASM函数。实践是最好的学习方法。
  6. 编写文档和注释:当您对某个部分的代码有了理解后,尝试编写或完善文档和注释。这不仅可以帮助您巩固所学,还可以为其他开发者提供帮助。

执行 wasm

运行一个 .wasm 文件通常需要 WebAssembly 的环境和相应的 JavaScript 调用。下面是一个简单的方法,用于在浏览器中执行您的 openIM.wasm 文件:

1. 准备 JavaScript 代码

确保您有 wasm_exec.js,这是 Go 提供的用于在 WebAssembly 环境中与 Go 代码交互的 JavaScript 文件。

然后,编写一个简单的 HTML 文件 index.html 来加载并执行您的 .wasm 文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>OpenIM WASM Demo</title>
</head>
<body>
    <script src="wasm_exec.js"></script>
    <script>
        async function runWasm() {
            const go = new Go();
            const result = await WebAssembly.instantiateStreaming(fetch("openIM.wasm.wasm"), go.importObject);
            go.run(result.instance);
        }
        runWasm();
    </script>
</body>
</html>

2. 运行一个本地服务器

由于跨域限制,您通常需要在本地服务器上运行此HTML文件。您可以使用各种工具来快速启动一个本地服务器。例如,使用 Python 的简单HTTP服务器:

python3 -m http.server

或者,如果您有安装 Node.js 和 npm,可以使用 http-server

npm install -g http-server
http-server .

启动服务器后,打开浏览器并导航到 http://localhost:8000/(或您选择的其他端口),然后查看 openIM.wasm 的运行结果。

3. 调试

打开浏览器的开发者工具,查看控制台输出。这有助于您捕获和调试可能遇到的任何错误。

注意:由于 WebAssembly 的异步加载和执行性质,确保您的 Go 代码能够与此种方式兼容,并且您的 HTML 和 JS 能够正确调用 .wasm 文件中的函数或方法。

main 代码

继续开始 main 的代码:

func main() {
	defer func() {
		if r := recover(); r != nil {
			log.Error("MAIN", "panic info is:", r, string(debug.Stack()))
		}
	}()
	fmt.Println("runtime env", runtime.GOARCH, runtime.GOOS)
	registerFunc()
	<-make(chan bool)
}

func registerFunc() {
	//register global listener function
	globalFuc := wasm_wrapper.NewWrapperCommon()
	js.Global().Set(wasm_wrapper.COMMONEVENTFUNC, js.FuncOf(globalFuc.CommonEventFunc))
	//register init login function
	wrapperInitLogin := wasm_wrapper.NewWrapperInitLogin(globalFuc)
	js.Global().Set("initSDK", js.FuncOf(wrapperInitLogin.InitSDK))
	js.Global().Set("login", js.FuncOf(wrapperInitLogin.Login))
	js.Global().Set("logout", js.FuncOf(wrapperInitLogin.Logout))
	js.Global().Set("getLoginStatus", js.FuncOf(wrapperInitLogin.GetLoginStatus))
	js.Global().Set("setAppBackgroundStatus", js.FuncOf(wrapperInitLogin.SetAppBackgroundStatus))
	js.Global().Set("networkStatusChanged", js.FuncOf(wrapperInitLogin.NetworkStatusChanged))
}

这段代码是用 Go 编写的,并用于与 WebAssembly (Wasm) 进行交互。它基于 OpenIM SDK,旨在提供各种即时消息(IM)功能。以下是代码的简要解释:

  1. 构建标记:
    • //go:build js && wasm// +build js,wasm 是构建约束,确保此代码仅在 Go 与 JavaScript 和 WebAssembly 目标构建时使用。
  2. 引入包:
    • 导入了多个 Go 标准库和 OpenIM SDK 的库。
  3. 主函数:
    • main 函数首先设置了一个 panic 恢复机制,用于捕获和记录任何运行时的恐慌。
    • 打印出当前运行的架构和操作系统。
    • 调用 registerFunc 来注册各种功能。
    • 最后,使用 <-make(chan bool) 无限等待,确保主函数不会退出。
  4. 注册函数:
    • registerFunc 函数是此代码的核心,它将各种即时消息(IM)功能与 JavaScript 全局对象进行绑定,使得在浏览器环境中可以通过 JavaScript 调用它们。
    • 使用 wasm_wrapper 包创建了不同的函数“包装器”实例,如 NewWrapperCommon, NewWrapperInitLogin, NewWrapperConMsg 等。
    • 对于每个包装器,都将其方法注册为 JavaScript 可调用的函数。例如,js.Global().Set("initSDK", js.FuncOf(wrapperInitLogin.InitSDK)) 会将 initSDK 函数添加到全局 JavaScript 对象,允许 JS 调用该功能。
  5. 功能注册:
    • 代码中注册了大量的功能,如登录、消息创建、消息发送、获取会话列表、处理群组、用户、好友等。

由于代码过长,我无法解释每个函数的详细内容,但大体上,它涵盖了 IM SDK 提供的大部分功能,并允许在浏览器环境中使用 WebAssembly 调用这些功能。

结构体:WrapperCommon

type WrapperCommon struct {
	commonFunc *js.Value
}
  1. type WrapperCommon struct {...}: 定义了一个名为WrapperCommon的结构体。
  2. commonFunc *js.Value: 在这个结构体内,有一个名为commonFunc的字段,它的类型是一个指向js.Value的指针。这里,js.Value很可能来自于Go的WebAssembly绑定包,通常用于与JavaScript代码互操作。

函数:NewWrapperCommon

func NewWrapperCommon() *WrapperCommon {
	return &WrapperCommon{}
}
  1. func NewWrapperCommon() *WrapperCommon: 定义了一个名为NewWrapperCommon的函数,这个函数没有接受任何参数并返回一个指向WrapperCommon类型的指针。
  2. return &WrapperCommon{}: 这里,函数创建了一个新的WrapperCommon实例,并返回了该实例的指针。这是一个常见的做法,称为工厂方法或构造函数,用于创建和初始化一个新的对象实例。

这段代码简单的展示了如何定义一个结构体和如何为该结构体提供一个构造函数。但从这段代码中,我们还看不出WrapperCommon的实际用途,以及commonFunc字段的具体用法。要完全理解这部分代码,可能需要更多上下文或相关代码。

继续 listener 代码解释

这段代码定义了一个ConnCallback结构体和一系列相关的方法。我们从头开始逐一解释。

结构体定义

type ConnCallback struct {
	uid string
	CallbackWriter
}

这里定义了一个叫ConnCallback的结构体。它有两个成员变量:uid(字符串类型)和一个匿名的CallbackWriter成员(这意味着ConnCallback继承了CallbackWriter的所有字段和方法)。

构造函数

func NewConnCallback(funcName string, callback *js.Value) *ConnCallback {
	return &ConnCallback{CallbackWriter: NewEventData(callback).SetEvent(funcName)}
}

这个函数是ConnCallback的构造函数,用于创建ConnCallback的新实例。它接收一个函数名funcName和一个callback(可能是一个JavaScript的回调函数),然后它初始化CallbackWriter字段并返回新的ConnCallback实例。

方法定义

以下是ConnCallback的方法,这些方法通常会在不同的连接状态下被调用,如成功连接、连接失败等。

  1. OnConnecting()
func (i *ConnCallback) OnConnecting() {
	i.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SendMessage()
}

当开始连接时,此方法会被调用。它使用utils.GetSelfFuncName()来获取当前函数的名称,并设置为事件名,然后发送消息。

  1. OnConnectSuccess()
func (i *ConnCallback) OnConnectSuccess() {
	i.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SendMessage()
}

当连接成功时,此方法会被调用。同样的,它也设置当前函数的名称为事件名并发送消息。

  1. OnConnectFailed(errCode int32, errMsg string)
func (i *ConnCallback) OnConnectFailed(errCode int32, errMsg string) {
	i.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SetErrCode(errCode).SetErrMsg(errMsg).SendMessage()
}

当连接失败时,此方法会被调用。除了设置事件名,它还设置错误码和错误消息,然后发送。

  1. OnKickedOffline()OnUserTokenExpired(): 这两个方法与前面的方法类似,它们在特定的事件发生时被调用,并设置对应的事件名发送消息。
  2. OnSelfInfoUpdated(userInfo string)
func (i *ConnCallback) OnSelfInfoUpdated(userInfo string) {
	i.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SetData(userInfo).SendMessage()
}

当用户信息更新时,此方法被调用。它设置事件名,并将更新的用户信息作为数据发送。

继续:

你好,我正在学习这块的逻辑,请详细帮我解析代码的设计,功能,以及作用:
```
type OnConnListener interface {
	OnConnecting()
	OnConnectSuccess()
	OnConnectFailed(errCode int32, errMsg string)
	OnKickedOffline()
	OnUserTokenExpired()
}

下面是 ws_wrapper/ws_local_server/ws_init_login.go
func (i *InitCallback) OnConnectFailed(ErrCode int32, ErrMsg string) {
	var ed EventData
	ed.Event = cleanUpfuncName(runFuncName())
	ed.ErrCode = ErrCode
	ed.ErrMsg = ErrMsg
	SendOneUserMessage(ed, i.uid)
}

下面是 wasm/event_listener/listener.go
func (i *ConnCallback) OnConnectFailed(errCode int32, errMsg string) {
	i.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SetErrCode(errCode).SetErrMsg(errMsg).SendMessage()
}
type EventData struct {
	Event       string `json:"event"`
	ErrCode     int32  `json:"errCode"`
	ErrMsg      string `json:"errMsg"`
	Data        string `json:"data"`
	OperationID string `json:"operationID"`
}
func cleanUpfuncName(funcName string) string {
	end := strings.LastIndex(funcName, ".")
	if end == -1 {
		log.Info("", "funcName not include.", funcName)
		return ""
	}
	return funcName[end+1:]
}
func SendOneUserMessage(data interface{}, uid string) {
	var chMsg ChanMsg
	chMsg.data, _ = json.Marshal(data)
	chMsg.uid = uid
	err := send2Ch(WS.ch, &chMsg, 2)
	if err != nil {
		log.Info("", "send2ch failed, ", err, string(chMsg.data), uid)
		return
	}
	log.Info("", "send response to web: ", string(chMsg.data))
}
func send2Ch(ch chan ChanMsg, value *ChanMsg, timeout int64) error {
	var flag = 0
	select {
	case ch <- *value:
		flag = 1
	case <-time.After(time.Second * time.Duration(timeout)):
		flag = 2
	}
	if flag == 1 {
		return nil
	} else {
		log.Info("", "send cmd timeout, ", timeout, value)
		return errors.New("send cmd timeout")
	}
}


```

你提供的代码是用Go语言编写的,涉及到WebSocket连接事件监听和处理的逻辑。我将逐一解释每个部分的设计、功能和作用。

首先,定义了一个名为 OnConnListener 的接口,包含几个方法:

  • OnConnecting():当WebSocket开始连接时调用。
  • OnConnectSuccess():当WebSocket成功连接时调用。
  • OnConnectFailed(errCode int32, errMsg string):当WebSocket连接失败时调用,传递错误代码和消息。
  • OnKickedOffline():当用户被踢下线时调用。
  • OnUserTokenExpired():当用户的token过期时调用。

这个接口定义了WebSocket连接生命周期中可能发生的事件。任何实现这个接口的类型都必须实现这些方法。

接下来,我们看到 InitCallbackConnCallback 类型分别实现了 OnConnListener 接口中的 OnConnectFailed 方法。不同的实现说明了在不同的上下文中处理连接失败事件的多样性:

  • InitCallback 会构建一个 EventData 结构体,设置相应的事件名称、错误代码和错误消息,然后通过 SendOneUserMessage 发送给特定的用户。
  • ConnCallback 则是通过一系列链式调用设置事件名称、错误代码和错误消息,并通过 SendMessage() 方法来发送这些信息。

这显示了不同对象可能会以不同的方式处理相同的事件。

EventData 结构体用于封装发送的事件数据,包含事件名、错误代码、错误消息、数据和操作ID。它是一个简单的数据容器。

cleanUpfuncName 函数用于处理函数名字符串,它找到最后一个点号(.)并截取它之后的字符串作为事件名。如果没有找到点号,则记录一条信息日志,并返回空字符串。

SendOneUserMessage 函数用于将 EventData 结构体序列化为JSON格式,并通过WebSocket通道发送给指定用户。如果发送过程中出现错误,会记录日志信息。

send2Ch 函数是一个辅助函数,它负责将消息发送到一个Go通道,如果在指定的超时时间内发送失败,则记录错误并返回一个超时错误。

在这段代码中,事件监听和消息发送是异步的,使用了Go的并发特性(通过goroutines和channels)。这种设计模式可以高效地处理并发事件和消息传递,适用于高并发的网络服务,例如WebSocket服务器。

你现在是一个优秀的程序员工程师,可能编写优雅的代码以及清晰的注释

我现在希望你基于 现有的 wasm 的函数编写新的函数,我会给你一个案例,然后你来根据我给你的案例去模拟写一个高质量的代码和注释

首先,我要告诉你的是结构体:
```go
type EventData struct {
	Event       string `json:"event"`
	ErrCode     int32  `json:"errCode"`
	ErrMsg      string `json:"errMsg"`
	Data        string `json:"data"`
	OperationID string `json:"operationID"`
}
```

我参考 wasm 模块的代码:
```go
type ConnCallback struct {
	uid string
	CallbackWriter
}

func NewConnCallback(funcName string, callback *js.Value) *ConnCallback {
	return &ConnCallback{CallbackWriter: NewEventData(callback).SetEvent(funcName)}
}

func (i *ConnCallback) OnConnecting() {
	i.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SendMessage()
}

func (i *ConnCallback) OnConnectSuccess() {
	i.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SendMessage()

}
func (i *ConnCallback) OnConnectFailed(errCode int32, errMsg string) {
	i.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SetErrCode(errCode).SetErrMsg(errMsg).SendMessage()
}

func (i *ConnCallback) OnKickedOffline() {
	i.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SendMessage()
}

func (i *ConnCallback) OnUserTokenExpired() {
	i.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SendMessage()
}

func (i *ConnCallback) OnSelfInfoUpdated(userInfo string) {
	i.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SetData(userInfo).SendMessage()
}
```

然后重构后的代码如下:

ws listener 中的函数:

```go
type ConnCallback struct {
	respMessage *RespMessage
}

func NewConnCallback(respMessage *RespMessage) *ConnCallback {
	return &ConnCallback{respMessage: respMessage}
}

func (c ConnCallback) OnConnecting() {
	c.respMessage.sendEventSuccessRespNoData(getSelfFuncName())
}

func (c ConnCallback) OnConnectSuccess() {
	c.respMessage.sendEventSuccessRespNoData(getSelfFuncName())
}

func (c ConnCallback) OnConnectFailed(errCode int32, errMsg string) {
	c.respMessage.sendEventFailedRespNoData(getSelfFuncName(), errCode, errMsg)
}

func (c ConnCallback) OnKickedOffline() {
	c.respMessage.sendEventSuccessRespNoData(getSelfFuncName())
}

func (c ConnCallback) OnUserTokenExpired() {
	c.respMessage.sendEventSuccessRespNoData(getSelfFuncName())
}
```

以及 reponder 函数:

```
func (r *RespMessage) sendEventSuccessRespNoData(event string) {
	r.respMessagesChan <- &EventData{
		Event: event,
	}
}

func (r *RespMessage) sendEventSuccessRespWithData(event string, data string) {
	r.respMessagesChan <- &EventData{
		Event: event,
		Data:  data,
	}
}

func (r *RespMessage) sendEventFailedRespNoData(event string, errCode int32, errMsg string) {
	r.respMessagesChan <- &EventData{
		Event:   event,
		ErrCode: errCode,
		ErrMsg:  errMsg,
	}
}

func (r *RespMessage) sendEventFailedREspNoErr(event string) {
	r.respMessagesChan <- &EventData{
		Event: event,
	}
}
```

我现在想让你,根据下面的重构前的代码如下:
```go

type GroupCallback struct {
	CallbackWriter
}

func NewGroupCallback(callback *js.Value) *GroupCallback {
	return &GroupCallback{CallbackWriter: NewEventData(callback)}
}

func (f *GroupCallback) OnJoinedGroupAdded(groupInfo string) {
	f.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SetData(groupInfo).SendMessage()
}
func (f *GroupCallback) OnJoinedGroupDeleted(groupInfo string) {
	f.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SetData(groupInfo).SendMessage()
}
func (f *GroupCallback) OnGroupMemberAdded(groupMemberInfo string) {
	f.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SetData(groupMemberInfo).SendMessage()
}
func (f *GroupCallback) OnGroupMemberDeleted(groupMemberInfo string) {
	f.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SetData(groupMemberInfo).SendMessage()
}
func (f *GroupCallback) OnGroupApplicationAdded(groupApplication string) {
	f.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SetData(groupApplication).SendMessage()
}
func (f *GroupCallback) OnGroupApplicationDeleted(groupApplication string) {
	f.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SetData(groupApplication).SendMessage()
}
func (f *GroupCallback) OnGroupInfoChanged(groupInfo string) {
	f.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SetData(groupInfo).SendMessage()
}
func (f *GroupCallback) OnGroupMemberInfoChanged(groupMemberInfo string) {
	f.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SetData(groupMemberInfo).SendMessage()
}
func (f *GroupCallback) OnGroupApplicationAccepted(groupApplication string) {
	f.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SetData(groupApplication).SendMessage()
}
func (f *GroupCallback) OnGroupApplicationRejected(groupApplication string) {
	f.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SetData(groupApplication).SendMessage()
}
func (f *GroupCallback) OnGroupDismissed(groupInfo string) {
	f.CallbackWriter.SetEvent(utils.GetSelfFuncName()).SetData(groupInfo).SendMessage()
}
```

请你基于上面重构前的代码模仿写出重构后的代码 ws listener 中的函数,以及必要的 以及 reponder 中不存在的函数(如果存在的不用写),以及必要的专业的函数注释,请直接给出代码: