diff --git a/http.go b/http.go index 519df53..b2fb543 100644 --- a/http.go +++ b/http.go @@ -2,6 +2,7 @@ package http import ( _ "embed" + "encoding/json" "net/http" "reflect" "strings" @@ -275,14 +276,14 @@ func (hc *Http) Connect(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { // ws.onPing = opt.Func("onPing") // ws.onPong = opt.Func("onPong") ws.onMessage = opt.Func("onMessage") - ws.onJSONMessage = opt.Func("onJSONMessage") ws.pingInterval = opt.Int64("pingInterval") ws.reconnectInterval = opt.Int64("reconnectInterval") - if ws.reconnectInterval == 0 && (ws.onMessage != nil || ws.onJSONMessage != nil) { + if ws.reconnectInterval == 0 && ws.onMessage != nil { ws.reconnectInterval = 1000 } } // fmt.Println(u.BMagenta("WS"), "start") + if err := ws.connect(vm); err == nil { if ws.pingInterval > 0 { // fmt.Println(u.BMagenta("WS"), "start ping") @@ -290,10 +291,7 @@ func (hc *Http) Connect(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { // ws.sleep(time.Duration(ws.pingInterval) * time.Millisecond) for { if ws.conn != nil { - // fmt.Println(u.BMagenta("WS"), "ping") - ws.writeLock.Lock() - ws.conn.WriteMessage(websocket.PingMessage, []byte{'P'}) - ws.writeLock.Unlock() + ws.writeMessage(websocket.PingMessage, nil) } if !ws.running { break @@ -310,30 +308,17 @@ func (hc *Http) Connect(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { ws.pingStopChan <- true } - if ws.onMessage != nil || ws.onJSONMessage != nil { + if ws.onMessage != nil { // fmt.Println(u.BMagenta("WS"), "start onMessage") go func() { for { for { if ws.conn != nil { - if ws.onJSONMessage != nil { - var obj interface{} - err := ws.conn.ReadJSON(&obj) - if err != nil { - break - } - _, _ = ws.onJSONMessage(ws.this, vm.ToValue(obj)) - } else { - typ, buf, err := ws.conn.ReadMessage() - if err != nil { - break - } - if typ == websocket.TextMessage { - _, _ = ws.onMessage(ws.this, vm.ToValue(string(buf))) - } else { - _, _ = ws.onMessage(ws.this, vm.ToValue(buf)) - } + typ, data, err := readWSMessage(ws.conn) + if err != nil { + break } + _, _ = ws.onMessage(ws.this, vm.ToValue(typ), vm.ToValue(data)) } } // fmt.Println(u.BMagenta("WS"), "stop onMessage") @@ -377,8 +362,11 @@ type WS struct { onError goja.Callable // onPing goja.Callable // onPong goja.Callable + pingTimes int64 + pongTimes int64 + lastPingTime int64 + lastPongTime int64 onMessage goja.Callable - onJSONMessage goja.Callable pingInterval int64 reconnectInterval int64 } @@ -437,6 +425,15 @@ func (ws *WS) connect(vm *goja.Runtime) error { // }) // return err // } + ws.pingTimes = 0 + ws.pongTimes = 0 + ws.lastPingTime = 0 + ws.lastPongTime = 0 + conn.SetPongHandler(func(appData string) error { + ws.pongTimes++ + ws.lastPongTime = time.Now().UnixMilli() + return nil + }) conn.SetCloseHandler(func(code int, text string) error { // fmt.Println(u.BMagenta("WS"), "onClose") if ws.onClose != nil { @@ -462,48 +459,111 @@ func (ws *WS) connect(vm *goja.Runtime) error { return nil } -func (ws *WS) Read(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { - typ, buf, err := ws.conn.ReadMessage() - if err != nil { - panic(vm.NewGoError(err)) - } - if typ == websocket.TextMessage { - return vm.ToValue(string(buf)) - } else { - return vm.ToValue(buf) - } +func (ws *WS) PingCount(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + return vm.ToValue(gojs.Map{ + "pingTimes": ws.pingTimes, + "pongTimes": ws.pongTimes, + "lastPingTime": ws.lastPingTime, + "lastPongTime": ws.lastPongTime, + }) } -func (ws *WS) ReadJSON(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { - var obj interface{} - err := ws.conn.ReadJSON(&obj) +func (ws *WS) Read(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + typ, data, err := readWSMessage(ws.conn) if err != nil { panic(vm.NewGoError(err)) } - return vm.ToValue(obj) + return vm.ToValue(gojs.Map{ + "type": typ, + "data": data, + }) +} + +func readWSMessage(client *websocket.Conn) (string, any, error) { + msgType := "close" + var msgData any + typ, data, err := client.ReadMessage() + if err == nil && typ != websocket.CloseMessage { + switch typ { + case websocket.TextMessage: + if err2 := json.Unmarshal(data, &msgData); err2 != nil { + msgData = string(data) + msgType = "text" + } else { + msgType = "json" + } + case websocket.BinaryMessage: + if err2 := json.Unmarshal(data, &msgData); err2 != nil { + msgData = data + msgType = "binary" + } else { + msgType = "json" + } + case websocket.PingMessage: + msgData = string(data) + msgType = "ping" + case websocket.PongMessage: + msgData = string(data) + msgType = "pong" + } + } + return msgType, msgData, err +} + +func (ws *WS) writeMessage(typ int, msg []byte) error { + ws.writeLock.Lock() + defer ws.writeLock.Unlock() + if typ == websocket.PingMessage { + ws.pingTimes++ + ws.lastPingTime = time.Now().UnixMilli() + } + return ws.conn.WriteMessage(typ, msg) } func (ws *WS) Write(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { args := gojs.MakeArgs(&argsIn, vm).Check(1) var err error - ws.writeLock.Lock() - if args.Arguments[0].ExportType().Kind() == reflect.String { - err = ws.conn.WriteMessage(websocket.TextMessage, args.Bytes(0)) - } else { - err = ws.conn.WriteMessage(websocket.BinaryMessage, args.Bytes(0)) + switch args.Any(0).(type) { + case []byte: + err = ws.writeMessage(websocket.BinaryMessage, args.Bytes(0)) + case string: + err = ws.writeMessage(websocket.TextMessage, args.Bytes(0)) + default: + err = ws.writeMessage(websocket.TextMessage, u.JsonBytes(args.Any(0))) } - ws.writeLock.Unlock() if err != nil { panic(vm.NewGoError(err)) } return nil } -func (ws *WS) WriteJSON(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { - args := gojs.MakeArgs(&argsIn, vm).Check(1) - ws.writeLock.Lock() - err := ws.conn.WriteJSON(args.Any(0)) - ws.writeLock.Unlock() +func (ws *WS) Ping(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + err := ws.writeMessage(websocket.PingMessage, nil) + if err != nil { + panic(vm.NewGoError(err)) + } + return nil +} + +func (ws *WS) WriteMessage(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value { + args := gojs.MakeArgs(&argsIn, vm).Check(2) + var err error + switch args.Str(0) { + case "text": + err = ws.writeMessage(websocket.TextMessage, args.Bytes(1)) + case "binary": + err = ws.writeMessage(websocket.BinaryMessage, args.Bytes(1)) + case "ping": + err = ws.writeMessage(websocket.PingMessage, args.Bytes(1)) + case "pong": + err = ws.writeMessage(websocket.PongMessage, args.Bytes(1)) + case "close": + err = ws.writeMessage(websocket.CloseMessage, args.Bytes(1)) + case "json": + err = ws.writeMessage(websocket.TextMessage, u.JsonBytes(args.Any(1))) + default: + err = ws.writeMessage(websocket.TextMessage, args.Bytes(1)) + } if err != nil { panic(vm.NewGoError(err)) } diff --git a/http.ts b/http.ts index 48c07ae..67f9828 100644 --- a/http.ts +++ b/http.ts @@ -59,16 +59,28 @@ interface WSConfig { compress: boolean onOpen: () => void onClose: (code: number, data: string) => void - onMessage: (data: any) => void - onJSONMessage: (data: any) => void + onMessage: (type: string, data: any) => void pingInterval: number reconnectInterval: number } interface WS { - read(): any - readJSON(): any + read: () => WSMessage write(data: any): void - writeJSON(data: any): void + writeMessage(type: string, data: any): void + ping: () => void + pingCount: () => PingCount close(): void -} \ No newline at end of file +} + +interface PingCount { + pingTimes: number + pongTimes: number + lastPingTime: number + lastPongTime: number +} + +interface WSMessage { + type: string + data: string | Object +} diff --git a/ws_test.js b/ws_test.js index b713def..28a6199 100644 --- a/ws_test.js +++ b/ws_test.js @@ -6,20 +6,21 @@ function main() { } function testSync() { let conn = http.connect("http://127.0.0.1:18001/ws") conn.write('111') - if (conn.read() !== '111') { - return false + let r1 = conn.read().data + if (r1 !== 111) { + return r1 } conn.write(new Uint8Array([0x01, 0x02, 0x03])) - let r2 = conn.read() + let r2 = conn.read().data if (r2[0] != 1 || r2[1] != 2 || r2[2] != 3) { - return false + return r2 } - conn.writeJSON({ name: 'Tom' }) - let r3 = conn.readJSON() + conn.write({ name: 'Tom' }) + let r3 = conn.read().data if (r3.name != 'Tom') { - return false + return r3 } conn.close() return true @@ -42,22 +43,22 @@ function testAsync() { onPong: function (data) { console.info("onPong", data) }, - onJSONMessage: function (data) { + onMessage: function (type, data) { // console.info("onJSONMessage", data) asyncResult += data.name }, }) asyncConn = conn - conn.writeJSON({ name: 'Tom1' }) - conn.writeJSON({ name: 'Tom2' }) - conn.writeJSON({ name: 'Tom3' }) + conn.write({ name: 'Tom1' }) + conn.write({ name: 'Tom2' }) + conn.write({ name: 'Tom3' }) } function testAsync2() { - asyncConn.writeJSON({ name: 'Tom4' }) - asyncConn.writeJSON({ name: 'Tom5' }) - asyncConn.writeJSON({ name: 'Tom6' }) + asyncConn.write({ name: 'Tom4' }) + asyncConn.write({ name: 'Tom5' }) + asyncConn.write({ name: 'Tom6' }) } function closeAsync() {