feat: significant overhaul of runtime

This commit is contained in:
Lea Anthony
2019-07-20 19:32:30 +10:00
parent d2f114e44e
commit 030e911ea4
58 changed files with 759 additions and 291 deletions

View File

@@ -1,43 +1,38 @@
/*
Wails Bridge (c) 2019-present Lea Anthony
This library creates a bridge between your application
and the frontend, allowing you to develop your app using
standard tooling (browser extensions, live reload, etc).
Usage:
```
import Bridge from "./wailsbridge";
Bridge.Start(startApp);
```
The given callback (startApp in the example) will be called
when the bridge has successfully initialised. It passes the
window.wails object back, in case it is not accessible directly.
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
// Bridge object
window.wailsbridge = {
reconnectOverlay: null,
reconnectTimer: 300,
wsURL: 'ws://localhost:34115/bridge',
connectionState: null,
config: {},
websocket: null,
callback: null,
overlayHTML:
'<div class="wails-reconnect-overlay"><div class="wails-reconnect-overlay-content"><div class="wails-reconnect-overlay-title">Wails Bridge</div><br><div class="wails-reconnect-overlay-loadingspinner"></div><br><div id="wails-reconnect-overlay-message">Waiting for backend</div></div></div>',
overlayCSS:
'.wails-reconnect-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.6);font-family:sans-serif;display:none;z-index:999999}.wails-reconnect-overlay-content{padding:20px 30px;text-align:center;width:20em;position:relative;height:14em;border-radius:1em;margin:5% auto 0;background-color:#fff;box-shadow:1px 1px 20px 3px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC8AAAAuCAMAAACPpbA7AAAAqFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAAAAAAAAEBAQAAAAAAAAAAAAEBAQEBAQDAwMBAQEAAAABAQEAAAAAAAAAAAABAQEAAAAAAAACAgICAgIBAQEAAAAAAAAAAAAAAAAAAAAAAAABAQEAAAACAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBQWKCj6oAAAAN3RSTlMALiIqDhkGBAswJjP0GxP6NR4W9/ztjRDMhWU50G9g5eHXvbZ9XEI9xZTcqZl2aldKo55QwoCvZUgzhAAAAs9JREFUSMeNleeWqjAUhU0BCaH3Itiw9zKT93+zG02QK1hm/5HF+jzZJ6fQe6cyXE+jg9X7o9wxuylIIf4Tv2V3+bOrEXnf8dwQ/KQIGDN2/S+4OmVCVXL/ScBnfibxURqIByP/hONE8r8T+bDMlQ98KSl7Y8hzjpS8v1qtDh8u5f8KQpGpfnPPhqG8JeogN37Hq9eaN2xRhIwAaGnvws8F1ShxqK5ob2twYi1FAMD4rXsYtnC/JEiRbl4cUrCWhnMCLRFemXezXbb59QK4WASOsm6n2W1+4CBT2JmtzQ6fsrbGubR/NFbd2g5Y179+5w/GEHaKsHjYCet7CgrXU3txarNC7YxOVJtIj4/ERzMdZfzc31hp+8cD6eGILgarZY9uZ12hAs03vfBD9C171gS5Omz7OcvxALQIn4u8RRBBBcsi9WW2woO9ipLgfzpYlggg3ZRdROUC8KT7QLqq3W9KB5BbdFVg4929kdwp6+qaZnMCCNBdj+NyN1W885Ry/AL3D4AQbsVV4noCiM/C83kyYq80XlDAYQtralOiDzoRAHlotWl8q2tjvYlOgcg1A8jEApZa+C06TBdAz2Qv0wu11I/zZOyJQ6EwGez2P2b8PIQr1hwwnAZsAxwA4UAYOyXUxM/xp6tHAn4GUmPGM9R28oVxgC0e/zQJJI6DyhyZ1r7uzRQhpcW7x7vTaWSzKSG6aep77kroTEl3U81uSVaUTtgEINfC8epx+Q4F9SpplHG84Ek6m4RAq9/TLkOBrxyeuddZhHvGIp1XXfFy3Z3vtwNblKGiDn+J+92vwwABHghj7HnzlS1H5kB49AZvdGCFgiBPq69qfXPr3y++yilF0ON4R8eR7spAsLpZ95NqAW5tab1c4vkZm6aleajchMwYTdILQQTwE2OV411ZM9WztDjPql12caBi6gDpUKmDd4U1XNdQxZ4LIXQ5/Tr4P7I9tYcFrDK3AAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center}.wails-reconnect-overlay-title{font-size:2em}.wails-reconnect-overlay-message{font-size:1.3em}.wails-reconnect-overlay-loadingspinner{pointer-events:none;width:2.5em;height:2.5em;border:.4em solid transparent;border-color:#3E67EC #eee #eee;border-radius:50%;animation:loadingspin 1s linear infinite;margin:auto;padding:2.5em}@keyframes loadingspin{100%{transform:rotate(360deg)}}',
log: function (message) {
// eslint-disable-next-line
console.log(
'%c wails bridge %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'
);
}
};
function init() {
// Bridge object
window.wailsbridge = {
reconnectOverlay: null,
reconnectTimer: 300,
wsURL: 'ws://localhost:34115/bridge',
connectionState: null,
config: {},
websocket: null,
callback: null,
overlayHTML:
'<div class="wails-reconnect-overlay"><div class="wails-reconnect-overlay-content"><div class="wails-reconnect-overlay-title">Wails Bridge</div><br><div class="wails-reconnect-overlay-loadingspinner"></div><br><div id="wails-reconnect-overlay-message">Waiting for backend</div></div></div>',
overlayCSS:
'.wails-reconnect-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.6);font-family:sans-serif;display:none;z-index:999999}.wails-reconnect-overlay-content{padding:20px 30px;text-align:center;width:20em;position:relative;height:14em;border-radius:1em;margin:5% auto 0;background-color:#fff;box-shadow:1px 1px 20px 3px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC8AAAAuCAMAAACPpbA7AAAAqFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAQAAAAAAAAEBAQAAAAAAAAAAAAEBAQEBAQDAwMBAQEAAAABAQEAAAAAAAAAAAABAQEAAAAAAAACAgICAgIBAQEAAAAAAAAAAAAAAAAAAAAAAAABAQEAAAACAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBQWKCj6oAAAAN3RSTlMALiIqDhkGBAswJjP0GxP6NR4W9/ztjRDMhWU50G9g5eHXvbZ9XEI9xZTcqZl2aldKo55QwoCvZUgzhAAAAs9JREFUSMeNleeWqjAUhU0BCaH3Itiw9zKT93+zG02QK1hm/5HF+jzZJ6fQe6cyXE+jg9X7o9wxuylIIf4Tv2V3+bOrEXnf8dwQ/KQIGDN2/S+4OmVCVXL/ScBnfibxURqIByP/hONE8r8T+bDMlQ98KSl7Y8hzjpS8v1qtDh8u5f8KQpGpfnPPhqG8JeogN37Hq9eaN2xRhIwAaGnvws8F1ShxqK5ob2twYi1FAMD4rXsYtnC/JEiRbl4cUrCWhnMCLRFemXezXbb59QK4WASOsm6n2W1+4CBT2JmtzQ6fsrbGubR/NFbd2g5Y179+5w/GEHaKsHjYCet7CgrXU3txarNC7YxOVJtIj4/ERzMdZfzc31hp+8cD6eGILgarZY9uZ12hAs03vfBD9C171gS5Omz7OcvxALQIn4u8RRBBBcsi9WW2woO9ipLgfzpYlggg3ZRdROUC8KT7QLqq3W9KB5BbdFVg4929kdwp6+qaZnMCCNBdj+NyN1W885Ry/AL3D4AQbsVV4noCiM/C83kyYq80XlDAYQtralOiDzoRAHlotWl8q2tjvYlOgcg1A8jEApZa+C06TBdAz2Qv0wu11I/zZOyJQ6EwGez2P2b8PIQr1hwwnAZsAxwA4UAYOyXUxM/xp6tHAn4GUmPGM9R28oVxgC0e/zQJJI6DyhyZ1r7uzRQhpcW7x7vTaWSzKSG6aep77kroTEl3U81uSVaUTtgEINfC8epx+Q4F9SpplHG84Ek6m4RAq9/TLkOBrxyeuddZhHvGIp1XXfFy3Z3vtwNblKGiDn+J+92vwwABHghj7HnzlS1H5kB49AZvdGCFgiBPq69qfXPr3y++yilF0ON4R8eR7spAsLpZ95NqAW5tab1c4vkZm6aleajchMwYTdILQQTwE2OV411ZM9WztDjPql12caBi6gDpUKmDd4U1XNdQxZ4LIXQ5/Tr4P7I9tYcFrDK3AAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center}.wails-reconnect-overlay-title{font-size:2em}.wails-reconnect-overlay-message{font-size:1.3em}.wails-reconnect-overlay-loadingspinner{pointer-events:none;width:2.5em;height:2.5em;border:.4em solid transparent;border-color:#3E67EC #eee #eee;border-radius:50%;animation:loadingspin 1s linear infinite;margin:auto;padding:2.5em}@keyframes loadingspin{100%{transform:rotate(360deg)}}',
log: function (message) {
// eslint-disable-next-line
console.log(
'%c wails bridge %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'
);
}
};
}
// Adapted from webview - thanks zserge!
function injectCSS(css) {
@@ -203,14 +198,20 @@ function startBridge() {
connect();
}
export default {
// The main function
// Passes the main Wails object to the callback if given.
Start: function (callback) {
// Save the callback
window.wailsbridge.callback = callback;
function start(callback) {
// Start Bridge
startBridge();
}
};
// Set up the bridge
init();
// Save the callback
window.wailsbridge.callback = callback;
// Start Bridge
startBridge();
}
function Init(callback) {
start(callback);
}
module.exports = { Init };

View File

@@ -1,5 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"></head>
<body><div id="app"></div><script type="text/javascript"></script></body>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
</head>
<body>
<div id="app"></div>
<script type="text/javascript">function AddScript(js, callbackID) {
var script = document.createElement('script');
script.text = js;
document.body.appendChild(script);
}</script>
</body>
</html>

1
runtime/assets/wails.js Normal file
View File

@@ -0,0 +1 @@
!function(n){var t={};function e(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return n[r].call(o.exports,o,o.exports,e),o.l=!0,o.exports}e.m=n,e.c=t,e.d=function(n,t,r){e.o(n,t)||Object.defineProperty(n,t,{enumerable:!0,get:r})},e.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},e.t=function(n,t){if(1&t&&(n=e(n)),8&t)return n;if(4&t&&"object"==typeof n&&n&&n.__esModule)return n;var r=Object.create(null);if(e.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:n}),2&t&&"string"!=typeof n)for(var o in n)e.d(r,o,function(t){return n[t]}.bind(null,o));return r},e.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return e.d(t,"a",t),t},e.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},e.p="",e(e.s=0)}([function(n,t,e){"use strict";e.r(t);var r={};e.r(r),e.d(r,"Debug",function(){return c}),e.d(r,"Info",function(){return u}),e.d(r,"Warning",function(){return l}),e.d(r,"Error",function(){return f}),e.d(r,"Fatal",function(){return d});var o={};function a(n,t,e){var r={type:n,callbackID:e,payload:t};!function(n){window.external.invoke(n)}(JSON.stringify(r))}function i(n,t){a("log",{level:n,message:t})}function c(n){i("debug",n)}function u(n){i("info",n)}function l(n){i("warning",n)}function f(n){i("error",n)}function d(n){i("fatal",n)}e.r(o),e.d(o,"OpenURL",function(){return y}),e.d(o,"OpenFile",function(){return g});var s,p={};function v(n,t,e){return null!=e&&null!=e||(e=0),new Promise(function(r,o){var i;do{i=n+"-"+s()}while(p[i]);if(e>0)var c=setTimeout(function(){o(Error("Call to "+n+" timed out. Request ID: "+i))},e);p[i]={timeoutHandle:c,reject:o,resolve:r};try{a("call",{bindingName:n,data:JSON.stringify(t)},i)}catch(n){console.error(n)}})}function w(n,t){return v(".wails."+n,t)}function y(n){return w("Browser.OpenURL",n)}function g(n){return w("Browser.OpenFile",n)}s=window.crypto?function(){var n=new Uint32Array(1);return window.crypto.getRandomValues(n)[0]}:function(){return 9007199254740991*Math.random()};var m=function n(t,e){!function(n,t){if(!(n instanceof t))throw new TypeError("Cannot call a class as a function")}(this,n),e=e||-1,this.Callback=function(n){return t.apply(null,n),-1!==e&&0===(e-=1)}},b={};function h(n,t,e){b[n]=b[n]||[];var r=new m(t,e);b[n].push(r)}function O(n){a("event",{name:n,data:JSON.stringify([].slice.apply(arguments).slice(1))})}var S={};function j(n){try{return new Function("var "+n),!0}catch(n){return!1}}function k(){return(k=Object.assign||function(n){for(var t=1;t<arguments.length;t++){var e=arguments[t];for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r])}return n}).apply(this,arguments)}window.backend={},e.d(t,"Init",function(){return N}),window.wails=window.wails||{},window.backend={};var E={NewBinding:function(n){var t=[].concat(n.split(".").splice(1)),e=window.backend;if(t.length>1)for(var r=0;r<t.length-1;r+=1){var o=t[r];if(!j(o))return new Error("".concat(o," is not a valid javascript identifier."));e[o]={},e=e[o]}var a=t.pop();if(!j(a))return new Error("".concat(a," is not a valid javascript identifier."));e[a]=function(){var t=0;function e(){var e=[].slice.call(arguments);return v(n,e,t)}return e.setTimeout=function(n){t=n},e.getTimeout=function(){return t},e}()},Callback:function(n){var t;n=decodeURIComponent(n.replace(/\s+/g,"").replace(/[0-9a-f]{2}/g,"%$&"));try{t=JSON.parse(n)}catch(t){var e="Invalid JSON passed to callback: ".concat(t.message,". Message: ").concat(n);throw c(e),new Error(e)}var r=t.callbackid,o=p[r];if(!o){var a="Callback '".concat(r,"' not registed!!!");throw console.error(a),new Error(a)}clearTimeout(o.timeoutHandle),delete p[r],t.error?o.reject(t.error):o.resolve(t.data)},Notify:function(n,t){if(b[n]){for(var e=b[n].slice(),r=0;r<b[n].length;r+=1){var o=b[n][r],a=[];if(t)try{a=JSON.parse(t)}catch(t){f("Invalid JSON data sent to notify. Event name = "+n)}o.Callback(a)&&e.splice(r,1)}b[n]=e}},AddScript:function(n,t){var e=document.createElement("script");e.text=n,document.body.appendChild(e),t&&O(t)},InjectCSS:function(n){var t=document.createElement("style");t.setAttribute("type","text/css"),t.styleSheet?t.styleSheet.cssText=n:t.appendChild(document.createTextNode(n)),(document.head||document.getElementsByTagName("head")[0]).appendChild(t)},Init:N},C={Log:r,Browser:o,Events:{On:function(n,t){h(n,t)},Emit:O,Heartbeat:function(n,t,e){var r=null;S[n]=function(){clearInterval(r),e()},r=setInterval(function(){O(n)},t)},Acknowledge:function(n){if(!S[n])throw new f("Cannot acknowledge unknown heartbeat '".concat(n,"'"));S[n]()}},_:E};function N(n){n()}k(window.wails,C),O("wails:loaded")}]);

View File

@@ -1,17 +0,0 @@
/*
Wails Bridge (c) 2019-present Lea Anthony
This prod version is to get around having to rewrite your code
for production. When doing a release build, this file will be used
instead of the full version.
*/
export default {
// The main function
// Passes the main Wails object to the callback if given.
Start: function (callback) {
if (callback) {
window.wails.Events.On('wails:ready', callback);
}
}
};

View File

@@ -7,8 +7,8 @@ type Dialog struct {
renderer interfaces.Renderer
}
// newDialog creates a new Dialog struct
func newDialog(renderer interfaces.Renderer) *Dialog {
// NewDialog creates a new Dialog struct
func NewDialog(renderer interfaces.Renderer) *Dialog {
return &Dialog{
renderer: renderer,
}

View File

@@ -7,7 +7,8 @@ type Events struct {
eventManager interfaces.EventManager
}
func newEvents(eventManager interfaces.EventManager) *Events {
// NewEvents creates a new Events struct
func NewEvents(eventManager interfaces.EventManager) *Events {
return &Events{
eventManager: eventManager,
}

View File

@@ -5,8 +5,8 @@ import homedir "github.com/mitchellh/go-homedir"
// FileSystem exposes file system utilities to the runtime
type FileSystem struct {}
// Creates a new FileSystem struct
func newFileSystem() *FileSystem {
// NewFileSystem creates a new FileSystem struct
func NewFileSystem() *FileSystem {
return &FileSystem{}
}

View File

@@ -1,25 +0,0 @@
package runtime
import "github.com/wailsapp/wails/lib/interfaces"
// Runtime is the Wails Runtime Interface, given to a user who has defined the WailsInit method
type Runtime struct {
Events *Events
Log *Log
Dialog *Dialog
Window *Window
Browser *Browser
FileSystem *FileSystem
}
// NewRuntime creates a new Runtime struct
func NewRuntime(eventManager interfaces.EventManager, renderer interfaces.Renderer) *Runtime {
return &Runtime{
Events: newEvents(eventManager),
Log: newLog(),
Dialog: newDialog(renderer),
Window: newWindow(renderer),
Browser: NewBrowser(),
FileSystem: newFileSystem(),
}
}

View File

@@ -1,7 +1,9 @@
{
"env": {
"browser": true,
"es6": true
"es6": true,
"amd": true,
"node": true,
},
"extends": "eslint:recommended",
"parserOptions": {

View File

@@ -13,7 +13,12 @@ import { Call } from './calls';
window.backend = {};
// Determines if the given identifier is valid Javascript
/**
* Determines if the given identifier is valid Javascript
*
* @param {boolean} name
* @returns
*/
function isValidIdentifier(name) {
// Don't xss yourself :-)
try {
@@ -24,6 +29,13 @@ function isValidIdentifier(name) {
}
}
/**
* NewBinding creates a new binding from the given binding name
*
* @export
* @param {string} bindingName
* @returns
*/
// eslint-disable-next-line max-lines-per-function
export function NewBinding(bindingName) {

View File

@@ -11,10 +11,24 @@ The lightweight framework for web-like apps
import { SystemCall } from './calls';
/**
* Opens the given URL in the system browser
*
* @export
* @param {string} url
* @returns
*/
export function OpenURL(url) {
return SystemCall('Browser.OpenURL', url);
}
/**
* Opens the given filename using the system's default file handler
*
* @export
* @param {sting} filename
* @returns
*/
export function OpenFile(filename) {
return SystemCall('Browser.OpenFile', filename);
}

View File

@@ -14,18 +14,27 @@ import { SendMessage } from './ipc';
var callbacks = {};
// AwesomeRandom
/**
* Returns a number from the native browser random function
*
* @returns number
*/
function cryptoRandom() {
var array = new Uint32Array(1);
return window.crypto.getRandomValues(array)[0];
}
// LOLRandom
/**
* Returns a number using da old-skool Math.Random
* I likes to call it LOLRandom
*
* @returns number
*/
function basicRandom() {
return Math.random() * 9007199254740991;
}
// Pick one based on browser capability
// Pick a random number function based on browser capability
var randomFunc;
if (window.crypto) {
randomFunc = cryptoRandom;
@@ -34,13 +43,22 @@ if (window.crypto) {
}
// Call sends a message to the backend to call the binding with the
// given data. A promise is returned and will be completed when the
// backend responds. This will be resolved when the call was successful
// or rejected if an error is passed back.
// There is a timeout mechanism. If the call doesn't respond in the given
// time (in milliseconds) then the promise is rejected.
/**
* Call sends a message to the backend to call the binding with the
* given data. A promise is returned and will be completed when the
* backend responds. This will be resolved when the call was successful
* or rejected if an error is passed back.
* There is a timeout mechanism. If the call doesn't respond in the given
* time (in milliseconds) then the promise is rejected.
*
* @export
* @param {string} bindingName
* @param {string} data
* @param {number=} timeout
* @returns
*/
export function Call(bindingName, data, timeout) {
// Timeout infinite by default
@@ -87,8 +105,14 @@ export function Call(bindingName, data, timeout) {
}
// Called by the backend to return data to a previously called
// binding invocation
/**
* Called by the backend to return data to a previously called
* binding invocation
*
* @export
* @param {string} incomingMessage
*/
export function Callback(incomingMessage) {
// Decode the message - Credit: https://stackoverflow.com/a/13865680
@@ -115,12 +139,20 @@ export function Callback(incomingMessage) {
delete callbacks[callbackID];
if (message.error) {
return callbackData.reject(message.error);
callbackData.reject(message.error);
} else {
callbackData.resolve(message.data);
}
return callbackData.resolve(message.data);
}
// systemCall is used to call wails methods from the frontend
/**
* SystemCall is used to call wails methods from the frontend
*
* @export
* @param {string} method
* @param {any[]=} data
* @returns
*/
export function SystemCall(method, data) {
return Call('.wails.' + method, data);
}

View File

@@ -13,7 +13,18 @@ import { Error } from './log';
import { SendMessage } from './ipc';
// Defines a single listener with a maximum number of times to callback
/**
* The Listener class defines a listener! :-)
*
* @class Listener
*/
class Listener {
/**
* Creates an instance of Listener.
* @param {function} callback
* @param {number} maxCallbacks
* @memberof Listener
*/
constructor(callback, maxCallbacks) {
// Default of -1 means infinite
maxCallbacks = maxCallbacks || -1;
@@ -34,24 +45,49 @@ class Listener {
var eventListeners = {};
// Registers an event listener that will be invoked `maxCallbacks` times before being destroyed
/**
* Registers an event listener that will be invoked `maxCallbacks` times before being destroyed
*
* @export
* @param {string} eventName
* @param {function} callback
* @param {number} maxCallbacks
*/
export function OnMultiple(eventName, callback, maxCallbacks) {
eventListeners[eventName] = eventListeners[eventName] || [];
const thisListener = new Listener(callback, maxCallbacks);
eventListeners[eventName].push(thisListener);
}
// Registers an event listener that will be invoked every time the event is emitted
/**
* Registers an event listener that will be invoked every time the event is emitted
*
* @export
* @param {string} eventName
* @param {function} callback
*/
export function On(eventName, callback) {
OnMultiple(eventName, callback);
}
// Registers an event listener that will be invoked once then destroyed
/**
* Registers an event listener that will be invoked once then destroyed
*
* @export
* @param {string} eventName
* @param {function} callback
*/
export function Once(eventName, callback) {
OnMultiple(eventName, callback, 1);
}
// Notify informs frontend listeners that an event was emitted with the given data
/**
* Notify informs frontend listeners that an event was emitted with the given data
*
* @export
* @param {string} eventName
* @param {string} data
*/
export function Notify(eventName, data) {
// Check if we have any listeners for this event
@@ -88,7 +124,12 @@ export function Notify(eventName, data) {
}
}
// Emit an event with the given name and data
/**
* Emit an event with the given name and data
*
* @export
* @param {string} eventName
*/
export function Emit(eventName) {
// Calculate the data
@@ -102,10 +143,18 @@ export function Emit(eventName) {
SendMessage('event', payload);
}
// Callbacks for the heartbeat calls
const heartbeatCallbacks = {};
// Heartbeat emits the event `eventName`, every `timeInMilliseconds` milliseconds until
// the event is acknowledged via `Event.Acknowledge`. Once this happens, `callback` is invoked ONCE
/**
* Heartbeat emits the event `eventName`, every `timeInMilliseconds` milliseconds until
* the event is acknowledged via `Event.Acknowledge`. Once this happens, `callback` is invoked ONCE
*
* @export
* @param {string} eventName
* @param {number} timeInMilliseconds
* @param {function} callback
*/
export function Heartbeat(eventName, timeInMilliseconds, callback) {
// Declare interval variable
@@ -128,6 +177,12 @@ export function Heartbeat(eventName, timeInMilliseconds, callback) {
}, timeInMilliseconds);
}
/**
* Acknowledges a heartbeat event by name
*
* @export
* @param {string} eventName
*/
export function Acknowledge(eventName) {
// If we are waiting for acknowledgement for this event type
if (heartbeatCallbacks[eventName]) {

View File

@@ -9,14 +9,23 @@ The lightweight framework for web-like apps
*/
/* jshint esversion: 6 */
/**
* Invoke sends the given message to the backend
*
* @param {string} message
*/
function Invoke(message) {
if (window && window.external && window.external.invoke) {
window.external.invoke(message);
} else {
console.log(`[No external.invoke] ${message}`); // eslint-disable-line
}
window.external.invoke(message);
}
/**
* Sends a message to the backend based on the given type, payload and callbackID
*
* @export
* @param {string} type
* @param {string} payload
* @param {string=} callbackID
*/
export function SendMessage(type, payload, callbackID) {
const message = {
type,

View File

@@ -7,12 +7,17 @@
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
import { SendMessage } from './ipc';
// Sends a log message to the backend with the given
// level + message
/**
* Sends a log message to the backend with the given level + message
*
* @param {string} level
* @param {string} message
*/
function sendLogMessage(level, message) {
// Log Message
@@ -23,22 +28,52 @@ function sendLogMessage(level, message) {
SendMessage('log', payload);
}
/**
* Log the given debug message with the backend
*
* @export
* @param {string} message
*/
export function Debug(message) {
sendLogMessage('debug', message);
}
/**
* Log the given info message with the backend
*
* @export
* @param {string} message
*/
export function Info(message) {
sendLogMessage('info', message);
}
/**
* Log the given warning message with the backend
*
* @export
* @param {string} message
*/
export function Warning(message) {
sendLogMessage('warning', message);
}
/**
* Log the given error message with the backend
*
* @export
* @param {string} message
*/
export function Error(message) {
sendLogMessage('error', message);
}
/**
* Log the given fatal message with the backend
*
* @export
* @param {string} message
*/
export function Fatal(message) {
sendLogMessage('fatal', message);
}

View File

@@ -9,6 +9,7 @@ The lightweight framework for web-like apps
*/
/* jshint esversion: 6 */
import * as Log from './log';
import * as Browser from './browser';
import { On, Emit, Notify, Heartbeat, Acknowledge } from './events';
import { NewBinding } from './bindings';
import { Callback } from './calls';
@@ -24,12 +25,14 @@ var internal = {
Callback,
Notify,
AddScript,
InjectCSS
InjectCSS,
Init,
};
// Setup runtime structure
var runtime = {
Log,
Browser,
Events: {
On,
Emit,
@@ -43,4 +46,9 @@ var runtime = {
Object.assign(window.wails, runtime);
// Emit loaded event
Emit('wails:loaded');
Emit('wails:loaded');
// Nothing to init in production
export function Init(callback) {
callback();
}

View File

@@ -1 +0,0 @@
!function(n){var e={};function t(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return n[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,r){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:r})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var r=Object.create(null);if(t.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(r,o,function(e){return n[e]}.bind(null,o));return r},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){"use strict";t.r(e);var r={};function o(n,e,t){!function(n){window&&window.external&&window.external.invoke?window.external.invoke(n):console.log("[No external.invoke] ".concat(n))}(JSON.stringify({type:n,callbackID:t,payload:e}))}function a(n,e){o("log",{level:n,message:e})}function i(n){a("debug",n)}function c(n){a("info",n)}function u(n){a("warning",n)}function l(n){a("error",n)}function f(n){a("fatal",n)}t.r(r),t.d(r,"Debug",function(){return i}),t.d(r,"Info",function(){return c}),t.d(r,"Warning",function(){return u}),t.d(r,"Error",function(){return l}),t.d(r,"Fatal",function(){return f});var d=function n(e,t){(function(n,e){if(!(n instanceof e))throw new TypeError("Cannot call a class as a function")})(this,n),t=t||-1,this.Callback=function(n){return e.apply(null,n),-1!==t&&0===(t-=1)}},s={};function p(n,e,t){s[n]=s[n]||[];var r=new d(e,t);s[n].push(r)}function v(n){o("event",{name:n,data:JSON.stringify([].slice.apply(arguments).slice(1))})}var w={};var y={};var g=window.crypto?function(){var n=new Uint32Array(1);return window.crypto.getRandomValues(n)[0]}:function(){return 9007199254740991*Math.random()};function m(n,e,t){return(null==t||null==t)&&(t=0),new Promise(function(r,a){var i;do{i=n+"-"+g()}while(y[i]);if(0<t)var c=setTimeout(function(){a(Error("Call to "+n+" timed out. Request ID: "+i))},t);y[i]={timeoutHandle:c,reject:a,resolve:r};try{o("call",{bindingName:n,data:JSON.stringify(e)},i)}catch(n){console.error(n)}})}function b(n){try{return new Function("var "+n),!0}catch(n){return!1}}function h(){return(h=Object.assign||function(n){for(var e,t=1;t<arguments.length;t++)for(var r in e=arguments[t])Object.prototype.hasOwnProperty.call(e,r)&&(n[r]=e[r]);return n}).apply(this,arguments)}window.backend={},window.wails=window.wails||{},window.backend={};var O={NewBinding:function(n){var e=[].concat(n.split(".").splice(1)),t=window.backend;if(1<e.length)for(var r,o=0;o<e.length-1;o+=1){if(!b(r=e[o]))return new Error("".concat(r," is not a valid javascript identifier."));t[r]={},t=t[r]}var a=e.pop();return b(a)?void(t[a]=function(){function e(){var e=[].slice.call(arguments);return m(n,e,t)}var t=0;return e.setTimeout=function(n){t=n},e.getTimeout=function(){return t},e}()):new Error("".concat(a," is not a valid javascript identifier."))},Callback:function(n){var e;n=decodeURIComponent(n.replace(/\s+/g,"").replace(/[0-9a-f]{2}/g,"%$&"));try{e=JSON.parse(n)}catch(e){return i("Invalid JSON passed to callback: "+e.message),void i("Message: "+n)}var t=e.callbackid,r=y[t];return r?(clearTimeout(r.timeoutHandle),delete y[t],e.error?r.reject(e.error):r.resolve(e.data)):void console.error("Callback '".concat(t,"' not registed!!!"))},Notify:function(n,e){if(s[n]){for(var t=s[n].slice(),r=0;r<s[n].length;r+=1){var o=s[n][r],a=[];if(e)try{a=JSON.parse(e)}catch(e){l("Invalid JSON data sent to notify. Event name = "+n)}o.Callback(a)&&t.splice(r,1)}s[n]=t}},AddScript:function(n,e){var t=document.createElement("script");t.text=n,document.body.appendChild(t),e&&v(e)},InjectCSS:function(n){var e=document.createElement("style");e.setAttribute("type","text/css"),e.styleSheet?e.styleSheet.cssText=n:e.appendChild(document.createTextNode(n)),(document.head||document.getElementsByTagName("head")[0]).appendChild(e)}},S={Log:r,Events:{On:function(n,e){p(n,e)},Emit:v,Heartbeat:function(n,e,t){var r=null;w[n]=function(){clearInterval(r),t()},r=setInterval(function(){v(n)},e)},Acknowledge:function(n){if(!w[n])throw new l("Cannot acknowledge unknown heartbeat '".concat(n,"'"));w[n]()}},_:O};h(window.wails,S),v("wails:loaded")}]);

View File

@@ -4,7 +4,8 @@
"description": "The Javascript Wails Runtime",
"main": "index.js",
"scripts": {
"build": "eslint src/ && webpack --config webpack.config.js",
"build": "eslint core/ && npm run build:prod",
"build:prod": "webpack --env prod --colors",
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
@@ -39,4 +40,4 @@
"webpack": "^4.35.3",
"webpack-cli": "^3.3.5"
}
}
}

View File

@@ -0,0 +1 @@
bridge.js

View File

@@ -0,0 +1,3 @@
# Wails Runtime
This module is the Javascript runtime library for the [Wails](https://wails.app) framework. It is intended to be installed as part of a [Wails](https://wails.app) project, not a standalone module.

View File

@@ -0,0 +1,37 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
/**
* Opens the given URL in the system browser
*
* @export
* @param {string} url
* @returns
*/
function OpenURL(url) {
return window.wails.Browser.OpenURL(url);
}
/**
* Opens the given filename using the system's default file handler
*
* @export
* @param {sting} filename
* @returns
*/
function OpenFile(filename) {
return window.wails.Browser.OpenFile(filename);
}
module.exports = {
OpenURL,
OpenFile
};

View File

@@ -0,0 +1,89 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
/**
* Registers an event listener that will be invoked `maxCallbacks` times before being destroyed
*
* @export
* @param {string} eventName
* @param {function} callback
* @param {number} maxCallbacks
*/
function OnMultiple(eventName, callback, maxCallbacks) {
window.wails.Events.OnMultiple(eventName, callback, maxCallbacks);
}
/**
* Registers an event listener that will be invoked every time the event is emitted
*
* @export
* @param {string} eventName
* @param {function} callback
*/
function On(eventName, callback) {
OnMultiple(eventName, callback);
}
/**
* Registers an event listener that will be invoked once then destroyed
*
* @export
* @param {string} eventName
* @param {function} callback
*/
function Once(eventName, callback) {
OnMultiple(eventName, callback, 1);
}
/**
* Emit an event with the given name and data
*
* @export
* @param {string} eventName
*/
function Emit(eventName) {
return window.wails.Events.Emit(eventName);
}
/**
* Heartbeat emits the event `eventName`, every `timeInMilliseconds` milliseconds until
* the event is acknowledged via `Event.Acknowledge`. Once this happens, `callback` is invoked ONCE
*
* @export
* @param {*} eventName
* @param {*} timeInMilliseconds
* @param {*} callback
*/
function Heartbeat(eventName, timeInMilliseconds, callback) {
window.wails.Events.Heartbeat(eventName, timeInMilliseconds, callback);
}
/**
* Acknowledges a heartbeat event by name
*
* @export
* @param {string} eventName
*/
function Acknowledge(eventName) {
return window.wails.Events.Acknowledge(eventName);
}
module.exports = {
OnMultiple,
On,
Once,
Emit,
Heartbeat,
Acknowledge
};

View File

@@ -0,0 +1,23 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
/**
* Initialises the Wails runtime
*
* @param {function} callback
*/
function Init(callback) {
window.wails._.Init(callback);
}
module.exports = {
Init
};

70
runtime/js/runtime/log.js Normal file
View File

@@ -0,0 +1,70 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
/**
* Log the given debug message with the backend
*
* @export
* @param {string} message
*/
function Debug(message) {
window.wails.Log.Debug(message);
}
/**
* Log the given info message with the backend
*
* @export
* @param {string} message
*/
function Info(message) {
window.wails.Log.Info(message);
}
/**
* Log the given warning message with the backend
*
* @export
* @param {string} message
*/
function Warning(message) {
window.wails.Log.Warning(message);
}
/**
* Log the given error message with the backend
*
* @export
* @param {string} message
*/
function Error(message) {
window.wails.Log.Error(message);
}
/**
* Log the given fatal message with the backend
*
* @export
* @param {string} message
*/
function Fatal(message) {
window.wails.Log.Fatal(message);
}
module.exports = {
Debug,
Info,
Warning,
Error,
Fatal
};

View File

@@ -0,0 +1,22 @@
/*
_ __ _ __
| | / /___ _(_) /____
| | /| / / __ `/ / / ___/
| |/ |/ / /_/ / / (__ )
|__/|__/\__,_/_/_/____/
The lightweight framework for web-like apps
(c) Lea Anthony 2019-present
*/
/* jshint esversion: 6 */
const Log = require('./log');
const Browser = require('./browser');
const Events = require('./events');
const Init = require('./init');
module.exports = {
Log,
Browser,
Events,
Init
};

View File

@@ -0,0 +1,24 @@
{
"name": "@wailsapp/runtime",
"version": "1.0.2",
"description": "Wails Javascript runtime library",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/wailsapp/wails.git"
},
"keywords": [
"Wails",
"Javascript",
"Go"
],
"author": "Lea Anthony <lea.anthony@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/wailsapp/wails/issues"
},
"homepage": "https://github.com/wailsapp/wails#readme"
}

View File

@@ -1,38 +1,4 @@
/* eslint-disable */
const path = require('path');
module.exports = {
entry: './src/main',
mode: 'production',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'wails.js'
},
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
plugins: ['@babel/plugin-transform-object-assign'],
presets: [
[
'@babel/preset-env',
{
'useBuiltIns': 'entry',
'corejs': {
'version': 3,
'proposals': true
}
}
], ['minify']
]
}
}
}
]
}
};
module.exports = (env) => {
return require(`./webpack.${env}.js`);
};

View File

@@ -0,0 +1,38 @@
/* eslint-disable */
const path = require('path');
module.exports = {
entry: './core/main',
mode: 'production',
output: {
path: path.resolve(__dirname, '..', 'assets'),
filename: 'wails.js'
},
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
plugins: ['@babel/plugin-transform-object-assign'],
presets: [
[
'@babel/preset-env',
{
'useBuiltIns': 'entry',
'corejs': {
'version': 3,
'proposals': true
}
}
]
]
}
}
}
]
}
};

View File

@@ -5,8 +5,8 @@ import "github.com/wailsapp/wails/lib/logger"
// Log exposes the logging interface to the runtime
type Log struct{}
// newLog creates a new Log struct
func newLog() *Log {
// NewLog creates a new Log struct
func NewLog() *Log {
return &Log{}
}

View File

@@ -7,7 +7,8 @@ type Window struct {
renderer interfaces.Renderer
}
func newWindow(renderer interfaces.Renderer) *Window {
// NewWindow creates a new Window struct
func NewWindow(renderer interfaces.Renderer) *Window {
return &Window{
renderer: renderer,
}