Ver código fonte

event API testing (#132)

* feat(examples): quasar + API [wip]

* fix(tauri) event system on quasar example

* feat(template): esm & js versions of tauri.js

* feat(tauri:event): change FnOnce to FnMut

* feat(tauri:api): rename to Listen

* chore(version:tauri): bump

* feat(Cargo.lock): update

* feat(eslint): add lodash-template

* chore(version:tauri.js): bump

* feat(examples:quasar): new Rust signature

* feat(tauri:event): don't remove the listener

* feat(examples:quasar): version and tauri signature

* Update tauri.conf.js

* Update event.rs
nothingismagick 5 anos atrás
pai
commit
22b07fc44a

+ 1 - 1
Cargo.lock

@@ -1925,7 +1925,7 @@ dependencies = [
 
 [[package]]
 name = "tauri"
-version = "0.1.2"
+version = "0.1.3"
 dependencies = [
  "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",

+ 4 - 1
cli/tauri.js/.eslintrc.js

@@ -4,7 +4,10 @@ module.exports = {
     jest: true
   },
 
-  extends: ["standard"],
+  extends: [
+    "standard",
+    "plugin:lodash-template/recommended"
+  ],
 
   plugins: [],
 

+ 2 - 1
cli/tauri.js/package.json

@@ -1,6 +1,6 @@
 {
   "name": "tauri",
-  "version": "0.1.3",
+  "version": "0.1.4",
   "description": "Multi-binding collection of libraries and templates for building Tauri apps",
   "bin": {
     "tauri": "./bin/tauri.js"
@@ -63,6 +63,7 @@
     "eslint": "6.7.2",
     "eslint-config-standard": "14.1.0",
     "eslint-plugin-import": "2.18.2",
+    "eslint-plugin-lodash-template": "0.15.0",
     "eslint-plugin-node": "10.0.0",
     "eslint-plugin-promise": "4.2.1",
     "eslint-plugin-standard": "4.0.1",

+ 363 - 0
cli/tauri.js/templates/tauri.esm.js

@@ -0,0 +1,363 @@
+/* eslint-disable */
+
+/**
+ *  * THIS FILE IS GENERATED AUTOMATICALLY.
+ * DO NOT EDIT.
+ *
+ * Please whitelist these API functions in tauri.conf.js
+ *
+ **/
+
+// open <a href="..."> links with the Tauri API
+
+/**
+ * @module tauri
+ * @description This API interface makes powerful interactions available
+ * to be run on client side applications. They are opt-in features, and
+ * must be enabled in tauri.conf.js
+ *
+ * Each binding MUST provide these interfaces in order to be compliant,
+ * and also whitelist them based upon the developer's settings.
+ */
+
+function s4() {
+  return Math.floor((1 + Math.random()) * 0x10000)
+    .toString(16)
+    .substring(1)
+}
+
+const uid = function () {
+  return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
+    s4() + '-' + s4() + s4() + s4()
+}
+
+<% if (ctx.dev) { %>
+/**
+ * @name __whitelistWarning
+ * @description Present a stylish warning to the developer that their API
+ * call has not been whitelisted in tauri.conf.js
+ * @param {String} func - function name to warn
+ * @private
+ */
+const __whitelistWarning = function (func) {
+  console.warn('%c[Tauri] Danger \ntauri.' + func + ' not whitelisted 💣\n%c\nAdd to tauri.conf.js: \n\ntauri: \n  whitelist: { \n    ' + func + ': true \n\nReference: https://tauri-apps.org/docs/api#' + func , 'background: red; color: white; font-weight: 800; padding: 2px; font-size:1.5em', ' ')
+}
+<% } %>
+
+<% if (ctx.dev) { %>
+/**
+ * @name __reject
+ * @description is a private promise used to deflect un-whitelisted tauri API calls
+ * Its only purpose is to maintain thenable structure in client code without
+ * breaking the application
+ *  * @type {Promise<any>}
+ * @private
+ */
+<% } %>
+const __reject = new Promise((reject) => { reject })
+
+window.tauri = {
+<% if (ctx.dev) { %>
+  /**
+   * @name invoke
+   * @description Calls a Tauri Core feature, such as setTitle
+   * @param {Object} args
+   */
+<% } %>
+  invoke (args) {
+    Object.freeze(args)
+    window.external.invoke(JSON.stringify(args))
+  },
+
+<% if (ctx.dev) { %>
+  /**
+   * @name listen
+   * @description Add an event listener to Tauri backend
+   * @param {String} event
+   * @param {Function} handler
+   * @param {Boolean} once
+   */
+<% } %>
+  listen (event, handler, once = false) {
+    this.invoke({
+      cmd: 'listen',
+      event,
+      handler: this.transformCallback(handler, once),
+      once
+    })
+  },
+
+<% if (ctx.dev) { %>
+  /**
+   * @name emit
+   * @description Emits an evt to the Tauri back end
+   * @param {String} evt
+   * @param {Object} payload
+   */
+<% } %>
+  emit (evt, payload) {
+    this.invoke({
+      cmd: 'emit',
+      event: evt,
+      payload
+    })
+  },
+
+<% if (ctx.dev) { %>
+  /**
+   * @name transformCallback
+   * @description Registers a callback with a uid
+   * @param {Function} callback
+   * @param {Boolean} once
+   * @returns {*}
+   */
+<% } %>
+  transformCallback (callback, once = true) {
+    const identifier = Object.freeze(uid())
+    window[identifier] = (result) => {
+      if (once) {
+        delete window[identifier]
+      }
+      return callback && callback(result)
+    }
+    return identifier
+  },
+
+<% if (ctx.dev) { %>
+  /**
+   * @name promisified
+   * @description Turns a request into a chainable promise
+   * @param {Object} args
+   * @returns {Promise<any>}
+   */
+<% } %>
+  promisified (args) {
+    return new Promise((resolve, reject) => {
+      this.invoke({
+        callback: this.transformCallback(resolve),
+        error: this.transformCallback(reject),
+        ...args
+      })
+    })
+  },
+
+<% if (ctx.dev) { %>
+  /**
+   * @name readTextFile
+   * @description Accesses a non-binary file on the user's filesystem
+   * and returns the content. Permissions based on the app's PID owner
+   * @param {String} path
+   * @returns {*|Promise<any>|Promise}
+   */
+<% } %>
+  readTextFile (path) {
+  <% if (tauri.whitelist.readTextFile === true || tauri.whitelist.all === true) { %>
+    Object.freeze(path)
+    return this.promisified({ cmd: 'readTextFile', path })
+      <% } else { %>
+  <% if (ctx.dev) { %>
+      __whitelistWarning('readTextFile')
+      <% } %>
+    return __reject
+      <% } %>
+  },
+
+<% if (ctx.dev) { %>
+  /**
+   * @name readBinaryFile
+   * @description Accesses a binary file on the user's filesystem
+   * and returns the content. Permissions based on the app's PID owner
+   * @param {String} path
+   * @returns {*|Promise<any>|Promise}
+   */
+<% } %>
+  readBinaryFile (path) {
+  <% if (tauri.whitelist.readBinaryFile === true || tauri.whitelist.all === true) { %>
+    Object.freeze(path)
+    return this.promisified({ cmd: 'readBinaryFile', path })
+      <% } else { %>
+  <% if (ctx.dev) { %>
+      __whitelistWarning('readBinaryFile')
+      <% } %>
+    return __reject
+      <% } %>
+  },
+
+<% if (ctx.dev) { %>
+  /**
+   * @name writeFile
+   * @description Write a file to the Local Filesystem.
+   * Permissions based on the app's PID owner
+   * @param {Object} cfg
+   * @param {String} cfg.file
+   * @param {String|Binary} cfg.contents
+   */
+<% } %>
+  writeFile (cfg) {
+  <% if (tauri.whitelist.writeFile === true || tauri.whitelist.all === true) { %>
+    Object.freeze(cfg)
+    this.invoke({ cmd: 'writeFile', file: cfg.file, contents: cfg.contents })
+    <% } else { %>
+  <% if (ctx.dev) { %>
+      __whitelistWarning('writeFile')
+      <% } %>
+    return __reject
+      <% } %>
+  },
+
+<% if (ctx.dev) { %>
+  /**
+   * @name listFiles
+   * @description Get the files in a path.
+   * Permissions based on the app's PID owner
+   * @param {String} path
+   * @returns {*|Promise<any>|Promise}
+   */
+<% } %>
+  listFiles (path) {
+  <% if (tauri.whitelist.listFiles === true || tauri.whitelist.all === true) { %>
+    Object.freeze(path)
+    return this.promisified({ cmd: 'listFiles', path })
+      <% } else { %>
+  <% if (ctx.dev) { %>
+      __whitelistWarning('listFiles')
+      <% } %>
+    return __reject
+      <% } %>
+  },
+
+<% if (ctx.dev) { %>
+  /**
+   * @name listDirs
+   * @description Get the directories in a path.
+   * Permissions based on the app's PID owner
+   * @param {String} path
+   * @returns {*|Promise<any>|Promise}
+   */
+<% } %>
+  listDirs (path) {
+  <% if (tauri.whitelist.listDirs === true || tauri.whitelist.all === true) { %>
+    Object.freeze(path)
+    return this.promisified({ cmd: 'listDirs', path })
+      <% } else { %>
+  <% if (ctx.dev) { %>
+      __whitelistWarning('listDirs')
+      <% } %>
+    return __reject
+      <% } %>
+  },
+
+<% if (ctx.dev) { %>
+  /**
+   * @name setTitle
+   * @description Set the application's title
+   * @param {String} title
+   */
+<% } %>
+  setTitle (title) {
+    <% if (tauri.whitelist.setTitle === true || tauri.whitelist.all === true) { %>
+    Object.freeze(title)
+    this.invoke({ cmd: 'setTitle', title })
+      <% } else { %>
+  <% if (ctx.dev) { %>
+      __whitelistWarning('setTitle')
+      <% } %>
+    return __reject
+      <% } %>
+  },
+
+  <% if (ctx.dev) { %>
+  /**
+   * @name open
+   * @description Open an URI
+   * @param {String} uri
+   */
+<% } %>
+  open (uri) {
+    <% if (tauri.whitelist.open === true || tauri.whitelist.all === true) { %>
+    Object.freeze(uri)
+    this.invoke({ cmd: 'open', uri })
+      <% } else { %>
+<% if (ctx.dev) { %>
+    __whitelistWarning('open')
+    <% } %>
+  return __reject
+    <% } %>
+  },
+
+<% if (ctx.dev) { %>
+  /**
+   * @name execute
+   * @description Execute a program with arguments.
+   * Permissions based on the app's PID owner
+   * @param {String} command
+   * @param {String|Array} args
+   * @returns {*|Promise<any>|Promise}
+   */
+<% } %>
+  execute (command, args) {
+    <% if (tauri.whitelist.execute === true || tauri.whitelist.all === true) { %>
+    Object.freeze(command)
+    if (typeof args === 'string' || typeof args === 'object') {
+      Object.freeze(args)
+    }
+    return this.promisified({ cmd: 'execute', command, args: typeof (args) === 'string' ? [args] : args })
+  <% } else { %>
+  <% if (ctx.dev) { %>
+    __whitelistWarning('execute')
+    <% } %>
+    return __reject
+      <% } %>
+  },
+
+<% if (ctx.dev) { %>
+  /**
+   * @name bridge
+   * @description Securely pass a message to the backend.
+   * @example
+   *  this.$q.tauri.bridge('QBP/1/ping/client-1', 'pingback')
+   * @param {String} command - a compressed, slash-delimited and
+   * versioned API call to the backend.
+   * @param {String|Object}payload
+   * @returns {*|Promise<any>|Promise}
+   */
+<% } %>
+  bridge (command, payload) {
+<% if (tauri.whitelist.bridge === true || tauri.whitelist.all === true) { %>
+    Object.freeze(command)
+    if (typeof payload === 'string' || typeof payload === 'object') {
+      Object.freeze(payload)
+    }
+    return this.promisified({ cmd: 'bridge', command, payload: typeof (payload) === 'object' ? [payload] : payload })
+<% } else { %>
+<% if (ctx.dev) { %>
+    __whitelistWarning('bridge')
+<% } %>
+      return __reject
+<% } %>
+  },
+
+  <% if (ctx.dev) { %>
+  /**
+   * @name setup
+   * @description Inform Rust that the webview has initialized and is
+   * ready for communication
+   */
+  <% } %>
+  setup () {
+    document.querySelector('body').addEventListener('click', function (e) {
+      let target = e.target
+      while (target != null) {
+        if (target.matches ? target.matches('a') : target.msMatchesSelector('a')) {
+          tauri.open(target.href)
+          break
+        }
+        target = target.parentElement
+      }
+    }, true)
+
+    tauri.invoke({
+      cmd: 'init'
+    })
+  }
+}

+ 314 - 270
cli/tauri.js/templates/tauri.js

@@ -1,4 +1,5 @@
 /* eslint-disable */
+
 /**
  *  * THIS FILE IS GENERATED AUTOMATICALLY.
  * DO NOT EDIT.
@@ -8,20 +9,6 @@
  **/
 
 // open <a href="..."> links with the Tauri API
-document.querySelector('body').addEventListener('click', function (e) {
-  let target = e.target
-  while (target != null) {
-    if (target.matches ? target.matches('a') : target.msMatchesSelector('a')) {
-      tauri.open(target.href)
-      break
-    }
-    target = target.parentElement
-  }
-}, true)
-
-document.addEventListener('DOMContentLoaded', function () {
-  tauri.invoke({ cmd: 'init' })
-})
 
 /**
  * @module tauri
@@ -44,306 +31,363 @@ const uid = function () {
     s4() + '-' + s4() + s4() + s4()
 }
 
-<% if (ctx.dev) { %>
-/**
- * @name __whitelistWarning
- * @description Present a stylish warning to the developer that their API
- * call has not been whitelisted in tauri.conf.js
- * @param {String} func - function name to warn
- * @private
- */
-const __whitelistWarning = function (func) {
-  console.warn('%c[Tauri] Danger \ntauri.' + func + ' not whitelisted 💣\n%c\nAdd to tauri.conf.js: \n\ntauri: \n  whitelist: { \n    ' + func + ': true \n\nReference: https://tauri-apps.org/docs/api#' + func , 'background: red; color: white; font-weight: 800; padding: 2px; font-size:1.5em', ' ')
-}
-<% } %>
+function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
 
-/**
- * @name __reject
- * @description is a private promise used to deflect un-whitelisted tauri API calls
- * Its only purpose is to maintain thenable structure in client code without
- * breaking the application
- *  * @type {Promise<any>}
- * @private
- */
-const __reject = new Promise((reject) => { reject })
+function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
 
-export default class Tauri {
-<% if (ctx.dev) { %>
-  /**
-   * @name invoke
-   * @description Calls a Tauri Core feature, such as setTitle
-   * @param {Object} args
-   */
-<% } %>
-  static invoke (args) {
-    Object.freeze(args)
-    window.external.invoke(JSON.stringify(args))
-  }
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+
+function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
 
 <% if (ctx.dev) { %>
   /**
-   * @name addEventListener
-   * @description Add an evt listener to Tauri back end
-   * @param {String} evt
-   * @param {Function} handler
-   * @param {Boolean} once
+   * @name __reject
+   * @description is a private promise used to deflect un-whitelisted tauri API calls
+   * Its only purpose is to maintain thenable structure in client code without
+   * breaking the application
+   *  * @type {Promise<any>}
+   * @private
    */
 <% } %>
-  static addEventListener (evt, handler, once = false) {
+var __reject = new Promise(function (reject) {
+  reject;
+});
+
+window.tauri = {
+  <% if (ctx.dev) { %>
+    /**
+     * @name invoke
+     * @description Calls a Tauri Core feature, such as setTitle
+     * @param {Object} args
+     */
+  <% } %>
+  invoke: function invoke(args) {
+    Object.freeze(args);
+    window.external.invoke(JSON.stringify(args));
+  },
+
+  <% if (ctx.dev) { %>
+    /**
+     * @name listen
+     * @description Add an event listener to Tauri backend
+     * @param {String} event
+     * @param {Function} handler
+     * @param {Boolean} once
+     */
+  <% } %>
+  listen: function listen(event, handler) {
+    var once = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
     this.invoke({
-      cmd: 'addEventListener',
-      evt,
+      cmd: 'listen',
+      event: event,
       handler: this.transformCallback(handler, once),
-      once
-    })
-  }
+      once: once
+    });
+  },
 
-<% if (ctx.dev) { %>
-  /**
-   * @name emit
-   * @description Emits an evt to the Tauri back end
-   * @param {String} evt
-   * @param {Object} payload
-   */
-<% } %>
-  static emit(evt, payload) {
+  <% if (ctx.dev) { %>
+    /**
+     * @name emit
+     * @description Emits an evt to the Tauri back end
+     * @param {String} evt
+     * @param {Object} payload
+     */
+  <% } %>
+  emit: function emit(evt, payload) {
     this.invoke({
       cmd: 'emit',
       event: evt,
-      payload
-    })
-  }
+      payload: payload
+    });
+  },
 
-<% if (ctx.dev) { %>
-  /**
-   * @name transformCallback
-   * @description Registers a callback with a uid
-   * @param {Function} callback
-   * @param {Boolean} once
-   * @returns {*}
-   */
-<% } %>
-  static transformCallback (callback, once = true) {
-    const identifier = Object.freeze(uid())
-    window[identifier] = (result) => {
+  <% if (ctx.dev) { %>
+    /**
+     * @name transformCallback
+     * @description Registers a callback with a uid
+     * @param {Function} callback
+     * @param {Boolean} once
+     * @returns {*}
+     */
+  <% } %>
+  transformCallback: function transformCallback(callback) {
+    var once = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
+    var identifier = Object.freeze(uid());
+
+    window[identifier] = function (result) {
       if (once) {
-        delete window[identifier]
+        delete window[identifier];
       }
-      return callback && callback(result)
-    }
-    return identifier
-  }
 
-<% if (ctx.dev) { %>
-  /**
-   * @name promisified
-   * @description Turns a request into a chainable promise
-   * @param {Object} args
-   * @returns {Promise<any>}
-   */
-<% } %>
-  static promisified (args) {
-    return new Promise((resolve, reject) => {
-      this.invoke({
-        callback: this.transformCallback(resolve),
-        error: this.transformCallback(reject),
-        ...args
-      })
-    })
-  }
+      return callback && callback(result);
+    };
+
+    return identifier;
+  },
 
-<% if (ctx.dev) { %>
-  /**
-   * @name readTextFile
-   * @description Accesses a non-binary file on the user's filesystem
-   * and returns the content. Permissions based on the app's PID owner
-   * @param {String} path
-   * @returns {*|Promise<any>|Promise}
-   */
-<% } %>
-  static readTextFile (path) {
-  <% if (tauri.whitelist.readTextFile === true || tauri.whitelist.all === true) { %>
-    Object.freeze(path)
-    return this.promisified({ cmd: 'readTextFile', path })
-      <% } else { %>
   <% if (ctx.dev) { %>
-      __whitelistWarning('readTextFile')
-      <% } %>
-    return __reject
-      <% } %>
-  }
+    /**
+     * @name promisified
+     * @description Turns a request into a chainable promise
+     * @param {Object} args
+     * @returns {Promise<any>}
+     */
+  <% } %>
+  promisified: function promisified(args) {
+    var _this = this;
+
+    return new Promise(function (resolve, reject) {
+      _this.invoke(_objectSpread({
+        callback: _this.transformCallback(resolve),
+        error: _this.transformCallback(reject)
+      }, args));
+    });
+  },
 
-<% if (ctx.dev) { %>
-  /**
-   * @name readBinaryFile
-   * @description Accesses a binary file on the user's filesystem
-   * and returns the content. Permissions based on the app's PID owner
-   * @param {String} path
-   * @returns {*|Promise<any>|Promise}
-   */
-<% } %>
-  static readBinaryFile (path) {
-  <% if (tauri.whitelist.readBinaryFile === true || tauri.whitelist.all === true) { %>
-    Object.freeze(path)
-    return this.promisified({ cmd: 'readBinaryFile', path })
-      <% } else { %>
   <% if (ctx.dev) { %>
-      __whitelistWarning('readBinaryFile')
-      <% } %>
-    return __reject
-      <% } %>
-  }
+    /**
+     * @name readTextFile
+     * @description Accesses a non-binary file on the user's filesystem
+     * and returns the content. Permissions based on the app's PID owner
+     * @param {String} path
+     * @returns {*|Promise<any>|Promise}
+     */
+  <% } %>
+  readTextFile: function readTextFile(path) {
+    <% if (tauri.whitelist.readTextFile === true || tauri.whitelist.all === true) { %>
+    Object.freeze(path);
+    return this.promisified({
+      cmd: 'readTextFile',
+      path: path
+    });
+    <% } else { %>
+      <% if (ctx.dev) { %>
+          __whitelistWarning('readTextFile')
+          <% } %>
+        return __reject
+    <% } %>
+  },
 
-<% if (ctx.dev) { %>
-  /**
-   * @name writeFile
-   * @description Write a file to the Local Filesystem.
-   * Permissions based on the app's PID owner
-   * @param {Object} cfg
-   * @param {String} cfg.file
-   * @param {String|Binary} cfg.contents
-   */
-<% } %>
-  static writeFile (cfg) {
-  Object.freeze(cfg)
-  <% if (tauri.whitelist.writeFile === true || tauri.whitelist.all === true) { %>
-    this.invoke({ cmd: 'writeFile', file: cfg.file, contents: cfg.contents })
+  <% if (ctx.dev) { %>
+    /**
+     * @name readBinaryFile
+     * @description Accesses a binary file on the user's filesystem
+     * and returns the content. Permissions based on the app's PID owner
+     * @param {String} path
+     * @returns {*|Promise<any>|Promise}
+     */
+  <% } %>
+  readBinaryFile: function readBinaryFile(path) {
+    <% if (tauri.whitelist.readBinaryFile === true || tauri.whitelist.all === true) { %>
+    Object.freeze(path);
+    return this.promisified({
+      cmd: 'readBinaryFile',
+      path: path
+    });
     <% } else { %>
+      <% if (ctx.dev) { %>
+          __whitelistWarning('readBinaryFile')
+          <% } %>
+        return __reject
+    <% } %>
+  },
+
   <% if (ctx.dev) { %>
-      __whitelistWarning('writeFile')
-      <% } %>
-    return __reject
-      <% } %>  }
+    /**
+     * @name writeFile
+     * @description Write a file to the Local Filesystem.
+     * Permissions based on the app's PID owner
+     * @param {Object} cfg
+     * @param {String} cfg.file
+     * @param {String|Binary} cfg.contents
+     */
+  <% } %>
+  writeFile: function writeFile(cfg) {
+    <% if (tauri.whitelist.writeFile === true || tauri.whitelist.all === true) { %>
+    Object.freeze(cfg);
+    this.invoke({
+      cmd: 'writeFile',
+      file: cfg.file,
+      contents: cfg.contents
+    });
+    <% } else { %>
+      <% if (ctx.dev) { %>
+          __whitelistWarning('writeFile')
+          <% } %>
+        return __reject
+    <% } %>
+  },
 
-<% if (ctx.dev) { %>
-  /**
-   * @name listFiles
-   * @description Get the files in a path.
-   * Permissions based on the app's PID owner
-   * @param {String} path
-   * @returns {*|Promise<any>|Promise}
-   */
-<% } %>
-  static listFiles (path) {
-  <% if (tauri.whitelist.listFiles === true || tauri.whitelist.all === true) { %>
-    Object.freeze(path)
-    return this.promisified({ cmd: 'listFiles', path })
-      <% } else { %>
   <% if (ctx.dev) { %>
-      __whitelistWarning('listFiles')
-      <% } %>
-    return __reject
-      <% } %>
-  }
+    /**
+     * @name listFiles
+     * @description Get the files in a path.
+     * Permissions based on the app's PID owner
+     * @param {String} path
+     * @returns {*|Promise<any>|Promise}
+     */
+  <% } %>
+  listFiles: function listFiles(path) {
+    <% if (tauri.whitelist.listFiles === true || tauri.whitelist.all === true) { %>
+
+    Object.freeze(path);
+    return this.promisified({
+      cmd: 'listFiles',
+      path: path
+    });
+    <% } else { %>
+      <% if (ctx.dev) { %>
+          __whitelistWarning('listDirs')
+          <% } %>
+        return __reject
+    <% } %>
+  },
 
-<% if (ctx.dev) { %>
-  /**
-   * @name listDirs
-   * @description Get the directories in a path.
-   * Permissions based on the app's PID owner
-   * @param {String} path
-   * @returns {*|Promise<any>|Promise}
-   */
-<% } %>
-  static listDirs (path) {
-  <% if (tauri.whitelist.listDirs === true || tauri.whitelist.all === true) { %>
-    Object.freeze(path)
-    return this.promisified({ cmd: 'listDirs', path })
-      <% } else { %>
   <% if (ctx.dev) { %>
-      __whitelistWarning('listDirs')
-      <% } %>
-    return __reject
-      <% } %>
-  }
+    /**
+     * @name listDirs
+     * @description Get the directories in a path.
+     * Permissions based on the app's PID owner
+     * @param {String} path
+     * @returns {*|Promise<any>|Promise}
+     */
+  <% } %>
+  listDirs: function listDirs(path) {
+    <% if (tauri.whitelist.listDirs === true || tauri.whitelist.all === true) { %>
+    Object.freeze(path);
+    return this.promisified({
+      cmd: 'listDirs',
+      path: path
+    });
+    <% } else { %>
+      <% if (ctx.dev) { %>
+          __whitelistWarning('listDirs')
+          <% } %>
+        return __reject
+    <% } %>
+  },
 
-<% if (ctx.dev) { %>
-  /**
-   * @name setTitle
-   * @description Set the application's title
-   * @param {String} title
-   */
-<% } %>
-  static setTitle (title) {
-    <% if (tauri.whitelist.setTitle === true || tauri.whitelist.all === true) { %>
-    Object.freeze(title)
-    this.invoke({ cmd: 'setTitle', title })
-      <% } else { %>
   <% if (ctx.dev) { %>
-      __whitelistWarning('setTitle')
+    /**
+     * @name setTitle
+     * @description Set the application's title
+     * @param {String} title
+     */
+  <% } %>
+  setTitle: function setTitle(title) {
+    <% if (tauri.whitelist.setTitle === true || tauri.whitelist.all === true) { %>
+    Object.freeze(title);
+    this.invoke({
+      cmd: 'setTitle',
+      title: title
+    });
+    <% } else { %>
+      <% if (ctx.dev) { %>
+    __whitelistWarning('setTitle')
       <% } %>
     return __reject
-      <% } %>
-  }
+    <% } %>
+  },
 
   <% if (ctx.dev) { %>
-  /**
-   * @name open
-   * @description Open an URI
-   * @param {String} uri
-   */
-<% } %>
-  static open (uri) {
+    /**
+     * @name open
+     * @description Open an URI
+     * @param {String} uri
+     */
+  <% } %>
+  open: function open(uri) {
     <% if (tauri.whitelist.open === true || tauri.whitelist.all === true) { %>
-    Object.freeze(uri)
-    this.invoke({ cmd: 'open', uri })
-      <% } else { %>
-  <% if (ctx.dev) { %>
+    Object.freeze(uri);
+    this.invoke({
+      cmd: 'open',
+      uri: uri
+    });
+    <% } else { %>
+    <% if (ctx.dev) { %>
       __whitelistWarning('open')
       <% } %>
     return __reject
       <% } %>
-  }
+  },
 
-<% if (ctx.dev) { %>
-  /**
-   * @name execute
-   * @description Execute a program with arguments.
-   * Permissions based on the app's PID owner
-   * @param {String} command
-   * @param {String|Array} args
-   * @returns {*|Promise<any>|Promise}
-   */
-<% } %>
-  static execute (command, args) {
+  <% if (ctx.dev) { %>
+    /**
+     * @name execute
+     * @description Execute a program with arguments.
+     * Permissions based on the app's PID owner
+     * @param {String} command
+     * @param {String|Array} args
+     * @returns {*|Promise<any>|Promise}
+     */
+  <% } %>
+  execute: function execute(command, args) {
     <% if (tauri.whitelist.execute === true || tauri.whitelist.all === true) { %>
-    Object.freeze(command)
-    if (typeof args === 'string' || typeof args === 'object') {
-      Object.freeze(args)
+
+    Object.freeze(command);
+
+    if (typeof args === 'string' || _typeof(args) === 'object') {
+      Object.freeze(args);
     }
-    return this.promisified({ cmd: 'execute', command, args: typeof (args) === 'string' ? [args] : args })
-  <% } else { %>
-  <% if (ctx.dev) { %>
-    __whitelistWarning('execute')
+
+    return this.promisified({
+      cmd: 'execute',
+      command: command,
+      args: typeof args === 'string' ? [args] : args
+    });
+    <% } else { %>
+      <% if (ctx.dev) { %>
+        __whitelistWarning('execute')
+        <% } %>
+        return __reject
     <% } %>
-    return __reject
+  },
+
+  bridge: function bridge(command, payload) {
+    <% if (tauri.whitelist.bridge === true || tauri.whitelist.all === true) { %>
+
+    Object.freeze(command);
+
+    if (typeof payload === 'string' || _typeof(payload) === 'object') {
+      Object.freeze(payload);
+    }
+
+    return this.promisified({
+      cmd: 'bridge',
+      command: command,
+      payload: _typeof(payload) === 'object' ? [payload] : payload
+    });
+    <% } else { %>
+      <% if (ctx.dev) { %>
+          __whitelistWarning('bridge')
       <% } %>
-  }
+            return __reject
+    <% } %>
+  },
 
-<% if (ctx.dev) { %>
+  <% if (ctx.dev) { %>
   /**
-   * @name bridge
-   * @description Securely pass a message to the backend.
-   * @example
-   *  this.$q.tauri.bridge('QBP/1/ping/client-1', 'pingback')
-   * @param {String} command - a compressed, slash-delimited and
-   * versioned API call to the backend.
-   * @param {String|Object}payload
-   * @returns {*|Promise<any>|Promise}
-   */
-<% } %>
-  static bridge (command, payload) {
-<% if (tauri.whitelist.bridge === true || tauri.whitelist.all === true) { %>
-    Object.freeze(command)
-    if (typeof payload === 'string' || typeof payload === 'object') {
-      Object.freeze(payload)
-    }
-    return this.promisified({ cmd: 'bridge', command, payload: typeof (payload) === 'object' ? [payload] : payload })
-<% } else { %>
-<% if (ctx.dev) { %>
-    __whitelistWarning('bridge')
-<% } %>
-      return __reject
-<% } %>
+   * @name setup
+   * @description Inform Rust that the webview has initialized and is
+   * ready for communication
+     */
+  <% } %>
+  setup: function setup() {
+    document.querySelector('body').addEventListener('click', function (e) {
+      var target = e.target;
+
+      while (target != null) {
+        if (target.matches ? target.matches('a') : target.msMatchesSelector('a')) {
+          tauri.open(target.href);
+          break;
+        }
+
+        target = target.parentElement;
+      }
+    }, true);
+    window.tauri.invoke({
+      cmd: 'init'
+    });
   }
-}
+};

+ 13 - 0
cli/tauri.js/yarn.lock

@@ -1728,6 +1728,14 @@ eslint-plugin-import@2.18.2:
     read-pkg-up "^2.0.0"
     resolve "^1.11.0"
 
+eslint-plugin-lodash-template@^0.15.0:
+  version "0.15.0"
+  resolved "https://registry.npmjs.org/eslint-plugin-lodash-template/-/eslint-plugin-lodash-template-0.15.0.tgz#649265456a3ad5345d5bb3a3eeb8995d1d31c877"
+  integrity sha512-OXfUJz3udNAbhOJknp2RGJp1xf3A3CrwHoP31B0sWWrsKEK1fGcoTN8KtUvEenni+oNWe0RFw3sT1cl+/A0oRw==
+  dependencies:
+    esquery "^1.0.1"
+    parse5 "^5.0.0"
+
 eslint-plugin-node@10.0.0:
   version "10.0.0"
   resolved "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-10.0.0.tgz#fd1adbc7a300cf7eb6ac55cf4b0b6fc6e577f5a6"
@@ -4683,6 +4691,11 @@ parse5@4.0.0:
   resolved "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608"
   integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==
 
+parse5@^5.0.0:
+  version "5.1.1"
+  resolved "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178"
+  integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==
+
 pascalcase@^0.1.1:
   version "0.1.1"
   resolved "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"

+ 2 - 2
examples/vue/quasar-app/package.json

@@ -22,10 +22,10 @@
   },
   "dependencies": {
     "@quasar/extras": "^1.0.0",
-    "quasar": "^1.0.0"
+    "quasar": "^1.5.3"
   },
   "devDependencies": {
-    "@quasar/app": "^1.0.0",
+    "@quasar/app": "1.3.2",
     "@vue/eslint-config-standard": "^4.0.0",
     "babel-eslint": "^10.0.1",
     "eslint": "^5.10.0",

+ 1 - 1
examples/vue/quasar-app/quasar.conf.js

@@ -78,7 +78,7 @@ module.exports = function (ctx) {
     // https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-devServer
     devServer: {
       // https: true,
-      // port: 8080,
+      port: 7334,
       open: true // opens browser window automatically
     },
 

+ 1 - 1
examples/vue/quasar-app/src-tauri/Cargo.toml

@@ -30,7 +30,7 @@ includedir = "0.5.0"
 
   [dependencies.tauri]
   path = "../../../../tauri"
-  features = [ "edge" ]
+  features = [ "all-api", "edge" ]
 
 [features]
 dev = [ "tauri/dev" ]

+ 17 - 13
examples/vue/quasar-app/src-tauri/src/main.rs

@@ -6,20 +6,24 @@ extern crate serde_json;
 
 fn main() {
   tauri::AppBuilder::new()
-    .invoke_handler(|_webview, arg| {
-      use cmd::Cmd::*;
-      match serde_json::from_str(arg) {
-        Err(_) => {}
-        Ok(command) => {
-          match command {
-            // definitions for your custom commands from Cmd here
-            MyCustomCommand { argument } => {
-              //  your command code
-              println!("{}", argument);
-            }
-          }
+    .setup(|_webview| {
+      let handle = _webview.handle();
+      tauri::event::listen("hello", move |msg| {
+        #[derive(Serialize)]
+        pub struct Reply {
+          pub msg: String,
+          pub rep: String
         }
-      }
+
+        let reply = Reply {
+          msg: format!("{}", msg).to_string(),
+          rep: "something else".to_string()
+        };
+
+        tauri::event::emit(&handle, "reply",  serde_json::to_string(&reply).unwrap());
+
+        println!("Message from emit:hello => {}", msg);
+      });
     })
     .build()
     .run();

+ 26 - 3
examples/vue/quasar-app/src/pages/Index.vue

@@ -1,11 +1,34 @@
 <template>
-  <q-page class="flex flex-center">
-    <img alt="Quasar logo" src="~assets/quasar-logo-full.svg">
+  <q-page class="flex flex-center column">
+    <h3 class="row text-center">{{ msg }}</h3>
+    <div class="row">
+      <q-btn @click="eventToRust()">SEND MSG</q-btn>
+    </div>
   </q-page>
 </template>
 
 <script>
+require('../../src-tauri/tauri.js')
+import { uid } from 'quasar'
+
 export default {
-  name: 'PageIndex'
+  name: 'HelloWorld',
+  data () {
+    return {
+      msg: 'waiting for rust'
+    }
+  },
+  mounted () {
+    window.tauri.setup()
+    window.tauri.listen('reply', res => {
+      this.msg = res.payload.msg
+    }, false)
+  },
+  methods: {
+    // set up an event listener
+    eventToRust () {
+      window.tauri.emit('hello', uid())
+    }
+  }
 }
 </script>

+ 3 - 3
examples/vue/quasar-app/tauri.conf.js

@@ -1,11 +1,11 @@
 const path = require('path')
-const distDir = path.resolve(__dirname, './dist')
+const distDir = path.resolve(__dirname, './dist/spa')
 
 module.exports = function () {
   return {
     build: {
       distDir: distDir,
-      devPath: 'http://localhost:4000' // devServer URL or path to html file
+      devPath: 'http://localhost:7334' // devServer URL or path to html file
     },
     ctx: {},
     tauri: {
@@ -16,7 +16,7 @@ module.exports = function () {
         active: true
       },
       whitelist: {
-        all: false
+        all: true
       },
       window: {
         title: 'Tauri App'

Diferenças do arquivo suprimidas por serem muito extensas
+ 633 - 16
examples/vue/quasar-app/yarn.lock


+ 1 - 1
tauri/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "tauri"
-version = "0.1.2"
+version = "0.1.3"
 authors = ["Lucas Fernandes Gonçalves Nogueira <lucas@quasar.dev>", "Daniel Thompson-Yvetot <denjell@sfosc.org>"]
 license = "MIT"
 homepage = "https://tauri-apps.org"

+ 1 - 1
tauri/src/api.rs

@@ -117,7 +117,7 @@ pub fn handler<T: 'static>(webview: &mut WebView<'_, T>, arg: &str) -> bool {
         } => {
           crate::salt::validate(webview, salt, callback, error);
         }
-        AddEventListener {
+        Listen {
           event,
           handler,
           once,

+ 1 - 1
tauri/src/api/cmd.rs

@@ -49,7 +49,7 @@ pub enum Cmd {
     callback: String,
     error: String,
   },
-  AddEventListener {
+  Listen {
     event: String,
     handler: String,
     once: bool,

+ 22 - 1
tauri/src/app.rs

@@ -5,6 +5,7 @@ use web_view::WebView;
 
 pub struct App {
   invoke_handler: Option<Box<dyn FnMut(&mut WebView<'_, ()>, &str)>>,
+  setup: Option<Box<dyn FnMut(&mut WebView<'_, ()>)>>,
 }
 
 impl App {
@@ -12,7 +13,7 @@ impl App {
     runner::run(&mut self);
   }
 
-  pub fn run_invoke_handler(&mut self, webview: &mut WebView<'_, ()>, arg: &str) {
+  pub(crate) fn run_invoke_handler(&mut self, webview: &mut WebView<'_, ()>, arg: &str) {
     match self.invoke_handler {
       Some(ref mut invoke_handler) => {
         invoke_handler(webview, arg);
@@ -20,16 +21,27 @@ impl App {
       None => {}
     }
   }
+
+  pub(crate) fn run_setup(&mut self, webview: &mut WebView<'_, ()>) {
+    match self.setup {
+      Some(ref mut setup) => {
+        setup(webview);
+      }
+      None => {}
+    }
+  }
 }
 
 pub struct AppBuilder {
   invoke_handler: Option<Box<dyn FnMut(&mut WebView<'_, ()>, &str)>>,
+  setup: Option<Box<dyn FnMut(&mut WebView<'_, ()>)>>,
 }
 
 impl AppBuilder {
   pub fn new() -> Self {
     Self {
       invoke_handler: None,
+      setup: None,
     }
   }
 
@@ -41,9 +53,18 @@ impl AppBuilder {
     self
   }
 
+  pub fn setup<F: FnMut(&mut WebView<'_, ()>) + 'static>(
+    mut self,
+    setup: F,
+  ) -> Self {
+    self.setup = Some(Box::new(setup));
+    self
+  }
+
   pub fn build(self) -> App {
     App {
       invoke_handler: self.invoke_handler,
+      setup: self.setup
     }
   }
 }

+ 7 - 1
tauri/src/app/runner.rs

@@ -93,6 +93,8 @@ pub(crate) fn run(application: &mut crate::App) {
     }
   }
 
+  let mut ran_setup = false;
+
   let webview = web_view::builder()
     .title(&config.window.title)
     .size(config.window.width, config.window.height)
@@ -100,10 +102,14 @@ pub(crate) fn run(application: &mut crate::App) {
     .debug(debug)
     .user_data(())
     .invoke_handler(|webview, arg| {
-      // leave this as is to use the tauri API from your JS code
       if !crate::api::handler(webview, arg) {
         application.run_invoke_handler(webview, arg);
       }
+      // the first command is always the `init`, so we can safely run the setup hook here
+      if !ran_setup {
+        ran_setup = true;
+        application.run_setup(webview);
+      }
 
       Ok(())
     })

+ 8 - 10
tauri/src/event.rs

@@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex};
 use web_view::Handle;
 
 struct EventHandler {
-  on_event: Box<dyn FnOnce(String)>,
+  on_event: Box<dyn FnMut(String)>,
 }
 
 thread_local!(static LISTENERS: Arc<Mutex<HashMap<String, EventHandler>>> = Arc::new(Mutex::new(HashMap::new())));
@@ -27,19 +27,19 @@ pub fn event_queue_object_name() -> String {
   EVENT_QUEUE_OBJECT_NAME.to_string()
 }
 
-pub fn listen<F: FnOnce(String) + 'static>(id: &'static str, handler: F) {
+pub fn listen<F: FnMut(String) + 'static>(id: &'static str, handler: F) {
   LISTENERS.with(|listeners| {
     let mut l = listeners.lock().unwrap();
     l.insert(
       id.to_string(),
       EventHandler {
         on_event: Box::new(handler),
-      },
+      }
     );
   });
 }
 
-pub fn emit<T: 'static>(webview_handle: Handle<T>, event: &'static str, mut payload: String) {
+pub fn emit<T: 'static>(webview_handle: &Handle<T>, event: &'static str, mut payload: String) {
   let salt = crate::salt::generate();
   if payload == "" {
     payload = "void 0".to_string();
@@ -59,16 +59,14 @@ pub fn emit<T: 'static>(webview_handle: Handle<T>, event: &'static str, mut payl
 }
 
 pub fn on_event(event: String, data: String) {
-  LISTENERS.with(|l| {
-    let mut listeners = l.lock().unwrap();
+  LISTENERS.with(|listeners| {
+    let mut l = listeners.lock().unwrap();
 
     let key = event.clone();
 
-    if listeners.contains_key(&event) {
-      let handler = listeners.remove(&event).unwrap();
+    if l.contains_key(&key) {
+      let handler = l.get_mut(&key).unwrap();
       (handler.on_event)(data);
     }
-
-    listeners.remove(&key);
   });
 }

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff