mirror of https://github.com/anoshenko/rui.git
Added wasm support
This commit is contained in:
parent
3c4315ed78
commit
4523a9a427
|
@ -0,0 +1,307 @@
|
|||
//go:build !wasm
|
||||
|
||||
package rui
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:embed app_socket.js
|
||||
var socketScripts string
|
||||
|
||||
type application struct {
|
||||
server *http.Server
|
||||
params AppParams
|
||||
createContentFunc func(Session) SessionContent
|
||||
sessions map[int]Session
|
||||
}
|
||||
|
||||
func (app *application) getStartPage() string {
|
||||
buffer := allocStringBuilder()
|
||||
defer freeStringBuilder(buffer)
|
||||
|
||||
buffer.WriteString("<!DOCTYPE html>\n<html>\n")
|
||||
getStartPage(buffer, app.params, socketScripts)
|
||||
buffer.WriteString("\n</html>")
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (app *application) Finish() {
|
||||
for _, session := range app.sessions {
|
||||
session.close()
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := app.server.Shutdown(ctx); err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) nextSessionID() int {
|
||||
n := rand.Intn(0x7FFFFFFE) + 1
|
||||
_, ok := app.sessions[n]
|
||||
for ok {
|
||||
n = rand.Intn(0x7FFFFFFE) + 1
|
||||
_, ok = app.sessions[n]
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (app *application) removeSession(id int) {
|
||||
delete(app.sessions, id)
|
||||
}
|
||||
|
||||
func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
if ProtocolInDebugLog {
|
||||
DebugLogF("%s %s", req.Method, req.URL.Path)
|
||||
}
|
||||
|
||||
switch req.Method {
|
||||
case "GET":
|
||||
switch req.URL.Path {
|
||||
case "/":
|
||||
w.WriteHeader(http.StatusOK)
|
||||
io.WriteString(w, app.getStartPage())
|
||||
|
||||
case "/ws":
|
||||
if brige := CreateSocketBrige(w, req); brige != nil {
|
||||
go app.socketReader(brige)
|
||||
}
|
||||
|
||||
default:
|
||||
filename := req.URL.Path[1:]
|
||||
if size := len(filename); size > 0 && filename[size-1] == '/' {
|
||||
filename = filename[:size-1]
|
||||
}
|
||||
|
||||
if !serveResourceFile(filename, w, req) &&
|
||||
!serveDownloadFile(filename, w, req) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) socketReader(brige webBrige) {
|
||||
var session Session
|
||||
events := make(chan DataObject, 1024)
|
||||
|
||||
for {
|
||||
message, ok := brige.readMessage()
|
||||
if !ok {
|
||||
events <- NewDataObject("disconnect")
|
||||
return
|
||||
}
|
||||
|
||||
if ProtocolInDebugLog {
|
||||
DebugLog(message)
|
||||
}
|
||||
|
||||
if obj := ParseDataText(message); obj != nil {
|
||||
command := obj.Tag()
|
||||
switch command {
|
||||
case "startSession":
|
||||
answer := ""
|
||||
if session, answer = app.startSession(obj, events, brige); session != nil {
|
||||
if !brige.writeMessage(answer) {
|
||||
return
|
||||
}
|
||||
session.onStart()
|
||||
go sessionEventHandler(session, events, brige)
|
||||
}
|
||||
|
||||
case "reconnect":
|
||||
if sessionText, ok := obj.PropertyValue("session"); ok {
|
||||
if sessionID, err := strconv.Atoi(sessionText); err == nil {
|
||||
if session = app.sessions[sessionID]; session != nil {
|
||||
session.setBrige(events, brige)
|
||||
answer := allocStringBuilder()
|
||||
defer freeStringBuilder(answer)
|
||||
|
||||
session.writeInitScript(answer)
|
||||
if !brige.writeMessage(answer.String()) {
|
||||
return
|
||||
}
|
||||
session.onReconnect()
|
||||
go sessionEventHandler(session, events, brige)
|
||||
return
|
||||
}
|
||||
DebugLogF("Session #%d not exists", sessionID)
|
||||
} else {
|
||||
ErrorLog(`strconv.Atoi(sessionText) error: ` + err.Error())
|
||||
}
|
||||
} else {
|
||||
ErrorLog(`"session" key not found`)
|
||||
}
|
||||
|
||||
answer := ""
|
||||
if session, answer = app.startSession(obj, events, brige); session != nil {
|
||||
if !brige.writeMessage(answer) {
|
||||
return
|
||||
}
|
||||
session.onStart()
|
||||
go sessionEventHandler(session, events, brige)
|
||||
}
|
||||
|
||||
case "answer":
|
||||
session.handleAnswer(obj)
|
||||
|
||||
case "imageLoaded":
|
||||
session.imageManager().imageLoaded(obj, session)
|
||||
|
||||
case "imageError":
|
||||
session.imageManager().imageLoadError(obj, session)
|
||||
|
||||
default:
|
||||
events <- obj
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sessionEventHandler(session Session, events chan DataObject, brige webBrige) {
|
||||
for {
|
||||
data := <-events
|
||||
|
||||
switch command := data.Tag(); command {
|
||||
case "disconnect":
|
||||
session.onDisconnect()
|
||||
return
|
||||
|
||||
case "session-close":
|
||||
session.onFinish()
|
||||
session.App().removeSession(session.ID())
|
||||
brige.close()
|
||||
|
||||
default:
|
||||
session.handleEvent(command, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) startSession(params DataObject, events chan DataObject, brige webBrige) (Session, string) {
|
||||
if app.createContentFunc == nil {
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
session := newSession(app, app.nextSessionID(), "", params)
|
||||
session.setBrige(events, brige)
|
||||
if !session.setContent(app.createContentFunc(session), session) {
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
app.sessions[session.ID()] = session
|
||||
|
||||
answer := allocStringBuilder()
|
||||
defer freeStringBuilder(answer)
|
||||
|
||||
answer.WriteString("sessionID = '")
|
||||
answer.WriteString(strconv.Itoa(session.ID()))
|
||||
answer.WriteString("';\n")
|
||||
session.writeInitScript(answer)
|
||||
answerText := answer.String()
|
||||
|
||||
if ProtocolInDebugLog {
|
||||
DebugLog("Start session:")
|
||||
DebugLog(answerText)
|
||||
}
|
||||
return session, answerText
|
||||
}
|
||||
|
||||
var apps = []*application{}
|
||||
|
||||
// StartApp - create the new application and start it
|
||||
func StartApp(addr string, createContentFunc func(Session) SessionContent, params AppParams) {
|
||||
app := new(application)
|
||||
app.params = params
|
||||
app.sessions = map[int]Session{}
|
||||
app.createContentFunc = createContentFunc
|
||||
apps = append(apps, app)
|
||||
|
||||
redirectAddr := ""
|
||||
if index := strings.IndexRune(addr, ':'); index >= 0 {
|
||||
redirectAddr = addr[:index] + ":80"
|
||||
} else {
|
||||
redirectAddr = addr + ":80"
|
||||
if params.CertFile != "" && params.KeyFile != "" {
|
||||
addr += ":443"
|
||||
} else {
|
||||
addr += ":80"
|
||||
}
|
||||
}
|
||||
|
||||
app.server = &http.Server{Addr: addr}
|
||||
http.Handle("/", app)
|
||||
|
||||
serverRun := func(err error) {
|
||||
if err != nil {
|
||||
if err == http.ErrServerClosed {
|
||||
log.Println(err)
|
||||
} else {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if params.CertFile != "" && params.KeyFile != "" {
|
||||
if params.Redirect80 {
|
||||
redirectTLS := func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "https://"+addr+r.RequestURI, http.StatusMovedPermanently)
|
||||
}
|
||||
|
||||
go func() {
|
||||
serverRun(http.ListenAndServe(redirectAddr, http.HandlerFunc(redirectTLS)))
|
||||
}()
|
||||
}
|
||||
serverRun(app.server.ListenAndServeTLS(params.CertFile, params.KeyFile))
|
||||
} else {
|
||||
serverRun(app.server.ListenAndServe())
|
||||
}
|
||||
}
|
||||
|
||||
func FinishApp() {
|
||||
for _, app := range apps {
|
||||
app.Finish()
|
||||
}
|
||||
apps = []*application{}
|
||||
}
|
||||
|
||||
func OpenBrowser(url string) bool {
|
||||
var err error
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
for _, provider := range []string{"xdg-open", "x-www-browser", "www-browser"} {
|
||||
if _, err = exec.LookPath(provider); err == nil {
|
||||
if exec.Command(provider, url).Start(); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case "windows":
|
||||
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
|
||||
|
||||
case "darwin":
|
||||
err = exec.Command("open", url).Start()
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("unsupported platform")
|
||||
}
|
||||
|
||||
return err != nil
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
//go:build wasm
|
||||
|
||||
package rui
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"strings"
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
//go:embed app_wasm.js
|
||||
var wasmScripts string
|
||||
|
||||
type wasmApp struct {
|
||||
params AppParams
|
||||
createContentFunc func(Session) SessionContent
|
||||
session Session
|
||||
brige webBrige
|
||||
}
|
||||
|
||||
func (app *wasmApp) Finish() {
|
||||
app.session.close()
|
||||
}
|
||||
|
||||
/*
|
||||
func (app *wasmApp) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
if ProtocolInDebugLog {
|
||||
DebugLogF("%s %s", req.Method, req.URL.Path)
|
||||
}
|
||||
|
||||
switch req.Method {
|
||||
case "GET":
|
||||
switch req.URL.Path {
|
||||
case "/":
|
||||
w.WriteHeader(http.StatusOK)
|
||||
io.WriteString(w, app.getStartPage())
|
||||
|
||||
case "/ws":
|
||||
if brige := CreateSocketBrige(w, req); brige != nil {
|
||||
go app.socketReader(brige)
|
||||
}
|
||||
|
||||
default:
|
||||
filename := req.URL.Path[1:]
|
||||
if size := len(filename); size > 0 && filename[size-1] == '/' {
|
||||
filename = filename[:size-1]
|
||||
}
|
||||
|
||||
if !serveResourceFile(filename, w, req) &&
|
||||
!serveDownloadFile(filename, w, req) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func (app *wasmApp) startSession(this js.Value, args []js.Value) interface{} {
|
||||
if app.createContentFunc == nil || len(args) == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
params := ParseDataText(args[0].String())
|
||||
session := newSession(app, 0, "", params)
|
||||
session.setBrige(make(chan DataObject), app.brige)
|
||||
if !session.setContent(app.createContentFunc(session), session) {
|
||||
return nil
|
||||
}
|
||||
|
||||
app.session = session
|
||||
|
||||
answer := allocStringBuilder()
|
||||
defer freeStringBuilder(answer)
|
||||
|
||||
session.writeInitScript(answer)
|
||||
answerText := answer.String()
|
||||
|
||||
if ProtocolInDebugLog {
|
||||
DebugLog("Start session:")
|
||||
DebugLog(answerText)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *wasmApp) removeSession(id int) {
|
||||
}
|
||||
|
||||
// StartApp - create the new wasmApp and start it
|
||||
func StartApp(addr string, createContentFunc func(Session) SessionContent, params AppParams) {
|
||||
app := new(wasmApp)
|
||||
app.params = params
|
||||
app.createContentFunc = createContentFunc
|
||||
|
||||
if createContentFunc == nil {
|
||||
return
|
||||
}
|
||||
|
||||
app.brige = createWasmBrige()
|
||||
js.Global().Set("startSession", js.FuncOf(app.startSession))
|
||||
|
||||
script := defaultScripts + wasmScripts
|
||||
script = strings.ReplaceAll(script, "\\", `\\`)
|
||||
script = strings.ReplaceAll(script, "\n", `\n`)
|
||||
script = strings.ReplaceAll(script, "\t", `\t`)
|
||||
script = strings.ReplaceAll(script, "\"", `\"`)
|
||||
script = strings.ReplaceAll(script, "'", `\'`)
|
||||
|
||||
//window := js.Global().Get("window")
|
||||
//window.Call("execScript", `document.getElementById('ruiscript').text = "`+script+`"`)
|
||||
js.Global().Call("execScript", `document.getElementById('ruiscript').text += "`+script+`"`)
|
||||
|
||||
document := js.Global().Get("document")
|
||||
body := document.Call("querySelector", "body")
|
||||
body.Set("innerHTML", `<div class="ruiRoot" id="ruiRootView"></div>
|
||||
<div class="ruiPopupLayer" id="ruiPopupLayer" style="visibility: hidden;" onclick="clickOutsidePopup(event)"></div>
|
||||
<a id="ruiDownloader" download style="display: none;"></a>`)
|
||||
|
||||
js.Global().Call("execScript", "initSession()")
|
||||
//window.Call("execScript", "initSession()")
|
||||
|
||||
for true {
|
||||
if message, ok := app.brige.readMessage(); ok && app.session != nil {
|
||||
if ProtocolInDebugLog {
|
||||
DebugLog(message)
|
||||
}
|
||||
|
||||
if obj := ParseDataText(message); obj != nil {
|
||||
switch command := obj.Tag(); command {
|
||||
/*
|
||||
case "startSession":
|
||||
answer := ""
|
||||
if session, answer = app.startSession(obj, events, brige); session != nil {
|
||||
if !brige.writeMessage(answer) {
|
||||
return
|
||||
}
|
||||
session.onStart()
|
||||
go sessionEventHandler(session, events, brige)
|
||||
}
|
||||
|
||||
case "reconnect":
|
||||
if sessionText, ok := obj.PropertyValue("session"); ok {
|
||||
if sessionID, err := strconv.Atoi(sessionText); err == nil {
|
||||
if session = app.sessions[sessionID]; session != nil {
|
||||
session.setBrige(events, brige)
|
||||
answer := allocStringBuilder()
|
||||
defer freeStringBuilder(answer)
|
||||
|
||||
session.writeInitScript(answer)
|
||||
if !brige.writeMessage(answer.String()) {
|
||||
return
|
||||
}
|
||||
session.onReconnect()
|
||||
go sessionEventHandler(session, events, brige)
|
||||
return
|
||||
}
|
||||
DebugLogF("Session #%d not exists", sessionID)
|
||||
} else {
|
||||
ErrorLog(`strconv.Atoi(sessionText) error: ` + err.Error())
|
||||
}
|
||||
} else {
|
||||
ErrorLog(`"session" key not found`)
|
||||
}
|
||||
|
||||
answer := ""
|
||||
if session, answer = app.startSession(obj, events, brige); session != nil {
|
||||
if !brige.writeMessage(answer) {
|
||||
return
|
||||
}
|
||||
session.onStart()
|
||||
go sessionEventHandler(session, events, brige)
|
||||
}
|
||||
|
||||
case "disconnect":
|
||||
session.onDisconnect()
|
||||
return
|
||||
|
||||
case "session-close":
|
||||
session.onFinish()
|
||||
session.App().removeSession(session.ID())
|
||||
brige.close()
|
||||
|
||||
*/
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FinishApp() {
|
||||
//app.Finish()
|
||||
}
|
||||
|
||||
func OpenBrowser(url string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
/*
|
||||
func OpenBrowser(url string) bool {
|
||||
var err error
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
for _, provider := range []string{"xdg-open", "x-www-browser", "www-browser"} {
|
||||
if _, err = exec.LookPath(provider); err == nil {
|
||||
if exec.Command(provider, url).Start(); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case "windows":
|
||||
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
|
||||
|
||||
case "darwin":
|
||||
err = exec.Command("open", url).Start()
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("unsupported platform")
|
||||
}
|
||||
|
||||
return err != nil
|
||||
}
|
||||
*/
|
108
app_scripts.js
108
app_scripts.js
|
@ -1,103 +1,6 @@
|
|||
var sessionID = "0"
|
||||
var socket
|
||||
var socketUrl
|
||||
|
||||
var images = new Map();
|
||||
var windowFocus = true
|
||||
|
||||
function sendMessage(message) {
|
||||
if (socket) {
|
||||
socket.send(message)
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
socketUrl = document.location.protocol == "https:" ? "wss://" : "ws://"
|
||||
socketUrl += document.location.hostname
|
||||
var 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() {
|
||||
|
||||
const touch_screen = (('ontouchstart' in document.documentElement) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) ? "1" : "0";
|
||||
var message = "startSession{touch=" + touch_screen
|
||||
|
||||
const style = window.getComputedStyle(document.body);
|
||||
if (style) {
|
||||
var direction = style.getPropertyValue('direction');
|
||||
if (direction) {
|
||||
message += ",direction=" + direction
|
||||
}
|
||||
}
|
||||
|
||||
const lang = window.navigator.language;
|
||||
if (lang) {
|
||||
message += ",language=\"" + lang + "\"";
|
||||
}
|
||||
|
||||
const langs = window.navigator.languages;
|
||||
if (langs) {
|
||||
message += ",languages=\"" + langs + "\"";
|
||||
}
|
||||
|
||||
const userAgent = window.navigator.userAgent
|
||||
if (userAgent) {
|
||||
message += ",user-agent=\"" + userAgent + "\"";
|
||||
}
|
||||
|
||||
const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
if (darkThemeMq.matches) {
|
||||
message += ",dark=1";
|
||||
}
|
||||
|
||||
const pixelRatio = window.devicePixelRatio;
|
||||
if (pixelRatio) {
|
||||
message += ",pixel-ratio=" + pixelRatio;
|
||||
}
|
||||
|
||||
sendMessage( message + "}" );
|
||||
}
|
||||
|
||||
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.onresize = function() {
|
||||
scanElementsSize();
|
||||
}
|
||||
|
@ -111,15 +14,6 @@ window.onblur = function(event) {
|
|||
sendMessage( "session-pause{session=" + sessionID +"}" );
|
||||
}
|
||||
|
||||
window.onfocus = function(event) {
|
||||
windowFocus = true
|
||||
if (!socket) {
|
||||
socketReconnect()
|
||||
} else {
|
||||
sendMessage( "session-resume{session=" + sessionID +"}" );
|
||||
}
|
||||
}
|
||||
|
||||
function getIntAttribute(element, tag) {
|
||||
let value = element.getAttribute(tag);
|
||||
if (value) {
|
||||
|
@ -1188,6 +1082,8 @@ function stackTransitionEndEvent(stackId, propertyName, event) {
|
|||
event.stopPropagation();
|
||||
}
|
||||
|
||||
var images = new Map();
|
||||
|
||||
function loadImage(url) {
|
||||
var img = images.get(url);
|
||||
if (img != undefined) {
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
var socket
|
||||
var socketUrl
|
||||
|
||||
function sendMessage(message) {
|
||||
if (socket) {
|
||||
socket.send(message)
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
socketUrl = document.location.protocol == "https:" ? "wss://" : "ws://"
|
||||
socketUrl += document.location.hostname
|
||||
var 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() {
|
||||
|
||||
const touch_screen = (('ontouchstart' in document.documentElement) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) ? "1" : "0";
|
||||
var message = "startSession{touch=" + touch_screen
|
||||
|
||||
const style = window.getComputedStyle(document.body);
|
||||
if (style) {
|
||||
var direction = style.getPropertyValue('direction');
|
||||
if (direction) {
|
||||
message += ",direction=" + direction
|
||||
}
|
||||
}
|
||||
|
||||
const lang = window.navigator.language;
|
||||
if (lang) {
|
||||
message += ",language=\"" + lang + "\"";
|
||||
}
|
||||
|
||||
const langs = window.navigator.languages;
|
||||
if (langs) {
|
||||
message += ",languages=\"" + langs + "\"";
|
||||
}
|
||||
|
||||
const userAgent = window.navigator.userAgent
|
||||
if (userAgent) {
|
||||
message += ",user-agent=\"" + userAgent + "\"";
|
||||
}
|
||||
|
||||
const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
if (darkThemeMq.matches) {
|
||||
message += ",dark=1";
|
||||
}
|
||||
|
||||
const pixelRatio = window.devicePixelRatio;
|
||||
if (pixelRatio) {
|
||||
message += ",pixel-ratio=" + pixelRatio;
|
||||
}
|
||||
|
||||
sendMessage( message + "}" );
|
||||
}
|
||||
|
||||
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
|
||||
if (!socket) {
|
||||
socketReconnect()
|
||||
} else {
|
||||
sendMessage( "session-resume{session=" + sessionID +"}" );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
|
||||
|
||||
function initSession() {
|
||||
|
||||
const touch_screen = (('ontouchstart' in document.documentElement) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)) ? "1" : "0";
|
||||
var message = "sessionInfo{touch=" + touch_screen
|
||||
|
||||
const style = window.getComputedStyle(document.body);
|
||||
if (style) {
|
||||
var direction = style.getPropertyValue('direction');
|
||||
if (direction) {
|
||||
message += ",direction=" + direction
|
||||
}
|
||||
}
|
||||
|
||||
const lang = window.navigator.language;
|
||||
if (lang) {
|
||||
message += ",language=\"" + lang + "\"";
|
||||
}
|
||||
|
||||
const langs = window.navigator.languages;
|
||||
if (langs) {
|
||||
message += ",languages=\"" + langs + "\"";
|
||||
}
|
||||
|
||||
const userAgent = window.navigator.userAgent
|
||||
if (userAgent) {
|
||||
message += ",user-agent=\"" + userAgent + "\"";
|
||||
}
|
||||
|
||||
const darkThemeMq = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
if (darkThemeMq.matches) {
|
||||
message += ",dark=1";
|
||||
}
|
||||
|
||||
const pixelRatio = window.devicePixelRatio;
|
||||
if (pixelRatio) {
|
||||
message += ",pixel-ratio=" + pixelRatio;
|
||||
}
|
||||
|
||||
startSession( message + "}" );
|
||||
}
|
||||
|
||||
|
||||
window.onfocus = function(event) {
|
||||
windowFocus = true
|
||||
sendMessage( "session-resume{session=" + sessionID +"}" );
|
||||
}
|
389
application.go
389
application.go
|
@ -1,21 +1,8 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:embed app_scripts.js
|
||||
|
@ -30,17 +17,9 @@ var defaultThemeText string
|
|||
// Application - app interface
|
||||
type Application interface {
|
||||
Finish()
|
||||
nextSessionID() int
|
||||
removeSession(id int)
|
||||
}
|
||||
|
||||
type application struct {
|
||||
server *http.Server
|
||||
params AppParams
|
||||
createContentFunc func(Session) SessionContent
|
||||
sessions map[int]Session
|
||||
}
|
||||
|
||||
// AppParams defines parameters of the app
|
||||
type AppParams struct {
|
||||
// Title - title of the app window/tab
|
||||
|
@ -61,28 +40,23 @@ type AppParams struct {
|
|||
Redirect80 bool
|
||||
}
|
||||
|
||||
func (app *application) getStartPage() string {
|
||||
buffer := allocStringBuilder()
|
||||
defer freeStringBuilder(buffer)
|
||||
|
||||
buffer.WriteString(`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
func getStartPage(buffer *strings.Builder, params AppParams, addScripts string) {
|
||||
buffer.WriteString(`<head>
|
||||
<meta charset="utf-8">
|
||||
<title>`)
|
||||
buffer.WriteString(app.params.Title)
|
||||
buffer.WriteString(params.Title)
|
||||
buffer.WriteString("</title>")
|
||||
if app.params.Icon != "" {
|
||||
if params.Icon != "" {
|
||||
buffer.WriteString(`
|
||||
<link rel="icon" href="`)
|
||||
buffer.WriteString(app.params.Icon)
|
||||
buffer.WriteString(params.Icon)
|
||||
buffer.WriteString(`">`)
|
||||
}
|
||||
|
||||
if app.params.TitleColor != 0 {
|
||||
if params.TitleColor != 0 {
|
||||
buffer.WriteString(`
|
||||
<meta name="theme-color" content="`)
|
||||
buffer.WriteString(app.params.TitleColor.cssString())
|
||||
buffer.WriteString(params.TitleColor.cssString())
|
||||
buffer.WriteString(`">`)
|
||||
}
|
||||
|
||||
|
@ -92,356 +66,15 @@ func (app *application) getStartPage() string {
|
|||
<style>`)
|
||||
buffer.WriteString(appStyles)
|
||||
buffer.WriteString(`</style>
|
||||
<script>`)
|
||||
<script>
|
||||
`)
|
||||
buffer.WriteString(defaultScripts)
|
||||
buffer.WriteString(addScripts)
|
||||
buffer.WriteString(`</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="ruiRoot" id="ruiRootView"></div>
|
||||
<div class="ruiPopupLayer" id="ruiPopupLayer" style="visibility: hidden;" onclick="clickOutsidePopup(event)"></div>
|
||||
<a id="ruiDownloader" download style="display: none;"></a>
|
||||
</body>
|
||||
</html>`)
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (app *application) Finish() {
|
||||
for _, session := range app.sessions {
|
||||
session.close()
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := app.server.Shutdown(ctx); err != nil {
|
||||
log.Println(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) nextSessionID() int {
|
||||
n := rand.Intn(0x7FFFFFFE) + 1
|
||||
_, ok := app.sessions[n]
|
||||
for ok {
|
||||
n = rand.Intn(0x7FFFFFFE) + 1
|
||||
_, ok = app.sessions[n]
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (app *application) removeSession(id int) {
|
||||
delete(app.sessions, id)
|
||||
}
|
||||
|
||||
func (app *application) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
|
||||
if ProtocolInDebugLog {
|
||||
DebugLogF("%s %s", req.Method, req.URL.Path)
|
||||
}
|
||||
|
||||
switch req.Method {
|
||||
case "GET":
|
||||
switch req.URL.Path {
|
||||
case "/":
|
||||
w.WriteHeader(http.StatusOK)
|
||||
io.WriteString(w, app.getStartPage())
|
||||
|
||||
case "/ws":
|
||||
if brige := CreateSocketBrige(w, req); brige != nil {
|
||||
go app.socketReader(brige)
|
||||
}
|
||||
|
||||
default:
|
||||
filename := req.URL.Path[1:]
|
||||
if size := len(filename); size > 0 && filename[size-1] == '/' {
|
||||
filename = filename[:size-1]
|
||||
}
|
||||
|
||||
if !serveResourceFile(filename, w, req) &&
|
||||
!serveDownloadFile(filename, w, req) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) socketReader(brige WebBrige) {
|
||||
var session Session
|
||||
events := make(chan DataObject, 1024)
|
||||
|
||||
for {
|
||||
message, ok := brige.ReadMessage()
|
||||
if !ok {
|
||||
events <- NewDataObject("disconnect")
|
||||
return
|
||||
}
|
||||
|
||||
if ProtocolInDebugLog {
|
||||
DebugLog(message)
|
||||
}
|
||||
|
||||
if obj := ParseDataText(message); obj != nil {
|
||||
command := obj.Tag()
|
||||
switch command {
|
||||
case "startSession":
|
||||
answer := ""
|
||||
if session, answer = app.startSession(obj, events, brige); session != nil {
|
||||
if !brige.WriteMessage(answer) {
|
||||
return
|
||||
}
|
||||
session.onStart()
|
||||
go sessionEventHandler(session, events, brige)
|
||||
}
|
||||
|
||||
case "reconnect":
|
||||
if sessionText, ok := obj.PropertyValue("session"); ok {
|
||||
if sessionID, err := strconv.Atoi(sessionText); err == nil {
|
||||
if session = app.sessions[sessionID]; session != nil {
|
||||
session.setBrige(events, brige)
|
||||
answer := allocStringBuilder()
|
||||
defer freeStringBuilder(answer)
|
||||
|
||||
session.writeInitScript(answer)
|
||||
if !brige.WriteMessage(answer.String()) {
|
||||
return
|
||||
}
|
||||
session.onReconnect()
|
||||
go sessionEventHandler(session, events, brige)
|
||||
return
|
||||
}
|
||||
DebugLogF("Session #%d not exists", sessionID)
|
||||
} else {
|
||||
ErrorLog(`strconv.Atoi(sessionText) error: ` + err.Error())
|
||||
}
|
||||
} else {
|
||||
ErrorLog(`"session" key not found`)
|
||||
}
|
||||
|
||||
answer := ""
|
||||
if session, answer = app.startSession(obj, events, brige); session != nil {
|
||||
if !brige.WriteMessage(answer) {
|
||||
return
|
||||
}
|
||||
session.onStart()
|
||||
go sessionEventHandler(session, events, brige)
|
||||
}
|
||||
|
||||
case "answer":
|
||||
session.handleAnswer(obj)
|
||||
|
||||
case "imageLoaded":
|
||||
session.imageManager().imageLoaded(obj, session)
|
||||
|
||||
case "imageError":
|
||||
session.imageManager().imageLoadError(obj, session)
|
||||
|
||||
default:
|
||||
events <- obj
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sessionEventHandler(session Session, events chan DataObject, brige WebBrige) {
|
||||
for {
|
||||
data := <-events
|
||||
|
||||
switch command := data.Tag(); command {
|
||||
case "disconnect":
|
||||
session.onDisconnect()
|
||||
return
|
||||
|
||||
case "session-close":
|
||||
session.onFinish()
|
||||
session.App().removeSession(session.ID())
|
||||
brige.Close()
|
||||
|
||||
case "session-pause":
|
||||
session.onPause()
|
||||
|
||||
case "session-resume":
|
||||
session.onResume()
|
||||
|
||||
case "root-size":
|
||||
session.handleRootSize(data)
|
||||
|
||||
case "resize":
|
||||
session.handleResize(data)
|
||||
|
||||
default:
|
||||
session.handleViewEvent(command, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (app *application) startSession(params DataObject, events chan DataObject, brige WebBrige) (Session, string) {
|
||||
if app.createContentFunc == nil {
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
session := newSession(app, app.nextSessionID(), "", params)
|
||||
session.setBrige(events, brige)
|
||||
if !session.setContent(app.createContentFunc(session), session) {
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
app.sessions[session.ID()] = session
|
||||
|
||||
answer := allocStringBuilder()
|
||||
defer freeStringBuilder(answer)
|
||||
|
||||
answer.WriteString("sessionID = '")
|
||||
answer.WriteString(strconv.Itoa(session.ID()))
|
||||
answer.WriteString("';\n")
|
||||
session.writeInitScript(answer)
|
||||
answerText := answer.String()
|
||||
|
||||
if ProtocolInDebugLog {
|
||||
DebugLog("Start session:")
|
||||
DebugLog(answerText)
|
||||
}
|
||||
return session, answerText
|
||||
}
|
||||
|
||||
var apps = []*application{}
|
||||
|
||||
// StartApp - create the new application and start it
|
||||
func StartApp(addr string, createContentFunc func(Session) SessionContent, params AppParams) {
|
||||
app := new(application)
|
||||
app.params = params
|
||||
app.sessions = map[int]Session{}
|
||||
app.createContentFunc = createContentFunc
|
||||
apps = append(apps, app)
|
||||
|
||||
redirectAddr := ""
|
||||
if index := strings.IndexRune(addr, ':'); index >= 0 {
|
||||
redirectAddr = addr[:index] + ":80"
|
||||
} else {
|
||||
redirectAddr = addr + ":80"
|
||||
if params.CertFile != "" && params.KeyFile != "" {
|
||||
addr += ":443"
|
||||
} else {
|
||||
addr += ":80"
|
||||
}
|
||||
}
|
||||
|
||||
app.server = &http.Server{Addr: addr}
|
||||
http.Handle("/", app)
|
||||
|
||||
serverRun := func(err error) {
|
||||
if err != nil {
|
||||
if err == http.ErrServerClosed {
|
||||
log.Println(err)
|
||||
} else {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if params.CertFile != "" && params.KeyFile != "" {
|
||||
if params.Redirect80 {
|
||||
redirectTLS := func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "https://"+addr+r.RequestURI, http.StatusMovedPermanently)
|
||||
}
|
||||
|
||||
go func() {
|
||||
serverRun(http.ListenAndServe(redirectAddr, http.HandlerFunc(redirectTLS)))
|
||||
}()
|
||||
}
|
||||
serverRun(app.server.ListenAndServeTLS(params.CertFile, params.KeyFile))
|
||||
} else {
|
||||
serverRun(app.server.ListenAndServe())
|
||||
}
|
||||
}
|
||||
|
||||
func FinishApp() {
|
||||
for _, app := range apps {
|
||||
app.Finish()
|
||||
}
|
||||
apps = []*application{}
|
||||
}
|
||||
|
||||
func OpenBrowser(url string) bool {
|
||||
var err error
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
for _, provider := range []string{"xdg-open", "x-www-browser", "www-browser"} {
|
||||
if _, err = exec.LookPath(provider); err == nil {
|
||||
if exec.Command(provider, url).Start(); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case "windows":
|
||||
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
|
||||
|
||||
case "darwin":
|
||||
err = exec.Command("open", url).Start()
|
||||
|
||||
default:
|
||||
err = fmt.Errorf("unsupported platform")
|
||||
}
|
||||
|
||||
return err != nil
|
||||
}
|
||||
|
||||
type downloadFile struct {
|
||||
filename string
|
||||
path string
|
||||
data []byte
|
||||
}
|
||||
|
||||
var currentDownloadId = int(rand.Int31())
|
||||
var downloadFiles = map[string]downloadFile{}
|
||||
|
||||
func (session *sessionData) startDownload(file downloadFile) {
|
||||
currentDownloadId++
|
||||
id := strconv.Itoa(currentDownloadId)
|
||||
downloadFiles[id] = file
|
||||
session.runScript(fmt.Sprintf(`startDowndload("%s", "%s")`, id, file.filename))
|
||||
}
|
||||
|
||||
func serveDownloadFile(id string, w http.ResponseWriter, r *http.Request) bool {
|
||||
if file, ok := downloadFiles[id]; ok {
|
||||
delete(downloadFiles, id)
|
||||
if file.data != nil {
|
||||
http.ServeContent(w, r, file.filename, time.Now(), bytes.NewReader(file.data))
|
||||
return true
|
||||
} else if _, err := os.Stat(file.path); err == nil {
|
||||
http.ServeFile(w, r, file.path)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// DownloadFile starts downloading the file on the client side.
|
||||
func (session *sessionData) DownloadFile(path string) {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
ErrorLog(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
_, filename := filepath.Split(path)
|
||||
session.startDownload(downloadFile{
|
||||
filename: filename,
|
||||
path: path,
|
||||
data: nil,
|
||||
})
|
||||
}
|
||||
|
||||
// DownloadFileData starts downloading the file on the client side. Arguments specify the name of the downloaded file and its contents
|
||||
func (session *sessionData) DownloadFileData(filename string, data []byte) {
|
||||
if data == nil {
|
||||
ErrorLog("Invalid download data. Must be not nil.")
|
||||
return
|
||||
}
|
||||
|
||||
session.startDownload(downloadFile{
|
||||
filename: filename,
|
||||
path: "",
|
||||
data: data,
|
||||
})
|
||||
</body>`)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
package rui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type downloadFile struct {
|
||||
filename string
|
||||
path string
|
||||
data []byte
|
||||
}
|
||||
|
||||
var currentDownloadId = int(rand.Int31())
|
||||
var downloadFiles = map[string]downloadFile{}
|
||||
|
||||
func (session *sessionData) startDownload(file downloadFile) {
|
||||
currentDownloadId++
|
||||
id := strconv.Itoa(currentDownloadId)
|
||||
downloadFiles[id] = file
|
||||
session.runScript(fmt.Sprintf(`startDowndload("%s", "%s")`, id, file.filename))
|
||||
}
|
||||
|
||||
func serveDownloadFile(id string, w http.ResponseWriter, r *http.Request) bool {
|
||||
if file, ok := downloadFiles[id]; ok {
|
||||
delete(downloadFiles, id)
|
||||
if file.data != nil {
|
||||
http.ServeContent(w, r, file.filename, time.Now(), bytes.NewReader(file.data))
|
||||
return true
|
||||
} else if _, err := os.Stat(file.path); err == nil {
|
||||
http.ServeFile(w, r, file.path)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// DownloadFile starts downloading the file on the client side.
|
||||
func (session *sessionData) DownloadFile(path string) {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
ErrorLog(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
_, filename := filepath.Split(path)
|
||||
session.startDownload(downloadFile{
|
||||
filename: filename,
|
||||
path: path,
|
||||
data: nil,
|
||||
})
|
||||
}
|
||||
|
||||
// DownloadFileData starts downloading the file on the client side. Arguments specify the name of the downloaded file and its contents
|
||||
func (session *sessionData) DownloadFileData(filename string, data []byte) {
|
||||
if data == nil {
|
||||
ErrorLog("Invalid download data. Must be not nil.")
|
||||
return
|
||||
}
|
||||
|
||||
session.startDownload(downloadFile{
|
||||
filename: filename,
|
||||
path: "",
|
||||
data: data,
|
||||
})
|
||||
}
|
123
session.go
123
session.go
|
@ -7,6 +7,15 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
type webBrige interface {
|
||||
readMessage() (string, bool)
|
||||
writeMessage(text string) bool
|
||||
runGetterScript(script string) DataObject
|
||||
answerReceived(answer DataObject)
|
||||
close()
|
||||
remoteAddr() string
|
||||
}
|
||||
|
||||
// SessionContent is the interface of a session content
|
||||
type SessionContent interface {
|
||||
CreateRootView(session Session) View
|
||||
|
@ -85,14 +94,14 @@ type Session interface {
|
|||
nextViewID() string
|
||||
styleProperty(styleTag, property string) any
|
||||
|
||||
setBrige(events chan DataObject, brige WebBrige)
|
||||
setBrige(events chan DataObject, brige webBrige)
|
||||
writeInitScript(writer *strings.Builder)
|
||||
runScript(script string)
|
||||
runGetterScript(script string) DataObject //, answer chan DataObject)
|
||||
handleAnswer(data DataObject)
|
||||
handleRootSize(data DataObject)
|
||||
handleResize(data DataObject)
|
||||
handleViewEvent(command string, data DataObject)
|
||||
handleEvent(command string, data DataObject)
|
||||
close()
|
||||
|
||||
onStart()
|
||||
|
@ -137,7 +146,7 @@ type sessionData struct {
|
|||
ignoreUpdates bool
|
||||
popups *popupManager
|
||||
images *imageManager
|
||||
brige WebBrige
|
||||
brige webBrige
|
||||
events chan DataObject
|
||||
animationCounter int
|
||||
animationCSS string
|
||||
|
@ -166,38 +175,8 @@ func newSession(app Application, id int, customTheme string, params DataObject)
|
|||
}
|
||||
}
|
||||
|
||||
if value, ok := params.PropertyValue("touch"); ok {
|
||||
session.touchScreen = (value == "1" || value == "true")
|
||||
}
|
||||
|
||||
if value, ok := params.PropertyValue("user-agent"); ok {
|
||||
session.userAgent = value
|
||||
}
|
||||
|
||||
if value, ok := params.PropertyValue("direction"); ok {
|
||||
if value == "rtl" {
|
||||
session.textDirection = RightToLeftDirection
|
||||
}
|
||||
}
|
||||
|
||||
if value, ok := params.PropertyValue("language"); ok {
|
||||
session.language = value
|
||||
}
|
||||
|
||||
if value, ok := params.PropertyValue("languages"); ok {
|
||||
session.languages = strings.Split(value, ",")
|
||||
}
|
||||
|
||||
if value, ok := params.PropertyValue("dark"); ok {
|
||||
session.darkTheme = (value == "1" || value == "true")
|
||||
}
|
||||
|
||||
if value, ok := params.PropertyValue("pixel-ratio"); ok {
|
||||
if f, err := strconv.ParseFloat(value, 64); err != nil {
|
||||
ErrorLog(err.Error())
|
||||
} else {
|
||||
session.pixelRatio = f
|
||||
}
|
||||
if params != nil {
|
||||
session.handleSessionInfo(params)
|
||||
}
|
||||
|
||||
return session
|
||||
|
@ -211,7 +190,7 @@ func (session *sessionData) ID() int {
|
|||
return session.sessionID
|
||||
}
|
||||
|
||||
func (session *sessionData) setBrige(events chan DataObject, brige WebBrige) {
|
||||
func (session *sessionData) setBrige(events chan DataObject, brige webBrige) {
|
||||
session.events = events
|
||||
session.brige = brige
|
||||
}
|
||||
|
@ -344,7 +323,7 @@ func (session *sessionData) imageManager() *imageManager {
|
|||
|
||||
func (session *sessionData) runScript(script string) {
|
||||
if session.brige != nil {
|
||||
session.brige.WriteMessage(script)
|
||||
session.brige.writeMessage(script)
|
||||
} else {
|
||||
ErrorLog("No connection")
|
||||
}
|
||||
|
@ -352,7 +331,7 @@ func (session *sessionData) runScript(script string) {
|
|||
|
||||
func (session *sessionData) runGetterScript(script string) DataObject { //}, answer chan DataObject) {
|
||||
if session.brige != nil {
|
||||
return session.brige.RunGetterScript(script)
|
||||
return session.brige.runGetterScript(script)
|
||||
}
|
||||
|
||||
ErrorLog("No connection")
|
||||
|
@ -362,7 +341,7 @@ func (session *sessionData) runGetterScript(script string) DataObject { //}, ans
|
|||
}
|
||||
|
||||
func (session *sessionData) handleAnswer(data DataObject) {
|
||||
session.brige.AnswerReceived(data)
|
||||
session.brige.answerReceived(data)
|
||||
}
|
||||
|
||||
func (session *sessionData) handleRootSize(data DataObject) {
|
||||
|
@ -429,13 +408,67 @@ func (session *sessionData) handleResize(data DataObject) {
|
|||
}
|
||||
}
|
||||
|
||||
func (session *sessionData) handleViewEvent(command string, data DataObject) {
|
||||
if viewID, ok := data.PropertyValue("id"); ok {
|
||||
if view := session.viewByHTMLID(viewID); view != nil {
|
||||
view.handleCommand(view, command, data)
|
||||
func (session *sessionData) handleSessionInfo(params DataObject) {
|
||||
if value, ok := params.PropertyValue("touch"); ok {
|
||||
session.touchScreen = (value == "1" || value == "true")
|
||||
}
|
||||
|
||||
if value, ok := params.PropertyValue("user-agent"); ok {
|
||||
session.userAgent = value
|
||||
}
|
||||
|
||||
if value, ok := params.PropertyValue("direction"); ok {
|
||||
if value == "rtl" {
|
||||
session.textDirection = RightToLeftDirection
|
||||
}
|
||||
}
|
||||
|
||||
if value, ok := params.PropertyValue("language"); ok {
|
||||
session.language = value
|
||||
}
|
||||
|
||||
if value, ok := params.PropertyValue("languages"); ok {
|
||||
session.languages = strings.Split(value, ",")
|
||||
}
|
||||
|
||||
if value, ok := params.PropertyValue("dark"); ok {
|
||||
session.darkTheme = (value == "1" || value == "true")
|
||||
}
|
||||
|
||||
if value, ok := params.PropertyValue("pixel-ratio"); ok {
|
||||
if f, err := strconv.ParseFloat(value, 64); err != nil {
|
||||
ErrorLog(err.Error())
|
||||
} else {
|
||||
session.pixelRatio = f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (session *sessionData) handleEvent(command string, data DataObject) {
|
||||
switch command {
|
||||
case "session-pause":
|
||||
session.onPause()
|
||||
|
||||
case "session-resume":
|
||||
session.onResume()
|
||||
|
||||
case "root-size":
|
||||
session.handleRootSize(data)
|
||||
|
||||
case "resize":
|
||||
session.handleResize(data)
|
||||
|
||||
case "sessionInfo":
|
||||
session.handleSessionInfo(data)
|
||||
|
||||
default:
|
||||
if viewID, ok := data.PropertyValue("id"); ok {
|
||||
if view := session.viewByHTMLID(viewID); view != nil {
|
||||
view.handleCommand(view, command, data)
|
||||
}
|
||||
} else if command != "clickOutsidePopup" {
|
||||
ErrorLog(`"id" property not found. Event: ` + command)
|
||||
}
|
||||
} else if command != "clickOutsidePopup" {
|
||||
ErrorLog(`"id" property not found. Event: ` + command)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
//go:build wasm
|
||||
|
||||
package rui
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall/js"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type wasmBrige struct {
|
||||
queue chan string
|
||||
answer map[int]chan DataObject
|
||||
answerID int
|
||||
answerMutex sync.Mutex
|
||||
closed bool
|
||||
}
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 8096,
|
||||
}
|
||||
|
||||
func createWasmBrige() webBrige {
|
||||
brige := new(wasmBrige)
|
||||
brige.queue = make(chan string, 1000)
|
||||
brige.answerID = 1
|
||||
brige.answer = make(map[int]chan DataObject)
|
||||
brige.closed = false
|
||||
|
||||
js.Global().Set("sendMessage", js.FuncOf(brige.sendMessage))
|
||||
|
||||
return brige
|
||||
}
|
||||
|
||||
func (brige *wasmBrige) sendMessage(this js.Value, args []js.Value) interface{} {
|
||||
if len(args) > 0 {
|
||||
brige.queue <- args[0].String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (brige *wasmBrige) close() {
|
||||
}
|
||||
|
||||
func (brige *wasmBrige) readMessage() (string, bool) {
|
||||
return <-brige.queue, true
|
||||
}
|
||||
|
||||
func (brige *wasmBrige) writeMessage(script string) bool {
|
||||
if ProtocolInDebugLog {
|
||||
DebugLog("Run script:")
|
||||
DebugLog(script)
|
||||
}
|
||||
|
||||
window := js.Global().Get("window")
|
||||
window.Call("execScript", script)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (brige *wasmBrige) runGetterScript(script string) DataObject {
|
||||
brige.answerMutex.Lock()
|
||||
answerID := brige.answerID
|
||||
brige.answerID++
|
||||
brige.answerMutex.Unlock()
|
||||
|
||||
answer := make(chan DataObject)
|
||||
brige.answer[answerID] = answer
|
||||
errorText := ""
|
||||
|
||||
js.Global().Set("answerID", strconv.Itoa(answerID))
|
||||
|
||||
window := js.Global().Get("window")
|
||||
window.Call("execScript", script)
|
||||
|
||||
result := NewDataObject("error")
|
||||
result.SetPropertyValue("text", errorText)
|
||||
delete(brige.answer, answerID)
|
||||
return result
|
||||
}
|
||||
|
||||
func (brige *wasmBrige) answerReceived(answer DataObject) {
|
||||
if text, ok := answer.PropertyValue("answerID"); ok {
|
||||
if id, err := strconv.Atoi(text); err == nil {
|
||||
if chanel, ok := brige.answer[id]; ok {
|
||||
chanel <- answer
|
||||
delete(brige.answer, id)
|
||||
} else {
|
||||
ErrorLog("Bad answerID = " + text + " (chan not found)")
|
||||
}
|
||||
} else {
|
||||
ErrorLog("Invalid answerID = " + text)
|
||||
}
|
||||
} else {
|
||||
ErrorLog("answerID not found")
|
||||
}
|
||||
}
|
||||
|
||||
func (brige *wasmBrige) remoteAddr() string {
|
||||
return "localhost"
|
||||
}
|
24
webBrige.go
24
webBrige.go
|
@ -1,3 +1,5 @@
|
|||
//go:build !wasm
|
||||
|
||||
package rui
|
||||
|
||||
import (
|
||||
|
@ -8,15 +10,6 @@ import (
|
|||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
type WebBrige interface {
|
||||
ReadMessage() (string, bool)
|
||||
WriteMessage(text string) bool
|
||||
RunGetterScript(script string) DataObject
|
||||
AnswerReceived(answer DataObject)
|
||||
Close()
|
||||
remoteAddr() string
|
||||
}
|
||||
|
||||
type wsBrige struct {
|
||||
conn *websocket.Conn
|
||||
answer map[int]chan DataObject
|
||||
|
@ -30,7 +23,7 @@ var upgrader = websocket.Upgrader{
|
|||
WriteBufferSize: 8096,
|
||||
}
|
||||
|
||||
func CreateSocketBrige(w http.ResponseWriter, req *http.Request) WebBrige {
|
||||
func CreateSocketBrige(w http.ResponseWriter, req *http.Request) webBrige {
|
||||
conn, err := upgrader.Upgrade(w, req, nil)
|
||||
if err != nil {
|
||||
ErrorLog(err.Error())
|
||||
|
@ -45,13 +38,12 @@ func CreateSocketBrige(w http.ResponseWriter, req *http.Request) WebBrige {
|
|||
return brige
|
||||
}
|
||||
|
||||
func (brige *wsBrige) Close() {
|
||||
func (brige *wsBrige) close() {
|
||||
brige.closed = true
|
||||
brige.conn.Close()
|
||||
}
|
||||
|
||||
func (brige *wsBrige) ReadMessage() (string, bool) {
|
||||
//messageType, p, err := brige.conn.ReadMessage()
|
||||
func (brige *wsBrige) readMessage() (string, bool) {
|
||||
_, p, err := brige.conn.ReadMessage()
|
||||
if err != nil {
|
||||
if !brige.closed {
|
||||
|
@ -63,7 +55,7 @@ func (brige *wsBrige) ReadMessage() (string, bool) {
|
|||
return string(p), true
|
||||
}
|
||||
|
||||
func (brige *wsBrige) WriteMessage(script string) bool {
|
||||
func (brige *wsBrige) writeMessage(script string) bool {
|
||||
if ProtocolInDebugLog {
|
||||
DebugLog("Run script:")
|
||||
DebugLog(script)
|
||||
|
@ -75,7 +67,7 @@ func (brige *wsBrige) WriteMessage(script string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (brige *wsBrige) RunGetterScript(script string) DataObject {
|
||||
func (brige *wsBrige) runGetterScript(script string) DataObject {
|
||||
brige.answerMutex.Lock()
|
||||
answerID := brige.answerID
|
||||
brige.answerID++
|
||||
|
@ -107,7 +99,7 @@ func (brige *wsBrige) RunGetterScript(script string) DataObject {
|
|||
return result
|
||||
}
|
||||
|
||||
func (brige *wsBrige) AnswerReceived(answer DataObject) {
|
||||
func (brige *wsBrige) answerReceived(answer DataObject) {
|
||||
if text, ok := answer.PropertyValue("answerID"); ok {
|
||||
if id, err := strconv.Atoi(text); err == nil {
|
||||
if chanel, ok := brige.answer[id]; ok {
|
||||
|
|
Loading…
Reference in New Issue