fix: multiple bug fixes
isolated users, db backup, weird disconnect issues for long voice sessions,
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
import { Injector, runInInjectionContext } from '@angular/core';
|
||||
import { environment } from '../../../../../environments/environment';
|
||||
import type { ServerEndpoint } from '../../domain/models/server-directory.model';
|
||||
import * as serverDirectoryStorageKeys from '../../infrastructure/constants/server-directory.infrastructure.constants';
|
||||
import { ServerEndpointStorageService } from '../../infrastructure/services/server-endpoint-storage.service';
|
||||
import { ServerEndpointStateService } from './server-endpoint-state.service';
|
||||
|
||||
function createLocalStorageMock(): Storage {
|
||||
const store = new Map<string, string>();
|
||||
|
||||
return {
|
||||
get length(): number {
|
||||
return store.size;
|
||||
},
|
||||
clear(): void {
|
||||
store.clear();
|
||||
},
|
||||
getItem(key: string): string | null {
|
||||
return store.get(key) ?? null;
|
||||
},
|
||||
key(index: number): string | null {
|
||||
return [...store.keys()][index] ?? null;
|
||||
},
|
||||
removeItem(key: string): void {
|
||||
store.delete(key);
|
||||
},
|
||||
setItem(key: string, value: string): void {
|
||||
store.set(key, value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Object.defineProperty(globalThis, 'localStorage', {
|
||||
value: createLocalStorageMock(),
|
||||
configurable: true
|
||||
});
|
||||
|
||||
function getConfiguredDefaultServer(key: string): { key?: string; name?: string; url?: string } {
|
||||
const defaultServer = environment.defaultServers.find((server) => server.key === key);
|
||||
|
||||
if (!defaultServer) {
|
||||
throw new Error(`Missing configured default server for key: ${key}`);
|
||||
}
|
||||
|
||||
return defaultServer;
|
||||
}
|
||||
|
||||
function seedStoredEndpoints(endpoints: ServerEndpoint[]): void {
|
||||
localStorage.setItem(serverDirectoryStorageKeys.SERVER_ENDPOINTS_STORAGE_KEY, JSON.stringify(endpoints));
|
||||
}
|
||||
|
||||
function createService(): ServerEndpointStateService {
|
||||
const injector = Injector.create({
|
||||
providers: [
|
||||
{
|
||||
provide: ServerEndpointStorageService,
|
||||
useClass: ServerEndpointStorageService,
|
||||
deps: []
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return runInInjectionContext(injector, () => new ServerEndpointStateService());
|
||||
}
|
||||
|
||||
function getRequiredDefaultEndpoint(service: ServerEndpointStateService, defaultKey: string | undefined): ServerEndpoint {
|
||||
const endpoint = service.servers().find((candidate) => candidate.defaultKey === defaultKey);
|
||||
|
||||
if (!endpoint) {
|
||||
throw new Error(`Expected default endpoint for key: ${defaultKey ?? 'unknown'}`);
|
||||
}
|
||||
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
describe('ServerEndpointStateService', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
it('reactivates configured default endpoints unless the user disabled them', () => {
|
||||
const defaultServer = getConfiguredDefaultServer('toju-primary');
|
||||
|
||||
seedStoredEndpoints([
|
||||
{
|
||||
id: 'default-server',
|
||||
name: 'Stored Default',
|
||||
url: defaultServer.url ?? '',
|
||||
isActive: false,
|
||||
isDefault: true,
|
||||
defaultKey: defaultServer.key,
|
||||
status: 'unknown'
|
||||
}
|
||||
]);
|
||||
|
||||
const service = createService();
|
||||
const endpoint = service.servers().find((candidate) => candidate.defaultKey === defaultServer.key);
|
||||
|
||||
expect(endpoint?.isActive).toBe(true);
|
||||
});
|
||||
|
||||
it('keeps a configured default endpoint inactive after the user turned it off', () => {
|
||||
const defaultServer = getConfiguredDefaultServer('toju-primary');
|
||||
|
||||
seedStoredEndpoints([
|
||||
{
|
||||
id: 'default-server',
|
||||
name: 'Stored Default',
|
||||
url: defaultServer.url ?? '',
|
||||
isActive: true,
|
||||
isDefault: true,
|
||||
defaultKey: defaultServer.key,
|
||||
status: 'unknown'
|
||||
}
|
||||
]);
|
||||
|
||||
localStorage.setItem(
|
||||
serverDirectoryStorageKeys.DISABLED_DEFAULT_SERVER_KEYS_STORAGE_KEY,
|
||||
JSON.stringify([defaultServer.key])
|
||||
);
|
||||
|
||||
const service = createService();
|
||||
const endpoint = service.servers().find((candidate) => candidate.defaultKey === defaultServer.key);
|
||||
|
||||
expect(endpoint?.isActive).toBe(false);
|
||||
});
|
||||
|
||||
it('keeps configured default endpoints active even when stored as incompatible unless the user disabled them', () => {
|
||||
const defaultServer = getConfiguredDefaultServer('toju-primary');
|
||||
|
||||
seedStoredEndpoints([
|
||||
{
|
||||
id: 'default-server',
|
||||
name: 'Stored Default',
|
||||
url: defaultServer.url ?? '',
|
||||
isActive: false,
|
||||
isDefault: true,
|
||||
defaultKey: defaultServer.key,
|
||||
status: 'incompatible'
|
||||
}
|
||||
]);
|
||||
|
||||
const service = createService();
|
||||
const endpoint = service.servers().find((candidate) => candidate.defaultKey === defaultServer.key);
|
||||
|
||||
expect(endpoint?.isActive).toBe(true);
|
||||
expect(endpoint?.status).toBe('incompatible');
|
||||
});
|
||||
|
||||
it('does not deactivate configured default endpoints when compatibility checks fail', () => {
|
||||
const defaultServer = getConfiguredDefaultServer('toju-primary');
|
||||
const service = createService();
|
||||
const endpoint = getRequiredDefaultEndpoint(service, defaultServer.key);
|
||||
|
||||
service.updateServerStatus(endpoint.id, 'incompatible');
|
||||
|
||||
expect(service.servers().find((candidate) => candidate.id === endpoint.id)?.isActive).toBe(true);
|
||||
});
|
||||
|
||||
it('persists turning a configured default endpoint off and back on', () => {
|
||||
const defaultServer = getConfiguredDefaultServer('toju-primary');
|
||||
const service = createService();
|
||||
const endpoint = getRequiredDefaultEndpoint(service, defaultServer.key);
|
||||
|
||||
service.deactivateServer(endpoint.id);
|
||||
|
||||
expect(service.servers().find((candidate) => candidate.id === endpoint.id)?.isActive).toBe(false);
|
||||
expect(JSON.parse(
|
||||
localStorage.getItem(serverDirectoryStorageKeys.DISABLED_DEFAULT_SERVER_KEYS_STORAGE_KEY) ?? '[]'
|
||||
)).toContain(defaultServer.key);
|
||||
|
||||
service.setActiveServer(endpoint.id);
|
||||
|
||||
expect(service.servers().find((candidate) => candidate.id === endpoint.id)?.isActive).toBe(true);
|
||||
expect(localStorage.getItem(serverDirectoryStorageKeys.DISABLED_DEFAULT_SERVER_KEYS_STORAGE_KEY)).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -145,6 +145,7 @@ export class ServerEndpointStateService {
|
||||
|
||||
if (target.isDefault) {
|
||||
this.markDefaultEndpointRemoved(target);
|
||||
this.clearDefaultEndpointDisabled(target);
|
||||
}
|
||||
|
||||
const updatedEndpoints = ensureAnyActiveEndpoint(
|
||||
@@ -171,6 +172,7 @@ export class ServerEndpointStateService {
|
||||
|
||||
this._servers.update((endpoints) => ensureAnyActiveEndpoint([...endpoints, ...restoredEndpoints]));
|
||||
this.storage.clearRemovedDefaultEndpointKeys();
|
||||
this.clearDisabledDefaultEndpointKeys(restoredEndpoints);
|
||||
this.saveEndpoints();
|
||||
return restoredEndpoints;
|
||||
}
|
||||
@@ -190,6 +192,12 @@ export class ServerEndpointStateService {
|
||||
);
|
||||
});
|
||||
|
||||
const target = this._servers().find((endpoint) => endpoint.id === endpointId);
|
||||
|
||||
if (target?.isDefault) {
|
||||
this.clearDefaultEndpointDisabled(target);
|
||||
}
|
||||
|
||||
this.saveEndpoints();
|
||||
}
|
||||
|
||||
@@ -206,6 +214,12 @@ export class ServerEndpointStateService {
|
||||
)
|
||||
);
|
||||
|
||||
const target = this._servers().find((endpoint) => endpoint.id === endpointId);
|
||||
|
||||
if (target?.isDefault) {
|
||||
this.markDefaultEndpointDisabled(target);
|
||||
}
|
||||
|
||||
this.saveEndpoints();
|
||||
}
|
||||
|
||||
@@ -225,7 +239,7 @@ export class ServerEndpointStateService {
|
||||
instanceId: versions?.serverInstanceId ?? endpoint.instanceId,
|
||||
status,
|
||||
latency,
|
||||
isActive: status === 'incompatible' ? false : endpoint.isActive,
|
||||
isActive: status === 'incompatible' && !endpoint.isDefault ? false : endpoint.isActive,
|
||||
serverVersion: versions?.serverVersion ?? endpoint.serverVersion,
|
||||
clientVersion: versions?.clientVersion ?? endpoint.clientVersion
|
||||
};
|
||||
@@ -258,6 +272,7 @@ export class ServerEndpointStateService {
|
||||
private reconcileStoredEndpoints(storedEndpoints: ServerEndpoint[]): ServerEndpoint[] {
|
||||
const reconciled: ServerEndpoint[] = [];
|
||||
const claimedDefaultKeys = new Set<string>();
|
||||
const disabledDefaultKeys = this.storage.loadDisabledDefaultEndpointKeys();
|
||||
const removedDefaultKeys = this.storage.loadRemovedDefaultEndpointKeys();
|
||||
|
||||
for (const endpoint of storedEndpoints) {
|
||||
@@ -279,6 +294,7 @@ export class ServerEndpointStateService {
|
||||
...endpoint,
|
||||
name: matchedDefault.name,
|
||||
url: matchedDefault.url,
|
||||
isActive: this.isDefaultEndpointActive(matchedDefault.defaultKey, disabledDefaultKeys),
|
||||
isDefault: true,
|
||||
defaultKey: matchedDefault.defaultKey,
|
||||
status: endpoint.status ?? 'unknown'
|
||||
@@ -303,7 +319,7 @@ export class ServerEndpointStateService {
|
||||
reconciled.push({
|
||||
...defaultEndpoint,
|
||||
id: uuidv4(),
|
||||
isActive: defaultEndpoint.isActive
|
||||
isActive: this.isDefaultEndpointActive(defaultEndpoint.defaultKey, disabledDefaultKeys)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -324,6 +340,64 @@ export class ServerEndpointStateService {
|
||||
this.storage.saveRemovedDefaultEndpointKeys(removedDefaultKeys);
|
||||
}
|
||||
|
||||
private markDefaultEndpointDisabled(endpoint: ServerEndpoint): void {
|
||||
const defaultKey = endpoint.defaultKey ?? findDefaultEndpointKeyByUrl(this.defaultEndpoints, endpoint.url);
|
||||
|
||||
if (!defaultKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const disabledDefaultKeys = this.storage.loadDisabledDefaultEndpointKeys();
|
||||
|
||||
disabledDefaultKeys.add(defaultKey);
|
||||
this.storage.saveDisabledDefaultEndpointKeys(disabledDefaultKeys);
|
||||
}
|
||||
|
||||
private clearDefaultEndpointDisabled(endpoint: ServerEndpoint): void {
|
||||
const defaultKey = endpoint.defaultKey ?? findDefaultEndpointKeyByUrl(this.defaultEndpoints, endpoint.url);
|
||||
|
||||
if (!defaultKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const disabledDefaultKeys = this.storage.loadDisabledDefaultEndpointKeys();
|
||||
|
||||
if (!disabledDefaultKeys.delete(defaultKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.storage.saveDisabledDefaultEndpointKeys(disabledDefaultKeys);
|
||||
}
|
||||
|
||||
private clearDisabledDefaultEndpointKeys(endpoints: ServerEndpoint[]): void {
|
||||
const disabledDefaultKeys = this.storage.loadDisabledDefaultEndpointKeys();
|
||||
|
||||
let didChange = false;
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
const defaultKey = endpoint.defaultKey ?? findDefaultEndpointKeyByUrl(this.defaultEndpoints, endpoint.url);
|
||||
|
||||
if (!defaultKey) {
|
||||
continue;
|
||||
}
|
||||
|
||||
didChange = disabledDefaultKeys.delete(defaultKey) || didChange;
|
||||
}
|
||||
|
||||
if (!didChange) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.storage.saveDisabledDefaultEndpointKeys(disabledDefaultKeys);
|
||||
}
|
||||
|
||||
private isDefaultEndpointActive(
|
||||
defaultKey: string,
|
||||
disabledDefaultKeys: Set<string>
|
||||
): boolean {
|
||||
return !disabledDefaultKeys.has(defaultKey);
|
||||
}
|
||||
|
||||
private saveEndpoints(): void {
|
||||
this.storage.saveEndpoints(this._servers());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user