Files

301 lines
7.5 KiB
Go
Raw Permalink Normal View History

2021-08-26 21:06:54 +10:00
//go:build dev
2022-01-31 21:21:48 +11:00
// +build dev
2021-08-26 21:06:54 +10:00
2021-09-07 18:37:47 +10:00
// Package devserver provides a web-based frontend so that
// it is possible to run a Wails app in a browsers.
2021-08-18 22:13:45 +10:00
package devserver
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"sync"
2023-01-08 21:25:21 +11:00
"github.com/wailsapp/wails/v2/pkg/assetserver"
2022-12-31 10:36:07 +11:00
"github.com/wailsapp/wails/v2/internal/frontend/runtime"
"github.com/labstack/echo/v4"
2021-08-18 22:13:45 +10:00
"github.com/wailsapp/wails/v2/internal/binding"
"github.com/wailsapp/wails/v2/internal/frontend"
"github.com/wailsapp/wails/v2/internal/logger"
"github.com/wailsapp/wails/v2/internal/menumanager"
2021-08-18 22:13:45 +10:00
"github.com/wailsapp/wails/v2/pkg/options"
"golang.org/x/net/websocket"
2021-08-18 22:13:45 +10:00
)
2022-07-16 05:33:37 +03:00
type Screen = frontend.Screen
2021-09-07 18:37:47 +10:00
type DevWebServer struct {
server *echo.Echo
ctx context.Context
appoptions *options.App
logger *logger.Logger
appBindings *binding.Bindings
dispatcher frontend.Dispatcher
socketMutex sync.Mutex
2021-10-11 21:10:55 +08:00
websocketClients map[*websocket.Conn]*sync.Mutex
menuManager *menumanager.Manager
starttime string
// Desktop frontend
2022-10-05 08:44:23 +11:00
frontend.Frontend
2021-08-18 22:13:45 +10:00
devServerAddr string
}
2021-09-07 18:37:47 +10:00
func (d *DevWebServer) Run(ctx context.Context) error {
d.ctx = ctx
2021-08-18 22:13:45 +10:00
d.server.GET("/wails/reload", d.handleReload)
d.server.GET("/wails/ipc", d.handleIPCWebSocket)
2021-12-06 08:49:12 +01:00
assetServerConfig, err := assetserver.BuildAssetServerConfig(d.appoptions)
if err != nil {
return err
}
var myLogger assetserver.Logger
if _logger := ctx.Value("logger"); _logger != nil {
myLogger = _logger.(*logger.Logger)
}
var wsHandler http.Handler
2022-12-31 10:36:07 +11:00
_fronendDevServerURL, _ := ctx.Value("frontenddevserverurl").(string)
if _fronendDevServerURL == "" {
assetdir, _ := ctx.Value("assetdir").(string)
d.server.GET("/wails/assetdir", func(c echo.Context) error {
return c.String(http.StatusOK, assetdir)
})
} else {
externalURL, err := url.Parse(_fronendDevServerURL)
if err != nil {
return err
}
// WebSockets aren't currently supported in prod mode, so a WebSocket connection is the result of the
// FrontendDevServer e.g. Vite to support auto reloads.
// Therefore we direct WebSockets directly to the FrontendDevServer instead of returning a NotImplementedStatus.
wsHandler = httputil.NewSingleHostReverseProxy(externalURL)
}
assetHandler, err := assetserver.NewAssetHandler(assetServerConfig, myLogger)
if err != nil {
log.Fatal(err)
}
// Setup internal dev server
bindingsJSON, err := d.appBindings.ToJSON()
if err != nil {
log.Fatal(err)
}
assetServer, err := assetserver.NewDevAssetServer(assetHandler, bindingsJSON, ctx.Value("assetdir") != nil, myLogger, runtime.RuntimeAssetsBundle)
if err != nil {
log.Fatal(err)
}
2022-04-12 12:18:27 +02:00
d.server.Any("/*", func(c echo.Context) error {
if c.IsWebSocket() {
wsHandler.ServeHTTP(c.Response(), c.Request())
} else {
assetServer.ServeHTTP(c.Response(), c.Request())
}
return nil
})
if devServerAddr := d.devServerAddr; devServerAddr != "" {
2021-10-03 15:07:10 +11:00
// Start server
go func(server *echo.Echo, log *logger.Logger) {
err := server.Start(devServerAddr)
2021-10-03 15:07:10 +11:00
if err != nil {
log.Error(err.Error())
}
d.LogDebug("Shutdown completed")
}(d.server, d.logger)
d.LogDebug("Serving DevServer at http://%s", devServerAddr)
2021-08-18 22:13:45 +10:00
}
// Launch desktop app
2022-10-05 08:44:23 +11:00
err = d.Frontend.Run(ctx)
return err
}
func (d *DevWebServer) WindowReload() {
d.broadcast("reload")
2022-10-05 08:44:23 +11:00
d.Frontend.WindowReload()
}
2022-04-21 07:40:56 +10:00
func (d *DevWebServer) WindowReloadApp() {
d.broadcast("reloadapp")
2022-10-05 08:44:23 +11:00
d.Frontend.WindowReloadApp()
}
2021-09-07 18:37:47 +10:00
func (d *DevWebServer) Notify(name string, data ...interface{}) {
d.notify(name, data...)
}
func (d *DevWebServer) handleReload(c echo.Context) error {
d.WindowReload()
return c.NoContent(http.StatusNoContent)
}
2022-10-05 08:44:23 +11:00
2022-04-21 07:40:56 +10:00
func (d *DevWebServer) handleReloadApp(c echo.Context) error {
d.WindowReloadApp()
return c.NoContent(http.StatusNoContent)
}
func (d *DevWebServer) handleIPCWebSocket(c echo.Context) error {
websocket.Handler(func(c *websocket.Conn) {
d.LogDebug(fmt.Sprintf("Websocket client %p connected", c))
d.socketMutex.Lock()
d.websocketClients[c] = &sync.Mutex{}
locker := d.websocketClients[c]
d.socketMutex.Unlock()
defer func() {
d.socketMutex.Lock()
delete(d.websocketClients, c)
d.socketMutex.Unlock()
d.LogDebug(fmt.Sprintf("Websocket client %p disconnected", c))
}()
var msg string
defer c.Close()
for {
if err := websocket.Message.Receive(c, &msg); err != nil {
break
}
// We do not support drag in browsers
if msg == "drag" {
continue
}
// Notify the other browsers of "EventEmit"
if len(msg) > 2 && strings.HasPrefix(string(msg), "EE") {
d.notifyExcludingSender([]byte(msg), c)
}
// Send the message to dispatch to the frontend
result, err := d.dispatcher.ProcessMessage(string(msg), d)
if err != nil {
d.logger.Error(err.Error())
}
if result != "" {
locker.Lock()
if err = websocket.Message.Send(c, result); err != nil {
locker.Unlock()
break
}
locker.Unlock()
}
}
}).ServeHTTP(c.Response(), c.Request())
2021-08-18 22:13:45 +10:00
return nil
}
2021-09-25 11:34:54 +10:00
func (d *DevWebServer) LogDebug(message string, args ...interface{}) {
d.logger.Debug("[DevWebServer] "+message, args...)
2021-08-18 22:13:45 +10:00
}
type EventNotify struct {
Name string `json:"name"`
Data []interface{} `json:"data"`
}
2021-09-07 18:37:47 +10:00
func (d *DevWebServer) broadcast(message string) {
d.socketMutex.Lock()
defer d.socketMutex.Unlock()
2021-10-11 21:10:55 +08:00
for client, locker := range d.websocketClients {
2022-02-19 20:29:55 +11:00
go func(client *websocket.Conn, locker *sync.Mutex) {
2021-10-11 21:10:55 +08:00
if client == nil {
d.logger.Error("Lost connection to websocket server")
return
}
locker.Lock()
err := websocket.Message.Send(client, message)
2021-10-11 21:10:55 +08:00
if err != nil {
locker.Unlock()
d.logger.Error(err.Error())
return
}
locker.Unlock()
2022-02-19 20:29:55 +11:00
}(client, locker)
}
}
2021-09-07 18:37:47 +10:00
func (d *DevWebServer) notify(name string, data ...interface{}) {
// Notify
notification := EventNotify{
Name: name,
Data: data,
}
payload, err := json.Marshal(notification)
if err != nil {
d.logger.Error(err.Error())
return
}
d.broadcast("n" + string(payload))
}
2021-09-07 18:37:47 +10:00
func (d *DevWebServer) broadcastExcludingSender(message string, sender *websocket.Conn) {
d.socketMutex.Lock()
defer d.socketMutex.Unlock()
2021-10-11 21:10:55 +08:00
for client, locker := range d.websocketClients {
2022-02-19 20:29:55 +11:00
go func(client *websocket.Conn, locker *sync.Mutex) {
2021-10-11 21:10:55 +08:00
if client == sender {
return
}
locker.Lock()
err := websocket.Message.Send(client, message)
2021-10-11 21:10:55 +08:00
if err != nil {
locker.Unlock()
d.logger.Error(err.Error())
return
}
locker.Unlock()
2022-02-19 20:29:55 +11:00
}(client, locker)
}
}
2021-09-07 18:37:47 +10:00
func (d *DevWebServer) notifyExcludingSender(eventMessage []byte, sender *websocket.Conn) {
message := "n" + string(eventMessage[2:])
d.broadcastExcludingSender(message, sender)
var notifyMessage EventNotify
err := json.Unmarshal(eventMessage[2:], &notifyMessage)
if err != nil {
d.logger.Error(err.Error())
return
}
2022-10-05 08:44:23 +11:00
d.Frontend.Notify(notifyMessage.Name, notifyMessage.Data...)
}
2021-09-07 18:37:47 +10:00
func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher, menuManager *menumanager.Manager, desktopFrontend frontend.Frontend) *DevWebServer {
result := &DevWebServer{
ctx: ctx,
2022-10-05 08:44:23 +11:00
Frontend: desktopFrontend,
appoptions: appoptions,
logger: myLogger,
appBindings: appBindings,
dispatcher: dispatcher,
server: echo.New(),
menuManager: menuManager,
2021-10-11 21:10:55 +08:00
websocketClients: make(map[*websocket.Conn]*sync.Mutex),
}
result.devServerAddr, _ = ctx.Value("devserver").(string)
result.server.HideBanner = true
result.server.HidePort = true
2021-08-18 22:13:45 +10:00
return result
}