Added NoSocket parameter of the app

This commit is contained in:
anoshenko 2024-03-12 19:32:22 +03:00
parent 30c915d73b
commit ebcba7f9c2
9 changed files with 501 additions and 206 deletions

View File

@ -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("<!DOCTYPE html>\n<html>\n")
getStartPage(buffer, app.params, socketScripts)
getStartPage(buffer, app.params)
buffer.WriteString("\n</html>")
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)

View File

@ -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)
}
}
}
}

75
app_post.js Normal file
View File

@ -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 +"}" );
}

View File

@ -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) {

View File

@ -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(`<head>
<meta charset="utf-8">
<title>`)
@ -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>

View File

@ -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
}

View File

@ -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
}

View File

@ -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() {
}

View File

@ -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
}