mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-04 16:14:07 +00:00
fix: keep one policy object per change so the login page renders in the desktop app
This commit is contained in:
@@ -97,7 +97,7 @@ function serve(plugins: unknown[]) {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
usePluginStore.setState({ plugins: [], initialized: false });
|
||||
harness.current = snapshotOf({});
|
||||
setPolicy({});
|
||||
localStorage.clear();
|
||||
usePluginStore.setState({ plugins: [], initialized: false });
|
||||
});
|
||||
|
||||
@@ -106,8 +106,8 @@ describe('loadSandboxedPlugin under the OS policy of the device', () => {
|
||||
it('starts nothing the policy refuses: no frame, no download', async () => {
|
||||
const { EMPTY_POLICY_LAYERS, resolvePolicies } = await import('../desktop/policy-schema');
|
||||
const snapshot = (forcedMachine: Record<string, unknown>) => resolvePolicies({ ...EMPTY_POLICY_LAYERS, forcedMachine });
|
||||
let current = snapshot({ PluginsMode: 'allowlist', PluginAllowlist: ['other-plugin'] });
|
||||
window.bulwarkDesktop = { version: 1, platform: 'win32', policy: { get: () => current, onChange: () => () => {}, onWipeOfflineData: () => () => {} } };
|
||||
const bridgeWith = (current: ReturnType<typeof snapshot>) => ({ version: 1, platform: 'win32' as const, policy: { get: () => current, onChange: () => () => {}, onWipeOfflineData: () => () => {} } });
|
||||
window.bulwarkDesktop = bridgeWith(snapshot({ PluginsMode: 'allowlist', PluginAllowlist: ['other-plugin'] }));
|
||||
mocks.getCode.mockResolvedValue(CODE);
|
||||
try {
|
||||
await loadSandboxedPlugin(plugin());
|
||||
@@ -116,7 +116,7 @@ describe('loadSandboxedPlugin under the OS policy of the device', () => {
|
||||
expect(mocks.downloadManagedBundle).not.toHaveBeenCalled();
|
||||
expect(mocks.getCode).not.toHaveBeenCalled();
|
||||
|
||||
current = snapshot({ PluginsMode: 'allowlist', PluginAllowlist: ['managed-plugin'] });
|
||||
window.bulwarkDesktop = bridgeWith(snapshot({ PluginsMode: 'allowlist', PluginAllowlist: ['managed-plugin'] }));
|
||||
await loadSandboxedPlugin(plugin());
|
||||
expect(lastStatus()).toMatchObject({ id: 'managed-plugin', status: 'running' });
|
||||
} finally {
|
||||
|
||||
@@ -85,6 +85,19 @@ describe('in the app', () => {
|
||||
expect(devicesForAccount('some-account').noOffline).toBe(false);
|
||||
}, 60_000);
|
||||
|
||||
it('the hook settles on a bridge that copies what it returns, as contextBridge does', async () => {
|
||||
setBridgePolicy(snapshotOf({ PluginsMode: 'off' }));
|
||||
window.bulwarkDesktop!.policy!.get = () => JSON.parse(JSON.stringify(current));
|
||||
let renders = 0;
|
||||
function Probe() {
|
||||
renders += 1;
|
||||
return <span>{useDesktopPolicy().PluginsMode.value}</span>;
|
||||
}
|
||||
render(<Probe />);
|
||||
expect(screen.getByText('off')).toBeInTheDocument();
|
||||
expect(renders).toBeLessThan(4);
|
||||
});
|
||||
|
||||
it('the hook shows a change at once', async () => {
|
||||
setBridgePolicy(snapshotOf({}));
|
||||
function Probe() {
|
||||
|
||||
+20
-1
@@ -8,6 +8,7 @@
|
||||
|
||||
import { useSyncExternalStore } from 'react';
|
||||
import { getDesktopBridge } from './bridge';
|
||||
import type { DesktopPolicyBridge } from './types';
|
||||
import {
|
||||
DEFAULT_POLICY_SNAPSHOT,
|
||||
effectivePolicyValue,
|
||||
@@ -21,11 +22,29 @@ import {
|
||||
/** A plugin the device's policy refuses, on a plugin's record and in the settings page. */
|
||||
export const PLUGIN_POLICY_REFUSED_ERROR = 'device-policy';
|
||||
|
||||
let cached: { bridge: DesktopPolicyBridge; snapshot: PolicySnapshot } | null = null;
|
||||
|
||||
/**
|
||||
* The same object until the policy changes. The bridge can't give that: contextBridge
|
||||
* copies what it returns, so each `get()` is a new object, and useSyncExternalStore takes
|
||||
* a new object for a change and renders forever.
|
||||
*/
|
||||
export function getDesktopPolicy(): PolicySnapshot {
|
||||
return getDesktopBridge()?.policy?.get?.() ?? DEFAULT_POLICY_SNAPSHOT;
|
||||
const bridge = getDesktopBridge()?.policy;
|
||||
if (!bridge?.get) return DEFAULT_POLICY_SNAPSHOT;
|
||||
if (cached?.bridge !== bridge) {
|
||||
const entry = { bridge, snapshot: bridge.get() };
|
||||
cached = entry;
|
||||
bridge.onChange?.((snapshot) => {
|
||||
entry.snapshot = snapshot;
|
||||
});
|
||||
}
|
||||
return cached.snapshot;
|
||||
}
|
||||
|
||||
export function onDesktopPolicyChange(listener: (snapshot: PolicySnapshot) => void): () => void {
|
||||
// First, so what the listener reads is already the new value.
|
||||
getDesktopPolicy();
|
||||
return getDesktopBridge()?.policy?.onChange?.(listener) ?? (() => {});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user