@zubridge/electron
Version:
A streamlined state management library for Electron applications using Zustand.
122 lines (117 loc) • 4.76 kB
JavaScript
;
var zustand = require('zustand');
var vanilla = require('zustand/vanilla');
/**
* Determines if the application is running in development mode
*
* Uses a combination of checks to ensure consistent behavior:
* 1. Checks if app is packaged (production builds are packaged)
* 2. Checks NODE_ENV environment variable
* 3. Checks ELECTRON_IS_DEV environment variable (set by electron-is-dev or similar utilities)
*
* @returns {boolean} True if running in development mode, false otherwise
*/
const isDev = async () => {
// Ensure we have access to the app object (should be in the main process)
const { app } = await import('electron');
if (typeof app !== 'undefined') {
return !app.isPackaged || process.env.NODE_ENV === 'development' || process.env.ELECTRON_IS_DEV === '1';
}
// Fallback for renderer process or when app isn't available
return (process.env.NODE_ENV === 'development' || process.env.ELECTRON_IS_DEV === '1' || !process.env.VITE_DEV_SERVER_URL); // Vite-specific check
};
// Store registry to implement singleton pattern
// Maps handler objects to their corresponding stores
const storeRegistry = new WeakMap();
// Internal implementation of store creation
const createStore = (bridge) => {
// Check if a store already exists for these handlers
if (storeRegistry.has(bridge)) {
return storeRegistry.get(bridge);
}
// Create a new store if one doesn't exist
const newStore = vanilla.createStore((setState) => {
// subscribe to changes
bridge.subscribe((state) => setState(state));
// get initial state
bridge.getState().then((state) => setState(state));
// no state keys - they will all come from main
return {};
});
// Register the store
storeRegistry.set(bridge, newStore);
return newStore;
};
// Create Electron-specific handlers
const createHandlers = () => {
if (typeof window === 'undefined' || !window.zubridge) {
throw new Error('Zubridge handlers not found in window. Make sure the preload script is properly set up.');
}
return window.zubridge;
};
/**
* Creates a hook for accessing the store state in React components
*/
const createUseStore = (customHandlers) => {
const handlers = customHandlers || createHandlers();
const vanillaStore = createStore(handlers);
const useBoundStore = (selector) => zustand.useStore(vanillaStore, selector);
Object.assign(useBoundStore, vanillaStore);
// return store hook
return useBoundStore;
};
/**
* Creates a dispatch function for sending actions to the main process
*
* @template S The state type
* @template TActions A record of action types to payload types mapping (optional)
* @param customHandlers Optional custom handlers to use instead of window.zubridge
* @returns A typed dispatch function
*
* @example
* // Basic usage
* const dispatch = useDispatch();
*
* @example
* // With typed actions
* type CounterActions = {
* 'COUNTER:INCREMENT': void;
* 'COUNTER:DECREMENT': void;
* 'COUNTER:SET': number;
* };
* const dispatch = useDispatch<State, CounterActions>();
* dispatch({ type: 'COUNTER:SET', payload: 5 }); // Type-safe payload
* dispatch({ type: 'UNKNOWN' }); // Type error
*/
const useDispatch = (customHandlers) => {
const handlers = customHandlers || createHandlers();
// Ensure we have a store for these handlers
const store = storeRegistry.has(handlers) ? storeRegistry.get(handlers) : createStore(handlers);
// Create a dispatch function that will handle both generic and typed actions
const dispatch = ((action, payload) => {
if (typeof action === 'function') {
// Handle thunks - execute them with the store's getState and our dispatch function
return action(store.getState, dispatch);
}
// Handle string action type with payload
if (typeof action === 'string') {
// Only pass the payload parameter if it's not undefined, and handle promise return value
return payload !== undefined ? handlers.dispatch(action, payload) : handlers.dispatch(action);
}
// For action objects, normalize to standard Action format
if (typeof action.type !== 'string') {
throw new Error(`Invalid action type: ${action.type}. Expected a string.`);
}
const normalizedAction = {
type: action.type,
payload: action.payload,
};
// Return the promise from dispatch
return handlers.dispatch(normalizedAction);
});
return dispatch;
};
exports.createHandlers = createHandlers;
exports.createUseStore = createUseStore;
exports.isDev = isDev;
exports.useDispatch = useDispatch;