8
11. API
nothingismagick edited this page 2020-05-09 23:52:26 +02:00

API

The Tauri API is a set of opt-in tools that you can enable in order to do things like read and write from the filesystem and pass messages back and forth between the WebView and Rust. There are two parts to it, the Rust API and the JS API. The former is consumed in your src-tauri/src/main.rs file, and the latter is available at either the window.tauri object or just tauri - depending on your integration technique.

Installation

If you want to use the API, you will need to first enable the parts of the API that you want to use by "whitelisting" them:

Whitelist

"tauri": {
  "whitelist": {              // all whitelist values are default:false
    "all": false,             // use this flag to enable all API features
    "answer": false,          // enable rust to direct the UI
    "event": false,           // enable listening to messages from webview
    "execute": false,         // enable binary execution
    "listFiles": false,       // get a list of files in a directory
    "open": false,            // open link in the user's default browser
    "readBinaryFile": false,  // read binary file from local filesystem
    "readTextFile": false,    // read text file from local filesystem
    "setTitle": false,        // set the webview window title
    "writeFile": false        // write file to local filesystem
  }
}

Then you will have to choose how to integrate it into your UI. There are two suggested methods:

  • Use the webpack plugin (tighter integration, more complicated)
  • Inject the official helper (easier)

Webpack Plugin

Webpack will take care of everything for you, so after you have this done right, you can use window.tauri wherever you need to.

Installation

yarn add @tauri-apps/tauri-webpack

Integration

In your webpack config, add the following:

chainWebpack (chain) {
  require('@tauri-apps/tauri-webpack').chain(chain, {
    tauriLazyLoading: !ctx.dev
  })
}

Official Helper

Somewhat easier is to add tauri to the dev dependencies of your project:

yarn add --dev tauri

and then import the Tauri api in the file where you want to consume the Tauri API (or in some other rigging location):

import tauri from 'tauri/api'

JS API

Here is the JS API, exposed by the tauri package in the "api" directory.

Dialog

import { open, save } from 'tauri/api/dialog'

Functions

open(options?: undefined | {}): Promise<string | string[]>

Open a file/directory selection dialog

  • Arguments

    • options: undefined | {} = {}
  • Returns Promise<string | string[]>

    promise resolving to the select path(s)

save(options?: undefined | {}): Promise<string> Open a file/directory save dialog

  • Arguments

    • options: undefined | {} = {}
  • Returns Promise

    promise resolving to the select path

Event

import { emit, listen } from 'tauri/api/event'

Functions

emit(event: string, payload: undefined | string): void

Emits an event to the backend

  • Arguments

    • event: string

      the event name

    • payload: undefined | string

  • Returns void

listen(event: string, handler: {}): void

Listen to an event from the backend

  • Arguments
    • event: string

      the event name

    • handler: {}

      the event handler callback

  • Returns void

File system

import {
  Dir,
  copyFile,
  createDir,
  readBinaryFile,
  readDir,
  readTextFile,
  removeDir,
  removeFile,
  renameFile,
  writeFile
} from 'tauri/api/fs'

Members

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
}

Functions

copyFile(source: string, destination: string, options?: undefined | {}): Promise<void>

  • Arguments

    • source: string
    • destination: string
    • options: undefined | {} = {}
  • Returns Promise

createDir(dir: string, options?: undefined | {}): Promise<void>

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

  • Arguments

    • dir: string

      path to the directory to create

    • options: undefined | {} = {}

  • Returns Promise

readBinaryFile(filePath: string, options?: undefined | {}): Promise<any[]>

Reads a file as binary

  • Arguments

    • filePath: string

      path to the file

    • options: undefined | {} = {}

  • Returns Promise<any[]>

readDir(dir: string, options?: undefined | {}): Promise<{}[]>

List directory files

  • Arguments

    • dir: string

      path to the directory to read

    • options: undefined | {} = {}

  • Returns Promise<{}[]>

readTextFile(filePath: string, options?: undefined | {}): Promise<string>

Reads a file as text

  • Arguments

    • filePath: string

      path to the file

    • options: undefined | {} = {}

  • Returns Promise

removeDir(dir: string, options?: undefined | {}): Promise<void>

Removes a directory if the directory is not empty and the recursive option isn't set to true, it will be rejected

  • Arguments

    • dir: string

      path to the directory to remove

    • options: undefined | {} = {}

  • Returns Promise

removeFile(file: string, options?: undefined | {}): Promise<void>

Removes a file

  • Arguments

    • file: string

      path to the file to remove

    • options: undefined | {} = {}

  • Returns Promise

renameFile(oldPath: string, newPath: string, options?: undefined | {}): Promise<void>

Renames a file

  • Arguments

    • oldPath: string
    • newPath: string
    • options: undefined | {} = {}
  • Returns Promise

writeFile(file: {}, options?: undefined | {}): Promise<void>

writes a text file

  • Arguments

    • file: {}
    • options: undefined | {} = {}
  • Returns Promise

Process

import { execute } from 'tauri/api/process'

Functions

execute(command: string, args: undefined | string | string[]): Promise<string>

Spawns a process

  • Arguments

    • command: string

      the name of the cmd to execute e.g. 'mkdir' or 'node'

    • args: undefined | string | string[]

  • Returns Promise

    promise resolving to the stdout text

Window

import { open, setTitle } from 'tauri/api/window'

Functions

open(url: string): void

opens an URL on the user default browser

  • Arguments

    • url: string

      the URL to open

  • Returns void

setTitle(title: string): void

sets the window title

  • Arguments

    • title: string

      the new title

  • Returns void

Example API integration

In our kitchensink, you may find a complete collection (written in ES5) of ways to interact with Tauri's JS API.

Let's review some of them here:

Events and communication

With Tauri, you can emit and listen to events or even send messages with a message handler (see communication.js):

Events - JS side

// Register a listener to the "rust-event" event
window.tauri.listen('rust-event', function (res) {
  document.getElementById('response').innerHTML = JSON.stringify(res)
})

// Emit an event "js-event" with a string as payload
window.tauri.emit('js-event', 'this is the payload string')

Events - Rust side

// Register a listener to the "js-event" event
tauri::event::listen(String::from("js-event"), move |msg| {
    println!("got js-event with message '{:?}'", msg);
    let reply = Reply {
      data: "something else".to_string(),
    };
  
    // Emit an event "rust-event" with a stringified Reply instance
    tauri::event::emit(
      &handle,
      String::from("rust-event"),
      Some(serde_json::to_string(&reply).unwrap()),
    );
  
});

Message handler for Rust-side command execution

Here's how's achieved the command execution, JS side

// Tauri will invoke the "logOperation" command
window.tauri.invoke({
  cmd: "logOperation",
  event: "tauri-click",
  payload: "this payload is optional because we used Option in Rust"
});

// promisified will use invoke but with a promise
window.tauri
  .promisified({
    cmd: "performRequest",
    endpoint: "dummy endpoint arg",
    body: {
      id: 5,
      name: "test"
    }
  })
  .then(registerResponse)
  .catch(registerResponse);

And here's the Rust part. Note that it's a sample, the following code should live inside the invoke_handler function call but has been skipped for readability purpose:

use cmd::Cmd::*;
match serde_json::from_str(arg) {
    Err(e) => {
        Err(e.to_string())
    }
    Ok(command) => {
        match command {
            LogOperation { event, payload } => {
                println!("{} {:?}", event, payload);
            },
            PerformRequest { endpoint, body, callback, error } => {
                // tauri::execute_promise is a helper for APIs that uses the tauri.promisified JS function
                // so you can easily communicate between JS and Rust with promises
                tauri::execute_promise(
                    _webview,
                    move || {
                        println!("{} {:?}", endpoint, body);
                        // perform an async operation here
                        // if the returned value is Ok, the promise will be resolved with its value
                        // if the returned value is Err, the promise will be rejected with its value
                        // the value is a string that will be eval'd
                        Ok("{ key: 'response', value: [{ id: 3 }] }".to_string())
                    },
                    callback,
                    error
                )
              },
            }
            Ok(())
    }
}

Notice that the command names have been normalized: it has been changed from camelCase in JS to PascalCase in Rust.

File system

Tauri provides a file system API that you can leverage to handle files from your machine.

Here's a sample from fs.js:

var pathToRead = pathInput.value
var isFile = pathToRead.match(/\S+\.\S+$/g)

// Tauri will try to read a file or a directory; those functions return a Promise
var promise = isFile ? window.tauri.readBinaryFile(pathToRead) : window.tauri.readDir(pathToRead)
promise.then(function (response) {
  if (isFile) {
    if (pathToRead.includes('.png') || pathToRead.includes('.jpg')) {
      arrayBufferToBase64(new Uint8Array(response), function (base64) {
        var src = 'data:image/png;base64,' + base64
        registerResponse('<img src="' + src + '"></img>')
      })
    } else {
      var value = String.fromCharCode.apply(null, response)
      registerResponse('<textarea id="file-response" style="height: 400px"></textarea><button id="file-save">Save</button>')
      var fileInput = document.getElementById('file-response')
      fileInput.value = value
      document.getElementById('file-save').addEventListener('click', function () {
        window.tauri.writeFile({
          file: pathToRead,
          contents: fileInput.value
        }).catch(registerResponse)
      })
    }
  } else {
    registerResponse(response)
  }
}).catch(registerResponse)

Next Step:

CLI