5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-02 18:42:23 +08:00
wails/wailsruntimeassets/wails.js
2019-02-19 23:37:02 +11:00

343 lines
7.9 KiB
JavaScript

// Wails runtime JS
(function () {
window.wails = window.wails || {};
window.backend = {};
/****************** Utility Functions ************************/
// -------------- Random --------------
// AwesomeRandom
function cryptoRandom() {
var array = new Uint32Array(1);
return window.crypto.getRandomValues(array)[0];
}
// LOLRandom
function basicRandom() {
return Math.random() * 9007199254740991;
}
// Pick one based on browser capability
var randomFunc;
if (window.crypto) {
randomFunc = cryptoRandom;
} else {
randomFunc = basicRandom;
}
// -------------- Identifiers ---------------
function isValidIdentifier(name) {
// Don't xss yourself :-)
try {
new Function("var " + name);
return true;
} catch (e) {
return false;
}
}
// -------------- JS ----------------
function addScript(js, callbackID) {
var script = document.createElement("script");
script.text = js;
document.body.appendChild(script);
window.wails.events.emit(callbackID);
}
// -------------- CSS ---------------
// Adapted from webview - thanks zserge!
function injectCSS(css) {
var elem = document.createElement('style');
elem.setAttribute('type', 'text/css');
if (elem.styleSheet) {
elem.styleSheet.cssText = css;
} else {
elem.appendChild(document.createTextNode(css));
}
var head = document.head || document.getElementsByTagName('head')[0];
head.appendChild(elem);
}
/************************* Bindings *************************/
var bindingsBasePath = window.backend;
// Creates the path given in the bindings path
function addBindingPath(pathSections) {
// Start at the base path
var currentPath = bindingsBasePath;
// for each section of the given path
for (var sectionIndex in pathSections) {
var section = pathSections[sectionIndex];
// Is section a valid javascript identifier?
if (!isValidIdentifier(section)) {
var errMessage = section + " is not a valid javascript identifier.";
var err = new Error(errMessage);
return [null, err];
}
// Add if doesn't exist
if (!currentPath[section]) {
currentPath[section] = {};
}
// update current path to new path
currentPath = currentPath[section];
}
return [currentPath, null];
}
function newBinding(bindingName) {
// Get all the sections of the binding
var bindingSections = bindingName.split('.').splice(1);
// Get the actual function/method call name
var callName = bindingSections.pop();
var pathToBinding;
var err;
// Add path to binding
var bs = addBindingPath(bindingSections);
var pathToBinding = bs[0];
var err = bs[1];
if (err != null) {
// We need to return an error
return err;
}
// Add binding call
pathToBinding[callName] = function () {
// No timeout by default
var timeout = 0;
// Actual function
function dynamic() {
var args = [].slice.call(arguments);
return call(bindingName, args, timeout);
}
// Allow setting timeout to function
dynamic.setTimeout = function (newTimeout) {
timeout = newTimeout;
}
// Allow getting timeout to function
dynamic.getTimeout = function () {
return timeout;
}
return dynamic;
}();
}
/************************************************************/
/*************************** Calls **************************/
var callbacks = {};
// 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.
function call(bindingName, data, timeout) {
// Timeout infinite by default
if (timeout == null || timeout == undefined) {
timeout = 0;
}
// Create a promise
return new Promise(function (resolve, reject) {
// Create a unique callbackID
var callbackID;
do {
callbackID = bindingName + "-" + randomFunc();
} while (callbacks[callbackID]);
// Set timeout
if (timeout > 0) {
var timeoutHandle = setTimeout(function () {
reject(Error("Call to " + bindingName + " timed out. Request ID: " + callbackID));
}, timeout);
}
// Store callback
callbacks[callbackID] = {
timeoutHandle: timeoutHandle,
reject: reject,
resolve: resolve
}
try {
var payloaddata = JSON.stringify(data);
// Create the message
message = {
type: "call",
callbackid: callbackID,
payload: {
bindingName: bindingName,
data: payloaddata,
}
}
// Make the call
var payload = JSON.stringify(message);
external.invoke(payload);
} catch (e) {
console.error(e);
}
})
}
// Called by the backend to return data to a previously called
// binding invocation
function callback(incomingMessage) {
// Parse the message
var message;
try {
message = JSON.parse(incomingMessage);
} catch (e) {
wails.log.debug("Invalid JSON passed to callback: " + e.message);
wails.log.debug("Message: " + incomingMessage);
return;
}
callbackID = message.callbackid;
callbackData = callbacks[callbackID];
if (!callbackData) {
console.error("Callback '" + callbackID + "' not registed!!!");
return
}
clearTimeout(callbackData.timeoutHandle);
delete callbacks[callbackID];
if (message.error) {
return callbackData.reject(message.error);
}
return callbackData.resolve(message.data);
}
/************************************************************/
/************************** Events **************************/
var eventListeners = {};
// Registers event listeners
function on(eventName, callback) {
eventListeners[eventName] = eventListeners[eventName] || [];
eventListeners[eventName].push(callback);
}
// notify informs frontend listeners that an event was emitted with the given data
function notify(eventName, data) {
if (eventListeners[eventName]) {
eventListeners[eventName].forEach(function(element) {
var parsedData = [];
// Parse data if we have it
if (data) {
try {
parsedData = JSON.parse(data);
} catch (e) {
wails.log.error("Invalid JSON data sent to notify. Event name = " + eventName)
}
}
element.apply(null, parsedData);
});
}
}
// emit an event with the given name and data
function emit(eventName) {
// Calculate the data
var data = JSON.stringify([].slice.apply(arguments).slice(1));
// Notify backend
message = {
type: "event",
payload: {
name: eventName,
data: data,
}
}
external.invoke(JSON.stringify(message));
}
// Events calls
window.wails.events = { emit: emit, on: on };
/************************************************************/
/************************* Logging **************************/
// Sends a log message to the backend with the given
// level + message
function sendLogMessage(level, message) {
// Log Message
message = {
type: "log",
payload: {
level: level,
message: message,
}
}
external.invoke(JSON.stringify(message));
}
function logDebug(message) {
sendLogMessage("debug", message);
}
function logInfo(message) {
sendLogMessage("info", message);
}
function logWarning(message) {
sendLogMessage("warning", message);
}
function logError(message) {
sendLogMessage("error", message);
}
function logFatal(message) {
sendLogMessage("fatal", message);
}
window.wails.log = {
debug: logDebug,
info: logInfo,
warning: logWarning,
error: logError,
fatal: logFatal,
};
/************************** Exports *************************/
window.wails._ = {
newBinding: newBinding,
callback: callback,
notify: notify,
sendLogMessage: sendLogMessage,
callbacks: callbacks,
injectCSS: injectCSS,
addScript: addScript,
}
/************************************************************/
// Notify backend that the runtime has finished loading
window.wails.events.emit("wails:loaded");
})()