Refactor VPN proxy components and update related behavior
This commit is contained in:
@@ -1,10 +1,19 @@
|
||||
import crypto from 'node:crypto';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { normalizeStoredState, type StoredState } from '../../shared/contracts/state.js';
|
||||
import {
|
||||
normalizeStoredState,
|
||||
type PersistedState,
|
||||
} from '../../shared/contracts/state.js';
|
||||
import { INITIAL_ROUTE_RULES } from '../../shared/routingRules.js';
|
||||
import {
|
||||
normalizeServers,
|
||||
resolveServerId,
|
||||
serverIdentityKey,
|
||||
type NormalizedServer,
|
||||
} from '../../shared/serverIdentity.js';
|
||||
|
||||
export const STATE_SCHEMA_VERSION = 4;
|
||||
export const STATE_SCHEMA_VERSION = 5;
|
||||
|
||||
export interface AtomicWriteOptions {
|
||||
beforeRename?: (temporaryPath: string, filePath: string) => void;
|
||||
@@ -59,6 +68,23 @@ function record(value: unknown): Record<string, unknown> {
|
||||
: {};
|
||||
}
|
||||
|
||||
function remapLegacyServerId(
|
||||
previousServers: NormalizedServer[],
|
||||
nextServers: NormalizedServer[],
|
||||
serverId: unknown,
|
||||
legacyTag: unknown = '',
|
||||
) {
|
||||
const direct = resolveServerId(nextServers, serverId, legacyTag);
|
||||
if (direct) return direct;
|
||||
const previousId = resolveServerId(previousServers, serverId, legacyTag);
|
||||
const previous = previousServers.find((server) => server.id === previousId);
|
||||
if (!previous) return '';
|
||||
const matches = nextServers.filter((server) => (
|
||||
serverIdentityKey(server) === serverIdentityKey(previous)
|
||||
));
|
||||
return matches.length === 1 ? matches[0].id : '';
|
||||
}
|
||||
|
||||
function syncDirectory(directory: string) {
|
||||
let descriptor: number | undefined;
|
||||
try {
|
||||
@@ -105,7 +131,10 @@ export function atomicWriteJson(filePath: string, value: unknown, options?: Atom
|
||||
atomicWriteFile(filePath, JSON.stringify(value, null, 2), options);
|
||||
}
|
||||
|
||||
export function migrateStoredState(value: unknown): StoredState & { schemaVersion: number } {
|
||||
export function migrateStoredState(
|
||||
value: unknown,
|
||||
legacySubscriptionCache: unknown = null,
|
||||
): PersistedState & { schemaVersion: number } {
|
||||
const stored = record(value);
|
||||
const version = Number.isSafeInteger(stored.schemaVersion) ? Number(stored.schemaVersion) : 0;
|
||||
if (version < 0 || version > STATE_SCHEMA_VERSION) {
|
||||
@@ -114,10 +143,60 @@ export function migrateStoredState(value: unknown): StoredState & { schemaVersio
|
||||
const routeRules = version < 3
|
||||
? [...INITIAL_ROUTE_RULES, ...(Array.isArray(stored.routeRules) ? stored.routeRules : [])]
|
||||
: stored.routeRules;
|
||||
const legacyCache = record(legacySubscriptionCache);
|
||||
const storedSubscriptionUrl = String(stored.subscriptionUrl || '').trim();
|
||||
const cachedSubscriptionUrl = String(legacyCache.url || '').trim();
|
||||
const cacheOwnsStoredSubscription = Boolean(cachedSubscriptionUrl)
|
||||
&& (!storedSubscriptionUrl || cachedSubscriptionUrl === storedSubscriptionUrl);
|
||||
const previousServers = normalizeServers(stored.servers);
|
||||
const cachedServers = cacheOwnsStoredSubscription
|
||||
? normalizeServers(legacyCache.servers)
|
||||
: [];
|
||||
const migratedServers = cachedServers.length ? cachedServers : previousServers;
|
||||
const selectedServerId = remapLegacyServerId(
|
||||
previousServers,
|
||||
migratedServers,
|
||||
stored.selectedServerId,
|
||||
stored.selectedTag,
|
||||
);
|
||||
const appliedServerId = remapLegacyServerId(
|
||||
previousServers,
|
||||
migratedServers,
|
||||
stored.appliedServerId,
|
||||
stored.appliedTag || stored.selectedTag,
|
||||
);
|
||||
const keepLegacyApplied = !(version < 5 && stored.connectionDesired === 'stopped');
|
||||
const normalized = normalizeStoredState({
|
||||
...stored,
|
||||
routeRules,
|
||||
...(version < 5 && !Array.isArray(stored.profiles) ? {
|
||||
subscriptionUrl: storedSubscriptionUrl || (cacheOwnsStoredSubscription ? cachedSubscriptionUrl : ''),
|
||||
subscriptionConfig: cacheOwnsStoredSubscription ? legacyCache.config : null,
|
||||
servers: migratedServers,
|
||||
selectedServerId,
|
||||
selectedTag: '',
|
||||
appliedServerId: keepLegacyApplied ? appliedServerId : '',
|
||||
appliedServerSnapshot: keepLegacyApplied ? stored.appliedServerSnapshot : null,
|
||||
appliedTag: '',
|
||||
userInfo: stored.userInfo || (cacheOwnsStoredSubscription ? legacyCache.userInfo : undefined),
|
||||
fetchedAt: stored.fetchedAt || (cacheOwnsStoredSubscription ? legacyCache.fetchedAt : undefined),
|
||||
} : {}),
|
||||
});
|
||||
const canonical = { ...normalized } as Record<string, unknown>;
|
||||
for (const key of [
|
||||
'subscriptionUrl',
|
||||
'selectedServerId',
|
||||
'selectedTag',
|
||||
'appliedTag',
|
||||
'servers',
|
||||
'userInfo',
|
||||
'fetchedAt',
|
||||
'subscriptionConfig',
|
||||
]) delete canonical[key];
|
||||
return {
|
||||
...normalizeStoredState({ ...stored, routeRules }),
|
||||
...canonical,
|
||||
schemaVersion: STATE_SCHEMA_VERSION,
|
||||
};
|
||||
} as PersistedState & { schemaVersion: number };
|
||||
}
|
||||
|
||||
export function createJsonStore<T>(options: JsonStoreOptions<T>): JsonStore<T>;
|
||||
@@ -204,14 +283,17 @@ export function createJsonStore(options: JsonStoreOptions<unknown> | RawJsonStor
|
||||
|
||||
export function createStateStore(
|
||||
filePath: string,
|
||||
options: Partial<Omit<JsonStoreOptions<StoredState & { schemaVersion: number }>, 'filePath' | 'defaultValue' | 'migrate'>> = {},
|
||||
options: Partial<Omit<JsonStoreOptions<PersistedState & { schemaVersion: number }>, 'filePath' | 'defaultValue' | 'migrate'>> & {
|
||||
legacySubscriptionCache?: unknown;
|
||||
} = {},
|
||||
) {
|
||||
return createJsonStore<StoredState & { schemaVersion: number }>({
|
||||
const { legacySubscriptionCache = null, ...storeOptions } = options;
|
||||
return createJsonStore<PersistedState & { schemaVersion: number }>({
|
||||
filePath,
|
||||
defaultValue: migrateStoredState({}),
|
||||
migrate: migrateStoredState,
|
||||
defaultValue: migrateStoredState({}, legacySubscriptionCache),
|
||||
migrate: (value) => migrateStoredState(value, legacySubscriptionCache),
|
||||
initializeMissing: true,
|
||||
backupWhen: (before, after) => record(before).schemaVersion !== record(after).schemaVersion,
|
||||
...options,
|
||||
...storeOptions,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user