From b18c632a44877a468efb32fefec244d0efc3b373 Mon Sep 17 00:00:00 2001 From: Alexei Anoshenko <2277098+anoshenko@users.noreply.github.com> Date: Thu, 6 Aug 2026 16:52:19 +0300 Subject: [PATCH] Bug fixing --- appServer.go | 88 ++++++++++++++++++++++-------------------------- app_post.js | 40 ++++++++++++++++++++-- app_scripts.js | 18 +++++++--- app_socket.js | 30 +++++++---------- clientStorage.go | 2 -- properties.go | 15 +++++++++ session.go | 59 +++++++++++++++++++++++--------- webBridge.go | 35 ++++++++++++++----- 8 files changed, 187 insertions(+), 100 deletions(-) diff --git a/appServer.go b/appServer.go index 5576876..9d88859 100644 --- a/appServer.go +++ b/appServer.go @@ -128,6 +128,9 @@ func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) io.WriteString(w, app.getStartPage()) + case "/e": + app.sseHandler(w, req) + case "/ws": if bridge := createSocketBridge(w, req); bridge != nil { go app.socketReader(bridge) @@ -157,6 +160,7 @@ func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) { } } +/* func setSessionIDCookie(w http.ResponseWriter, sessionID int) { cookie := http.Cookie{ Name: "session", @@ -174,6 +178,7 @@ func getSessionIDCookie(req *http.Request) (int, error) { return strconv.Atoi(cookie.Value) } +*/ func (app *application) postHandler(w http.ResponseWriter, req *http.Request) { @@ -208,7 +213,12 @@ func (app *application) postHandler(w http.ResponseWriter, req *http.Request) { command := obj.Tag() - if session == nil || command == "startSession" { + if session == nil { + if command != "start-session" { + io.WriteString(w, "reloadPage();") + return + } + events := make(chan DataObject, 1024) bridge := createHttpBridge(req) response = bridge.response @@ -219,30 +229,25 @@ func (app *application) postHandler(w http.ResponseWriter, req *http.Request) { } go sessionEventHandler(session, events, bridge) - - start := func() { - startSession(session, app.createContentFunc) - bridge.sendResponse() - } - go start() } switch command { - case "startSession": - // do nothing - - case "nop": - session.sendResponse() - case "session-close": session.onFinish() session.App().removeSession(session.ID()) return + case "nop": + if len(response) == 0 { + session.addToEventsQueue(obj) + } + default: if !session.handleAnswer(command, obj) { session.addToEventsQueue(obj) + } else { + io.WriteString(w, "sendNop();") } } @@ -252,6 +257,20 @@ func (app *application) postHandler(w http.ResponseWriter, req *http.Request) { } } +func (app *application) sseHandler(w http.ResponseWriter, req *http.Request) { + /* + sessionID, err := strconv.Atoi(req.URL.Query().Get("id")) + if err != nil { + ErrorLog("SessionID error: " + err.Error()) + return + } + + if sessionInfo, ok := app.sessions[sessionID]; ok { + + } + */ +} + func getSessionID(obj DataObject) (int, bool) { sessionText, ok := obj.PropertyValue("session") if !ok { @@ -286,14 +305,14 @@ func (app *application) socketReader(bridge *wsBridge) { obj, err := ParseDataText(message) if err != nil { ErrorLog(err.Error()) - return + continue } switch command := obj.Tag(); command { - case "startSession": + case "start-session": if session = app.createSession(obj, events, bridge, nil); session != nil { - go startSession(session, app.createContentFunc) go sessionEventHandler(session, events, bridge) + events <- obj } case "reconnect": @@ -311,16 +330,6 @@ func (app *application) socketReader(bridge *wsBridge) { } if session == nil { - /* answer := "" - if session, answer = app.startSession(obj, events, bridge, nil); session != nil { - if !bridge.writeMessage(answer) { - return - } - session.onStart() - go sessionEventHandler(session, events, bridge) - bridge.writeMessage("restartSession();") - } - */ bridge.writeMessage("reloadPage();") return } @@ -339,6 +348,7 @@ func sessionEventHandler(session Session, events chan DataObject, bridge bridge) switch command := data.Tag(); command { case "disconnect": + session.setBridge(nil, nil) session.onDisconnect() return @@ -348,8 +358,11 @@ func sessionEventHandler(session Session, events chan DataObject, bridge bridge) bridge.close() return + case "nop": + session.sendResponse() + default: - go session.handleEvent(command, data) + session.handleEvent(command, data) } } } @@ -373,27 +386,6 @@ func (app *application) createSession(params DataObject, events chan DataObject, return session } -func startSession(session Session, createContentFunc func(Session) SessionContent) { - - if !session.setContent(createContentFunc(session)) { - return - } - - answer := allocStringBuilder() - defer freeStringBuilder(answer) - - session.writeInitScript(answer) - answerText := answer.String() - - if ProtocolInDebugLog { - DebugLog("Start session:") - DebugLog(answerText) - } - - session.writeScript(answerText) - session.onStart() -} - var apps = []*application{} // StartApp - create the new application and start it diff --git a/app_post.js b/app_post.js index e17e625..14f6901 100644 --- a/app_post.js +++ b/app_post.js @@ -1,5 +1,10 @@ +let eventSource async function sendMessage(message) { + if (!eventSource) { + createEventSource(); + } + const response = await fetch('/', { method : 'POST', body : message, @@ -12,14 +17,43 @@ async function sendMessage(message) { } } +function createEventSource() { + /* + eventSource = new EventSource("e?id="+sessionID); + eventSource.onmessage = onEventMessage; + eventSource.onerror = onEventError; + */ +} + +function onEventMessage(event) { + let script = base64ToString(event.data); + if (script != "") { + window.eval(script) + } +} + +function onEventError(err) { + console.log(err); + eventSource = null; +} + + window.onload = function() { - sendMessage( sessionInfo() ); + sendMessage( sessionInfo("start-session") ); } window.onfocus = function() { - windowFocus = true - sendMessage( "session-resume{}" ); + windowFocus = true; + sendMessage( "session-resume{session=" + sessionID +"}" ); } function closeSocket() { + if (eventSource) { + eventSource.close(); + eventSource = null; + } } + +function sendNop() { + sendMessage( "nop{session=" + sessionID +"}" ); +} \ No newline at end of file diff --git a/app_scripts.js b/app_scripts.js index 70f763c..2e1d6e6 100644 --- a/app_scripts.js +++ b/app_scripts.js @@ -1,4 +1,3 @@ -//let sessionID = "0" let windowFocus = true window.onresize = function() { @@ -18,11 +17,16 @@ function reloadPage() { location.reload(); } -function sessionInfo() { +function sessionInfo(messageID) { - const touch_screen = (('ontouchstart' in document.documentElement) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) ? "1" : "0"; - let message = "startSession{session=" + sessionID + ",touch=" + touch_screen + let message = messageID + "{session=" + sessionID + if (('ontouchstart' in document.documentElement) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) { + message += ",touch=1" + } else { + message += ",touch=0" + } + const style = window.getComputedStyle(document.body); if (style) { const direction = style.getPropertyValue('direction'); @@ -65,8 +69,12 @@ function sessionInfo() { return message + "}"; } +function sendReconnectMessage() { + sendMessage( sessionInfo("reconnect") ); +} + function restartSession() { - sendMessage( sessionInfo() ); + sendMessage( sessionInfo("start-session") ); } function getIntAttribute(element, tag) { diff --git a/app_socket.js b/app_socket.js index 53aa2cf..847469d 100644 --- a/app_socket.js +++ b/app_socket.js @@ -1,9 +1,9 @@ -let socket +let socket; function sendMessage(message) { if (!socket) { createSocket(function() { - sendMessage( "reconnect{session=" + sessionID + "}" ); + sendReconnectMessage(); if (!windowFocus) { windowFocus = true; sendMessage( "session-resume{session=" + sessionID +"}" ); @@ -16,13 +16,13 @@ function sendMessage(message) { } function createSocket(onopen) { - let socketUrl = document.location.protocol == "https:" ? "wss://" : "ws://" - socketUrl += document.location.hostname - const port = document.location.port + let socketUrl = document.location.protocol == "https:" ? "wss://" : "ws://" ; + socketUrl += document.location.hostname; + const port = document.location.port; if (port) { - socketUrl += ":" + port + socketUrl += ":" + port; } - socketUrl += window.location.pathname + "ws" + socketUrl += window.location.pathname + "ws"; socket = new WebSocket(socketUrl); socket.onopen = onopen; @@ -35,19 +35,19 @@ function createSocket(onopen) { function closeSocket() { if (socket) { - socket.close() + socket.close(); } } window.onload = createSocket(function() { - sendMessage( sessionInfo() ); + sendMessage( sessionInfo("start-session") ); }); window.onfocus = function() { - windowFocus = true + windowFocus = true; if (!socket) { createSocket(function() { - sendMessage( "reconnect{session=" + sessionID + "}" ); + sendReconnectMessage(); sendMessage( "session-resume{session=" + sessionID +"}" ); }); } else { @@ -55,18 +55,14 @@ window.onfocus = function() { } } -function onSocketReopen() { - sendMessage( "reconnect{session=" + sessionID + "}" ); -} - function socketReconnect() { if (!socket) { - createSocket(onSocketReopen); + createSocket(sendReconnectMessage); } } function onSocketClose(event) { - console.log("socket closed") + console.log("socket closed"); socket = null; if (!event.wasClean && windowFocus) { window.setTimeout(socketReconnect, 10000); diff --git a/clientStorage.go b/clientStorage.go index 059f6bf..4c78b31 100644 --- a/clientStorage.go +++ b/clientStorage.go @@ -130,8 +130,6 @@ func (storage *clientStorageData) handleEvent(command string, data DataObject) { ErrorLog(text) } - //case "storageSuccess": - case "storageValues": fn, ok := storage.getResult[request] if !ok { diff --git a/properties.go b/properties.go index b3a55e2..bd43877 100644 --- a/properties.go +++ b/properties.go @@ -4,6 +4,7 @@ import ( "iter" "slices" "strings" + "sync" ) // Properties interface of properties map @@ -37,6 +38,7 @@ type Properties interface { type propertyList struct { properties map[PropertyName]any normalize func(PropertyName) PropertyName + mutex sync.Mutex } type dataProperty struct { @@ -60,10 +62,15 @@ func (properties *propertyList) init() { } func (properties *propertyList) IsEmpty() bool { + properties.mutex.Lock() + defer properties.mutex.Unlock() return len(properties.properties) == 0 } func (properties *propertyList) getRaw(tag PropertyName) any { + properties.mutex.Lock() + defer properties.mutex.Unlock() + if value, ok := properties.properties[tag]; ok { return value } @@ -71,11 +78,13 @@ func (properties *propertyList) getRaw(tag PropertyName) any { } func (properties *propertyList) setRaw(tag PropertyName, value any) { + properties.mutex.Lock() if value == nil { delete(properties.properties, tag) } else { properties.properties[tag] = value } + properties.mutex.Unlock() } /* @@ -83,8 +92,11 @@ func (properties *propertyList) setRaw(tag PropertyName, value any) { properties.remove(properties, properties.normalize(tag)) } */ + func (properties *propertyList) Clear() { + properties.mutex.Lock() properties.properties = map[PropertyName]any{} + properties.mutex.Unlock() } func (properties *propertyList) All() iter.Seq2[PropertyName, any] { @@ -98,10 +110,13 @@ func (properties *propertyList) All() iter.Seq2[PropertyName, any] { } func (properties *propertyList) AllTags() []PropertyName { + properties.mutex.Lock() tags := make([]PropertyName, 0, len(properties.properties)) for tag := range properties.properties { tags = append(tags, tag) } + properties.mutex.Unlock() + slices.Sort(tags) return tags } diff --git a/session.go b/session.go index 8764fc5..5936ef8 100644 --- a/session.go +++ b/session.go @@ -169,7 +169,7 @@ type Session interface { styleProperty(styleTag string, propertyTag PropertyName) any setBridge(events chan DataObject, bridge bridge) - writeInitScript(writer *strings.Builder) + writeInitScript() callFunc(funcName string, args ...any) updateInnerHTML(htmlID, html string) appendToInnerHTML(htmlID, html string) @@ -294,6 +294,9 @@ func (session *sessionData) ID() int { } func (session *sessionData) setBridge(events chan DataObject, bridge bridge) { + if session.events != nil { + close(session.events) + } session.events = events session.bridge = bridge } @@ -351,23 +354,39 @@ func (session *sessionData) RootView() View { return session.rootView } -func (session *sessionData) writeInitScript(writer *strings.Builder) { +func (session *sessionData) writeInitScript() { + + if session.bridge == nil { + return + } + + if ProtocolInDebugLog { + DebugLog("Start session:") + } + if css := session.getCurrentTheme().cssText(session); css != "" { css = strings.ReplaceAll(css, "\n", `\n`) css = strings.ReplaceAll(css, "\t", `\t`) - writer.WriteString(`document.querySelector('style').textContent += "`) - writer.WriteString(css) - writer.WriteString("\";\n") + + script := `document.querySelector('style').textContent += "` + css + "\";\n" + session.bridge.writeScript(script) + + if ProtocolInDebugLog { + DebugLog(script) + } } if session.rootView != nil { - writer.WriteString(`document.getElementById('ruiRootView').innerHTML = '`) buffer := allocStringBuilder() defer freeStringBuilder(buffer) viewHTML(session.rootView, buffer, "") - text := strings.ReplaceAll(buffer.String(), "'", `\'`) - writer.WriteString(text) - writer.WriteString("';\nscanElementsSize();") + + html := buffer.String() + session.bridge.callFunc("updateInnerHTML", "ruiRootView", html) + + if ProtocolInDebugLog { + DebugLog(html) + } } session.updateTooltipConstants() @@ -666,7 +685,7 @@ func (session *sessionData) handleAnswer(command string, data DataObject) bool { switch command { case "answer": if session.bridge != nil { - session.bridge.answerReceived(data) + go session.bridge.answerReceived(data) } case "imageLoaded": @@ -675,6 +694,11 @@ func (session *sessionData) handleAnswer(command string, data DataObject) bool { case "imageError": session.imageManager().imageLoadError(data) + case "storageError", "storageValues": + if session.clientStorage != nil { + session.clientStorage.handleEvent(command, data) + } + default: return false } @@ -793,6 +817,12 @@ func (session *sessionData) handleSessionInfo(params DataObject) { func (session *sessionData) handleEvent(command string, data DataObject) { switch command { + case "start-session": + if session.setContent(session.App().getCreateContentFunc()(session)) { + session.writeInitScript() + session.onStart() + } + case "session-pause": session.onPause() @@ -824,11 +854,6 @@ func (session *sessionData) handleEvent(command string, data DataObject) { case "sessionInfo": session.handleSessionInfo(data) - case "storageError", "storageSuccess", "storageValues": - if session.clientStorage != nil { - session.clientStorage.handleEvent(command, data) - } - default: if viewID, ok := data.PropertyValue("id"); ok { if viewID != "body" { @@ -931,7 +956,9 @@ func (session *sessionData) OpenURL(urlStr string) { } func (session *sessionData) addToEventsQueue(data DataObject) { - session.events <- data + if session.events != nil { + session.events <- data + } } func (session *sessionData) StartTimer(ms int, timerFunc func(Session)) int { diff --git a/webBridge.go b/webBridge.go index 2f67060..156663b 100644 --- a/webBridge.go +++ b/webBridge.go @@ -84,7 +84,7 @@ func createSocketBridge(w http.ResponseWriter, req *http.Request) *wsBridge { func createHttpBridge(req *http.Request) *httpBridge { bridge := new(httpBridge) bridge.initBridge() - bridge.response = make(chan string, 10) + bridge.response = make(chan string, 100) bridge.writeMessage = func(script string) bool { if script != "" { if ProtocolInDebugLog { @@ -139,14 +139,31 @@ func (bridge *webBridge) finishUpdateScript(htmlID string) { func (bridge *webBridge) argToString(arg any) (string, bool) { switch arg := arg.(type) { case string: - arg = strings.ReplaceAll(arg, "\\", `\\`) - arg = strings.ReplaceAll(arg, "'", `\'`) - arg = strings.ReplaceAll(arg, "\n", `\n`) - arg = strings.ReplaceAll(arg, "\r", `\r`) - arg = strings.ReplaceAll(arg, "\t", `\t`) - arg = strings.ReplaceAll(arg, "\b", `\b`) - arg = strings.ReplaceAll(arg, "\f", `\f`) - arg = strings.ReplaceAll(arg, "\v", `\v`) + escChars := []struct{ esc, repl string }{ + {esc: "\\", repl: `\\`}, + {esc: "'", repl: `\'`}, + {esc: "\b", repl: `\b`}, + {esc: "\t", repl: `\t`}, + {esc: "\n", repl: `\n`}, + {esc: "\v", repl: `\v`}, + {esc: "\r", repl: `\r`}, + {esc: "\f", repl: `\f`}, + } + + for _, s := range escChars { + if strings.Contains(arg, s.esc) { + arg = strings.ReplaceAll(arg, s.esc, s.repl) + } + } + + for n := range 0x20 { + esc := string([]rune{rune(n)}) + if strings.Contains(arg, esc) { + repl := fmt.Sprintf(`\x%02d`, n) + arg = strings.ReplaceAll(arg, esc, repl) + } + } + return `'` + arg + `'`, true case rune: