electron-state-sync 1.1.0
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/CLAUDE.md +254 -0
- package/LICENSE +21 -0
- package/README.md +753 -0
- package/README.zh-CN.md +743 -0
- package/bun.lock +542 -0
- package/bunfig.toml +7 -0
- package/dist/jotai.cjs +106 -0
- package/dist/jotai.d.cts +48 -0
- package/dist/jotai.d.cts.map +1 -0
- package/dist/jotai.d.ts +48 -0
- package/dist/jotai.d.ts.map +1 -0
- package/dist/jotai.js +105 -0
- package/dist/jotai.js.map +1 -0
- package/dist/main.cjs +177 -0
- package/dist/main.d.cts +26 -0
- package/dist/main.d.cts.map +1 -0
- package/dist/main.d.ts +26 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +177 -0
- package/dist/main.js.map +1 -0
- package/dist/preact.cjs +46 -0
- package/dist/preact.d.cts +11 -0
- package/dist/preact.d.cts.map +1 -0
- package/dist/preact.d.ts +11 -0
- package/dist/preact.d.ts.map +1 -0
- package/dist/preact.js +46 -0
- package/dist/preact.js.map +1 -0
- package/dist/preload.cjs +51 -0
- package/dist/preload.d.cts +20 -0
- package/dist/preload.d.cts.map +1 -0
- package/dist/preload.d.ts +20 -0
- package/dist/preload.d.ts.map +1 -0
- package/dist/preload.js +51 -0
- package/dist/preload.js.map +1 -0
- package/dist/react-query.cjs +113 -0
- package/dist/react-query.d.cts +58 -0
- package/dist/react-query.d.cts.map +1 -0
- package/dist/react-query.d.ts +58 -0
- package/dist/react-query.d.ts.map +1 -0
- package/dist/react-query.js +112 -0
- package/dist/react-query.js.map +1 -0
- package/dist/react.cjs +46 -0
- package/dist/react.d.cts +11 -0
- package/dist/react.d.cts.map +1 -0
- package/dist/react.d.ts +11 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +46 -0
- package/dist/react.js.map +1 -0
- package/dist/redux.cjs +148 -0
- package/dist/redux.d.cts +80 -0
- package/dist/redux.d.cts.map +1 -0
- package/dist/redux.d.ts +80 -0
- package/dist/redux.d.ts.map +1 -0
- package/dist/redux.js +146 -0
- package/dist/redux.js.map +1 -0
- package/dist/renderer/index.cjs +7 -0
- package/dist/renderer/index.d.cts +23 -0
- package/dist/renderer/index.d.cts.map +1 -0
- package/dist/renderer/index.d.ts +23 -0
- package/dist/renderer/index.d.ts.map +1 -0
- package/dist/renderer/index.js +3 -0
- package/dist/renderer-C7zF3UQm.js +57 -0
- package/dist/renderer-C7zF3UQm.js.map +1 -0
- package/dist/renderer-D3YziJ_U.cjs +86 -0
- package/dist/solid.cjs +74 -0
- package/dist/solid.d.cts +13 -0
- package/dist/solid.d.cts.map +1 -0
- package/dist/solid.d.ts +13 -0
- package/dist/solid.d.ts.map +1 -0
- package/dist/solid.js +74 -0
- package/dist/solid.js.map +1 -0
- package/dist/svelte.cjs +63 -0
- package/dist/svelte.d.cts +14 -0
- package/dist/svelte.d.cts.map +1 -0
- package/dist/svelte.d.ts +14 -0
- package/dist/svelte.d.ts.map +1 -0
- package/dist/svelte.js +63 -0
- package/dist/svelte.js.map +1 -0
- package/dist/types-7wPPX0ty.d.ts +37 -0
- package/dist/types-7wPPX0ty.d.ts.map +1 -0
- package/dist/types-C18dHgLI.d.cts +37 -0
- package/dist/types-C18dHgLI.d.cts.map +1 -0
- package/dist/vue.cjs +69 -0
- package/dist/vue.d.cts +15 -0
- package/dist/vue.d.cts.map +1 -0
- package/dist/vue.d.ts +15 -0
- package/dist/vue.d.ts.map +1 -0
- package/dist/vue.js +70 -0
- package/dist/vue.js.map +1 -0
- package/dist/zustand.cjs +193 -0
- package/dist/zustand.d.cts +61 -0
- package/dist/zustand.d.cts.map +1 -0
- package/dist/zustand.d.ts +61 -0
- package/dist/zustand.d.ts.map +1 -0
- package/dist/zustand.js +191 -0
- package/dist/zustand.js.map +1 -0
- package/package.json +162 -0
package/dist/main.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { ipcMain } from "electron";
|
|
2
|
+
|
|
3
|
+
//#region src/channels.ts
|
|
4
|
+
const createSyncStateChannels = (options) => {
|
|
5
|
+
const channelPrefix = `${options.baseChannel ?? "state"}:${options.name}`;
|
|
6
|
+
return {
|
|
7
|
+
getChannel: `${channelPrefix}:get`,
|
|
8
|
+
setChannel: `${channelPrefix}:set`,
|
|
9
|
+
subscribeChannel: `${channelPrefix}:subscribe`,
|
|
10
|
+
unsubscribeChannel: `${channelPrefix}:unsubscribe`,
|
|
11
|
+
updateChannel: `${channelPrefix}:update`
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/types.ts
|
|
17
|
+
var SyncStateError = class SyncStateError extends Error {
|
|
18
|
+
code;
|
|
19
|
+
stateName;
|
|
20
|
+
baseChannel;
|
|
21
|
+
cause;
|
|
22
|
+
constructor(code, message, context) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.code = code;
|
|
25
|
+
this.name = "SyncStateError";
|
|
26
|
+
this.stateName = context?.stateName;
|
|
27
|
+
this.baseChannel = context?.baseChannel;
|
|
28
|
+
this.cause = context?.cause;
|
|
29
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, SyncStateError);
|
|
30
|
+
}
|
|
31
|
+
getFullMessage() {
|
|
32
|
+
const parts = [this.message];
|
|
33
|
+
if (this.stateName) parts.push(`\n State name: "${this.stateName}"`);
|
|
34
|
+
if (this.baseChannel) parts.push(`\n Channel prefix: "${this.baseChannel}"`);
|
|
35
|
+
if (this.cause) parts.push(`\n Original error: ${this.cause.message}`);
|
|
36
|
+
return parts.join("");
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/main.ts
|
|
42
|
+
let globalConfig = {};
|
|
43
|
+
const initSyncStateMain = (config) => {
|
|
44
|
+
globalConfig = { ...config };
|
|
45
|
+
};
|
|
46
|
+
const createReadonlyErrorMessage = (stateName, baseChannel) => `syncState renderer write is disabled by main process. State "${stateName}" has channel prefix "${baseChannel}". Please set allowRendererSet: true in main process to enable renderer write.`;
|
|
47
|
+
const createInvalidValueErrorMessage = (stateName, baseChannel) => `syncState renderer write value validation failed. State "${stateName}" has channel prefix "${baseChannel}". Please check if the value conforms to resolveRendererValue validation rules.`;
|
|
48
|
+
const INVALID_INITIAL_VALUE_WARNING_MESSAGE = "syncState Warning: Initial value contains non-serializable content (such as functions, Symbols, BigInts, etc.), which will prevent the value from being transmitted to renderer process via IPC";
|
|
49
|
+
const checkType = (val) => {
|
|
50
|
+
if (val === null || val === void 0) return true;
|
|
51
|
+
if (typeof val === "string") return true;
|
|
52
|
+
if (typeof val === "number") return true;
|
|
53
|
+
if (typeof val === "boolean") return true;
|
|
54
|
+
if (typeof val === "function") return false;
|
|
55
|
+
if (typeof val === "symbol") return false;
|
|
56
|
+
if (typeof val === "bigint") return false;
|
|
57
|
+
if (Array.isArray(val)) return val.every((item) => checkType(item));
|
|
58
|
+
if (typeof val === "object") try {
|
|
59
|
+
JSON.stringify(val);
|
|
60
|
+
return Object.values(val).every((item) => checkType(item));
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
};
|
|
66
|
+
const validateSerializable = (value) => checkType(value);
|
|
67
|
+
const createSyncStateStore = (initialValue) => {
|
|
68
|
+
let state$1 = initialValue;
|
|
69
|
+
const getState = () => state$1;
|
|
70
|
+
const setState = (value) => {
|
|
71
|
+
state$1 = value;
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
getState,
|
|
75
|
+
setState
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
const createSubscriptions = (channels, getState) => {
|
|
79
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
80
|
+
const broadcast = (value) => {
|
|
81
|
+
for (const subscriber of subscribers) if (subscriber.isDestroyed()) subscribers.delete(subscriber);
|
|
82
|
+
else subscriber.send(channels.updateChannel, value);
|
|
83
|
+
};
|
|
84
|
+
const handleSubscribe = ({ sender }) => {
|
|
85
|
+
subscribers.add(sender);
|
|
86
|
+
sender.once("destroyed", () => {
|
|
87
|
+
subscribers.delete(sender);
|
|
88
|
+
});
|
|
89
|
+
sender.send(channels.updateChannel, getState());
|
|
90
|
+
};
|
|
91
|
+
const handleUnsubscribe = ({ sender }) => {
|
|
92
|
+
subscribers.delete(sender);
|
|
93
|
+
};
|
|
94
|
+
const clear = () => {
|
|
95
|
+
subscribers.clear();
|
|
96
|
+
};
|
|
97
|
+
return {
|
|
98
|
+
broadcast,
|
|
99
|
+
clear,
|
|
100
|
+
handleSubscribe,
|
|
101
|
+
handleUnsubscribe
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
const createSetStateAndBroadcast = (setState, broadcast) => (value) => {
|
|
105
|
+
setState(value);
|
|
106
|
+
broadcast(value);
|
|
107
|
+
};
|
|
108
|
+
const createInvokeHandlers = (getState, setStateAndBroadcast, options) => {
|
|
109
|
+
const handleGet = () => getState();
|
|
110
|
+
const ensureRendererWritable = () => {
|
|
111
|
+
if (options.allowRendererSet === false) throw new SyncStateError("RENDERER_READONLY", createReadonlyErrorMessage(options.name, options.baseChannel ?? "state"), {
|
|
112
|
+
baseChannel: options.baseChannel ?? "state",
|
|
113
|
+
stateName: options.name
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
const resolveRendererValue = (value) => {
|
|
117
|
+
if (!options.resolveRendererValue) return value;
|
|
118
|
+
try {
|
|
119
|
+
return options.resolveRendererValue(value);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
let message = "";
|
|
122
|
+
let cause = void 0;
|
|
123
|
+
if (error instanceof Error) {
|
|
124
|
+
const { message: errorMessage } = error;
|
|
125
|
+
message = errorMessage;
|
|
126
|
+
cause = error;
|
|
127
|
+
} else message = createInvalidValueErrorMessage(options.name, options.baseChannel ?? "state");
|
|
128
|
+
throw new SyncStateError("RENDERER_INVALID_VALUE", message, {
|
|
129
|
+
baseChannel: options.baseChannel ?? "state",
|
|
130
|
+
cause,
|
|
131
|
+
stateName: options.name
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
const handleSet = (_event, value) => {
|
|
136
|
+
ensureRendererWritable();
|
|
137
|
+
setStateAndBroadcast(resolveRendererValue(value));
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
handleGet,
|
|
141
|
+
handleSet
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
const registerIpcHandlers = (channels, invokeHandlers, subscriptions) => {
|
|
145
|
+
ipcMain.handle(channels.getChannel, invokeHandlers.handleGet);
|
|
146
|
+
ipcMain.handle(channels.setChannel, invokeHandlers.handleSet);
|
|
147
|
+
ipcMain.on(channels.subscribeChannel, subscriptions.handleSubscribe);
|
|
148
|
+
ipcMain.on(channels.unsubscribeChannel, subscriptions.handleUnsubscribe);
|
|
149
|
+
return () => {
|
|
150
|
+
ipcMain.removeHandler(channels.getChannel);
|
|
151
|
+
ipcMain.removeHandler(channels.setChannel);
|
|
152
|
+
ipcMain.removeListener(channels.subscribeChannel, subscriptions.handleSubscribe);
|
|
153
|
+
ipcMain.removeListener(channels.unsubscribeChannel, subscriptions.handleUnsubscribe);
|
|
154
|
+
subscriptions.clear();
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
const state = (options) => {
|
|
158
|
+
const mergedOptions = {
|
|
159
|
+
allowRendererSet: globalConfig.allowRendererSet,
|
|
160
|
+
baseChannel: globalConfig.baseChannel,
|
|
161
|
+
...options
|
|
162
|
+
};
|
|
163
|
+
if (!validateSerializable(mergedOptions.initialValue)) console.warn(`${INVALID_INITIAL_VALUE_WARNING_MESSAGE}\n State name: "${mergedOptions.name}"`);
|
|
164
|
+
const channels = createSyncStateChannels(mergedOptions);
|
|
165
|
+
const store = createSyncStateStore(mergedOptions.initialValue);
|
|
166
|
+
const subscriptions = createSubscriptions(channels, store.getState);
|
|
167
|
+
const setStateAndBroadcast = createSetStateAndBroadcast(store.setState, subscriptions.broadcast);
|
|
168
|
+
return {
|
|
169
|
+
dispose: registerIpcHandlers(channels, createInvokeHandlers(store.getState, setStateAndBroadcast, mergedOptions), subscriptions),
|
|
170
|
+
get: store.getState,
|
|
171
|
+
set: setStateAndBroadcast
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
//#endregion
|
|
176
|
+
export { initSyncStateMain, state };
|
|
177
|
+
//# sourceMappingURL=main.js.map
|
package/dist/main.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main.js","names":["state"],"sources":["../src/channels.ts","../src/types.ts","../src/main.ts"],"sourcesContent":["export interface SyncStateChannelOptions {\n name: string;\n baseChannel?: string;\n}\n\nexport interface SyncStateChannels {\n getChannel: string;\n setChannel: string;\n subscribeChannel: string;\n unsubscribeChannel: string;\n updateChannel: string;\n}\n\nexport const createSyncStateChannels = (options: SyncStateChannelOptions): SyncStateChannels => {\n const baseChannel = options.baseChannel ?? \"state\";\n const channelPrefix = `${baseChannel}:${options.name}`;\n\n return {\n getChannel: `${channelPrefix}:get`,\n setChannel: `${channelPrefix}:set`,\n subscribeChannel: `${channelPrefix}:subscribe`,\n unsubscribeChannel: `${channelPrefix}:unsubscribe`,\n updateChannel: `${channelPrefix}:update`,\n };\n};\n","import type { SyncStateChannelOptions } from \"./channels\";\n\nexport type SyncStateListener<StateValue> = (value: StateValue) => void;\n\nexport type SyncStateErrorCode = \"RENDERER_READONLY\" | \"RENDERER_INVALID_VALUE\";\n\nexport class SyncStateError extends Error {\n public readonly code: SyncStateErrorCode;\n public readonly stateName?: string;\n public readonly baseChannel?: string;\n public readonly cause?: Error;\n\n public constructor(\n code: SyncStateErrorCode,\n message: string,\n context?: { stateName?: string; baseChannel?: string; cause?: Error },\n ) {\n super(message);\n this.code = code;\n this.name = \"SyncStateError\";\n this.stateName = context?.stateName;\n this.baseChannel = context?.baseChannel;\n this.cause = context?.cause;\n\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, SyncStateError);\n }\n }\n\n public getFullMessage(): string {\n const parts = [this.message];\n if (this.stateName) {\n parts.push(`\\n State name: \"${this.stateName}\"`);\n }\n if (this.baseChannel) {\n parts.push(`\\n Channel prefix: \"${this.baseChannel}\"`);\n }\n if (this.cause) {\n parts.push(`\\n Original error: ${this.cause.message}`);\n }\n return parts.join(\"\");\n }\n}\n\nexport interface SyncStateBridge {\n get: <StateValue>(options: SyncStateChannelOptions) => Promise<StateValue>;\n set: <StateValue>(options: SyncStateChannelOptions, value: StateValue) => Promise<void>;\n subscribe: <StateValue>(\n options: SyncStateChannelOptions,\n listener: SyncStateListener<StateValue>,\n ) => () => void;\n}\n","import { ipcMain, type IpcMainEvent, type IpcMainInvokeEvent, type WebContents } from \"electron\";\n\nimport {\n createSyncStateChannels,\n type SyncStateChannelOptions,\n type SyncStateChannels,\n} from \"./channels\";\nimport { SyncStateError } from \"./types\";\n\nexport interface SyncStateMainGlobalConfig {\n baseChannel?: string;\n allowRendererSet?: boolean;\n}\n\nlet globalConfig: SyncStateMainGlobalConfig = {};\n\nexport const initSyncStateMain = (config: SyncStateMainGlobalConfig): void => {\n globalConfig = { ...config };\n};\n\nexport interface SyncStateMainOptions<StateValue> extends SyncStateChannelOptions {\n initialValue: StateValue;\n allowRendererSet?: boolean;\n resolveRendererValue?: (value: StateValue) => StateValue;\n}\n\nexport interface SyncStateMainHandle<StateValue> {\n get: () => StateValue;\n set: (value: StateValue) => void;\n dispose: () => void;\n}\n\ninterface SyncStateStore<StateValue> {\n getState: () => StateValue;\n setState: (value: StateValue) => void;\n}\n\ninterface SyncStateSubscriptions<StateValue> {\n broadcast: (value: StateValue) => void;\n clear: () => void;\n handleSubscribe: (event: IpcMainEvent) => void;\n handleUnsubscribe: (event: IpcMainEvent) => void;\n}\n\ninterface SyncStateInvokeHandlers<StateValue> {\n handleGet: () => StateValue;\n handleSet: (_event: IpcMainInvokeEvent, value: StateValue) => void;\n}\n\nconst createReadonlyErrorMessage = (stateName: string, baseChannel: string): string =>\n `syncState renderer write is disabled by main process. State \"${stateName}\" has channel prefix \"${baseChannel}\". Please set allowRendererSet: true in main process to enable renderer write.`;\n\nconst createInvalidValueErrorMessage = (stateName: string, baseChannel: string): string =>\n `syncState renderer write value validation failed. State \"${stateName}\" has channel prefix \"${baseChannel}\". Please check if the value conforms to resolveRendererValue validation rules.`;\n\nconst INVALID_INITIAL_VALUE_WARNING_MESSAGE =\n \"syncState Warning: Initial value contains non-serializable content (such as functions, Symbols, BigInts, etc.), which will prevent the value from being transmitted to renderer process via IPC\";\n\nconst checkType = (val: unknown): boolean => {\n if (val === null || val === undefined) {\n return true;\n }\n if (typeof val === \"string\") {\n return true;\n }\n if (typeof val === \"number\") {\n return true;\n }\n if (typeof val === \"boolean\") {\n return true;\n }\n if (typeof val === \"function\") {\n return false;\n }\n if (typeof val === \"symbol\") {\n return false;\n }\n if (typeof val === \"bigint\") {\n return false;\n }\n\n if (Array.isArray(val)) {\n return val.every((item) => checkType(item));\n }\n\n if (typeof val === \"object\") {\n try {\n JSON.stringify(val);\n return Object.values(val).every((item) => checkType(item));\n } catch {\n return false;\n }\n }\n\n return true;\n};\n\nconst validateSerializable = (value: unknown): boolean => checkType(value);\n\nconst createSyncStateStore = <StateValue>(initialValue: StateValue): SyncStateStore<StateValue> => {\n let state = initialValue;\n\n const getState = (): StateValue => state;\n\n const setState = (value: StateValue): void => {\n state = value;\n };\n\n return {\n getState,\n setState,\n };\n};\n\nconst createSubscriptions = <StateValue>(\n channels: SyncStateChannels,\n getState: () => StateValue,\n): SyncStateSubscriptions<StateValue> => {\n const subscribers = new Set<WebContents>();\n\n const broadcast = (value: StateValue): void => {\n for (const subscriber of subscribers) {\n if (subscriber.isDestroyed()) {\n subscribers.delete(subscriber);\n } else {\n subscriber.send(channels.updateChannel, value);\n }\n }\n };\n\n const handleSubscribe = ({ sender }: IpcMainEvent): void => {\n subscribers.add(sender);\n sender.once(\"destroyed\", () => {\n subscribers.delete(sender);\n });\n sender.send(channels.updateChannel, getState());\n };\n\n const handleUnsubscribe = ({ sender }: IpcMainEvent): void => {\n subscribers.delete(sender);\n };\n\n const clear = (): void => {\n subscribers.clear();\n };\n\n return {\n broadcast,\n clear,\n handleSubscribe,\n handleUnsubscribe,\n };\n};\n\nconst createSetStateAndBroadcast =\n <StateValue>(\n setState: (value: StateValue) => void,\n broadcast: (value: StateValue) => void,\n ): ((value: StateValue) => void) =>\n (value: StateValue): void => {\n setState(value);\n broadcast(value);\n };\n\nconst createInvokeHandlers = <StateValue>(\n getState: () => StateValue,\n setStateAndBroadcast: (value: StateValue) => void,\n options: SyncStateMainOptions<StateValue>,\n): SyncStateInvokeHandlers<StateValue> => {\n const handleGet = (): StateValue => getState();\n\n const ensureRendererWritable = (): void => {\n if (options.allowRendererSet === false) {\n throw new SyncStateError(\n \"RENDERER_READONLY\",\n createReadonlyErrorMessage(options.name, options.baseChannel ?? \"state\"),\n {\n baseChannel: options.baseChannel ?? \"state\",\n stateName: options.name,\n },\n );\n }\n };\n\n const resolveRendererValue = (value: StateValue): StateValue => {\n if (!options.resolveRendererValue) {\n return value;\n }\n\n try {\n return options.resolveRendererValue(value);\n } catch (error) {\n let message = \"\";\n let cause: Error | undefined = undefined;\n if (error instanceof Error) {\n const { message: errorMessage } = error;\n message = errorMessage;\n cause = error;\n } else {\n message = createInvalidValueErrorMessage(options.name, options.baseChannel ?? \"state\");\n }\n\n throw new SyncStateError(\"RENDERER_INVALID_VALUE\", message, {\n baseChannel: options.baseChannel ?? \"state\",\n cause,\n stateName: options.name,\n });\n }\n };\n\n const handleSet = (_event: IpcMainInvokeEvent, value: StateValue): void => {\n ensureRendererWritable();\n const resolvedValue = resolveRendererValue(value);\n setStateAndBroadcast(resolvedValue);\n };\n\n return {\n handleGet,\n handleSet,\n };\n};\n\nconst registerIpcHandlers = <StateValue>(\n channels: SyncStateChannels,\n invokeHandlers: SyncStateInvokeHandlers<StateValue>,\n subscriptions: SyncStateSubscriptions<StateValue>,\n): (() => void) => {\n ipcMain.handle(channels.getChannel, invokeHandlers.handleGet);\n ipcMain.handle(channels.setChannel, invokeHandlers.handleSet);\n ipcMain.on(channels.subscribeChannel, subscriptions.handleSubscribe);\n ipcMain.on(channels.unsubscribeChannel, subscriptions.handleUnsubscribe);\n\n return (): void => {\n ipcMain.removeHandler(channels.getChannel);\n ipcMain.removeHandler(channels.setChannel);\n ipcMain.removeListener(channels.subscribeChannel, subscriptions.handleSubscribe);\n ipcMain.removeListener(channels.unsubscribeChannel, subscriptions.handleUnsubscribe);\n subscriptions.clear();\n };\n};\n\nexport const state = <StateValue>(\n options: SyncStateMainOptions<StateValue>,\n): SyncStateMainHandle<StateValue> => {\n const mergedOptions: SyncStateMainOptions<StateValue> = {\n allowRendererSet: globalConfig.allowRendererSet,\n baseChannel: globalConfig.baseChannel,\n ...options,\n };\n\n if (!validateSerializable(mergedOptions.initialValue)) {\n console.warn(`${INVALID_INITIAL_VALUE_WARNING_MESSAGE}\\n State name: \"${mergedOptions.name}\"`);\n }\n\n const channels = createSyncStateChannels(mergedOptions);\n const store = createSyncStateStore(mergedOptions.initialValue);\n const subscriptions = createSubscriptions(channels, store.getState);\n const setStateAndBroadcast = createSetStateAndBroadcast(store.setState, subscriptions.broadcast);\n const invokeHandlers = createInvokeHandlers(store.getState, setStateAndBroadcast, mergedOptions);\n\n const dispose = registerIpcHandlers(channels, invokeHandlers, subscriptions);\n\n return {\n dispose,\n get: store.getState,\n set: setStateAndBroadcast,\n };\n};\n"],"mappings":"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"}
|
package/dist/preact.cjs
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const require_renderer = require('./renderer-D3YziJ_U.cjs');
|
|
2
|
+
let preact_hooks = require("preact/hooks");
|
|
3
|
+
|
|
4
|
+
//#region src/renderer/preact.ts
|
|
5
|
+
const createChannelOptions = (options) => {
|
|
6
|
+
const globalConfig = require_renderer.getGlobalConfig();
|
|
7
|
+
return {
|
|
8
|
+
baseChannel: options.baseChannel ?? globalConfig.baseChannel,
|
|
9
|
+
name: options.name
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
const useSyncState = (initialValue, options) => {
|
|
13
|
+
const [stateValue, setStateValue] = (0, preact_hooks.useState)(initialValue);
|
|
14
|
+
const [isSynced, setIsSynced] = (0, preact_hooks.useState)(false);
|
|
15
|
+
const globalConfig = require_renderer.getGlobalConfig();
|
|
16
|
+
const bridge = require_renderer.resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);
|
|
17
|
+
const channelOptions = (0, preact_hooks.useMemo)(() => createChannelOptions(options), [options.baseChannel, options.name]);
|
|
18
|
+
const applyRemoteValue = (0, preact_hooks.useCallback)((value) => {
|
|
19
|
+
setStateValue(value);
|
|
20
|
+
setIsSynced(true);
|
|
21
|
+
}, []);
|
|
22
|
+
const setAndSync = (0, preact_hooks.useCallback)((value) => {
|
|
23
|
+
setStateValue(value);
|
|
24
|
+
bridge.set(channelOptions, value);
|
|
25
|
+
}, [bridge, channelOptions]);
|
|
26
|
+
(0, preact_hooks.useEffect)(() => {
|
|
27
|
+
const unsubscribe = bridge.subscribe(channelOptions, applyRemoteValue);
|
|
28
|
+
bridge.get(channelOptions).then(applyRemoteValue);
|
|
29
|
+
return () => {
|
|
30
|
+
unsubscribe();
|
|
31
|
+
};
|
|
32
|
+
}, [
|
|
33
|
+
applyRemoteValue,
|
|
34
|
+
bridge,
|
|
35
|
+
channelOptions
|
|
36
|
+
]);
|
|
37
|
+
return [
|
|
38
|
+
stateValue,
|
|
39
|
+
setAndSync,
|
|
40
|
+
isSynced
|
|
41
|
+
];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
exports.useSyncState = useSyncState;
|
|
46
|
+
exports.useSyncStatePreact = useSyncState;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { a as SyncStateChannelOptions, t as SyncStateBridge } from "./types-C18dHgLI.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/renderer/preact.d.ts
|
|
4
|
+
interface UseSyncStatePreactOptions extends SyncStateChannelOptions {
|
|
5
|
+
bridge?: SyncStateBridge;
|
|
6
|
+
}
|
|
7
|
+
type UseSyncStatePreactResult<StateValue> = readonly [StateValue, (value: StateValue) => void, boolean];
|
|
8
|
+
declare const useSyncState: <StateValue>(initialValue: StateValue, options: UseSyncStatePreactOptions) => UseSyncStatePreactResult<StateValue>;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { UseSyncStatePreactOptions, UseSyncStatePreactResult, useSyncState, useSyncState as useSyncStatePreact };
|
|
11
|
+
//# sourceMappingURL=preact.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preact.d.cts","names":[],"sources":["../src/renderer/preact.ts"],"sourcesContent":[],"mappings":";;;UAOiB,yBAAA,SAAkC;EAAlC,MAAA,CAAA,EACN,eADM;AAIjB;AAca,KAdD,wBAkDX,CAAA,UAAA,CAAA,GAAA,SAAA,CAjDC,UAcc,EACL,CAAA,KAAA,EAdD,UAcC,EAAA,GAAA,IAAA,EACiB,OAAA,CAAzB;AAAwB,cAHd,YAGc,EAAA,CAAA,UAAA,CAAA,CAAA,YAAA,EAFX,UAEW,EAAA,OAAA,EADhB,yBACgB,EAAA,GAAxB,wBAAwB,CAAC,UAAD,CAAA"}
|
package/dist/preact.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { a as SyncStateChannelOptions, t as SyncStateBridge } from "./types-7wPPX0ty.js";
|
|
2
|
+
|
|
3
|
+
//#region src/renderer/preact.d.ts
|
|
4
|
+
interface UseSyncStatePreactOptions extends SyncStateChannelOptions {
|
|
5
|
+
bridge?: SyncStateBridge;
|
|
6
|
+
}
|
|
7
|
+
type UseSyncStatePreactResult<StateValue> = readonly [StateValue, (value: StateValue) => void, boolean];
|
|
8
|
+
declare const useSyncState: <StateValue>(initialValue: StateValue, options: UseSyncStatePreactOptions) => UseSyncStatePreactResult<StateValue>;
|
|
9
|
+
//#endregion
|
|
10
|
+
export { UseSyncStatePreactOptions, UseSyncStatePreactResult, useSyncState, useSyncState as useSyncStatePreact };
|
|
11
|
+
//# sourceMappingURL=preact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preact.d.ts","names":[],"sources":["../src/renderer/preact.ts"],"sourcesContent":[],"mappings":";;;UAOiB,yBAAA,SAAkC;EAAlC,MAAA,CAAA,EACN,eADM;AAIjB;AAca,KAdD,wBAkDX,CAAA,UAAA,CAAA,GAAA,SAAA,CAjDC,UAcc,EACL,CAAA,KAAA,EAdD,UAcC,EAAA,GAAA,IAAA,EACiB,OAAA,CAAzB;AAAwB,cAHd,YAGc,EAAA,CAAA,UAAA,CAAA,CAAA,YAAA,EAFX,UAEW,EAAA,OAAA,EADhB,yBACgB,EAAA,GAAxB,wBAAwB,CAAC,UAAD,CAAA"}
|
package/dist/preact.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { a as resolveSyncStateBridge, t as getGlobalConfig } from "./renderer-C7zF3UQm.js";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from "preact/hooks";
|
|
3
|
+
|
|
4
|
+
//#region src/renderer/preact.ts
|
|
5
|
+
const createChannelOptions = (options) => {
|
|
6
|
+
const globalConfig = getGlobalConfig();
|
|
7
|
+
return {
|
|
8
|
+
baseChannel: options.baseChannel ?? globalConfig.baseChannel,
|
|
9
|
+
name: options.name
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
const useSyncState = (initialValue, options) => {
|
|
13
|
+
const [stateValue, setStateValue] = useState(initialValue);
|
|
14
|
+
const [isSynced, setIsSynced] = useState(false);
|
|
15
|
+
const globalConfig = getGlobalConfig();
|
|
16
|
+
const bridge = resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);
|
|
17
|
+
const channelOptions = useMemo(() => createChannelOptions(options), [options.baseChannel, options.name]);
|
|
18
|
+
const applyRemoteValue = useCallback((value) => {
|
|
19
|
+
setStateValue(value);
|
|
20
|
+
setIsSynced(true);
|
|
21
|
+
}, []);
|
|
22
|
+
const setAndSync = useCallback((value) => {
|
|
23
|
+
setStateValue(value);
|
|
24
|
+
bridge.set(channelOptions, value);
|
|
25
|
+
}, [bridge, channelOptions]);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const unsubscribe = bridge.subscribe(channelOptions, applyRemoteValue);
|
|
28
|
+
bridge.get(channelOptions).then(applyRemoteValue);
|
|
29
|
+
return () => {
|
|
30
|
+
unsubscribe();
|
|
31
|
+
};
|
|
32
|
+
}, [
|
|
33
|
+
applyRemoteValue,
|
|
34
|
+
bridge,
|
|
35
|
+
channelOptions
|
|
36
|
+
]);
|
|
37
|
+
return [
|
|
38
|
+
stateValue,
|
|
39
|
+
setAndSync,
|
|
40
|
+
isSynced
|
|
41
|
+
];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
export { useSyncState, useSyncState as useSyncStatePreact };
|
|
46
|
+
//# sourceMappingURL=preact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preact.js","names":[],"sources":["../src/renderer/preact.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useState } from \"preact/hooks\";\n\nimport type { SyncStateChannelOptions } from \"../channels\";\nimport type { SyncStateBridge } from \"../types\";\nimport { resolveSyncStateBridge } from \"./bridge\";\nimport { getGlobalConfig } from \"./index\";\n\nexport interface UseSyncStatePreactOptions extends SyncStateChannelOptions {\n bridge?: SyncStateBridge;\n}\n\nexport type UseSyncStatePreactResult<StateValue> = readonly [\n StateValue,\n (value: StateValue) => void,\n boolean,\n];\n\nconst createChannelOptions = (options: UseSyncStatePreactOptions): SyncStateChannelOptions => {\n const globalConfig = getGlobalConfig();\n return {\n baseChannel: options.baseChannel ?? globalConfig.baseChannel,\n name: options.name,\n };\n};\n\nexport const useSyncState = <StateValue>(\n initialValue: StateValue,\n options: UseSyncStatePreactOptions,\n): UseSyncStatePreactResult<StateValue> => {\n const [stateValue, setStateValue] = useState(initialValue);\n const [isSynced, setIsSynced] = useState(false);\n const globalConfig = getGlobalConfig();\n const bridge = resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);\n const channelOptions = useMemo(\n () => createChannelOptions(options),\n [options.baseChannel, options.name],\n );\n\n const applyRemoteValue = useCallback((value: StateValue): void => {\n setStateValue(value);\n setIsSynced(true);\n }, []);\n\n const setAndSync = useCallback(\n (value: StateValue): void => {\n setStateValue(value);\n void bridge.set(channelOptions, value);\n },\n [bridge, channelOptions],\n );\n\n useEffect(() => {\n const unsubscribe = bridge.subscribe(channelOptions, applyRemoteValue);\n void bridge.get<StateValue>(channelOptions).then(applyRemoteValue);\n\n return () => {\n unsubscribe();\n };\n }, [applyRemoteValue, bridge, channelOptions]);\n\n return [stateValue, setAndSync, isSynced] as const;\n};\n\n// Backward compatibility alias\nexport { useSyncState as useSyncStatePreact };\n"],"mappings":";;;;AAiBA,MAAM,wBAAwB,YAAgE;CAC5F,MAAM,eAAe,iBAAiB;AACtC,QAAO;EACL,aAAa,QAAQ,eAAe,aAAa;EACjD,MAAM,QAAQ;EACf;;AAGH,MAAa,gBACX,cACA,YACyC;CACzC,MAAM,CAAC,YAAY,iBAAiB,SAAS,aAAa;CAC1D,MAAM,CAAC,UAAU,eAAe,SAAS,MAAM;CAC/C,MAAM,eAAe,iBAAiB;CACtC,MAAM,SAAS,uBAAuB,QAAQ,UAAU,aAAa,OAAO;CAC5E,MAAM,iBAAiB,cACf,qBAAqB,QAAQ,EACnC,CAAC,QAAQ,aAAa,QAAQ,KAAK,CACpC;CAED,MAAM,mBAAmB,aAAa,UAA4B;AAChE,gBAAc,MAAM;AACpB,cAAY,KAAK;IAChB,EAAE,CAAC;CAEN,MAAM,aAAa,aAChB,UAA4B;AAC3B,gBAAc,MAAM;AACpB,EAAK,OAAO,IAAI,gBAAgB,MAAM;IAExC,CAAC,QAAQ,eAAe,CACzB;AAED,iBAAgB;EACd,MAAM,cAAc,OAAO,UAAU,gBAAgB,iBAAiB;AACtE,EAAK,OAAO,IAAgB,eAAe,CAAC,KAAK,iBAAiB;AAElE,eAAa;AACX,gBAAa;;IAEd;EAAC;EAAkB;EAAQ;EAAe,CAAC;AAE9C,QAAO;EAAC;EAAY;EAAY;EAAS"}
|
package/dist/preload.cjs
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
let electron = require("electron");
|
|
2
|
+
|
|
3
|
+
//#region src/channels.ts
|
|
4
|
+
const createSyncStateChannels = (options) => {
|
|
5
|
+
const channelPrefix = `${options.baseChannel ?? "state"}:${options.name}`;
|
|
6
|
+
return {
|
|
7
|
+
getChannel: `${channelPrefix}:get`,
|
|
8
|
+
setChannel: `${channelPrefix}:set`,
|
|
9
|
+
subscribeChannel: `${channelPrefix}:subscribe`,
|
|
10
|
+
unsubscribeChannel: `${channelPrefix}:unsubscribe`,
|
|
11
|
+
updateChannel: `${channelPrefix}:update`
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/preload.ts
|
|
17
|
+
const createSyncStateBridge = () => {
|
|
18
|
+
const get = async (options) => {
|
|
19
|
+
const channels = createSyncStateChannels(options);
|
|
20
|
+
return await electron.ipcRenderer.invoke(channels.getChannel);
|
|
21
|
+
};
|
|
22
|
+
const set = async (options, value) => {
|
|
23
|
+
const channels = createSyncStateChannels(options);
|
|
24
|
+
await electron.ipcRenderer.invoke(channels.setChannel, value);
|
|
25
|
+
};
|
|
26
|
+
const subscribe = (options, listener) => {
|
|
27
|
+
const channels = createSyncStateChannels(options);
|
|
28
|
+
const handler = (_event, value) => {
|
|
29
|
+
listener(value);
|
|
30
|
+
};
|
|
31
|
+
electron.ipcRenderer.on(channels.updateChannel, handler);
|
|
32
|
+
electron.ipcRenderer.send(channels.subscribeChannel);
|
|
33
|
+
return () => {
|
|
34
|
+
electron.ipcRenderer.off(channels.updateChannel, handler);
|
|
35
|
+
electron.ipcRenderer.send(channels.unsubscribeChannel);
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
get,
|
|
40
|
+
set,
|
|
41
|
+
subscribe
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
const exposeSyncState = () => {
|
|
45
|
+
const bridge = createSyncStateBridge();
|
|
46
|
+
electron.contextBridge.exposeInMainWorld("syncState", bridge);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
exports.createSyncStateBridge = createSyncStateBridge;
|
|
51
|
+
exports.exposeSyncState = exposeSyncState;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/channels.d.ts
|
|
2
|
+
interface SyncStateChannelOptions {
|
|
3
|
+
name: string;
|
|
4
|
+
baseChannel?: string;
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/types.d.ts
|
|
8
|
+
type SyncStateListener<StateValue> = (value: StateValue) => void;
|
|
9
|
+
interface SyncStateBridge {
|
|
10
|
+
get: <StateValue>(options: SyncStateChannelOptions) => Promise<StateValue>;
|
|
11
|
+
set: <StateValue>(options: SyncStateChannelOptions, value: StateValue) => Promise<void>;
|
|
12
|
+
subscribe: <StateValue>(options: SyncStateChannelOptions, listener: SyncStateListener<StateValue>) => () => void;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/preload.d.ts
|
|
16
|
+
declare const createSyncStateBridge: () => SyncStateBridge;
|
|
17
|
+
declare const exposeSyncState: () => void;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { type SyncStateBridge, type SyncStateChannelOptions, type SyncStateListener, createSyncStateBridge, exposeSyncState };
|
|
20
|
+
//# sourceMappingURL=preload.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preload.d.cts","names":[],"sources":["../src/channels.ts","../src/types.ts","../src/preload.ts"],"sourcesContent":[],"mappings":";UAAiB,uBAAA;EAAA,IAAA,EAAA,MAAA;;;;;AAAA,KCEL,iBDFK,CAAuB,UAAA,CAAA,GAAA,CAAA,KAAA,ECEY,UDFZ,EAAA,GAAA,IAAA;ACiD1B,UALG,eAAA,CAKH;EAAiB,GAAA,EAAA,CAAA,UAAA,CAAA,CAAA,OAAA,EAJF,uBAIE,EAAA,GAJ0B,OAI1B,CAJkC,UAIlC,CAAA;6BAHF,gCAAgC,eAAe;mCAE/D,mCACC,kBAAkB;;;;ADjDf,cEKJ,qBFL2B,EAAA,GAAA,GEKC,eFLD;cE4C3B"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/channels.d.ts
|
|
2
|
+
interface SyncStateChannelOptions {
|
|
3
|
+
name: string;
|
|
4
|
+
baseChannel?: string;
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
//#region src/types.d.ts
|
|
8
|
+
type SyncStateListener<StateValue> = (value: StateValue) => void;
|
|
9
|
+
interface SyncStateBridge {
|
|
10
|
+
get: <StateValue>(options: SyncStateChannelOptions) => Promise<StateValue>;
|
|
11
|
+
set: <StateValue>(options: SyncStateChannelOptions, value: StateValue) => Promise<void>;
|
|
12
|
+
subscribe: <StateValue>(options: SyncStateChannelOptions, listener: SyncStateListener<StateValue>) => () => void;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/preload.d.ts
|
|
16
|
+
declare const createSyncStateBridge: () => SyncStateBridge;
|
|
17
|
+
declare const exposeSyncState: () => void;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { type SyncStateBridge, type SyncStateChannelOptions, type SyncStateListener, createSyncStateBridge, exposeSyncState };
|
|
20
|
+
//# sourceMappingURL=preload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preload.d.ts","names":[],"sources":["../src/channels.ts","../src/types.ts","../src/preload.ts"],"sourcesContent":[],"mappings":";UAAiB,uBAAA;EAAA,IAAA,EAAA,MAAA;;;;;AAAA,KCEL,iBDFK,CAAuB,UAAA,CAAA,GAAA,CAAA,KAAA,ECEY,UDFZ,EAAA,GAAA,IAAA;ACiD1B,UALG,eAAA,CAKH;EAAiB,GAAA,EAAA,CAAA,UAAA,CAAA,CAAA,OAAA,EAJF,uBAIE,EAAA,GAJ0B,OAI1B,CAJkC,UAIlC,CAAA;6BAHF,gCAAgC,eAAe;mCAE/D,mCACC,kBAAkB;;;;ADjDf,cEKJ,qBFL2B,EAAA,GAAA,GEKC,eFLD;cE4C3B"}
|
package/dist/preload.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { contextBridge, ipcRenderer } from "electron";
|
|
2
|
+
|
|
3
|
+
//#region src/channels.ts
|
|
4
|
+
const createSyncStateChannels = (options) => {
|
|
5
|
+
const channelPrefix = `${options.baseChannel ?? "state"}:${options.name}`;
|
|
6
|
+
return {
|
|
7
|
+
getChannel: `${channelPrefix}:get`,
|
|
8
|
+
setChannel: `${channelPrefix}:set`,
|
|
9
|
+
subscribeChannel: `${channelPrefix}:subscribe`,
|
|
10
|
+
unsubscribeChannel: `${channelPrefix}:unsubscribe`,
|
|
11
|
+
updateChannel: `${channelPrefix}:update`
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region src/preload.ts
|
|
17
|
+
const createSyncStateBridge = () => {
|
|
18
|
+
const get = async (options) => {
|
|
19
|
+
const channels = createSyncStateChannels(options);
|
|
20
|
+
return await ipcRenderer.invoke(channels.getChannel);
|
|
21
|
+
};
|
|
22
|
+
const set = async (options, value) => {
|
|
23
|
+
const channels = createSyncStateChannels(options);
|
|
24
|
+
await ipcRenderer.invoke(channels.setChannel, value);
|
|
25
|
+
};
|
|
26
|
+
const subscribe = (options, listener) => {
|
|
27
|
+
const channels = createSyncStateChannels(options);
|
|
28
|
+
const handler = (_event, value) => {
|
|
29
|
+
listener(value);
|
|
30
|
+
};
|
|
31
|
+
ipcRenderer.on(channels.updateChannel, handler);
|
|
32
|
+
ipcRenderer.send(channels.subscribeChannel);
|
|
33
|
+
return () => {
|
|
34
|
+
ipcRenderer.off(channels.updateChannel, handler);
|
|
35
|
+
ipcRenderer.send(channels.unsubscribeChannel);
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
get,
|
|
40
|
+
set,
|
|
41
|
+
subscribe
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
const exposeSyncState = () => {
|
|
45
|
+
const bridge = createSyncStateBridge();
|
|
46
|
+
contextBridge.exposeInMainWorld("syncState", bridge);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
export { createSyncStateBridge, exposeSyncState };
|
|
51
|
+
//# sourceMappingURL=preload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preload.js","names":[],"sources":["../src/channels.ts","../src/preload.ts"],"sourcesContent":["export interface SyncStateChannelOptions {\n name: string;\n baseChannel?: string;\n}\n\nexport interface SyncStateChannels {\n getChannel: string;\n setChannel: string;\n subscribeChannel: string;\n unsubscribeChannel: string;\n updateChannel: string;\n}\n\nexport const createSyncStateChannels = (options: SyncStateChannelOptions): SyncStateChannels => {\n const baseChannel = options.baseChannel ?? \"state\";\n const channelPrefix = `${baseChannel}:${options.name}`;\n\n return {\n getChannel: `${channelPrefix}:get`,\n setChannel: `${channelPrefix}:set`,\n subscribeChannel: `${channelPrefix}:subscribe`,\n unsubscribeChannel: `${channelPrefix}:unsubscribe`,\n updateChannel: `${channelPrefix}:update`,\n };\n};\n","import { contextBridge, ipcRenderer, type IpcRendererEvent } from \"electron\";\n\nimport { createSyncStateChannels, type SyncStateChannelOptions } from \"./channels\";\nimport type { SyncStateBridge, SyncStateListener } from \"./types\";\n\nexport const createSyncStateBridge = (): SyncStateBridge => {\n const get = async <StateValue>(options: SyncStateChannelOptions): Promise<StateValue> => {\n const channels = createSyncStateChannels(options);\n return (await ipcRenderer.invoke(channels.getChannel)) as StateValue;\n };\n\n const set = async <StateValue>(\n options: SyncStateChannelOptions,\n value: StateValue,\n ): Promise<void> => {\n const channels = createSyncStateChannels(options);\n await ipcRenderer.invoke(channels.setChannel, value);\n };\n\n const subscribe = <StateValue>(\n options: SyncStateChannelOptions,\n listener: SyncStateListener<StateValue>,\n ): (() => void) => {\n const channels = createSyncStateChannels(options);\n const handler = (_event: IpcRendererEvent, value: StateValue): void => {\n listener(value);\n };\n\n ipcRenderer.on(channels.updateChannel, handler);\n ipcRenderer.send(channels.subscribeChannel);\n\n return () => {\n ipcRenderer.off(channels.updateChannel, handler);\n ipcRenderer.send(channels.unsubscribeChannel);\n };\n };\n\n return {\n get,\n set,\n subscribe,\n };\n};\n\nexport const exposeSyncState = (): void => {\n const bridge = createSyncStateBridge();\n contextBridge.exposeInMainWorld(\"syncState\", bridge);\n};\n\nexport type { SyncStateBridge, SyncStateListener } from \"./types\";\nexport type { SyncStateChannelOptions } from \"./channels\";\n"],"mappings":";;;AAaA,MAAa,2BAA2B,YAAwD;CAE9F,MAAM,gBAAgB,GADF,QAAQ,eAAe,QACN,GAAG,QAAQ;AAEhD,QAAO;EACL,YAAY,GAAG,cAAc;EAC7B,YAAY,GAAG,cAAc;EAC7B,kBAAkB,GAAG,cAAc;EACnC,oBAAoB,GAAG,cAAc;EACrC,eAAe,GAAG,cAAc;EACjC;;;;;AClBH,MAAa,8BAA+C;CAC1D,MAAM,MAAM,OAAmB,YAA0D;EACvF,MAAM,WAAW,wBAAwB,QAAQ;AACjD,SAAQ,MAAM,YAAY,OAAO,SAAS,WAAW;;CAGvD,MAAM,MAAM,OACV,SACA,UACkB;EAClB,MAAM,WAAW,wBAAwB,QAAQ;AACjD,QAAM,YAAY,OAAO,SAAS,YAAY,MAAM;;CAGtD,MAAM,aACJ,SACA,aACiB;EACjB,MAAM,WAAW,wBAAwB,QAAQ;EACjD,MAAM,WAAW,QAA0B,UAA4B;AACrE,YAAS,MAAM;;AAGjB,cAAY,GAAG,SAAS,eAAe,QAAQ;AAC/C,cAAY,KAAK,SAAS,iBAAiB;AAE3C,eAAa;AACX,eAAY,IAAI,SAAS,eAAe,QAAQ;AAChD,eAAY,KAAK,SAAS,mBAAmB;;;AAIjD,QAAO;EACL;EACA;EACA;EACD;;AAGH,MAAa,wBAA8B;CACzC,MAAM,SAAS,uBAAuB;AACtC,eAAc,kBAAkB,aAAa,OAAO"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const require_renderer = require('./renderer-D3YziJ_U.cjs');
|
|
2
|
+
let react = require("react");
|
|
3
|
+
let _tanstack_react_query = require("@tanstack/react-query");
|
|
4
|
+
|
|
5
|
+
//#region src/renderer/react-query.ts
|
|
6
|
+
const DEFAULT_STALE_TIME = 1e3;
|
|
7
|
+
const DEFAULT_GC_TIME = 3e5;
|
|
8
|
+
const createChannelOptions = (options) => {
|
|
9
|
+
const globalConfig = require_renderer.getGlobalConfig();
|
|
10
|
+
return {
|
|
11
|
+
baseChannel: options.baseChannel ?? globalConfig.baseChannel,
|
|
12
|
+
name: options.name
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Hook for syncing state with TanStack Query
|
|
17
|
+
*
|
|
18
|
+
* Provides a query-based interface to sync state between renderer and main process.
|
|
19
|
+
* Automatically refetches when remote changes occur.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* import { useSyncState } from 'electron-state-sync/react-query';
|
|
24
|
+
*
|
|
25
|
+
* function App() {
|
|
26
|
+
* const { data: count, isSynced, update } = useSyncState(0, {
|
|
27
|
+
* name: 'counter'
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* return <div onClick={() => update(5)}>{count}</div>;
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
const useSyncState = (initialValue, options) => {
|
|
35
|
+
const [isSynced, setIsSynced] = (0, react.useState)(false);
|
|
36
|
+
const globalConfig = require_renderer.getGlobalConfig();
|
|
37
|
+
const bridge = require_renderer.resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);
|
|
38
|
+
const channelOptions = (0, react.useMemo)(() => createChannelOptions(options), [options.baseChannel, options.name]);
|
|
39
|
+
const query = (0, _tanstack_react_query.useQuery)({
|
|
40
|
+
gcTime: options.gcTime ?? DEFAULT_GC_TIME,
|
|
41
|
+
queryFn: () => bridge.get(channelOptions),
|
|
42
|
+
queryKey: options.queryKey ?? [
|
|
43
|
+
"sync-state",
|
|
44
|
+
channelOptions.baseChannel,
|
|
45
|
+
channelOptions.name
|
|
46
|
+
],
|
|
47
|
+
refetchOnWindowFocus: false,
|
|
48
|
+
staleTime: options.staleTime ?? DEFAULT_STALE_TIME
|
|
49
|
+
});
|
|
50
|
+
const mutation = (0, _tanstack_react_query.useMutation)({ mutationFn: (value) => bridge.set(channelOptions, value) });
|
|
51
|
+
(0, react.useEffect)(() => {
|
|
52
|
+
const unsubscribe = bridge.subscribe(channelOptions, () => {
|
|
53
|
+
query.refetch();
|
|
54
|
+
});
|
|
55
|
+
return () => {
|
|
56
|
+
unsubscribe();
|
|
57
|
+
};
|
|
58
|
+
}, [
|
|
59
|
+
bridge,
|
|
60
|
+
channelOptions,
|
|
61
|
+
query
|
|
62
|
+
]);
|
|
63
|
+
(0, react.useEffect)(() => {
|
|
64
|
+
if (!query.isLoading && !query.isFetching) setIsSynced(true);
|
|
65
|
+
}, [query.isLoading, query.isFetching]);
|
|
66
|
+
const update = (0, react.useCallback)((value) => {
|
|
67
|
+
mutation.mutate(value);
|
|
68
|
+
}, [mutation]);
|
|
69
|
+
return {
|
|
70
|
+
data: query.data ?? initialValue,
|
|
71
|
+
isSynced,
|
|
72
|
+
update
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Hook for creating a mutation to update synced state
|
|
77
|
+
*
|
|
78
|
+
* Provides a simpler mutation-only interface for updating state.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* import { useSyncMutation } from 'electron-state-sync/react-query';
|
|
83
|
+
*
|
|
84
|
+
* function App() {
|
|
85
|
+
* const { mutate, isSynced } = useSyncMutation<number>({
|
|
86
|
+
* name: 'counter'
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* return <button onClick={() => mutate(10)}>Set to 10</button>;
|
|
90
|
+
* }
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
const useSyncMutation = (options) => {
|
|
94
|
+
const [isSynced, setIsSynced] = (0, react.useState)(false);
|
|
95
|
+
const globalConfig = require_renderer.getGlobalConfig();
|
|
96
|
+
const bridge = require_renderer.resolveSyncStateBridge(options.bridge ?? globalConfig.bridge);
|
|
97
|
+
const channelOptions = (0, react.useMemo)(() => createChannelOptions(options), [options.baseChannel, options.name]);
|
|
98
|
+
(0, react.useEffect)(() => {
|
|
99
|
+
bridge.get(channelOptions).then(() => {
|
|
100
|
+
setIsSynced(true);
|
|
101
|
+
});
|
|
102
|
+
}, [bridge, channelOptions]);
|
|
103
|
+
const mutation = (0, _tanstack_react_query.useMutation)({ mutationFn: (value) => bridge.set(channelOptions, value) });
|
|
104
|
+
return {
|
|
105
|
+
isSynced,
|
|
106
|
+
mutate: (value) => mutation.mutate(value)
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
//#endregion
|
|
111
|
+
exports.useQuerySyncState = useSyncState;
|
|
112
|
+
exports.useSyncState = useSyncState;
|
|
113
|
+
exports.useSyncMutation = useSyncMutation;
|