feat: add domain rules management feature

- Introduced a new DomainRulesPage component for managing custom and global equivalent domains.
- Updated AppMainRoutes to include a route for domain rules.
- Added API functions to fetch and save domain rules.
- Enhanced localization with new strings for domain rules in multiple languages.
- Updated styles for the new domain rules interface and ensured responsiveness.
- Added types for domain rules in the TypeScript definitions.
This commit is contained in:
shuaiplus
2026-05-06 00:33:09 +08:00
parent 246c73a3d3
commit 0a001bebcc
32 changed files with 2045 additions and 32 deletions
+80
View File
@@ -0,0 +1,80 @@
import type { Env } from '../types';
import { StorageService } from '../services/storage';
import {
buildDomainsResponse,
customRulesToActiveEquivalentDomains,
normalizeCustomEquivalentDomains,
normalizeEquivalentDomains,
normalizeExcludedGlobalTypes,
} from '../services/domain-rules';
import { errorResponse, jsonResponse } from '../utils/response';
function firstPresent(payload: Record<string, unknown>, keys: string[]): unknown {
for (const key of keys) {
if (Object.prototype.hasOwnProperty.call(payload, key)) return payload[key];
}
return undefined;
}
async function readPayload(request: Request): Promise<Record<string, unknown>> {
try {
const parsed = await request.json();
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
? parsed as Record<string, unknown>
: {};
} catch {
return {};
}
}
export async function handleGetDomains(env: Env, userId: string): Promise<Response> {
const storage = new StorageService(env.DB);
const settings = await storage.getUserDomainSettings(userId);
return jsonResponse(buildDomainsResponse(
settings.equivalentDomains,
settings.customEquivalentDomains,
settings.excludedGlobalEquivalentDomains
));
}
export async function handleUpdateDomains(request: Request, env: Env, userId: string): Promise<Response> {
const storage = new StorageService(env.DB);
const payload = await readPayload(request);
const current = await storage.getUserDomainSettings(userId);
const equivalentDomainsRaw = firstPresent(payload, [
'equivalentDomains',
'EquivalentDomains',
]);
const customEquivalentDomainsRaw = firstPresent(payload, [
'customEquivalentDomains',
'CustomEquivalentDomains',
]);
const excludedGlobalEquivalentDomainsRaw = firstPresent(payload, [
'excludedGlobalEquivalentDomains',
'ExcludedGlobalEquivalentDomains',
// Some older compatible clients send the excluded type list under this key.
'globalEquivalentDomains',
'GlobalEquivalentDomains',
]);
const customEquivalentDomains = customEquivalentDomainsRaw === undefined
? (equivalentDomainsRaw === undefined
? current.customEquivalentDomains
: normalizeCustomEquivalentDomains(normalizeEquivalentDomains(equivalentDomainsRaw)))
: normalizeCustomEquivalentDomains(customEquivalentDomainsRaw);
const equivalentDomains = customRulesToActiveEquivalentDomains(customEquivalentDomains);
const excludedGlobalEquivalentDomains = excludedGlobalEquivalentDomainsRaw === undefined
? current.excludedGlobalEquivalentDomains
: normalizeExcludedGlobalTypes(excludedGlobalEquivalentDomainsRaw);
await storage.saveUserDomainSettings(userId, equivalentDomains, customEquivalentDomains, excludedGlobalEquivalentDomains);
const settings = await storage.getUserDomainSettings(userId);
if (!settings) {
return errorResponse('Domain settings unavailable', 500);
}
return jsonResponse(buildDomainsResponse(
settings.equivalentDomains,
settings.customEquivalentDomains,
settings.excludedGlobalEquivalentDomains
));
}
+9 -6
View File
@@ -9,6 +9,7 @@ import {
buildUserDecryptionCompat,
buildUserDecryptionOptions,
} from '../utils/user-decryption';
import { buildDomainsResponse } from '../services/domain-rules';
function buildSyncCacheRequest(request: Request, userId: string, revisionDate: string, excludeDomains: boolean, excludeSends: boolean): Request {
const url = new URL(request.url);
@@ -50,11 +51,12 @@ export async function handleSync(request: Request, env: Env, userId: string): Pr
return cachedResponse;
}
const [ciphers, folders, sends, attachmentsByCipher] = await Promise.all([
const [ciphers, folders, sends, attachmentsByCipher, domainSettings] = await Promise.all([
storage.getAllCiphers(userId),
storage.getAllFolders(userId),
excludeSends ? Promise.resolve([]) : storage.getAllSends(userId),
storage.getAttachmentsByUserId(userId),
excludeDomains ? Promise.resolve(null) : storage.getUserDomainSettings(userId),
]);
const accountKeys = buildAccountKeys(user);
const userDecryptionOptions = buildUserDecryptionOptions(user);
@@ -111,11 +113,12 @@ export async function handleSync(request: Request, env: Env, userId: string): Pr
ciphers: cipherResponses,
domains: excludeDomains
? null
: {
equivalentDomains: [],
globalEquivalentDomains: [],
object: 'domains',
},
: buildDomainsResponse(
domainSettings?.equivalentDomains || [],
domainSettings?.customEquivalentDomains || [],
domainSettings?.excludedGlobalEquivalentDomains || [],
{ omitExcludedGlobals: true }
),
policies: [],
sends: sendResponses,
UserDecryption: {