import schema from "@/api/API.json" assert { type: "json" }; import { API } from "@/api/API"; import { nanoid } from "nanoid"; export type OperationNames = keyof API; export type OperationArgs = API[T]["arguments"]; export type OperationResponse = API[T]["return"]; export type SuccessQuery = Extract< OperationResponse, { status: "success" } >; export type SuccessData = SuccessQuery["data"]; export type ErrorQuery = Extract< OperationResponse, { status: "error" } >; export type ErrorData = ErrorQuery["errors"]; export type ClanOperations = { [K in OperationNames]: (str: string) => void; }; export interface GtkResponse { result: T; op_key: string; } declare global { interface Window { clan: ClanOperations; webkit: { messageHandlers: { gtk: { postMessage: (message: { method: OperationNames; data: OperationArgs; }) => void; }; }; }; } } // Make sure window.webkit is defined although the type is not correctly filled yet. window.clan = {} as ClanOperations; const operations = schema.properties; const operationNames = Object.keys(operations) as OperationNames[]; type ObserverRegistry = { [K in OperationNames]: Record< string, (response: OperationResponse) => void >; }; const registry: ObserverRegistry = operationNames.reduce( (acc, opName) => ({ ...acc, [opName]: {}, }), {} as ObserverRegistry, ); function createFunctions( operationName: K, ): { dispatch: (args: OperationArgs) => void; receive: (fn: (response: OperationResponse) => void, id: string) => void; } { window.clan[operationName] = (s: string) => { const f = (response: OperationResponse) => { // Get the correct receiver function for the op_key const receiver = registry[operationName][response.op_key]; if (receiver) { receiver(response); } }; deserialize(f)(s); }; return { dispatch: (args: OperationArgs) => { // Send the data to the gtk app window.webkit.messageHandlers.gtk.postMessage({ method: operationName, data: args, }); }, receive: (fn: (response: OperationResponse) => void, id: string) => { // @ts-expect-error: This should work although typescript doesn't let us write registry[operationName][id] = fn; }, }; } type PyApi = { [K in OperationNames]: { dispatch: (args: OperationArgs) => void; receive: (fn: (response: OperationResponse) => void, id: string) => void; }; }; function download(filename: string, text: string) { const element = document.createElement("a"); element.setAttribute( "href", "data:text/plain;charset=utf-8," + encodeURIComponent(text), ); element.setAttribute("download", filename); element.style.display = "none"; document.body.appendChild(element); element.click(); document.body.removeChild(element); } export const callApi = ( method: K, args: OperationArgs, ) => { return new Promise>((resolve) => { const id = nanoid(); pyApi[method].receive((response) => { console.log(method, "Received response: ", { response }); resolve(response); }, id); pyApi[method].dispatch({ ...args, op_key: id }); }); }; const deserialize = (fn: (response: T) => void) => (str: string) => { try { const r = JSON.parse(str) as T; fn(r); } catch (e) { console.log("Error parsing JSON: ", e); window.localStorage.setItem("error", str); console.error(str); console.error("See localStorage 'error'"); alert(`Error parsing JSON: ${e}`); } }; // Create the API object const pyApi: PyApi = {} as PyApi; operationNames.forEach((opName) => { const name = opName as OperationNames; // @ts-expect-error - TODO: Fix this. Typescript is not recognizing the receive function correctly pyApi[name] = createFunctions(name); }); export { pyApi };