From ebcba7f9c2297acef670b6613ecb063753c03c12 Mon Sep 17 00:00:00 2001 From: anoshenko Date: Tue, 12 Mar 2024 19:32:22 +0300 Subject: [PATCH] Added NoSocket parameter of the app --- appServer.go | 164 +++++++++++++++++++++---- appWasm.go | 15 +-- app_post.js | 75 ++++++++++++ app_scripts.js | 18 +++ application.go | 11 +- image.go | 1 + session.go | 70 +++++++---- wasmBridge.go | 26 ++-- webBridge.go | 327 +++++++++++++++++++++++++++++-------------------- 9 files changed, 501 insertions(+), 206 deletions(-) create mode 100644 app_post.js diff --git a/appServer.go b/appServer.go index 6979145..7ac41c3 100644 --- a/appServer.go +++ b/appServer.go @@ -20,6 +20,9 @@ import ( //go:embed app_socket.js var socketScripts string +//go:embed app_post.js +var httpPostScripts string + func debugLog(text string) { log.Println("\033[34m" + text) } @@ -28,11 +31,16 @@ func errorLog(text string) { log.Println("\033[31m" + text) } +type sessionInfo struct { + session Session + response chan string +} + type application struct { server *http.Server params AppParams createContentFunc func(Session) SessionContent - sessions map[int]Session + sessions map[int]sessionInfo } func (app *application) getStartPage() string { @@ -40,14 +48,18 @@ func (app *application) getStartPage() string { defer freeStringBuilder(buffer) buffer.WriteString("\n\n") - getStartPage(buffer, app.params, socketScripts) + getStartPage(buffer, app.params) buffer.WriteString("\n") return buffer.String() } func (app *application) Finish() { for _, session := range app.sessions { - session.close() + session.session.close() + if session.response != nil { + close(session.response) + session.response = nil + } } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) @@ -69,7 +81,12 @@ func (app *application) nextSessionID() int { } func (app *application) removeSession(id int) { - delete(app.sessions, id) + if info, ok := app.sessions[id]; ok { + if info.response != nil { + close(info.response) + } + delete(app.sessions, id) + } } func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) { @@ -79,6 +96,11 @@ func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) { } switch req.Method { + case "POST": + if req.URL.Path == "/" { + app.postHandler(w, req) + } + case "GET": switch req.URL.Path { case "/": @@ -86,10 +108,20 @@ func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) { io.WriteString(w, app.getStartPage()) case "/ws": - if bridge := CreateSocketBridge(w, req); bridge != nil { + if bridge := createSocketBridge(w, req); bridge != nil { go app.socketReader(bridge) } + case "/script.js": + w.WriteHeader(http.StatusOK) + if app.params.NoSocket { + io.WriteString(w, httpPostScripts) + } else { + io.WriteString(w, socketScripts) + } + io.WriteString(w, "\n") + io.WriteString(w, defaultScripts) + default: filename := req.URL.Path[1:] if size := len(filename); size > 0 && filename[size-1] == '/' { @@ -104,7 +136,92 @@ func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) { } } -func (app *application) socketReader(bridge webBridge) { +func setSessionIDCookie(w http.ResponseWriter, sessionID int) { + cookie := http.Cookie{ + Name: "session", + Value: strconv.Itoa(sessionID), + HttpOnly: true, + } + http.SetCookie(w, &cookie) +} + +func (app *application) postHandler(w http.ResponseWriter, req *http.Request) { + + if reqBody, err := io.ReadAll(req.Body); err == nil { + message := string(reqBody) + + if ProtocolInDebugLog { + DebugLog(message) + } + + if obj := ParseDataText(message); obj != nil { + var session Session = nil + var response chan string = nil + + if cookie, err := req.Cookie("session"); err == nil { + sessionID, err := strconv.Atoi(cookie.Value) + if err != nil { + ErrorLog(err.Error()) + } else if info, ok := app.sessions[sessionID]; ok && info.response != nil { + response = info.response + session = info.session + } + } + + command := obj.Tag() + + if session == nil { + switch command { + case "startSession": + events := make(chan DataObject, 1024) + bridge := createHttpBridge(req) + response = bridge.response + answer := "" + session, answer = app.startSession(obj, events, bridge, response) + + bridge.writeMessage(answer) + session.onStart() + bridge.sendResponse() + + setSessionIDCookie(w, session.ID()) + + go sessionEventHandler(session, events, bridge) + + default: + return + } + } + + switch command { + case "startSession": + + case "nop": + session.sendResponse() + /* + case "disconnect": + session.onDisconnect() + return + */ + case "session-close": + session.onFinish() + session.App().removeSession(session.ID()) + return + + default: + if !session.handleAnswer(command, obj) { + session.addToEventsQueue(obj) + } + } + + io.WriteString(w, <-response) + for len(response) > 0 { + io.WriteString(w, <-response) + } + } + } +} + +func (app *application) socketReader(bridge *wsBridge) { var session Session events := make(chan DataObject, 1024) @@ -116,7 +233,7 @@ func (app *application) socketReader(bridge webBridge) { } if ProtocolInDebugLog { - DebugLog(message) + DebugLog("🖥️ -> " + message) } if obj := ParseDataText(message); obj != nil { @@ -124,7 +241,7 @@ func (app *application) socketReader(bridge webBridge) { switch command { case "startSession": answer := "" - if session, answer = app.startSession(obj, events, bridge); session != nil { + if session, answer = app.startSession(obj, events, bridge, nil); session != nil { if !bridge.writeMessage(answer) { return } @@ -135,7 +252,8 @@ func (app *application) socketReader(bridge webBridge) { case "reconnect": if sessionText, ok := obj.PropertyValue("session"); ok { if sessionID, err := strconv.Atoi(sessionText); err == nil { - if session = app.sessions[sessionID]; session != nil { + if info, ok := app.sessions[sessionID]; ok { + session := info.session session.setBridge(events, bridge) answer := allocStringBuilder() defer freeStringBuilder(answer) @@ -157,7 +275,7 @@ func (app *application) socketReader(bridge webBridge) { } answer := "" - if session, answer = app.startSession(obj, events, bridge); session != nil { + if session, answer = app.startSession(obj, events, bridge, nil); session != nil { if !bridge.writeMessage(answer) { return } @@ -165,23 +283,16 @@ func (app *application) socketReader(bridge webBridge) { go sessionEventHandler(session, events, bridge) } - case "answer": - session.handleAnswer(obj) - - case "imageLoaded": - session.imageManager().imageLoaded(obj) - - case "imageError": - session.imageManager().imageLoadError(obj) - default: - events <- obj + if !session.handleAnswer(command, obj) { + events <- obj + } } } } } -func sessionEventHandler(session Session, events chan DataObject, bridge webBridge) { +func sessionEventHandler(session Session, events chan DataObject, bridge bridge) { for { data := <-events @@ -201,7 +312,9 @@ func sessionEventHandler(session Session, events chan DataObject, bridge webBrid } } -func (app *application) startSession(params DataObject, events chan DataObject, bridge webBridge) (Session, string) { +func (app *application) startSession(params DataObject, events chan DataObject, + bridge bridge, response chan string) (Session, string) { + if app.createContentFunc == nil { return nil, "" } @@ -212,7 +325,10 @@ func (app *application) startSession(params DataObject, events chan DataObject, return nil, "" } - app.sessions[session.ID()] = session + app.sessions[session.ID()] = sessionInfo{ + session: session, + response: response, + } answer := allocStringBuilder() defer freeStringBuilder(answer) @@ -236,7 +352,7 @@ var apps = []*application{} func StartApp(addr string, createContentFunc func(Session) SessionContent, params AppParams) { app := new(application) app.params = params - app.sessions = map[int]Session{} + app.sessions = map[int]sessionInfo{} app.createContentFunc = createContentFunc apps = append(apps, app) diff --git a/appWasm.go b/appWasm.go index 0b66622..9824674 100644 --- a/appWasm.go +++ b/appWasm.go @@ -17,7 +17,7 @@ type wasmApp struct { params AppParams createContentFunc func(Session) SessionContent session Session - bridge webBridge + bridge bridge close chan DataObject } @@ -44,17 +44,10 @@ func (app *wasmApp) handleMessage(this js.Value, args []js.Value) any { case "session-close": app.close <- obj - case "answer": - app.session.handleAnswer(obj) - - case "imageLoaded": - app.session.imageManager().imageLoaded(obj, app.session) - - case "imageError": - app.session.imageManager().imageLoadError(obj, app.session) - default: - app.session.handleEvent(command, obj) + if !app.session.handleAnswer(command, obj) { + app.session.handleEvent(command, obj) + } } } } diff --git a/app_post.js b/app_post.js new file mode 100644 index 0000000..71bd258 --- /dev/null +++ b/app_post.js @@ -0,0 +1,75 @@ + +function sendMessage(message) { + let xhr = new XMLHttpRequest(); + xhr.open('POST', '/', true); + xhr.onreadystatechange = function() { + const script = this.responseText + if (script != "") { + window.eval(script) + //sendMessage("nop{session=" + sessionID +"}") + } + + } + xhr.send(message); +} + +window.onload = function() { + sendMessage( sessionInfo() ); +} + +/* +window.onload = function() { + socketUrl = document.location.protocol == "https:" ? "wss://" : "ws://" + socketUrl += document.location.hostname + const port = document.location.port + if (port) { + socketUrl += ":" + port + } + socketUrl += window.location.pathname + "ws" + + socket = new WebSocket(socketUrl); + socket.onopen = socketOpen; + socket.onclose = socketClose; + socket.onerror = socketError; + socket.onmessage = function(event) { + window.execScript ? window.execScript(event.data) : window.eval(event.data); + }; +}; + +function socketOpen() { + sendMessage( sessionInfo() ); +} + +function socketReopen() { + sendMessage( "reconnect{session=" + sessionID + "}" ); +} + +function socketReconnect() { + if (!socket) { + socket = new WebSocket(socketUrl); + socket.onopen = socketReopen; + socket.onclose = socketClose; + socket.onerror = socketError; + socket.onmessage = function(event) { + window.execScript ? window.execScript(event.data) : window.eval(event.data); + }; + } +} + +function socketClose(event) { + console.log("socket closed") + socket = null; + if (!event.wasClean && windowFocus) { + window.setTimeout(socketReconnect, 10000); + } +} +*/ + +function socketError(error) { + console.log(error); +} + +window.onfocus = function(event) { + windowFocus = true + sendMessage( "session-resume{session=" + sessionID +"}" ); +} diff --git a/app_scripts.js b/app_scripts.js index 7eeace6..af89df7 100644 --- a/app_scripts.js +++ b/app_scripts.js @@ -1902,10 +1902,28 @@ function getPropertyValue(answerID, elementId, name) { sendMessage('answer{answerID=' + answerID + ', value=""}') } +function setStyles(styles) { + document.querySelector('style').textContent = styles +} + function appendStyles(styles) { document.querySelector('style').textContent += styles } +function appendAnimationCSS(css) { + let styles = document.getElementById('ruiAnimations'); + if (styles) { + styles.textContent += css; + } +} + +function setAnimationCSS(css) { + let styles = document.getElementById('ruiAnimations'); + if (styles) { + styles.textContent = css; + } +} + function getCanvasContext(elementId) { const canvas = document.getElementById(elementId) if (canvas) { diff --git a/application.go b/application.go index 6b2afc8..4cad322 100644 --- a/application.go +++ b/application.go @@ -38,9 +38,12 @@ type AppParams struct { KeyFile string // Redirect80 - if true then the function of redirect from port 80 to 443 is created Redirect80 bool + // NoSocket - if true then WebSockets will not be used and information exchange + // between the client and the server will be carried out only via http. + NoSocket bool } -func getStartPage(buffer *strings.Builder, params AppParams, addScripts string) { +func getStartPage(buffer *strings.Builder, params AppParams) { buffer.WriteString(` `) @@ -67,11 +70,7 @@ func getStartPage(buffer *strings.Builder, params AppParams, addScripts string) buffer.WriteString(appStyles) buffer.WriteString(`</style> <style id="ruiAnimations"></style> - <script> -`) - buffer.WriteString(defaultScripts) - buffer.WriteString(addScripts) - buffer.WriteString(`</script> + <script src="/script.js"></script> </head> <body id="body" onkeydown="keyDownEvent(this, event)"> <div class="ruiRoot" id="ruiRootView"></div> diff --git a/image.go b/image.go index fb7e154..3a740a4 100644 --- a/image.go +++ b/image.go @@ -80,6 +80,7 @@ func (manager *imageManager) loadImage(url string, onLoaded func(Image), session manager.images[url] = image session.callFunc("loadImage", url) + session.sendResponse() return image } diff --git a/session.go b/session.go index 08dd300..a78deb4 100644 --- a/session.go +++ b/session.go @@ -7,7 +7,7 @@ import ( "strings" ) -type webBridge interface { +type bridge interface { startUpdateScript(htmlID string) bool finishUpdateScript(htmlID string) callFunc(funcName string, args ...any) bool @@ -16,10 +16,9 @@ type webBridge interface { updateCSSProperty(htmlID, property, value string) updateProperty(htmlID, property string, value any) removeProperty(htmlID, property string) - readMessage() (string, bool) - writeMessage(text string) bool - addAnimationCSS(css string) - clearAnimation() + sendResponse() + setAnimationCSS(css string) + appendAnimationCSS(css string) canvasStart(htmlID string) callCanvasFunc(funcName string, args ...any) callCanvasVarFunc(v any, funcName string, args ...any) @@ -124,7 +123,7 @@ type Session interface { nextViewID() string styleProperty(styleTag, property string) any - setBridge(events chan DataObject, bridge webBridge) + setBridge(events chan DataObject, bridge bridge) writeInitScript(writer *strings.Builder) callFunc(funcName string, args ...any) updateInnerHTML(htmlID, html string) @@ -134,6 +133,7 @@ type Session interface { removeProperty(htmlID, property string) startUpdateScript(htmlID string) bool finishUpdateScript(htmlID string) + sendResponse() addAnimationCSS(css string) clearAnimation() canvasStart(htmlID string) @@ -145,7 +145,8 @@ type Session interface { canvasFinish() canvasTextMetrics(htmlID, font, text string) TextMetrics htmlPropertyValue(htmlID, name string) string - handleAnswer(data DataObject) + addToEventsQueue(data DataObject) + handleAnswer(command string, data DataObject) bool handleRootSize(data DataObject) handleResize(data DataObject) handleEvent(command string, data DataObject) @@ -189,7 +190,7 @@ type sessionData struct { ignoreUpdates bool popups *popupManager images *imageManager - bridge webBridge + bridge bridge events chan DataObject animationCounter int animationCSS string @@ -237,7 +238,7 @@ func (session *sessionData) ID() int { return session.sessionID } -func (session *sessionData) setBridge(events chan DataObject, bridge webBridge) { +func (session *sessionData) setBridge(events chan DataObject, bridge bridge) { session.events = events session.bridge = bridge } @@ -330,23 +331,19 @@ func (session *sessionData) updateTooltipConstants() { } func (session *sessionData) reload() { - buffer := allocStringBuilder() - defer freeStringBuilder(buffer) css := appStyles + session.getCurrentTheme().cssText(session) + session.animationCSS - css = strings.ReplaceAll(css, "\n", `\n`) - css = strings.ReplaceAll(css, "\t", `\t`) - buffer.WriteString(`document.querySelector('style').textContent = "`) - buffer.WriteString(css) - buffer.WriteString("\";\n") + session.bridge.callFunc("setStyles", css) if session.rootView != nil { - buffer.WriteString(`document.getElementById('ruiRootView').innerHTML = '`) + buffer := allocStringBuilder() + defer freeStringBuilder(buffer) + viewHTML(session.rootView, buffer) - buffer.WriteString("';\nscanElementsSize();") + session.bridge.updateInnerHTML("ruiRootView", buffer.String()) + session.bridge.callFunc("scanElementsSize") } - session.bridge.writeMessage(buffer.String()) session.updateTooltipConstants() } @@ -447,15 +444,21 @@ func (session *sessionData) finishUpdateScript(htmlID string) { } } +func (session *sessionData) sendResponse() { + if session.bridge != nil { + session.bridge.sendResponse() + } +} + func (session *sessionData) addAnimationCSS(css string) { if session.bridge != nil { - session.bridge.addAnimationCSS(css) + session.bridge.appendAnimationCSS(css) } } func (session *sessionData) clearAnimation() { if session.bridge != nil { - session.bridge.clearAnimation() + session.bridge.setAnimationCSS("") } } @@ -520,8 +523,23 @@ func (session *sessionData) htmlPropertyValue(htmlID, name string) string { return "" } -func (session *sessionData) handleAnswer(data DataObject) { - session.bridge.answerReceived(data) +func (session *sessionData) handleAnswer(command string, data DataObject) bool { + switch command { + case "answer": + session.bridge.answerReceived(data) + + case "imageLoaded": + session.imageManager().imageLoaded(data) + + case "imageError": + session.imageManager().imageLoadError(data) + + default: + return false + } + + session.bridge.sendResponse() + return true } func (session *sessionData) handleRootSize(data DataObject) { @@ -672,6 +690,8 @@ func (session *sessionData) handleEvent(command string, data DataObject) { ErrorLog(`"id" property not found. Event: ` + command) } } + + session.bridge.sendResponse() } func (session *sessionData) hotKey(event KeyEvent) { @@ -769,3 +789,7 @@ func (session *sessionData) RemoveAllClientItems() { session.clientStorage = map[string]string{} session.bridge.callFunc("localStorageClear") } + +func (session *sessionData) addToEventsQueue(data DataObject) { + session.events <- data +} diff --git a/wasmBridge.go b/wasmBridge.go index 8d14415..bc1902e 100644 --- a/wasmBridge.go +++ b/wasmBridge.go @@ -16,7 +16,7 @@ type wasmBridge struct { closeEvent chan DataObject } -func createWasmBridge(close chan DataObject) webBridge { +func createWasmBridge(close chan DataObject) bridge { bridge := new(wasmBridge) bridge.answerID = 1 bridge.answer = make(map[int]chan DataObject) @@ -102,10 +102,6 @@ func (bridge *wasmBridge) close() { bridge.closeEvent <- NewDataObject("close") } -func (bridge *wasmBridge) readMessage() (string, bool) { - return "", false -} - func (bridge *wasmBridge) writeMessage(script string) bool { if ProtocolInDebugLog { DebugLog("Run script:") @@ -118,21 +114,24 @@ func (bridge *wasmBridge) writeMessage(script string) bool { return true } -func (bridge *wasmBridge) addAnimationCSS(css string) { +func (bridge *wasmBridge) prepareCSS(css string) string { css = strings.ReplaceAll(css, `\t`, "\t") css = strings.ReplaceAll(css, `\n`, "\n") css = strings.ReplaceAll(css, `\'`, "'") css = strings.ReplaceAll(css, `\"`, "\"") css = strings.ReplaceAll(css, `\\`, "\\") - - styles := js.Global().Get("document").Call("getElementById", "ruiAnimations") - content := styles.Get("textContent").String() - styles.Set("textContent", content+"\n"+css) + return css } -func (bridge *wasmBridge) clearAnimation() { +func (bridge *wasmBridge) appendAnimationCSS(css string) { styles := js.Global().Get("document").Call("getElementById", "ruiAnimations") - styles.Set("textContent", "") + content := styles.Get("textContent").String() + styles.Set("textContent", content+"\n"+bridge.prepareCSS(css)) +} + +func (bridge *wasmBridge) setAnimationCSS(css string) { + styles := js.Global().Get("document").Call("getElementById", "ruiAnimations") + styles.Set("textContent", bridge.prepareCSS(css)) } func (bridge *wasmBridge) canvasStart(htmlID string) { @@ -276,3 +275,6 @@ func (bridge *wasmBridge) answerReceived(answer DataObject) { func (bridge *wasmBridge) remoteAddr() string { return "localhost" } + +func (bridge *wasmBridge) sendResponse() { +} diff --git a/webBridge.go b/webBridge.go index 89da145..9ec2ea6 100644 --- a/webBridge.go +++ b/webBridge.go @@ -12,17 +12,30 @@ import ( "github.com/gorilla/websocket" ) +type webBridge struct { + answer map[int]chan DataObject + answerID int + answerMutex sync.Mutex + writeMutex sync.Mutex + closed bool + canvasBuffer strings.Builder + canvasVarNumber int + updateScripts map[string]*strings.Builder + writeMessage func(string) bool + callFuncImmediately func(funcName string, args ...any) bool +} + type wsBridge struct { - conn *websocket.Conn - answer map[int]chan DataObject - answerID int - answerMutex sync.Mutex - writeMutex sync.Mutex - closed bool - buffer strings.Builder - canvasBuffer strings.Builder - canvasVarNumber int - updateScripts map[string]*strings.Builder + webBridge + conn *websocket.Conn +} + +type httpBridge struct { + webBridge + responseBuffer strings.Builder + response chan string + remoteAddress string + //conn *websocket.Conn } type canvasVar struct { @@ -34,7 +47,7 @@ var upgrader = websocket.Upgrader{ WriteBufferSize: 8096, } -func CreateSocketBridge(w http.ResponseWriter, req *http.Request) webBridge { +func createSocketBridge(w http.ResponseWriter, req *http.Request) *wsBridge { conn, err := upgrader.Upgrade(w, req, nil) if err != nil { ErrorLog(err.Error()) @@ -42,42 +55,84 @@ func CreateSocketBridge(w http.ResponseWriter, req *http.Request) webBridge { } bridge := new(wsBridge) - bridge.answerID = 1 - bridge.answer = make(map[int]chan DataObject) + bridge.initBridge() bridge.conn = conn - bridge.closed = false - bridge.updateScripts = map[string]*strings.Builder{} + bridge.writeMessage = func(script string) bool { + if ProtocolInDebugLog { + DebugLog("🖥️ <- " + script) + } + + if bridge.conn == nil { + ErrorLog("No connection") + return false + } + + bridge.writeMutex.Lock() + err := bridge.conn.WriteMessage(websocket.TextMessage, []byte(script)) + bridge.writeMutex.Unlock() + + if err != nil { + ErrorLog(err.Error()) + return false + } + return true + } + bridge.callFuncImmediately = bridge.callFunc return bridge } -func (bridge *wsBridge) close() { - bridge.closed = true - defer bridge.conn.Close() - bridge.conn = nil +func createHttpBridge(req *http.Request) *httpBridge { + bridge := new(httpBridge) + bridge.initBridge() + bridge.response = make(chan string, 10) + bridge.writeMessage = func(script string) bool { + if script != "" { + if ProtocolInDebugLog { + DebugLog(script) + } + + if bridge.responseBuffer.Len() > 0 { + bridge.responseBuffer.WriteRune('\n') + } + bridge.responseBuffer.WriteString(script) + } + return true + } + bridge.callFuncImmediately = bridge.callImmediately + bridge.remoteAddress = req.RemoteAddr + return bridge } -func (bridge *wsBridge) startUpdateScript(htmlID string) bool { +func (bridge *webBridge) initBridge() { + bridge.answerID = 1 + bridge.answer = make(map[int]chan DataObject) + bridge.closed = false + bridge.updateScripts = map[string]*strings.Builder{} +} + +func (bridge *webBridge) startUpdateScript(htmlID string) bool { if _, ok := bridge.updateScripts[htmlID]; ok { return false } buffer := allocStringBuilder() bridge.updateScripts[htmlID] = buffer - buffer.WriteString("let element = document.getElementById('") + buffer.WriteString("{\nlet element = document.getElementById('") buffer.WriteString(htmlID) buffer.WriteString("');\nif (element) {\n") return true } -func (bridge *wsBridge) finishUpdateScript(htmlID string) { +func (bridge *webBridge) finishUpdateScript(htmlID string) { if buffer, ok := bridge.updateScripts[htmlID]; ok { - buffer.WriteString("scanElementsSize();\n}\n") + buffer.WriteString("scanElementsSize();\n}\n}\n") bridge.writeMessage(buffer.String()) + freeStringBuilder(buffer) delete(bridge.updateScripts, htmlID) } } -func (bridge *wsBridge) argToString(arg any) (string, bool) { +func (bridge *webBridge) argToString(arg any) (string, bool) { switch arg := arg.(type) { case string: arg = strings.ReplaceAll(arg, "\\", `\\`) @@ -152,47 +207,44 @@ func (bridge *wsBridge) argToString(arg any) (string, bool) { return "", false } -func (bridge *wsBridge) callFunc(funcName string, args ...any) bool { - bridge.buffer.Reset() - bridge.buffer.WriteString(funcName) - bridge.buffer.WriteRune('(') +func (bridge *webBridge) callFuncScript(funcName string, args ...any) (string, bool) { + buffer := allocStringBuilder() + defer freeStringBuilder(buffer) + + buffer.WriteString(funcName) + buffer.WriteRune('(') for i, arg := range args { argText, ok := bridge.argToString(arg) if !ok { - return false + return "", false } if i > 0 { - bridge.buffer.WriteString(", ") + buffer.WriteString(", ") } - bridge.buffer.WriteString(argText) + buffer.WriteString(argText) } - bridge.buffer.WriteString(");") + buffer.WriteString(");") - funcText := bridge.buffer.String() - if ProtocolInDebugLog { - DebugLog("Run func: " + funcText) - } - - bridge.writeMutex.Lock() - err := bridge.conn.WriteMessage(websocket.TextMessage, []byte(funcText)) - bridge.writeMutex.Unlock() - if err != nil { - ErrorLog(err.Error()) - return false - } - return true + return buffer.String(), true } -func (bridge *wsBridge) updateInnerHTML(htmlID, html string) { +func (bridge *webBridge) callFunc(funcName string, args ...any) bool { + if funcText, ok := bridge.callFuncScript(funcName, args...); ok { + return bridge.writeMessage(funcText) + } + return false +} + +func (bridge *webBridge) updateInnerHTML(htmlID, html string) { bridge.callFunc("updateInnerHTML", htmlID, html) } -func (bridge *wsBridge) appendToInnerHTML(htmlID, html string) { +func (bridge *webBridge) appendToInnerHTML(htmlID, html string) { bridge.callFunc("appendToInnerHTML", htmlID, html) } -func (bridge *wsBridge) updateCSSProperty(htmlID, property, value string) { +func (bridge *webBridge) updateCSSProperty(htmlID, property, value string) { if buffer, ok := bridge.updateScripts[htmlID]; ok { buffer.WriteString(`element.style['`) buffer.WriteString(property) @@ -204,7 +256,7 @@ func (bridge *wsBridge) updateCSSProperty(htmlID, property, value string) { } } -func (bridge *wsBridge) updateProperty(htmlID, property string, value any) { +func (bridge *webBridge) updateProperty(htmlID, property string, value any) { if buffer, ok := bridge.updateScripts[htmlID]; ok { if val, ok := bridge.argToString(value); ok { buffer.WriteString(`element.setAttribute('`) @@ -218,7 +270,7 @@ func (bridge *wsBridge) updateProperty(htmlID, property string, value any) { } } -func (bridge *wsBridge) removeProperty(htmlID, property string) { +func (bridge *webBridge) removeProperty(htmlID, property string) { if buffer, ok := bridge.updateScripts[htmlID]; ok { buffer.WriteString(`if (element.hasAttribute('`) buffer.WriteString(property) @@ -230,28 +282,34 @@ func (bridge *wsBridge) removeProperty(htmlID, property string) { } } -func (bridge *wsBridge) addAnimationCSS(css string) { - bridge.writeMessage(`var styles = document.getElementById('ruiAnimations'); -if (styles) { - styles.textContent += '` + css + `'; +func (bridge *webBridge) appendAnimationCSS(css string) { + //bridge.callFunc("appendAnimationCSS", css) + bridge.writeMessage(`{ + let styles = document.getElementById('ruiAnimations'); + if (styles) { + styles.textContent += '` + css + `'; + } }`) } -func (bridge *wsBridge) clearAnimation() { - bridge.writeMessage(`var styles = document.getElementById('ruiAnimations'); -if (styles) { - styles.textContent = ''; +func (bridge *webBridge) setAnimationCSS(css string) { + //bridge.callFunc("setAnimationCSS", css) + bridge.writeMessage(`{ + let styles = document.getElementById('ruiAnimations'); + if (styles) { + styles.textContent = '` + css + `'; + } }`) } -func (bridge *wsBridge) canvasStart(htmlID string) { +func (bridge *webBridge) canvasStart(htmlID string) { bridge.canvasBuffer.Reset() - bridge.canvasBuffer.WriteString(`const ctx = getCanvasContext('`) + bridge.canvasBuffer.WriteString("{\nconst ctx = getCanvasContext('") bridge.canvasBuffer.WriteString(htmlID) bridge.canvasBuffer.WriteString(`');`) } -func (bridge *wsBridge) callCanvasFunc(funcName string, args ...any) { +func (bridge *webBridge) callCanvasFunc(funcName string, args ...any) { bridge.canvasBuffer.WriteString("\nctx.") bridge.canvasBuffer.WriteString(funcName) bridge.canvasBuffer.WriteRune('(') @@ -265,7 +323,7 @@ func (bridge *wsBridge) callCanvasFunc(funcName string, args ...any) { bridge.canvasBuffer.WriteString(");") } -func (bridge *wsBridge) updateCanvasProperty(property string, value any) { +func (bridge *webBridge) updateCanvasProperty(property string, value any) { bridge.canvasBuffer.WriteString("\nctx.") bridge.canvasBuffer.WriteString(property) bridge.canvasBuffer.WriteString(" = ") @@ -274,7 +332,7 @@ func (bridge *wsBridge) updateCanvasProperty(property string, value any) { bridge.canvasBuffer.WriteString(";") } -func (bridge *wsBridge) createCanvasVar(funcName string, args ...any) any { +func (bridge *webBridge) createCanvasVar(funcName string, args ...any) any { bridge.canvasVarNumber++ result := canvasVar{name: fmt.Sprintf("v%d", bridge.canvasVarNumber)} bridge.canvasBuffer.WriteString("\nlet ") @@ -293,7 +351,7 @@ func (bridge *wsBridge) createCanvasVar(funcName string, args ...any) any { return result } -func (bridge *wsBridge) callCanvasVarFunc(v any, funcName string, args ...any) { +func (bridge *webBridge) callCanvasVarFunc(v any, funcName string, args ...any) { varName, ok := v.(canvasVar) if !ok { return @@ -313,7 +371,7 @@ func (bridge *wsBridge) callCanvasVarFunc(v any, funcName string, args ...any) { bridge.canvasBuffer.WriteString(");") } -func (bridge *wsBridge) callCanvasImageFunc(url string, property string, funcName string, args ...any) { +func (bridge *webBridge) callCanvasImageFunc(url string, property string, funcName string, args ...any) { bridge.canvasBuffer.WriteString("\nimg = images.get('") bridge.canvasBuffer.WriteString(url) @@ -334,56 +392,12 @@ func (bridge *wsBridge) callCanvasImageFunc(url string, property string, funcNam bridge.canvasBuffer.WriteString(");\n}") } -func (bridge *wsBridge) canvasFinish() { - bridge.canvasBuffer.WriteString("\n") - script := bridge.canvasBuffer.String() - if ProtocolInDebugLog { - DebugLog("Run script:") - DebugLog(script) - } - bridge.writeMutex.Lock() - if bridge.conn == nil { - ErrorLog("No connection") - } else if err := bridge.conn.WriteMessage(websocket.TextMessage, []byte(script)); err != nil { - ErrorLog(err.Error()) - } - bridge.writeMutex.Unlock() +func (bridge *webBridge) canvasFinish() { + bridge.canvasBuffer.WriteString("\n}\n") + bridge.writeMessage(bridge.canvasBuffer.String()) } -func (bridge *wsBridge) readMessage() (string, bool) { - _, p, err := bridge.conn.ReadMessage() - if err != nil { - if !bridge.closed { - ErrorLog(err.Error()) - } - return "", false - } - - return string(p), true -} - -func (bridge *wsBridge) writeMessage(script string) bool { - if ProtocolInDebugLog { - DebugLog("Run script:") - DebugLog(script) - } - if bridge.conn == nil { - ErrorLog("No connection") - return false - } - bridge.writeMutex.Lock() - err := bridge.conn.WriteMessage(websocket.TextMessage, []byte(script)) - bridge.writeMutex.Unlock() - if err != nil { - ErrorLog(err.Error()) - return false - } - return true -} - -func (bridge *wsBridge) canvasTextMetrics(htmlID, font, text string) TextMetrics { - result := TextMetrics{} - +func (bridge *webBridge) removeValue(funcName, htmlID string, args ...string) (DataObject, bool) { bridge.answerMutex.Lock() answerID := bridge.answerID bridge.answerID++ @@ -392,36 +406,40 @@ func (bridge *wsBridge) canvasTextMetrics(htmlID, font, text string) TextMetrics answer := make(chan DataObject) bridge.answer[answerID] = answer - if bridge.callFunc("canvasTextMetrics", answerID, htmlID, font, text) { - data := <-answer - result.Width = dataFloatProperty(data, "width") + funcArgs := []any{answerID, htmlID} + for _, arg := range args { + funcArgs = append(funcArgs, arg) } + var result DataObject = nil + ok := bridge.callFuncImmediately(funcName, funcArgs...) + if ok { + result = <-answer + } + + close(answer) delete(bridge.answer, answerID) + return result, true +} + +func (bridge *webBridge) canvasTextMetrics(htmlID, font, text string) TextMetrics { + result := TextMetrics{} + if data, ok := bridge.removeValue("canvasTextMetrics", htmlID, font, text); ok { + result.Width = dataFloatProperty(data, "width") + } return result } -func (bridge *wsBridge) htmlPropertyValue(htmlID, name string) string { - bridge.answerMutex.Lock() - answerID := bridge.answerID - bridge.answerID++ - bridge.answerMutex.Unlock() - - answer := make(chan DataObject) - bridge.answer[answerID] = answer - - if bridge.callFunc("getPropertyValue", answerID, htmlID, name) { - data := <-answer +func (bridge *webBridge) htmlPropertyValue(htmlID, name string) string { + if data, ok := bridge.removeValue("getPropertyValue", htmlID, name); ok { if value, ok := data.PropertyValue("value"); ok { return value } } - - delete(bridge.answer, answerID) return "" } -func (bridge *wsBridge) answerReceived(answer DataObject) { +func (bridge *webBridge) answerReceived(answer DataObject) { if text, ok := answer.PropertyValue("answerID"); ok { if id, err := strconv.Atoi(text); err == nil { if chanel, ok := bridge.answer[id]; ok { @@ -438,6 +456,55 @@ func (bridge *wsBridge) answerReceived(answer DataObject) { } } +func (bridge *wsBridge) close() { + bridge.closed = true + defer bridge.conn.Close() + bridge.conn = nil +} + +func (bridge *wsBridge) readMessage() (string, bool) { + _, p, err := bridge.conn.ReadMessage() + if err != nil { + if !bridge.closed { + ErrorLog(err.Error()) + } + return "", false + } + + return string(p), true +} + +func (bridge *wsBridge) sendResponse() { +} + func (bridge *wsBridge) remoteAddr() string { return bridge.conn.RemoteAddr().String() } + +func (bridge *httpBridge) close() { + bridge.closed = true + // TODO +} + +func (bridge *httpBridge) callImmediately(funcName string, args ...any) bool { + if funcText, ok := bridge.callFuncScript(funcName, args...); ok { + if ProtocolInDebugLog { + DebugLog("Run func: " + funcText) + } + bridge.response <- funcText + return true + } + return false +} + +func (bridge *httpBridge) sendResponse() { + bridge.writeMutex.Lock() + text := bridge.responseBuffer.String() + bridge.responseBuffer.Reset() + bridge.writeMutex.Unlock() + bridge.response <- text +} + +func (bridge *httpBridge) remoteAddr() string { + return bridge.remoteAddress +}