clan-app: working js<->python api bridge

This commit is contained in:
Qubasa
2025-01-04 20:02:43 +01:00
parent 44a2ce1583
commit 0123fcd3a5
15 changed files with 103 additions and 613 deletions

View File

@@ -43,109 +43,15 @@ export interface GtkResponse<T> {
op_key: string;
}
declare global {
interface Window {
clan: ClanOperations;
webkit: {
messageHandlers: {
gtk: {
postMessage: (message: {
method: OperationNames;
data: OperationArgs<OperationNames>;
}) => 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<K>) => void
>;
};
const registry: ObserverRegistry = operationNames.reduce(
(acc, opName) => ({
...acc,
[opName]: {},
}),
{} as ObserverRegistry,
);
function createFunctions<K extends OperationNames>(
operationName: K,
): {
dispatch: (args: OperationArgs<K>) => void;
receive: (fn: (response: OperationResponse<K>) => void, id: string) => void;
} {
window.clan[operationName] = (s: string) => {
const f = (response: OperationResponse<K>) => {
// 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<K>) => {
// Send the data to the gtk app
window.webkit.messageHandlers.gtk.postMessage({
method: operationName,
data: args,
});
},
receive: (fn: (response: OperationResponse<K>) => 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<K>) => void;
receive: (fn: (response: OperationResponse<K>) => 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 = <K extends OperationNames>(
method: K,
args: OperationArgs<K>,
) => {
return new Promise<OperationResponse<K>>((resolve) => {
const id = nanoid();
pyApi[method].receive((response) => {
console.log(method, "Received response: ", { response });
resolve(response);
}, id);
pyApi[method].dispatch({ ...args, op_key: id });
});
console.log("Calling API", method, args);
return (window as any)[method](args);
};
const deserialize =
@@ -161,15 +67,3 @@ const deserialize =
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 };

View File

@@ -1,5 +1,5 @@
import { type Component, createSignal, For, Show } from "solid-js";
import { OperationResponse, pyApi } from "@/src/api";
import { OperationResponse, callApi } from "@/src/api";
import { Button } from "@/src/components/button";
import Icon from "@/src/components/icon";
@@ -16,7 +16,7 @@ export const HostList: Component = () => {
<div class="tooltip tooltip-bottom" data-tip="Refresh install targets">
<Button
variant="light"
onClick={() => pyApi.show_mdns.dispatch({})}
onClick={() => callApi("show_mdns", {})}
startIcon={<Icon icon="Update" />}
></Button>
</div>