Compare commits

...

2 Commits

Author SHA1 Message Date
Jovan Krunić
bb1f596bfc fix: enable starting the app without backend
Closes #223
2024-09-30 11:03:53 +00:00
0c49fd8c34 refactor: update nix dependencies 2024-09-30 13:00:59 +02:00
3 changed files with 134 additions and 92 deletions

View File

@@ -4,22 +4,37 @@
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-utils.url = "github:numtide/flake-utils"; flake-utils.url = "github:numtide/flake-utils";
}; };
outputs = { outputs =
{
self, self,
nixpkgs, nixpkgs,
flake-utils, flake-utils,
}: let }:
let
aapt2buildToolsVersion = "33.0.2"; aapt2buildToolsVersion = "33.0.2";
in in
flake-utils.lib.eachDefaultSystem (system: let flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system; inherit system;
overlays = [ overlays = [
(final: prev: rec { (final: prev: rec {
fontMin = prev.python311.withPackages (ps: with ps; [brotli fonttools] ++ (with fonttools.optional-dependencies; [woff])); fontMin = prev.python311.withPackages (
ps:
with ps;
[
brotli
fonttools
]
++ (with fonttools.optional-dependencies; [ woff ])
);
android = prev.androidenv.composeAndroidPackages { android = prev.androidenv.composeAndroidPackages {
buildToolsVersions = ["30.0.3" aapt2buildToolsVersion]; buildToolsVersions = [
platformVersions = ["33"]; "34.0.0"
aapt2buildToolsVersion
];
platformVersions = [ "34" ];
}; };
cypress = prev.cypress.overrideAttrs (cyPrev: rec { cypress = prev.cypress.overrideAttrs (cyPrev: rec {
version = "13.2.0"; version = "13.2.0";
@@ -29,6 +44,7 @@
}; };
}); });
nodejs = prev.nodejs_18; nodejs = prev.nodejs_18;
corepack = prev.corepack_18;
}) })
]; ];
config = { config = {
@@ -38,7 +54,7 @@
}; };
androidFhs = pkgs.buildFHSUserEnv { androidFhs = pkgs.buildFHSUserEnv {
name = "android-env"; name = "android-env";
targetPkgs = pkgs: with pkgs; []; targetPkgs = pkgs: with pkgs; [ ];
runScript = "bash"; runScript = "bash";
profile = '' profile = ''
export ALLOW_NINJA_ENV=true export ALLOW_NINJA_ENV=true
@@ -46,9 +62,10 @@
export LD_LIBRARY_PATH=/usr/lib:/usr/lib32 export LD_LIBRARY_PATH=/usr/lib:/usr/lib32
''; '';
}; };
in { in
{
devShell = pkgs.mkShell rec { devShell = pkgs.mkShell rec {
nativeBuildInputs = [androidFhs]; nativeBuildInputs = [ androidFhs ];
buildInputs = with pkgs; [ buildInputs = with pkgs; [
nodejs nodejs
corepack corepack
@@ -67,5 +84,6 @@
CYPRESS_INSTALL_BINARY = "0"; CYPRESS_INSTALL_BINARY = "0";
CYPRESS_RUN_BINARY = "${pkgs.cypress}/bin/Cypress"; CYPRESS_RUN_BINARY = "${pkgs.cypress}/bin/Cypress";
}; };
}); }
);
} }

View File

@@ -67,7 +67,8 @@ describe('ConfigProvider', () => {
it('should fetch app configuration', async () => { it('should fetch app configuration', async () => {
spyOn(configProvider.client, 'handshake').and.returnValue(Promise.resolve(sampleIndexResponse)); spyOn(configProvider.client, 'handshake').and.returnValue(Promise.resolve(sampleIndexResponse));
const result = await configProvider.fetch(); await configProvider.fetch();
const result = configProvider.config;
expect(result).toEqual(sampleIndexResponse); expect(result).toEqual(sampleIndexResponse);
}); });
@@ -110,7 +111,7 @@ describe('ConfigProvider', () => {
expect(storageProviderSpy.has).toHaveBeenCalled(); expect(storageProviderSpy.has).toHaveBeenCalled();
expect(storageProviderSpy.get).toHaveBeenCalledTimes(0); expect(storageProviderSpy.get).toHaveBeenCalledTimes(0);
expect(configProvider.client.handshake).toHaveBeenCalled(); expect(configProvider.client.handshake).toHaveBeenCalled();
expect(await configProvider.getValue('name')).toEqual(sampleIndexResponse.app.name); expect(configProvider.getValue('name')).toEqual(sampleIndexResponse.app.name);
}); });
it('should throw error on failed initialisation', async () => { it('should throw error on failed initialisation', async () => {
@@ -192,4 +193,31 @@ describe('ConfigProvider', () => {
expect(configProvider.getValue('name')).toEqual(sampleIndexResponse.app.name); expect(configProvider.getValue('name')).toEqual(sampleIndexResponse.app.name);
}); });
it('should fetch new config from remote on init', async () => {
storageProviderSpy.has.and.returnValue(Promise.resolve(true));
storageProviderSpy.get.and.returnValue(Promise.resolve(sampleIndexResponse));
spyOn(configProvider, 'fetch');
await configProvider.init();
expect(configProvider.fetch).toHaveBeenCalled();
});
it('should update the local config with the one from remote', async () => {
storageProviderSpy.has.and.returnValue(Promise.resolve(true));
storageProviderSpy.get.and.returnValue(Promise.resolve(sampleIndexResponse));
const newConfig = structuredClone(sampleIndexResponse);
newConfig.app.name = 'New app name';
spyOn(configProvider.client, 'handshake').and.returnValue(Promise.resolve(newConfig));
await configProvider.init();
// Validate that the initial configuration is loaded
expect(configProvider.getValue('name')).toEqual(sampleIndexResponse.app.name);
// Fetch the new configuration from the remote
await configProvider.fetch();
// Validate that the new configuration is now set
expect(configProvider.getValue('name')).toEqual(newConfig.app.name);
});
}); });

View File

@@ -83,17 +83,20 @@ export class ConfigProvider {
/** /**
* Fetches configuration from backend * Fetches configuration from backend
*/ */
async fetch(): Promise<SCIndexResponse> { async fetch(): Promise<void> {
try { try {
const isOffline = await firstValueFrom(this.internetConnectionService.offline$); const isOffline = await firstValueFrom(this.internetConnectionService.offline$);
if (isOffline) { if (isOffline) {
throw new Error('Device is offline.'); throw new Error('Device is offline.');
} else { } else {
return await this.client.handshake(this.scVersion); const fetchedConfig: SCIndexResponse = await this.client.handshake(this.scVersion);
await this.set(fetchedConfig);
this.logger.log(`Configuration updated from remote`);
} }
} catch (error) { } catch (error) {
const error_ = error instanceof Error ? new ConfigFetchError(error.message) : new ConfigFetchError(); const error_ = error instanceof Error ? new ConfigFetchError(error.message) : new ConfigFetchError();
throw error_; this.logger.warn(`Failed to fetch remote configuration:`, error_);
throw error_; // Rethrow the error to handle it in init()
} }
} }
@@ -121,40 +124,33 @@ export class ConfigProvider {
/** /**
* Initialises the ConfigProvider * Initialises the ConfigProvider
* @throws ConfigInitError if no configuration could be loaded. * @throws ConfigInitError if no configuration could be loaded both locally and remote.
* @throws WrongConfigVersionInStorage if fetch failed and saved config has wrong SCVersion
*/ */
async init(): Promise<void> { async init(): Promise<void> {
let loadError;
let fetchError;
// load saved configuration
try { try {
// Attempt to load the configuration from local storage
this.config = await this.loadLocal(); this.config = await this.loadLocal();
this.firstSession = false; this.firstSession = false;
this.logger.log(`initialised configuration from storage`); this.logger.log(`initialised configuration from storage`);
// Check if the stored configuration has the correct version
if (this.config.backend.SCVersion.split('.')[0] !== this.scVersion.split('.')[0]) { if (this.config.backend.SCVersion.split('.')[0] !== this.scVersion.split('.')[0]) {
loadError = new WrongConfigVersionInStorage(this.scVersion, this.config.backend.SCVersion); throw new WrongConfigVersionInStorage(this.scVersion, this.config.backend.SCVersion);
} }
} catch (error) {
loadError = error; // Fetch the remote configuration in a non-blocking manner
} void this.fetch();
// fetch remote configuration from backend } catch (loadError) {
this.logger.warn(loadError);
try { try {
const fetchedConfig: SCIndexResponse = await this.fetch(); // If local loading fails, immediately try to fetch the configuration from remote
await this.set(fetchedConfig); await this.fetch();
this.logger.log(`initialised configuration from remote`); } catch (fetchError) {
} catch (error) { this.logger.warn(`Failed to fetch remote configuration:`, fetchError);
fetchError = error; // If both local loading and remote fetching fail, throw ConfigInitError
}
// check for occurred errors and throw them
if (loadError !== undefined && fetchError !== undefined) {
throw new ConfigInitError(); throw new ConfigInitError();
} }
if (loadError !== undefined) {
this.logger.warn(loadError);
}
if (fetchError !== undefined) {
this.logger.warn(fetchError);
} }
} }