178 lines · 6.4 KB · TypeScript
RawDownload
export type LinuxPasswordStorePreference =  | "auto"  | "gnome-libsecret"  | "kwallet"  | "kwallet5"  | "kwallet6";export type LinuxPasswordStoreSwitch = Exclude<LinuxPasswordStorePreference, "auto">; export const DEFAULT_LINUX_PASSWORD_STORE: LinuxPasswordStorePreference = "auto"; // Chromium matches XDG_CURRENT_DESKTOP values case-sensitively and returns on the first value it// recognizes, so these stay exact literals and are scanned in order. Omitting a real desktop fails// safe: we force gnome-libsecret, which is the backend Chromium selects for all of these anyway.const ELECTRON_LIBSECRET_DESKTOPS = new Set([  "Deepin",  "GNOME",  "Pantheon",  "UKUI",  "Unity",  "X-Cinnamon",  "XFCE",]);// Chromium selects a KWallet generation for KDE from KDE_SESSION_VERSION, so it needs no help.const ELECTRON_KDE_DESKTOP = "KDE";// Chromium recognizes LXQt and still selects basic text for it, so it does need a forced backend.const ELECTRON_UNPROTECTED_DESKTOPS = new Set(["LXQt"]); const KDE_NAME_PREFIXES = ["kde", "plasma"];const NEGATIVE_FLAG_VALUES = new Set(["0", "false", "no", "off"]); export function normalizeLinuxPasswordStorePreference(  value: unknown,): LinuxPasswordStorePreference {  return value === "gnome-libsecret" ||    value === "kwallet" ||    value === "kwallet5" ||    value === "kwallet6"    ? value    : DEFAULT_LINUX_PASSWORD_STORE;} // Auto mode asks one question: will Electron select a real keyring on its own? If so, stay out of// the way, which is how canonical KDE sessions keep the KWallet generation Chromium picks for them.// Otherwise force gnome-libsecret, because the alternative is basic text, which is barely// encryption at all. Forcing never guesses a KWallet generation; a KDE session that needs a// specific one sets linuxPasswordStore explicitly.export function resolveLinuxPasswordStoreSwitch(input: {  readonly preference: LinuxPasswordStorePreference;  readonly env: NodeJS.ProcessEnv;}): LinuxPasswordStoreSwitch | null {  if (input.preference !== "auto") {    return input.preference;  }   return electronSelectsProtectedBackend(input.env) ? null : "gnome-libsecret";} // Only an exact XDG_CURRENT_DESKTOP literal proves Electron will protect the session. Chromium can// also reach a real backend through DESKTOP_SESSION and the legacy KDE markers, but those are the// variables a previous session leaves behind, and trusting them is what let stale hints suppress// the forced backend before. Forcing where Chromium would have chosen libsecret is harmless, since// it lands on the same backend.function electronSelectsProtectedBackend(env: NodeJS.ProcessEnv): boolean {  for (const name of splitDesktopNameList(env.XDG_CURRENT_DESKTOP)) {    const trimmed = name.trim();    if (trimmed.length === 0) {      continue;    }    if (trimmed === ELECTRON_KDE_DESKTOP || ELECTRON_LIBSECRET_DESKTOPS.has(trimmed)) {      return true;    }    if (ELECTRON_UNPROTECTED_DESKTOPS.has(trimmed)) {      return false;    }  }   return false;} export function resolveLinuxSecretStorageUnavailableMessage(input: {  readonly configuredPreference: LinuxPasswordStorePreference;  readonly selectedBackend: string | null;  readonly env: NodeJS.ProcessEnv;}): string {  if (input.configuredPreference === "gnome-libsecret") {    return getGnomeKeyringRemediationMessage();  }   if (    input.configuredPreference === "kwallet" ||    input.configuredPreference === "kwallet5" ||    input.configuredPreference === "kwallet6"  ) {    return getKWalletRemediationMessage();  }   const backend = normalizeSelectedStorageBackend(input.selectedBackend);  if (backend === "gnome-libsecret") {    return getGnomeKeyringRemediationMessage();  }   if (    backend === "kwallet" ||    backend === "kwallet5" ||    backend === "kwallet6" ||    looksLikeKdeSession(input.env)  ) {    return getKWalletRemediationMessage();  }   return getGnomeKeyringRemediationMessage();} function getGnomeKeyringRemediationMessage(): string {  return "T3 Code could not access GNOME Keyring to save this environment credential. Install and start GNOME Keyring, then restart T3 Code.";} function getKWalletRemediationMessage(): string {  return "T3 Code could not access KWallet to save this environment credential. Enable the KDE wallet subsystem in System Settings, then restart T3 Code.";} // Advisory only: this picks between the GNOME Keyring and KWallet wording in the failure notice. It// never decides which backend to select, so a loose match costs a user slightly wrong instructions// rather than an unprotected credential store.function looksLikeKdeSession(env: NodeJS.ProcessEnv): boolean {  const currentDesktopNames = nonEmptyDesktopNames(env.XDG_CURRENT_DESKTOP);  if (currentDesktopNames.length > 0) {    return currentDesktopNames.some(isKdeDesktopName);  }   const legacyNames = legacyDesktopNames(env);  if (legacyNames.length > 0) {    return legacyNames.some(isKdeDesktopName);  }   return isSet(env.KDE_SESSION_VERSION) || isAffirmativeFlag(env.KDE_FULL_SESSION);} function isKdeDesktopName(name: string): boolean {  return KDE_NAME_PREFIXES.some((prefix) => name.startsWith(prefix));} function legacyDesktopNames(env: NodeJS.ProcessEnv): string[] {  return [env.XDG_SESSION_DESKTOP, env.DESKTOP_SESSION, env.GDMSESSION].flatMap((entry) => {    const normalized = normalizeDesktopName(entry);    return normalized ? [normalized] : [];  });} function nonEmptyDesktopNames(value: string | undefined): string[] {  return splitDesktopNameList(value).flatMap((entry) => {    const normalized = normalizeDesktopName(entry);    return normalized ? [normalized] : [];  });} function isSet(value: string | undefined): boolean {  return Boolean(value?.trim());} function isAffirmativeFlag(value: string | undefined): boolean {  const normalized = value?.trim().toLowerCase();  return normalized ? !NEGATIVE_FLAG_VALUES.has(normalized) : false;} function splitDesktopNameList(value: string | undefined): string[] {  return value?.split(":") ?? [];} function normalizeDesktopName(value: string | undefined): string | null {  const normalized = value?.trim().toLowerCase();  return normalized && normalized.length > 0 ? normalized : null;} function normalizeSelectedStorageBackend(value: string | null): string | null {  const normalized = value?.trim().toLowerCase().replace(/_/gu, "-");  return normalized && normalized.length > 0 ? normalized : null;}