Browse Source

feat(tauri) add Notifications API (#505)

Lucas Fernandes Nogueira 5 years ago
parent
commit
406dea79ed

+ 7 - 0
.changes/notification_feature.md

@@ -0,0 +1,7 @@
+---
+"tauri.js": minor
+"tauri": minor
+"tauri-api": minor
+---
+
+Adds desktop notifications API.

+ 3 - 1
cli/tauri-bundler/src/bundle/templates/main.wxs

@@ -103,7 +103,9 @@
                     Name="{{{product_name}}}"
                     Description="Runs {{{product_name}}}"
                     Target="[!Path]"
-                    Icon="ProductIcon"/>
+                    Icon="ProductIcon">
+                    <ShortcutProperty Key="System.AppUserModel.ID" Value="{{{manufacturer}}}"/>
+                </Shortcut>
                 <RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/>
                 <RegistryValue Root="HKCU" Key="Software\{{{manufacturer}}}\{{{product_name}}}" Name="installed" Type="integer" Value="1" KeyPath="yes"/>
            </Component>

+ 703 - 591
cli/tauri.js/templates/tauri.js

@@ -51,676 +51,788 @@ switch (navigator.platform) {
     break;
 }
 
+(function () {
+  function s4() {
+    return Math.floor((1 + Math.random()) * 0x10000)
+      .toString(16)
+      .substring(1)
+  }
 
-function s4() {
-  return Math.floor((1 + Math.random()) * 0x10000)
-    .toString(16)
-    .substring(1)
-}
-
-var uid = function () {
-  return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
-    s4() + '-' + s4() + s4() + s4()
-}
-
-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; }
+  var uid = function () {
+    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
+      s4() + '-' + s4() + s4() + s4()
+  }
 
-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; }
+  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; }
 
-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 _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; }
 
+  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); }
 
-/**
- * @typedef {number} BaseDirectory
- */
-/**
- * @enum {BaseDirectory}
- */
-var Dir = {
-  Audio: 1,
-  Cache: 2,
-  Config: 3, 
-  Data: 4,
-  LocalData: 5,
-  Desktop: 6,
-  Document: 7,
-  Download: 8,
-  Executable: 9,
-  Font: 10,
-  Home: 11,
-  Picture: 12,
-  Public: 13,
-  Runtime: 14,
-  Template: 15,
-  Video: 16,
-  Resource: 17,
-  App: 18
-}
+  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) { %>
-function camelToKebab (string) {
-  return string.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase()
-}
-/**
- * @name return __whitelistWarning
- * @description Present a stylish warning to the developer that their API
- * call has not been whitelisted in tauri.conf.json
- * @param {String} func - function name to warn
- * @private
- */
-var __whitelistWarning = function (func) {
-    console.warn('%c[Tauri] Danger \ntauri.' + func + ' not whitelisted 💣\n%c\nAdd to tauri.conf.json: \n\ntauri: \n  whitelist: { \n    ' + camelToKebab(func) + ': true \n\nReference: https://github.com/tauri-apps/tauri/wiki' + func, 'background: red; color: white; font-weight: 800; padding: 2px; font-size:1.5em', ' ')
-    return __reject()
+  /**
+   * @typedef {number} BaseDirectory
+   */
+  /**
+   * @enum {BaseDirectory}
+   */
+  var Dir = {
+    Audio: 1,
+    Cache: 2,
+    Config: 3,
+    Data: 4,
+    LocalData: 5,
+    Desktop: 6,
+    Document: 7,
+    Download: 8,
+    Executable: 9,
+    Font: 10,
+    Home: 11,
+    Picture: 12,
+    Public: 13,
+    Runtime: 14,
+    Template: 15,
+    Video: 16,
+    Resource: 17,
+    App: 18
   }
-    <% } %>
 
-<% if (ctx.dev) { %>
+  <% if (ctx.dev) { %>
+  function camelToKebab (string) {
+    return string.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase()
+  }
   /**
-   * @name __reject
-   * @description generates a 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>}
+   * @name return __whitelistWarning
+   * @description Present a stylish warning to the developer that their API
+   * call has not been whitelisted in tauri.conf.json
+   * @param {String} func - function name to warn
    * @private
    */
-<% } %>
-var __reject = function () {
-  return new Promise(function (_, reject) {
-    reject();
-  });
-}
+  var __whitelistWarning = function (func) {
+      console.warn('%c[Tauri] Danger \ntauri.' + func + ' not whitelisted 💣\n%c\nAdd to tauri.conf.json: \n\ntauri: \n  whitelist: { \n    ' + camelToKebab(func) + ': true \n\nReference: https://github.com/tauri-apps/tauri/wiki' + func, 'background: red; color: white; font-weight: 800; padding: 2px; font-size:1.5em', ' ')
+      return __reject()
+    }
+      <% } %>
 
-window.tauri = {
-  Dir: Dir,
   <% if (ctx.dev) { %>
     /**
-     * @name invoke
-     * @description Calls a Tauri Core feature, such as setTitle
-     * @param {Object} args
+     * @name __reject
+     * @description generates a 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
      */
   <% } %>
-  invoke: function invoke(args) {
-    window.external.invoke(JSON.stringify(args));
-  },
+  var __reject = function () {
+    return new Promise(function (_, reject) {
+      reject();
+    });
+  }
 
-  <% 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) {
-    <% if (tauri.whitelist.event === true || tauri.whitelist.all === true) { %>
-    var once = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
-      this.invoke({
-        cmd: 'listen',
-        event: event,
-        handler: window.tauri.transformCallback(handler, once),
-        once: once
-      });
-    <% } else { %>
-      <% if (ctx.dev) { %>
-          return __whitelistWarning('event')
-          <% } %>
-        return __reject()
-        <% } %>
-  },
+  window.tauri = {
+    Dir: Dir,
+    <% if (ctx.dev) { %>
+      /**
+       * @name invoke
+       * @description Calls a Tauri Core feature, such as setTitle
+       * @param {Object} args
+       */
+    <% } %>
+    invoke: function invoke(args) {
+      window.external.invoke(JSON.stringify(args));
+    },
 
-  <% 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) {
-    <% if (tauri.whitelist.event === true || tauri.whitelist.all === true) { %>
-      this.invoke({
-        cmd: 'emit',
-        event: evt,
-        payload: payload
-      });
-    <% } else { %>
-      <% if (ctx.dev) { %>
-          return __whitelistWarning('event')
+    <% 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) {
+      <% if (tauri.whitelist.event === true || tauri.whitelist.all === true) { %>
+      var once = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
+        this.invoke({
+          cmd: 'listen',
+          event: event,
+          handler: window.tauri.transformCallback(handler, once),
+          once: once
+        });
+      <% } else { %>
+        <% if (ctx.dev) { %>
+            return __whitelistWarning('event')
+            <% } %>
+          return __reject()
           <% } %>
-        return __reject()
-        <% } %>
-  },
+    },
 
-  <% 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 = uid();
+    <% 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) {
+      <% if (tauri.whitelist.event === true || tauri.whitelist.all === true) { %>
+        this.invoke({
+          cmd: 'emit',
+          event: evt,
+          payload: payload
+        });
+      <% } else { %>
+        <% if (ctx.dev) { %>
+            return __whitelistWarning('event')
+            <% } %>
+          return __reject()
+          <% } %>
+    },
 
-    window[identifier] = function (result) {
-      if (once) {
-        delete window[identifier];
-      }
+    <% 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 = uid();
 
-      return callback && callback(result);
-    };
+      window[identifier] = function (result) {
+        if (once) {
+          delete window[identifier];
+        }
 
-    return identifier;
-  },
+        return callback && callback(result);
+      };
 
-  <% if (ctx.dev) { %>
-    /**
-     * @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));
-    });
-  },
+      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
-     * @param {Object} [options]
-     * @param {BaseDirectory} [options.dir]
-     * @returns {*|Promise<any>|Promise}
-     */
-  <% } %>
-  readTextFile: function readTextFile(path, options) {
-    <% if (tauri.whitelist.readTextFile === true || tauri.whitelist.all === true) { %>
-      return this.promisified({
-        cmd: 'readTextFile',
-        path: path,
-        options: options
+    <% if (ctx.dev) { %>
+      /**
+       * @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));
       });
-    <% } else { %>
-      <% if (ctx.dev) { %>
-          return __whitelistWarning('readTextFile')
+    },
+
+    <% 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
+       * @param {Object} [options]
+       * @param {BaseDirectory} [options.dir]
+       * @returns {*|Promise<any>|Promise}
+       */
+    <% } %>
+    readTextFile: function readTextFile(path, options) {
+      <% if (tauri.whitelist.readTextFile === true || tauri.whitelist.all === true) { %>
+        return this.promisified({
+          cmd: 'readTextFile',
+          path: path,
+          options: options
+        });
+      <% } else { %>
+        <% if (ctx.dev) { %>
+            return __whitelistWarning('readTextFile')
+            <% } %>
+          return __reject()
           <% } %>
-        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
-     * @param {Object} [options]
-     * @param {BaseDirectory} [options.dir]
-     * @returns {*|Promise<any>|Promise}
-     */
-  <% } %>
-  readBinaryFile: function readBinaryFile(path, options) {
-    <% if (tauri.whitelist.readBinaryFile === true || tauri.whitelist.all === true) { %>
-      return this.promisified({
-        cmd: 'readBinaryFile',
-        path: path,
-        options: options
-      });
-    <% } else { %>
-      <% if (ctx.dev) { %>
-          return __whitelistWarning('readBinaryFile')
+    <% 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
+       * @param {Object} [options]
+       * @param {BaseDirectory} [options.dir]
+       * @returns {*|Promise<any>|Promise}
+       */
+    <% } %>
+    readBinaryFile: function readBinaryFile(path, options) {
+      <% if (tauri.whitelist.readBinaryFile === true || tauri.whitelist.all === true) { %>
+        return this.promisified({
+          cmd: 'readBinaryFile',
+          path: path,
+          options: options
+        });
+      <% } else { %>
+        <% if (ctx.dev) { %>
+            return __whitelistWarning('readBinaryFile')
+            <% } %>
+          return __reject()
           <% } %>
-        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
-     * @param {Object} [options]
-     * @param {BaseDirectory} [options.dir]
-     */
-  <% } %>
-  writeFile: function writeFile(cfg, options) {
-    <% if (tauri.whitelist.writeFile === true || tauri.whitelist.all === true) { %>
-      if (_typeof(cfg) === 'object') {
-        Object.freeze(cfg);
-      }
-      return this.promisified({
-        cmd: 'writeFile',
-        file: cfg.file,
-        contents: cfg.contents,
-        options: options
-      });
-    <% } else { %>
-      <% if (ctx.dev) { %>
-          return __whitelistWarning('writeFile')
+    <% 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
+       * @param {Object} [options]
+       * @param {BaseDirectory} [options.dir]
+       */
+    <% } %>
+    writeFile: function writeFile(cfg, options) {
+      <% if (tauri.whitelist.writeFile === true || tauri.whitelist.all === true) { %>
+        if (_typeof(cfg) === 'object') {
+          Object.freeze(cfg);
+        }
+        return this.promisified({
+          cmd: 'writeFile',
+          file: cfg.file,
+          contents: cfg.contents,
+          options: options
+        });
+      <% } else { %>
+        <% if (ctx.dev) { %>
+            return __whitelistWarning('writeFile')
+            <% } %>
+          return __reject()
           <% } %>
-        return __reject()
-        <% } %>
-  },
+    },
 
-  <% if (ctx.dev) { %>
-    /**
-     * @name readDir
-     * @description Reads a directory
-     * Permissions based on the app's PID owner
-     * @param {String} path
-     * @param {Object} [options]
-     * @param {Boolean} [options.recursive]
-     * @param {BaseDirectory} [options.dir]
-     * @returns {*|Promise<any>|Promise}
-     */
-  <% } %>
-  readDir: function readDir(path, options) {
-    <% if (tauri.whitelist.readDir === true || tauri.whitelist.all === true) { %>
-      return this.promisified({
-        cmd: 'readDir',
-        path: path,
-        options: options
-      });
-    <% } else { %>
-      <% if (ctx.dev) { %>
-          return __whitelistWarning('readDir')
+    <% if (ctx.dev) { %>
+      /**
+       * @name readDir
+       * @description Reads a directory
+       * Permissions based on the app's PID owner
+       * @param {String} path
+       * @param {Object} [options]
+       * @param {Boolean} [options.recursive]
+       * @param {BaseDirectory} [options.dir]
+       * @returns {*|Promise<any>|Promise}
+       */
+    <% } %>
+    readDir: function readDir(path, options) {
+      <% if (tauri.whitelist.readDir === true || tauri.whitelist.all === true) { %>
+        return this.promisified({
+          cmd: 'readDir',
+          path: path,
+          options: options
+        });
+      <% } else { %>
+        <% if (ctx.dev) { %>
+            return __whitelistWarning('readDir')
+            <% } %>
+          return __reject()
           <% } %>
-        return __reject()
-        <% } %>
-  },
+    },
 
-  <% if (ctx.dev) { %>
-    /**
-     * @name createDir
-     * @description Creates a directory
-     * Permissions based on the app's PID owner
-     * @param {String} path
-     * @param {Object} [options]
-     * @param {Boolean} [options.recursive]
-     * @param {BaseDirectory} [options.dir]
-     * @returns {*|Promise<any>|Promise}
-     */
-  <% } %>
-  createDir: function createDir(path, options) {
-    <% if (tauri.whitelist.createDir === true || tauri.whitelist.all === true) { %>
-      return this.promisified({
-        cmd: 'createDir',
-        path: path,
-        options: options
-      });
-    <% } else { %>
-      <% if (ctx.dev) { %>
-          return __whitelistWarning('createDir')
+    <% if (ctx.dev) { %>
+      /**
+       * @name createDir
+       * @description Creates a directory
+       * Permissions based on the app's PID owner
+       * @param {String} path
+       * @param {Object} [options]
+       * @param {Boolean} [options.recursive]
+       * @param {BaseDirectory} [options.dir]
+       * @returns {*|Promise<any>|Promise}
+       */
+    <% } %>
+    createDir: function createDir(path, options) {
+      <% if (tauri.whitelist.createDir === true || tauri.whitelist.all === true) { %>
+        return this.promisified({
+          cmd: 'createDir',
+          path: path,
+          options: options
+        });
+      <% } else { %>
+        <% if (ctx.dev) { %>
+            return __whitelistWarning('createDir')
+            <% } %>
+          return __reject()
           <% } %>
-        return __reject()
-        <% } %>
-  },
+    },
 
-  <% if (ctx.dev) { %>
-    /**
-     * @name removeDir
-     * @description Removes a directory
-     * Permissions based on the app's PID owner
-     * @param {String} path
-     * @param {Object} [options]
-     * @param {Boolean} [options.recursive]
-     * @param {BaseDirectory} [options.dir]
-     * @returns {*|Promise<any>|Promise}
-     */
-  <% } %>
-  removeDir: function removeDir(path, options) {
-    <% if (tauri.whitelist.removeDir === true || tauri.whitelist.all === true) { %>
-      return this.promisified({
-        cmd: 'removeDir',
-        path: path,
-        options: options
-      });
-    <% } else { %>
-      <% if (ctx.dev) { %>
-          return __whitelistWarning('removeDir')
+    <% if (ctx.dev) { %>
+      /**
+       * @name removeDir
+       * @description Removes a directory
+       * Permissions based on the app's PID owner
+       * @param {String} path
+       * @param {Object} [options]
+       * @param {Boolean} [options.recursive]
+       * @param {BaseDirectory} [options.dir]
+       * @returns {*|Promise<any>|Promise}
+       */
+    <% } %>
+    removeDir: function removeDir(path, options) {
+      <% if (tauri.whitelist.removeDir === true || tauri.whitelist.all === true) { %>
+        return this.promisified({
+          cmd: 'removeDir',
+          path: path,
+          options: options
+        });
+      <% } else { %>
+        <% if (ctx.dev) { %>
+            return __whitelistWarning('removeDir')
+            <% } %>
+          return __reject()
           <% } %>
-        return __reject()
-        <% } %>
-  },
+    },
 
-  <% if (ctx.dev) { %>
-    /**
-     * @name copyFile
-     * @description Copy file
-     * Permissions based on the app's PID owner
-     * @param {String} source
-     * @param {String} destination
-     * @param {Object} [options]
-     * @param {BaseDirectory} [options.dir]
-     * @returns {*|Promise<any>|Promise}
-     */
-  <% } %>
-  copyFile: function copyFile(source, destination, options) {
-    <% if (tauri.whitelist.copyFile === true || tauri.whitelist.all === true) { %>
-      return this.promisified({
-        cmd: 'copyFile',
-        source: source,
-        destination: destination,
-        options: options
-      });
-    <% } else { %>
-      <% if (ctx.dev) { %>
-          return __whitelistWarning('copyFile')
+    <% if (ctx.dev) { %>
+      /**
+       * @name copyFile
+       * @description Copy file
+       * Permissions based on the app's PID owner
+       * @param {String} source
+       * @param {String} destination
+       * @param {Object} [options]
+       * @param {BaseDirectory} [options.dir]
+       * @returns {*|Promise<any>|Promise}
+       */
+    <% } %>
+    copyFile: function copyFile(source, destination, options) {
+      <% if (tauri.whitelist.copyFile === true || tauri.whitelist.all === true) { %>
+        return this.promisified({
+          cmd: 'copyFile',
+          source: source,
+          destination: destination,
+          options: options
+        });
+      <% } else { %>
+        <% if (ctx.dev) { %>
+            return __whitelistWarning('copyFile')
+            <% } %>
+          return __reject()
           <% } %>
-        return __reject()
-        <% } %>
-  },
+    },
 
-  <% if (ctx.dev) { %>
-    /**
-     * @name removeFile
-     * @description Removes a file
-     * Permissions based on the app's PID owner
-     * @param {String} path
-     * @param {Object} [options]
-     * @param {BaseDirectory} [options.dir]
-     * @returns {*|Promise<any>|Promise}
-     */
-  <% } %>
-  removeFile: function removeFile(path, options) {
-    <% if (tauri.whitelist.removeFile === true || tauri.whitelist.all === true) { %>
-      return this.promisified({
-        cmd: 'removeFile',
-        path: path,
-        options: options
-      });
-    <% } else { %>
-      <% if (ctx.dev) { %>
-          return __whitelistWarning('removeFile')
+    <% if (ctx.dev) { %>
+      /**
+       * @name removeFile
+       * @description Removes a file
+       * Permissions based on the app's PID owner
+       * @param {String} path
+       * @param {Object} [options]
+       * @param {BaseDirectory} [options.dir]
+       * @returns {*|Promise<any>|Promise}
+       */
+    <% } %>
+    removeFile: function removeFile(path, options) {
+      <% if (tauri.whitelist.removeFile === true || tauri.whitelist.all === true) { %>
+        return this.promisified({
+          cmd: 'removeFile',
+          path: path,
+          options: options
+        });
+      <% } else { %>
+        <% if (ctx.dev) { %>
+            return __whitelistWarning('removeFile')
+            <% } %>
+          return __reject()
           <% } %>
-        return __reject()
-        <% } %>
-  },
+    },
 
-  <% if (ctx.dev) { %>
-    /**
-     * @name renameFile
-     * @description Renames a file
-     * Permissions based on the app's PID owner
-     * @param {String} path
-     * @param {Object} [options]
-     * @param {BaseDirectory} [options.dir]
-     * @returns {*|Promise<any>|Promise}
-     */
-  <% } %>
-  renameFile: function renameFile(oldPath, newPath, options) {
-    <% if (tauri.whitelist.renameFile === true || tauri.whitelist.all === true) { %>
-      return this.promisified({
-        cmd: 'renameFile',
-        oldPath: oldPath,
-        newPath: newPath,
-        options: options
-      });
-    <% } else { %>
-      <% if (ctx.dev) { %>
-          return __whitelistWarning('renameFile')
+    <% if (ctx.dev) { %>
+      /**
+       * @name renameFile
+       * @description Renames a file
+       * Permissions based on the app's PID owner
+       * @param {String} path
+       * @param {Object} [options]
+       * @param {BaseDirectory} [options.dir]
+       * @returns {*|Promise<any>|Promise}
+       */
+    <% } %>
+    renameFile: function renameFile(oldPath, newPath, options) {
+      <% if (tauri.whitelist.renameFile === true || tauri.whitelist.all === true) { %>
+        return this.promisified({
+          cmd: 'renameFile',
+          oldPath: oldPath,
+          newPath: newPath,
+          options: options
+        });
+      <% } else { %>
+        <% if (ctx.dev) { %>
+            return __whitelistWarning('renameFile')
+            <% } %>
+          return __reject()
           <% } %>
-        return __reject()
-        <% } %>
-  },
+    },
 
-  <% if (ctx.dev) { %>
-    /**
-     * @name setTitle
-     * @description Set the application's title
-     * @param {String} title
-     */
-  <% } %>
-  setTitle: function setTitle(title) {
-    <% if (tauri.whitelist.setTitle === true || tauri.whitelist.all === true) { %>
-      this.invoke({
-        cmd: 'setTitle',
-        title: title
-      });
-    <% } else { %>
-      <% if (ctx.dev) { %>
-    return __whitelistWarning('setTitle')
+    <% if (ctx.dev) { %>
+      /**
+       * @name setTitle
+       * @description Set the application's title
+       * @param {String} title
+       */
+    <% } %>
+    setTitle: function setTitle(title) {
+      <% if (tauri.whitelist.setTitle === true || tauri.whitelist.all === true) { %>
+        this.invoke({
+          cmd: 'setTitle',
+          title: title
+        });
+      <% } else { %>
+        <% if (ctx.dev) { %>
+      return __whitelistWarning('setTitle')
+            <% } %>
+      return __reject()
           <% } %>
-    return __reject()
-        <% } %>
-  },
+    },
 
-  <% if (ctx.dev) { %>
-    /**
-     * @name open
-     * @description Open an URI
-     * @param {String} uri
-     */
-  <% } %>
-  open: function open(uri) {
-    <% if (tauri.whitelist.open === true || tauri.whitelist.all === true) { %>
-      this.invoke({
-        cmd: 'open',
-        uri: uri
-      });
-    <% } else { %>
     <% if (ctx.dev) { %>
-      return __whitelistWarning('open')
+      /**
+       * @name open
+       * @description Open an URI
+       * @param {String} uri
+       */
+    <% } %>
+    open: function open(uri) {
+      <% if (tauri.whitelist.open === true || tauri.whitelist.all === true) { %>
+        this.invoke({
+          cmd: 'open',
+          uri: uri
+        });
+      <% } else { %>
+      <% if (ctx.dev) { %>
+        return __whitelistWarning('open')
+            <% } %>
+      return __reject()
           <% } %>
-    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: function execute(command, args) {
-    <% if (tauri.whitelist.execute === true || tauri.whitelist.all === true) { %>
+    <% 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) { %>
 
-      if (_typeof(args) === 'object') {
-        Object.freeze(args);
-      }
+        if (_typeof(args) === 'object') {
+          Object.freeze(args);
+        }
 
-      return this.promisified({
-        cmd: 'execute',
-        command: command,
-        args: typeof args === 'string' ? [args] : args
-      });
-    <% } else { %>
+        return this.promisified({
+          cmd: 'execute',
+          command: command,
+          args: typeof args === 'string' ? [args] : args
+        });
+      <% } else { %>
+        <% if (ctx.dev) { %>
+          return __whitelistWarning('execute')
+            <% } %>
+          return __reject()
+          <% } %>
+    },
+
+    <% if (ctx.dev) { %>
+      /**
+       * @name openDialog
+       * @description Open a file/directory selection dialog
+       * @param {String} [options]
+       * @param {String} [options.filter]
+       * @param {String} [options.defaultPath]
+       * @param {Boolean} [options.multiple=false]
+       * @param {Boolean} [options.directory=false]
+       * @returns {Promise<String|String[]>} promise resolving to the select path(s)
+       */
+    <% } %>
+    openDialog: function openDialog(options) {
+      <% if (tauri.whitelist.openDialog === true || tauri.whitelist.all === true) { %>
+        var opts = options || {}
+        if (_typeof(options) === 'object') {
+          opts.default_path = opts.defaultPath
+          Object.freeze(options);
+        }
+        return this.promisified({
+          cmd: 'openDialog',
+          options: opts
+        });
+      <% } else { %>
       <% if (ctx.dev) { %>
-        return __whitelistWarning('execute')
+        return __whitelistWarning('openDialog')
+            <% } %>
+      return __reject()
           <% } %>
-        return __reject()
-        <% } %>
-  },
+    },
 
-  <% if (ctx.dev) { %>
-    /**
-     * @name openDialog
-     * @description Open a file/directory selection dialog
-     * @param {String} [options]
-     * @param {String} [options.filter]
-     * @param {String} [options.defaultPath]
-     * @param {Boolean} [options.multiple=false]
-     * @param {Boolean} [options.directory=false]
-     * @returns {Promise<String|String[]>} promise resolving to the select path(s)
-     */
-  <% } %>
-  openDialog: function openDialog(options) {
-    <% if (tauri.whitelist.openDialog === true || tauri.whitelist.all === true) { %>
-      var opts = options || {}
-      if (_typeof(options) === 'object') {
-        opts.default_path = opts.defaultPath
-        Object.freeze(options);
-      }
-      return this.promisified({
-        cmd: 'openDialog',
-        options: opts
-      });
-    <% } else { %>
     <% if (ctx.dev) { %>
-      return __whitelistWarning('openDialog')
+      /**
+       * @name saveDialog
+       * @description Open a file/directory save dialog
+       * @param {String} [options]
+       * @param {String} [options.filter]
+       * @param {String} [options.defaultPath]
+       * @returns {Promise<String>} promise resolving to the select path
+       */
+    <% } %>
+    saveDialog: function saveDialog(options) {
+      <% if (tauri.whitelist.saveDialog === true || tauri.whitelist.all === true) { %>
+        var opts = options || {}
+        if (_typeof(options) === 'object') {
+          opts.default_path = opts.defaultPath
+          Object.freeze(options);
+        }
+        return this.promisified({
+          cmd: 'saveDialog',
+          options: opts
+        });
+      <% } else { %>
+      <% if (ctx.dev) { %>
+        return __whitelistWarning('saveDialog')
+            <% } %>
+      return __reject()
           <% } %>
-    return __reject()
-        <% } %>
-  },
+    },
 
-  <% if (ctx.dev) { %>
-    /**
-     * @name saveDialog
-     * @description Open a file/directory save dialog
-     * @param {String} [options]
-     * @param {String} [options.filter]
-     * @param {String} [options.defaultPath]
-     * @returns {Promise<String>} promise resolving to the select path
-     */
-  <% } %>
-  saveDialog: function saveDialog(options) {
-    <% if (tauri.whitelist.saveDialog === true || tauri.whitelist.all === true) { %>
-      var opts = options || {}
-      if (_typeof(options) === 'object') {
-        opts.default_path = opts.defaultPath
-        Object.freeze(options);
-      }
-      return this.promisified({
-        cmd: 'saveDialog',
-        options: opts
-      });
-    <% } else { %>
     <% if (ctx.dev) { %>
-      return __whitelistWarning('saveDialog')
+      /**
+       * @name httpRequest
+       * @description Makes an HTTP request
+       * @param {Object} options
+       * @param {String} options.method GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT or TRACE
+       * @param {String} options.url the request URL
+       * @param {Object} [options.headers] the request headers
+       * @param {Object} [options.params] the request query params
+       * @param {Object|String|Binary} [options.body] the request body
+       * @param {Boolean} followRedirects whether to follow redirects or not
+       * @param {Number} maxRedirections max number of redirections
+       * @param {Number} connectTimeout request connect timeout
+       * @param {Number} readTimeout request read timeout
+       * @param {Number} timeout request timeout
+       * @param {Boolean} allowCompression
+       * @param {Number} [responseType=1] 1 - JSON, 2 - Text, 3 - Binary
+       * @param {Number} [bodyType=3] 1 - Form, 2 - File, 3 - Auto
+       * @returns {Promise<any>}
+       */
+    <% } %>
+    httpRequest: function httpRequest(options) {
+      <% if (tauri.whitelist.readBinaryFile === true || tauri.whitelist.all === true) { %>
+        return this.promisified({
+          cmd: 'httpRequest',
+          options: options
+        });
+      <% } else { %>
+        <% if (ctx.dev) { %>
+            return __whitelistWarning('httpRequest')
+            <% } %>
+          return __reject()
           <% } %>
-    return __reject()
-        <% } %>
-  },
+    },
+    <% if (ctx.dev) { %>
+      /**
+       * @name notification
+       * @description Display a desktop notification
+       * @param {Object|String} options the notifications options if an object, otherwise its body
+       * @param {String} [options.summary] the notification's summary
+       * @param {String} options.body the notification's body
+       * @param {String} [options.icon] the notifications's icon
+       * @returns {*|Promise<any>|Promise}
+       */
+    <% } %>
+    notification: function notification(options) {
+      <% if (tauri.whitelist.notification === true || tauri.whitelist.all === true) { %>
 
-  <% if (ctx.dev) { %>
-    /**
-     * @name httpRequest
-     * @description Makes an HTTP request
-     * @param {Object} options
-     * @param {String} options.method GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT or TRACE
-     * @param {String} options.url the request URL
-     * @param {Object} [options.headers] the request headers
-     * @param {Object} [options.params] the request query params
-     * @param {Object|String|Binary} [options.body] the request body
-     * @param {Boolean} followRedirects whether to follow redirects or not
-     * @param {Number} maxRedirections max number of redirections
-     * @param {Number} connectTimeout request connect timeout
-     * @param {Number} readTimeout request read timeout
-     * @param {Number} timeout request timeout
-     * @param {Boolean} allowCompression
-     * @param {Number} [responseType=1] 1 - JSON, 2 - Text, 3 - Binary
-     * @param {Number} [bodyType=3] 1 - Form, 2 - File, 3 - Auto
-     * @returns {Promise<any>}
-     */
-  <% } %>
-  httpRequest: function httpRequest(options) {
-    <% if (tauri.whitelist.readBinaryFile === true || tauri.whitelist.all === true) { %>
-      return this.promisified({
-        cmd: 'httpRequest',
-        options: options
-      });
-    <% } else { %>
+        if (_typeof(options) === 'object') {
+          Object.freeze(options);
+        }
+
+        return window.tauri.isNotificationPermissionGranted()
+          .then(function (permission) {
+            if (permission) {
+              return window.tauri.promisified({
+                cmd: 'notification',
+                options: typeof options === 'string' ? {
+                  body: options
+                } : options
+              });
+            }
+          })
+      <% } else { %>
+        <% if (ctx.dev) { %>
+          return __whitelistWarning('notification')
+            <% } %>
+          return __reject()
+          <% } %>
+    },
+
+    isNotificationPermissionGranted: function isNotificationPermissionGranted() {
+      <% if (tauri.whitelist.notification === true || tauri.whitelist.all === true) { %>
+        if (window.Notification.permission !== 'default' && window.Notification.permission !== 'loading') {
+          return Promise.resolve(window.Notification.permission === 'granted')
+        }
+        return window.tauri.promisified({
+          cmd: 'isNotificationPermissionGranted'
+        })
+      <% } else { %>
       <% if (ctx.dev) { %>
-          return __whitelistWarning('httpRequest')
+        return __whitelistWarning('notification')
           <% } %>
         return __reject()
         <% } %>
-  },
-
-  loadAsset: function loadAsset(assetName, assetType) {
-    return this.promisified({
-      cmd: 'loadAsset',
-      asset: assetName,
-      assetType: assetType || 'unknown'
-    })
-  },
+    },
+
+    requestNotificationPermission: function requestNotificationPermission() {
+      <% if (tauri.whitelist.notification === true || tauri.whitelist.all === true) { %>
+        return window.tauri.promisified({
+          cmd: 'requestNotificationPermission'
+        }).then(function (state) {
+          setNotificationPermission(state)
+          return state
+        })
+      <% } else { %>
+      <% if (ctx.dev) { %>
+        return __whitelistWarning('notification')
+          <% } %>
+        return __reject()
+        <% } %>
+    },
 
-  cliMatches: function () {
-    <% if (tauri.cli) { %>
+    loadAsset: function loadAsset(assetName, assetType) {
       return this.promisified({
-        cmd: 'cliMatches'
+        cmd: 'loadAsset',
+        asset: assetName,
+        assetType: assetType || 'unknown'
       })
-    <% } else { %>
-      <% if (ctx.dev) { %>
-        console.error('You must add the CLI args configuration under tauri.conf.json > tauri > cli')
-        return __reject()
-      <% } %>
-        return __reject()
-      <% } %>
-    
-  }
-};
-
-// init tauri API
-try {
-  window.tauri.invoke({
-    cmd: 'init'
-  })
-} catch (e) {
-  window.addEventListener('DOMContentLoaded', function () {
-    window.tauri.invoke({
-      cmd: 'init'
-    })
-  }, true)
-}
+    },
 
-document.addEventListener('error', function (e) {
-  var target = e.target
-  while (target != null) {
-    if (target.matches ? target.matches('img') : target.msMatchesSelector('img')) {
-      window.tauri.loadAsset(target.src, 'image')
-        .then(function (img) {
-          target.src = img
+    cliMatches: function () {
+      <% if (tauri.cli) { %>
+        return this.promisified({
+          cmd: 'cliMatches'
         })
-      break
+      <% } else { %>
+        <% if (ctx.dev) { %>
+          console.error('You must add the CLI args configuration under tauri.conf.json > tauri > cli')
+          return __reject()
+        <% } %>
+          return __reject()
+        <% } %>
+      
     }
-    target = target.parentElement
+  };
+
+  <% if (tauri.whitelist.notification === true || tauri.whitelist.all === true) { %>
+    var notificationPermissionSettable = false
+    var notificationPermission = 'default'
+    function setNotificationPermission(value) {
+      notificationPermissionSettable = true
+      window.Notification.permission = value
+      notificationPermissionSettable = false
+    }
+
+    window.Notification = function (title, options) {
+      if (options === void 0) {
+        options = {}
+      }
+      options.title = title
+      window.tauri.notification(options)
+    }
+    window.Notification.requestPermission = window.tauri.requestNotificationPermission
+
+    Object.defineProperty(window.Notification, 'permission', {
+      enumerable: true,
+      get: function () {
+        return notificationPermission
+      },
+      set: function(v) {
+        if (!notificationPermissionSettable) {
+          throw new Error("Readonly property")
+        }
+        notificationPermission = v
+      }
+    });
+
+    setNotificationPermission('loading')
+    window.tauri.isNotificationPermissionGranted()
+      .then(function (response) {
+        if (response === null) {
+          setNotificationPermission('default')
+        } else {
+          setNotificationPermission(response ? 'granted' : 'denied')
+        }
+      })
+  <% } %>
+
+
+  // init tauri API
+  try {
+    window.tauri.invoke({
+      cmd: 'init'
+    })
+  } catch (e) {
+    window.addEventListener('DOMContentLoaded', function () {
+      window.tauri.invoke({
+        cmd: 'init'
+      })
+    }, true)
   }
-}, true)
 
-// open <a href="..."> links with the Tauri API
-function __openLinks() {
-  document.querySelector('body').addEventListener('click', function (e) {
+  document.addEventListener('error', function (e) {
     var target = e.target
     while (target != null) {
-      if (target.matches ? target.matches('a') : target.msMatchesSelector('a')) {
-        if (target.href && target.href.startsWith('http') && target.target === '_blank') {
-          window.tauri.open(target.href)
-          e.preventDefault()
-        }
+      if (target.matches ? target.matches('img') : target.msMatchesSelector('img')) {
+        window.tauri.loadAsset(target.src, 'image')
+          .then(function (img) {
+            target.src = img
+          })
         break
       }
       target = target.parentElement
     }
   }, true)
-}
 
-if (document.readyState === 'complete' || document.readyState === 'interactive') {
-  __openLinks()
-} else {
-  window.addEventListener('DOMContentLoaded', function () {
+  // open <a href="..."> links with the Tauri API
+  function __openLinks() {
+    document.querySelector('body').addEventListener('click', function (e) {
+      var target = e.target
+      while (target != null) {
+        if (target.matches ? target.matches('a') : target.msMatchesSelector('a')) {
+          if (target.href && target.href.startsWith('http') && target.target === '_blank') {
+            window.tauri.open(target.href)
+            e.preventDefault()
+          }
+          break
+        }
+        target = target.parentElement
+      }
+    }, true)
+  }
+
+  if (document.readyState === 'complete' || document.readyState === 'interactive') {
     __openLinks()
-  }, true)
-}
+  } else {
+    window.addEventListener('DOMContentLoaded', function () {
+      __openLinks()
+    }, true)
+  }
+})()

+ 4 - 1
tauri-api/Cargo.toml

@@ -26,11 +26,13 @@ anyhow = "1.0.31"
 thiserror = "1.0.19"
 rand = "0.7"
 nfd = "0.0.4"
+tauri-dialog = { git = "https://github.com/tauri-apps/tauri-dialog-rs", version = "0.1" }
 attohttpc = {version = "0.14.0", features=["json", "form" ]}
 http = "0.2"
 tauri-utils = {version = "0.5", path = "../tauri-utils"}
 envmnt = "0.8.2"
 clap = { git = "https://github.com/clap-rs/clap", rev = "1a276f8", version = "3.0.0-beta.1", optional = true }
+notify-rust = { version = "4.0.0-beta.4", optional = true }
 
 [dev-dependencies]
 quickcheck = "0.9.2"
@@ -38,4 +40,5 @@ quickcheck_macros = "0.9.1"
 totems = "0.2.7"
 
 [features]
-cli = ["clap"]
+cli = ["clap"]
+notification = ["notify-rust"]

+ 24 - 0
tauri-api/src/config.rs

@@ -159,6 +159,18 @@ macro_rules! impl_cli {
   }
 }
 
+#[derive(PartialEq, Deserialize, Clone, Debug)]
+#[serde(tag = "bundle", rename_all = "camelCase")]
+pub struct BundleConfig {
+  pub identifier: String,
+}
+
+fn default_bundle() -> BundleConfig {
+  BundleConfig {
+    identifier: String::from(""),
+  }
+}
+
 impl_cli!(CliSubcommand, CliConfig);
 
 #[derive(PartialEq, Deserialize, Clone, Debug)]
@@ -170,6 +182,8 @@ pub struct TauriConfig {
   pub embedded_server: EmbeddedServerConfig,
   #[serde(default)]
   pub cli: Option<CliConfig>,
+  #[serde(default = "default_bundle")]
+  pub bundle: BundleConfig,
 }
 
 #[derive(PartialEq, Deserialize, Clone, Debug)]
@@ -197,6 +211,7 @@ fn default_tauri() -> TauriConfig {
     window: default_window(),
     embedded_server: default_embedded_server(),
     cli: None,
+    bundle: default_bundle(),
   }
 }
 
@@ -254,6 +269,9 @@ mod test {
           host: String::from("http://127.0.0.1"),
           port: String::from("random"),
         },
+        bundle: BundleConfig {
+          identifier: String::from("com.tauri.communication"),
+        },
         cli: Some(CliConfig {
           description: Some("Tauri communication example".to_string()),
           long_description: None,
@@ -331,6 +349,8 @@ mod test {
     let d_window = default_window();
     // get default title
     let d_title = default_title();
+    // get default bundle
+    let d_bundle = default_bundle();
 
     // create a tauri config.
     let tauri = TauriConfig {
@@ -345,6 +365,9 @@ mod test {
         host: String::from("http://127.0.0.1"),
         port: String::from("random"),
       },
+      bundle: BundleConfig {
+        identifier: String::from(""),
+      },
       cli: None,
     };
 
@@ -357,6 +380,7 @@ mod test {
     assert_eq!(t_config, tauri);
     assert_eq!(b_config, build);
     assert_eq!(de_server, tauri.embedded_server);
+    assert_eq!(d_bundle, tauri.bundle);
     assert_eq!(d_path, String::from(""));
     assert_eq!(d_title, tauri.window.title);
     assert_eq!(d_window, tauri.window);

+ 13 - 0
tauri-api/src/dialog.rs

@@ -1,5 +1,7 @@
 pub use nfd::Response;
 use nfd::{open_dialog, DialogType};
+pub use tauri_dialog::DialogSelection;
+use tauri_dialog::{DialogBuilder, DialogButtons, DialogStyle};
 
 fn open_dialog_internal(
   dialog_type: DialogType,
@@ -13,6 +15,17 @@ fn open_dialog_internal(
   }
 }
 
+/// Displays a dialog with a message and an optional title with a "yes" and a "no" button.
+pub fn ask<'a>(message: &'a str, title: &'a str) -> DialogSelection {
+  DialogBuilder::new()
+    .message(message)
+    .title(title)
+    .style(DialogStyle::Question)
+    .buttons(DialogButtons::YesNo)
+    .build()
+    .show()
+}
+
 /// Open single select file dialog
 pub fn select(
   filter_list: Option<String>,

+ 3 - 0
tauri-api/src/lib.rs

@@ -20,6 +20,9 @@ pub mod cli;
 #[macro_use]
 extern crate clap;
 
+#[cfg(feature = "notification")]
+pub mod notification;
+
 pub use tauri_utils::*;
 
 pub use anyhow::Result;

+ 67 - 0
tauri-api/src/notification.rs

@@ -0,0 +1,67 @@
+use crate::config::Config;
+#[cfg(windows)]
+use std::path::MAIN_SEPARATOR;
+
+#[allow(dead_code)]
+pub struct Notification {
+  body: Option<String>,
+  title: Option<String>,
+  icon: Option<String>,
+  config: Config,
+}
+
+impl Notification {
+  pub fn new(config: Config) -> Self {
+    Self {
+      body: None,
+      title: None,
+      icon: None,
+      config,
+    }
+  }
+
+  pub fn body(&mut self, body: String) -> &mut Self {
+    self.body = Some(body);
+    self
+  }
+
+  pub fn title(&mut self, title: String) -> &mut Self {
+    self.title = Some(title);
+    self
+  }
+
+  pub fn icon(&mut self, icon: String) -> &mut Self {
+    self.icon = Some(icon);
+    self
+  }
+
+  pub fn show(self) -> crate::Result<()> {
+    let mut notification = notify_rust::Notification::new();
+    if let Some(body) = self.body {
+      notification.body(&body);
+    }
+    if let Some(title) = self.title {
+      notification.summary(&title);
+    }
+    if let Some(icon) = self.icon {
+      notification.icon(&icon);
+    }
+    #[cfg(windowss)]
+    {
+      let exe = std::env::current_exe()?;
+      let exe_dir = exe.parent().expect("failed to get exe directory");
+      let curr_dir = exe_dir.display().to_string();
+      // set the notification's System.AppUserModel.ID only when running the installed app
+      if !(curr_dir.ends_with(format!("{S}target{S}debug", S = MAIN_SEPARATOR).as_str())
+        || curr_dir.ends_with(format!("{S}target{S}release", S = MAIN_SEPARATOR).as_str()))
+      {
+        let identifier = self.config.tauri.bundle.identifier.clone();
+        notification.id(&identifier);
+      }
+    }
+    notification
+      .show()
+      .map(|_| ())
+      .map_err(|e| anyhow::anyhow!(e.to_string()))
+  }
+}

+ 2 - 1
tauri/Cargo.toml

@@ -43,7 +43,7 @@ cli = ["tauri-api/cli"]
 edge = ["web-view/edge"]
 embedded-server = ["tiny_http"]
 no-server = []
-all-api = []
+all-api = ["tauri-api/notification"]
 read-text-file = []
 read-binary-file = []
 write-file = []
@@ -60,6 +60,7 @@ event = []
 updater = []
 open-dialog = []
 save-dialog = []
+notification = ["tauri-api/notification"]
 
 [package.metadata.docs.rs]
 features = ["all-api"]

+ 2 - 0
tauri/examples/communication/dist/index.html

@@ -187,6 +187,7 @@
           <button class="button" id="log">Call Log API</button>
           <button class="button" id="request">Call Request (async) API</button>
           <button class="button" id="event">Send event to Rust</button>
+          <button class="button" id="notification">Send test notification</button>
 
           <div style="margin-top: 24px">
             <input id="title" value="Awesome Tauri Example!">
@@ -307,6 +308,7 @@
   <script src="dialog.js"></script>
   <script src="http.js"></script>
   <script src="cli.js"></script>
+  <script src="notification.js"></script>
 </body>
 
 </html>

File diff suppressed because it is too large
+ 625 - 528
tauri/examples/communication/dist/index.tauri.html


+ 21 - 0
tauri/examples/communication/dist/notification.js

@@ -0,0 +1,21 @@
+function sendNotification() {
+  new Notification('Notification title', {
+    body: 'This is the notification body'
+  })
+}
+
+document.getElementById('notification').addEventListener('click', function () {
+  if (Notification.permission === 'default') {
+    Notification.requestPermission().then(function (response) {
+      if (response === 'granted') {
+        sendNotification()
+      } else {
+        registerResponse('Permission is ' + response)
+      }
+    }).catch(registerResponse)
+  } else if (Notification.permission === 'granted') {
+    sendNotification()
+  } else {
+    registerResponse('Permission is denied')
+  }
+})

+ 1 - 1
tauri/examples/communication/src-tauri/tauri.conf.json

@@ -47,7 +47,7 @@
     },
     "bundle": {
       "active": true,
-      "identifier": "com.example.app",
+      "identifier": "com.tauri.communication",
       "icon": [
         "icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"
       ]

+ 625 - 528
tauri/examples/communication/src-tauri/tauri.js

@@ -51,585 +51,682 @@ switch (navigator.platform) {
     break;
 }
 
+(function () {
+  function s4() {
+    return Math.floor((1 + Math.random()) * 0x10000)
+      .toString(16)
+      .substring(1)
+  }
 
-function s4() {
-  return Math.floor((1 + Math.random()) * 0x10000)
-    .toString(16)
-    .substring(1)
-}
-
-var uid = function () {
-  return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
-    s4() + '-' + s4() + s4() + s4()
-}
-
-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; }
-
-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; }
+  var uid = function () {
+    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
+      s4() + '-' + s4() + s4() + s4()
+  }
 
-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 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; }
 
+  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; }
 
-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); }
+  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; }
 
-/**
- * @typedef {number} BaseDirectory
- */
-/**
- * @enum {BaseDirectory}
- */
-var Dir = {
-  Audio: 1,
-  Cache: 2,
-  Config: 3, 
-  Data: 4,
-  LocalData: 5,
-  Desktop: 6,
-  Document: 7,
-  Download: 8,
-  Executable: 9,
-  Font: 10,
-  Home: 11,
-  Picture: 12,
-  Public: 13,
-  Runtime: 14,
-  Template: 15,
-  Video: 16,
-  Resource: 17,
-  App: 18
-}
 
+  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); }
 
-function camelToKebab (string) {
-  return string.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase()
-}
-/**
- * @name return __whitelistWarning
- * @description Present a stylish warning to the developer that their API
- * call has not been whitelisted in tauri.conf.json
- * @param {String} func - function name to warn
- * @private
- */
-var __whitelistWarning = function (func) {
-    console.warn('%c[Tauri] Danger \ntauri.' + func + ' not whitelisted 💣\n%c\nAdd to tauri.conf.json: \n\ntauri: \n  whitelist: { \n    ' + camelToKebab(func) + ': true \n\nReference: https://github.com/tauri-apps/tauri/wiki' + func, 'background: red; color: white; font-weight: 800; padding: 2px; font-size:1.5em', ' ')
-    return __reject()
+  /**
+   * @typedef {number} BaseDirectory
+   */
+  /**
+   * @enum {BaseDirectory}
+   */
+  var Dir = {
+    Audio: 1,
+    Cache: 2,
+    Config: 3, 
+    Data: 4,
+    LocalData: 5,
+    Desktop: 6,
+    Document: 7,
+    Download: 8,
+    Executable: 9,
+    Font: 10,
+    Home: 11,
+    Picture: 12,
+    Public: 13,
+    Runtime: 14,
+    Template: 15,
+    Video: 16,
+    Resource: 17,
+    App: 18
   }
-    
-
 
+  
+  function camelToKebab (string) {
+    return string.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase()
+  }
   /**
-   * @name __reject
-   * @description generates a 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>}
+   * @name return __whitelistWarning
+   * @description Present a stylish warning to the developer that their API
+   * call has not been whitelisted in tauri.conf.json
+   * @param {String} func - function name to warn
    * @private
    */
-
-var __reject = function () {
-  return new Promise(function (_, reject) {
-    reject();
-  });
-}
-
-window.tauri = {
-  Dir: Dir,
-  
-    /**
-     * @name invoke
-     * @description Calls a Tauri Core feature, such as setTitle
-     * @param {Object} args
-     */
-  
-  invoke: function invoke(args) {
-    window.external.invoke(JSON.stringify(args));
-  },
-
-  
-    /**
-     * @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: 'listen',
-        event: event,
-        handler: window.tauri.transformCallback(handler, once),
-        once: once
-      });
-    
-  },
-
-  
-    /**
-     * @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
-      });
-    
-  },
-
-  
-    /**
-     * @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 = uid();
-
-    window[identifier] = function (result) {
-      if (once) {
-        delete window[identifier];
-      }
-
-      return callback && callback(result);
-    };
-
-    return identifier;
-  },
+  var __whitelistWarning = function (func) {
+      console.warn('%c[Tauri] Danger \ntauri.' + func + ' not whitelisted 💣\n%c\nAdd to tauri.conf.json: \n\ntauri: \n  whitelist: { \n    ' + camelToKebab(func) + ': true \n\nReference: https://github.com/tauri-apps/tauri/wiki' + func, 'background: red; color: white; font-weight: 800; padding: 2px; font-size:1.5em', ' ')
+      return __reject()
+    }
+      
 
   
     /**
-     * @name promisified
-     * @description Turns a request into a chainable promise
-     * @param {Object} args
-     * @returns {Promise<any>}
+     * @name __reject
+     * @description generates a 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
      */
   
-  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));
+  var __reject = function () {
+    return new Promise(function (_, reject) {
+      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
-     * @param {Object} [options]
-     * @param {BaseDirectory} [options.dir]
-     * @returns {*|Promise<any>|Promise}
-     */
-  
-  readTextFile: function readTextFile(path, options) {
-    
-      return this.promisified({
-        cmd: 'readTextFile',
-        path: path,
-        options: options
-      });
-    
-  },
+  }
 
-  
-    /**
-     * @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
-     * @param {Object} [options]
-     * @param {BaseDirectory} [options.dir]
-     * @returns {*|Promise<any>|Promise}
-     */
-  
-  readBinaryFile: function readBinaryFile(path, options) {
-    
-      return this.promisified({
-        cmd: 'readBinaryFile',
-        path: path,
-        options: options
-      });
-    
-  },
+  window.tauri = {
+    Dir: Dir,
+    
+      /**
+       * @name invoke
+       * @description Calls a Tauri Core feature, such as setTitle
+       * @param {Object} args
+       */
+    
+    invoke: function invoke(args) {
+      window.external.invoke(JSON.stringify(args));
+    },
+
+    
+      /**
+       * @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: 'listen',
+          event: event,
+          handler: window.tauri.transformCallback(handler, once),
+          once: once
+        });
+      
+    },
+
+    
+      /**
+       * @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
+        });
+      
+    },
+
+    
+      /**
+       * @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 = uid();
+
+      window[identifier] = function (result) {
+        if (once) {
+          delete window[identifier];
+        }
 
-  
-    /**
-     * @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
-     * @param {Object} [options]
-     * @param {BaseDirectory} [options.dir]
-     */
-  
-  writeFile: function writeFile(cfg, options) {
-    
-      if (_typeof(cfg) === 'object') {
-        Object.freeze(cfg);
-      }
-      return this.promisified({
-        cmd: 'writeFile',
-        file: cfg.file,
-        contents: cfg.contents,
-        options: options
-      });
-    
-  },
+        return callback && callback(result);
+      };
 
-  
-    /**
-     * @name readDir
-     * @description Reads a directory
-     * Permissions based on the app's PID owner
-     * @param {String} path
-     * @param {Object} [options]
-     * @param {Boolean} [options.recursive]
-     * @param {BaseDirectory} [options.dir]
-     * @returns {*|Promise<any>|Promise}
-     */
-  
-  readDir: function readDir(path, options) {
-    
-      return this.promisified({
-        cmd: 'readDir',
-        path: path,
-        options: options
-      });
-    
-  },
+      return identifier;
+    },
 
-  
-    /**
-     * @name createDir
-     * @description Creates a directory
-     * Permissions based on the app's PID owner
-     * @param {String} path
-     * @param {Object} [options]
-     * @param {Boolean} [options.recursive]
-     * @param {BaseDirectory} [options.dir]
-     * @returns {*|Promise<any>|Promise}
-     */
-  
-  createDir: function createDir(path, options) {
     
-      return this.promisified({
-        cmd: 'createDir',
-        path: path,
-        options: options
-      });
+      /**
+       * @name promisified
+       * @description Turns a request into a chainable promise
+       * @param {Object} args
+       * @returns {Promise<any>}
+       */
     
-  },
+    promisified: function promisified(args) {
+      var _this = this;
 
-  
-    /**
-     * @name removeDir
-     * @description Removes a directory
-     * Permissions based on the app's PID owner
-     * @param {String} path
-     * @param {Object} [options]
-     * @param {Boolean} [options.recursive]
-     * @param {BaseDirectory} [options.dir]
-     * @returns {*|Promise<any>|Promise}
-     */
-  
-  removeDir: function removeDir(path, options) {
-    
-      return this.promisified({
-        cmd: 'removeDir',
-        path: path,
-        options: options
+      return new Promise(function (resolve, reject) {
+        _this.invoke(_objectSpread({
+          callback: _this.transformCallback(resolve),
+          error: _this.transformCallback(reject)
+        }, args));
       });
-    
-  },
+    },
+
+    
+      /**
+       * @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
+       * @param {Object} [options]
+       * @param {BaseDirectory} [options.dir]
+       * @returns {*|Promise<any>|Promise}
+       */
+    
+    readTextFile: function readTextFile(path, options) {
+      
+        return this.promisified({
+          cmd: 'readTextFile',
+          path: path,
+          options: options
+        });
+      
+    },
+
+    
+      /**
+       * @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
+       * @param {Object} [options]
+       * @param {BaseDirectory} [options.dir]
+       * @returns {*|Promise<any>|Promise}
+       */
+    
+    readBinaryFile: function readBinaryFile(path, options) {
+      
+        return this.promisified({
+          cmd: 'readBinaryFile',
+          path: path,
+          options: options
+        });
+      
+    },
+
+    
+      /**
+       * @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
+       * @param {Object} [options]
+       * @param {BaseDirectory} [options.dir]
+       */
+    
+    writeFile: function writeFile(cfg, options) {
+      
+        if (_typeof(cfg) === 'object') {
+          Object.freeze(cfg);
+        }
+        return this.promisified({
+          cmd: 'writeFile',
+          file: cfg.file,
+          contents: cfg.contents,
+          options: options
+        });
+      
+    },
+
+    
+      /**
+       * @name readDir
+       * @description Reads a directory
+       * Permissions based on the app's PID owner
+       * @param {String} path
+       * @param {Object} [options]
+       * @param {Boolean} [options.recursive]
+       * @param {BaseDirectory} [options.dir]
+       * @returns {*|Promise<any>|Promise}
+       */
+    
+    readDir: function readDir(path, options) {
+      
+        return this.promisified({
+          cmd: 'readDir',
+          path: path,
+          options: options
+        });
+      
+    },
+
+    
+      /**
+       * @name createDir
+       * @description Creates a directory
+       * Permissions based on the app's PID owner
+       * @param {String} path
+       * @param {Object} [options]
+       * @param {Boolean} [options.recursive]
+       * @param {BaseDirectory} [options.dir]
+       * @returns {*|Promise<any>|Promise}
+       */
+    
+    createDir: function createDir(path, options) {
+      
+        return this.promisified({
+          cmd: 'createDir',
+          path: path,
+          options: options
+        });
+      
+    },
+
+    
+      /**
+       * @name removeDir
+       * @description Removes a directory
+       * Permissions based on the app's PID owner
+       * @param {String} path
+       * @param {Object} [options]
+       * @param {Boolean} [options.recursive]
+       * @param {BaseDirectory} [options.dir]
+       * @returns {*|Promise<any>|Promise}
+       */
+    
+    removeDir: function removeDir(path, options) {
+      
+        return this.promisified({
+          cmd: 'removeDir',
+          path: path,
+          options: options
+        });
+      
+    },
+
+    
+      /**
+       * @name copyFile
+       * @description Copy file
+       * Permissions based on the app's PID owner
+       * @param {String} source
+       * @param {String} destination
+       * @param {Object} [options]
+       * @param {BaseDirectory} [options.dir]
+       * @returns {*|Promise<any>|Promise}
+       */
+    
+    copyFile: function copyFile(source, destination, options) {
+      
+        return this.promisified({
+          cmd: 'copyFile',
+          source: source,
+          destination: destination,
+          options: options
+        });
+      
+    },
+
+    
+      /**
+       * @name removeFile
+       * @description Removes a file
+       * Permissions based on the app's PID owner
+       * @param {String} path
+       * @param {Object} [options]
+       * @param {BaseDirectory} [options.dir]
+       * @returns {*|Promise<any>|Promise}
+       */
+    
+    removeFile: function removeFile(path, options) {
+      
+        return this.promisified({
+          cmd: 'removeFile',
+          path: path,
+          options: options
+        });
+      
+    },
+
+    
+      /**
+       * @name renameFile
+       * @description Renames a file
+       * Permissions based on the app's PID owner
+       * @param {String} path
+       * @param {Object} [options]
+       * @param {BaseDirectory} [options.dir]
+       * @returns {*|Promise<any>|Promise}
+       */
+    
+    renameFile: function renameFile(oldPath, newPath, options) {
+      
+        return this.promisified({
+          cmd: 'renameFile',
+          oldPath: oldPath,
+          newPath: newPath,
+          options: options
+        });
+      
+    },
+
+    
+      /**
+       * @name setTitle
+       * @description Set the application's title
+       * @param {String} title
+       */
+    
+    setTitle: function setTitle(title) {
+      
+        this.invoke({
+          cmd: 'setTitle',
+          title: title
+        });
+      
+    },
+
+    
+      /**
+       * @name open
+       * @description Open an URI
+       * @param {String} uri
+       */
+    
+    open: function open(uri) {
+      
+        this.invoke({
+          cmd: 'open',
+          uri: uri
+        });
+      
+    },
+
+    
+      /**
+       * @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 (_typeof(args) === 'object') {
+          Object.freeze(args);
+        }
 
-  
-    /**
-     * @name copyFile
-     * @description Copy file
-     * Permissions based on the app's PID owner
-     * @param {String} source
-     * @param {String} destination
-     * @param {Object} [options]
-     * @param {BaseDirectory} [options.dir]
-     * @returns {*|Promise<any>|Promise}
-     */
-  
-  copyFile: function copyFile(source, destination, options) {
-    
-      return this.promisified({
-        cmd: 'copyFile',
-        source: source,
-        destination: destination,
-        options: options
-      });
-    
-  },
+        return this.promisified({
+          cmd: 'execute',
+          command: command,
+          args: typeof args === 'string' ? [args] : args
+        });
+      
+    },
+
+    
+      /**
+       * @name openDialog
+       * @description Open a file/directory selection dialog
+       * @param {String} [options]
+       * @param {String} [options.filter]
+       * @param {String} [options.defaultPath]
+       * @param {Boolean} [options.multiple=false]
+       * @param {Boolean} [options.directory=false]
+       * @returns {Promise<String|String[]>} promise resolving to the select path(s)
+       */
+    
+    openDialog: function openDialog(options) {
+      
+        var opts = options || {}
+        if (_typeof(options) === 'object') {
+          opts.default_path = opts.defaultPath
+          Object.freeze(options);
+        }
+        return this.promisified({
+          cmd: 'openDialog',
+          options: opts
+        });
+      
+    },
+
+    
+      /**
+       * @name saveDialog
+       * @description Open a file/directory save dialog
+       * @param {String} [options]
+       * @param {String} [options.filter]
+       * @param {String} [options.defaultPath]
+       * @returns {Promise<String>} promise resolving to the select path
+       */
+    
+    saveDialog: function saveDialog(options) {
+      
+        var opts = options || {}
+        if (_typeof(options) === 'object') {
+          opts.default_path = opts.defaultPath
+          Object.freeze(options);
+        }
+        return this.promisified({
+          cmd: 'saveDialog',
+          options: opts
+        });
+      
+    },
+
+    
+      /**
+       * @name httpRequest
+       * @description Makes an HTTP request
+       * @param {Object} options
+       * @param {String} options.method GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT or TRACE
+       * @param {String} options.url the request URL
+       * @param {Object} [options.headers] the request headers
+       * @param {Object} [options.params] the request query params
+       * @param {Object|String|Binary} [options.body] the request body
+       * @param {Boolean} followRedirects whether to follow redirects or not
+       * @param {Number} maxRedirections max number of redirections
+       * @param {Number} connectTimeout request connect timeout
+       * @param {Number} readTimeout request read timeout
+       * @param {Number} timeout request timeout
+       * @param {Boolean} allowCompression
+       * @param {Number} [responseType=1] 1 - JSON, 2 - Text, 3 - Binary
+       * @param {Number} [bodyType=3] 1 - Form, 2 - File, 3 - Auto
+       * @returns {Promise<any>}
+       */
+    
+    httpRequest: function httpRequest(options) {
+      
+        return this.promisified({
+          cmd: 'httpRequest',
+          options: options
+        });
+      
+    },
+    
+      /**
+       * @name notification
+       * @description Display a desktop notification
+       * @param {Object|String} options the notifications options if an object, otherwise its body
+       * @param {String} [options.summary] the notification's summary
+       * @param {String} options.body the notification's body
+       * @param {String} [options.icon] the notifications's icon
+       * @returns {*|Promise<any>|Promise}
+       */
+    
+    notification: function notification(options) {
+      
+
+        if (_typeof(options) === 'object') {
+          Object.freeze(options);
+        }
 
-  
-    /**
-     * @name removeFile
-     * @description Removes a file
-     * Permissions based on the app's PID owner
-     * @param {String} path
-     * @param {Object} [options]
-     * @param {BaseDirectory} [options.dir]
-     * @returns {*|Promise<any>|Promise}
-     */
-  
-  removeFile: function removeFile(path, options) {
-    
-      return this.promisified({
-        cmd: 'removeFile',
-        path: path,
-        options: options
-      });
-    
-  },
+        return window.tauri.isNotificationPermissionGranted()
+          .then(function (permission) {
+            if (permission) {
+              return window.tauri.promisified({
+                cmd: 'notification',
+                options: typeof options === 'string' ? {
+                  body: options
+                } : options
+              });
+            }
+          })
+      
+    },
+
+    isNotificationPermissionGranted: function isNotificationPermissionGranted() {
+      
+        if (window.Notification.permission !== 'default' && window.Notification.permission !== 'loading') {
+          return Promise.resolve(window.Notification.permission === 'granted')
+        }
+        return window.tauri.promisified({
+          cmd: 'isNotificationPermissionGranted'
+        })
+      
+    },
+
+    requestNotificationPermission: function requestNotificationPermission() {
+      
+        return window.tauri.promisified({
+          cmd: 'requestNotificationPermission'
+        }).then(function (state) {
+          setNotificationPermission(state)
+          return state
+        })
+      
+    },
 
-  
-    /**
-     * @name renameFile
-     * @description Renames a file
-     * Permissions based on the app's PID owner
-     * @param {String} path
-     * @param {Object} [options]
-     * @param {BaseDirectory} [options.dir]
-     * @returns {*|Promise<any>|Promise}
-     */
-  
-  renameFile: function renameFile(oldPath, newPath, options) {
-    
+    loadAsset: function loadAsset(assetName, assetType) {
       return this.promisified({
-        cmd: 'renameFile',
-        oldPath: oldPath,
-        newPath: newPath,
-        options: options
-      });
-    
-  },
-
-  
-    /**
-     * @name setTitle
-     * @description Set the application's title
-     * @param {String} title
-     */
-  
-  setTitle: function setTitle(title) {
-    
-      this.invoke({
-        cmd: 'setTitle',
-        title: title
-      });
-    
-  },
+        cmd: 'loadAsset',
+        asset: assetName,
+        assetType: assetType || 'unknown'
+      })
+    },
 
-  
-    /**
-     * @name open
-     * @description Open an URI
-     * @param {String} uri
-     */
-  
-  open: function open(uri) {
-    
-      this.invoke({
-        cmd: 'open',
-        uri: uri
-      });
-    
-  },
+    cliMatches: function () {
+      
+        return this.promisified({
+          cmd: 'cliMatches'
+        })
+      
+      
+    }
+  };
 
   
-    /**
-     * @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 (_typeof(args) === 'object') {
-        Object.freeze(args);
-      }
-
-      return this.promisified({
-        cmd: 'execute',
-        command: command,
-        args: typeof args === 'string' ? [args] : args
-      });
-    
-  },
+    var notificationPermissionSettable = false
+    var notificationPermission = 'default'
+    function setNotificationPermission(value) {
+      notificationPermissionSettable = true
+      window.Notification.permission = value
+      notificationPermissionSettable = false
+    }
 
-  
-    /**
-     * @name openDialog
-     * @description Open a file/directory selection dialog
-     * @param {String} [options]
-     * @param {String} [options.filter]
-     * @param {String} [options.defaultPath]
-     * @param {Boolean} [options.multiple=false]
-     * @param {Boolean} [options.directory=false]
-     * @returns {Promise<String|String[]>} promise resolving to the select path(s)
-     */
-  
-  openDialog: function openDialog(options) {
-    
-      var opts = options || {}
-      if (_typeof(options) === 'object') {
-        opts.default_path = opts.defaultPath
-        Object.freeze(options);
+    window.Notification = function (title, options) {
+      if (options === void 0) {
+        options = {}
       }
-      return this.promisified({
-        cmd: 'openDialog',
-        options: opts
-      });
-    
-  },
-
-  
-    /**
-     * @name saveDialog
-     * @description Open a file/directory save dialog
-     * @param {String} [options]
-     * @param {String} [options.filter]
-     * @param {String} [options.defaultPath]
-     * @returns {Promise<String>} promise resolving to the select path
-     */
-  
-  saveDialog: function saveDialog(options) {
-    
-      var opts = options || {}
-      if (_typeof(options) === 'object') {
-        opts.default_path = opts.defaultPath
-        Object.freeze(options);
+      options.title = title
+      window.tauri.notification(options)
+    }
+    window.Notification.requestPermission = window.tauri.requestNotificationPermission
+
+    Object.defineProperty(window.Notification, 'permission', {
+      enumerable: true,
+      get: function () {
+        return notificationPermission
+      },
+      set: function(v) {
+        if (!notificationPermissionSettable) {
+          throw new Error("Readonly property")
+        }
+        notificationPermission = v
       }
-      return this.promisified({
-        cmd: 'saveDialog',
-        options: opts
-      });
-    
-  },
+    });
 
+    setNotificationPermission('loading')
+    window.tauri.isNotificationPermissionGranted()
+      .then(function (response) {
+        if (response === null) {
+          setNotificationPermission('default')
+        } else {
+          setNotificationPermission(response ? 'granted' : 'denied')
+        }
+      })
   
-    /**
-     * @name httpRequest
-     * @description Makes an HTTP request
-     * @param {Object} options
-     * @param {String} options.method GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT or TRACE
-     * @param {String} options.url the request URL
-     * @param {Object} [options.headers] the request headers
-     * @param {Object} [options.params] the request query params
-     * @param {Object|String|Binary} [options.body] the request body
-     * @param {Boolean} followRedirects whether to follow redirects or not
-     * @param {Number} maxRedirections max number of redirections
-     * @param {Number} connectTimeout request connect timeout
-     * @param {Number} readTimeout request read timeout
-     * @param {Number} timeout request timeout
-     * @param {Boolean} allowCompression
-     * @param {Number} [responseType=1] 1 - JSON, 2 - Text, 3 - Binary
-     * @param {Number} [bodyType=3] 1 - Form, 2 - File, 3 - Auto
-     * @returns {Promise<any>}
-     */
-  
-  httpRequest: function httpRequest(options) {
-    
-      return this.promisified({
-        cmd: 'httpRequest',
-        options: options
-      });
-    
-  },
 
-  loadAsset: function loadAsset(assetName, assetType) {
-    return this.promisified({
-      cmd: 'loadAsset',
-      asset: assetName,
-      assetType: assetType || 'unknown'
-    })
-  },
 
-  cliMatches: function () {
-    
-      return this.promisified({
-        cmd: 'cliMatches'
-      })
-    
-    
-  }
-};
-
-// init tauri API
-try {
-  window.tauri.invoke({
-    cmd: 'init'
-  })
-} catch (e) {
-  window.addEventListener('DOMContentLoaded', function () {
+  // init tauri API
+  try {
     window.tauri.invoke({
       cmd: 'init'
     })
-  }, true)
-}
-
-document.addEventListener('error', function (e) {
-  var target = e.target
-  while (target != null) {
-    if (target.matches ? target.matches('img') : target.msMatchesSelector('img')) {
-      window.tauri.loadAsset(target.src, 'image')
-        .then(function (img) {
-          target.src = img
-        })
-      break
-    }
-    target = target.parentElement
+  } catch (e) {
+    window.addEventListener('DOMContentLoaded', function () {
+      window.tauri.invoke({
+        cmd: 'init'
+      })
+    }, true)
   }
-}, true)
 
-// open <a href="..."> links with the Tauri API
-function __openLinks() {
-  document.querySelector('body').addEventListener('click', function (e) {
+  document.addEventListener('error', function (e) {
     var target = e.target
     while (target != null) {
-      if (target.matches ? target.matches('a') : target.msMatchesSelector('a')) {
-        if (target.href && target.href.startsWith('http') && target.target === '_blank') {
-          window.tauri.open(target.href)
-          e.preventDefault()
-        }
+      if (target.matches ? target.matches('img') : target.msMatchesSelector('img')) {
+        window.tauri.loadAsset(target.src, 'image')
+          .then(function (img) {
+            target.src = img
+          })
         break
       }
       target = target.parentElement
     }
   }, true)
-}
 
-if (document.readyState === 'complete' || document.readyState === 'interactive') {
-  __openLinks()
-} else {
-  window.addEventListener('DOMContentLoaded', function () {
+  // open <a href="..."> links with the Tauri API
+  function __openLinks() {
+    document.querySelector('body').addEventListener('click', function (e) {
+      var target = e.target
+      while (target != null) {
+        if (target.matches ? target.matches('a') : target.msMatchesSelector('a')) {
+          if (target.href && target.href.startsWith('http') && target.target === '_blank') {
+            window.tauri.open(target.href)
+            e.preventDefault()
+          }
+          break
+        }
+        target = target.parentElement
+      }
+    }, true)
+  }
+
+  if (document.readyState === 'complete' || document.readyState === 'interactive') {
     __openLinks()
-  }, true)
-}
+  } else {
+    window.addEventListener('DOMContentLoaded', function () {
+      __openLinks()
+    }, true)
+  }
+})()

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

@@ -195,6 +195,7 @@ fn build_webview(
     Content::Url(ref url) => Content::Url(url.clone()),
   };
   let debug = cfg!(debug_assertions);
+  let config_clone = config.clone();
   // get properties from config struct
   let width = config.tauri.window.width;
   let height = config.tauri.window.height;
@@ -228,7 +229,9 @@ fn build_webview(
         webview.eval(&format!(r#"window.location.href = "{}""#, content_href))?;
       } else {
         let handler_error;
-        if let Err(tauri_handle_error) = crate::endpoints::handle(webview, arg) {
+        if let Err(tauri_handle_error) =
+          crate::endpoints::handle(webview, arg, config_clone.clone())
+        {
           let tauri_handle_error_str = tauri_handle_error.to_string();
           if tauri_handle_error_str.contains("unknown variant") {
             let handled_by_app = application.run_invoke_handler(webview, arg);

+ 87 - 1
tauri/src/endpoints.rs

@@ -6,10 +6,15 @@ mod salt;
 
 #[cfg(any(feature = "embedded-server", feature = "no-server"))]
 use std::path::PathBuf;
+use tauri_api::config::Config;
 use web_view::WebView;
 
 #[allow(unused_variables)]
-pub(crate) fn handle<T: 'static>(webview: &mut WebView<'_, T>, arg: &str) -> crate::Result<()> {
+pub(crate) fn handle<T: 'static>(
+  webview: &mut WebView<'_, T>,
+  arg: &str,
+  config: Config,
+) -> crate::Result<()> {
   use cmd::Cmd::*;
   match serde_json::from_str(arg) {
     Err(e) => Err(e.into()),
@@ -188,6 +193,57 @@ pub(crate) fn handle<T: 'static>(webview: &mut WebView<'_, T>, arg: &str) -> cra
           callback,
           error,
         ),
+        #[cfg(any(feature = "all-api", feature = "notification"))]
+        Notification {
+          options,
+          callback,
+          error,
+        } => {
+          notification(webview, options, callback, error, config)?;
+        }
+        #[cfg(any(feature = "all-api", feature = "notification"))]
+        IsNotificationPermissionGranted { callback, error } => {
+          crate::execute_promise(
+            webview,
+            move || {
+              let settings = crate::settings::read_settings()?;
+              if let Some(allow_notification) = settings.allow_notification {
+                Ok(allow_notification.to_string())
+              } else {
+                Ok("null".to_string())
+              }
+            },
+            callback,
+            error,
+          );
+        }
+        #[cfg(any(feature = "all-api", feature = "notification"))]
+        RequestNotificationPermission { callback, error } => crate::execute_promise_sync(
+          webview,
+          move || {
+            let mut settings = crate::settings::read_settings()?;
+            let granted = r#""granted""#.to_string();
+            let denied = r#""denied""#.to_string();
+            if let Some(allow_notification) = settings.allow_notification {
+              return Ok(if allow_notification { granted } else { denied });
+            }
+            let answer = tauri_api::dialog::ask(
+              "This app wants to show notifications. Do you allow?",
+              "Permissions",
+            );
+            match answer {
+              tauri_api::dialog::DialogSelection::Yes => {
+                settings.allow_notification = Some(true);
+                crate::settings::write_settings(settings)?;
+                Ok(granted)
+              }
+              tauri_api::dialog::DialogSelection::No => Ok(denied),
+              _ => Ok(r#""default""#.to_string()),
+            }
+          },
+          callback,
+          error,
+        ),
       }
       Ok(())
     }
@@ -351,6 +407,36 @@ fn load_asset<T: 'static>(
   Ok(())
 }
 
+#[cfg(any(feature = "all-api", feature = "notification"))]
+fn notification<T: 'static>(
+  webview: &mut WebView<'_, T>,
+  options: cmd::NotificationOptions,
+  callback: String,
+  error: String,
+  config: Config,
+) -> crate::Result<()> {
+  crate::execute_promise(
+    webview,
+    move || {
+      let mut notification = tauri_api::notification::Notification::new(config);
+      notification.body(options.body);
+      if let Some(title) = options.title {
+        notification.title(title);
+      }
+      if let Some(icon) = options.icon {
+        notification.icon(icon);
+      }
+      notification
+        .show()
+        .map_err(|e| anyhow::anyhow!(r#""{}""#, e.to_string()))?;
+      Ok("".to_string())
+    },
+    callback,
+    error,
+  );
+  Ok(())
+}
+
 #[cfg(test)]
 mod test {
   use proptest::prelude::*;

+ 23 - 0
tauri/src/endpoints/cmd.rs

@@ -32,6 +32,13 @@ pub struct SaveDialogOptions {
   pub default_path: Option<String>,
 }
 
+#[derive(Deserialize)]
+pub struct NotificationOptions {
+  pub title: Option<String>,
+  pub body: String,
+  pub icon: Option<String>,
+}
+
 #[derive(Deserialize)]
 #[serde(tag = "cmd", rename_all = "camelCase")]
 pub enum Cmd {
@@ -165,4 +172,20 @@ pub enum Cmd {
     callback: String,
     error: String,
   },
+  #[cfg(any(feature = "all-api", feature = "notification"))]
+  Notification {
+    options: NotificationOptions,
+    callback: String,
+    error: String,
+  },
+  #[cfg(any(feature = "all-api", feature = "notification"))]
+  RequestNotificationPermission {
+    callback: String,
+    error: String,
+  },
+  #[cfg(any(feature = "all-api", feature = "notification"))]
+  IsNotificationPermissionGranted {
+    callback: String,
+    error: String,
+  },
 }

+ 1 - 1
tauri/src/lib.rs

@@ -8,6 +8,7 @@ pub mod assets;
 pub mod event;
 #[cfg(feature = "embedded-server")]
 pub mod server;
+pub mod settings;
 
 #[cfg(feature = "cli")]
 pub mod cli;
@@ -27,7 +28,6 @@ use web_view::WebView;
 
 pub use app::*;
 pub use tauri_api as api;
-
 thread_local!(static POOL: ThreadPool = ThreadPool::new(4));
 
 pub fn spawn<F: FnOnce() -> () + Send + 'static>(task: F) {

+ 38 - 0
tauri/src/settings.rs

@@ -0,0 +1,38 @@
+use anyhow::anyhow;
+use serde::{Deserialize, Serialize};
+use std::fs::File;
+use std::io::Write;
+use std::path::Path;
+use tauri_api::file::read_string;
+use tauri_api::path::{resolve_path, BaseDirectory};
+
+#[derive(Default, Deserialize, Serialize)]
+pub struct Settings {
+  #[cfg(any(feature = "all-api", feature = "notification"))]
+  pub allow_notification: Option<bool>,
+}
+
+fn get_settings_path() -> tauri_api::Result<String> {
+  resolve_path(".tauri-settings.json".to_string(), Some(BaseDirectory::App))
+}
+
+pub(crate) fn write_settings(settings: Settings) -> crate::Result<()> {
+  let settings_path = get_settings_path()?;
+  std::fs::create_dir(Path::new(&settings_path).parent().unwrap())?;
+  File::create(settings_path)
+    .map_err(|e| anyhow!(e))
+    .and_then(|mut f| {
+      f.write_all(serde_json::to_string(&settings)?.as_bytes())
+        .map_err(|err| anyhow!(err))
+    })
+}
+
+pub fn read_settings() -> crate::Result<Settings> {
+  let settings_path = get_settings_path()?;
+  if Path::new(settings_path.as_str()).exists() {
+    read_string(settings_path)
+      .and_then(|settings| serde_json::from_str(settings.as_str()).map_err(|e| anyhow!(e)))
+  } else {
+    Ok(Default::default())
+  }
+}

Some files were not shown because too many files changed in this diff