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(`
-
+
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
+}