195 lines
5.9 KiB
JavaScript
195 lines
5.9 KiB
JavaScript
/*
|
|
_ __ _ __
|
|
| | / /___ _(_) /____
|
|
| | /| / / __ `/ / / ___/
|
|
| |/ |/ / /_/ / / (__ )
|
|
|__/|__/\__,_/_/_/____/
|
|
The electron alternative for Go
|
|
(c) Lea Anthony 2019-present
|
|
*/
|
|
import { CancellablePromise } from "./cancellable.js";
|
|
import { newRuntimeCaller, objectNames } from "./runtime.js";
|
|
import { nanoid } from "./nanoid.js";
|
|
// Setup
|
|
window._wails = window._wails || {};
|
|
window._wails.callResultHandler = resultHandler;
|
|
window._wails.callErrorHandler = errorHandler;
|
|
const call = newRuntimeCaller(objectNames.Call);
|
|
const cancelCall = newRuntimeCaller(objectNames.CancelCall);
|
|
const callResponses = new Map();
|
|
const CallBinding = 0;
|
|
const CancelMethod = 0;
|
|
/**
|
|
* Exception class that will be thrown in case the bound method returns an error.
|
|
* The value of the {@link RuntimeError#name} property is "RuntimeError".
|
|
*/
|
|
export class RuntimeError extends Error {
|
|
/**
|
|
* Constructs a new RuntimeError instance.
|
|
* @param message - The error message.
|
|
* @param options - Options to be forwarded to the Error constructor.
|
|
*/
|
|
constructor(message, options) {
|
|
super(message, options);
|
|
this.name = "RuntimeError";
|
|
}
|
|
}
|
|
/**
|
|
* Handles the result of a call request.
|
|
*
|
|
* @param id - The id of the request to handle the result for.
|
|
* @param data - The result data of the request.
|
|
* @param isJSON - Indicates whether the data is JSON or not.
|
|
*/
|
|
function resultHandler(id, data, isJSON) {
|
|
const resolvers = getAndDeleteResponse(id);
|
|
if (!resolvers) {
|
|
return;
|
|
}
|
|
if (!data) {
|
|
resolvers.resolve(undefined);
|
|
}
|
|
else if (!isJSON) {
|
|
resolvers.resolve(data);
|
|
}
|
|
else {
|
|
try {
|
|
resolvers.resolve(JSON.parse(data));
|
|
}
|
|
catch (err) {
|
|
resolvers.reject(new TypeError("could not parse result: " + err.message, { cause: err }));
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Handles the error from a call request.
|
|
*
|
|
* @param id - The id of the promise handler.
|
|
* @param data - The error data to reject the promise handler with.
|
|
* @param isJSON - Indicates whether the data is JSON or not.
|
|
*/
|
|
function errorHandler(id, data, isJSON) {
|
|
const resolvers = getAndDeleteResponse(id);
|
|
if (!resolvers) {
|
|
return;
|
|
}
|
|
if (!isJSON) {
|
|
resolvers.reject(new Error(data));
|
|
}
|
|
else {
|
|
let error;
|
|
try {
|
|
error = JSON.parse(data);
|
|
}
|
|
catch (err) {
|
|
resolvers.reject(new TypeError("could not parse error: " + err.message, { cause: err }));
|
|
return;
|
|
}
|
|
let options = {};
|
|
if (error.cause) {
|
|
options.cause = error.cause;
|
|
}
|
|
let exception;
|
|
switch (error.kind) {
|
|
case "ReferenceError":
|
|
exception = new ReferenceError(error.message, options);
|
|
break;
|
|
case "TypeError":
|
|
exception = new TypeError(error.message, options);
|
|
break;
|
|
case "RuntimeError":
|
|
exception = new RuntimeError(error.message, options);
|
|
break;
|
|
default:
|
|
exception = new Error(error.message, options);
|
|
break;
|
|
}
|
|
resolvers.reject(exception);
|
|
}
|
|
}
|
|
/**
|
|
* Retrieves and removes the response associated with the given ID from the callResponses map.
|
|
*
|
|
* @param id - The ID of the response to be retrieved and removed.
|
|
* @returns The response object associated with the given ID, if any.
|
|
*/
|
|
function getAndDeleteResponse(id) {
|
|
const response = callResponses.get(id);
|
|
callResponses.delete(id);
|
|
return response;
|
|
}
|
|
/**
|
|
* Generates a unique ID using the nanoid library.
|
|
*
|
|
* @returns A unique ID that does not exist in the callResponses set.
|
|
*/
|
|
function generateID() {
|
|
let result;
|
|
do {
|
|
result = nanoid();
|
|
} while (callResponses.has(result));
|
|
return result;
|
|
}
|
|
/**
|
|
* Call a bound method according to the given call options.
|
|
*
|
|
* In case of failure, the returned promise will reject with an exception
|
|
* among ReferenceError (unknown method), TypeError (wrong argument count or type),
|
|
* {@link RuntimeError} (method returned an error), or other (network or internal errors).
|
|
* The exception might have a "cause" field with the value returned
|
|
* by the application- or service-level error marshaling functions.
|
|
*
|
|
* @param options - A method call descriptor.
|
|
* @returns The result of the call.
|
|
*/
|
|
export function Call(options) {
|
|
const id = generateID();
|
|
const result = CancellablePromise.withResolvers();
|
|
callResponses.set(id, { resolve: result.resolve, reject: result.reject });
|
|
const request = call(CallBinding, Object.assign({ "call-id": id }, options));
|
|
let running = false;
|
|
request.then(() => {
|
|
running = true;
|
|
}, (err) => {
|
|
callResponses.delete(id);
|
|
result.reject(err);
|
|
});
|
|
const cancel = () => {
|
|
callResponses.delete(id);
|
|
return cancelCall(CancelMethod, { "call-id": id }).catch((err) => {
|
|
console.error("Error while requesting binding call cancellation:", err);
|
|
});
|
|
};
|
|
result.oncancelled = () => {
|
|
if (running) {
|
|
return cancel();
|
|
}
|
|
else {
|
|
return request.then(cancel);
|
|
}
|
|
};
|
|
return result.promise;
|
|
}
|
|
/**
|
|
* Calls a bound method by name with the specified arguments.
|
|
* See {@link Call} for details.
|
|
*
|
|
* @param methodName - The name of the method in the format 'package.struct.method'.
|
|
* @param args - The arguments to pass to the method.
|
|
* @returns The result of the method call.
|
|
*/
|
|
export function ByName(methodName, ...args) {
|
|
return Call({ methodName, args });
|
|
}
|
|
/**
|
|
* Calls a method by its numeric ID with the specified arguments.
|
|
* See {@link Call} for details.
|
|
*
|
|
* @param methodID - The ID of the method to call.
|
|
* @param args - The arguments to pass to the method.
|
|
* @return The result of the method call.
|
|
*/
|
|
export function ByID(methodID, ...args) {
|
|
return Call({ methodID, args });
|
|
}
|