UNPKG

@itwin/core-frontend

Version:
178 lines • 9.44 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module NativeApp */ Object.defineProperty(exports, "__esModule", { value: true }); exports.NotificationHandler = exports.IpcApp = void 0; const core_bentley_1 = require("@itwin/core-bentley"); const core_common_1 = require("@itwin/core-common"); const Symbols_1 = require("./common/internal/Symbols"); const IModelApp_1 = require("./IModelApp"); /** * The frontend of apps with a dedicated backend that can use [Ipc]($docs/learning/IpcInterface.md). * @public */ class IpcApp { static _ipc; static _removeAppNotify; /** Get the implementation of the [[IpcSocketFrontend]] interface. */ static get ipc() { return this._ipc; } /** Determine whether Ipc is available for this frontend. This will only be true if [[startup]] has been called on this class. */ static get isValid() { return undefined !== this._ipc; } /** * Establish a message handler function for the supplied channel over Ipc. The handler will be called when messages are sent for * the channel via [[BackendIpc.send]]. * @param channel the name of the channel * @param handler the message handler * @returns A function to remove the handler * @note Ipc is only supported if [[isValid]] is true. */ static addListener(channel, handler) { return this.ipc.addListener((0, core_common_1.iTwinChannel)(channel), handler); } /** * Remove a previously registered listener * @param channel The name of the channel for the listener previously registered with [[addListener]] * @param listener The function passed to [[addListener]] */ static removeListener(channel, listener) { this.ipc.removeListener((0, core_common_1.iTwinChannel)(channel), listener); } /** * Send a message to the backend via `channel` and expect a result asynchronously. The handler must be established on the backend via [[BackendIpc.handle]] * @param channel The name of the channel for the method. * @see Electron [ipcRenderer.invoke](https://www.electronjs.org/docs/api/ipc-renderer) documentation for details. * Note that this interface may be implemented via Electron for desktop apps, or via * [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) for mobile or web-based * Ipc connections. In either case, the Electron documentation provides the specifications for how it works. * @note `args` are serialized with the [Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm), so only * primitive types and `ArrayBuffers` are allowed. */ static async invoke(channel, ...args) { return this.ipc.invoke((0, core_common_1.iTwinChannel)(channel), ...args); } /** * Send a message over the socket. * @param channel The name of the channel for the message. * @param data The optional data of the message. * @note `data` is serialized with the [Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm), so only * primitive types and `ArrayBuffers` are allowed. */ static send(channel, ...data) { return this.ipc.send((0, core_common_1.iTwinChannel)(channel), ...data); } /** * Call a method on the backend through an Ipc channel. * @param channelName the channel registered by the backend handler. * @param methodName the name of a method implemented by the backend handler. * @param args arguments to `methodName` * @return a Promise with the return value from `methodName` * @note If the backend implementation throws an exception, this method will throw an exception with its contents * @internal Use [[makeIpcProxy]] for a type-safe interface. */ static async [Symbols_1._callIpcChannel](channelName, methodName, ...args) { const retVal = (await this.invoke(channelName, methodName, ...args)); if (retVal.error === undefined) return retVal.result; // method was successful // backend threw an exception, rethrow one on frontend const err = retVal.error; if (!core_bentley_1.JsonUtils.isObject(err)) { // Exception wasn't an object? throw retVal.error; // eslint-disable-line @typescript-eslint/only-throw-error } // Note: for backwards compatibility, if the exception was from a BentleyError on the backend, throw an exception of type `BackendError`. if (!core_bentley_1.BentleyError.isError(err)) throw Object.assign(new Error(typeof err.message === "string" ? err.message : "unknown error"), err); const trimErr = { ...err }; delete trimErr.iTwinErrorId; // these are methods on BackendError and will cause Object.assign to fail. delete trimErr.loggingMetadata; throw Object.assign(new core_common_1.BackendError(err.errorNumber, err.iTwinErrorId.key, err.message, err.loggingMetadata), trimErr); } /** @internal * @deprecated in 4.8 - will not be removed until after 2026-06-13. Use [[makeIpcProxy]] for a type-safe interface. */ static async callIpcChannel(channelName, methodName, ...args) { return this[Symbols_1._callIpcChannel](channelName, methodName, ...args); } /** Create a type safe Proxy object to make IPC calls to a registered backend interface. * @param channelName the channel registered by the backend handler. */ static makeIpcProxy(channelName) { return new Proxy({}, { get(_target, methodName) { return async (...args) => IpcApp[Symbols_1._callIpcChannel](channelName, methodName, ...args); }, }); } /** Create a type safe Proxy object to call an IPC function on a of registered backend handler that accepts a "methodName" argument followed by optional arguments * @param channelName the channel registered by the backend handler. * @param functionName the function to call on the handler. */ static makeIpcFunctionProxy(channelName, functionName) { return new Proxy({}, { get(_target, methodName) { return async (...args) => IpcApp[Symbols_1._callIpcChannel](channelName, functionName, methodName, ...args); }, }); } /** A Proxy to call one of the [IpcAppFunctions]($common) functions via IPC. */ static appFunctionIpc = IpcApp.makeIpcProxy(core_common_1.ipcAppChannels.functions); /** start an IpcApp. * @note this should not be called directly. It is called by NativeApp.startup */ static async startup(ipc, opts) { this._ipc = ipc; this._removeAppNotify = IpcAppNotifyHandler.register(); // receives notifications from backend await IModelApp_1.IModelApp.startup(opts?.iModelApp); } /** @internal */ static async shutdown() { this._removeAppNotify?.(); this._ipc = undefined; await IModelApp_1.IModelApp.shutdown(); } } exports.IpcApp = IpcApp; /** * Base class for all implementations of an Ipc notification response interface. This class is implemented on your frontend to supply * methods to receive notifications from your backend. * * Create a subclass to implement your Ipc response interface. Your class should be declared like this: * ```ts * class MyNotificationHandler extends NotificationHandler implements MyNotifications * ``` * to ensure all method names and signatures are correct. Your methods cannot have a return value. * * Then, call `MyNotificationHandler.register` at startup to connect your class to your channel. * @public * @extensions */ class NotificationHandler { registerImpl() { return IpcApp.addListener(this.channelName, (_evt, funcName, ...args) => { const func = this[funcName]; if (typeof func !== "function") throw new core_common_1.IModelError(core_bentley_1.IModelStatus.FunctionNotFound, `Method "${this.constructor.name}.${funcName}" not found on NotificationHandler registered for channel: ${this.channelName}`); func.call(this, ...args); }); } /** * Register this class as the handler for notifications on its channel. This static method creates a new instance * that becomes the notification handler and is `this` when its methods are called. * @returns A function that can be called to remove the handler. * @note this method should only be called once per channel. If it is called multiple times, multiple handlers are established. */ static register() { return new this().registerImpl(); // create an instance of subclass. "as any" is necessary because base class is abstract } } exports.NotificationHandler = NotificationHandler; /** IpcApp notifications from backend */ class IpcAppNotifyHandler extends NotificationHandler { get channelName() { return core_common_1.ipcAppChannels.appNotify; } notifyApp() { } } //# sourceMappingURL=IpcApp.js.map