s3si.ts/gui/src/services/s3si.tsx

151 lines
3.3 KiB
TypeScript

import { invoke } from "@tauri-apps/api";
import { JSONRPCClient, S3SIService, StdioTransport } from "jsonrpc";
import { ExportOpts, Log, LoggerLevel, State } from "jsonrpc/types";
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
const client = new JSONRPCClient<S3SIService>({
transport: new StdioTransport()
}).getProxy();
const LOG_SUB = new Set<(logs: Log[]) => void>();
async function getLogs() {
while (true) {
const r = await client.getLogs()
if (r.error) {
throw new Error(r.error.message);
}
for (const { level, msg } of r.result) {
switch (level) {
case 'debug':
console.debug(...msg);
break;
case 'log':
console.log(...msg);
break;
case 'warn':
console.warn(...msg);
break;
case 'error':
console.error(...msg);
break;
}
}
for (const cb of LOG_SUB) {
cb(r.result);
}
}
}
getLogs()
export function addLog(...log: Log[]) {
for (const cb of LOG_SUB) {
cb(log);
}
}
const LOG_CONTEXT = createContext<{
logs: Log[],
renderedLogs: React.ReactNode[]
}>({
logs: [],
renderedLogs: [],
});
export const useLog = () => {
return useContext(LOG_CONTEXT);
}
function renderMsg(i: any) {
if (i instanceof Error) {
return i.message
}
return String(i)
}
const DISPLAY_MAP: Record<LoggerLevel, string> = {
debug: 'DEBUG',
log: 'INFO',
warn: 'WARN',
error: 'ERROR',
}
function renderLevel(log: Log) {
return `[${DISPLAY_MAP[log.level]}]`.padEnd(7)
}
function renderLog(log: Log) {
return `${renderLevel(log)} ${log.msg.map(renderMsg).join(' ')}`
}
export const LogProvider: React.FC<{ limit?: number, children?: React.ReactNode }> = ({ children, limit = 10 }) => {
const [logs, setLogs] = useState<Log[]>([]);
useEffect(() => {
const cb = (logs: Log[]) => {
setLogs(old => [...old, ...logs].slice(-limit));
}
LOG_SUB.add(cb);
return () => {
LOG_SUB.delete(cb);
}
}, [limit])
const renderedLogs = useMemo(() => logs.map(renderLog), [logs])
return <LOG_CONTEXT.Provider value={{
logs,
renderedLogs,
}}>
{children}
</LOG_CONTEXT.Provider>
}
export const useLogin = () => {
const login = useCallback(async () => {
const result = await client.loginSteps();
if (result.error) {
throw new Error(result.error.message);
}
const login: string | null = await invoke('open_login_window', {
url: result.result.url
})
if (login === null || login === '') {
console.log('user cancel login');
return;
}
const loginResult: { url: string } = JSON.parse(login);
const sessionToken = await client.loginSteps({
authCodeVerifier: result.result.authCodeVerifier,
login: loginResult.url,
})
if (sessionToken.error) {
throw new Error(sessionToken.error.message);
}
return sessionToken.result;
}, [])
return {
login
}
}
export async function run(state: State, opts: ExportOpts) {
const r = await client.run(state, opts);
if (r.error) {
throw new Error(r.error.message);
}
return r.result;
}
export async function ensureTokenValid(state: State) {
const r = await client.ensureTokenValid(state);
if (r.error) {
throw new Error(r.error.message);
}
return r.result;
}