Webview: Add async method handling

This commit is contained in:
Johannes Kirschbauer
2024-07-11 16:34:27 +02:00
parent 1e4a761c53
commit ac413a4d13

View File

@@ -1,5 +1,6 @@
import { FromSchema } from "json-schema-to-ts"; import { FromSchema } from "json-schema-to-ts";
import { schema } from "@/api"; import { schema } from "@/api";
import { nanoid } from "nanoid";
export type API = FromSchema<typeof schema>; export type API = FromSchema<typeof schema>;
@@ -41,47 +42,44 @@ const operations = schema.properties;
const operationNames = Object.keys(operations) as OperationNames[]; const operationNames = Object.keys(operations) as OperationNames[];
type ObserverRegistry = { type ObserverRegistry = {
[K in OperationNames]: ((response: OperationResponse<K>) => void)[]; [K in OperationNames]: Record<
string,
(response: OperationResponse<K>) => void
>;
}; };
const obs: ObserverRegistry = operationNames.reduce( const registry: ObserverRegistry = operationNames.reduce(
(acc, opName) => ({ (acc, opName) => ({
...acc, ...acc,
[opName]: [], [opName]: {},
}), }),
{} as ObserverRegistry {} as ObserverRegistry
); );
interface ReceiveOptions {
/**
* Calls only the registered function that has the same key as used with dispatch
*
*/
fnKey: string;
}
function createFunctions<K extends OperationNames>( function createFunctions<K extends OperationNames>(
operationName: K operationName: K
): { ): {
dispatch: (args: OperationArgs<K>) => void; dispatch: (args: OperationArgs<K>) => void;
receive: (fn: (response: OperationResponse<K>) => void) => void; receive: (fn: (response: OperationResponse<K>) => void, id: string) => void;
} { } {
return { return {
dispatch: (args: OperationArgs<K>) => { dispatch: (args: OperationArgs<K>) => {
// console.log(
// `Operation: ${String(operationName)}, Arguments: ${JSON.stringify(args)}`
// );
// Send the data to the gtk app // Send the data to the gtk app
window.webkit.messageHandlers.gtk.postMessage({ window.webkit.messageHandlers.gtk.postMessage({
method: operationName, method: operationName,
data: args, data: args,
}); });
}, },
receive: ( receive: (fn: (response: OperationResponse<K>) => void, id: string) => {
fn: (response: OperationResponse<K>) => void // @ts-expect-error: This should work although typescript doesn't let us write
// options?: ReceiveOptions registry[operationName][id] = fn;
) => {
obs[operationName].push(fn);
window.clan[operationName] = (s: string) => { window.clan[operationName] = (s: string) => {
obs[operationName].forEach((f) => deserialize(f)(s)); const f = (response: OperationResponse<K>) => {
if (response.op_key === id) {
registry[operationName][id](response);
}
};
deserialize(f)(s);
}; };
}, },
}; };
@@ -90,7 +88,7 @@ function createFunctions<K extends OperationNames>(
type PyApi = { type PyApi = {
[K in OperationNames]: { [K in OperationNames]: {
dispatch: (args: OperationArgs<K>) => void; dispatch: (args: OperationArgs<K>) => void;
receive: (fn: (response: OperationResponse<K>) => void) => void; receive: (fn: (response: OperationResponse<K>) => void, id: string) => void;
}; };
}; };
@@ -110,6 +108,23 @@ function download(filename: string, text: string) {
document.body.removeChild(element); document.body.removeChild(element);
} }
export const callApi = <K extends OperationNames>(
method: K,
args: OperationArgs<K>
) => {
return new Promise<OperationResponse<K>>((resolve, reject) => {
const id = nanoid();
pyApi[method].receive((response) => {
if (response.status === "error") {
reject(response);
}
resolve(response);
}, id);
pyApi[method].dispatch({ ...args, op_key: id });
});
};
const deserialize = const deserialize =
<T>(fn: (response: T) => void) => <T>(fn: (response: T) => void) =>
(str: string) => { (str: string) => {