From 2681ad361b4295756bef560b5d74ece2a757749f Mon Sep 17 00:00:00 2001 From: nothingismagick Date: Sat, 27 Jun 2020 17:20:00 +0200 Subject: [PATCH] refactor(tauri.js) rewrite API module in TypeScript, closes #679 #435 (#703) Co-authored-by: Quentin Goinaud Co-authored-by: Lucas Fernandes Nogueira Co-authored-by: Lucas Nogueira --- .changes/ts-api.md | 5 + .changes/window-tauri.md | 6 + .github/workflows/build-smoke-tests.yml | 2 +- cli/deno | 1 + cli/tauri.js/.gitignore | 3 + cli/tauri.js/api-src/bundle.ts | 23 + cli/tauri.js/api-src/cli.ts | 15 + cli/tauri.js/api-src/dialog.ts | 47 + cli/tauri.js/api-src/event.ts | 36 + cli/tauri.js/api-src/fs.ts | 239 +++++ cli/tauri.js/api-src/http.ts | 111 +++ cli/tauri.js/api-src/index.ts | 2 + cli/tauri.js/api-src/notification.ts | 84 ++ cli/tauri.js/api-src/process.ts | 24 + cli/tauri.js/api-src/tauri.ts | 53 ++ cli/tauri.js/api-src/tsconfig.json | 17 + cli/tauri.js/api-src/types/cli.ts | 22 + cli/tauri.js/api-src/types/dialog.ts | 8 + cli/tauri.js/api-src/types/event.ts | 6 + cli/tauri.js/api-src/types/fs.ts | 41 + cli/tauri.js/api-src/types/http.ts | 33 + cli/tauri.js/api-src/types/notification.ts | 8 + cli/tauri.js/api-src/window.ts | 30 + cli/tauri.js/api/cli.js | 12 - cli/tauri.js/api/dialog.js | 32 - cli/tauri.js/api/event.js | 34 - cli/tauri.js/api/fs/dir.js | 30 - cli/tauri.js/api/fs/index.js | 191 ---- cli/tauri.js/api/http.js | 160 ---- cli/tauri.js/api/index.js | 3 - cli/tauri.js/api/process.js | 16 - cli/tauri.js/api/tauri.js | 1 - cli/tauri.js/api/window.js | 24 - cli/tauri.js/package.json | 16 +- cli/tauri.js/rollup.config.js | 98 ++ cli/tauri.js/src/entry.ts | 14 - cli/tauri.js/src/runner.ts | 23 +- cli/tauri.js/src/types/config.ts | 1 + cli/tauri.js/templates/mutation-observer.js | 2 +- cli/tauri.js/templates/src-tauri/_gitignore | 1 - cli/tauri.js/templates/tauri.esm.js | 385 -------- cli/tauri.js/templates/tauri.js | 848 ++---------------- .../test/jest/fixtures/app/dist/index.html | 85 +- cli/tauri.js/tsconfig.json | 3 +- cli/tauri.js/webpack.config.js | 2 +- cli/tauri.js/yarn.lock | 291 +++++- maskfile.md | 4 +- tauri/Cargo.toml | 1 + tauri/build.rs | 46 + tauri/examples/communication/dist/cli.js | 2 +- .../communication/dist/communication.js | 7 +- tauri/examples/communication/dist/dialog.js | 6 +- tauri/examples/communication/dist/fs.js | 11 +- tauri/examples/communication/dist/http.js | 4 +- tauri/examples/communication/dist/index.html | 18 +- .../communication/dist/index.tauri.html | 723 ++------------- tauri/examples/communication/dist/window.js | 4 +- .../communication/src-tauri/tauri.conf.json | 3 +- .../examples/communication/src-tauri/tauri.js | 732 --------------- tauri/src/app/runner.rs | 28 +- tauri/src/endpoints.rs | 399 +++----- tauri/src/endpoints/asset.rs | 79 ++ tauri/src/endpoints/browser.rs | 10 + tauri/src/endpoints/cmd.rs | 22 - tauri/src/endpoints/dialog.rs | 2 + tauri/src/endpoints/event.rs | 27 + tauri/src/endpoints/file_system.rs | 10 + tauri/src/endpoints/init.rs | 36 + tauri/src/endpoints/notification.rs | 86 ++ tauri/src/lib.rs | 6 +- tauri/src/settings.rs | 7 +- tauri/test/fixture/src-tauri/.gitkeep | 0 tauri/test/fixture/src-tauri/tauri.js | 219 ----- 73 files changed, 1929 insertions(+), 3651 deletions(-) create mode 100644 .changes/ts-api.md create mode 100644 .changes/window-tauri.md create mode 160000 cli/deno create mode 100644 cli/tauri.js/api-src/bundle.ts create mode 100644 cli/tauri.js/api-src/cli.ts create mode 100644 cli/tauri.js/api-src/dialog.ts create mode 100644 cli/tauri.js/api-src/event.ts create mode 100644 cli/tauri.js/api-src/fs.ts create mode 100644 cli/tauri.js/api-src/http.ts create mode 100644 cli/tauri.js/api-src/index.ts create mode 100644 cli/tauri.js/api-src/notification.ts create mode 100644 cli/tauri.js/api-src/process.ts create mode 100644 cli/tauri.js/api-src/tauri.ts create mode 100644 cli/tauri.js/api-src/tsconfig.json create mode 100644 cli/tauri.js/api-src/types/cli.ts create mode 100644 cli/tauri.js/api-src/types/dialog.ts create mode 100644 cli/tauri.js/api-src/types/event.ts create mode 100644 cli/tauri.js/api-src/types/fs.ts create mode 100644 cli/tauri.js/api-src/types/http.ts create mode 100644 cli/tauri.js/api-src/types/notification.ts create mode 100644 cli/tauri.js/api-src/window.ts delete mode 100644 cli/tauri.js/api/cli.js delete mode 100644 cli/tauri.js/api/dialog.js delete mode 100644 cli/tauri.js/api/event.js delete mode 100644 cli/tauri.js/api/fs/dir.js delete mode 100644 cli/tauri.js/api/fs/index.js delete mode 100644 cli/tauri.js/api/http.js delete mode 100644 cli/tauri.js/api/index.js delete mode 100644 cli/tauri.js/api/process.js delete mode 100644 cli/tauri.js/api/tauri.js delete mode 100644 cli/tauri.js/api/window.js create mode 100644 cli/tauri.js/rollup.config.js delete mode 100644 cli/tauri.js/src/entry.ts delete mode 100644 cli/tauri.js/templates/tauri.esm.js delete mode 100644 tauri/examples/communication/src-tauri/tauri.js create mode 100644 tauri/src/endpoints/asset.rs create mode 100644 tauri/src/endpoints/browser.rs create mode 100644 tauri/src/endpoints/event.rs create mode 100644 tauri/src/endpoints/init.rs create mode 100644 tauri/src/endpoints/notification.rs delete mode 100644 tauri/test/fixture/src-tauri/.gitkeep delete mode 100644 tauri/test/fixture/src-tauri/tauri.js diff --git a/.changes/ts-api.md b/.changes/ts-api.md new file mode 100644 index 000000000..597364b5f --- /dev/null +++ b/.changes/ts-api.md @@ -0,0 +1,5 @@ +--- +"tauri.js": minor +--- + +Create UMD, ESM and CJS artifacts for the JavaScript API entry point from TS source using rollup. diff --git a/.changes/window-tauri.md b/.changes/window-tauri.md new file mode 100644 index 000000000..7f731cac7 --- /dev/null +++ b/.changes/window-tauri.md @@ -0,0 +1,6 @@ +--- +"tauri.js": minor +--- + +Renaming `window.tauri` to `window.__TAURI__`, closing #435. +The `__TAURI__` object now follows the TypeScript API structure (e.g. `window.__TAURI__.readTextFile` is now `window.__TAURI__.fs.readTextFile`). diff --git a/.github/workflows/build-smoke-tests.yml b/.github/workflows/build-smoke-tests.yml index 95f3fbb16..b19febc55 100644 --- a/.github/workflows/build-smoke-tests.yml +++ b/.github/workflows/build-smoke-tests.yml @@ -87,7 +87,7 @@ jobs: - name: install cli deps via yarn working-directory: ./cli/tauri.js run: yarn - - name: build cli + - name: build cli & api working-directory: ./cli/tauri.js run: yarn build - name: cache node modules diff --git a/cli/deno b/cli/deno new file mode 160000 index 000000000..55fd9104f --- /dev/null +++ b/cli/deno @@ -0,0 +1 @@ +Subproject commit 55fd9104fa4da3b7afb96a16bb1ed8378b028a5a diff --git a/cli/tauri.js/.gitignore b/cli/tauri.js/.gitignore index b485c8643..00f89df6c 100644 --- a/cli/tauri.js/.gitignore +++ b/cli/tauri.js/.gitignore @@ -63,6 +63,9 @@ debug.log package-lock.json .vscode/settings.json +#api +api/* + # Tauri output bundle.json config.json diff --git a/cli/tauri.js/api-src/bundle.ts b/cli/tauri.js/api-src/bundle.ts new file mode 100644 index 000000000..627abd45c --- /dev/null +++ b/cli/tauri.js/api-src/bundle.ts @@ -0,0 +1,23 @@ + +import 'regenerator-runtime/runtime' +import * as cli from './cli' +import * as dialog from './dialog' +import * as event from './event' +import * as fs from './fs' +import http from './http' +import * as process from './process' +import * as tauri from './tauri' +import * as window from './window' +import * as notification from './notification' + +export { + cli, + dialog, + event, + fs, + http, + process, + tauri, + window, + notification +} diff --git a/cli/tauri.js/api-src/cli.ts b/cli/tauri.js/api-src/cli.ts new file mode 100644 index 000000000..39011db9e --- /dev/null +++ b/cli/tauri.js/api-src/cli.ts @@ -0,0 +1,15 @@ +import { CliMatches } from './types/cli' +import { promisified } from './tauri' + +/** + * gets the CLI matches + */ +async function getMatches(): Promise { + return await promisified({ + cmd: 'cliMatches' + }) +} + +export { + getMatches +} diff --git a/cli/tauri.js/api-src/dialog.ts b/cli/tauri.js/api-src/dialog.ts new file mode 100644 index 000000000..a9e9e4a07 --- /dev/null +++ b/cli/tauri.js/api-src/dialog.ts @@ -0,0 +1,47 @@ +import { OpenDialogOptions, SaveDialogOptions } from './types/dialog' +import { promisified } from './tauri' + +/** + * @name openDialog + * @description Open a file/directory selection dialog + * @param [options] + * @param [options.filter] + * @param [options.defaultPath] + * @param [options.multiple=false] + * @param [options.directory=false] + * @returns promise resolving to the select path(s) + */ +async function open(options: OpenDialogOptions = {}): Promise { + if (typeof options === 'object') { + Object.freeze(options) + } + + return await promisified({ + cmd: 'openDialog', + options + }) +} + +/** + * @name save + * @description Open a file/directory save dialog + * @param [options] + * @param [options.filter] + * @param [options.defaultPath] + * @returns promise resolving to the select path + */ +async function save(options: SaveDialogOptions = {}): Promise { + if (typeof options === 'object') { + Object.freeze(options) + } + + return await promisified({ + cmd: 'saveDialog', + options + }) +} + +export { + open, + save +} diff --git a/cli/tauri.js/api-src/event.ts b/cli/tauri.js/api-src/event.ts new file mode 100644 index 000000000..4be135ab4 --- /dev/null +++ b/cli/tauri.js/api-src/event.ts @@ -0,0 +1,36 @@ +import { invoke, transformCallback } from './tauri' +import { EventCallback } from './types/event' + +/** + * listen to an event from the backend + * + * @param event the event name + * @param handler the event handler callback + */ +function listen(event: string, handler: EventCallback, once = false): void { + invoke({ + cmd: 'listen', + event, + handler: transformCallback(handler, once), + once + }) +} + +/** + * emits an event to the backend + * + * @param event the event name + * @param [payload] the event payload + */ +function emit(event: string, payload?: string): void { + invoke({ + cmd: 'emit', + event, + payload + }) +} + +export { + listen, + emit +} diff --git a/cli/tauri.js/api-src/fs.ts b/cli/tauri.js/api-src/fs.ts new file mode 100644 index 000000000..966681892 --- /dev/null +++ b/cli/tauri.js/api-src/fs.ts @@ -0,0 +1,239 @@ +import { promisified } from './tauri' +import { BaseDirectory, FsOptions, FsTextFileOption, FsBinaryFileOption, FileEntry } from './types/fs' + +/** + * reads a file as text + * + * @param filePath path to the file + * @param [options] configuration object + * @param [options.dir] base directory + * @return + */ +async function readTextFile(filePath: string, options: FsOptions = {}): Promise { + return await promisified({ + cmd: 'readTextFile', + path: filePath, + options + }) +} + +/** + * reads a file as binary + * + * @param filePath path to the file + * @param {Object} [options] configuration object + * @param {BaseDirectory} [options.dir] base directory + * @return {Promise} + */ +async function readBinaryFile(filePath: string, options: FsOptions = {}): Promise { + return await promisified({ + cmd: 'readBinaryFile', + path: filePath, + options + }) +} + +/** + * writes a text file + * + * @param file + * @param file.path path of the file + * @param file.contents contents of the file + * @param [options] configuration object + * @param [options.dir] base directory + * @return + */ +async function writeFile(file: FsTextFileOption, options: FsOptions = {}): Promise { + if (typeof options === 'object') { + Object.freeze(options) + } + if (typeof file === 'object') { + Object.freeze(file) + } + + return await promisified({ + cmd: 'writeFile', + file: file.path, + contents: file.contents, + options + }) +} + +const CHUNK_SIZE = 65536 + +/** + * convert an Uint8Array to ascii string + * + * @param arr + * @return ASCII string + */ +function uint8ArrayToString(arr: Uint8Array): string { + if (arr.length < CHUNK_SIZE) { + return String.fromCharCode.apply(null, Array.from(arr)) + } + + let result = '' + const arrLen = arr.length + for (let i = 0; i < arrLen; i++) { + const chunk = arr.subarray(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE) + result += String.fromCharCode.apply(null, Array.from(chunk)) + } + return result +} + +/** + * convert an ArrayBuffer to base64 encoded string + * + * @param buffer + * @return base64 encoded string + */ +function arrayBufferToBase64(buffer: ArrayBuffer): string { + const str = uint8ArrayToString(new Uint8Array(buffer)) + return btoa(str) +} + +/** + * writes a binary file + * + * @param file + * @param file.path path of the file + * @param file.contents contents of the file + * @param [options] configuration object + * @param [options.dir] base directory + * @return + */ +async function writeBinaryFile(file: FsBinaryFileOption, options: FsOptions = {}): Promise { + if (typeof options === 'object') { + Object.freeze(options) + } + if (typeof file === 'object') { + Object.freeze(file) + } + + return await promisified({ + cmd: 'writeFile', + file: file.path, + contents: arrayBufferToBase64(file.contents), + options + }) +} + +/** + * list directory files + * + * @param dir path to the directory to read + * @param [options] configuration object + * @param [options.recursive] whether to list dirs recursively or not + * @param [options.dir] base directory + * @return + */ +async function readDir(dir: string, options: FsOptions = {}): Promise { + return await promisified({ + cmd: 'readDir', + path: dir, + options + }) +} + +/** + * Creates a directory + * If one of the path's parent components doesn't exist + * and the `recursive` option isn't set to true, it will be rejected + * + * @param dir path to the directory to create + * @param [options] configuration object + * @param [options.recursive] whether to create the directory's parent components or not + * @param [options.dir] base directory + * @return + */ +async function createDir(dir: string, options: FsOptions = {}): Promise { + return await promisified({ + cmd: 'createDir', + path: dir, + options + }) +} + +/** + * Removes a directory + * If the directory is not empty and the `recursive` option isn't set to true, it will be rejected + * + * @param dir path to the directory to remove + * @param [options] configuration object + * @param [options.recursive] whether to remove all of the directory's content or not + * @param [options.dir] base directory + * @return + */ +async function removeDir(dir: string, options: FsOptions = {}): Promise { + return await promisified({ + cmd: 'removeDir', + path: dir, + options + }) +} + +/** + * Copy file + * + * @param source + * @param destination + * @param [options] configuration object + * @param [options.dir] base directory + * @return + */ +async function copyFile(source: string, destination: string, options: FsOptions = {}): Promise { + return await promisified({ + cmd: 'copyFile', + source, + destination, + options + }) +} + +/** + * Removes a file + * + * @param file path to the file to remove + * @param [options] configuration object + * @param [options.dir] base directory + * @return + */ +async function removeFile(file: string, options: FsOptions = {}): Promise { + return await promisified({ + cmd: 'removeFile', + path: file, + options: options + }) +} + +/** + * Renames a file + * + * @param oldPath + * @param newPath + * @param [options] configuration object + * @param [options.dir] base directory + * @return + */ +async function renameFile(oldPath: string, newPath: string, options: FsOptions = {}): Promise { + return await promisified({ + cmd: 'renameFile', + oldPath, + newPath, + options + }) +} + +export { + BaseDirectory as Dir, + readTextFile, + readBinaryFile, + writeFile, + writeBinaryFile, + readDir, + createDir, + removeDir, + copyFile, + removeFile, + renameFile +} diff --git a/cli/tauri.js/api-src/http.ts b/cli/tauri.js/api-src/http.ts new file mode 100644 index 000000000..c88e4f965 --- /dev/null +++ b/cli/tauri.js/api-src/http.ts @@ -0,0 +1,111 @@ +import { promisified } from './tauri' +import { HttpOptions, Body, BodyType, ResponseType, PartialOptions } from './types/http' + +/** + * makes a HTTP request + * + * @param options request options + * + * @return promise resolving to the response + */ +async function request(options: HttpOptions): Promise { + return await promisified({ + cmd: 'httpRequest', + options: options + }) +} + +/** + * makes a GET request + * + * @param url request URL + * @param options request options + * + * @return promise resolving to the response + */ +async function get(url: string, options: PartialOptions): Promise { + return await request({ + method: 'GET', + url, + ...options + }) +} + +/** + * makes a POST request + * + * @param url request URL + * @param body request body + * @param options request options + * + * @return promise resolving to the response + */ +async function post(url: string, body: Body, options: PartialOptions): Promise { + return await request({ + method: 'POST', + url, + body, + ...options + }) +} + +/** + * makes a PUT request + * + * @param url request URL + * @param body request body + * @param options request options + * + * @return promise resolving to the response + */ +async function put(url: string, body: Body, options: PartialOptions): Promise { + return await request({ + method: 'PUT', + url, + body, + ...options + }) +} + +/** + * makes a PATCH request + * + * @param url request URL + * @param options request options + * + * @return promise resolving to the response + */ +async function patch(url: string, options: PartialOptions): Promise { + return await request({ + method: 'PATCH', + url, + ...options + }) +} + +/** + * makes a DELETE request + * + * @param url request URL + * @param options request options + * + * @return promise resolving to the response + */ +async function deleteRequest(url: string, options: PartialOptions): Promise { + return await request({ + method: 'DELETE', + url, + ...options + }) +} + +export default { + request, + get, + post, + put, + patch, + delete: deleteRequest, + ResponseType, + BodyType +} diff --git a/cli/tauri.js/api-src/index.ts b/cli/tauri.js/api-src/index.ts new file mode 100644 index 000000000..b42a238b9 --- /dev/null +++ b/cli/tauri.js/api-src/index.ts @@ -0,0 +1,2 @@ +import * as api from './bundle' +export default api diff --git a/cli/tauri.js/api-src/notification.ts b/cli/tauri.js/api-src/notification.ts new file mode 100644 index 000000000..72b96a2ee --- /dev/null +++ b/cli/tauri.js/api-src/notification.ts @@ -0,0 +1,84 @@ +import { Options, PartialOptions, Permission } from './types/notification' +import { promisified } from './tauri' + +let permissionSettable = false +let permissionValue = 'default' +function setNotificationPermission(value: Permission): void { + permissionSettable = true + // @ts-expect-error + window.Notification.permission = value + permissionSettable = false +} + +// @ts-expect-error +window.Notification = (title: string, options?: PartialOptions) => { + sendNotification({ + title, + ...options + }) +} + +window.Notification.requestPermission = requestPermission + +Object.defineProperty(window.Notification, 'permission', { + enumerable: true, + get: () => permissionValue, + set(v: Permission) { + if (!permissionSettable) { + throw new Error('Readonly property') + } + permissionValue = v + } +}) + +isPermissionGranted() + .then(response => { + if (response === null) { + setNotificationPermission('default') + } else { + setNotificationPermission(response ? 'granted' : 'denied') + } + }) + .catch(err => { throw err }) + +async function isPermissionGranted(): Promise { + if (window.Notification.permission !== 'default') { + return await Promise.resolve(window.Notification.permission === 'granted') + } + return await promisified({ + cmd: 'isNotificationPermissionGranted' + }) +} + +async function requestPermission(): Promise { + return await promisified({ + cmd: 'requestNotificationPermission' + }).then(permission => { + setNotificationPermission(permission) + return permission + }) +} + +function sendNotification(options: Options | string): void { + if (typeof options === 'object') { + Object.freeze(options) + } + + isPermissionGranted() + .then(permission => { + if (permission) { + return promisified({ + cmd: 'notification', + options: typeof options === 'string' ? { + body: options + } : options + }) + } + }) + .catch(err => { throw err }) +} + +export { + sendNotification, + isPermissionGranted +} diff --git a/cli/tauri.js/api-src/process.ts b/cli/tauri.js/api-src/process.ts new file mode 100644 index 000000000..38d0f29ca --- /dev/null +++ b/cli/tauri.js/api-src/process.ts @@ -0,0 +1,24 @@ +import { promisified } from './tauri' + +/** + * spawns a process + * + * @param command the name of the cmd to execute e.g. 'mkdir' or 'node' + * @param [args] command args + * @return promise resolving to the stdout text + */ +async function execute(command: string, args?: string | string[]): Promise { + if (typeof args === 'object') { + Object.freeze(args) + } + + return await promisified({ + cmd: 'execute', + command, + args: typeof args === 'string' ? [args] : args + }) +} + +export { + execute +} diff --git a/cli/tauri.js/api-src/tauri.ts b/cli/tauri.js/api-src/tauri.ts new file mode 100644 index 000000000..5bfbe0db6 --- /dev/null +++ b/cli/tauri.js/api-src/tauri.ts @@ -0,0 +1,53 @@ +declare global { + interface External { + invoke: (command: string) => void + } +} + +function s4(): string { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1) +} + +function uid(): string { + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + + s4() + '-' + s4() + s4() + s4() +} + +function invoke(args: any): void { + window.external.invoke(typeof args === 'object' ? JSON.stringify(args) : args) +} + +function transformCallback(callback?: (response: any) => void, once = false): string { + const identifier = uid() + + Object.defineProperty(window, identifier, { + value: (result: any) => { + if (once) { + Reflect.deleteProperty(window, identifier) + } + + return callback?.(result) + }, + writable: false + }) + + return identifier +} + +async function promisified(args: any): Promise { + return await new Promise((resolve, reject) => { + invoke({ + callback: transformCallback(resolve), + error: transformCallback(reject), + ...args + }) + }) +} + +export { + invoke, + transformCallback, + promisified +} diff --git a/cli/tauri.js/api-src/tsconfig.json b/cli/tauri.js/api-src/tsconfig.json new file mode 100644 index 000000000..f6c70fb98 --- /dev/null +++ b/cli/tauri.js/api-src/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "outDir": "./dist/", + "strict": true, + "module": "commonjs", + "target": "es5", + "allowJs": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "moduleResolution": "node", + "baseUrl": ".", + "paths": { + "types": ["@types"] + }, + }, + "include": ["./"] +} diff --git a/cli/tauri.js/api-src/types/cli.ts b/cli/tauri.js/api-src/types/cli.ts new file mode 100644 index 000000000..483e75089 --- /dev/null +++ b/cli/tauri.js/api-src/types/cli.ts @@ -0,0 +1,22 @@ +export interface ArgMatch { + /** + * string if takes value + * boolean if flag + * string[] or null if takes multiple values + */ + value: string | boolean | string[] | null + /** + * number of occurrences + */ + occurrences: number +} + +export interface SubcommandMatch { + name: string + matches: CliMatches +} + +export interface CliMatches { + args: { [name: string]: ArgMatch } + subcommand: SubcommandMatch | null +} diff --git a/cli/tauri.js/api-src/types/dialog.ts b/cli/tauri.js/api-src/types/dialog.ts new file mode 100644 index 000000000..6a45e3add --- /dev/null +++ b/cli/tauri.js/api-src/types/dialog.ts @@ -0,0 +1,8 @@ +export interface OpenDialogOptions { + filter?: string + defaultPath?: string + multiple?: boolean + directory?: boolean +} + +export type SaveDialogOptions = Pick diff --git a/cli/tauri.js/api-src/types/event.ts b/cli/tauri.js/api-src/types/event.ts new file mode 100644 index 000000000..802b809f5 --- /dev/null +++ b/cli/tauri.js/api-src/types/event.ts @@ -0,0 +1,6 @@ +export interface Event { + type: string + payload: unknown +} + +export type EventCallback = (event: Event) => void diff --git a/cli/tauri.js/api-src/types/fs.ts b/cli/tauri.js/api-src/types/fs.ts new file mode 100644 index 000000000..a7ca9efe6 --- /dev/null +++ b/cli/tauri.js/api-src/types/fs.ts @@ -0,0 +1,41 @@ +export enum BaseDirectory { + Audio = 1, + Cache, + Config, + Data, + LocalData, + Desktop, + Document, + Download, + Executable, + Font, + Home, + Picture, + Public, + Runtime, + Template, + Video, + Resource, + App, +} + +export interface FsOptions { + dir?: BaseDirectory +} + +export interface FsTextFileOption { + path: string + contents: string +} + +export interface FsBinaryFileOption { + path: string + contents: ArrayBuffer +} + +export interface FileEntry { + path: string + // TODO why not camelCase ? + is_dir: boolean + name: string +} diff --git a/cli/tauri.js/api-src/types/http.ts b/cli/tauri.js/api-src/types/http.ts new file mode 100644 index 000000000..caa0bda5f --- /dev/null +++ b/cli/tauri.js/api-src/types/http.ts @@ -0,0 +1,33 @@ +export enum ResponseType { + JSON = 1, + Text = 2, + Binary = 3 +} + +export enum BodyType { + Form = 1, + File = 2, + Auto = 3 +} + +export type Body = object | string | BinaryType + +export type HttpVerb = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS' | 'CONNECT' | 'TRACE' + +export interface HttpOptions { + method: HttpVerb + url: string + headers?: Record + propertys?: Record + body?: Body + followRedirects: boolean + maxRedirections: boolean + connectTimeout: number + readTimeout: number + timeout: number + allowCompression: boolean + responseType?: ResponseType + bodyType: BodyType +} + +export type PartialOptions = Omit diff --git a/cli/tauri.js/api-src/types/notification.ts b/cli/tauri.js/api-src/types/notification.ts new file mode 100644 index 000000000..3ba7f5ca7 --- /dev/null +++ b/cli/tauri.js/api-src/types/notification.ts @@ -0,0 +1,8 @@ +export interface Options { + title?: string + body?: string + icon?: string +} + +export type PartialOptions = Omit +export type Permission = 'granted' | 'denied' | 'default' diff --git a/cli/tauri.js/api-src/window.ts b/cli/tauri.js/api-src/window.ts new file mode 100644 index 000000000..a46833e3d --- /dev/null +++ b/cli/tauri.js/api-src/window.ts @@ -0,0 +1,30 @@ +import { invoke } from './tauri' + +/** + * sets the window title + * + * @param title the new title + */ +function setTitle(title: string): void { + invoke({ + cmd: 'setTitle', + title + }) +} + +/** + * opens an URL on the user default browser + * + * @param url the URL to open + */ +function open(url: string): void { + invoke({ + cmd: 'open', + uri: url + }) +} + +export { + setTitle, + open +} diff --git a/cli/tauri.js/api/cli.js b/cli/tauri.js/api/cli.js deleted file mode 100644 index 60c36e674..000000000 --- a/cli/tauri.js/api/cli.js +++ /dev/null @@ -1,12 +0,0 @@ -import tauri from './tauri' - -/** - * gets the CLI matches - */ -function getMatches() { - return tauri.cliMatches() -} - -export { - getMatches -} diff --git a/cli/tauri.js/api/dialog.js b/cli/tauri.js/api/dialog.js deleted file mode 100644 index a06243740..000000000 --- a/cli/tauri.js/api/dialog.js +++ /dev/null @@ -1,32 +0,0 @@ -import tauri from './tauri' - -/** - * @name openDialog - * @description Open a file/directory selection dialog - * @param {Object} [options] - * @param {String} [options.filter] - * @param {String} [options.defaultPath] - * @param {Boolean} [options.multiple=false] - * @param {Boolean} [options.directory=false] - * @returns {Promise} promise resolving to the select path(s) - */ -function open (options = {}) { - return tauri.openDialog(options) -} - -/** - * @name save - * @description Open a file/directory save dialog - * @param {Object} [options] - * @param {String} [options.filter] - * @param {String} [options.defaultPath] - * @returns {Promise} promise resolving to the select path - */ -function save (options = {}) { - return tauri.saveDialog(options) -} - -export { - open, - save -} diff --git a/cli/tauri.js/api/event.js b/cli/tauri.js/api/event.js deleted file mode 100644 index 18fd314c7..000000000 --- a/cli/tauri.js/api/event.js +++ /dev/null @@ -1,34 +0,0 @@ -import tauri from './tauri' - -/** - * The event handler callback - * @callback EventCallback - * @param {Object} event - * @param {String} event.type - * @param {any} [event.payload] - */ - -/** - * listen to an event from the backend - * - * @param {String} event the event name - * @param {EventCallback} handler the event handler callback - */ -function listen (event, handler) { - tauri.listen(event, handler) -} - -/** - * emits an event to the backend - * - * @param {String} event the event name - * @param {String} [payload] the event payload - */ -function emit (event, payload) { - tauri.emit(event, payload) -} - -export { - listen, - emit -} diff --git a/cli/tauri.js/api/fs/dir.js b/cli/tauri.js/api/fs/dir.js deleted file mode 100644 index 318ade7b6..000000000 --- a/cli/tauri.js/api/fs/dir.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @typedef {number} BaseDirectory - */ -/** - * @enum {BaseDirectory} - */ -const 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 -} - -export { - Dir -} diff --git a/cli/tauri.js/api/fs/index.js b/cli/tauri.js/api/fs/index.js deleted file mode 100644 index 5d3e80132..000000000 --- a/cli/tauri.js/api/fs/index.js +++ /dev/null @@ -1,191 +0,0 @@ -import tauri from '../tauri' -import { Dir } from './dir' - -/** - * reads a file as text - * - * @param {String} filePath path to the file - * @param {Object} [options] configuration object - * @param {BaseDirectory} [options.dir] base directory - * @return {Promise} - */ -function readTextFile (filePath, options = {}) { - return tauri.readTextFile(filePath, options) -} - -/** - * reads a file as binary - * - * @param {String} filePath path to the file - * @param {Object} [options] configuration object - * @param {BaseDirectory} [options.dir] base directory - * @return {Promise} - */ -function readBinaryFile (filePath, options = {}) { - return tauri.readBinaryFile(filePath, options) -} - -/** - * writes a text file - * - * @param {Object} file - * @param {String} file.path path of the file - * @param {String} file.contents contents of the file - * @param {Object} [options] configuration object - * @param {BaseDirectory} [options.dir] base directory - * @return {Promise} - */ -function writeFile (file, options = {}) { - return tauri.writeFile(file, options) -} - -const CHUNK_SIZE = 65536; - -/** - * convert an Uint8Array to ascii string - * - * @param {Uint8Array} arr - * @return {String} - */ -function uint8ArrayToString(arr) { - if (arr.length < CHUNK_SIZE) { - return String.fromCharCode.apply(null, arr) - } - - let result = '' - const arrLen = arr.length - for (let i = 0; i < arrLen; i++) { - const chunk = arr.subarray(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE) - result += String.fromCharCode.apply(null, chunk) - } - return result -} - -/** - * convert an ArrayBuffer to base64 encoded string - * - * @param {ArrayBuffer} buffer - * @return {String} - */ -function arrayBufferToBase64(buffer) { - const str = uint8ArrayToString(new Uint8Array(buffer)) - return btoa(str) -} - -/** - * writes a binary file - * - * @param {Object} file - * @param {String} file.path path of the file - * @param {ArrayBuffer} file.contents contents of the file - * @param {Object} [options] configuration object - * @param {BaseDirectory} [options.dir] base directory - * @return {Promise} - */ -function writeBinaryFile(file, options = {}) { - return tauri.writeBinaryFile({ - ...file, - contents: arrayBufferToBase64(file.contents) - }, options) -} - -/** - * @typedef {Object} FileEntry - * @property {String} path - * @property {Boolean} is_dir - * @property {String} name - */ - -/** - * list directory files - * - * @param {String} dir path to the directory to read - * @param {Object} [options] configuration object - * @param {Boolean} [options.recursive] whether to list dirs recursively or not - * @param {BaseDirectory} [options.dir] base directory - * @return {Promise} - */ -function readDir (dir, options = {}) { - return tauri.readDir(dir, options) -} - -/** - * Creates a directory - * If one of the path's parent components doesn't exist - * and the `recursive` option isn't set to true, it will be rejected - * - * @param {String} dir path to the directory to create - * @param {Object} [options] configuration object - * @param {Boolean} [options.recursive] whether to create the directory's parent components or not - * @param {BaseDirectory} [options.dir] base directory - * @return {Promise} - */ -function createDir (dir, options = {}) { - return tauri.createDir(dir, options) -} - -/** - * Removes a directory - * If the directory is not empty and the `recursive` option isn't set to true, it will be rejected - * - * @param {String} dir path to the directory to remove - * @param {Object} [options] configuration object - * @param {Boolean} [options.recursive] whether to remove all of the directory's content or not - * @param {BaseDirectory} [options.dir] base directory - * @return {Promise} - */ -function removeDir (dir, options = {}) { - return tauri.removeDir(dir, options) -} - -/** - * Copy file - * - * @param {string} source - * @param {string} destination - * @param {object} [options] configuration object - * @param {BaseDirectory} [options.dir] base directory - * @return {Promise} - */ -function copyFile (source, destination, options = {}) { - return tauri.copyFile(source, destination, options) -} - -/** - * Removes a file - * - * @param {String} file path to the file to remove - * @param {Object} [options] configuration object - * @param {BaseDirectory} [options.dir] base directory - * @return {Promise} - */ -function removeFile (file, options = {}) { - return tauri.removeFile(file, options) -} - -/** - * Renames a file - * - * @param {String} oldPath - * @param {String} newPath - * @param {Object} [options] configuration object - * @param {BaseDirectory} [options.dir] base directory - * @return {Promise} - */ -function renameFile (oldPath, newPath, options = {}) { - return tauri.renameFile(oldPath, newPath, options) -} - -export { - Dir, - readTextFile, - readBinaryFile, - writeFile, - writeBinaryFile, - readDir, - createDir, - removeDir, - copyFile, - removeFile, - renameFile -} diff --git a/cli/tauri.js/api/http.js b/cli/tauri.js/api/http.js deleted file mode 100644 index a739fe3fe..000000000 --- a/cli/tauri.js/api/http.js +++ /dev/null @@ -1,160 +0,0 @@ -import tauri from './tauri' - -/** - * @typedef {number} ResponseType - */ -/** - * @enum {ResponseType} - */ -const ResponseType = { - JSON: 1, - Text: 2, - Binary: 3 -} - -/** - * @typedef {number} BodyType - */ -/** - * @enum {BodyType} - */ -const BodyType = { - Form: 1, - File: 2, - Auto: 3 -} - -/** - * @typedef {Object} HttpOptions - * @property {String} options.method GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT or TRACE - * @property {String} options.url the request URL - * @property {Object} [options.headers] the request headers - * @property {Object} [options.propertys] the request query propertys - * @property {Object|String|Binary} [options.body] the request body - * @property {Boolean} followRedirects whether to follow redirects or not - * @property {Number} maxRedirections max number of redirections - * @property {Number} connectTimeout request connect timeout - * @property {Number} readTimeout request read timeout - * @property {Number} timeout request timeout - * @property {Boolean} allowCompression - * @property {ResponseType} [responseType=1] response type - * @property {BodyType} [bodyType=3] body type -*/ - -/** - * makes a HTTP request - * - * @param {HttpOptions} options request options - * - * @return {Promise} promise resolving to the response - */ -function request (options) { - return tauri.httpRequest(options) -} - -/** - * makes a GET request - * - * @param {String} url request URL - * @param {String|Object|Binary} body request body - * @param {HttpOptions} options request options - * - * @return {Promise} promise resolving to the response - */ -function get (url, options = {}) { - return request({ - method: 'GET', - url, - ...options - }) -} - -/** - * makes a POST request - * - * @param {String} url request URL - * @param {String|Object|Binary} body request body - * @param {HttpOptions} options request options - * - * @return {Promise} promise resolving to the response - */ -function post (url, body = void 0, options = {}) { - return request({ - method: 'POST', - url, - body, - ...options - }) -} - -/** - * makes a PUT request - * - * @param {String} url request URL - * @param {String|Object|Binary} body request body - * @param {HttpOptions} options request options - * - * @return {Promise} promise resolving to the response - */ -function put (url, body = void 0, options = {}) { - return request({ - method: 'PUT', - url, - body, - ...options - }) -} - -/** - * makes a PATCH request - * - * @param {String} url request URL - * @param {HttpOptions} options request options - * - * @return {Promise} promise resolving to the response - */ -function patch (url, options = {}) { - return request({ - method: 'PATCH', - url, - ...options - }) -} - -/** - * makes a DELETE request - * - * @param {String} url request URL - * @param {HttpOptions} options request options - * - * @return {Promise} promise resolving to the response - */ -function deleteRequest (url, options = {}) { - return request({ - method: 'DELETE', - url, - ...options - }) -} - -export { - request, - get, - post, - put, - patch, - deleteRequest, - ResponseType, - BodyType -} - -export default { - request, - get, - post, - put, - patch, - delete: deleteRequest, - ResponseType, - BodyType -} diff --git a/cli/tauri.js/api/index.js b/cli/tauri.js/api/index.js deleted file mode 100644 index 740a32da0..000000000 --- a/cli/tauri.js/api/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import tauri from './tauri' - -export default tauri diff --git a/cli/tauri.js/api/process.js b/cli/tauri.js/api/process.js deleted file mode 100644 index dcab4df57..000000000 --- a/cli/tauri.js/api/process.js +++ /dev/null @@ -1,16 +0,0 @@ -import tauri from './tauri' - -/** - * spawns a process - * - * @param {String} command the name of the cmd to execute e.g. 'mkdir' or 'node' - * @param {(String[]|String)} [args] command args - * @return {Promise} promise resolving to the stdout text - */ -function execute (command, args) { - return tauri.execute(command, args) -} - -export { - execute -} diff --git a/cli/tauri.js/api/tauri.js b/cli/tauri.js/api/tauri.js deleted file mode 100644 index 043500b79..000000000 --- a/cli/tauri.js/api/tauri.js +++ /dev/null @@ -1 +0,0 @@ -export default window.tauri diff --git a/cli/tauri.js/api/window.js b/cli/tauri.js/api/window.js deleted file mode 100644 index 094ad67ae..000000000 --- a/cli/tauri.js/api/window.js +++ /dev/null @@ -1,24 +0,0 @@ -import tauri from './tauri' - -/** - * sets the window title - * - * @param {String} title the new title - */ -function setTitle (title) { - tauri.setTitle(title) -} - -/** - * opens an URL on the user default browser - * - * @param {String} url the URL to open - */ -function open (url) { - tauri.open(url) -} - -export { - setTitle, - open -} diff --git a/cli/tauri.js/package.json b/cli/tauri.js/package.json index 79bdc63c6..337cddcfe 100644 --- a/cli/tauri.js/package.json +++ b/cli/tauri.js/package.json @@ -10,13 +10,15 @@ "url": "https://opencollective.com/tauri" }, "scripts": { - "build": "webpack --progress", + "build": "rollup -c --silent && webpack --progress", + "build:webpack": "webpack --progress", + "build:api": "rollup -c", "build-release": "yarn build --display none --progress false", "test": "jest --runInBand --no-cache --testPathIgnorePatterns=\"(build|dev)\"", "pretest": "yarn build", "prepublishOnly": "yarn build-release", "test:local": "jest --runInBand", - "lint": "eslint --ext ts ./src/**/*.ts", + "lint": "eslint --ext ts ./src/**/*.ts ./api-src/**/*.ts", "lint-fix": "eslint --fix --ext ts ./src/**/*.ts", "lint:lockfile": "lockfile-lint --path yarn.lock --type yarn --validate-https --allowed-hosts npm yarn", "build:tauri[rust]": "cd ../tauri && TAURI_DIST_DIR=../../test/fixture/dist TAURI_DIR=../test/fixture cargo publish --dry-run --allow-dirty" @@ -72,6 +74,12 @@ "@babel/core": "7.10.3", "@babel/preset-env": "7.10.3", "@babel/preset-typescript": "7.10.1", + "@rollup/plugin-babel": "^5.0.3", + "@rollup/plugin-commonjs": "13.0.0", + "@rollup/plugin-json": "4.1.0", + "@rollup/plugin-node-resolve": "8.0.1", + "@rollup/plugin-sucrase": "^3.0.2", + "@rollup/plugin-typescript": "4.1.2", "@types/cross-spawn": "6.0.2", "@types/fs-extra": "9.0.1", "@types/http-proxy": "1.17.4", @@ -102,8 +110,12 @@ "lockfile-lint": "4.3.7", "promise": "8.1.0", "raw-loader": "4.0.1", + "rollup": "2.17.1", + "rollup-plugin-terser": "6.1.0", + "rollup-plugin-typescript2": "0.27.1", "toml-loader": "1.0.0", "ts-loader": "7.0.5", + "tslib": "2.0.0", "typescript": "3.9.5", "webpack": "4.43.0", "webpack-cli": "3.3.12", diff --git a/cli/tauri.js/rollup.config.js b/cli/tauri.js/rollup.config.js new file mode 100644 index 000000000..355ed2e82 --- /dev/null +++ b/cli/tauri.js/rollup.config.js @@ -0,0 +1,98 @@ +// rollup.config.js +import { terser } from 'rollup-plugin-terser' +import resolve from '@rollup/plugin-node-resolve' +import commonjs from '@rollup/plugin-commonjs' +import sucrase from '@rollup/plugin-sucrase' +import { getBabelOutputPlugin } from '@rollup/plugin-babel' +import pkg from './package.json' + +export default [{ + input: { + 'fs': './api-src/fs.ts', + 'dialog': './api-src/dialog.ts', + 'event': './api-src/event.ts', + 'http': './api-src/http.ts', + 'index': './api-src/index.ts', + 'process': './api-src/process.ts', + 'tauri': './api-src/tauri.ts', + 'window': './api-src/window.ts', + }, + treeshake: true, + perf: true, + output: [ + { + dir: 'api/', // if you want to consume in node but want it tiny + entryFileNames: '[name].js', + format: 'cjs', + plugins: [ terser() ], + exports: 'named', + globals: {} + }, + { + dir: 'api/esm/', // if you will be transpiling and minifying yourself + entryFileNames: '[name].js', + format: 'esm', + sourcemap: true, + exports: 'named', + globals: {} + } + ], + plugins: [ + commonjs({}), + sucrase({ + exclude: ['node_modules'], + transforms: ['typescript'] + }), + resolve({ + // pass custom options to the resolve plugin + customResolveOptions: { + moduleDirectory: 'node_modules' + } + }) + ], + external: [ + ...Object.keys(pkg.dependencies || {}), + ...Object.keys(pkg.peerDependencies || {}) + ], + watch: { + chokidar: true, + include: 'api-src/**', + exclude: 'node_modules/**' + } +}, +{ + input: { + 'bundle': './api-src/bundle.ts' + }, + output: [{ + name: '__TAURI__', + dir: 'api/', // if it needs to run in the browser + entryFileNames: 'tauri.bundle.umd.js', + format: 'umd', + plugins: [ + getBabelOutputPlugin({ + presets: [['@babel/preset-env', { modules: 'umd' }]], + allowAllFormats: true + }), + terser() + ], + globals: { + } + }], + plugins: [ + sucrase({ + exclude: ['node_modules'], + transforms: ['typescript'] + }), + resolve({ + // pass custom options to the resolve plugin + customResolveOptions: { + moduleDirectory: 'node_modules' + } + }) + ], + external: [ + ...Object.keys(pkg.dependencies || {}), + ...Object.keys(pkg.peerDependencies || {}) + ] +}] diff --git a/cli/tauri.js/src/entry.ts b/cli/tauri.js/src/entry.ts deleted file mode 100644 index 438262efe..000000000 --- a/cli/tauri.js/src/entry.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ensureDirSync, writeFileSync } from 'fs-extra' -import { template } from 'lodash' -import path from 'path' -import { TauriConfig } from './types/config' - -export const generate = (outDir: string, cfg: TauriConfig): void => { - // this MUST be from the templates repo - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-var-requires - const apiTemplate = require('../templates/tauri.js').default - const compiledApi = template(apiTemplate) - - ensureDirSync(outDir) - writeFileSync(path.join(outDir, 'tauri.js'), compiledApi(cfg), 'utf-8') -} diff --git a/cli/tauri.js/src/runner.ts b/cli/tauri.js/src/runner.ts index a58381dc7..116b4b584 100644 --- a/cli/tauri.js/src/runner.ts +++ b/cli/tauri.js/src/runner.ts @@ -9,7 +9,6 @@ import http from 'http' import * as net from 'net' import os from 'os' import { findClosestOpenPort } from './helpers/net' -import * as entry from './entry' import { tauriDir, appDir } from './helpers/app-paths' import logger from './helpers/logger' import onShutdown from './helpers/on-shutdown' @@ -75,8 +74,6 @@ class Runner { this.__whitelistApi(cfg, cargoManifest) this.__rewriteManifest(cargoManifest as unknown as toml.JsonMap) - entry.generate(tauriDir, cfg) - const runningDevServer = devPath.startsWith('http') let inlinedAssets: string[] = [] @@ -94,13 +91,13 @@ class Runner { selfHandleResponse: true }) - proxy.on('proxyRes', function(proxyRes: http.IncomingMessage, req: http.IncomingMessage, res: http.ServerResponse) { + proxy.on('proxyRes', function (proxyRes: http.IncomingMessage, req: http.IncomingMessage, res: http.ServerResponse) { if (req.url === '/') { const body: Uint8Array[] = [] - proxyRes.on('data', function(chunk: Uint8Array) { + proxyRes.on('data', function (chunk: Uint8Array) { body.push(chunk) }) - proxyRes.on('end', function() { + proxyRes.on('end', function () { const bodyStr = body.join('') const indexDir = os.tmpdir() writeFileSync(path.join(indexDir, 'index.html'), bodyStr) @@ -227,8 +224,6 @@ class Runner { this.__whitelistApi(cfg, cargoManifest as unknown as CargoManifest) this.__rewriteManifest(cargoManifest) - entry.generate(tauriDir, cfg) - const inlinedAssets = (await this.__parseHtml(cfg, cfg.build.distDir)).inlinedAssets process.env.TAURI_INLINED_ASSSTS = inlinedAssets.join('|') @@ -307,9 +302,17 @@ class Runner { } const tauriScript = document.createElement('script') - tauriScript.text = readFileSync(path.join(tauriDir, 'tauri.js')).toString() + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-member-access + tauriScript.text = require('../templates/tauri.js').default document.head.insertBefore(tauriScript, document.head.firstChild) + if (cfg.build.withGlobalTauri) { + const tauriUmdScript = document.createElement('script') + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires, @typescript-eslint/no-unsafe-member-access + tauriUmdScript.text = require('../api/tauri.bundle.umd').default + document.head.insertBefore(tauriUmdScript, document.head.firstChild) + } + const csp = cfg.tauri.security.csp if (csp) { const cspTag = document.createElement('meta') @@ -501,7 +504,7 @@ class Runner { } if (cfg.tauri.cli) { - tomlFeatures.push('cli') + tomlFeatures.push('cli') } if (typeof manifest.dependencies.tauri === 'string') { diff --git a/cli/tauri.js/src/types/config.ts b/cli/tauri.js/src/types/config.ts index e7885a689..fe666f419 100644 --- a/cli/tauri.js/src/types/config.ts +++ b/cli/tauri.js/src/types/config.ts @@ -37,6 +37,7 @@ export interface TauriConfig { devPath: string beforeDevCommand?: string beforeBuildCommand?: string + withGlobalTauri?: boolean } ctx: { prod?: boolean diff --git a/cli/tauri.js/templates/mutation-observer.js b/cli/tauri.js/templates/mutation-observer.js index 7c8f225bd..3d8a1cbe8 100644 --- a/cli/tauri.js/templates/mutation-observer.js +++ b/cli/tauri.js/templates/mutation-observer.js @@ -1,7 +1,7 @@ (function () { function loadAsset(path, type) { if (path) { - window.tauri.loadAsset(path, type) + window.__TAURI__.loadAsset(path, type) } } diff --git a/cli/tauri.js/templates/src-tauri/_gitignore b/cli/tauri.js/templates/src-tauri/_gitignore index 5dae1ee09..270a92d27 100755 --- a/cli/tauri.js/templates/src-tauri/_gitignore +++ b/cli/tauri.js/templates/src-tauri/_gitignore @@ -6,6 +6,5 @@ WixTools # These are backup files generated by rustfmt **/*.rs.bk -tauri.js config.json bundle.json diff --git a/cli/tauri.js/templates/tauri.esm.js b/cli/tauri.js/templates/tauri.esm.js deleted file mode 100644 index ad3eecc49..000000000 --- a/cli/tauri.js/templates/tauri.esm.js +++ /dev/null @@ -1,385 +0,0 @@ -/* eslint-disable */ - -/** - * * THIS FILE IS GENERATED AUTOMATICALLY. - * DO NOT EDIT. - * - * Please whitelist these API functions in tauri.conf.json - * - **/ - -// open links with the Tauri API - -/** - * @module tauri - * @description This API interface makes powerful interactions available - * to be run on client side applications. They are opt-in features, and - * must be enabled in tauri.conf.json - * - * Each binding MUST provide these interfaces in order to be compliant, - * and also whitelist them based upon the developer's settings. - */ - -function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1) -} - -const uid = function () { - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + - s4() + '-' + s4() + s4() + s4() -} - -<% if (ctx.dev) { %> -/** - * @name __whitelistWarning - * @description Present a stylish warning to the developer that their API - * call has not been whitelisted in tauri.conf.json - * @param {String} func - function name to warn - * @private - */ -const __whitelistWarning = function (func) { - console.warn('%c[Tauri] Danger \ntauri.' + func + ' not whitelisted 💣\n%c\nAdd to tauri.conf.json: \n\ntauri: \n whitelist: { \n ' + 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', ' ') -} -<% } %> - -<% if (ctx.dev) { %> -/** - * @name __reject - * @description is a private promise used to deflect un-whitelisted tauri API calls - * Its only purpose is to maintain thenable structure in client code without - * breaking the application - * * @type {Promise} - * @private - */ -<% } %> -const __reject = new Promise((reject) => { reject }) - -window.tauri = { -<% if (ctx.dev) { %> - /** - * @name invoke - * @description Calls a Tauri Core feature, such as setTitle - * @param {Object} args - */ -<% } %> - invoke (args) { - Object.freeze(args) - window.external.invoke(JSON.stringify(args)) - }, - -<% if (ctx.dev) { %> - /** - * @name listen - * @description Add an event listener to Tauri backend - * @param {String} event - * @param {Function} handler - * @param {Boolean} once - */ -<% } %> - listen (event, handler, once = false) { - this.invoke({ - cmd: 'listen', - event, - handler: this.transformCallback(handler, once), - once - }) - }, - -<% if (ctx.dev) { %> - /** - * @name emit - * @description Emits an evt to the Tauri back end - * @param {String} evt - * @param {Object} payload - */ -<% } %> - emit (evt, payload) { - this.invoke({ - cmd: 'emit', - event: evt, - payload - }) - }, - -<% if (ctx.dev) { %> - /** - * @name transformCallback - * @description Registers a callback with a uid - * @param {Function} callback - * @param {Boolean} once - * @returns {*} - */ -<% } %> - transformCallback (callback, once = true) { - const identifier = Object.freeze(uid()) - window[identifier] = (result) => { - if (once) { - delete window[identifier] - } - return callback && callback(result) - } - return identifier - }, - -<% if (ctx.dev) { %> - /** - * @name promisified - * @description Turns a request into a chainable promise - * @param {Object} args - * @returns {Promise} - */ -<% } %> - promisified (args) { - return new Promise((resolve, reject) => { - this.invoke({ - callback: this.transformCallback(resolve), - error: this.transformCallback(reject), - ...args - }) - }) - }, - -<% if (ctx.dev) { %> - /** - * @name readTextFile - * @description Accesses a non-binary file on the user's filesystem - * and returns the content. Permissions based on the app's PID owner - * @param {String} path - * @returns {*|Promise|Promise} - */ -<% } %> - readTextFile (path) { - <% if (tauri.whitelist.readTextFile === true || tauri.whitelist.all === true) { %> - Object.freeze(path) - return this.promisified({ cmd: 'readTextFile', path }) - <% } else { %> - <% if (ctx.dev) { %> - __whitelistWarning('readTextFile') - <% } %> - return __reject - <% } %> - }, - -<% if (ctx.dev) { %> - /** - * @name readBinaryFile - * @description Accesses a binary file on the user's filesystem - * and returns the content. Permissions based on the app's PID owner - * @param {String} path - * @returns {*|Promise|Promise} - */ -<% } %> - readBinaryFile (path) { - <% if (tauri.whitelist.readBinaryFile === true || tauri.whitelist.all === true) { %> - Object.freeze(path) - return this.promisified({ cmd: 'readBinaryFile', path }) - <% } else { %> - <% if (ctx.dev) { %> - __whitelistWarning('readBinaryFile') - <% } %> - return __reject - <% } %> - }, - -<% if (ctx.dev) { %> - /** - * @name writeFile - * @description Write a file to the Local Filesystem. - * Permissions based on the app's PID owner - * @param {Object} cfg - * @param {String} cfg.file - * @param {String|Binary} cfg.contents - */ -<% } %> - writeFile (cfg) { - <% if (tauri.whitelist.writeFile === true || tauri.whitelist.all === true) { %> - Object.freeze(cfg) - this.invoke({ cmd: 'writeFile', file: cfg.file, contents: cfg.contents }) - <% } else { %> - <% if (ctx.dev) { %> - __whitelistWarning('writeFile') - <% } %> - return __reject - <% } %> - }, - -<% if (ctx.dev) { %> - /** - * @name writeBinaryFile - * @description Write a binary 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 - */ -<% } %> - writeBinaryFile (cfg) { - <% if (tauri.whitelist.writeBinaryFile === true || tauri.whitelist.all === true) { %> - Object.freeze(cfg) - this.invoke({ cmd: 'writeBinaryFile', file: cfg.file, contents: cfg.contents }) - <% } else { %> - <% if (ctx.dev) { %> - __whitelistWarning('writeBinaryFile') - <% } %> - return __reject - <% } %> - }, - -<% if (ctx.dev) { %> - /** - * @name listFiles - * @description Get the files in a path. - * Permissions based on the app's PID owner - * @param {String} path - * @returns {*|Promise|Promise} - */ -<% } %> - listFiles (path) { - <% if (tauri.whitelist.listFiles === true || tauri.whitelist.all === true) { %> - Object.freeze(path) - return this.promisified({ cmd: 'listFiles', path }) - <% } else { %> - <% if (ctx.dev) { %> - __whitelistWarning('listFiles') - <% } %> - return __reject - <% } %> - }, - -<% if (ctx.dev) { %> - /** - * @name listDirs - * @description Get the directories in a path. - * Permissions based on the app's PID owner - * @param {String} path - * @returns {*|Promise|Promise} - */ -<% } %> - listDirs (path) { - <% if (tauri.whitelist.listDirs === true || tauri.whitelist.all === true) { %> - Object.freeze(path) - return this.promisified({ cmd: 'listDirs', path }) - <% } else { %> - <% if (ctx.dev) { %> - __whitelistWarning('listDirs') - <% } %> - return __reject - <% } %> - }, - -<% if (ctx.dev) { %> - /** - * @name setTitle - * @description Set the application's title - * @param {String} title - */ -<% } %> - setTitle (title) { - <% if (tauri.whitelist.setTitle === true || tauri.whitelist.all === true) { %> - Object.freeze(title) - this.invoke({ cmd: 'setTitle', title }) - <% } else { %> - <% if (ctx.dev) { %> - __whitelistWarning('setTitle') - <% } %> - return __reject - <% } %> - }, - - <% if (ctx.dev) { %> - /** - * @name open - * @description Open an URI - * @param {String} uri - */ -<% } %> - open (uri) { - <% if (tauri.whitelist.open === true || tauri.whitelist.all === true) { %> - Object.freeze(uri) - this.invoke({ cmd: 'open', uri }) - <% } else { %> -<% if (ctx.dev) { %> - __whitelistWarning('open') - <% } %> - return __reject - <% } %> - }, - -<% if (ctx.dev) { %> - /** - * @name execute - * @description Execute a program with arguments. - * Permissions based on the app's PID owner - * @param {String} command - * @param {String|Array} args - * @returns {*|Promise|Promise} - */ -<% } %> - execute (command, args) { - <% if (tauri.whitelist.execute === true || tauri.whitelist.all === true) { %> - Object.freeze(command) - if (typeof args === 'string' || typeof args === 'object') { - Object.freeze(args) - } - return this.promisified({ cmd: 'execute', command, args: typeof (args) === 'string' ? [args] : args }) - <% } else { %> - <% if (ctx.dev) { %> - __whitelistWarning('execute') - <% } %> - return __reject - <% } %> - }, - -<% if (ctx.dev) { %> - /** - * @name bridge - * @description Securely pass a message to the backend. - * @example - * this.$q.tauri.bridge('QBP/1/ping/client-1', 'pingback') - * @param {String} command - a compressed, slash-delimited and - * versioned API call to the backend. - * @param {String|Object}payload - * @returns {*|Promise|Promise} - */ -<% } %> - bridge (command, payload) { -<% if (tauri.whitelist.bridge === true || tauri.whitelist.all === true) { %> - Object.freeze(command) - if (typeof payload === 'string' || typeof payload === 'object') { - Object.freeze(payload) - } - return this.promisified({ cmd: 'bridge', command, payload: typeof (payload) === 'object' ? [payload] : payload }) -<% } else { %> -<% if (ctx.dev) { %> - __whitelistWarning('bridge') -<% } %> - return __reject -<% } %> - }, - - <% if (ctx.dev) { %> - /** - * @name setup - * @description Inform Rust that the webview has initialized and is - * ready for communication - */ - <% } %> - setup () { - document.querySelector('body').addEventListener('click', function (e) { - let target = e.target - while (target != null) { - if (target.matches ? target.matches('a') : target.msMatchesSelector('a')) { - tauri.open(target.href) - break - } - target = target.parentElement - } - }, true) - - tauri.invoke({ - cmd: 'init' - }) - } -} diff --git a/cli/tauri.js/templates/tauri.js b/cli/tauri.js/templates/tauri.js index ed9c91ba1..f50453259 100644 --- a/cli/tauri.js/templates/tauri.js +++ b/cli/tauri.js/templates/tauri.js @@ -1,23 +1,3 @@ -/* eslint-disable */ - -/** - * * THIS FILE IS GENERATED AUTOMATICALLY. - * DO NOT EDIT. - * - * Please whitelist these API functions in tauri.conf.json - * - **/ - -/** - * @module tauri - * @description This API interface makes powerful interactions available - * to be run on client side applications. They are opt-in features, and - * must be enabled in tauri.conf.json - * - * Each binding MUST provide these interfaces in order to be compliant, - * and also whitelist them based upon the developer's settings. - */ - // polyfills if (!String.prototype.startsWith) { String.prototype.startsWith = function (searchString, position) { @@ -27,7 +7,6 @@ if (!String.prototype.startsWith) { } // makes the window.external.invoke API available after window.location.href changes - switch (navigator.platform) { case "Macintosh": case "MacPPC": @@ -63,765 +42,99 @@ switch (navigator.platform) { 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; } - - 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 - } - - <% 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() - } - <% } %> - - <% if (ctx.dev) { %> - /** - * @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} - * @private - */ - <% } %> - var __reject = function () { - return new Promise(function (_, reject) { - 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 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() - <% } %> - }, - - <% 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() - <% } %> - }, - - <% 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(); - - window[identifier] = function (result) { - if (once) { - delete window[identifier]; - } - - return callback && callback(result); - }; - - return identifier; - }, - - <% if (ctx.dev) { %> - /** - * @name promisified - * @description Turns a request into a chainable promise - * @param {Object} args - * @returns {Promise} - */ - <% } %> - 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)); + 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; }); - }, - - <% 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|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() - <% } %> - }, - - <% 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|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() - <% } %> - }, - - <% 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() - <% } %> - }, - - <% if (ctx.dev) { %> - /** - * @name writeBinaryFile - * @description Write a binary 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] - */ - <% } %> - writeBinaryFile: function writeBinaryFile(cfg, options) { - <% if (tauri.whitelist.writeBinaryFile === true || tauri.whitelist.all === true) { %> - if (_typeof(cfg) === 'object') { - Object.freeze(cfg); - } - return this.promisified({ - cmd: 'writeBinaryFile', - file: cfg.file, - contents: cfg.contents, - options: options - }); - <% } else { %> - <% if (ctx.dev) { %> - return __whitelistWarning('writeBinaryFile') - <% } %> - 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|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 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|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() - <% } %> - }, - - <% 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|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() - <% } %> - }, - - <% 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|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() - <% } %> - }, - - <% 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|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() - <% } %> - }, - - <% 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|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() - <% } %> - }, - - <% 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() - <% } %> - }, - - <% 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') - <% } %> - 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|Promise} - */ - <% } %> - execute: function execute(command, args) { - <% if (tauri.whitelist.execute === true || tauri.whitelist.all === true) { %> - - if (_typeof(args) === 'object') { - Object.freeze(args); - } - - 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} 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') - <% } %> - 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} 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() - <% } %> - }, - - <% 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} - */ - <% } %> - 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() - <% } %> - }, - <% 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|Promise} - */ - <% } %> - notification: function notification(options) { - <% if (tauri.whitelist.notification === true || tauri.whitelist.all === true) { %> - - 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('notification') - <% } %> - return __reject() - <% } %> - }, - - 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() - <% } %> - }, - - loadAsset: function loadAsset(assetName, assetType) { - return this.promisified({ - cmd: 'loadAsset', - asset: assetName, - assetType: assetType || 'unknown' - }) - }, - - cliMatches: function () { - <% if (tauri.cli) { %> - return this.promisified({ - cmd: 'cliMatches' - }) - <% } else { %> - <% if (ctx.dev) { %> - console.error('You must add the CLI args configuration under tauri.conf.json > tauri > cli') - return __reject() - <% } %> - return __reject() - <% } %> - + keys.push.apply(keys, symbols); } - }; + return keys; + } - <% 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 = {} + 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)); + }); } - options.title = title - window.tauri.notification(options) } - window.Notification.requestPermission = window.tauri.requestNotificationPermission + return target; + } - Object.defineProperty(window.Notification, 'permission', { - enumerable: true, - get: function () { - return notificationPermission - }, - set: function(v) { - if (!notificationPermissionSettable) { - throw new Error("Readonly property") - } - notificationPermission = v + 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; + } + + if (!window.__TAURI__) { + window.__TAURI__ = {} + } + window.__TAURI__.invoke = function invoke(args) { + window.external.invoke(JSON.stringify(args)) + } + + window.__TAURI__.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] } - }); - setNotificationPermission('loading') - window.tauri.isNotificationPermissionGranted() - .then(function (response) { - if (response === null) { - setNotificationPermission('default') - } else { - setNotificationPermission(response ? 'granted' : 'denied') - } - }) - <% } %> + return callback && callback(result) + } + return identifier; + } + + window.__TAURI__.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)) + }) + } + + window.__TAURI__.loadAsset = function loadAsset(assetName, assetType) { + return this.promisified({ + cmd: 'loadAsset', + asset: assetName, + assetType: assetType || 'unknown' + }) + } // init tauri API try { - window.tauri.invoke({ + window.__TAURI__.invoke({ cmd: 'init' }) } catch (e) { window.addEventListener('DOMContentLoaded', function () { - window.tauri.invoke({ + window.__TAURI__.invoke({ cmd: 'init' }) }, true) @@ -831,7 +144,7 @@ switch (navigator.platform) { var target = e.target while (target != null) { if (target.matches ? target.matches('img') : target.msMatchesSelector('img')) { - window.tauri.loadAsset(target.src, 'image') + window.__TAURI__.loadAsset(target.src, 'image') .then(function (img) { target.src = img }) @@ -848,7 +161,10 @@ switch (navigator.platform) { 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) + window.__TAURI__.invoke({ + cmd: 'open', + uri: target.href + }) e.preventDefault() } break diff --git a/cli/tauri.js/test/jest/fixtures/app/dist/index.html b/cli/tauri.js/test/jest/fixtures/app/dist/index.html index de1a75648..8ff07736e 100644 --- a/cli/tauri.js/test/jest/fixtures/app/dist/index.html +++ b/cli/tauri.js/test/jest/fixtures/app/dist/index.html @@ -1,8 +1,9 @@ + - \ No newline at end of file + diff --git a/cli/tauri.js/tsconfig.json b/cli/tauri.js/tsconfig.json index 3ef8df3e3..cac982c5f 100644 --- a/cli/tauri.js/tsconfig.json +++ b/cli/tauri.js/tsconfig.json @@ -12,7 +12,6 @@ "paths": { "types": ["src/types"] }, - "resolveJsonModule": true }, - "include": ["src"] + "include": ["src", "api-src"] } diff --git a/cli/tauri.js/webpack.config.js b/cli/tauri.js/webpack.config.js index ed9aea5b0..d787e5c7e 100644 --- a/cli/tauri.js/webpack.config.js +++ b/cli/tauri.js/webpack.config.js @@ -20,7 +20,7 @@ module.exports = { exclude: /node_modules/ }, { - test: /templates[\\/](tauri|mutation-observer)\.js/, + test: /(templates|api)[\\/].+\.js/, use: 'raw-loader' }, { diff --git a/cli/tauri.js/yarn.lock b/cli/tauri.js/yarn.lock index f289e37f4..712604fdc 100644 --- a/cli/tauri.js/yarn.lock +++ b/cli/tauri.js/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.1": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.1", "@babel/code-frame@^7.8.3": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.1.tgz#d5481c5095daa1c57e16e54c6f9198443afb49ff" integrity sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw== @@ -208,7 +208,7 @@ dependencies: "@babel/types" "^7.10.1" -"@babel/helper-module-imports@^7.10.1": +"@babel/helper-module-imports@^7.10.1", "@babel/helper-module-imports@^7.7.4": version "7.10.1" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.10.1.tgz#dd331bd45bccc566ce77004e9d05fe17add13876" integrity sha512-SFxgwYmZ3HZPyZwJRiVNLRHWuW2OgE5k2nrVs6D9Iv4PPnXVffuEHy83Sfx/l4SqF+5kyJXjAyUmrG7tNm+qVg== @@ -1189,6 +1189,72 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" +"@rollup/plugin-babel@^5.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.0.3.tgz#8d416865b0da79faf14e07c8d233abe0eac0753d" + integrity sha512-NlaPf4E6YFxeOCbqc+A2PTkB1BSy3rfKu6EJuQ1MGhMHpTVvMqKi6Rf0DlwtnEsTNK9LueUgsGEgp5Occ4KDVA== + dependencies: + "@babel/helper-module-imports" "^7.7.4" + "@rollup/pluginutils" "^3.0.8" + +"@rollup/plugin-commonjs@13.0.0": + version "13.0.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-13.0.0.tgz#8a1d684ba6848afe8b9e3d85649d4b2f6f7217ec" + integrity sha512-Anxc3qgkAi7peAyesTqGYidG5GRim9jtg8xhmykNaZkImtvjA7Wsqep08D2mYsqw1IF7rA3lYfciLgzUSgRoqw== + dependencies: + "@rollup/pluginutils" "^3.0.8" + commondir "^1.0.1" + estree-walker "^1.0.1" + glob "^7.1.2" + is-reference "^1.1.2" + magic-string "^0.25.2" + resolve "^1.11.0" + +"@rollup/plugin-json@4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3" + integrity sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw== + dependencies: + "@rollup/pluginutils" "^3.0.8" + +"@rollup/plugin-node-resolve@8.0.1": + version "8.0.1" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-8.0.1.tgz#364b5938808ee6b5164dea5ef7291be3f7395199" + integrity sha512-KIeAmueDDaYMqMBnUngLVVZhURwxA12nq/YB6nGm5/JpVyOMwI1fCVU3oL/dAnnLBG7oiPXntO5LHOiMrfNXCA== + dependencies: + "@rollup/pluginutils" "^3.0.8" + "@types/resolve" "0.0.8" + builtin-modules "^3.1.0" + deep-freeze "^0.0.1" + deepmerge "^4.2.2" + is-module "^1.0.0" + resolve "^1.14.2" + +"@rollup/plugin-sucrase@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@rollup/plugin-sucrase/-/plugin-sucrase-3.0.2.tgz#681cacef67a0328a903e5f431f461d73899b545b" + integrity sha512-6fglZKerNRlGN7MJLuUX9fFpOT/md4mqje5pC1gltJAdFo++5NqTfPmcJ9sgXSytwqAK6jiwnUQwbBovieaEzw== + dependencies: + "@rollup/pluginutils" "^3.0.1" + sucrase "^3.10.1" + +"@rollup/plugin-typescript@4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-4.1.2.tgz#6f910430276ae3e53a47a12ad65820627e7b6ad9" + integrity sha512-+7UlGat/99e2JbmGNnIauxwEhYLwrL7adO/tSJxUN57xrrS3Ps+ZzYpLCDGPZJ57j+ZJTZLLN89KXW9JMEB+jg== + dependencies: + "@rollup/pluginutils" "^3.0.1" + resolve "^1.14.1" + +"@rollup/pluginutils@^3.0.1", "@rollup/pluginutils@^3.0.8": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" + integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== + dependencies: + "@types/estree" "0.0.39" + estree-walker "^1.0.1" + picomatch "^2.2.2" + "@sindresorhus/is@^0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" @@ -1314,6 +1380,16 @@ resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== +"@types/estree@0.0.39": + version "0.0.39" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" + integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== + +"@types/estree@0.0.44": + version "0.0.44" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.44.tgz#980cc5a29a3ef3bea6ff1f7d021047d7ea575e21" + integrity sha512-iaIVzr+w2ZJ5HkidlZ3EJM8VTZb2MJLCjw3V+505yVts0gRC4UMvjw0d1HPtGqI/HQC/KdsYtayfzl+AXY2R8g== + "@types/events@*": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" @@ -1447,6 +1523,13 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24" integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug== +"@types/resolve@0.0.8": + version "0.0.8" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-0.0.8.tgz#f26074d238e02659e323ce1a13d041eee280e194" + integrity sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ== + dependencies: + "@types/node" "*" + "@types/sharp@0.25.0": version "0.25.0" resolved "https://registry.yarnpkg.com/@types/sharp/-/sharp-0.25.0.tgz#e21501779b110e26f33cec7fb1969e49e0cdc3c0" @@ -1879,6 +1962,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0, ansi-styles@^4.2.1: "@types/color-name" "^1.1.1" color-convert "^2.0.1" +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= + anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -2439,6 +2527,11 @@ buffer@^5.2.1, buffer@^5.5.0: base64-js "^1.0.2" ieee754 "^1.1.4" +builtin-modules@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484" + integrity sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw== + builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" @@ -2845,6 +2938,11 @@ commander@^2.20.0, commander@~2.20.3: resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== +commander@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + commander@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" @@ -3292,6 +3390,11 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== +deep-freeze@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/deep-freeze/-/deep-freeze-0.0.1.tgz#3a0b0005de18672819dfd38cd31f91179c893e84" + integrity sha1-OgsABd4YZygZ39OM0x+RF5yJPoQ= + deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -3897,6 +4000,11 @@ estraverse@^5.1.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642" integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw== +estree-walker@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" + integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -4247,6 +4355,15 @@ find-cache-dir@^2.1.0: make-dir "^2.0.0" pkg-dir "^3.0.0" +find-cache-dir@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" + integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -4362,6 +4479,15 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== +fs-extra@8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-extra@9.0.1: version "9.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" @@ -4522,7 +4648,7 @@ glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0: dependencies: is-glob "^4.0.1" -glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@7.1.6, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -5237,6 +5363,11 @@ is-installed-globally@^0.3.1: global-dirs "^2.0.1" is-path-inside "^3.0.1" +is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" + integrity sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE= + is-natural-number@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" @@ -5301,6 +5432,13 @@ is-potential-custom-element-name@^1.0.0: resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397" integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c= +is-reference@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.0.tgz#d938b0cf85a0df09849417b274f02fb509293599" + integrity sha512-ZVxq+5TkOx6GQdnoMm2aRdCKADdcrOWXLGzGT+vIA8DMpqEJaRk5AL1bS80zJ2bjHunVmjdzfCt0e4BymIEqKQ== + dependencies: + "@types/estree" "0.0.44" + is-regex@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" @@ -5932,6 +6070,13 @@ json5@^2.1.2: dependencies: minimist "^1.2.5" +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + jsonfile@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179" @@ -6315,6 +6460,13 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +magic-string@^0.25.2: + version "0.25.7" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" + integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== + dependencies: + sourcemap-codec "^1.4.4" + make-dir@^1.0.0, make-dir@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" @@ -6330,7 +6482,7 @@ make-dir@^2.0.0: pify "^4.0.1" semver "^5.6.0" -make-dir@^3.0.0: +make-dir@^3.0.0, make-dir@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== @@ -6594,6 +6746,20 @@ ms@2.1.2, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + nan@^2.12.1: version "2.14.1" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" @@ -7237,7 +7403,7 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1: +picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1, picomatch@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== @@ -7290,7 +7456,7 @@ pkg-dir@^3.0.0: dependencies: find-up "^3.0.0" -pkg-dir@^4.2.0: +pkg-dir@^4.1.0, pkg-dir@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== @@ -7537,7 +7703,7 @@ querystring@0.2.0: resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== @@ -7884,7 +8050,14 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.10.0, resolve@^1.10.1, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.3.2: +resolve@1.15.1: + version "1.15.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" + integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== + dependencies: + path-parse "^1.0.6" + +resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.0, resolve@^1.13.1, resolve@^1.14.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.3.2: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== @@ -7945,6 +8118,34 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +rollup-plugin-terser@6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-6.1.0.tgz#071866585aea104bfbb9dd1019ac523e63c81e45" + integrity sha512-4fB3M9nuoWxrwm39habpd4hvrbrde2W2GG4zEGPQg1YITNkM3Tqur5jSuXlWNzbv/2aMLJ+dZJaySc3GCD8oDw== + dependencies: + "@babel/code-frame" "^7.8.3" + jest-worker "^26.0.0" + serialize-javascript "^3.0.0" + terser "^4.7.0" + +rollup-plugin-typescript2@0.27.1: + version "0.27.1" + resolved "https://registry.yarnpkg.com/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.27.1.tgz#4f27193408a8f040139eed3e3db7b0c7f3668200" + integrity sha512-RJl77Bbj1EunAQDC3dK/O2HWuSUX3oJbRGzyLoS5o9W4Hs1Nix3Gavqj1Lzs5Y6Ff4H2xXfmZ1WWUQCYocSbzQ== + dependencies: + "@rollup/pluginutils" "^3.0.8" + find-cache-dir "^3.3.1" + fs-extra "8.1.0" + resolve "1.15.1" + tslib "1.11.2" + +rollup@2.17.1: + version "2.17.1" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.17.1.tgz#d01a27c1b76e42efe2cd786609589f6332e81aa6" + integrity sha512-lVrtCXJ+08Eapa0SfApLmRNWNWm2FsYFnLPIJZJvZz2uI2Gv+dfPyu1zgF7KKF/HYFJDvjxbdCbI8lUVMnG7Sg== + optionalDependencies: + fsevents "~2.1.2" + rsvp@^4.8.4: version "4.8.5" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" @@ -8092,6 +8293,13 @@ serialize-javascript@^2.1.2: resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== +serialize-javascript@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea" + integrity sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg== + dependencies: + randombytes "^2.1.0" + set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -8331,6 +8539,11 @@ source-map@^0.7.3: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== +sourcemap-codec@^1.4.4: + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== + spdx-correct@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" @@ -8653,6 +8866,25 @@ strip-outer@^1.0.0: dependencies: escape-string-regexp "^1.0.2" +sucrase@^3.10.1: + version "3.15.0" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.15.0.tgz#78596a78be7264a65b52ed8d873883413ef0220c" + integrity sha512-05TJOUfMgckH7wKqfk/1p4G6q16nIeW/GHQwD44vkT0mQMqqzgfHCwkX3whNmwyOo7nVF0jDLwVu/qOBTtsscw== + dependencies: + commander "^4.0.0" + glob "7.1.6" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + ts-interface-checker "^0.1.9" + +supports-color@6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -8822,6 +9054,15 @@ terser@^4.1.2: source-map "~0.6.1" source-map-support "~0.5.12" +terser@^4.7.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" + integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== + dependencies: + commander "^2.20.0" + source-map "~0.6.1" + source-map-support "~0.5.12" + test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" @@ -8843,6 +9084,20 @@ then-fs@^2.0.0: dependencies: promise ">=3.2 <8" +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY= + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + throat@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" @@ -8978,6 +9233,11 @@ trim-repeated@^1.0.0: dependencies: escape-string-regexp "^1.0.2" +ts-interface-checker@^0.1.9: + version "0.1.11" + resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.11.tgz#07e7eddb08212f83fef12c253d0cefa8c70fe1bc" + integrity sha512-Jx6cFBiuCQrRl3CgoIOamIE/toZ8jQJbIlsLGpkBiUpCEUyFcyZ2pvjP8kSXIcz8V5v/murgm/5EfIQapUmh6A== + ts-loader@7.0.5: version "7.0.5" resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-7.0.5.tgz#789338fb01cb5dc0a33c54e50558b34a73c9c4c5" @@ -8999,6 +9259,16 @@ tsconfig-paths@^3.9.0: minimist "^1.2.0" strip-bom "^3.0.0" +tslib@1.11.2: + version "1.11.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.2.tgz#9c79d83272c9a7aaf166f73915c9667ecdde3cc9" + integrity sha512-tTSkux6IGPnUGUd1XAZHcpu85MOkIl5zX49pO+jfsie3eP0B6pyhOlLXm3cAC6T7s+euSDDUUV+Acop5WmtkVg== + +tslib@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.0.tgz#18d13fc2dce04051e20f074cc8387fd8089ce4f3" + integrity sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g== + tslib@^1.8.1, tslib@^1.9.0: version "1.13.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" @@ -9148,6 +9418,11 @@ unique-string@^2.0.0: dependencies: crypto-random-string "^2.0.0" +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + universalify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" diff --git a/maskfile.md b/maskfile.md index 801be8d6f..c7fb92741 100644 --- a/maskfile.md +++ b/maskfile.md @@ -58,9 +58,9 @@ if ((Test-Path $dist_path -PathType Any) -Or (Test-Path $src_path -PathType Any) cargo install --path cli\tauri-bundler --force cargo install cargo-web - # install the tauri Node CLI. + # install the tauri Node CLI and transpile the TS version of the API. cd cli\tauri.js - yarn; yarn build + yarn; yarn build; } ``` diff --git a/tauri/Cargo.toml b/tauri/Cargo.toml index 136707f53..bccf6e8aa 100644 --- a/tauri/Cargo.toml +++ b/tauri/Cargo.toml @@ -37,6 +37,7 @@ tauri-api = { version = "0.6", path = "../tauri-api" } [build-dependencies] tauri_includedir_codegen = "0.6.0" +cfg_aliases = "0.1.0" [dev-dependencies] proptest = "0.10.0" diff --git a/tauri/build.rs b/tauri/build.rs index 882ad2abe..86aed2254 100644 --- a/tauri/build.rs +++ b/tauri/build.rs @@ -1,3 +1,5 @@ +use cfg_aliases::cfg_aliases; + #[cfg(any(feature = "embedded-server", feature = "no-server"))] use std::{ env, @@ -72,4 +74,48 @@ fn shared() { tauri_path.push("tauri.conf.json"); println!("cargo:rerun-if-changed={:?}", tauri_path); } + + cfg_aliases! { + embedded_server: { feature = "embedded-server" }, + no_server: { feature = "no-server" }, + assets: { any(feature = "embedded-server", feature = "no-server") }, + dev: { not(any(feature = "embedded-server", feature = "no-server")) }, + + all_api: { feature = "all-api" }, + + // fs + read_text_file: { any(all_api, feature = "read-text-file") }, + read_binary_file: { any(all_api, feature = "read-binary-file") }, + write_file: { any(all_api, feature = "write-file") }, + write_binary_file: { any(all_api, feature = "write-binary-file") }, + read_dir: { any(all_api, feature = "read-dir") }, + copy_file: { any(all_api, feature = "copy-file") }, + create_dir: { any(all_api, feature = "create_dir") }, + remove_dir: { any(all_api, feature = "remove-dir") }, + remove_file: { any(all_api, feature = "remove-file") }, + rename_file: { any(all_api, feature = "rename-file") }, + + // window + set_title: { any(all_api, feature = "set-title") }, + open: { any(all_api, feature = "open") }, + + // process + execute: { any(all_api, feature = "execute") }, + + // event + event: { any(all_api, feature = "event") }, + + // dialog + open_dialog: { any(all_api, feature = "open-dialog") }, + save_dialog: { any(all_api, feature = "save-dialog") }, + + // http + http_request: { any(all_api, feature = "http-request") }, + + // cli + cli: { feature = "cli" }, + + // notification + notification: { any(all_api, feature = "notification") }, + } } diff --git a/tauri/examples/communication/dist/cli.js b/tauri/examples/communication/dist/cli.js index ec80da7bc..d9862d81b 100644 --- a/tauri/examples/communication/dist/cli.js +++ b/tauri/examples/communication/dist/cli.js @@ -1,5 +1,5 @@ document.getElementById('cli-matches').addEventListener('click', function () { - window.tauri.cliMatches() + window.__TAURI__.cli.getMatches() .then(registerResponse) .catch(registerResponse) }) diff --git a/tauri/examples/communication/dist/communication.js b/tauri/examples/communication/dist/communication.js index 9ce2b1548..50386fe13 100644 --- a/tauri/examples/communication/dist/communication.js +++ b/tauri/examples/communication/dist/communication.js @@ -1,5 +1,5 @@ document.getElementById('log').addEventListener('click', function () { - window.tauri.invoke({ + window.__TAURI__.invoke({ cmd: 'logOperation', event: 'tauri-click', payload: 'this payload is optional because we used Option in Rust' @@ -7,7 +7,7 @@ document.getElementById('log').addEventListener('click', function () { }) document.getElementById('request').addEventListener('click', function () { - window.tauri.promisified({ + window.__TAURI__.promisified({ cmd: 'performRequest', endpoint: 'dummy endpoint arg', body: { @@ -18,6 +18,5 @@ document.getElementById('request').addEventListener('click', function () { }) document.getElementById('event').addEventListener('click', function () { - window.tauri.emit('js-event', 'this is the payload string') + window.__TAURI__.event.emit('js-event', 'this is the payload string') }) - diff --git a/tauri/examples/communication/dist/dialog.js b/tauri/examples/communication/dist/dialog.js index 73d17291e..0c48966de 100644 --- a/tauri/examples/communication/dist/dialog.js +++ b/tauri/examples/communication/dist/dialog.js @@ -4,7 +4,7 @@ var multipleInput = document.getElementById('dialog-multiple') var directoryInput = document.getElementById('dialog-directory') document.getElementById('open-dialog').addEventListener('click', function () { - window.tauri.openDialog({ + window.__TAURI__.dialog.open({ defaultPath: defaultPathInput.value || null, filter: filterInput.value || null, multiple: multipleInput.checked, @@ -13,7 +13,7 @@ document.getElementById('open-dialog').addEventListener('click', function () { console.log(res) var pathToRead = res var isFile = pathToRead.match(/\S+\.\S+$/g) - window.tauri.readBinaryFile(pathToRead).then(function (response) { + window.__TAURI__.fs.readBinaryFile(pathToRead).then(function (response) { if (isFile) { if (pathToRead.includes('.png') || pathToRead.includes('.jpg')) { arrayBufferToBase64(new Uint8Array(response), function (base64) { @@ -31,7 +31,7 @@ document.getElementById('open-dialog').addEventListener('click', function () { }) document.getElementById('save-dialog').addEventListener('click', function () { - window.tauri.saveDialog({ + window.__TAURI__.dialog.save({ defaultPath: defaultPathInput.value || null, filter: filterInput.value || null }).then(registerResponse).catch(registerResponse) diff --git a/tauri/examples/communication/dist/fs.js b/tauri/examples/communication/dist/fs.js index 602548bff..016ae65f4 100644 --- a/tauri/examples/communication/dist/fs.js +++ b/tauri/examples/communication/dist/fs.js @@ -1,5 +1,6 @@ var dirSelect = document.getElementById('dir') -function getDir () { + +function getDir() { return dirSelect.value ? parseInt(dir.value) : null } @@ -23,8 +24,10 @@ addClickEnterHandler( function () { var pathToRead = pathInput.value var isFile = pathToRead.match(/\S+\.\S+$/g) - var opts = { dir: getDir() } - var promise = isFile ? window.tauri.readBinaryFile(pathToRead, opts) : window.tauri.readDir(pathToRead, opts) + var opts = { + dir: getDir() + } + var promise = isFile ? window.__TAURI__.fs.readBinaryFile(pathToRead, opts) : window.__TAURI__.fs.readDir(pathToRead, opts) promise.then(function (response) { if (isFile) { if (pathToRead.includes('.png') || pathToRead.includes('.jpg')) { @@ -38,7 +41,7 @@ addClickEnterHandler( var fileInput = document.getElementById('file-response') fileInput.value = value document.getElementById('file-save').addEventListener('click', function () { - window.tauri.writeFile({ + window.__TAURI__.fs.writeFile({ file: pathToRead, contents: fileInput.value }, { diff --git a/tauri/examples/communication/dist/http.js b/tauri/examples/communication/dist/http.js index 3ed91f95a..f7a11938d 100644 --- a/tauri/examples/communication/dist/http.js +++ b/tauri/examples/communication/dist/http.js @@ -19,5 +19,5 @@ document.getElementById('make-request').addEventListener('click', function () { } options.body = body - window.tauri.httpRequest(options).then(registerResponse).catch(registerResponse) -}) \ No newline at end of file + window.__TAURI__.http.request(options).then(registerResponse).catch(registerResponse) +}) diff --git a/tauri/examples/communication/dist/index.html b/tauri/examples/communication/dist/index.html index c969ed385..d19ee4a7c 100644 --- a/tauri/examples/communication/dist/index.html +++ b/tauri/examples/communication/dist/index.html @@ -284,21 +284,23 @@ }) } - window.tauri.listen('rust-event', function (res) { + window.__TAURI__.event.listen('rust-event', function (res) { document.getElementById('response').innerHTML = JSON.stringify(res) }) document.querySelector('.logo').addEventListener('click', function () { - window.tauri.open('https://tauri.studio/') + window.__TAURI__.window.open('https://tauri.studio/') }) var dirSelect = document.getElementById('dir') - for (var key in window.tauri.Dir) { - var value = window.tauri.Dir[key] - var opt = document.createElement("option") - opt.value = value - opt.innerHTML = key - dirSelect.appendChild(opt) + for (var key in window.__TAURI__.fs.Dir) { + if (isNaN(parseInt(key))) { + var value = window.__TAURI__.fs.Dir[key] + var opt = document.createElement("option") + opt.value = value + opt.innerHTML = key + dirSelect.appendChild(opt) + } } diff --git a/tauri/examples/communication/dist/index.tauri.html b/tauri/examples/communication/dist/index.tauri.html index ba2c94f86..54ef99f00 100644 --- a/tauri/examples/communication/dist/index.tauri.html +++ b/tauri/examples/communication/dist/index.tauri.html @@ -1,24 +1,5 @@ -


\ No newline at end of file +


\ No newline at end of file diff --git a/tauri/examples/communication/dist/window.js b/tauri/examples/communication/dist/window.js index 9edd9aea3..a1148c6e3 100644 --- a/tauri/examples/communication/dist/window.js +++ b/tauri/examples/communication/dist/window.js @@ -4,7 +4,7 @@ addClickEnterHandler( document.getElementById('open-url'), urlInput, function () { - window.tauri.open(urlInput.value) + window.__TAURI__.window.open(urlInput.value) } ) @@ -14,6 +14,6 @@ addClickEnterHandler( document.getElementById('set-title'), titleInput, function () { - window.tauri.setTitle(titleInput.value) + window.__TAURI__.window.setTitle(titleInput.value) } ) diff --git a/tauri/examples/communication/src-tauri/tauri.conf.json b/tauri/examples/communication/src-tauri/tauri.conf.json index e0e12bdda..bf18f8b39 100644 --- a/tauri/examples/communication/src-tauri/tauri.conf.json +++ b/tauri/examples/communication/src-tauri/tauri.conf.json @@ -1,7 +1,8 @@ { "build": { "distDir": "../dist", - "devPath": "../dist" + "devPath": "../dist", + "withGlobalTauri": true }, "ctx": {}, "tauri": { diff --git a/tauri/examples/communication/src-tauri/tauri.js b/tauri/examples/communication/src-tauri/tauri.js deleted file mode 100644 index 7b6a5537c..000000000 --- a/tauri/examples/communication/src-tauri/tauri.js +++ /dev/null @@ -1,732 +0,0 @@ -/* eslint-disable */ - -/** - * * THIS FILE IS GENERATED AUTOMATICALLY. - * DO NOT EDIT. - * - * Please whitelist these API functions in tauri.conf.json - * - **/ - -/** - * @module tauri - * @description This API interface makes powerful interactions available - * to be run on client side applications. They are opt-in features, and - * must be enabled in tauri.conf.json - * - * Each binding MUST provide these interfaces in order to be compliant, - * and also whitelist them based upon the developer's settings. - */ - -// polyfills -if (!String.prototype.startsWith) { - String.prototype.startsWith = function (searchString, position) { - position = position || 0 - return this.substr(position, searchString.length) === searchString - } -} - -// makes the window.external.invoke API available after window.location.href changes - -switch (navigator.platform) { - case "Macintosh": - case "MacPPC": - case "MacIntel": - case "Mac68K": - window.external = this - invoke = function (x) { - webkit.messageHandlers.invoke.postMessage(x); - } - break; - case "Windows": - case "WinCE": - case "Win32": - case "Win64": - break; - default: - window.external = this - invoke = function (x) { - window.webkit.messageHandlers.external.postMessage(x); - } - break; -} - -(function () { - 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; } - - 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 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() - } - - - - /** - * @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} - * @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; - }, - - - /** - * @name promisified - * @description Turns a request into a chainable promise - * @param {Object} args - * @returns {Promise} - */ - - 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)); - }); - }, - - - /** - * @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|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|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|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|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|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|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|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|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|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 - }); - - }, - - - /** - * @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} 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} 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} - */ - - 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|Promise} - */ - - notification: function notification(options) { - - - 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 - }); - } - }) - - }, - - 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 - }) - - }, - - loadAsset: function loadAsset(assetName, assetType) { - return this.promisified({ - cmd: 'loadAsset', - asset: assetName, - assetType: assetType || 'unknown' - }) - }, - - cliMatches: function () { - - return this.promisified({ - cmd: 'cliMatches' - }) - - - } - }; - - - 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) - } - - 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 - } - }, true) - - // open 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() - } else { - window.addEventListener('DOMContentLoaded', function () { - __openLinks() - }, true) - } -})() diff --git a/tauri/src/app/runner.rs b/tauri/src/app/runner.rs index 0248d5b9a..eca991b19 100644 --- a/tauri/src/app/runner.rs +++ b/tauri/src/app/runner.rs @@ -10,7 +10,7 @@ use std::{ use web_view::{builder, Content, WebView}; use super::App; -#[cfg(feature = "embedded-server")] +#[cfg(embedded_server)] use crate::api::tcp::{get_available_port, port_is_available}; use tauri_api::config::get; @@ -20,7 +20,7 @@ pub(crate) fn run(application: &mut App) -> crate::Result<()> { let main_content = setup_content()?; // setup the server url for the embedded-server - #[cfg(feature = "embedded-server")] + #[cfg(embedded_server)] let server_url = { if let Content::Url(ref url) = &main_content { String::from(url) @@ -46,7 +46,7 @@ pub(crate) fn run(application: &mut App) -> crate::Result<()> { )?; // spawn the embedded server on our server url - #[cfg(feature = "embedded-server")] + #[cfg(embedded_server)] spawn_server(server_url.to_string())?; // spin up the updater process @@ -60,7 +60,7 @@ pub(crate) fn run(application: &mut App) -> crate::Result<()> { } // setup content for dev-server -#[cfg(not(any(feature = "embedded-server", feature = "no-server")))] +#[cfg(dev)] fn setup_content() -> crate::Result> { let config = get()?; if config.build.dev_path.starts_with("http") { @@ -79,7 +79,7 @@ fn setup_content() -> crate::Result> { } // setup content for embedded server -#[cfg(feature = "embedded-server")] +#[cfg(embedded_server)] fn setup_content() -> crate::Result> { let (port, valid) = setup_port()?; let url = (if valid { @@ -93,14 +93,14 @@ fn setup_content() -> crate::Result> { } // setup content for no-server -#[cfg(feature = "no-server")] +#[cfg(no_server)] fn setup_content() -> crate::Result> { let html = include_str!(concat!(env!("OUT_DIR"), "/index.tauri.html")); Ok(Content::Html(html.to_string())) } // get the port for the embedded server -#[cfg(feature = "embedded-server")] +#[cfg(embedded_server)] fn setup_port() -> crate::Result<(String, bool)> { let config = get()?; if config.tauri.embedded_server.port == "random" { @@ -120,7 +120,7 @@ fn setup_port() -> crate::Result<(String, bool)> { } // setup the server url for embedded server -#[cfg(feature = "embedded-server")] +#[cfg(embedded_server)] fn setup_server_url(port: String) -> crate::Result { let config = get()?; let mut url = format!("{}:{}", config.tauri.embedded_server.host, port); @@ -131,7 +131,7 @@ fn setup_server_url(port: String) -> crate::Result { } // spawn the embedded server -#[cfg(feature = "embedded-server")] +#[cfg(embedded_server)] fn spawn_server(server_url: String) -> crate::Result<()> { spawn(move || { let server = tiny_http::Server::http( @@ -294,13 +294,13 @@ mod test { env::set_current_dir(tauri_dir).expect("failed to change cwd"); let res = super::setup_content(); - #[cfg(feature = "embedded-server")] + #[cfg(embedded_server)] match res { Ok(Content::Url(u)) => assert!(u.contains("http://")), _ => assert!(false), } - #[cfg(feature = "no-server")] + #[cfg(no_server)] match res { Ok(Content::Html(s)) => { let dist_dir = match option_env!("TAURI_DIST_DIR") { @@ -319,7 +319,7 @@ mod test { _ => assert!(false), } - #[cfg(not(any(feature = "embedded-server", feature = "no-server")))] + #[cfg(dev)] match res { Ok(Content::Url(dp)) => assert_eq!(dp, config.build.dev_path), Ok(Content::Html(s)) => { @@ -334,7 +334,7 @@ mod test { } } - #[cfg(feature = "embedded-server")] + #[cfg(embedded_server)] #[test] fn check_setup_port() { let res = super::setup_port(); @@ -346,7 +346,7 @@ mod test { proptest! { #![proptest_config(ProptestConfig::with_cases(10000))] - #[cfg(feature = "embedded-server")] + #[cfg(embedded_server)] #[test] fn check_server_url(port in (any::().prop_map(|v| v.to_string()))) { let p = port.clone(); diff --git a/tauri/src/endpoints.rs b/tauri/src/endpoints.rs index 2924c7eab..0c6e8cefc 100644 --- a/tauri/src/endpoints.rs +++ b/tauri/src/endpoints.rs @@ -1,11 +1,24 @@ mod cmd; -mod dialog; +#[allow(unused_imports)] mod file_system; -mod http; +mod init; mod salt; -#[cfg(any(feature = "embedded-server", feature = "no-server"))] -use std::path::PathBuf; +use init::init; + +#[cfg(assets)] +mod asset; +#[cfg(open)] +mod browser; +#[cfg(any(open_dialog, save_dialog))] +mod dialog; +#[cfg(event)] +mod event; +#[cfg(http_request)] +mod http; +#[cfg(notification)] +mod notification; + use web_view::WebView; #[allow(unused_variables)] @@ -24,25 +37,28 @@ pub(crate) fn handle(webview: &mut WebView<'_, T>, arg: &str) -> cra event_init = event_init ))?; } - #[cfg(any(feature = "all-api", feature = "read-text-file"))] ReadTextFile { path, options, callback, error, } => { + #[cfg(read_text_file)] file_system::read_text_file(webview, path, options, callback, error); + #[cfg(not(read_text_file))] + whitelist_error(webview, error, "readTextFile"); } - #[cfg(any(feature = "all-api", feature = "read-binary-file"))] ReadBinaryFile { path, options, callback, error, } => { + #[cfg(read_binary_file)] file_system::read_binary_file(webview, path, options, callback, error); + #[cfg(not(read_binary_file))] + whitelist_error(webview, error, "readBinaryFile"); } - #[cfg(any(feature = "all-api", feature = "write-file"))] WriteFile { file, contents, @@ -50,9 +66,11 @@ pub(crate) fn handle(webview: &mut WebView<'_, T>, arg: &str) -> cra callback, error, } => { + #[cfg(write_file)] file_system::write_file(webview, file, contents, options, callback, error); + #[cfg(not(write_file))] + whitelist_error(webview, error, "writeFile"); } - #[cfg(any(feature = "all-api", feature = "write-binary-file"))] WriteBinaryFile { file, contents, @@ -62,16 +80,17 @@ pub(crate) fn handle(webview: &mut WebView<'_, T>, arg: &str) -> cra } => { file_system::write_binary_file(webview, file, contents, options, callback, error); } - #[cfg(any(feature = "all-api", feature = "read-dir"))] ReadDir { path, options, callback, error, } => { + #[cfg(read_dir)] file_system::read_dir(webview, path, options, callback, error); + #[cfg(not(read_dir))] + whitelist_error(webview, error, "readDir"); } - #[cfg(any(feature = "all-api", feature = "copy-file"))] CopyFile { source, destination, @@ -79,36 +98,44 @@ pub(crate) fn handle(webview: &mut WebView<'_, T>, arg: &str) -> cra callback, error, } => { + #[cfg(copy_file)] file_system::copy_file(webview, source, destination, options, callback, error); + #[cfg(not(copy_file))] + whitelist_error(webview, error, "copyFile"); } - #[cfg(any(feature = "all-api", feature = "create-dir"))] CreateDir { path, options, callback, error, } => { + #[cfg(create_dir)] file_system::create_dir(webview, path, options, callback, error); + #[cfg(not(create_dir))] + whitelist_error(webview, error, "createDir"); } - #[cfg(any(feature = "all-api", feature = "remove-dir"))] RemoveDir { path, options, callback, error, } => { + #[cfg(remove_dir)] file_system::remove_dir(webview, path, options, callback, error); + #[cfg(not(remove_dir))] + whitelist_error(webview, error, "removeDir"); } - #[cfg(any(feature = "all-api", feature = "remove-file"))] RemoveFile { path, options, callback, error, } => { + #[cfg(remove_file)] file_system::remove_file(webview, path, options, callback, error); + #[cfg(not(remove_file))] + whitelist_error(webview, error, "removeFile"); } - #[cfg(any(feature = "all-api", feature = "rename-file"))] RenameFile { old_path, new_path, @@ -116,24 +143,33 @@ pub(crate) fn handle(webview: &mut WebView<'_, T>, arg: &str) -> cra callback, error, } => { + #[cfg(rename_file)] file_system::rename_file(webview, old_path, new_path, options, callback, error); + #[cfg(not(rename_file))] + whitelist_error(webview, error, "renameFile"); } - #[cfg(any(feature = "all-api", feature = "set-title"))] SetTitle { title } => { + #[cfg(set_title)] webview.set_title(&title)?; + #[cfg(not(set_title))] + throw_whitelist_error(webview, "title"); } - #[cfg(any(feature = "all-api", feature = "execute"))] Execute { command, args, callback, error, } => { + #[cfg(execute)] crate::call(webview, command, args, callback, error); + #[cfg(not(execute))] + throw_whitelist_error(webview, "execute"); } - #[cfg(any(feature = "all-api", feature = "open"))] Open { uri } => { - open_fn(uri)?; + #[cfg(open)] + browser::open(uri); + #[cfg(not(open))] + throw_whitelist_error(webview, "open"); } ValidateSalt { salt, @@ -142,304 +178,127 @@ pub(crate) fn handle(webview: &mut WebView<'_, T>, arg: &str) -> cra } => { salt::validate(webview, salt, callback, error); } - #[cfg(any(feature = "all-api", feature = "event"))] Listen { event, handler, once, } => { - let js_string = listen_fn(event, handler, once)?; - webview.eval(&js_string)?; + #[cfg(event)] + { + let js_string = event::listen_fn(event, handler, once)?; + webview.eval(&js_string)?; + } + #[cfg(not(event))] + throw_whitelist_error(webview, "event"); } - #[cfg(any(feature = "all-api", feature = "event"))] Emit { event, payload } => { + #[cfg(event)] crate::event::on_event(event, payload); + #[cfg(not(event))] + throw_whitelist_error(webview, "event"); } - #[cfg(any(feature = "all-api", feature = "open-dialog"))] OpenDialog { options, callback, error, } => { + #[cfg(open_dialog)] dialog::open(webview, options, callback, error); + #[cfg(not(open_dialog))] + whitelist_error(webview, error, "title"); } - #[cfg(any(feature = "all-api", feature = "save-dialog"))] SaveDialog { options, callback, error, } => { + #[cfg(save_dialog)] dialog::save(webview, options, callback, error); + #[cfg(not(save_dialog))] + throw_whitelist_error(webview, "saveDialog"); } - #[cfg(any(feature = "all-api", feature = "http-request"))] HttpRequest { options, callback, error, } => { + #[cfg(http_request)] http::make_request(webview, *options, callback, error); + #[cfg(not(http_request))] + whitelist_error(webview, error, "httpRequest"); } - #[cfg(any(feature = "embedded-server", feature = "no-server"))] + #[cfg(assets)] LoadAsset { asset, asset_type, callback, error, } => { - load_asset(webview, asset, asset_type, callback, error)?; + asset::load(webview, asset, asset_type, callback, error); + } + CliMatches { callback, error } => { + #[cfg(cli)] + crate::execute_promise( + webview, + move || match crate::cli::get_matches() { + Some(matches) => Ok(serde_json::to_string(matches)?), + None => Err(anyhow::anyhow!(r#""failed to get matches""#)), + }, + callback, + error, + ); + #[cfg(not(cli))] + whitelist_error(webview, error, "cli"); } - #[cfg(feature = "cli")] - CliMatches { callback, error } => crate::execute_promise( - webview, - move || match crate::cli::get_matches() { - Some(matches) => Ok(serde_json::to_string(matches)?), - None => Err(anyhow::anyhow!(r#""failed to get matches""#)), - }, - callback, - error, - ), - #[cfg(any(feature = "all-api", feature = "notification"))] Notification { options, callback, error, } => { - notification(webview, options, callback, error)?; + #[cfg(notification)] + notification::send(webview, options, callback, error); + #[cfg(not(notification))] + whitelist_error(webview, error, "notification"); } - #[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(notification)] + notification::is_permission_granted(webview, callback, error); + #[cfg(not(notification))] + whitelist_error(webview, error, "notification"); + } + RequestNotificationPermission { callback, error } => { + #[cfg(notification)] + notification::request_permission(webview, callback, error); + #[cfg(not(notification))] + whitelist_error(webview, error, "notification"); } - #[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(()) } } } -fn init() -> crate::Result { - #[cfg(not(any(feature = "all-api", feature = "event")))] - return Ok(String::from("")); - #[cfg(any(feature = "all-api", feature = "event"))] - return Ok(format!( - " - window['{queue}'] = []; - window['{fn}'] = function (payload, salt, ignoreQueue) {{ - const listeners = (window['{listeners}'] && window['{listeners}'][payload.type]) || [] - if (!ignoreQueue && listeners.length === 0) {{ - window['{queue}'].push({{ - payload: payload, - salt: salt - }}) - }} - - if (listeners.length > 0) {{ - window.tauri.promisified({{ - cmd: 'validateSalt', - salt: salt - }}).then(function () {{ - for (let i = listeners.length - 1; i >= 0; i--) {{ - const listener = listeners[i] - if (listener.once) - listeners.splice(i, 1) - listener.handler(payload) - }} - }}) - }} - }} - ", - fn = crate::event::emit_function_name(), - queue = crate::event::event_queue_object_name(), - listeners = crate::event::event_listeners_object_name() - )); -} - -#[cfg(any(feature = "all-api", feature = "open"))] -fn open_fn(uri: String) -> crate::Result<()> { - crate::spawn(move || { - #[cfg(test)] - assert!(uri.contains("http://")); - - #[cfg(not(test))] - webbrowser::open(&uri).expect("Failed to open webbrowser with uri"); - }); - - Ok(()) -} - -#[cfg(any(feature = "all-api", feature = "event"))] -fn listen_fn(event: String, handler: String, once: bool) -> crate::Result { - Ok(format!( - "if (window['{listeners}'] === void 0) {{ - window['{listeners}'] = {{}} - }} - if (window['{listeners}']['{evt}'] === void 0) {{ - window['{listeners}']['{evt}'] = [] - }} - window['{listeners}']['{evt}'].push({{ - handler: window['{handler}'], - once: {once_flag} - }}); - - for (let i = 0; i < (window['{queue}'] || []).length; i++) {{ - const e = window['{queue}'][i]; - window['{emit}'](e.payload, e.salt, true) - }} - ", - listeners = crate::event::event_listeners_object_name(), - queue = crate::event::event_queue_object_name(), - emit = crate::event::emit_function_name(), - evt = event, - handler = handler, - once_flag = if once { "true" } else { "false" } - )) -} - -#[cfg(any(feature = "embedded-server", feature = "no-server"))] -fn load_asset( +#[allow(dead_code)] +fn whitelist_error( webview: &mut WebView<'_, T>, - asset: String, - asset_type: String, - callback: String, - error: String, -) -> crate::Result<()> { - let handle = webview.handle(); - crate::execute_promise( - webview, - move || { - let mut path = PathBuf::from(if asset.starts_with('/') { - asset.replacen("/", "", 1) - } else { - asset.clone() - }); - let mut read_asset; - loop { - read_asset = crate::assets::ASSETS.get(&format!( - "{}/{}", - option_env!("TAURI_DIST_DIR") - .expect("tauri apps should be built with the TAURI_DIST_DIR environment variable"), - path.to_string_lossy() - )); - if read_asset.is_err() { - match path.iter().next() { - Some(component) => { - let first_component = component.to_str().expect("failed to read path component"); - path = PathBuf::from(path.to_string_lossy().replacen( - format!("{}/", first_component).as_str(), - "", - 1, - )); - } - None => { - return Err(anyhow::anyhow!("Asset '{}' not found", asset)); - } - } - } else { - break; - } - } - - if asset_type == "image" { - let ext = if asset.ends_with("gif") { - "gif" - } else if asset.ends_with("png") { - "png" - } else { - "jpeg" - }; - Ok(format!( - r#""data:image/{};base64,{}""#, - ext, - base64::encode(&read_asset.expect("Failed to read asset type").into_owned()) - )) - } else { - handle - .dispatch(move |_webview| { - let asset_bytes = &read_asset.expect("Failed to read asset type").into_owned(); - let asset_str = - &std::str::from_utf8(asset_bytes).expect("failed to convert asset bytes to u8 slice"); - if asset_type == "stylesheet" { - _webview.inject_css(asset_str) - } else { - _webview.eval(asset_str) - } - }) - .map_err(|err| err.into()) - .map(|_| r#""Asset loaded successfully""#.to_string()) - } - }, - callback, - error, + error_fn: String, + whitelist_key: &str, +) { + let reject_code = tauri_api::rpc::format_callback( + error_fn, + format!(r#""'{}' not whitelisted""#, whitelist_key), ); - - Ok(()) + webview + .eval(&reject_code) + .expect("failed to eval whitelist error") } -#[cfg(any(feature = "all-api", feature = "notification"))] -fn notification( - webview: &mut WebView<'_, T>, - options: cmd::NotificationOptions, - callback: String, - error: String, -) -> crate::Result<()> { - crate::execute_promise( - webview, - move || { - let mut notification = tauri_api::notification::Notification::new(); - 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(()) +#[allow(dead_code)] +fn throw_whitelist_error(webview: &mut WebView<'_, T>, whitelist_key: &str) { + let reject_code = format!(r#"throw new Error("'{}' not whitelisted")"#, whitelist_key); + webview + .eval(&reject_code) + .expect("failed to eval whitelist error") } #[cfg(test)] @@ -449,16 +308,16 @@ mod test { #[test] // test to see if check init produces a string or not. fn check_init() { - if cfg!(not(any(feature = "all-api", feature = "event"))) { + if cfg!(not(event)) { let res = super::init(); match res { Ok(s) => assert_eq!(s, ""), Err(_) => assert!(false), } - } else if cfg!(any(feature = "all-api", feature = "event")) { + } else if cfg!(event) { let res = super::init(); match res { - Ok(s) => assert!(s.contains("window.tauri.promisified")), + Ok(s) => assert!(s.contains("window.__TAURI__.promisified")), Err(_) => assert!(false), } } @@ -466,10 +325,10 @@ mod test { // check the listen_fn for various usecases. proptest! { - #[cfg(any(feature = "all-api", feature = "event"))] + #[cfg(event)] #[test] fn check_listen_fn(event in "", handler in "", once in proptest::bool::ANY) { - let res = super::listen_fn(event, handler, once); + let res = super::event::listen_fn(event, handler, once); match res { Ok(_) => assert!(true), Err(_) => assert!(false) @@ -479,14 +338,10 @@ mod test { // Test the open func to see if proper uris can be opened by the browser. proptest! { - #[cfg(any(feature = "all-api", feature = "open"))] + #[cfg(open)] #[test] fn check_open(uri in r"(http://)([\\w\\d\\.]+([\\w]{2,6})?)") { - let res = super::open_fn(uri); - match res { - Ok(_) => assert!(true), - Err(_) => assert!(false), - } + super::browser::open(uri); } } } diff --git a/tauri/src/endpoints/asset.rs b/tauri/src/endpoints/asset.rs new file mode 100644 index 000000000..8ad87784b --- /dev/null +++ b/tauri/src/endpoints/asset.rs @@ -0,0 +1,79 @@ +use std::path::PathBuf; +use web_view::WebView; + +pub fn load( + webview: &mut WebView<'_, T>, + asset: String, + asset_type: String, + callback: String, + error: String, +) { + let handle = webview.handle(); + crate::execute_promise( + webview, + move || { + let mut path = PathBuf::from(if asset.starts_with('/') { + asset.replacen("/", "", 1) + } else { + asset.clone() + }); + let mut read_asset; + loop { + read_asset = crate::assets::ASSETS.get(&format!( + "{}/{}", + option_env!("TAURI_DIST_DIR") + .expect("tauri apps should be built with the TAURI_DIST_DIR environment variable"), + path.to_string_lossy() + )); + if read_asset.is_err() { + match path.iter().next() { + Some(component) => { + let first_component = component.to_str().expect("failed to read path component"); + path = PathBuf::from(path.to_string_lossy().replacen( + format!("{}/", first_component).as_str(), + "", + 1, + )); + } + None => { + return Err(anyhow::anyhow!("Asset '{}' not found", asset)); + } + } + } else { + break; + } + } + + if asset_type == "image" { + let ext = if asset.ends_with("gif") { + "gif" + } else if asset.ends_with("png") { + "png" + } else { + "jpeg" + }; + Ok(format!( + r#""data:image/{};base64,{}""#, + ext, + base64::encode(&read_asset.expect("Failed to read asset type").into_owned()) + )) + } else { + handle + .dispatch(move |_webview| { + let asset_bytes = &read_asset.expect("Failed to read asset type").into_owned(); + let asset_str = + &std::str::from_utf8(asset_bytes).expect("failed to convert asset bytes to u8 slice"); + if asset_type == "stylesheet" { + _webview.inject_css(asset_str) + } else { + _webview.eval(asset_str) + } + }) + .map_err(|err| err.into()) + .map(|_| r#""Asset loaded successfully""#.to_string()) + } + }, + callback, + error, + ); +} diff --git a/tauri/src/endpoints/browser.rs b/tauri/src/endpoints/browser.rs new file mode 100644 index 000000000..d69b69693 --- /dev/null +++ b/tauri/src/endpoints/browser.rs @@ -0,0 +1,10 @@ +#[cfg(open)] +pub fn open(uri: String) { + crate::spawn(move || { + #[cfg(test)] + assert!(uri.contains("http://")); + + #[cfg(not(test))] + webbrowser::open(&uri).expect("Failed to open webbrowser with uri"); + }); +} diff --git a/tauri/src/endpoints/cmd.rs b/tauri/src/endpoints/cmd.rs index ace5912ae..af927dfb2 100644 --- a/tauri/src/endpoints/cmd.rs +++ b/tauri/src/endpoints/cmd.rs @@ -43,21 +43,18 @@ pub struct NotificationOptions { #[serde(tag = "cmd", rename_all = "camelCase")] pub enum Cmd { Init {}, - #[cfg(any(feature = "all-api", feature = "read-text-file"))] ReadTextFile { path: String, options: Option, callback: String, error: String, }, - #[cfg(any(feature = "all-api", feature = "read-binary-file"))] ReadBinaryFile { path: String, options: Option, callback: String, error: String, }, - #[cfg(any(feature = "all-api", feature = "write-file"))] WriteFile { file: String, contents: String, @@ -65,7 +62,6 @@ pub enum Cmd { callback: String, error: String, }, - #[cfg(any(feature = "all-api", feature = "write-binary-file"))] WriteBinaryFile { file: String, contents: String, @@ -73,14 +69,12 @@ pub enum Cmd { callback: String, error: String, }, - #[cfg(any(feature = "all-api", feature = "read-dir"))] ReadDir { path: String, options: Option, callback: String, error: String, }, - #[cfg(any(feature = "all-api", feature = "copy-file"))] CopyFile { source: String, destination: String, @@ -88,21 +82,18 @@ pub enum Cmd { callback: String, error: String, }, - #[cfg(any(feature = "all-api", feature = "create-dir"))] CreateDir { path: String, options: Option, callback: String, error: String, }, - #[cfg(any(feature = "all-api", feature = "remove-dir"))] RemoveDir { path: String, options: Option, callback: String, error: String, }, - #[cfg(any(feature = "all-api", feature = "remove-file"))] RemoveFile { path: String, options: Option, @@ -110,7 +101,6 @@ pub enum Cmd { error: String, }, #[serde(rename_all = "camelCase")] - #[cfg(any(feature = "all-api", feature = "rename-file"))] RenameFile { old_path: String, new_path: String, @@ -118,18 +108,15 @@ pub enum Cmd { callback: String, error: String, }, - #[cfg(any(feature = "all-api", feature = "set-title"))] SetTitle { title: String, }, - #[cfg(any(feature = "all-api", feature = "execute"))] Execute { command: String, args: Vec, callback: String, error: String, }, - #[cfg(any(feature = "all-api", feature = "open"))] Open { uri: String, }, @@ -138,30 +125,25 @@ pub enum Cmd { callback: String, error: String, }, - #[cfg(any(feature = "all-api", feature = "event"))] Listen { event: String, handler: String, once: bool, }, - #[cfg(any(feature = "all-api", feature = "event"))] Emit { event: String, payload: Option, }, - #[cfg(any(feature = "all-api", feature = "open-dialog"))] OpenDialog { options: OpenDialogOptions, callback: String, error: String, }, - #[cfg(any(feature = "all-api", feature = "save-dialog"))] SaveDialog { options: SaveDialogOptions, callback: String, error: String, }, - #[cfg(any(feature = "all-api", feature = "http-request"))] HttpRequest { options: Box, callback: String, @@ -175,23 +157,19 @@ pub enum Cmd { callback: String, error: String, }, - #[cfg(feature = "cli")] CliMatches { 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, diff --git a/tauri/src/endpoints/dialog.rs b/tauri/src/endpoints/dialog.rs index 1d69a2aab..6b80ef7df 100644 --- a/tauri/src/endpoints/dialog.rs +++ b/tauri/src/endpoints/dialog.rs @@ -10,6 +10,7 @@ fn map_response(response: Response) -> String { } } +#[cfg(open_dialog)] pub fn open( webview: &mut WebView<'_, T>, options: OpenDialogOptions, @@ -33,6 +34,7 @@ pub fn open( ); } +#[cfg(save_dialog)] pub fn save( webview: &mut WebView<'_, T>, options: SaveDialogOptions, diff --git a/tauri/src/endpoints/event.rs b/tauri/src/endpoints/event.rs new file mode 100644 index 000000000..1661bcfb0 --- /dev/null +++ b/tauri/src/endpoints/event.rs @@ -0,0 +1,27 @@ +#[cfg(event)] +pub fn listen_fn(event: String, handler: String, once: bool) -> crate::Result { + Ok(format!( + "if (window['{listeners}'] === void 0) {{ + window['{listeners}'] = {{}} + }} + if (window['{listeners}']['{evt}'] === void 0) {{ + window['{listeners}']['{evt}'] = [] + }} + window['{listeners}']['{evt}'].push({{ + handler: window['{handler}'], + once: {once_flag} + }}); + + for (let i = 0; i < (window['{queue}'] || []).length; i++) {{ + const e = window['{queue}'][i]; + window['{emit}'](e.payload, e.salt, true) + }} + ", + listeners = crate::event::event_listeners_object_name(), + queue = crate::event::event_queue_object_name(), + emit = crate::event::emit_function_name(), + evt = event, + handler = handler, + once_flag = if once { "true" } else { "false" } + )) +} diff --git a/tauri/src/endpoints/file_system.rs b/tauri/src/endpoints/file_system.rs index 8cce17b77..0622f8409 100644 --- a/tauri/src/endpoints/file_system.rs +++ b/tauri/src/endpoints/file_system.rs @@ -10,6 +10,7 @@ use std::io::Write; use super::cmd::{DirOperationOptions, FileOperationOptions}; +#[cfg(read_dir)] pub fn read_dir( webview: &mut WebView<'_, T>, path: String, @@ -38,6 +39,7 @@ pub fn read_dir( ); } +#[cfg(copy_file)] pub fn copy_file( webview: &mut WebView<'_, T>, source: String, @@ -65,6 +67,7 @@ pub fn copy_file( ); } +#[cfg(create_dir)] pub fn create_dir( webview: &mut WebView<'_, T>, path: String, @@ -94,6 +97,7 @@ pub fn create_dir( ); } +#[cfg(remove_dir)] pub fn remove_dir( webview: &mut WebView<'_, T>, path: String, @@ -123,6 +127,7 @@ pub fn remove_dir( ); } +#[cfg(remove_file)] pub fn remove_file( webview: &mut WebView<'_, T>, path: String, @@ -143,6 +148,7 @@ pub fn remove_file( ); } +#[cfg(rename_file)] pub fn rename_file( webview: &mut WebView<'_, T>, old_path: String, @@ -170,6 +176,7 @@ pub fn rename_file( ); } +#[cfg(write_file)] pub fn write_file( webview: &mut WebView<'_, T>, file: String, @@ -194,6 +201,7 @@ pub fn write_file( ); } +#[cfg(write_binary_file)] pub fn write_binary_file( webview: &mut WebView<'_, T>, file: String, @@ -222,6 +230,7 @@ pub fn write_binary_file( ); } +#[cfg(read_text_file)] pub fn read_text_file( webview: &mut WebView<'_, T>, path: String, @@ -240,6 +249,7 @@ pub fn read_text_file( ); } +#[cfg(read_binary_file)] pub fn read_binary_file( webview: &mut WebView<'_, T>, path: String, diff --git a/tauri/src/endpoints/init.rs b/tauri/src/endpoints/init.rs new file mode 100644 index 000000000..644aecc86 --- /dev/null +++ b/tauri/src/endpoints/init.rs @@ -0,0 +1,36 @@ +pub fn init() -> crate::Result { + #[cfg(not(event))] + return Ok(String::from("")); + #[cfg(event)] + return Ok(format!( + " + window['{queue}'] = []; + window['{fn}'] = function (payload, salt, ignoreQueue) {{ + const listeners = (window['{listeners}'] && window['{listeners}'][payload.type]) || [] + if (!ignoreQueue && listeners.length === 0) {{ + window['{queue}'].push({{ + payload: payload, + salt: salt + }}) + }} + + if (listeners.length > 0) {{ + window.__TAURI__.promisified({{ + cmd: 'validateSalt', + salt: salt + }}).then(function () {{ + for (let i = listeners.length - 1; i >= 0; i--) {{ + const listener = listeners[i] + if (listener.once) + listeners.splice(i, 1) + listener.handler(payload) + }} + }}) + }} + }} + ", + fn = crate::event::emit_function_name(), + queue = crate::event::event_queue_object_name(), + listeners = crate::event::event_listeners_object_name() + )); +} diff --git a/tauri/src/endpoints/notification.rs b/tauri/src/endpoints/notification.rs new file mode 100644 index 000000000..a3e31d607 --- /dev/null +++ b/tauri/src/endpoints/notification.rs @@ -0,0 +1,86 @@ +use super::cmd::NotificationOptions; +use web_view::WebView; + +pub fn send( + webview: &mut WebView<'_, T>, + options: NotificationOptions, + callback: String, + error: String, +) { + crate::execute_promise( + webview, + move || { + let mut notification = tauri_api::notification::Notification::new(); + 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, + ); +} + +pub fn is_permission_granted( + webview: &mut WebView<'_, T>, + callback: String, + error: String, +) { + 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, + ); +} + +pub fn request_permission( + webview: &mut WebView<'_, T>, + callback: String, + error: String, +) { + 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 => { + settings.allow_notification = Some(false); + crate::settings::write_settings(settings)?; + Ok(denied) + } + _ => Ok(r#""default""#.to_string()), + } + }, + callback, + error, + ); +} diff --git a/tauri/src/lib.rs b/tauri/src/lib.rs index ad0fd4b09..4e3225498 100644 --- a/tauri/src/lib.rs +++ b/tauri/src/lib.rs @@ -3,14 +3,14 @@ windows_subsystem = "windows" )] -#[cfg(any(feature = "embedded-server", feature = "no-server"))] +#[cfg(assets)] pub mod assets; pub mod event; -#[cfg(feature = "embedded-server")] +#[cfg(embedded_server)] pub mod server; pub mod settings; -#[cfg(feature = "cli")] +#[cfg(cli)] pub mod cli; mod app; diff --git a/tauri/src/settings.rs b/tauri/src/settings.rs index 34eb59b6a..970dafeeb 100644 --- a/tauri/src/settings.rs +++ b/tauri/src/settings.rs @@ -8,7 +8,7 @@ use tauri_api::path::{resolve_path, BaseDirectory}; #[derive(Default, Deserialize, Serialize)] pub struct Settings { - #[cfg(any(feature = "all-api", feature = "notification"))] + #[cfg(notification)] pub allow_notification: Option, } @@ -18,7 +18,10 @@ fn get_settings_path() -> tauri_api::Result { 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())?; + let settings_folder = Path::new(&settings_path).parent().unwrap(); + if !settings_folder.exists() { + std::fs::create_dir(settings_folder)?; + } File::create(settings_path) .map_err(|e| anyhow!(e)) .and_then(|mut f| { diff --git a/tauri/test/fixture/src-tauri/.gitkeep b/tauri/test/fixture/src-tauri/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/tauri/test/fixture/src-tauri/tauri.js b/tauri/test/fixture/src-tauri/tauri.js deleted file mode 100644 index d66898cd0..000000000 --- a/tauri/test/fixture/src-tauri/tauri.js +++ /dev/null @@ -1,219 +0,0 @@ -/* eslint-disable */ - -/** - * * THIS FILE IS GENERATED AUTOMATICALLY. - * DO NOT EDIT. - * - * Please whitelist these API functions in tauri.conf.json - * - **/ - -/** - * @module tauri - * @description This API interface makes powerful interactions available - * to be run on client side applications. They are opt-in features, and - * must be enabled in tauri.conf.json - * - * Each binding MUST provide these interfaces in order to be compliant, - * and also whitelist them based upon the developer's settings. - */ - -function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1) -} - -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; } - -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); } - - - - -var __reject = function () { - return new Promise(function (_, reject) { - reject(); - }); -} - -window.tauri = { - - invoke: function invoke(args) { - window.external.invoke(JSON.stringify(args)); - }, - - - listen: function listen(event, handler) { - - - return __reject() - - }, - - - emit: function emit(evt, payload) { - - - return __reject() - - }, - - - transformCallback: function transformCallback(callback) { - var once = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; - var identifier = Object.freeze(uid()); - - window[identifier] = function (result) { - if (once) { - delete window[identifier]; - } - - return callback && callback(result); - }; - - return identifier; - }, - - - 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)); - }); - }, - - - readTextFile: function readTextFile(path) { - - - return __reject() - - }, - - - readBinaryFile: function readBinaryFile(path) { - - - return __reject() - - }, - - - writeFile: function writeFile(cfg) { - - - return __reject() - - }, - - - listFiles: function listFiles(path) { - - - return __reject() - - }, - - - listDirs: function listDirs(path) { - - - return __reject() - - }, - - - setTitle: function setTitle(title) { - - - return __reject() - - }, - - - open: function open(uri) { - - - return __reject() - - }, - - - execute: function execute(command, args) { - - - return __reject() - - }, - - bridge: function bridge(command, payload) { - - - return __reject() - - }, - - loadAsset: function loadAsset(assetName, assetType) { - return this.promisified({ - cmd: 'loadAsset', - asset: assetName, - asset_type: assetType || 'unknown' - }) - } -}; - -// 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(img => { - target.src = img - }) - break - } - target = target.parentElement - } -}, true) - -window.addEventListener('DOMContentLoaded', function () { - // open links with the Tauri API - document.querySelector('body').addEventListener('click', function (e) { - var target = e.target - while (target != null) { - if (target.matches ? target.matches('a') : target.msMatchesSelector('a')) { - window.tauri.open(target.href) - break - } - target = target.parentElement - } - }, true) -}, true)