[v3 plugin/server] initial implementation

This commit is contained in:
Travis McLane 2023-08-19 17:10:15 -05:00
commit 2449b473c0
17 changed files with 2395 additions and 0 deletions

View file

@ -0,0 +1,34 @@
# Server Plugin
This plugin provides a simple server for your Wails applications to make them accessible over the local network.
Bidirectional communication occurs over a websocket connection.
## Installation
Add the plugin to the `Plugins` option in the Applications options:
```go
package main
import (
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/plugins/server"
)
func main() {
app := application.New(application.Options{
// ...
Plugins: map[string]application.Plugin{
"server": server.NewPlugin(&server.Config{
Host: "0.0.0.0",
Port: 31115,
}),
},
})
```
## Support
If you find a bug in this plugin, please raise a ticket on the Wails [Issue Tracker](https://github.com/wailsapp/wails/issues).

View file

@ -0,0 +1,5 @@
package server
type Client struct {
Address string
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,16 @@
package server
import "net/http"
type Consumer struct{}
func (c Consumer) Header() http.Header {
return http.Header{}
}
func (c Consumer) Write(data []byte) (int, error) {
return len(data), nil
}
func (c Consumer) WriteHeader(statusCode int) {
}

View file

@ -0,0 +1,54 @@
<script>
import {overlayVisible} from './store'
import {fade,} from 'svelte/transition';
</script>
{#if $overlayVisible }
<div class="wails-reconnect-overlay" transition:fade="{{ duration: 300 }}">
<div class="wails-reconnect-overlay-content">
<div class="wails-reconnect-overlay-loadingspinner"></div>
</div>
</div>
{/if}
<style>
.wails-reconnect-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
backdrop-filter: blur(2px) saturate(0%) contrast(50%) brightness(25%);
z-index: 999999
}
.wails-reconnect-overlay-content {
position: relative;
top: 50%;
transform: translateY(-50%);
margin: 0;
background-image: url(data:image/png;base64,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);
background-repeat: no-repeat;
background-position: center
}
.wails-reconnect-overlay-loadingspinner {
pointer-events: none;
width: 2.5em;
height: 2.5em;
border: .4em solid transparent;
border-color: #f00 #eee0 #f00 #eee0;
border-radius: 50%;
animation: loadingspin 1s linear infinite;
margin: auto;
padding: 2.5em
}
@keyframes loadingspin {
100% {
transform: rotate(360deg);
}
}
</style>

View file

@ -0,0 +1,15 @@
/* jshint esversion: 8 */
const esbuild = require("esbuild");
const sveltePlugin = require("esbuild-svelte");
esbuild
.build({
entryPoints: ["main.js"],
bundle: true,
minify: true,
outfile: "../client.js",
plugins: [sveltePlugin({compileOptions: {css: true}})],
logLevel: "info",
sourcemap: "inline",
})
.catch(() => process.exit(1));

View file

@ -0,0 +1,8 @@
export function log(message) {
// eslint-disable-next-line
console.log(
'%c wails dev %c ' + message + ' ',
'background: #aa0000; color: #fff; border-radius: 3px 0px 0px 3px; padding: 1px; font-size: 0.7rem',
'background: #009900; color: #fff; border-radius: 0px 3px 3px 0px; padding: 1px; font-size: 0.7rem'
);
}

View file

@ -0,0 +1,97 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The electron alternative for Go
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
import {log} from "./log";
import Overlay from "./Overlay.svelte";
import {hideOverlay, showOverlay} from "./store";
import { nanoid } from 'nanoid/non-secure';
let components = {};
let source = null;
function handleCallback(e) {
const payload = JSON.parse(e.data);
_wails.callCallback(payload.id,
payload.result,
true);
}
function handleCallbackError(e) {
const payload = JSON.parse(e.data);
_wails.callErrorCallback(payload.id, payload.result);
}
function handleDialog(e) {
const payload = JSON.parse(e.data);
_wails.dialogCallback(payload.id,
payload.result,
true);
}
function handleDialogError(e) {
const payload = JSON.parse(e.data);
_wails.dialogErrorCallback(payload.id, payload.result);
}
function handleWailsEvent(e) {
console.log("WailsEvent: " + e.data)
}
window.addEventListener('DOMContentLoaded', () => {
components.overlay = new Overlay({
target: document.body,
anchor: document.querySelector('#wails-spinner'),
});
connect();
});
window.onbeforeunload = function () {
if (source) {
source.onclose = function () { };
source.close();
source = null;
}
};
// Handles sse connections
function handleConnect(e) {
hideOverlay();
source.onclose = handleDisconnect;
}
// Handles SSE disconnects
// EventSource will attempt to reconnect on it's own
function handleDisconnect(e) {
if (this.readyState == EventSource.CONNECTING) {
showOverlay();
} else {
console.log(e);
}
}
function _connect() {
if (source == null) {
source = new EventSource("/server/events?clientId="+wails.clientId);
source.onopen = handleConnect;
source.onerror = handleDisconnect;
source.addEventListener('cb', handleCallback);
source.addEventListener('cberror', handleCallbackError);
source.addEventListener('dlgcb', handleDialog);
source.addEventListener('dlgcberror', handleDialogError);
source.addEventListener('wailsevent', handleWailsEvent);
}
}
// Try to connect to the backend every .5s
function connect() {
_connect();
}

1561
v3/plugins/server/ipc/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,19 @@
{
"name": "sse",
"version": "3.0.0",
"description": "Wails Server Plugin SSE",
"main": "main.js",
"scripts": {
"build": "run-p build:*",
"build:dev": "node build.js"
},
"author": "Lea Anthony <lea.anthony@gmail.com>",
"license": "ISC",
"devDependencies": {
"esbuild": "^0.12.17",
"esbuild-svelte": "^0.5.6",
"nanoid": "^4.0.0",
"npm-run-all": "^4.1.5",
"svelte": "^3.55.1"
}
}

View file

@ -0,0 +1,12 @@
import {writable} from 'svelte/store';
/** Overlay */
export const overlayVisible = writable(false);
export function showOverlay() {
overlayVisible.set(true);
}
export function hideOverlay() {
overlayVisible.set(false);
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,63 @@
package server
import (
_ "embed"
"fmt"
"github.com/wailsapp/wails/v3/pkg/application"
)
//go:embed plugin.js
var pluginJS string
//go:embed client.js
var clientJS string
type Config struct {
Host string
Port int
Enabled bool
Headers map[string]string
}
func (c Config) ListenAddress() string {
return fmt.Sprintf("%s:%d", c.Host, c.Port)
}
type Plugin struct {
config *Config
server *Server
}
func NewPlugin(config *Config) *Plugin {
return &Plugin{
config: config,
server: NewServer(config),
}
}
func (s *Plugin) CallableByJS() []string {
return []string{} // maybe # clients?
}
func (p *Plugin) InjectJS() string {
return pluginJS
}
// Init is called when the plugin is loaded. It is passed the application.App
// instance. This is where you should do any setup.
func (p *Plugin) Init() error {
p.server.app = application.Get()
p.server.run()
return nil
}
// Shutdown will stop the server
func (s *Plugin) Shutdown() {
s.server.Shutdown()
}
// Name returns the name of the plugin.
func (s *Plugin) Name() string {
return "github.com/wailsapp/wails/v3/plugins/server"
}

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,9 @@
# This is the plugin definition file for the "server" plugin.
Name = "server"
Description = "A Simple Server for Wails Applications"
Author = "Travis McLane <tmclane@gmail.com>"
Version = "v1.0.0"
Website = "https://wails.io"
Repository = "https://github.com/wailsapp/wails/v3/plugins/server"
License = "MIT"

217
v3/plugins/server/server.go Normal file
View file

@ -0,0 +1,217 @@
package server
import (
"context"
_ "embed"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"sync"
"time"
"github.com/wailsapp/wails/v3/pkg/application"
)
type message struct {
Type string
Data string
}
type callback struct {
ID string `json:"id"`
Result string `json:"result"`
}
type client struct {
address string
events chan message
}
func (c client) close() {
if _, ok := (<-c.events); ok {
close(c.events)
}
}
func (c client) Identifier() string {
return c.address
}
func (c *client) Send(msg message) error {
var err error
defer func() {
if e := recover(); e != nil {
err = fmt.Errorf("connection lost")
}
}()
c.events <- msg
return err
}
type Server struct {
id uint // to allow for registration as a window
app *application.App
config *Config
srv *http.Server
window Window
clients map[string]client
clientLock sync.Mutex
}
func NewServer(config *Config) *Server {
s := &Server{
config: config,
clients: map[string]client{},
}
s.window.server = s
return s
}
func (s *Server) Info(msg string) {
// s.app.Log(&logger.Message{
// Level: "INFO",
// Message: fmt.Sprintf("[plugin/server]: %v", msg),
// })
}
func (s *Server) Shutdown() {
if err := s.srv.Shutdown(context.TODO()); err != nil {
panic(err) // failure/timeout shutting down the server gracefully
}
}
func (s *Server) handleClient(rw http.ResponseWriter, req *http.Request) {
client := client{
events: make(chan message, 5),
address: req.RemoteAddr,
}
s.Info(fmt.Sprintf("client %v connected", client.Identifier()))
clientID := req.URL.Query().Get("clientId")
if clientID != "" {
// we only save if we have an identifier
s.clientLock.Lock()
s.clients[clientID] = client
s.clientLock.Unlock()
}
rw.Header().Set("Content-Type", "text/event-stream")
rw.Header().Set("Cache-Control", "no-cache")
rw.Header().Set("Connection", "keep-alive")
rw.Header().Set("Access-Control-Allow-Origin", "*")
rw.Header().Set("Access-Control-Allow-Headers", "Content-Type")
for header, value := range s.config.Headers {
rw.Header().Set(header, value)
}
flusher, ok := rw.(http.Flusher)
if !ok {
http.Error(rw, "Connection does not support streaming", http.StatusBadRequest)
return
}
for {
timeout := time.After(500 * time.Millisecond)
select {
case <-req.Context().Done():
client.close()
s.removeClient(client.Identifier())
return
case msg := <-client.events:
fmt.Fprintf(rw, "event: %s\n", msg.Type)
fmt.Fprintf(rw, "data: %v\n\n", msg.Data)
case <-timeout:
continue
}
flusher.Flush()
}
}
func (s *Server) serveIPC(rw http.ResponseWriter, req *http.Request) {
rw.Header().Set("Content-Type", "application/javascript")
rw.Header().Set("Content-Length", fmt.Sprintf("%d", len(clientJS)))
io.WriteString(rw, clientJS)
}
func (s *Server) removeClient(clientID string) {
s.clientLock.Lock()
defer s.clientLock.Unlock()
delete(s.clients, clientID)
s.Info(fmt.Sprintf("client %v disconnected", clientID))
}
func (s *Server) sendToClient(requestID string, message message) {
client, ok := s.clients[requestID]
if !ok {
return
}
if err := client.Send(message); err != nil {
s.removeClient(client.Identifier())
}
}
func (s *Server) sendToAllClients(msg message) {
if len(s.clients) == 0 {
return
}
s.clientLock.Lock()
defer s.clientLock.Unlock()
dead := []client{}
for _, client := range s.clients {
if err := client.Send(msg); err != nil {
dead = append(dead, client)
}
}
for _, d := range dead {
s.removeClient(d.Identifier())
}
}
func updateCallID(windowID uint, req *http.Request) *http.Request {
argMap := map[string]any{}
values := req.URL.Query()
args := values.Get("args")
if args != "" {
json.Unmarshal([]byte(args), &argMap)
}
callID := argMap["call-id"]
clientID := req.Header.Get("x-wails-client-id")
if clientID != "" {
argMap["call-id"] = fmt.Sprintf("%s|%s", clientID, callID)
}
newArgs, _ := json.Marshal(argMap)
values.Set("args", string(newArgs))
req.Header.Add("x-wails-window-id", fmt.Sprintf("%d", windowID))
req.URL.RawQuery = values.Encode()
return req
}
func (s *Server) handleHTTP(rw http.ResponseWriter, req *http.Request) {
req = updateCallID(s.window.id, req)
s.app.AssetServerHandler()(
rw,
req,
)
}
func (s *Server) run() {
if s.srv != nil || s.config.Enabled == false {
return
}
address := s.config.ListenAddress()
s.srv = &http.Server{Addr: address}
http.HandleFunc("/wails/ipc.js", s.serveIPC)
http.HandleFunc("/server/events", s.handleClient)
http.HandleFunc("/", s.handleHTTP)
s.window.id = s.app.RegisterWindow(s.window)
go s.serve()
}
// ---------------- Plugin Methods ----------------
func (s *Server) serve() {
s.Info(fmt.Sprintf("listening %s", s.config.ListenAddress()))
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%d", s.config.Host, s.config.Port), nil))
}

268
v3/plugins/server/window.go Normal file
View file

@ -0,0 +1,268 @@
package server
import (
"encoding/json"
"fmt"
"log"
"strings"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/events"
)
type Window struct {
id uint
server *Server
}
// formatJS ensures the 'data' provided marshals to valid json or panics
func (w Window) formatJS(f string, callID string, data string) string {
j, err := json.Marshal(data)
if err != nil {
panic(err)
}
return fmt.Sprintf(f, callID, j)
}
func (w Window) AbsolutePosition() (x, y int) {
return 0, 0
}
func (w Window) CallError(callID string, result string) {
w.ExecJS(callID, w.formatJS("_wails.callErrorCallback('%s', %s);", callID, result))
}
func (w Window) CallResponse(callID string, result string) {
ids := strings.Split(callID, "|")
j, err := json.Marshal(callback{
ID: ids[1],
Result: result,
})
if err != nil {
fmt.Println("Failed to build CallResponse data", result)
}
w.server.sendToClient(ids[0], message{Type: "cb", Data: string(j)})
}
func (w Window) DialogError(dialogID string, result string) {
w.ExecJS(dialogID, w.formatJS("_wails.dialogErrorCallback('%s', %s);", dialogID, result))
}
func (w Window) DialogResponse(dialogID string, result string) {
w.ExecJS(dialogID, w.formatJS("_wails.dialogCallback('%s', %s, true);", dialogID, result))
}
func (w Window) ID() uint {
return w.id
}
func (w Window) Center() {
}
func (w Window) Close() {}
func (w Window) Destroy() {}
func (w Window) ExecJS(callID, js string) {
w.server.sendToClient(callID, message{
Type: "javascript",
Data: js,
})
}
func (w Window) Focus() {}
func (w Window) ForceReload() {}
func (w Window) Fullscreen() application.Window {
return w
}
func (w Window) GetScreen() (*application.Screen, error) {
return nil, fmt.Errorf("can't return screen for external window")
}
func (w Window) GetZoom() float64 {
return 1.0
}
func (w Window) Height() int {
return 0
}
func (w Window) Hide() application.Window {
return w
}
func (w Window) IsFullscreen() bool {
return false
}
func (w Window) IsMaximised() bool {
return false
}
func (w Window) IsMinimised() bool {
return false
}
func (w Window) Maximise() application.Window {
return w
}
func (w Window) Minimise() application.Window {
return w
}
func (w Window) Minimize() {}
func (w Window) Name() string {
return "external window"
}
func (w Window) On(eventType events.WindowEventType, callback func(ctx *application.WindowEvent)) func() {
return func() {
fmt.Printf("server.Window.On(%v)\n", eventType)
}
}
func (w Window) Position() (int, int) {
return 0, 0
}
func (w Window) RegisterContextMenu(name string, menu *application.Menu) {}
func (w Window) RelativePosition() (x, y int) {
return 0, 0
}
func (w Window) Reload() {}
func (w Window) Resizable() bool {
return true
}
func (w Window) Restore() {}
func (w Window) SetAbsolutePosition(x, y int) {}
func (w Window) SetAlwaysOnTop(b bool) application.Window {
return w
}
func (w Window) SetBackgroundColour(colour application.RGBA) application.Window {
return w
}
func (w Window) SetFrameless(frameless bool) application.Window {
return w
}
func (w Window) SetFullscreenButtonEnabled(enabled bool) application.Window {
return w
}
func (w Window) SetHTML(html string) application.Window {
return w
}
func (w Window) SetMaxSize(maxWidth, maxHeight int) application.Window {
return w
}
func (w Window) SetMinSize(minWidth, minHeight int) application.Window {
return w
}
func (w Window) SetRelativePosition(x, y int) application.Window {
return w
}
func (w Window) SetResizable(b bool) application.Window {
return w
}
func (w Window) SetSize(width, height int) application.Window {
return w
}
func (w Window) SetTitle(title string) application.Window {
return w
}
func (w Window) SetURL(s string) application.Window {
return w
}
func (w Window) SetZoom(magnification float64) application.Window {
return w
}
func (w Window) Show() application.Window {
return w
}
func (w Window) Size() (width int, height int) {
return 0, 0
}
func (w Window) ToggleDevTools() {
}
func (w Window) ToggleFullscreen() {}
func (w Window) UnFullscreen() {}
func (w Window) UnMaximise() {}
func (w Window) UnMinimise() {}
func (w Window) Width() int {
return 0
}
func (w Window) Zoom() {}
func (w Window) ZoomIn() {}
func (w Window) ZoomOut() {}
func (w Window) ZoomReset() application.Window {
return w
}
func (w Window) DisableSizeConstraints() {}
func (w Window) DispatchWailsEvent(event *application.WailsEvent) {
w.server.sendToAllClients(
message{
Type: "wailsevent",
Data: event.ToJSON(),
})
}
func (w Window) EnableSizeConstraints() {}
func (w Window) Error(message string, args ...any) {}
func (w Window) HandleDragAndDropMessage(filenames []string) {
}
func (w Window) HandleKeyEvent(acceleratorString string) {
}
func (w Window) HandleMessage(message string) {
log.Println("HandleMessage", message)
}
func (w Window) HandleWindowEvent(id uint) {}
func (w Window) Info(message string, args ...any) {}
func (w Window) OpenContextMenu(data *application.ContextMenuData) {}
func (w Window) Run() {}