{"id":13580723,"url":"https://github.com/vladimiry/electron-rpc-api","last_synced_at":"2025-04-15T18:57:46.230Z","repository":{"id":32635728,"uuid":"138491518","full_name":"vladimiry/electron-rpc-api","owner":"vladimiry","description":"Wrapper around the Electron's IPC for building type-safe API based RPC-like and reactive interactions","archived":false,"fork":false,"pushed_at":"2024-10-02T19:12:10.000Z","size":2766,"stargazers_count":25,"open_issues_count":7,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-10T04:41:01.756Z","etag":null,"topics":["api","electron","ipc","rpc","rxjs"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vladimiry.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-06-24T15:04:03.000Z","updated_at":"2025-04-07T17:22:12.000Z","dependencies_parsed_at":"2023-02-18T03:45:59.582Z","dependency_job_id":"c837454a-0b69-453d-bf9c-c12e0620af00","html_url":"https://github.com/vladimiry/electron-rpc-api","commit_stats":{"total_commits":151,"total_committers":3,"mean_commits":"50.333333333333336","dds":0.5364238410596027,"last_synced_commit":"5ad2e6f58012af4dd79f1c4a052fd40d9ce53cd2"},"previous_names":[],"tags_count":42,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vladimiry%2Felectron-rpc-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vladimiry%2Felectron-rpc-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vladimiry%2Felectron-rpc-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vladimiry%2Felectron-rpc-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vladimiry","download_url":"https://codeload.github.com/vladimiry/electron-rpc-api/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249135790,"owners_count":21218365,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","electron","ipc","rpc","rxjs"],"created_at":"2024-08-01T15:01:54.533Z","updated_at":"2025-04-15T18:57:46.212Z","avatar_url":"https://github.com/vladimiry.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# electron-rpc-api\n\n[![GitHub Actions CI](https://github.com/vladimiry/electron-rpc-api/workflows/GitHub%20Actions%20CI/badge.svg?branch=master)](https://github.com/vladimiry/electron-rpc-api/actions)\n\nIs a wrapper around the Electron's IPC for building type-safe API based RPC-like and reactive interactions.\n\nYou describe an API structure and communication channel only once creating an API Service definition and then you share that definition between provider and client. It means that API method names and types of the input/return parameters on the client side are the same as on the provider side, so you get a type-safety on both sides having no overhead in runtime, thanks to TypeScript.\n\nThe module provides `createIpcMainApiService` and `createWebViewApiService` factory-like functions that can be used to create respective service instances.\n\n## Getting started\n\nYour project needs `rxjs` module to be installed, which is a peer dependency of this project.\n\nMethod resolving and method calling are type-safe actions here:\n\n![type-safety](img/README-img1.gif)\n\n`IpcMainApiService` usage example is shown below. It's based on the [example app](example/electron-app), so you can jump there and run the app.\n\n- First of all an API structure needs to be defined ([example/electron-app/src/shared/ipc-main-api-definition.ts](example/electron-app/src/shared/ipc-main-api-definition.ts)):\n    ```typescript\n    // no need to put API implementation logic here\n    // but only API definition and service instance creating\n    // as this file is supposed to be shared between the provider and client implementations\n    import {ActionType, ScanService, createIpcMainApiService} from \"electron-rpc-api\";\n    \n    const apiDefinition = {\n        ping: ActionType.SubscribableLike\u003c{ domain: string, times: number }, { domain: string, value: number }\u003e(),\n        sanitizeHtml: ActionType.Promise\u003cstring, string\u003e(),\n        quitApp: ActionType.Promise(),\n    };\n    \n    export const IPC_MAIN_API_SERVICE = createIpcMainApiService({\n        channel: \"some-event-name\", // event name used to communicate between the event emitters\n        apiDefinition,\n    });\n    \n    // optionally exposing inferred API structure\n    export type ScannedIpcMainApiService = ScanService\u003ctypeof IPC_MAIN_API_SERVICE\u003e;\n    ```\n\n- API methods implementation and registration in `main` process using previously created `IPC_MAIN_API_SERVICE` service instance ([example/electron-app/src/main/ipc-main-api.ts](example/electron-app/src/main/ipc-main-api.ts)):\n    ```typescript\n    import sanitizeHtml from \"sanitize-html\";\n    import tcpPing from \"tcp-ping\";\n    import {app} from \"electron\";\n    import {interval} from \"rxjs\";\n    import {map, mergeMap, take} from \"rxjs/operators\";\n    import {observableToSubscribableLike} from \"electron-rpc-api\";\n    import {promisify} from \"util\";\n    \n    import {IPC_MAIN_API_SERVICE, ScannedIpcMainApiService} from \"src/shared/ipc-main-api-definition\";\n    \n    export function register(): ScannedIpcMainApiService[\"ApiClient\"] {\n        const api: ScannedIpcMainApiService[\"ApiImpl\"] = {\n            ping: ({domain, times}) =\u003e {\n                return observableToSubscribableLike(\n                    interval(/*one second*/ 1000).pipe(\n                        take(times),\n                        mergeMap(() =\u003e promisify(tcpPing.ping)({address: domain, attempts: times})),\n                        map(({avg: value}) =\u003e {\n                            if (typeof value === \"undefined\" || isNaN(value)) {\n                                throw new Error(`Host \"${domain}\" is unreachable`);\n                            }\n                            return {domain, value};\n                        }),\n                    )\n                );\n            },\n            async sanitizeHtml(input) {\n                return sanitizeHtml(\n                    input,\n                    {\n                        allowedTags: sanitizeHtml.defaults.allowedTags.concat([\"span\"]),\n                        allowedClasses: {\n                            span: [\"badge\", \"badge-light\", \"badge-danger\"],\n                        },\n                    },\n                );\n            },\n            async quitApp() {\n                app.quit();\n            },\n        };\n    \n        IPC_MAIN_API_SERVICE.register(api);\n    \n        return api;\n    }\n    ```\n\n- Exposing the API Client factory function to `renderer` process as `window.__ELECTRON_EXPOSURE__` property using `contextBridge.exposeInMainWorld` call in `preload` script ([example/electron-app/src/renderer/browser-window-preload/index.ts](example/electron-app/src/renderer/browser-window-preload/index.ts)):\n    ```typescript\n    import {contextBridge} from \"electron\";\n    \n    import {ElectronWindow} from \"src/shared/model\";\n    import {IPC_MAIN_API_SERVICE} from \"src/shared/ipc-main-api-definition\";\n    \n    const electronWindow: ElectronWindow = {\n        __ELECTRON_EXPOSURE__: {\n            buildIpcMainClient: IPC_MAIN_API_SERVICE.client.bind(IPC_MAIN_API_SERVICE),\n        },\n    };\n    \n    const exposeKey: keyof typeof electronWindow = \"__ELECTRON_EXPOSURE__\";\n    \n    contextBridge.exposeInMainWorld(exposeKey, electronWindow[exposeKey]);\n    ```\n\n- And finally calling the API methods in `renderer` process using exposed in preload script `window.__ELECTRON_EXPOSURE__` property ([example/electron-app/src/renderer/browser-window/index.ts](example/electron-app/src/renderer/browser-window/index.ts)):\n    ```typescript\n    import {subscribableLikeToObservable} from \"electron-rpc-api\";\n    \n    import \"./index.scss\";\n    \n    // the below code block is recommended for adding if you create/destroy\n    // the renderer processes dynamically (multiple times)\n    const cleanupPromise = new Promise\u003cvoid\u003e((resolve) =\u003e {\n        // don't call \".destroy()\" on the BrowserWindow instance in the main process but \".close()\"\n        // since the app needs \"window.beforeunload\" event handler to be triggered\n        window.addEventListener(\"beforeunload\", () =\u003e resolve());\n    });\n    \n    const ipcMainApiClient = __ELECTRON_EXPOSURE__.buildIpcMainClient({\n        // the below code line is recommended for adding if you create/destroy\n        // the renderer processes dynamically (multiple times)\n        options: {finishPromise: cleanupPromise},\n    });\n    \n    // resolved methods\n    const ipcMainPingMethod = ipcMainApiClient(\"ping\"); // type-safe API method resolving\n    const ipcMainSanitizeHtmlMethod = ipcMainApiClient(\"sanitizeHtml\"); // type-safe API method resolving\n    \n    window.addEventListener(\"DOMContentLoaded\", () =\u003e {\n        const form = document.querySelector(\"form\") as HTMLFormElement;\n        const fieldset = form.querySelector(\"fieldset\") as HTMLFieldSetElement;\n        const input = form.querySelector(\"[name=domain]\") as HTMLInputElement;\n        const times = form.querySelector(\"[name=times]\") as HTMLInputElement;\n        const quitBtn = form.querySelector(`[type=\"button\"]`) as HTMLFormElement;\n        const disableForm = (disable: boolean) =\u003e {\n            disable\n                ? fieldset.setAttribute(\"disabled\", \"disabled\")\n                : fieldset.removeAttribute(\"disabled\")\n        };\n    \n        form.addEventListener(\"submit\", async (event) =\u003e {\n            event.preventDefault();\n            disableForm(true);\n    \n            // type-safe API method calling\n            subscribableLikeToObservable(\n                ipcMainPingMethod({domain: input.value, times: Number(times.value)})\n            ).subscribe(\n                async ({domain, value}) =\u003e {\n                    await append(`\u003cspan class=\"badge badge-light\"\u003e${domain}\u003c/span\u003e \u003csmall\u003e${value}\u003c/small\u003e`);\n                },\n                // \"error\" handler\n                async ({message}) =\u003e {\n                    disableForm(false);\n                    await append(`\u003cspan class=\"badge badge-danger\"\u003e${message}\u003c/span\u003e`);\n                },\n                // \"complete\" handler\n                () =\u003e {\n                    disableForm(false);\n                },\n            );\n        });\n    \n        quitBtn.addEventListener(\"click\", async () =\u003e {\n            await ipcMainApiClient(\"quitApp\")();\n        });\n    });\n    \n    async function append(html: string) {\n        document.body\n            .appendChild(document.createElement(\"div\"))\n            .innerHTML = await ipcMainSanitizeHtmlMethod(html);\n    }\n    ```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvladimiry%2Felectron-rpc-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvladimiry%2Felectron-rpc-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvladimiry%2Felectron-rpc-api/lists"}