Repository: tauri-apps/tauri
File: tooling/api/src/tauri.ts (TypeScript)

Code before:
 * const source = document.createElement('source');
 * source.type = 'video/mp4';
 * source.src = assetUrl;
 * video.appendChild(source);
 * video.load();
 * ```
 *
 * @return the URL that can be used as source on the webview.
 *
 * @since 1.0.0
 */
function convertFileSrc(
  filePath: string,
  protocol = 'asset',
  http?: boolean
): string {
  const path = encodeURIComponent(filePath)

  let scheme =
    window.location.origin === 'http://tauri.localhost' ? 'http' : 'https'
  if (http === true) {
    scheme = 'http'
  }
  if (http === false) {
    scheme = 'https'
  }

  return navigator.userAgent.includes('Windows')
    ? `${scheme}://${protocol}.localhost/${path}`
    : `${protocol}://localhost/${path}`
}

export type { InvokeArgs }

export { transformCallback, invoke, convertFileSrc }


Review comment (suggestion):
```suggestion
  let scheme =
    window.location.origin === 'http://tauri.localhost' ? 'http' : 'https'
  if (http) {
    scheme = http ? 'http' : 'https'
  }
```

Code after:
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

/**
 * Invoke your custom commands.
 *
 * This package is also accessible with `window.__TAURI__.tauri` when [`build.withGlobalTauri`](https://tauri.app/v1/api/config/#buildconfig.withglobaltauri) in `tauri.conf.json` is set to `true`.
 * @module
 */

/** @ignore */
declare global {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  interface Window {
    __TAURI_IPC__: (message: any) => void
    ipc: {
      postMessage: (args: string) => void
    }
    __TAURI__: {
      convertFileSrc: (src: string, protocol: string) => string
    }
  }
}

/** @ignore */
function uid(): number {
  return window.crypto.getRandomValues(new Uint32Array(1))[0]
}

/**
 * Transforms a callback function to a string identifier that can be passed to the backend.
 * The backend uses the identifier to `eval()` the callback.
 *
 * @return A unique identifier associated with the callback function.
 *
 * @since 1.0.0
 */
function transformCallback(
  callback?: (response: any) => void,
  once = false
): number {
  const identifier = uid()
  const prop = `_${identifier}`

  Object.defineProperty(window, prop, {
    value: (result: any) => {
      if (once) {
        Reflect.deleteProperty(window, prop)
      }

      return callback?.(result)
    },
    writable: false,
    configurable: true
  })

  return identifier
}

/**
 * Command arguments.
 *
 * @since 1.0.0
 */
type InvokeArgs = Record<string, unknown>

/**
 * Sends a message to the backend.
 * @example
 * ```typescript
 * import { invoke } from '@tauri-apps/api/tauri';
 * await invoke('login', { user: 'tauri', password: 'poiwe3h4r5ip3yrhtew9ty' });
 * ```
 *
 * @param cmd The command name.
 * @param args The optional arguments to pass to the command.
 * @return A promise resolving or rejecting to the backend response.
 *
 * @since 1.0.0
 */
async function invoke<T>(cmd: string, args: InvokeArgs = {}): Promise<T> {
  return new Promise((resolve, reject) => {
    const callback = transformCallback((e: T) => {
      resolve(e)
      Reflect.deleteProperty(window, `_${error}`)
    }, true)
    const error = transformCallback((e) => {
      reject(e)
      Reflect.deleteProperty(window, `_${callback}`)
    }, true)

    window.__TAURI_IPC__({
      cmd,
      callback,
      error,
      ...args
    })
  })
}

/**
 * Convert a device file path to an URL that can be loaded by the webview.
 * Note that `asset:` and `https://asset.localhost` must be added to [`tauri.security.csp`](https://tauri.app/v1/api/config/#securityconfig.csp) in `tauri.conf.json`.
 * Example CSP value: `"csp": "default-src 'self'; img-src 'self' asset: https://asset.localhost"` to use the asset protocol on image sources.
 *
 * Additionally, `asset` must be added to [`tauri.allowlist.protocol`](https://tauri.app/v1/api/config/#allowlistconfig.protocol)
 * in `tauri.conf.json` and its access scope must be defined on the `assetScope` array on the same `protocol` object.
 *
 * @param  filePath The file path.
 * @param  protocol The protocol to use. Defaults to `asset`. You only need to set this when using a custom protocol.
 * @example
 * ```typescript
 * import { appDataDir, join } from '@tauri-apps/api/path';
 * import { convertFileSrc } from '@tauri-apps/api/tauri';
 * const appDataDirPath = await appDataDir();
 * const filePath = await join(appDataDirPath, 'assets/video.mp4');
 * const assetUrl = convertFileSrc(filePath);
 *
 * const video = document.getElementById('my-video');
 * const source = document.createElement('source');
 * source.type = 'video/mp4';
 * source.src = assetUrl;
 * video.appendChild(source);
 * video.load();
 * ```
 *
 * @return the URL that can be used as source on the webview.
 *
 * @since 1.0.0
 */
function convertFileSrc(filePath: string, protocol = 'asset'): string {
  return window.__TAURI__.convertFileSrc(filePath, protocol)
}

export type { InvokeArgs }

export { transformCallback, invoke, convertFileSrc }