#50419: fix: lazily initialize safeStorage async encryptor
Description of Change
The SafeStorage constructor previously registered a browser observer that called os_crypt_async()->GetInstance() on app-ready. Because ESM named imports (import { x } from 'electron') eagerly evaluate all electron module getters, simply importing electron in an ESM entrypoint would construct SafeStorage and touch the OS keychain on app-ready — even when safeStorage was never used.
This showed up as a macOS CI hang: the esm-spec import-meta fixture triggers a keychain access prompt that blocks the test runner until timeout, manifesting as flaky macos-x64 / test (darwin, 2) failures (~50% rate, ~14 occurrences over the last ~50 commits). Diagnosed via timeout screenshot artifacts showing the "Electron Safe Storage" keychain dialog.
Trigger chain:
spec/fixtures/esm/import-meta/main.mjsdoesimport { app, BrowserWindow } from 'electron'- Node's ESM loader eagerly evaluates all electron lazy getters to build the namespace, including
safeStorage - This calls
_linkedBinding('electron_browser_safe_storage')→SafeStorage()constructor →AddObserver(this) - App reaches ready →
OnFinishLaunching→os_crypt_async()->GetInstance()→KeychainKeyProvider::GetKey()→ keychain prompt → hang
Fix: The async encryptor is now requested lazily on the first call to encryptStringAsync, decryptStringAsync, or isAsyncEncryptionAvailable. The constructor is now a no-op.
isAsyncEncryptionAvailable() now returns a Promise<boolean> that resolves once initialization completes, matching what the docs already stated (the previous implementation returned a sync bool, contradicting the docs).
Regression from #49054.
Checklist
- PR description included
-
npm testpasses - Relevant documentation updated
- PR title follows semantic commit guidelines
Release Notes
Notes: Fixed an issue where importing electron via ESM would touch the OS keychain on app-ready even when safeStorage was never used. safeStorage.isAsyncEncryptionAvailable() now returns a Promise as documented.
Backports
Semver Impact
Semantic Versioning helps users understand the impact of updates:
- Major (X.y.z): Breaking changes that may require code modifications
- Minor (x.Y.z): New features that maintain backward compatibility
- Patch (x.y.Z): Bug fixes that don't change the API
- None: Changes that don't affect using facing parts of Electron