[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
240
domain/proxyProfiles.ts
Normal file
240
domain/proxyProfiles.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
import { isEncryptedCredentialPlaceholder, sanitizeCredentialValue } from "./credentials";
|
||||
import type { GroupConfig, Host, Identity, ProxyConfig, ProxyProfile } from "./models";
|
||||
|
||||
const cloneProxyConfig = (config: ProxyConfig): ProxyConfig => ({
|
||||
...config,
|
||||
});
|
||||
|
||||
export const isValidProxyPort = (port: unknown): boolean => {
|
||||
const value = Number(port);
|
||||
return Number.isInteger(value) && value >= 1 && value <= 65535;
|
||||
};
|
||||
|
||||
export const isProxyCommandConfig = (config: ProxyConfig | undefined): boolean => {
|
||||
return config?.type === "command";
|
||||
};
|
||||
|
||||
export const isEmptyProxyConfigDraft = (config: ProxyConfig | undefined): boolean => {
|
||||
if (!config) return true;
|
||||
if (isProxyCommandConfig(config)) return !config.command?.trim();
|
||||
return !config.host.trim() && !config.username?.trim() && !config.password?.trim();
|
||||
};
|
||||
|
||||
export const isCompleteProxyConfig = (config: ProxyConfig | undefined): boolean => {
|
||||
if (isProxyCommandConfig(config)) return Boolean(config?.command?.trim());
|
||||
return Boolean(config?.host.trim()) && isValidProxyPort(config?.port);
|
||||
};
|
||||
|
||||
export const normalizeManualProxyConfig = (
|
||||
config: ProxyConfig | undefined,
|
||||
): ProxyConfig | undefined => {
|
||||
if (!config || isEmptyProxyConfigDraft(config)) return undefined;
|
||||
if (isProxyCommandConfig(config)) {
|
||||
return {
|
||||
type: "command",
|
||||
host: "",
|
||||
port: 0,
|
||||
command: config.command?.trim(),
|
||||
};
|
||||
}
|
||||
const normalized: ProxyConfig = {
|
||||
type: config.type,
|
||||
host: config.host.trim(),
|
||||
port: Number(config.port),
|
||||
};
|
||||
if (config.identityId) {
|
||||
normalized.identityId = config.identityId;
|
||||
} else {
|
||||
normalized.username = config.username?.trim() || undefined;
|
||||
normalized.password = config.password || undefined;
|
||||
}
|
||||
return normalized;
|
||||
};
|
||||
|
||||
export const updateProxyConfigField = (
|
||||
config: ProxyConfig | undefined,
|
||||
field: keyof ProxyConfig,
|
||||
value: ProxyConfig[keyof ProxyConfig],
|
||||
): ProxyConfig => {
|
||||
const next: ProxyConfig = {
|
||||
type: config?.type || "http",
|
||||
host: config?.host || "",
|
||||
port: config?.port || 8080,
|
||||
...config,
|
||||
[field]: value,
|
||||
};
|
||||
if (field === "identityId") {
|
||||
delete next.username;
|
||||
delete next.password;
|
||||
if (!value) delete next.identityId;
|
||||
} else if (field === "username" || field === "password") {
|
||||
delete next.identityId;
|
||||
} else if (field === "type") {
|
||||
if (value === "command") {
|
||||
delete next.identityId;
|
||||
delete next.username;
|
||||
delete next.password;
|
||||
} else {
|
||||
delete next.command;
|
||||
}
|
||||
}
|
||||
return next;
|
||||
};
|
||||
|
||||
const findIdentity = (
|
||||
identityId: string | undefined,
|
||||
identities: Identity[] = [],
|
||||
): Identity | undefined => {
|
||||
if (!identityId) return undefined;
|
||||
return identities.find((identity) => identity.id === identityId);
|
||||
};
|
||||
|
||||
export const findMissingProxyIdentityId = (
|
||||
config: ProxyConfig | undefined,
|
||||
identities: Identity[] = [],
|
||||
): string | undefined => {
|
||||
if (!config?.identityId) return undefined;
|
||||
return findIdentity(config.identityId, identities) ? undefined : config.identityId;
|
||||
};
|
||||
|
||||
export const findIncompleteProxyIdentityId = (
|
||||
config: ProxyConfig | undefined,
|
||||
identities: Identity[] = [],
|
||||
): string | undefined => {
|
||||
if (!config?.identityId) return undefined;
|
||||
const identity = findIdentity(config.identityId, identities);
|
||||
if (!identity) return undefined;
|
||||
if (!identity.username?.trim()) return config.identityId;
|
||||
if (isEncryptedCredentialPlaceholder(identity.password)) return undefined;
|
||||
return sanitizeCredentialValue(identity.password)
|
||||
? undefined
|
||||
: config.identityId;
|
||||
};
|
||||
|
||||
export const hasMissingProxyIdentity = (
|
||||
config: ProxyConfig | undefined,
|
||||
identities: Identity[] = [],
|
||||
): boolean => {
|
||||
return Boolean(findMissingProxyIdentityId(config, identities));
|
||||
};
|
||||
|
||||
export const hasIncompleteProxyIdentity = (
|
||||
config: ProxyConfig | undefined,
|
||||
identities: Identity[] = [],
|
||||
): boolean => {
|
||||
return Boolean(findIncompleteProxyIdentityId(config, identities));
|
||||
};
|
||||
|
||||
export const formatMissingProxyIdentityMessage = (
|
||||
ownerLabel: string,
|
||||
): string => {
|
||||
return `Proxy identity for "${ownerLabel}" is missing. Open settings and select a valid proxy identity.`;
|
||||
};
|
||||
|
||||
export const formatIncompleteProxyIdentityMessage = (
|
||||
ownerLabel: string,
|
||||
): string => {
|
||||
return `Proxy identity for "${ownerLabel}" is incomplete. Select an identity with a username and password, or enter proxy credentials manually.`;
|
||||
};
|
||||
|
||||
export const resolveProxyConfigAuth = (
|
||||
config: ProxyConfig,
|
||||
identities?: Identity[],
|
||||
): ProxyConfig => {
|
||||
const identity = findIdentity(config.identityId, identities);
|
||||
const username = config.identityId
|
||||
? identity?.username?.trim()
|
||||
: config.username?.trim();
|
||||
const password = config.identityId
|
||||
? sanitizeCredentialValue(identity?.password)
|
||||
: sanitizeCredentialValue(config.password);
|
||||
|
||||
const resolved: ProxyConfig = {
|
||||
type: config.type,
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
username: username || undefined,
|
||||
password,
|
||||
};
|
||||
if (config.command !== undefined) {
|
||||
resolved.command = config.command;
|
||||
}
|
||||
return resolved;
|
||||
};
|
||||
|
||||
export const hasUnreadableProxyCredential = (
|
||||
config: ProxyConfig | undefined,
|
||||
identities?: Identity[],
|
||||
): boolean => {
|
||||
if (!config) return false;
|
||||
const identity = findIdentity(config.identityId, identities);
|
||||
const username = config.identityId
|
||||
? identity?.username?.trim()
|
||||
: config.username?.trim();
|
||||
const rawPassword = config.identityId ? identity?.password : config.password;
|
||||
return Boolean(username) &&
|
||||
isEncryptedCredentialPlaceholder(rawPassword) &&
|
||||
!sanitizeCredentialValue(rawPassword);
|
||||
};
|
||||
|
||||
export const hasUsableProxyConfig = (config: ProxyConfig | undefined): boolean => {
|
||||
return isCompleteProxyConfig(config);
|
||||
};
|
||||
|
||||
export const formatProxyConfigEndpoint = (config: ProxyConfig | undefined): string => {
|
||||
if (!config) return "";
|
||||
if (isProxyCommandConfig(config)) return "ProxyCommand";
|
||||
return `${config.host}:${config.port}`;
|
||||
};
|
||||
|
||||
export const formatProxyConfigType = (config: ProxyConfig | undefined): string => {
|
||||
if (!config) return "";
|
||||
if (isProxyCommandConfig(config)) return "ProxyCommand";
|
||||
return config.type.toUpperCase();
|
||||
};
|
||||
|
||||
export function findProxyProfile(
|
||||
proxyProfileId: string | undefined,
|
||||
proxyProfiles: ProxyProfile[],
|
||||
): ProxyProfile | undefined {
|
||||
if (!proxyProfileId) return undefined;
|
||||
return proxyProfiles.find((profile) => profile.id === proxyProfileId);
|
||||
}
|
||||
|
||||
export function materializeHostProxyProfile<T extends Host>(
|
||||
host: T,
|
||||
proxyProfiles: ProxyProfile[],
|
||||
): T {
|
||||
if (host.proxyConfig || !host.proxyProfileId) return host;
|
||||
const profile = findProxyProfile(host.proxyProfileId, proxyProfiles);
|
||||
if (!profile) return host;
|
||||
return {
|
||||
...host,
|
||||
proxyConfig: cloneProxyConfig(profile.config),
|
||||
};
|
||||
}
|
||||
|
||||
const clearProxyProfileId = <T extends { proxyProfileId?: string }>(
|
||||
item: T,
|
||||
proxyProfileId: string,
|
||||
): T => {
|
||||
if (item.proxyProfileId !== proxyProfileId) return item;
|
||||
const { proxyProfileId: _proxyProfileId, ...rest } = item;
|
||||
return rest as T;
|
||||
};
|
||||
|
||||
export function removeProxyProfileReferences(
|
||||
proxyProfileId: string,
|
||||
data: {
|
||||
hosts: Host[];
|
||||
groupConfigs: GroupConfig[];
|
||||
},
|
||||
): {
|
||||
hosts: Host[];
|
||||
groupConfigs: GroupConfig[];
|
||||
} {
|
||||
return {
|
||||
hosts: data.hosts.map((host) => clearProxyProfileId(host, proxyProfileId)),
|
||||
groupConfigs: data.groupConfigs.map((config) => clearProxyProfileId(config, proxyProfileId)),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user