Refactor VPN proxy client implementation
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
export type OperationKey = 'connection' | 'serverApply' | 'subscriptionImport'
|
||||
| 'subscriptionRefresh' | 'subscriptionDelete' | 'gatewayAuto' | 'routeRules';
|
||||
export interface OperationState { status: 'running'; startedAt: string }
|
||||
export type OperationRegistrySnapshot = Partial<Record<OperationKey, OperationState>>;
|
||||
|
||||
export const OPERATION_CONFLICTS: Readonly<Record<OperationKey, readonly OperationKey[]>> = Object.freeze({
|
||||
connection: ['serverApply', 'subscriptionImport', 'subscriptionRefresh', 'subscriptionDelete', 'gatewayAuto', 'routeRules'],
|
||||
serverApply: ['connection', 'subscriptionImport', 'subscriptionRefresh', 'subscriptionDelete', 'gatewayAuto', 'routeRules'],
|
||||
subscriptionImport: ['connection', 'serverApply', 'subscriptionRefresh', 'subscriptionDelete', 'gatewayAuto', 'routeRules'],
|
||||
subscriptionRefresh: ['connection', 'serverApply', 'subscriptionImport', 'subscriptionDelete', 'gatewayAuto', 'routeRules'],
|
||||
subscriptionDelete: ['connection', 'serverApply', 'subscriptionImport', 'subscriptionRefresh', 'gatewayAuto', 'routeRules'],
|
||||
gatewayAuto: ['connection', 'serverApply', 'subscriptionImport', 'subscriptionRefresh', 'subscriptionDelete', 'routeRules'],
|
||||
routeRules: ['connection', 'serverApply', 'subscriptionImport', 'subscriptionRefresh', 'subscriptionDelete', 'gatewayAuto'],
|
||||
});
|
||||
|
||||
export function operationBlocked(operations: OperationRegistrySnapshot, key: OperationKey) {
|
||||
if (operations[key]?.status === 'running') return true;
|
||||
return (OPERATION_CONFLICTS[key] || []).some(
|
||||
(conflict) => operations[conflict]?.status === 'running',
|
||||
);
|
||||
}
|
||||
|
||||
export function createOperationRegistry(
|
||||
onChange: (operations: OperationRegistrySnapshot) => void = () => {},
|
||||
now = () => new Date().toISOString(),
|
||||
) {
|
||||
let operations: OperationRegistrySnapshot = {};
|
||||
const inFlight = new Map<OperationKey, Promise<unknown>>();
|
||||
|
||||
function run<T>(key: OperationKey, action: () => T | Promise<T>): Promise<T | false> {
|
||||
const existing = inFlight.get(key);
|
||||
if (existing) return existing as Promise<T>;
|
||||
if (operationBlocked(operations, key)) return Promise.resolve(false);
|
||||
|
||||
operations = { ...operations, [key]: { status: 'running', startedAt: now() } };
|
||||
onChange(operations);
|
||||
|
||||
const promise: Promise<T> = Promise.resolve()
|
||||
.then(action)
|
||||
.finally(() => {
|
||||
const { [key]: completed, ...remaining } = operations;
|
||||
operations = remaining;
|
||||
inFlight.delete(key);
|
||||
onChange(operations);
|
||||
});
|
||||
inFlight.set(key, promise as Promise<unknown>);
|
||||
return promise;
|
||||
}
|
||||
|
||||
return { run, getSnapshot: () => operations };
|
||||
}
|
||||
Reference in New Issue
Block a user