@witchcraft/nuxt-electron 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/dist/module.json +1 -1
- package/dist/runtime/electron/createApi.d.ts +74 -0
- package/dist/runtime/electron/createApi.js +8 -0
- package/dist/runtime/electron/createProxiedProtocolHandler.js +0 -5
- package/dist/runtime/electron/getPaths.js +1 -1
- package/dist/runtime/electron/handleApi.d.ts +5 -0
- package/dist/runtime/electron/handleApi.js +4 -0
- package/dist/runtime/electron/index.d.ts +14 -11
- package/dist/runtime/electron/index.js +14 -11
- package/dist/runtime/electron/mergeApi.d.ts +2 -0
- package/dist/runtime/electron/mergeApi.js +18 -0
- package/dist/runtime/electron/promisifyApi.d.ts +4 -2
- package/dist/runtime/electron/promisifyReply.d.ts +5 -1
- package/dist/runtime/electron/types.d.ts +24 -0
- package/package.json +4 -5
- package/src/runtime/electron/createApi.ts +89 -0
- package/src/runtime/electron/createProxiedProtocolHandler.ts +0 -5
- package/src/runtime/electron/getPaths.ts +1 -1
- package/src/runtime/electron/handleApi.ts +19 -0
- package/src/runtime/electron/index.ts +14 -11
- package/src/runtime/electron/mergeApi.ts +22 -0
- package/src/runtime/electron/promisifyApi.ts +4 -2
- package/src/runtime/electron/promisifyReply.ts +5 -1
- package/src/runtime/electron/types.ts +28 -0
package/README.md
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
## Features
|
|
11
11
|
|
|
12
12
|
- :zap: Auto reloads/restarts electron on changes to the main/renderer code or nuxt server restarts.
|
|
13
|
-
- :rocket: Api calls are proxied to the server.
|
|
14
|
-
- :
|
|
13
|
+
- :rocket: Api calls are proxied to the server (you can also easily proxy other requests).
|
|
14
|
+
- :star: Rendering strategy isn't changed. Does not require static builds or changing baseURL/buildAssetsDir.
|
|
15
15
|
- :scissors: Trims server and non-electron routes from the electron bundle.
|
|
16
16
|
- :open_file_folder: Modifies directory structure for easier multi-platform builds.
|
|
17
17
|
- :snowflake: Nix Support - Playground contains an example flake for reproducible development and builds.
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
- Various helpers for setting up apis such as:
|
|
27
27
|
- `createBroadcasters/createBroadcastHandlers` for sending messages to all windows.
|
|
28
28
|
- `createWindowControlsApi` for calling close/minimize/maximize/pin from the renderer and an `ElectronWindowControls` component for rending a basic set.
|
|
29
|
-
- `
|
|
29
|
+
- `createApi`(preload) and `handleApi`(main) for easily creating and handling apis in the preload script.
|
|
30
30
|
- See also [@witchcraft/nuxt-logger](https://github.com/witchcraftjs/nuxt-logger) for electron logging utilities.
|
|
31
31
|
|
|
32
32
|
# Playground
|
|
@@ -61,7 +61,12 @@ See [#Usage on Nix](#Usage-on-Nix) for more details.
|
|
|
61
61
|
## Install
|
|
62
62
|
```bash
|
|
63
63
|
pnpx nuxi module add @witchcraft/nuxt-electron
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Be sure to shamefully hoist if using pnpm:
|
|
64
67
|
|
|
68
|
+
```rc [.npmrc]
|
|
69
|
+
shamefully-hoist=true
|
|
65
70
|
```
|
|
66
71
|
### Components
|
|
67
72
|
|
package/dist/module.json
CHANGED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { ElectronIpcMessages } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Type safe wrapper around ipcRenderer.invoke which also creates the api function at the proper path.
|
|
4
|
+
*
|
|
5
|
+
* This makes every single part of the api type safe while avoiding repetition.
|
|
6
|
+
*
|
|
7
|
+
* ```ts [preload.ts]
|
|
8
|
+
* import { mergeApi, createApi } from "@witchcraft/nuxt-electron/electron"
|
|
9
|
+
*
|
|
10
|
+
* declare module "@witchcraft/nuxt-electron" {
|
|
11
|
+
* interface Register {
|
|
12
|
+
* ElectronIpcTestMethod: {
|
|
13
|
+
* path: "my.test.method"
|
|
14
|
+
* func: (arg1: string, arg2: number) => Promise<void>
|
|
15
|
+
* prefix: "avoidConflict" // optional (and yes you can do avoid.conflict and have it nested further)
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* contextBridge.exposeInMainWorld("electron", {
|
|
21
|
+
* api: mergeApi(
|
|
22
|
+
* createApi("my.test.method"), // returns { "my.test.method": (...args: any[]) => Promise<void> }
|
|
23
|
+
* createApi("avoidConflict.my.test.method") // returns { "avoidConflict.my.test.method": (...args: any[]) => Promise<void> }
|
|
24
|
+
* ),
|
|
25
|
+
* // mergeApi creates a structure like:
|
|
26
|
+
* // {
|
|
27
|
+
* // my: {
|
|
28
|
+
* // test: {
|
|
29
|
+
* // method: (arg1: string, arg2: number) => Promise<void>
|
|
30
|
+
* // }
|
|
31
|
+
* // },
|
|
32
|
+
* // avoidConflict: {
|
|
33
|
+
* // my: {
|
|
34
|
+
* // test: {
|
|
35
|
+
* // method: (arg1: string, arg2: number) => Promise<void>
|
|
36
|
+
* // }
|
|
37
|
+
* // }
|
|
38
|
+
* // }
|
|
39
|
+
* // }
|
|
40
|
+
* })
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* ```ts [main.ts]
|
|
44
|
+
* import { createApi } from "@witchcraft/nuxt-electron/electron"
|
|
45
|
+
* ipcHandle("my.test.method", (event, arg1, arg2) => { ... })
|
|
46
|
+
* ipcHandle("avoidConflict.my.test.method", (event, arg1, arg2) => { ... })
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* Add the window types:
|
|
50
|
+
* ```ts [global.d.ts]
|
|
51
|
+
* import type { ElectronIpcWindowApi } from "@witchcraft/nuxt-electron/electron"
|
|
52
|
+
*
|
|
53
|
+
* declare global {
|
|
54
|
+
* interface Window {
|
|
55
|
+
* electron: {
|
|
56
|
+
* api: ElectronIpcWindowApi
|
|
57
|
+
* }
|
|
58
|
+
* }
|
|
59
|
+
* }
|
|
60
|
+
*
|
|
61
|
+
* export {}
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* Use from the renderer:
|
|
65
|
+
* ```ts [renderer.ts]
|
|
66
|
+
* const res = await window.electron.api.my.test.method("hello", 123)
|
|
67
|
+
* const res2 = await window.electron.avoidConflict.api.my.test.method("hello", 123)
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export declare function createApi<TKey extends keyof ElectronIpcMessages, TEntry extends ElectronIpcMessages[TKey] = ElectronIpcMessages[TKey], TPrefix extends string = TEntry extends {
|
|
71
|
+
prefix: infer P extends string;
|
|
72
|
+
} ? P : "", TPath extends string = TEntry["path"], TFunction extends TEntry["func"] = TEntry["func"]>(path: TPath, prefix?: TPrefix): {
|
|
73
|
+
[K in `${TPrefix}${TPath}`]: TFunction;
|
|
74
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { set } from "@alanscodelog/utils/set";
|
|
2
|
+
import { ipcRenderer } from "electron";
|
|
3
|
+
export function createApi(path, prefix = "") {
|
|
4
|
+
const res = {};
|
|
5
|
+
const fullPath = prefix !== "" ? `${prefix}.${path}` : path;
|
|
6
|
+
set(res, fullPath.split("."), (...args) => ipcRenderer.invoke(fullPath, ...args));
|
|
7
|
+
return res;
|
|
8
|
+
}
|
|
@@ -47,11 +47,6 @@ export function createProxiedProtocolHandler(protocol, protocolName = "app", bas
|
|
|
47
47
|
if (protocol.isProtocolHandled(protocolName)) {
|
|
48
48
|
throw new Error(`Protocol ${protocolName} is already handled.`);
|
|
49
49
|
}
|
|
50
|
-
if (routeProxyKeys.length > 0) {
|
|
51
|
-
if (!process.env.PUBLIC_SERVER_URL && !process.env.VITE_DEV_URL && !process.env.PUBLIC_SERVER_URL) {
|
|
52
|
-
throw new Error("You defined proxy routes but didn't set PUBLIC_SERVER_URL or VITE_DEV_URL set. This is required for the /api routes to work.");
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
50
|
let errorPage404;
|
|
56
51
|
protocol.handle(protocolName, async (request) => {
|
|
57
52
|
errorPage404 ??= await getFromCacheOr(cache, "404.html", (key) => getPathToServe(path.join(basePath, errorPage), "404.html", key, logger), logger);
|
|
@@ -23,7 +23,7 @@ export function getPaths(protocolName = "app", overridingEnvs = {
|
|
|
23
23
|
windowUrl: `${overridingEnvs.windowUrl}${STATIC.ELECTRON_ROUTE}`
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
|
-
if (process.env.NODE_ENV
|
|
26
|
+
if (process.env.NODE_ENV !== "production" && process.env.VITE_DEV_SERVER_URL) {
|
|
27
27
|
return {
|
|
28
28
|
...base,
|
|
29
29
|
windowUrl: `${process.env.VITE_DEV_SERVER_URL}${STATIC.ELECTRON_ROUTE}`
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ElectronIpcMessages } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Type safe wrapper around ipcMain.handle. See {@link createApi} for more info.
|
|
4
|
+
*/
|
|
5
|
+
export declare function handleApi<TFullPath extends keyof ElectronIpcMessages, TFunction extends ElectronIpcMessages[TFullPath]["func"]>(path: TFullPath, cb: (event: Electron.IpcMainInvokeEvent, ...args: Parameters<TFunction>) => ReturnType<TFunction> | Awaited<ReturnType<TFunction>>): void;
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
export * from "./types";
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
2
|
+
export { STATIC } from "./static";
|
|
3
|
+
export { apiBuilder } from "./apiBuilder";
|
|
4
|
+
export { createApi } from "./createApi";
|
|
5
|
+
export { createBroadcastHandlers } from "./createBroadcastHandlers";
|
|
6
|
+
export { createBroadcaster } from "./createBroadcaster";
|
|
4
7
|
export { createPrivilegedProtocolScheme } from "./createPrivilegedProtocolScheme";
|
|
8
|
+
export { createProxiedProtocolHandler } from "./createProxiedProtocolHandler";
|
|
5
9
|
export { createWindowControlsApi } from "./createWindowControlsApi";
|
|
6
10
|
export { createWindowControlsApiHandler } from "./createWindowControlsApiHandler";
|
|
7
|
-
export { useNuxtRuntimeConfig } from "./useNuxtRuntimeConfig";
|
|
8
|
-
export { STATIC } from "./static";
|
|
9
|
-
export { apiBuilder } from "./apiBuilder";
|
|
10
|
-
export { useDevDataDir } from "./useDevDataDir";
|
|
11
|
-
export { registerDevtoolsShortcuts } from "./registerDevtoolsShortcuts";
|
|
12
|
-
export { promisifyApi } from "./promisifyApi";
|
|
13
|
-
export { getPreloadMeta } from "./getPreloadMeta";
|
|
14
11
|
export { getEventWindow } from "./getEventWindow";
|
|
12
|
+
export { getPaths } from "./getPaths";
|
|
13
|
+
export { getPreloadMeta } from "./getPreloadMeta";
|
|
14
|
+
export { handleApi } from "./handleApi";
|
|
15
|
+
export { mergeApi } from "./mergeApi";
|
|
16
|
+
export { promisifyApi } from "./promisifyApi";
|
|
15
17
|
export { promisifyReply } from "./promisifyReply";
|
|
16
|
-
export {
|
|
17
|
-
export {
|
|
18
|
+
export { registerDevtoolsShortcuts } from "./registerDevtoolsShortcuts";
|
|
19
|
+
export { useDevDataDir } from "./useDevDataDir";
|
|
20
|
+
export { useNuxtRuntimeConfig } from "./useNuxtRuntimeConfig";
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
export * from "./types.js";
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
2
|
+
export { STATIC } from "./static.js";
|
|
3
|
+
export { apiBuilder } from "./apiBuilder.js";
|
|
4
|
+
export { createApi } from "./createApi.js";
|
|
5
|
+
export { createBroadcastHandlers } from "./createBroadcastHandlers.js";
|
|
6
|
+
export { createBroadcaster } from "./createBroadcaster.js";
|
|
4
7
|
export { createPrivilegedProtocolScheme } from "./createPrivilegedProtocolScheme.js";
|
|
8
|
+
export { createProxiedProtocolHandler } from "./createProxiedProtocolHandler.js";
|
|
5
9
|
export { createWindowControlsApi } from "./createWindowControlsApi.js";
|
|
6
10
|
export { createWindowControlsApiHandler } from "./createWindowControlsApiHandler.js";
|
|
7
|
-
export { useNuxtRuntimeConfig } from "./useNuxtRuntimeConfig.js";
|
|
8
|
-
export { STATIC } from "./static.js";
|
|
9
|
-
export { apiBuilder } from "./apiBuilder.js";
|
|
10
|
-
export { useDevDataDir } from "./useDevDataDir.js";
|
|
11
|
-
export { registerDevtoolsShortcuts } from "./registerDevtoolsShortcuts.js";
|
|
12
|
-
export { promisifyApi } from "./promisifyApi.js";
|
|
13
|
-
export { getPreloadMeta } from "./getPreloadMeta.js";
|
|
14
11
|
export { getEventWindow } from "./getEventWindow.js";
|
|
12
|
+
export { getPaths } from "./getPaths.js";
|
|
13
|
+
export { getPreloadMeta } from "./getPreloadMeta.js";
|
|
14
|
+
export { handleApi } from "./handleApi.js";
|
|
15
|
+
export { mergeApi } from "./mergeApi.js";
|
|
16
|
+
export { promisifyApi } from "./promisifyApi.js";
|
|
15
17
|
export { promisifyReply } from "./promisifyReply.js";
|
|
16
|
-
export {
|
|
17
|
-
export {
|
|
18
|
+
export { registerDevtoolsShortcuts } from "./registerDevtoolsShortcuts.js";
|
|
19
|
+
export { useDevDataDir } from "./useDevDataDir.js";
|
|
20
|
+
export { useNuxtRuntimeConfig } from "./useNuxtRuntimeConfig.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { get } from "@alanscodelog/utils/get";
|
|
2
|
+
import { set } from "@alanscodelog/utils/set";
|
|
3
|
+
import { walk } from "@alanscodelog/utils/walk";
|
|
4
|
+
export function mergeApi(...apis) {
|
|
5
|
+
const result = {};
|
|
6
|
+
for (const api of apis) {
|
|
7
|
+
walk(api, (el, keyPath) => {
|
|
8
|
+
const value = get(result, keyPath);
|
|
9
|
+
const canExtend = typeof value === "object" || typeof value === "undefined";
|
|
10
|
+
if (!canExtend) {
|
|
11
|
+
throw new Error(`Value (${typeof value}) in way of keypath ${keyPath.join(".")}`);
|
|
12
|
+
} else {
|
|
13
|
+
set(result, keyPath, el);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
@@ -2,7 +2,7 @@ import type { IpcRenderer } from "electron";
|
|
|
2
2
|
/**
|
|
3
3
|
* Promisify an electron api and make it type safe so it can be awaited client side. Note that promisifyReply will throw if it can't find a window.
|
|
4
4
|
*
|
|
5
|
-
* ```ts[
|
|
5
|
+
* ```ts[types.ts]
|
|
6
6
|
* export type ElectronApi = {
|
|
7
7
|
* api: {
|
|
8
8
|
* someApi: (apiParam: string, apiParam2: number) => Promise<void>
|
|
@@ -18,7 +18,7 @@ import type { IpcRenderer } from "electron";
|
|
|
18
18
|
* ```
|
|
19
19
|
*
|
|
20
20
|
* ```ts [main.ts]
|
|
21
|
-
* import { promisifyReply } from "@witchcraft/nuxt-electron/
|
|
21
|
+
* import { promisifyReply } from "@witchcraft/nuxt-electron/electron"
|
|
22
22
|
* promisifyReply<
|
|
23
23
|
* ElectronApi["api"]["someApi"]
|
|
24
24
|
* >(ipcRenderer, MESSAGE.UNIQUE_KEY, (win, apiParam, apiParam2) => {
|
|
@@ -33,6 +33,8 @@ import type { IpcRenderer } from "electron";
|
|
|
33
33
|
* ```
|
|
34
34
|
*
|
|
35
35
|
* By default, calls will timeout and reject after 10 seconds. This can be changed by changing the timeout option.
|
|
36
|
+
*
|
|
37
|
+
* @deprecated Use {@link createApi} instead.
|
|
36
38
|
*/
|
|
37
39
|
export declare function promisifyApi<TKey extends string, TFunction extends (...args: any) => Promise<any>, TArgs extends Parameters<TFunction> = Parameters<TFunction>>(ipcRenderer: IpcRenderer, key: TKey, messageKey: string, modifyArgs?: (args: TArgs) => any, { debug, timeout }?: {
|
|
38
40
|
debug?: boolean;
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { type BrowserWindow } from "electron";
|
|
2
|
-
/**
|
|
2
|
+
/**
|
|
3
|
+
* See {@link promisifyApi} for more info.
|
|
4
|
+
*
|
|
5
|
+
* @deprecated Use {@link handleApi} instead.
|
|
6
|
+
*/
|
|
3
7
|
export declare function promisifyReply<TFunction extends ((...args: any[]) => any), TKey extends string = string, TArgs extends Parameters<TFunction> = Parameters<TFunction>, TReturn extends ReturnType<TFunction> = ReturnType<TFunction>>(key: TKey, cb: (win?: BrowserWindow, ...args: TArgs) => Promise<TReturn> | TReturn,
|
|
4
8
|
/** See {@link getEventWindow}. */
|
|
5
9
|
{ defaultToFocused, debug }?: {
|
|
@@ -1 +1,25 @@
|
|
|
1
|
+
import type { AnyFunction, Flatten, OrToAnd } from "@alanscodelog/utils/types";
|
|
1
2
|
export type WindowControlsApi = (action: "close" | "minimize" | "toggleMaximize" | "togglePin") => Promise<void>;
|
|
3
|
+
export interface Register {
|
|
4
|
+
}
|
|
5
|
+
export type ElectronIpcMessages = Flatten<OrToAnd<{
|
|
6
|
+
[K in keyof Register as K extends `ElectronIpc${string}` ? K : never]: Register[K] extends {
|
|
7
|
+
func: infer TFunc extends AnyFunction;
|
|
8
|
+
path: infer TPath extends string;
|
|
9
|
+
} ? {
|
|
10
|
+
[K2 in Register[K] extends {
|
|
11
|
+
prefix: string;
|
|
12
|
+
} ? `${Register[K]["prefix"]}.${Register[K]["path"]}` : Register[K]["path"]]: {
|
|
13
|
+
func: TFunc;
|
|
14
|
+
path: TPath;
|
|
15
|
+
};
|
|
16
|
+
} : never;
|
|
17
|
+
}[keyof Register & `ElectronIpc${string}`] | {}>>;
|
|
18
|
+
export type PathToObject<TPath extends string, TValue> = TPath extends `${infer Head}.${infer Tail}` ? {
|
|
19
|
+
[K in Head]: PathToObject<Tail, TValue>;
|
|
20
|
+
} : {
|
|
21
|
+
[K in TPath]: TValue;
|
|
22
|
+
};
|
|
23
|
+
export type ElectronIpcWindowApi = Flatten<OrToAnd<{
|
|
24
|
+
[K in keyof ElectronIpcMessages]: PathToObject<K, ElectronIpcMessages[K]["func"]>;
|
|
25
|
+
}[keyof ElectronIpcMessages]>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@witchcraft/nuxt-electron",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.5",
|
|
4
4
|
"description": "Nuxt module for working with electron.",
|
|
5
5
|
"repository": "https://github.com/witchcraftjs/nuxt-electron",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"genDevDesktop.js"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@alanscodelog/utils": "^6.0
|
|
28
|
+
"@alanscodelog/utils": "^6.2.0",
|
|
29
29
|
"@nuxt/kit": "^4.3.1",
|
|
30
30
|
"@witchcraft/nuxt-utils": "^0.3.6",
|
|
31
31
|
"@witchcraft/ui": "^0.3.24",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@alanscodelog/eslint-config": "^6.3.1",
|
|
52
52
|
"@alanscodelog/semantic-release-config": "^6.0.2",
|
|
53
|
-
"@alanscodelog/tsconfigs": "^6.
|
|
53
|
+
"@alanscodelog/tsconfigs": "^6.3.0",
|
|
54
54
|
"@nuxt/eslint-config": "^1.15.1",
|
|
55
55
|
"@nuxt/module-builder": "^1.0.2",
|
|
56
56
|
"@nuxt/schema": "^4.3.1",
|
|
@@ -88,7 +88,6 @@
|
|
|
88
88
|
"lint": "pnpm lint:eslint && pnpm lint:types",
|
|
89
89
|
"lint:eslint": "eslint \"{src,test,playground/app}/**/*.{ts,vue}\" \"*.{js,cjs,mjs,ts}\"",
|
|
90
90
|
"lint:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit",
|
|
91
|
-
"test": "
|
|
92
|
-
"test:watch": "vitest watch"
|
|
91
|
+
"test": "echo \"No tests\""
|
|
93
92
|
}
|
|
94
93
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { set } from "@alanscodelog/utils/set"
|
|
2
|
+
import { ipcRenderer } from "electron"
|
|
3
|
+
|
|
4
|
+
import type { ElectronIpcMessages } from "./types.js"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Type safe wrapper around ipcRenderer.invoke which also creates the api function at the proper path.
|
|
8
|
+
*
|
|
9
|
+
* This makes every single part of the api type safe while avoiding repetition.
|
|
10
|
+
*
|
|
11
|
+
* ```ts [preload.ts]
|
|
12
|
+
* import { mergeApi, createApi } from "@witchcraft/nuxt-electron/electron"
|
|
13
|
+
*
|
|
14
|
+
* declare module "@witchcraft/nuxt-electron" {
|
|
15
|
+
* interface Register {
|
|
16
|
+
* ElectronIpcTestMethod: {
|
|
17
|
+
* path: "my.test.method"
|
|
18
|
+
* func: (arg1: string, arg2: number) => Promise<void>
|
|
19
|
+
* prefix: "avoidConflict" // optional (and yes you can do avoid.conflict and have it nested further)
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* contextBridge.exposeInMainWorld("electron", {
|
|
25
|
+
* api: mergeApi(
|
|
26
|
+
* createApi("my.test.method"), // returns { "my.test.method": (...args: any[]) => Promise<void> }
|
|
27
|
+
* createApi("avoidConflict.my.test.method") // returns { "avoidConflict.my.test.method": (...args: any[]) => Promise<void> }
|
|
28
|
+
* ),
|
|
29
|
+
* // mergeApi creates a structure like:
|
|
30
|
+
* // {
|
|
31
|
+
* // my: {
|
|
32
|
+
* // test: {
|
|
33
|
+
* // method: (arg1: string, arg2: number) => Promise<void>
|
|
34
|
+
* // }
|
|
35
|
+
* // },
|
|
36
|
+
* // avoidConflict: {
|
|
37
|
+
* // my: {
|
|
38
|
+
* // test: {
|
|
39
|
+
* // method: (arg1: string, arg2: number) => Promise<void>
|
|
40
|
+
* // }
|
|
41
|
+
* // }
|
|
42
|
+
* // }
|
|
43
|
+
* // }
|
|
44
|
+
* })
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* ```ts [main.ts]
|
|
48
|
+
* import { createApi } from "@witchcraft/nuxt-electron/electron"
|
|
49
|
+
* ipcHandle("my.test.method", (event, arg1, arg2) => { ... })
|
|
50
|
+
* ipcHandle("avoidConflict.my.test.method", (event, arg1, arg2) => { ... })
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* Add the window types:
|
|
54
|
+
* ```ts [global.d.ts]
|
|
55
|
+
* import type { ElectronIpcWindowApi } from "@witchcraft/nuxt-electron/electron"
|
|
56
|
+
*
|
|
57
|
+
* declare global {
|
|
58
|
+
* interface Window {
|
|
59
|
+
* electron: {
|
|
60
|
+
* api: ElectronIpcWindowApi
|
|
61
|
+
* }
|
|
62
|
+
* }
|
|
63
|
+
* }
|
|
64
|
+
*
|
|
65
|
+
* export {}
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* Use from the renderer:
|
|
69
|
+
* ```ts [renderer.ts]
|
|
70
|
+
* const res = await window.electron.api.my.test.method("hello", 123)
|
|
71
|
+
* const res2 = await window.electron.avoidConflict.api.my.test.method("hello", 123)
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export function createApi<
|
|
75
|
+
TKey extends keyof ElectronIpcMessages,
|
|
76
|
+
TEntry extends ElectronIpcMessages[TKey] = ElectronIpcMessages[TKey],
|
|
77
|
+
// Extract prefix if it exists, otherwise default to empty string
|
|
78
|
+
TPrefix extends string = TEntry extends { prefix: infer P extends string } ? P : "",
|
|
79
|
+
TPath extends string = TEntry["path"],
|
|
80
|
+
TFunction extends TEntry["func"] = TEntry["func"]
|
|
81
|
+
>(
|
|
82
|
+
path: TPath,
|
|
83
|
+
prefix: TPrefix = "" as any
|
|
84
|
+
): { [K in `${TPrefix}${TPath}`]: TFunction } {
|
|
85
|
+
const res: any = {}
|
|
86
|
+
const fullPath = prefix !== "" ? `${prefix}.${path}` : path
|
|
87
|
+
set(res, fullPath.split("."), (...args: Parameters<TFunction>) => ipcRenderer.invoke(fullPath, ...args))
|
|
88
|
+
return res
|
|
89
|
+
}
|
|
@@ -129,11 +129,6 @@ export function createProxiedProtocolHandler(
|
|
|
129
129
|
if (protocol.isProtocolHandled(protocolName)) {
|
|
130
130
|
throw new Error(`Protocol ${protocolName} is already handled.`)
|
|
131
131
|
}
|
|
132
|
-
if (routeProxyKeys.length > 0) {
|
|
133
|
-
if (!process.env.PUBLIC_SERVER_URL && !process.env.VITE_DEV_URL && !process.env.PUBLIC_SERVER_URL) {
|
|
134
|
-
throw new Error("You defined proxy routes but didn't set PUBLIC_SERVER_URL or VITE_DEV_URL set. This is required for the /api routes to work.")
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
132
|
|
|
138
133
|
let errorPage404: string | undefined
|
|
139
134
|
// note that while it would be nice to do protocol.isProtocolRegistered
|
|
@@ -56,7 +56,7 @@ export function getPaths(
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
if (process.env.NODE_ENV
|
|
59
|
+
if (process.env.NODE_ENV !== "production" && process.env.VITE_DEV_SERVER_URL) {
|
|
60
60
|
return {
|
|
61
61
|
...base,
|
|
62
62
|
windowUrl: `${process.env.VITE_DEV_SERVER_URL}${STATIC.ELECTRON_ROUTE}`
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ipcMain } from "electron"
|
|
2
|
+
|
|
3
|
+
import type { ElectronIpcMessages } from "./types.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Type safe wrapper around ipcMain.handle. See {@link createApi} for more info.
|
|
7
|
+
*/
|
|
8
|
+
export function handleApi<
|
|
9
|
+
TFullPath extends keyof ElectronIpcMessages,
|
|
10
|
+
TFunction extends ElectronIpcMessages[TFullPath]["func"]
|
|
11
|
+
>(
|
|
12
|
+
path: TFullPath,
|
|
13
|
+
// it can return a plain value or a promise that will resolve to that value
|
|
14
|
+
// since it's promisified anyways
|
|
15
|
+
cb: (event: Electron.IpcMainInvokeEvent, ...args: Parameters<TFunction>) => ReturnType<TFunction> | Awaited<ReturnType<TFunction>>
|
|
16
|
+
): void {
|
|
17
|
+
ipcMain.handle(path, cb)
|
|
18
|
+
}
|
|
19
|
+
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
export * from "./types"
|
|
2
2
|
// note adding file endings breaks build types
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
3
|
+
export { STATIC } from "./static"
|
|
4
|
+
export { apiBuilder } from "./apiBuilder"
|
|
5
|
+
export { createApi } from "./createApi"
|
|
6
|
+
export { createBroadcastHandlers } from "./createBroadcastHandlers"
|
|
7
|
+
export { createBroadcaster } from "./createBroadcaster"
|
|
5
8
|
export { createPrivilegedProtocolScheme } from "./createPrivilegedProtocolScheme"
|
|
9
|
+
export { createProxiedProtocolHandler } from "./createProxiedProtocolHandler"
|
|
6
10
|
export { createWindowControlsApi } from "./createWindowControlsApi"
|
|
7
11
|
export { createWindowControlsApiHandler } from "./createWindowControlsApiHandler"
|
|
8
|
-
export { useNuxtRuntimeConfig } from "./useNuxtRuntimeConfig"
|
|
9
|
-
export { STATIC } from "./static"
|
|
10
|
-
export { apiBuilder } from "./apiBuilder"
|
|
11
|
-
export { useDevDataDir } from "./useDevDataDir"
|
|
12
|
-
export { registerDevtoolsShortcuts } from "./registerDevtoolsShortcuts"
|
|
13
|
-
export { promisifyApi } from "./promisifyApi"
|
|
14
|
-
export { getPreloadMeta } from "./getPreloadMeta"
|
|
15
12
|
export { getEventWindow } from "./getEventWindow"
|
|
13
|
+
export { getPaths } from "./getPaths"
|
|
14
|
+
export { getPreloadMeta } from "./getPreloadMeta"
|
|
15
|
+
export { handleApi } from "./handleApi"
|
|
16
|
+
export { mergeApi } from "./mergeApi"
|
|
17
|
+
export { promisifyApi } from "./promisifyApi"
|
|
16
18
|
export { promisifyReply } from "./promisifyReply"
|
|
17
|
-
export {
|
|
18
|
-
export {
|
|
19
|
+
export { registerDevtoolsShortcuts } from "./registerDevtoolsShortcuts"
|
|
20
|
+
export { useDevDataDir } from "./useDevDataDir"
|
|
21
|
+
export { useNuxtRuntimeConfig } from "./useNuxtRuntimeConfig"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { get } from "@alanscodelog/utils/get"
|
|
2
|
+
import { set } from "@alanscodelog/utils/set"
|
|
3
|
+
import type { Flatten, OrToAnd } from "@alanscodelog/utils/types"
|
|
4
|
+
import { walk } from "@alanscodelog/utils/walk"
|
|
5
|
+
|
|
6
|
+
export function mergeApi<T extends Record<string, any>[]>(
|
|
7
|
+
...apis: T
|
|
8
|
+
): Flatten<OrToAnd<T[number]>> {
|
|
9
|
+
const result = {} as any
|
|
10
|
+
for (const api of apis) {
|
|
11
|
+
walk(api, (el, keyPath) => {
|
|
12
|
+
const value = get(result, keyPath)
|
|
13
|
+
const canExtend = typeof value === "object" || typeof value === "undefined"
|
|
14
|
+
if (!canExtend) {
|
|
15
|
+
throw new Error(`Value (${typeof value}) in way of keypath ${keyPath.join(".")}`)
|
|
16
|
+
} else {
|
|
17
|
+
set(result, keyPath, el)
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
return result
|
|
22
|
+
}
|
|
@@ -8,7 +8,7 @@ const promiseResolveMap = new Map<string, {
|
|
|
8
8
|
/**
|
|
9
9
|
* Promisify an electron api and make it type safe so it can be awaited client side. Note that promisifyReply will throw if it can't find a window.
|
|
10
10
|
*
|
|
11
|
-
* ```ts[
|
|
11
|
+
* ```ts[types.ts]
|
|
12
12
|
* export type ElectronApi = {
|
|
13
13
|
* api: {
|
|
14
14
|
* someApi: (apiParam: string, apiParam2: number) => Promise<void>
|
|
@@ -24,7 +24,7 @@ const promiseResolveMap = new Map<string, {
|
|
|
24
24
|
* ```
|
|
25
25
|
*
|
|
26
26
|
* ```ts [main.ts]
|
|
27
|
-
* import { promisifyReply } from "@witchcraft/nuxt-electron/
|
|
27
|
+
* import { promisifyReply } from "@witchcraft/nuxt-electron/electron"
|
|
28
28
|
* promisifyReply<
|
|
29
29
|
* ElectronApi["api"]["someApi"]
|
|
30
30
|
* >(ipcRenderer, MESSAGE.UNIQUE_KEY, (win, apiParam, apiParam2) => {
|
|
@@ -39,6 +39,8 @@ const promiseResolveMap = new Map<string, {
|
|
|
39
39
|
* ```
|
|
40
40
|
*
|
|
41
41
|
* By default, calls will timeout and reject after 10 seconds. This can be changed by changing the timeout option.
|
|
42
|
+
*
|
|
43
|
+
* @deprecated Use {@link createApi} instead.
|
|
42
44
|
*/
|
|
43
45
|
export function promisifyApi<
|
|
44
46
|
TKey extends string,
|
|
@@ -2,7 +2,11 @@ import { type BrowserWindow, ipcMain } from "electron"
|
|
|
2
2
|
|
|
3
3
|
import { getEventWindow } from "./getEventWindow.js"
|
|
4
4
|
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* See {@link promisifyApi} for more info.
|
|
7
|
+
*
|
|
8
|
+
* @deprecated Use {@link handleApi} instead.
|
|
9
|
+
*/
|
|
6
10
|
export function promisifyReply<
|
|
7
11
|
TFunction extends ((...args: any[]) => any),
|
|
8
12
|
TKey extends string = string,
|
|
@@ -1 +1,29 @@
|
|
|
1
|
+
import type { AnyFunction, Flatten, OrToAnd } from "@alanscodelog/utils/types"
|
|
2
|
+
|
|
1
3
|
export type WindowControlsApi = (action: "close" | "minimize" | "toggleMaximize" | "togglePin") => Promise<void>
|
|
4
|
+
|
|
5
|
+
export interface Register { }
|
|
6
|
+
|
|
7
|
+
// this is a version of my extension trick, regular version wasn't working
|
|
8
|
+
export type ElectronIpcMessages = Flatten<OrToAnd<{
|
|
9
|
+
[K in keyof Register as K extends `ElectronIpc${string}` ? K : never]:
|
|
10
|
+
Register[K] extends { func: infer TFunc extends AnyFunction, path: infer TPath extends string }
|
|
11
|
+
? {
|
|
12
|
+
[K2 in Register[K] extends { prefix: string } ? `${Register[K]["prefix"]}.${Register[K]["path"]}` : Register[K]["path"]]: {
|
|
13
|
+
func: TFunc
|
|
14
|
+
path: TPath
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
: never
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
19
|
+
}[keyof Register & `ElectronIpc${string}`] | {}>>
|
|
20
|
+
|
|
21
|
+
export type PathToObject<TPath extends string, TValue>
|
|
22
|
+
= TPath extends `${infer Head}.${infer Tail}`
|
|
23
|
+
? { [K in Head]: PathToObject<Tail, TValue> }
|
|
24
|
+
: { [K in TPath]: TValue }
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
export type ElectronIpcWindowApi = Flatten<OrToAnd<{
|
|
28
|
+
[K in keyof ElectronIpcMessages]: PathToObject<K, ElectronIpcMessages[K]["func"]>
|
|
29
|
+
}[keyof ElectronIpcMessages]>>
|