@witchcraft/nuxt-electron 0.0.1

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.
Files changed (82) hide show
  1. package/README.md +299 -0
  2. package/dist/module.d.mts +117 -0
  3. package/dist/module.json +9 -0
  4. package/dist/module.mjs +362 -0
  5. package/dist/runtime/components/ElectronWindowControls.d.vue.ts +31 -0
  6. package/dist/runtime/components/ElectronWindowControls.vue +67 -0
  7. package/dist/runtime/components/ElectronWindowControls.vue.d.ts +31 -0
  8. package/dist/runtime/components/WindowControls/CloseButton.d.vue.ts +16 -0
  9. package/dist/runtime/components/WindowControls/CloseButton.vue +54 -0
  10. package/dist/runtime/components/WindowControls/CloseButton.vue.d.ts +16 -0
  11. package/dist/runtime/components/WindowControls/MaximizeButton.d.vue.ts +16 -0
  12. package/dist/runtime/components/WindowControls/MaximizeButton.vue +33 -0
  13. package/dist/runtime/components/WindowControls/MaximizeButton.vue.d.ts +16 -0
  14. package/dist/runtime/components/WindowControls/MinimizeButton.d.vue.ts +16 -0
  15. package/dist/runtime/components/WindowControls/MinimizeButton.vue +40 -0
  16. package/dist/runtime/components/WindowControls/MinimizeButton.vue.d.ts +16 -0
  17. package/dist/runtime/components/WindowControls/PinButton.d.vue.ts +27 -0
  18. package/dist/runtime/components/WindowControls/PinButton.vue +51 -0
  19. package/dist/runtime/components/WindowControls/PinButton.vue.d.ts +27 -0
  20. package/dist/runtime/electron/apiBuilder.d.ts +8 -0
  21. package/dist/runtime/electron/apiBuilder.js +9 -0
  22. package/dist/runtime/electron/createBroadcastHandlers.d.ts +4 -0
  23. package/dist/runtime/electron/createBroadcastHandlers.js +30 -0
  24. package/dist/runtime/electron/createBroadcaster.d.ts +2 -0
  25. package/dist/runtime/electron/createBroadcaster.js +7 -0
  26. package/dist/runtime/electron/createNuxtFileProtocolHandler.d.ts +14 -0
  27. package/dist/runtime/electron/createNuxtFileProtocolHandler.js +20 -0
  28. package/dist/runtime/electron/createWindowControlsApi.d.ts +19 -0
  29. package/dist/runtime/electron/createWindowControlsApi.js +6 -0
  30. package/dist/runtime/electron/createWindowControlsApiHandler.d.ts +4 -0
  31. package/dist/runtime/electron/createWindowControlsApiHandler.js +24 -0
  32. package/dist/runtime/electron/getEventWindow.d.ts +11 -0
  33. package/dist/runtime/electron/getEventWindow.js +8 -0
  34. package/dist/runtime/electron/getPaths.d.ts +27 -0
  35. package/dist/runtime/electron/getPaths.js +33 -0
  36. package/dist/runtime/electron/getPreloadMeta.d.ts +25 -0
  37. package/dist/runtime/electron/getPreloadMeta.js +7 -0
  38. package/dist/runtime/electron/index.d.ts +16 -0
  39. package/dist/runtime/electron/index.js +16 -0
  40. package/dist/runtime/electron/promisifyApi.d.ts +40 -0
  41. package/dist/runtime/electron/promisifyApi.js +41 -0
  42. package/dist/runtime/electron/promisifyReply.d.ts +8 -0
  43. package/dist/runtime/electron/promisifyReply.js +27 -0
  44. package/dist/runtime/electron/registerDevtoolsShortcuts.d.ts +2 -0
  45. package/dist/runtime/electron/registerDevtoolsShortcuts.js +10 -0
  46. package/dist/runtime/electron/static.d.ts +12 -0
  47. package/dist/runtime/electron/static.js +8 -0
  48. package/dist/runtime/electron/types.d.ts +1 -0
  49. package/dist/runtime/electron/types.js +0 -0
  50. package/dist/runtime/electron/useDevDataDir.d.ts +1 -0
  51. package/dist/runtime/electron/useDevDataDir.js +8 -0
  52. package/dist/runtime/electron/useNuxtRuntimeConfig.d.ts +2 -0
  53. package/dist/runtime/electron/useNuxtRuntimeConfig.js +4 -0
  54. package/dist/runtime/utils/isElectron.d.ts +9 -0
  55. package/dist/runtime/utils/isElectron.js +3 -0
  56. package/dist/types.d.mts +3 -0
  57. package/genDevDesktop.js +47 -0
  58. package/package.json +93 -0
  59. package/src/module.ts +549 -0
  60. package/src/runtime/components/ElectronWindowControls.vue +94 -0
  61. package/src/runtime/components/WindowControls/CloseButton.vue +56 -0
  62. package/src/runtime/components/WindowControls/MaximizeButton.vue +35 -0
  63. package/src/runtime/components/WindowControls/MinimizeButton.vue +42 -0
  64. package/src/runtime/components/WindowControls/PinButton.vue +56 -0
  65. package/src/runtime/electron/apiBuilder.ts +27 -0
  66. package/src/runtime/electron/createBroadcastHandlers.ts +36 -0
  67. package/src/runtime/electron/createBroadcaster.ts +11 -0
  68. package/src/runtime/electron/createNuxtFileProtocolHandler.ts +42 -0
  69. package/src/runtime/electron/createWindowControlsApi.ts +27 -0
  70. package/src/runtime/electron/createWindowControlsApiHandler.ts +34 -0
  71. package/src/runtime/electron/getEventWindow.ts +19 -0
  72. package/src/runtime/electron/getPaths.ts +68 -0
  73. package/src/runtime/electron/getPreloadMeta.ts +36 -0
  74. package/src/runtime/electron/index.ts +17 -0
  75. package/src/runtime/electron/promisifyApi.ts +102 -0
  76. package/src/runtime/electron/promisifyReply.ts +49 -0
  77. package/src/runtime/electron/registerDevtoolsShortcuts.ts +12 -0
  78. package/src/runtime/electron/static.ts +14 -0
  79. package/src/runtime/electron/types.ts +1 -0
  80. package/src/runtime/electron/useDevDataDir.ts +8 -0
  81. package/src/runtime/electron/useNuxtRuntimeConfig.ts +7 -0
  82. package/src/runtime/utils/isElectron.ts +11 -0
@@ -0,0 +1,40 @@
1
+ <template>
2
+ <WButton
3
+ :border="false"
4
+ aria-label="Minimize"
5
+ class="
6
+ p-0
7
+ [&:hover_.default-icon:after]:bg-accent-500
8
+ [&:hover_.default-icon:after]:shadow-xs
9
+ [&:hover_.default-icon:after]:shadow-fg/50
10
+ "
11
+ @click="emit('action', 'minimize')"
12
+ >
13
+ <slot>
14
+ <div
15
+ class="
16
+ default-icon
17
+ border-fg
18
+ dark:border-bg
19
+ w-[var(--electron-wc-size)]
20
+ h-[var(--electron-wc-size)]
21
+ relative
22
+ after:absolute
23
+ after:content-['']
24
+ after:bottom-0
25
+ after:left-0
26
+ after:w-[var(--electron-wc-size)]
27
+ after:h-[var(--electron-wc-border)]
28
+ after:rounded-(--electron-wc-rounded)
29
+ after:bg-fg
30
+ dark:after:bg-bg
31
+
32
+ "
33
+ />
34
+ </slot>
35
+ </WButton>
36
+ </template>
37
+
38
+ <script setup>
39
+ const emit = defineEmits(["action"]);
40
+ </script>
@@ -0,0 +1,16 @@
1
+ declare var __VLS_10: {};
2
+ type __VLS_Slots = {} & {
3
+ default?: (props: typeof __VLS_10) => any;
4
+ };
5
+ declare const __VLS_component: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
6
+ action: (action: "minimize") => any;
7
+ }, string, import("vue").PublicProps, Readonly<{}> & Readonly<{
8
+ onAction?: ((action: "minimize") => any) | undefined;
9
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
10
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
11
+ export default _default;
12
+ type __VLS_WithSlots<T, S> = T & {
13
+ new (): {
14
+ $slots: S;
15
+ };
16
+ };
@@ -0,0 +1,27 @@
1
+ type __VLS_PublicProps = {
2
+ /**
3
+ * If there is a `window.electron.on` api that allows listening to events and main sends an `always-on-top-changed`, it will update the state of the pin.
4
+ */
5
+ modelValue?: boolean;
6
+ };
7
+ declare var __VLS_10: {
8
+ alwaysOnTop: any;
9
+ };
10
+ type __VLS_Slots = {} & {
11
+ default?: (props: typeof __VLS_10) => any;
12
+ };
13
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
14
+ "update:modelValue": (value: boolean) => any;
15
+ } & {
16
+ action: (action: "togglePin") => any;
17
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
18
+ onAction?: ((action: "togglePin") => any) | undefined;
19
+ "onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
20
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
22
+ export default _default;
23
+ type __VLS_WithSlots<T, S> = T & {
24
+ new (): {
25
+ $slots: S;
26
+ };
27
+ };
@@ -0,0 +1,51 @@
1
+ <template>
2
+ <WButton
3
+ aria-label="Toggle Always On Top"
4
+ auto-title-from-aria
5
+ :border="false"
6
+ :class="twMerge(
7
+ `
8
+ p-0
9
+ hover:text-accent-500
10
+ `,
11
+ alwaysOnTop && `
12
+ [&_.default-icon_svg]:drop-shadow-[0_1px_1px_rgba(0,0,0,0.5)]
13
+ `,
14
+ !alwaysOnTop && `
15
+ text-neutral-400
16
+ dark:text-neutral-600
17
+ `
18
+ )"
19
+
20
+ @click="emit('action', 'togglePin')"
21
+ >
22
+ <slot v-bind="{ alwaysOnTop }">
23
+ <WIcon
24
+ :class="
25
+ twMerge(
26
+ `
27
+ default-icon
28
+ w-[var(--electron-wc-size)]
29
+ h-[var(--electron-wc-size)]
30
+ flex items-center justify-center
31
+ scale-105
32
+ `
33
+ )
34
+ "
35
+ >
36
+ <i-octicon-pin-16/>
37
+ </WIcon>
38
+ </slot>
39
+ </WButton>
40
+ </template>
41
+
42
+ <script setup>
43
+ import { twMerge } from "#imports";
44
+ const emit = defineEmits(["action"]);
45
+ const alwaysOnTop = defineModel({ type: Boolean, ...{ default: false } });
46
+ if (import.meta.client && window?.electron?.on) {
47
+ window.electron.on("always-on-top-changed", (val) => {
48
+ alwaysOnTop.value = val;
49
+ });
50
+ }
51
+ </script>
@@ -0,0 +1,27 @@
1
+ type __VLS_PublicProps = {
2
+ /**
3
+ * If there is a `window.electron.on` api that allows listening to events and main sends an `always-on-top-changed`, it will update the state of the pin.
4
+ */
5
+ modelValue?: boolean;
6
+ };
7
+ declare var __VLS_10: {
8
+ alwaysOnTop: any;
9
+ };
10
+ type __VLS_Slots = {} & {
11
+ default?: (props: typeof __VLS_10) => any;
12
+ };
13
+ declare const __VLS_component: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
14
+ "update:modelValue": (value: boolean) => any;
15
+ } & {
16
+ action: (action: "togglePin") => any;
17
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
18
+ onAction?: ((action: "togglePin") => any) | undefined;
19
+ "onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
20
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
+ declare const _default: __VLS_WithSlots<typeof __VLS_component, __VLS_Slots>;
22
+ export default _default;
23
+ type __VLS_WithSlots<T, S> = T & {
24
+ new (): {
25
+ $slots: S;
26
+ };
27
+ };
@@ -0,0 +1,8 @@
1
+ import type { DeepPartial } from "@alanscodelog/utils";
2
+ import type { IpcRenderer } from "electron";
3
+ /**
4
+ * Helper for building an api object.
5
+ *
6
+ * @experimental
7
+ */
8
+ export declare function apiBuilder<TPreloadMeta extends Record<string, any>, TRawApi extends Record<string, any> = Record<string, any>, TBuilders extends ((meta: DeepPartial<TPreloadMeta>, ipcRenderer: IpcRenderer) => any)[] = ((meta: DeepPartial<TPreloadMeta>, ipcRenderer: IpcRenderer) => any)[], TBuilderOutput extends ReturnType<TBuilders[number]> = ReturnType<TBuilders[number]>>(ipcRenderer: IpcRenderer, api: TRawApi, meta: TPreloadMeta, builders: TBuilders): TRawApi & TBuilderOutput;
@@ -0,0 +1,9 @@
1
+ import defu from "defu";
2
+ export function apiBuilder(ipcRenderer, api, meta, builders) {
3
+ const resolutions = [];
4
+ resolutions.push(api);
5
+ for (const builder of builders) {
6
+ resolutions.push(builder(meta, ipcRenderer));
7
+ }
8
+ return defu(...resolutions.reverse());
9
+ }
@@ -0,0 +1,4 @@
1
+ export declare function createBroadcastHandlers<TEvents extends Record<string, (...args: any) => any>>(key: string): {
2
+ on: <T extends keyof TEvents>(event: T, listener: TEvents[T]) => void;
3
+ off: <T extends keyof TEvents>(event: T, listener: TEvents[T]) => void;
4
+ };
@@ -0,0 +1,30 @@
1
+ import { ipcRenderer } from "electron";
2
+ export function createBroadcastHandlers(key) {
3
+ const listeners = /* @__PURE__ */ new Map();
4
+ ipcRenderer.on(key, (_e, eventName, ...args) => {
5
+ const cbs = listeners.get(eventName);
6
+ if (cbs) {
7
+ for (const cb of cbs) {
8
+ cb(...args);
9
+ }
10
+ }
11
+ });
12
+ function on(event, listener) {
13
+ const cbs = listeners.get(event);
14
+ if (cbs) {
15
+ cbs.push(listener);
16
+ } else {
17
+ listeners.set(event, [listener]);
18
+ }
19
+ }
20
+ function off(event, listener) {
21
+ const cbs = listeners.get(event);
22
+ if (cbs) {
23
+ cbs.splice(cbs.indexOf(listener), 1);
24
+ }
25
+ }
26
+ return {
27
+ on,
28
+ off
29
+ };
30
+ }
@@ -0,0 +1,2 @@
1
+ import type { BrowserWindow } from "electron";
2
+ export declare function createBroadcaster<TEvents extends Record<string, (...args: any) => any>>(key: string, getWindows: () => BrowserWindow[]): <T extends keyof TEvents>(event: T, ...args: Parameters<TEvents[T]>) => void;
@@ -0,0 +1,7 @@
1
+ export function createBroadcaster(key, getWindows) {
2
+ return function broadcast(event, ...args) {
3
+ for (const win of getWindows()) {
4
+ win.webContents.send(key, event, ...args);
5
+ }
6
+ };
7
+ }
@@ -0,0 +1,14 @@
1
+ export declare function createNuxtFileProtocolHandler(session: Electron.Session, basePath: string,
2
+ /**
3
+ * If any route starts with one of these keys, it will get rerouted to the given value.
4
+ *
5
+ * At least `/api` should be added for a basic nuxt app to work correctly.
6
+ *
7
+ *
8
+ * ```ts
9
+ * routeProxies: {
10
+ * "/api": "http://localhost:3000/api",
11
+ * }
12
+ * ```
13
+ */
14
+ routeProxies: Record<string, string>): void;
@@ -0,0 +1,20 @@
1
+ import { keys } from "@alanscodelog/utils/keys";
2
+ import { net } from "electron";
3
+ import path from "node:path";
4
+ export function createNuxtFileProtocolHandler(session, basePath, routeProxies) {
5
+ const routeProxyKeys = keys(routeProxies);
6
+ session.protocol.handle("file", async (request) => {
7
+ const url = decodeURIComponent(request.url.slice(7));
8
+ const newUrl = path.isAbsolute(url) ? path.resolve(basePath, url.slice(1)) : path.resolve(basePath, url);
9
+ const proxyKey = routeProxyKeys.find((key) => url.startsWith(key));
10
+ if (proxyKey) {
11
+ const proxyUrl = routeProxies[proxyKey];
12
+ return net.fetch(proxyUrl + url);
13
+ }
14
+ const res = await net.fetch(`file://${newUrl}`, {
15
+ // avoid infinite loop
16
+ bypassCustomProtocolHandlers: true
17
+ }).catch((err) => new Response(err, { status: 404 }));
18
+ return res;
19
+ });
20
+ }
@@ -0,0 +1,19 @@
1
+ import type { IpcRenderer } from "electron";
2
+ import type { WindowControlsApi } from "./types.js.js";
3
+ /**
4
+ * Creates the apis to control the window from the preload script / client.
5
+ *
6
+ * ```ts
7
+ * // the default handler expects it to exist under `electron.api.ui.windowAction`
8
+ * // but you can configure it to use a different path
9
+ * constextBridge.exposeInMainWorld("electron", {
10
+ * api: {
11
+ * ui: {
12
+ * ...createWindowControlsApi("windowAction")
13
+ * }
14
+ * }
15
+ * })
16
+ * ```
17
+ */
18
+ export declare function createWindowControlsApi(ipcRenderer: IpcRenderer, name: string): Record<string, WindowControlsApi>;
19
+ export declare const windowControlsMessageKey = "window-control-action";
@@ -0,0 +1,6 @@
1
+ import { promisifyApi } from "./promisifyApi.js";
2
+ const key = "window-control-action";
3
+ export function createWindowControlsApi(ipcRenderer, name) {
4
+ return promisifyApi(ipcRenderer, name, key);
5
+ }
6
+ export const windowControlsMessageKey = key;
@@ -0,0 +1,4 @@
1
+ import type { BrowserWindow } from "electron";
2
+ export declare function createWindowControlsApiHandler(
3
+ /** Mostly for logging. Does not replace the action. */
4
+ cb?: (win: BrowserWindow | undefined, action: "close" | "minimize" | "toggleMaximize" | "togglePin") => void): void;
@@ -0,0 +1,24 @@
1
+ import { windowControlsMessageKey } from "./createWindowControlsApi.js";
2
+ import { promisifyReply } from "./promisifyReply.js";
3
+ export function createWindowControlsApiHandler(cb) {
4
+ promisifyReply(windowControlsMessageKey, async (win, action) => {
5
+ switch (action) {
6
+ case "close":
7
+ win?.close();
8
+ break;
9
+ case "minimize":
10
+ win?.minimize();
11
+ break;
12
+ case "toggleMaximize":
13
+ console.log(win?.isMaximized());
14
+ win?.isMaximized() ? win?.unmaximize() : win?.maximize();
15
+ break;
16
+ case "togglePin":
17
+ win?.setAlwaysOnTop(!win?.isAlwaysOnTop());
18
+ break;
19
+ default:
20
+ throw new Error(`Invalid action: ${action}`);
21
+ }
22
+ cb?.(win, action);
23
+ });
24
+ }
@@ -0,0 +1,11 @@
1
+ import { BrowserWindow } from "electron";
2
+ /**
3
+ * Gets the window that sent the event if it exists, otherwise tries to use the focused window.
4
+ *
5
+ * Set `defaultToFocused` to false to only use the sender window.
6
+ *
7
+ * Returns undefined if no window is found.
8
+ */
9
+ export declare function getEventWindow(e: Electron.IpcMainEvent, { defaultToFocused }?: {
10
+ defaultToFocused?: boolean | undefined;
11
+ }): BrowserWindow | undefined;
@@ -0,0 +1,8 @@
1
+ import { BrowserWindow } from "electron";
2
+ export function getEventWindow(e, { defaultToFocused = true } = {}) {
3
+ const senderWindow = BrowserWindow.fromId(e.sender.id);
4
+ if (defaultToFocused) {
5
+ return senderWindow ?? BrowserWindow.getFocusedWindow() ?? void 0;
6
+ }
7
+ return senderWindow ?? void 0;
8
+ }
@@ -0,0 +1,27 @@
1
+ export declare function forceRelativePath(filepath: string): string;
2
+ /**
3
+ * Calculates the correct paths for the app to run in electron.
4
+ *
5
+ * Note: For serverUrl to not be overridable in production, you should set `electron.additionalElectronVariables.publicServerUrl` to a quoted string.
6
+ *
7
+ * ```ts[nuxt.config.ts]
8
+ * export default defineNuxtConfig({
9
+ * modules: [
10
+ * "@witchcraft/nuxt-electron",
11
+ * ],
12
+ * electron: {
13
+ * additionalElectronVariables: {
14
+ * publicServerUrl: process.env.NODE_ENV === "production"
15
+ * ? `"mysite.com"`
16
+ * : `undefined`
17
+ * }
18
+ * }
19
+ * })
20
+ * ```
21
+ */
22
+ export declare function getPaths(): {
23
+ windowUrl: string;
24
+ publicServerUrl: string;
25
+ nuxtPublicDir: string;
26
+ preloadPath: string;
27
+ };
@@ -0,0 +1,33 @@
1
+ import { unreachable } from "@alanscodelog/utils/unreachable";
2
+ import { app } from "electron";
3
+ import path from "node:path";
4
+ import { STATIC } from "./static.js";
5
+ export function forceRelativePath(filepath) {
6
+ return path.join(`.${path.sep}`, filepath);
7
+ }
8
+ export function getPaths() {
9
+ const rootDir = app.getAppPath();
10
+ const nuxtPublicDir = path.resolve(rootDir, forceRelativePath(STATIC.ELECTRON_NUXT_PUBLIC_DIR));
11
+ const preloadPath = path.resolve(rootDir, forceRelativePath(STATIC.ELECTRON_BUILD_DIR), "./preload.cjs");
12
+ const base = {
13
+ nuxtPublicDir,
14
+ preloadPath,
15
+ publicServerUrl: process.env.PUBLIC_SERVER_URL ?? process.env.PUBLIC_SERVER_URL ?? process.env.VITE_DEV_SERVER_URL
16
+ };
17
+ if (!base.publicServerUrl) {
18
+ throw new Error("publicServerUrl could not be determined.");
19
+ }
20
+ if (process.env.VITE_DEV_SERVER_URL) {
21
+ return {
22
+ ...base,
23
+ windowUrl: `${process.env.VITE_DEV_SERVER_URL}${STATIC.ELECTRON_ROUTE}`
24
+ };
25
+ } else if (STATIC.ELECTRON_PROD_URL && STATIC.ELECTRON_BUILD_DIR) {
26
+ return {
27
+ ...base,
28
+ // careful, do not use path.join, it will remove extra slashes
29
+ windowUrl: `file://${STATIC.ELECTRON_PROD_URL}`
30
+ };
31
+ }
32
+ unreachable();
33
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The preload script can be passed arguments using the `webPreferences.additionalArguments` option.
3
+ *
4
+ * ````ts [main.ts]
5
+ * const meta = { ... }
6
+ *
7
+ * const win = new BrowserWindow({
8
+ * webPreferences: {
9
+ * additionalArguments: [
10
+ * // you can change which key is used
11
+ * `--metadata=${JSON.stringify(meta)}`,
12
+ * ],
13
+ * },
14
+ * })
15
+ * ```
16
+ *
17
+ * ````ts [preload.ts]
18
+ * const meta = getPreloadMeta<Meta>("metadata")
19
+ * ```
20
+ *
21
+ * This function can be used to get the metadata and type it in the preload script.
22
+ *
23
+ * Note that if it can't find the argument or there is an error parsing the json, it will throw.
24
+ */
25
+ export declare function getPreloadMeta<T>(key: string): T;
@@ -0,0 +1,7 @@
1
+ export function getPreloadMeta(key) {
2
+ const meta = process.argv.find((arg) => arg.startsWith(`--${key}=`))?.split("=")?.[1];
3
+ if (!meta) {
4
+ throw new Error("No metadata passed to renderer.");
5
+ }
6
+ return JSON.parse(meta);
7
+ }
@@ -0,0 +1,16 @@
1
+ export * from "./types.js";
2
+ export { getPaths } from "./getPaths.js";
3
+ export { createNuxtFileProtocolHandler } from "./createNuxtFileProtocolHandler.js";
4
+ export { createWindowControlsApi } from "./createWindowControlsApi.js";
5
+ export { createWindowControlsApiHandler } from "./createWindowControlsApiHandler.js";
6
+ export { useNuxtRuntimeConfig } from "./useNuxtRuntimeConfig.js";
7
+ export { STATIC } from "./static.js";
8
+ export { apiBuilder } from "./apiBuilder.js";
9
+ export { useDevDataDir } from "./useDevDataDir.js";
10
+ export { registerDevtoolsShortcuts } from "./registerDevtoolsShortcuts.js";
11
+ export { promisifyApi } from "./promisifyApi.js";
12
+ export { getPreloadMeta } from "./getPreloadMeta.js";
13
+ export { getEventWindow } from "./getEventWindow.js";
14
+ export { promisifyReply } from "./promisifyReply.js";
15
+ export { createBroadcaster } from "./createBroadcaster.js";
16
+ export { createBroadcastHandlers } from "./createBroadcastHandlers.js";
@@ -0,0 +1,16 @@
1
+ export * from "./types.js";
2
+ export { getPaths } from "./getPaths.js";
3
+ export { createNuxtFileProtocolHandler } from "./createNuxtFileProtocolHandler.js";
4
+ export { createWindowControlsApi } from "./createWindowControlsApi.js";
5
+ export { createWindowControlsApiHandler } from "./createWindowControlsApiHandler.js";
6
+ export { useNuxtRuntimeConfig } from "./useNuxtRuntimeConfig.js";
7
+ export { STATIC } from "./static.js";
8
+ export { apiBuilder } from "./apiBuilder.js";
9
+ export { useDevDataDir } from "./useDevDataDir.js";
10
+ export { registerDevtoolsShortcuts } from "./registerDevtoolsShortcuts.js";
11
+ export { promisifyApi } from "./promisifyApi.js";
12
+ export { getPreloadMeta } from "./getPreloadMeta.js";
13
+ export { getEventWindow } from "./getEventWindow.js";
14
+ export { promisifyReply } from "./promisifyReply.js";
15
+ export { createBroadcaster } from "./createBroadcaster.js";
16
+ export { createBroadcastHandlers } from "./createBroadcastHandlers.js";
@@ -0,0 +1,40 @@
1
+ import type { IpcRenderer } from "electron";
2
+ /**
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
+ *
5
+ * ```ts[type.st]
6
+ * export type ElectronApi = {
7
+ * api: {
8
+ * someApi: (apiParam: string, apiParam2: number) => Promise<void>
9
+ * }
10
+ * }
11
+ * ```
12
+ * ```ts [preload.ts]
13
+ * contextBridge.exposeInMainWorld("electron", {
14
+ * api: {
15
+ * ...promisifyApi<"someApi", ElectronApi["api"]["someApi"]>(ipcRenderer, "someApi", MESSAGE.UNIQUE_KEY),
16
+ * },
17
+ * })
18
+ * ```
19
+ *
20
+ * ```ts [main.ts]
21
+ * import { promisifyReply } from "@witchcraft/nuxt-electron/runtime/electron"
22
+ * promisifyReply<
23
+ * ElectronApi["api"]["someApi"]
24
+ * >(ipcRenderer, MESSAGE.UNIQUE_KEY, (win, apiParam, apiParam2) => {
25
+ * // this doesn't have to be async, though on the client side,
26
+ * // it will always be async
27
+ * return electronSideOfApi(apiParam, apiParam2)
28
+ * })
29
+ * ```
30
+ *
31
+ * ```ts [renderer.ts]
32
+ * const result = await electron.api.someApi(apiParam, apiParam2)
33
+ * ```
34
+ *
35
+ * By default, calls will timeout and reject after 10 seconds. This can be changed by changing the timeout option.
36
+ */
37
+ 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
+ debug?: boolean;
39
+ timeout?: number;
40
+ }): Record<TKey, TFunction>;
@@ -0,0 +1,41 @@
1
+ const promiseResolveMap = /* @__PURE__ */ new Map();
2
+ export function promisifyApi(ipcRenderer, key, messageKey, modifyArgs, { debug, timeout } = { debug: true, timeout: 1e4 }) {
3
+ return {
4
+ [key]: async (...args) => new Promise((resolve, reject) => {
5
+ const promiseId = crypto.randomUUID();
6
+ promiseResolveMap.set(promiseId, { resolve, reject });
7
+ if (modifyArgs) {
8
+ args = modifyArgs(args);
9
+ }
10
+ if (debug) {
11
+ console.log("promisifyApi:sent", messageKey, promiseId, args);
12
+ }
13
+ const timer = setTimeout(() => {
14
+ const resolver = promiseResolveMap.get(promiseId);
15
+ if (resolver) {
16
+ promiseResolveMap.delete(promiseId);
17
+ ipcRenderer.off(messageKey, listener);
18
+ resolver.reject(new Error(`promisifyApi: Timeout for ${messageKey}`));
19
+ }
20
+ }, timeout);
21
+ function listener(_event, resPromiseId, res, info) {
22
+ if (debug) {
23
+ console.log("promiseApi:received", resPromiseId, res);
24
+ }
25
+ const resolver = promiseResolveMap.get(resPromiseId);
26
+ if (resolver) {
27
+ clearTimeout(timer);
28
+ promiseResolveMap.delete(resPromiseId);
29
+ ipcRenderer.off(messageKey, listener);
30
+ if (info?.isError) {
31
+ resolver.reject(res);
32
+ } else {
33
+ resolver.resolve(res);
34
+ }
35
+ }
36
+ }
37
+ ipcRenderer.on(messageKey, listener);
38
+ ipcRenderer.send(messageKey, promiseId, ...args);
39
+ })
40
+ };
41
+ }
@@ -0,0 +1,8 @@
1
+ import { type BrowserWindow } from "electron";
2
+ /** See {@link promisifyApi} for more info. */
3
+ 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
+ /** See {@link getEventWindow}. */
5
+ { defaultToFocused, debug }?: {
6
+ defaultToFocused?: boolean;
7
+ debug?: boolean;
8
+ }): void;
@@ -0,0 +1,27 @@
1
+ import { ipcMain } from "electron";
2
+ import { getEventWindow } from "./getEventWindow.js";
3
+ export function promisifyReply(key, cb, {
4
+ defaultToFocused = true,
5
+ debug = false
6
+ } = {}) {
7
+ ipcMain.on(key, (e, promiseId, ...args) => {
8
+ const win = getEventWindow(e, { defaultToFocused });
9
+ if (!win) {
10
+ throw new Error("No window to send reply to.");
11
+ }
12
+ if (debug) {
13
+ console.log({ key, promiseId, args });
14
+ }
15
+ const res = cb(win, ...args);
16
+ if (res instanceof Promise) {
17
+ res.then((innerRes) => {
18
+ win.webContents.send(key, promiseId, innerRes);
19
+ }).catch((err) => {
20
+ console.error(err, { key, promiseId, args });
21
+ win.webContents.send(key, promiseId, err, { isError: true });
22
+ });
23
+ } else {
24
+ win.webContents.send(key, promiseId, res);
25
+ }
26
+ });
27
+ }
@@ -0,0 +1,2 @@
1
+ /** Registers F12 and Ctrl+Shift+I as shortcuts to open the devtools. */
2
+ export declare function registerDevtoolsShortcuts(win: Electron.BrowserWindow): void;
@@ -0,0 +1,10 @@
1
+ export function registerDevtoolsShortcuts(win) {
2
+ win.webContents.on("before-input-event", (event, input) => {
3
+ if (input.type === "keyDown") {
4
+ if (input.shift && input.control && input.key.toLowerCase() === "i" || input.key === "F12") {
5
+ win.webContents.openDevTools();
6
+ event.preventDefault();
7
+ }
8
+ }
9
+ });
10
+ }
@@ -0,0 +1,12 @@
1
+ import type { PublicRuntimeConfig } from "nuxt/schema";
2
+ /**
3
+ * Group of the module's vite's injected variables.
4
+ */
5
+ export declare const STATIC: {
6
+ ELECTRON_ROUTE: string;
7
+ ELECTRON_PROD_URL: string;
8
+ ELECTRON_NUXT_DIR: string;
9
+ ELECTRON_NUXT_PUBLIC_DIR: string;
10
+ ELECTRON_BUILD_DIR: string;
11
+ ELECTRON_RUNTIME_CONFIG: PublicRuntimeConfig;
12
+ };
@@ -0,0 +1,8 @@
1
+ export const STATIC = {
2
+ ELECTRON_ROUTE: process.env.ELECTRON_ROUTE,
3
+ ELECTRON_PROD_URL: process.env.ELECTRON_PROD_URL,
4
+ ELECTRON_NUXT_DIR: process.env.ELECTRON_NUXT_DIR,
5
+ ELECTRON_NUXT_PUBLIC_DIR: process.env.ELECTRON_NUXT_PUBLIC_DIR,
6
+ ELECTRON_BUILD_DIR: process.env.ELECTRON_BUILD_DIR,
7
+ ELECTRON_RUNTIME_CONFIG: process.env.ELECTRON_RUNTIME_CONFIG ? JSON.parse(process.env.ELECTRON_RUNTIME_CONFIG) : void 0
8
+ };
@@ -0,0 +1 @@
1
+ export type WindowControlsApi = (action: "close" | "minimize" | "toggleMaximize" | "togglePin") => Promise<void>;
File without changes
@@ -0,0 +1 @@
1
+ export declare function useDevDataDir(): string | undefined;