Bug fixing

This commit is contained in:
Alexei Anoshenko 2026-08-06 16:52:19 +03:00
parent a9e4eddb3f
commit b18c632a44
8 changed files with 187 additions and 100 deletions

View File

@ -128,6 +128,9 @@ func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
io.WriteString(w, app.getStartPage()) io.WriteString(w, app.getStartPage())
case "/e":
app.sseHandler(w, req)
case "/ws": case "/ws":
if bridge := createSocketBridge(w, req); bridge != nil { if bridge := createSocketBridge(w, req); bridge != nil {
go app.socketReader(bridge) 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) { func setSessionIDCookie(w http.ResponseWriter, sessionID int) {
cookie := http.Cookie{ cookie := http.Cookie{
Name: "session", Name: "session",
@ -174,6 +178,7 @@ func getSessionIDCookie(req *http.Request) (int, error) {
return strconv.Atoi(cookie.Value) return strconv.Atoi(cookie.Value)
} }
*/
func (app *application) postHandler(w http.ResponseWriter, req *http.Request) { 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() 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) events := make(chan DataObject, 1024)
bridge := createHttpBridge(req) bridge := createHttpBridge(req)
response = bridge.response response = bridge.response
@ -219,30 +229,25 @@ func (app *application) postHandler(w http.ResponseWriter, req *http.Request) {
} }
go sessionEventHandler(session, events, bridge) go sessionEventHandler(session, events, bridge)
start := func() {
startSession(session, app.createContentFunc)
bridge.sendResponse()
}
go start()
} }
switch command { switch command {
case "startSession":
// do nothing
case "nop":
session.sendResponse()
case "session-close": case "session-close":
session.onFinish() session.onFinish()
session.App().removeSession(session.ID()) session.App().removeSession(session.ID())
return return
case "nop":
if len(response) == 0 {
session.addToEventsQueue(obj)
}
default: default:
if !session.handleAnswer(command, obj) { if !session.handleAnswer(command, obj) {
session.addToEventsQueue(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) { func getSessionID(obj DataObject) (int, bool) {
sessionText, ok := obj.PropertyValue("session") sessionText, ok := obj.PropertyValue("session")
if !ok { if !ok {
@ -286,14 +305,14 @@ func (app *application) socketReader(bridge *wsBridge) {
obj, err := ParseDataText(message) obj, err := ParseDataText(message)
if err != nil { if err != nil {
ErrorLog(err.Error()) ErrorLog(err.Error())
return continue
} }
switch command := obj.Tag(); command { switch command := obj.Tag(); command {
case "startSession": case "start-session":
if session = app.createSession(obj, events, bridge, nil); session != nil { if session = app.createSession(obj, events, bridge, nil); session != nil {
go startSession(session, app.createContentFunc)
go sessionEventHandler(session, events, bridge) go sessionEventHandler(session, events, bridge)
events <- obj
} }
case "reconnect": case "reconnect":
@ -311,16 +330,6 @@ func (app *application) socketReader(bridge *wsBridge) {
} }
if session == nil { 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();") bridge.writeMessage("reloadPage();")
return return
} }
@ -339,6 +348,7 @@ func sessionEventHandler(session Session, events chan DataObject, bridge bridge)
switch command := data.Tag(); command { switch command := data.Tag(); command {
case "disconnect": case "disconnect":
session.setBridge(nil, nil)
session.onDisconnect() session.onDisconnect()
return return
@ -348,8 +358,11 @@ func sessionEventHandler(session Session, events chan DataObject, bridge bridge)
bridge.close() bridge.close()
return return
case "nop":
session.sendResponse()
default: 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 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{} var apps = []*application{}
// StartApp - create the new application and start it // StartApp - create the new application and start it

View File

@ -1,5 +1,10 @@
let eventSource
async function sendMessage(message) { async function sendMessage(message) {
if (!eventSource) {
createEventSource();
}
const response = await fetch('/', { const response = await fetch('/', {
method : 'POST', method : 'POST',
body : message, 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() { window.onload = function() {
sendMessage( sessionInfo() ); sendMessage( sessionInfo("start-session") );
} }
window.onfocus = function() { window.onfocus = function() {
windowFocus = true windowFocus = true;
sendMessage( "session-resume{}" ); sendMessage( "session-resume{session=" + sessionID +"}" );
} }
function closeSocket() { function closeSocket() {
if (eventSource) {
eventSource.close();
eventSource = null;
}
} }
function sendNop() {
sendMessage( "nop{session=" + sessionID +"}" );
}

View File

@ -1,4 +1,3 @@
//let sessionID = "0"
let windowFocus = true let windowFocus = true
window.onresize = function() { window.onresize = function() {
@ -18,11 +17,16 @@ function reloadPage() {
location.reload(); location.reload();
} }
function sessionInfo() { function sessionInfo(messageID) {
const touch_screen = (('ontouchstart' in document.documentElement) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) ? "1" : "0"; let message = messageID + "{session=" + sessionID
let message = "startSession{session=" + sessionID + ",touch=" + touch_screen
if (('ontouchstart' in document.documentElement) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) {
message += ",touch=1"
} else {
message += ",touch=0"
}
const style = window.getComputedStyle(document.body); const style = window.getComputedStyle(document.body);
if (style) { if (style) {
const direction = style.getPropertyValue('direction'); const direction = style.getPropertyValue('direction');
@ -65,8 +69,12 @@ function sessionInfo() {
return message + "}"; return message + "}";
} }
function sendReconnectMessage() {
sendMessage( sessionInfo("reconnect") );
}
function restartSession() { function restartSession() {
sendMessage( sessionInfo() ); sendMessage( sessionInfo("start-session") );
} }
function getIntAttribute(element, tag) { function getIntAttribute(element, tag) {

View File

@ -1,9 +1,9 @@
let socket let socket;
function sendMessage(message) { function sendMessage(message) {
if (!socket) { if (!socket) {
createSocket(function() { createSocket(function() {
sendMessage( "reconnect{session=" + sessionID + "}" ); sendReconnectMessage();
if (!windowFocus) { if (!windowFocus) {
windowFocus = true; windowFocus = true;
sendMessage( "session-resume{session=" + sessionID +"}" ); sendMessage( "session-resume{session=" + sessionID +"}" );
@ -16,13 +16,13 @@ function sendMessage(message) {
} }
function createSocket(onopen) { function createSocket(onopen) {
let socketUrl = document.location.protocol == "https:" ? "wss://" : "ws://" let socketUrl = document.location.protocol == "https:" ? "wss://" : "ws://" ;
socketUrl += document.location.hostname socketUrl += document.location.hostname;
const port = document.location.port const port = document.location.port;
if (port) { if (port) {
socketUrl += ":" + port socketUrl += ":" + port;
} }
socketUrl += window.location.pathname + "ws" socketUrl += window.location.pathname + "ws";
socket = new WebSocket(socketUrl); socket = new WebSocket(socketUrl);
socket.onopen = onopen; socket.onopen = onopen;
@ -35,19 +35,19 @@ function createSocket(onopen) {
function closeSocket() { function closeSocket() {
if (socket) { if (socket) {
socket.close() socket.close();
} }
} }
window.onload = createSocket(function() { window.onload = createSocket(function() {
sendMessage( sessionInfo() ); sendMessage( sessionInfo("start-session") );
}); });
window.onfocus = function() { window.onfocus = function() {
windowFocus = true windowFocus = true;
if (!socket) { if (!socket) {
createSocket(function() { createSocket(function() {
sendMessage( "reconnect{session=" + sessionID + "}" ); sendReconnectMessage();
sendMessage( "session-resume{session=" + sessionID +"}" ); sendMessage( "session-resume{session=" + sessionID +"}" );
}); });
} else { } else {
@ -55,18 +55,14 @@ window.onfocus = function() {
} }
} }
function onSocketReopen() {
sendMessage( "reconnect{session=" + sessionID + "}" );
}
function socketReconnect() { function socketReconnect() {
if (!socket) { if (!socket) {
createSocket(onSocketReopen); createSocket(sendReconnectMessage);
} }
} }
function onSocketClose(event) { function onSocketClose(event) {
console.log("socket closed") console.log("socket closed");
socket = null; socket = null;
if (!event.wasClean && windowFocus) { if (!event.wasClean && windowFocus) {
window.setTimeout(socketReconnect, 10000); window.setTimeout(socketReconnect, 10000);

View File

@ -130,8 +130,6 @@ func (storage *clientStorageData) handleEvent(command string, data DataObject) {
ErrorLog(text) ErrorLog(text)
} }
//case "storageSuccess":
case "storageValues": case "storageValues":
fn, ok := storage.getResult[request] fn, ok := storage.getResult[request]
if !ok { if !ok {

View File

@ -4,6 +4,7 @@ import (
"iter" "iter"
"slices" "slices"
"strings" "strings"
"sync"
) )
// Properties interface of properties map // Properties interface of properties map
@ -37,6 +38,7 @@ type Properties interface {
type propertyList struct { type propertyList struct {
properties map[PropertyName]any properties map[PropertyName]any
normalize func(PropertyName) PropertyName normalize func(PropertyName) PropertyName
mutex sync.Mutex
} }
type dataProperty struct { type dataProperty struct {
@ -60,10 +62,15 @@ func (properties *propertyList) init() {
} }
func (properties *propertyList) IsEmpty() bool { func (properties *propertyList) IsEmpty() bool {
properties.mutex.Lock()
defer properties.mutex.Unlock()
return len(properties.properties) == 0 return len(properties.properties) == 0
} }
func (properties *propertyList) getRaw(tag PropertyName) any { func (properties *propertyList) getRaw(tag PropertyName) any {
properties.mutex.Lock()
defer properties.mutex.Unlock()
if value, ok := properties.properties[tag]; ok { if value, ok := properties.properties[tag]; ok {
return value return value
} }
@ -71,11 +78,13 @@ func (properties *propertyList) getRaw(tag PropertyName) any {
} }
func (properties *propertyList) setRaw(tag PropertyName, value any) { func (properties *propertyList) setRaw(tag PropertyName, value any) {
properties.mutex.Lock()
if value == nil { if value == nil {
delete(properties.properties, tag) delete(properties.properties, tag)
} else { } else {
properties.properties[tag] = value 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)) properties.remove(properties, properties.normalize(tag))
} }
*/ */
func (properties *propertyList) Clear() { func (properties *propertyList) Clear() {
properties.mutex.Lock()
properties.properties = map[PropertyName]any{} properties.properties = map[PropertyName]any{}
properties.mutex.Unlock()
} }
func (properties *propertyList) All() iter.Seq2[PropertyName, any] { 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 { func (properties *propertyList) AllTags() []PropertyName {
properties.mutex.Lock()
tags := make([]PropertyName, 0, len(properties.properties)) tags := make([]PropertyName, 0, len(properties.properties))
for tag := range properties.properties { for tag := range properties.properties {
tags = append(tags, tag) tags = append(tags, tag)
} }
properties.mutex.Unlock()
slices.Sort(tags) slices.Sort(tags)
return tags return tags
} }

View File

@ -169,7 +169,7 @@ type Session interface {
styleProperty(styleTag string, propertyTag PropertyName) any styleProperty(styleTag string, propertyTag PropertyName) any
setBridge(events chan DataObject, bridge bridge) setBridge(events chan DataObject, bridge bridge)
writeInitScript(writer *strings.Builder) writeInitScript()
callFunc(funcName string, args ...any) callFunc(funcName string, args ...any)
updateInnerHTML(htmlID, html string) updateInnerHTML(htmlID, html string)
appendToInnerHTML(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) { func (session *sessionData) setBridge(events chan DataObject, bridge bridge) {
if session.events != nil {
close(session.events)
}
session.events = events session.events = events
session.bridge = bridge session.bridge = bridge
} }
@ -351,23 +354,39 @@ func (session *sessionData) RootView() View {
return session.rootView 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 != "" { if css := session.getCurrentTheme().cssText(session); css != "" {
css = strings.ReplaceAll(css, "\n", `\n`) css = strings.ReplaceAll(css, "\n", `\n`)
css = strings.ReplaceAll(css, "\t", `\t`) css = strings.ReplaceAll(css, "\t", `\t`)
writer.WriteString(`document.querySelector('style').textContent += "`)
writer.WriteString(css) script := `document.querySelector('style').textContent += "` + css + "\";\n"
writer.WriteString("\";\n") session.bridge.writeScript(script)
if ProtocolInDebugLog {
DebugLog(script)
}
} }
if session.rootView != nil { if session.rootView != nil {
writer.WriteString(`document.getElementById('ruiRootView').innerHTML = '`)
buffer := allocStringBuilder() buffer := allocStringBuilder()
defer freeStringBuilder(buffer) defer freeStringBuilder(buffer)
viewHTML(session.rootView, buffer, "") viewHTML(session.rootView, buffer, "")
text := strings.ReplaceAll(buffer.String(), "'", `\'`)
writer.WriteString(text) html := buffer.String()
writer.WriteString("';\nscanElementsSize();") session.bridge.callFunc("updateInnerHTML", "ruiRootView", html)
if ProtocolInDebugLog {
DebugLog(html)
}
} }
session.updateTooltipConstants() session.updateTooltipConstants()
@ -666,7 +685,7 @@ func (session *sessionData) handleAnswer(command string, data DataObject) bool {
switch command { switch command {
case "answer": case "answer":
if session.bridge != nil { if session.bridge != nil {
session.bridge.answerReceived(data) go session.bridge.answerReceived(data)
} }
case "imageLoaded": case "imageLoaded":
@ -675,6 +694,11 @@ func (session *sessionData) handleAnswer(command string, data DataObject) bool {
case "imageError": case "imageError":
session.imageManager().imageLoadError(data) session.imageManager().imageLoadError(data)
case "storageError", "storageValues":
if session.clientStorage != nil {
session.clientStorage.handleEvent(command, data)
}
default: default:
return false return false
} }
@ -793,6 +817,12 @@ func (session *sessionData) handleSessionInfo(params DataObject) {
func (session *sessionData) handleEvent(command string, data DataObject) { func (session *sessionData) handleEvent(command string, data DataObject) {
switch command { switch command {
case "start-session":
if session.setContent(session.App().getCreateContentFunc()(session)) {
session.writeInitScript()
session.onStart()
}
case "session-pause": case "session-pause":
session.onPause() session.onPause()
@ -824,11 +854,6 @@ func (session *sessionData) handleEvent(command string, data DataObject) {
case "sessionInfo": case "sessionInfo":
session.handleSessionInfo(data) session.handleSessionInfo(data)
case "storageError", "storageSuccess", "storageValues":
if session.clientStorage != nil {
session.clientStorage.handleEvent(command, data)
}
default: default:
if viewID, ok := data.PropertyValue("id"); ok { if viewID, ok := data.PropertyValue("id"); ok {
if viewID != "body" { if viewID != "body" {
@ -931,7 +956,9 @@ func (session *sessionData) OpenURL(urlStr string) {
} }
func (session *sessionData) addToEventsQueue(data DataObject) { 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 { func (session *sessionData) StartTimer(ms int, timerFunc func(Session)) int {

View File

@ -84,7 +84,7 @@ func createSocketBridge(w http.ResponseWriter, req *http.Request) *wsBridge {
func createHttpBridge(req *http.Request) *httpBridge { func createHttpBridge(req *http.Request) *httpBridge {
bridge := new(httpBridge) bridge := new(httpBridge)
bridge.initBridge() bridge.initBridge()
bridge.response = make(chan string, 10) bridge.response = make(chan string, 100)
bridge.writeMessage = func(script string) bool { bridge.writeMessage = func(script string) bool {
if script != "" { if script != "" {
if ProtocolInDebugLog { if ProtocolInDebugLog {
@ -139,14 +139,31 @@ func (bridge *webBridge) finishUpdateScript(htmlID string) {
func (bridge *webBridge) argToString(arg any) (string, bool) { func (bridge *webBridge) argToString(arg any) (string, bool) {
switch arg := arg.(type) { switch arg := arg.(type) {
case string: case string:
arg = strings.ReplaceAll(arg, "\\", `\\`) escChars := []struct{ esc, repl string }{
arg = strings.ReplaceAll(arg, "'", `\'`) {esc: "\\", repl: `\\`},
arg = strings.ReplaceAll(arg, "\n", `\n`) {esc: "'", repl: `\'`},
arg = strings.ReplaceAll(arg, "\r", `\r`) {esc: "\b", repl: `\b`},
arg = strings.ReplaceAll(arg, "\t", `\t`) {esc: "\t", repl: `\t`},
arg = strings.ReplaceAll(arg, "\b", `\b`) {esc: "\n", repl: `\n`},
arg = strings.ReplaceAll(arg, "\f", `\f`) {esc: "\v", repl: `\v`},
arg = strings.ReplaceAll(arg, "\v", `\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 return `'` + arg + `'`, true
case rune: case rune: