chore: add a promise based wait group for the shell variables (#7647)

This commit is contained in:
Sid
2026-04-01 20:41:30 +05:30
committed by GitHub
parent 40298b96a4
commit 87ca5a85d0
3 changed files with 138 additions and 5 deletions

View File

@@ -3,7 +3,7 @@ const path = require('path');
const { execSync } = require('node:child_process');
const isDev = require('electron-is-dev');
const os = require('os');
const { initializeShellEnv } = require('@usebruno/requests');
const { initializeShellEnv, waitForShellEnv } = require('./store/shell-env-state');
const { percentageToZoomLevel } = require('@usebruno/common');
if (isDev) {
@@ -181,8 +181,7 @@ if (useSingleInstance && !gotTheLock) {
// Prepare the renderer once the app is ready
app.on('ready', async () => {
// Ensure shell environment is loaded before any operations that need it
await initializeShellEnv();
initializeShellEnv();
if (isDev) {
const { installExtension, REDUX_DEVTOOLS, REACT_DEVELOPER_TOOLS } = require('electron-devtools-installer');
@@ -203,8 +202,10 @@ app.on('ready', async () => {
// Initialize system proxy cache early (non-blocking)
const { fetchSystemProxy } = require('./store/system-proxy');
fetchSystemProxy().catch((err) => {
console.warn('Failed to initialize system proxy cache:', err);
waitForShellEnv().then(() => {
fetchSystemProxy().catch((err) => {
console.warn('Failed to initialize system proxy cache:', err);
});
});
Menu.setApplicationMenu(menu);

View File

@@ -0,0 +1,27 @@
const { initializeShellEnv: _initializeShellEnv } = require('@usebruno/requests');
const TIMEOUT_MS = 60_000;
let _promise = null;
const _initWithTimeout = () => {
let timer;
const timeout = new Promise((_, reject) => {
timer = setTimeout(() => {
_promise = null;
reject(new Error('Shell environment initialization timed out'));
}, TIMEOUT_MS);
});
return Promise.race([_initializeShellEnv(), timeout]).finally(() => clearTimeout(timer));
};
const initializeShellEnv = () => {
if (!_promise) _promise = _initWithTimeout();
};
const waitForShellEnv = () => {
if (!_promise) _promise = _initWithTimeout();
return _promise;
};
module.exports = { initializeShellEnv, waitForShellEnv };

View File

@@ -0,0 +1,105 @@
let mockInitialize;
jest.mock('@usebruno/requests', () => ({
initializeShellEnv: (...args) => mockInitialize(...args)
}));
describe('shell-env-state', () => {
let initializeShellEnv, waitForShellEnv;
beforeEach(() => {
jest.resetModules();
mockInitialize = jest.fn(() => Promise.resolve());
({ initializeShellEnv, waitForShellEnv } = require('../shell-env-state'));
});
describe('initializeShellEnv', () => {
it('calls the underlying initializer exactly once on first call', () => {
initializeShellEnv();
initializeShellEnv();
initializeShellEnv();
initializeShellEnv();
initializeShellEnv();
initializeShellEnv();
initializeShellEnv();
expect(mockInitialize).toHaveBeenCalledTimes(1);
});
it('returns undefined (fire-and-forget)', () => {
const result = initializeShellEnv();
expect(result).toBeUndefined();
});
});
describe('waitForShellEnv', () => {
it('returns a promise', () => {
const result = waitForShellEnv();
expect(result).toBeInstanceOf(Promise);
});
it('resolves when the underlying promise resolves', async () => {
mockInitialize = jest.fn(() => Promise.resolve('shell-ready'));
await expect(waitForShellEnv()).resolves.toBe('shell-ready');
});
it('returns the same promise on repeated calls', () => {
const p1 = waitForShellEnv();
const p2 = waitForShellEnv();
expect(p1).toBe(p2);
});
it('does not reinitialize if initializeShellEnv was already called', () => {
initializeShellEnv();
waitForShellEnv();
expect(mockInitialize).toHaveBeenCalledTimes(1);
});
it('propagates rejection from the underlying initializer', async () => {
const err = new Error('shell init failed');
mockInitialize = jest.fn(() => Promise.reject(err));
await expect(waitForShellEnv()).rejects.toThrow('shell init failed');
});
describe('timeout', () => {
beforeEach(() => jest.useFakeTimers());
afterEach(() => jest.useRealTimers());
it('rejects after 60 seconds', async () => {
mockInitialize = jest.fn(() => new Promise(() => {})); // never resolves
({ waitForShellEnv } = require('../shell-env-state'));
const p = waitForShellEnv();
jest.advanceTimersByTime(60_000);
await expect(p).rejects.toThrow('Shell environment initialization timed out');
});
it('resets the promise after timeout so next call retries', async () => {
mockInitialize = jest.fn(() => new Promise(() => {}));
({ initializeShellEnv, waitForShellEnv } = require('../shell-env-state'));
const p = waitForShellEnv();
jest.advanceTimersByTime(60_000);
await expect(p).rejects.toThrow('timed out');
// After timeout _promise is null — next call should reinitialize
mockInitialize = jest.fn(() => Promise.resolve('retry-ok'));
const p2 = waitForShellEnv();
await expect(p2).resolves.toBe('retry-ok');
expect(mockInitialize).toHaveBeenCalledTimes(1);
});
it('does not time out if the initializer resolves in time', async () => {
mockInitialize = jest.fn(() => Promise.resolve('fast'));
({ waitForShellEnv } = require('../shell-env-state'));
const p = waitForShellEnv();
jest.advanceTimersByTime(59_999);
await expect(p).resolves.toBe('fast');
});
});
});
});