chore: standardize protocol
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -1,276 +1,61 @@
|
||||
/**
|
||||
* Helper functions for working with service identity and location types
|
||||
* Helper functions for working with service types
|
||||
*/
|
||||
|
||||
import type {
|
||||
ServiceIdentity,
|
||||
ServiceIdentityNone,
|
||||
ServiceIdentityBasic,
|
||||
ServiceIdentityToken,
|
||||
ServiceIdentityOAuth,
|
||||
ServiceIdentityCertificate,
|
||||
ServiceLocation,
|
||||
ServiceLocationUri,
|
||||
ServiceLocationSocketSole,
|
||||
ServiceLocationSocketSplit,
|
||||
ServiceLocationFile
|
||||
} from '@/types/service';
|
||||
|
||||
// ==================== Identity Helpers ====================
|
||||
import type { ServiceListFilterDefinition } from '@/types/service';
|
||||
import { ListFilterComparisonOperator } from '@/types/common';
|
||||
|
||||
/**
|
||||
* Create a "None" identity (no authentication)
|
||||
* Parse a filter specification string into its components
|
||||
*
|
||||
* @param spec - Filter specification string (e.g., "s:200:256:771")
|
||||
* @returns Parsed filter specification object
|
||||
*
|
||||
* @example
|
||||
* parseFilterSpec("s:200:256:771")
|
||||
* // Returns: {
|
||||
* // type: 'string',
|
||||
* // length: 200,
|
||||
* // defaultComparator: 256 (LIKE),
|
||||
* // supportedComparators: [1, 2, 256, 512] (EQ, NEQ, LIKE, NLIKE)
|
||||
* // }
|
||||
*/
|
||||
export function createIdentityNone(): ServiceIdentityNone {
|
||||
return { type: 'NA' };
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Basic Auth identity
|
||||
*/
|
||||
export function createIdentityBasic(identity: string, secret: string): ServiceIdentityBasic {
|
||||
return {
|
||||
type: 'BA',
|
||||
identity,
|
||||
secret
|
||||
export function parseFilterSpec(spec: string): ServiceListFilterDefinition {
|
||||
const [typeCode, lengthStr, defaultComparatorStr, supportedComparatorsStr] = spec.split(':');
|
||||
|
||||
const typeMap: Record<string, ServiceListFilterDefinition['type']> = {
|
||||
's': 'string',
|
||||
'i': 'integer',
|
||||
'd': 'date',
|
||||
'b': 'boolean',
|
||||
'a': 'array',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Token Auth identity
|
||||
*/
|
||||
export function createIdentityToken(token: string): ServiceIdentityToken {
|
||||
return {
|
||||
type: 'TA',
|
||||
token
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an OAuth identity
|
||||
*/
|
||||
export function createIdentityOAuth(
|
||||
accessToken: string,
|
||||
options?: {
|
||||
accessScope?: string[];
|
||||
accessExpiry?: number;
|
||||
refreshToken?: string;
|
||||
refreshLocation?: string;
|
||||
|
||||
const type = typeMap[typeCode];
|
||||
if (!type) {
|
||||
throw new Error(`Invalid filter type code: ${typeCode}`);
|
||||
}
|
||||
): ServiceIdentityOAuth {
|
||||
|
||||
const length = parseInt(lengthStr, 10);
|
||||
const defaultComparator = parseInt(defaultComparatorStr, 10) as ListFilterComparisonOperator;
|
||||
|
||||
// Parse supported comparators from bitmask
|
||||
const supportedComparators: ListFilterComparisonOperator[] = [];
|
||||
const supportedBitmask = parseInt(supportedComparatorsStr, 10);
|
||||
|
||||
if (supportedBitmask !== 0) {
|
||||
const allComparators = Object.values(ListFilterComparisonOperator).filter(v => typeof v === 'number') as number[];
|
||||
for (const comparator of allComparators) {
|
||||
if ((supportedBitmask & comparator) === comparator) {
|
||||
supportedComparators.push(comparator as ListFilterComparisonOperator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'OA',
|
||||
accessToken,
|
||||
...options
|
||||
type,
|
||||
length,
|
||||
defaultComparator,
|
||||
supportedComparators,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Certificate identity
|
||||
*/
|
||||
export function createIdentityCertificate(
|
||||
certificate: string,
|
||||
privateKey: string,
|
||||
passphrase?: string
|
||||
): ServiceIdentityCertificate {
|
||||
return {
|
||||
type: 'CC',
|
||||
certificate,
|
||||
privateKey,
|
||||
...(passphrase && { passphrase })
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== Location Helpers ====================
|
||||
|
||||
/**
|
||||
* Create a URI-based location
|
||||
*/
|
||||
export function createLocationUri(
|
||||
host: string,
|
||||
options?: {
|
||||
scheme?: 'http' | 'https';
|
||||
port?: number;
|
||||
path?: string;
|
||||
verifyPeer?: boolean;
|
||||
verifyHost?: boolean;
|
||||
}
|
||||
): ServiceLocationUri {
|
||||
return {
|
||||
type: 'URI',
|
||||
scheme: options?.scheme || 'https',
|
||||
host,
|
||||
port: options?.port || (options?.scheme === 'http' ? 80 : 443),
|
||||
...(options?.path && { path: options.path }),
|
||||
verifyPeer: options?.verifyPeer ?? true,
|
||||
verifyHost: options?.verifyHost ?? true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a URI location from a full URL string
|
||||
*/
|
||||
export function createLocationFromUrl(url: string): ServiceLocationUri {
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
return {
|
||||
type: 'URI',
|
||||
scheme: parsed.protocol.replace(':', '') as 'http' | 'https',
|
||||
host: parsed.hostname,
|
||||
port: parsed.port ? parseInt(parsed.port) : (parsed.protocol === 'https:' ? 443 : 80),
|
||||
path: parsed.pathname,
|
||||
verifyPeer: true,
|
||||
verifyHost: true
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Invalid URL: ${url}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single socket location (IMAP, SMTP on same server)
|
||||
*/
|
||||
export function createLocationSocketSole(
|
||||
host: string,
|
||||
port: number,
|
||||
encryption: 'none' | 'ssl' | 'tls' | 'starttls' = 'ssl',
|
||||
options?: {
|
||||
verifyPeer?: boolean;
|
||||
verifyHost?: boolean;
|
||||
}
|
||||
): ServiceLocationSocketSole {
|
||||
return {
|
||||
type: 'SOCKET_SOLE',
|
||||
host,
|
||||
port,
|
||||
encryption,
|
||||
verifyPeer: options?.verifyPeer ?? true,
|
||||
verifyHost: options?.verifyHost ?? true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a split socket location (separate IMAP/SMTP servers)
|
||||
*/
|
||||
export function createLocationSocketSplit(
|
||||
config: {
|
||||
inboundHost: string;
|
||||
inboundPort: number;
|
||||
inboundEncryption?: 'none' | 'ssl' | 'tls' | 'starttls';
|
||||
outboundHost: string;
|
||||
outboundPort: number;
|
||||
outboundEncryption?: 'none' | 'ssl' | 'tls' | 'starttls';
|
||||
inboundVerifyPeer?: boolean;
|
||||
inboundVerifyHost?: boolean;
|
||||
outboundVerifyPeer?: boolean;
|
||||
outboundVerifyHost?: boolean;
|
||||
}
|
||||
): ServiceLocationSocketSplit {
|
||||
return {
|
||||
type: 'SOCKET_SPLIT',
|
||||
inboundHost: config.inboundHost,
|
||||
inboundPort: config.inboundPort,
|
||||
inboundEncryption: config.inboundEncryption || 'ssl',
|
||||
outboundHost: config.outboundHost,
|
||||
outboundPort: config.outboundPort,
|
||||
outboundEncryption: config.outboundEncryption || 'ssl',
|
||||
inboundVerifyPeer: config.inboundVerifyPeer ?? true,
|
||||
inboundVerifyHost: config.inboundVerifyHost ?? true,
|
||||
outboundVerifyPeer: config.outboundVerifyPeer ?? true,
|
||||
outboundVerifyHost: config.outboundVerifyHost ?? true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a file-based location
|
||||
*/
|
||||
export function createLocationFile(path: string): ServiceLocationFile {
|
||||
return {
|
||||
type: 'FILE',
|
||||
path
|
||||
};
|
||||
}
|
||||
|
||||
// ==================== Validation Helpers ====================
|
||||
|
||||
/**
|
||||
* Validate that an identity object is properly formed
|
||||
*/
|
||||
export function validateIdentity(identity: ServiceIdentity): boolean {
|
||||
switch (identity.type) {
|
||||
case 'NA':
|
||||
return true;
|
||||
case 'BA':
|
||||
return !!(identity as ServiceIdentityBasic).identity &&
|
||||
!!(identity as ServiceIdentityBasic).secret;
|
||||
case 'TA':
|
||||
return !!(identity as ServiceIdentityToken).token;
|
||||
case 'OA':
|
||||
return !!(identity as ServiceIdentityOAuth).accessToken;
|
||||
case 'CC':
|
||||
return !!(identity as ServiceIdentityCertificate).certificate &&
|
||||
!!(identity as ServiceIdentityCertificate).privateKey;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that a location object is properly formed
|
||||
*/
|
||||
export function validateLocation(location: ServiceLocation): boolean {
|
||||
switch (location.type) {
|
||||
case 'URI':
|
||||
return !!(location as ServiceLocationUri).host &&
|
||||
!!(location as ServiceLocationUri).port;
|
||||
case 'SOCKET_SOLE':
|
||||
return !!(location as ServiceLocationSocketSole).host &&
|
||||
!!(location as ServiceLocationSocketSole).port;
|
||||
case 'SOCKET_SPLIT':
|
||||
const split = location as ServiceLocationSocketSplit;
|
||||
return !!split.inboundHost && !!split.inboundPort &&
|
||||
!!split.outboundHost && !!split.outboundPort;
|
||||
case 'FILE':
|
||||
return !!(location as ServiceLocationFile).path;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Type Guards ====================
|
||||
|
||||
export function isIdentityNone(identity: ServiceIdentity): identity is ServiceIdentityNone {
|
||||
return identity.type === 'NA';
|
||||
}
|
||||
|
||||
export function isIdentityBasic(identity: ServiceIdentity): identity is ServiceIdentityBasic {
|
||||
return identity.type === 'BA';
|
||||
}
|
||||
|
||||
export function isIdentityToken(identity: ServiceIdentity): identity is ServiceIdentityToken {
|
||||
return identity.type === 'TA';
|
||||
}
|
||||
|
||||
export function isIdentityOAuth(identity: ServiceIdentity): identity is ServiceIdentityOAuth {
|
||||
return identity.type === 'OA';
|
||||
}
|
||||
|
||||
export function isIdentityCertificate(identity: ServiceIdentity): identity is ServiceIdentityCertificate {
|
||||
return identity.type === 'CC';
|
||||
}
|
||||
|
||||
export function isLocationUri(location: ServiceLocation): location is ServiceLocationUri {
|
||||
return location.type === 'URI';
|
||||
}
|
||||
|
||||
export function isLocationSocketSole(location: ServiceLocation): location is ServiceLocationSocketSole {
|
||||
return location.type === 'SOCKET_SOLE';
|
||||
}
|
||||
|
||||
export function isLocationSocketSplit(location: ServiceLocation): location is ServiceLocationSocketSplit {
|
||||
return location.type === 'SOCKET_SPLIT';
|
||||
}
|
||||
|
||||
export function isLocationFile(location: ServiceLocation): location is ServiceLocationFile {
|
||||
return location.type === 'FILE';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user