fix: keep a new default-port account apart from a kept host-only one

This commit is contained in:
Linus Rath
2026-10-31 14:50:25 +01:00
parent 36cc49b706
commit 7b249ab9d9
2 changed files with 55 additions and 2 deletions
+39
View File
@@ -49,6 +49,45 @@ describe('generateAccountId', () => {
});
});
describe('a new default-port account next to a kept host-only id', () => {
const legacy = { id: 'u@example.org@mail.example.org', username: 'u@example.org', serverUrl: 'https://mail.example.org:8120' };
it('gets an id of its own instead of the one the other server kept', () => {
setKnownAccounts([legacy]);
expect(generateAccountId('u@example.org', 'https://mail.example.org')).toBe('u@example.org@mail.example.org:443');
expect(generateAccountId('u@example.org', 'http://mail.example.org/jmap')).toBe('u@example.org@mail.example.org:80');
// The kept account itself still resolves to its old id.
expect(generateAccountId('u@example.org', 'https://mail.example.org:8120')).toBe(legacy.id);
});
it('keeps that id after the other account is gone', () => {
const own = { id: 'u@example.org@mail.example.org:443', username: 'u@example.org', serverUrl: 'https://mail.example.org' };
setKnownAccounts([legacy, own]);
expect(generateAccountId('u@example.org', 'https://mail.example.org')).toBe(own.id);
setKnownAccounts([own]);
expect(generateAccountId('u@example.org', 'https://mail.example.org')).toBe(own.id);
});
it('leaves an existing default-port account under its plain id', () => {
setKnownAccounts([{ id: 'u@example.org@mail.example.org', username: 'u@example.org', serverUrl: 'https://mail.example.org/' }]);
expect(generateAccountId('u@example.org', 'https://mail.example.org')).toBe('u@example.org@mail.example.org');
});
it('adds it to the registry as a second account', () => {
useAccountStore.setState({
accounts: [{ ...entry('u@example.org', legacy.serverUrl), id: legacy.id, avatarColor: '#000', isDefault: true }],
activeAccountId: legacy.id,
defaultAccountId: legacy.id,
});
const id = useAccountStore.getState().addAccount(entry('u@example.org', 'https://mail.example.org'));
expect(id).toBe('u@example.org@mail.example.org:443');
expect(useAccountStore.getState().accounts.map((x) => [x.id, x.serverUrl])).toEqual([
[legacy.id, legacy.serverUrl],
['u@example.org@mail.example.org:443', 'https://mail.example.org'],
]);
});
});
describe('the account registry', () => {
it('adds the same address on another port of the same host as a second account', () => {
const store = useAccountStore.getState();
+16 -2
View File
@@ -50,6 +50,11 @@ function hostOnlyAccountId(username: string, serverUrl: string): string {
return `${username}@${host}`;
}
/** The default port of `serverUrl`'s scheme: 80 for http, 443 otherwise. */
function defaultPortOf(serverUrl: string): string {
return /^http:/i.test(serverUrl.trim()) ? '80' : '443';
}
/** What the registry tells generateAccountId (structural: no AccountEntry import). */
export interface KnownAccount {
id: string;
@@ -78,13 +83,22 @@ export function setKnownAccounts(accounts: readonly KnownAccount[]): void {
* outbox, push registration and settings: when the registry holds the
* host-only id for the same server, that is the answer. A second server on
* the same host then gets the id with its port, and the two stay apart.
* Accounts on the default ports have the same id either way.
* Accounts on the default ports have the same id either way, with one
* exception: when the registry holds username@host for a server on another
* port of that host (kept from before ports counted), the default-port
* server gets username@host:443 (or :80 for http), so the two don't share
* credentials and caches. It keeps that id once it has it.
*/
export function generateAccountId(username: string, serverUrl: string): string {
const legacy = hostOnlyAccountId(username, serverUrl);
const host = serverHostLabel(serverUrl);
const id = `${username}@${host}`;
if (id === legacy) return id;
if (id === legacy) {
const explicit = `${id}:${defaultPortOf(serverUrl)}`;
if (knownAccounts.some((a) => a.id === explicit)) return explicit;
const taken = knownAccounts.some((a) => a.id === id && `${a.username}@${serverHostLabel(a.serverUrl)}` !== id);
return taken ? explicit : id;
}
const kept = knownAccounts.find((a) => a.id === legacy
&& a.username === username
&& `${a.username}@${serverHostLabel(a.serverUrl)}` === id);