signal-desktop/ts/hooks/useNavBlocker.ts

100 lines
2.2 KiB
TypeScript
Raw Normal View History

2025-09-29 15:34:24 -07:00
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { useEffect, useRef, useState } from 'react';
import {
beforeNavigateService,
BeforeNavigateResponse,
} from '../services/BeforeNavigate.js';
2025-09-29 15:34:24 -07:00
import type {
BeforeNavigateCallback,
BeforeNavigateTransitionDetails,
} from '../services/BeforeNavigate.js';
type NavBlockerBlocked = Readonly<{
state: 'blocked';
respond: (response: BeforeNavigateResponse) => void;
}>;
type NavBlockerUnblocked = Readonly<{
state: 'unblocked';
respond?: never;
}>;
export type NavBlocker = NavBlockerBlocked | NavBlockerUnblocked;
export type NavBlockerFunction = (
details: BeforeNavigateTransitionDetails
) => boolean;
export type ShouldBlock = boolean | NavBlockerFunction;
function checkShouldBlock(
shouldBlock: ShouldBlock,
details: BeforeNavigateTransitionDetails
): boolean {
if (typeof shouldBlock === 'function') {
return shouldBlock(details);
}
return shouldBlock;
}
export function useNavBlocker(
name: string,
shouldBlock: ShouldBlock
): NavBlocker {
const nameRef = useRef(name);
useEffect(() => {
nameRef.current = name;
}, [name]);
const shouldBlockRef = useRef(shouldBlock);
useEffect(() => {
shouldBlockRef.current = shouldBlock;
}, [shouldBlock]);
const [blocker, setBlocker] = useState<NavBlocker>(() => {
return { state: 'unblocked' };
});
useEffect(() => {
const nameValue = nameRef.current;
const callback: BeforeNavigateCallback = async details => {
const shouldBlockNav = checkShouldBlock(shouldBlockRef.current, details);
if (!shouldBlockNav) {
return BeforeNavigateResponse.Noop;
}
const { promise, resolve } =
Promise.withResolvers<BeforeNavigateResponse>();
function respond(response: BeforeNavigateResponse) {
setBlocker({ state: 'unblocked' });
resolve(response);
}
setBlocker({
state: 'blocked',
respond,
});
return promise;
};
beforeNavigateService.registerCallback({
2025-09-29 15:34:24 -07:00
callback,
name: nameValue,
});
return () => {
beforeNavigateService.unregisterCallback({
2025-09-29 15:34:24 -07:00
callback,
name: nameValue,
});
};
}, []);
return blocker;
}