@witchcraft/nuxt-electron 0.2.4 → 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 CHANGED
@@ -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
- - `promisifyApi`(preload) and `promisifyReply`(main) for easily creating and handling apis in the preload script.
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "electron",
3
3
  "configKey": "electron",
4
- "version": "0.2.4",
4
+ "version": "0.2.5",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -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 === "production" && process.env.VITE_DEV_SERVER_URL) {
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;
@@ -0,0 +1,4 @@
1
+ import { ipcMain } from "electron";
2
+ export function handleApi(path, cb) {
3
+ ipcMain.handle(path, cb);
4
+ }
@@ -1,17 +1,20 @@
1
1
  export * from "./types";
2
- export { getPaths } from "./getPaths";
3
- export { createProxiedProtocolHandler } from "./createProxiedProtocolHandler";
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 { createBroadcaster } from "./createBroadcaster";
17
- export { createBroadcastHandlers } from "./createBroadcastHandlers";
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 { getPaths } from "./getPaths.js";
3
- export { createProxiedProtocolHandler } from "./createProxiedProtocolHandler.js";
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 { createBroadcaster } from "./createBroadcaster.js";
17
- export { createBroadcastHandlers } from "./createBroadcastHandlers.js";
18
+ export { registerDevtoolsShortcuts } from "./registerDevtoolsShortcuts.js";
19
+ export { useDevDataDir } from "./useDevDataDir.js";
20
+ export { useNuxtRuntimeConfig } from "./useNuxtRuntimeConfig.js";
@@ -0,0 +1,2 @@
1
+ import type { Flatten, OrToAnd } from "@alanscodelog/utils/types";
2
+ export declare function mergeApi<T extends Record<string, any>[]>(...apis: T): Flatten<OrToAnd<T[number]>>;
@@ -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[type.st]
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/runtime/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
- /** See {@link promisifyApi} for more info. */
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.4",
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.2",
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.2.0",
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": "vitest run",
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 === "production" && process.env.VITE_DEV_SERVER_URL) {
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 { getPaths } from "./getPaths"
4
- export { createProxiedProtocolHandler } from "./createProxiedProtocolHandler"
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 { createBroadcaster } from "./createBroadcaster"
18
- export { createBroadcastHandlers } from "./createBroadcastHandlers"
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[type.st]
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/runtime/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
- /** See {@link promisifyApi} for more info. */
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]>>