tauri/tooling/api/src/shell.ts

217 lines
5.1 KiB
TypeScript
Raw Normal View History

feat(license): SPDX Headers (#1449) * chore(licenses): api Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(licenses): scripts Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): cli/core Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): cli/tauri-bundler Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): workflows Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): require license_template in rust Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): core/tauri Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): core/tauri-api Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): core/tauri-build Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): core/tauri-codegen Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): core/tauri-macros Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): core/tauri-updater Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): core/tauri-utils Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): examples Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): cli/tauri.js Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): changefile Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): place both licenses in root Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): package.json SPDX Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): SPDX everywhere Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * fix(tauri.js): tests more time for ubuntu Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): commons conservancy language Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): add spdx file Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * fix(license): clippy Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com> * chore(license): language Signed-off-by: Daniel Thompson-Yvetot <denjell@mailscript.com>
2021-04-11 01:09:09 +03:00
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
import { invokeTauriCommand } from './helpers/tauri'
import { transformCallback } from './tauri'
/**
* Spawns a process.
*
* @param program The name of the program to execute e.g. 'mkdir' or 'node'
* @param sidecar Whether the program is a sidecar or a system program
* @param onEvent
* @param [args] Command args
* @returns A promise resolving to the process id.
*/
async function execute(
program: string,
sidecar: boolean,
onEvent: (event: CommandEvent) => void,
args?: string | string[]
): Promise<number> {
if (typeof args === 'object') {
Object.freeze(args)
}
return invokeTauriCommand<number>({
__tauriModule: 'Shell',
message: {
cmd: 'execute',
program,
sidecar,
onEventFn: transformCallback(onEvent),
args: typeof args === 'string' ? [args] : args
}
})
}
interface ChildProcess {
code: number | null
signal: number | null
stdout: string
stderr: string
}
class EventEmitter<E> {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
eventListeners: { [key: string]: Array<(arg: any) => void> } = Object.create(
null
)
private addEventListener(event: string, handler: (arg: any) => void): void {
if (event in this.eventListeners) {
// eslint-disable-next-line security/detect-object-injection
this.eventListeners[event].push(handler)
} else {
// eslint-disable-next-line security/detect-object-injection
this.eventListeners[event] = [handler]
}
}
_emit(event: E, payload: any): void {
if (event in this.eventListeners) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const listeners = this.eventListeners[event as any]
for (const listener of listeners) {
listener(payload)
}
}
}
on(event: E, handler: (arg: any) => void): EventEmitter<E> {
this.addEventListener(event as any, handler)
return this
}
}
class Child {
pid: number
constructor(pid: number) {
this.pid = pid
}
async write(data: string | number[]): Promise<void> {
return invokeTauriCommand({
__tauriModule: 'Shell',
message: {
cmd: 'stdinWrite',
pid: this.pid,
buffer: data
}
})
}
async kill(): Promise<void> {
return invokeTauriCommand({
__tauriModule: 'Shell',
message: {
cmd: 'killChild',
pid: this.pid
}
})
}
}
class Command extends EventEmitter<'close' | 'error'> {
program: string
args: string[]
sidecar = false
stdout = new EventEmitter<'data'>()
stderr = new EventEmitter<'data'>()
pid: number | null = null
constructor(program: string, args: string | string[] = []) {
super()
this.program = program
this.args = typeof args === 'string' ? [args] : args
}
/**
* Creates a command to execute the given sidecar binary.
*
* @param program Binary name
* @returns
*/
static sidecar(program: string, args: string | string[] = []): Command {
const instance = new Command(program, args)
instance.sidecar = true
return instance
}
async spawn(): Promise<Child> {
return execute(
this.program,
this.sidecar,
(event) => {
switch (event.event) {
case 'Error':
this._emit('error', event.payload)
break
case 'Terminated':
this._emit('close', event.payload)
break
case 'Stdout':
this.stdout._emit('data', event.payload)
break
case 'Stderr':
this.stderr._emit('data', event.payload)
break
}
},
this.args
).then((pid) => new Child(pid))
}
async execute(): Promise<ChildProcess> {
return new Promise((resolve, reject) => {
this.on('error', reject)
const stdout: string[] = []
const stderr: string[] = []
this.stdout.on('data', (line) => {
stdout.push(line)
})
this.stderr.on('data', (line) => {
stderr.push(line)
})
this.on('close', (payload: TerminatedPayload) => {
resolve({
code: payload.code,
signal: payload.signal,
stdout: stdout.join('\n'),
stderr: stderr.join('\n')
})
})
this.spawn().catch(reject)
})
}
}
interface Event<T, V> {
event: T
payload: V
}
interface TerminatedPayload {
code: number | null
signal: number | null
}
type CommandEvent =
| Event<'Stdout', string>
| Event<'Stderr', string>
| Event<'Terminated', TerminatedPayload>
| Event<'Error', string>
/**
* Opens a path or URL with the system's default app,
* or the one specified with `openWith`.
*
* @param path the path or URL to open
* @param [openWith] the app to open the file or URL with
* @returns
*/
async function open(path: string, openWith?: string): Promise<void> {
return invokeTauriCommand({
__tauriModule: 'Shell',
message: {
cmd: 'open',
path,
with: openWith
}
})
}
export { Command, Child, open }