Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,8 @@ await init({
- **Kept on disk.** The last configuration accepted is kept in the application's `userData`
directory. A graceful exit retries pending or failed writes; a forced termination can leave an
older cache. New sessions use it before the network answers, or without the network at all. It
applies only to the same intake, application, `env` and `version`.
applies only to the same intake, application and `env`; an application upgrade keeps it, so the
first session after one is drawn on the previous release's settings until a new answer is stored.
- **Resumed sessions.** A valid session resumed from the previous launch keeps its original draw,
subject to immediate activation: when what was kept asks to apply immediately, the session is
judged by it at startup, as below, unless it was drawn under a newer version than the one kept —
Expand Down
42 changes: 37 additions & 5 deletions src/domain/RemoteConfiguration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ function storedFile(overrides: Record<string, unknown> = {}) {
return JSON.stringify({
format: 1,
sdkVersion: 'test',
identity: JSON.stringify(['http://proxy.test', 'test-app-id', 'prod', '1.2.3']),
identity: JSON.stringify(['http://proxy.test', 'test-app-id', 'prod']),
version: 2,
values: { sessionSampleRate: 0, sessionOnError: true },
custom: { flag: 'cached' },
Expand Down Expand Up @@ -473,7 +473,7 @@ describe('RemoteConfiguration', () => {
{
format: 1,
sdkVersion: 'test',
identity: JSON.stringify(['http://proxy.test', 'test-app-id', 'prod', '1.2.3']),
identity: JSON.stringify(['http://proxy.test', 'test-app-id', 'prod']),
version: 3,
values: { sessionSampleRate: 25, sessionOnError: true },
custom: { flag: 1 },
Expand All @@ -497,9 +497,13 @@ describe('RemoteConfiguration', () => {
});

it.each([
['another env', { identity: JSON.stringify(['http://proxy.test', 'test-app-id', 'staging', '1.2.3']) }],
['another app version', { identity: JSON.stringify(['http://proxy.test', 'test-app-id', 'prod', '1.2.2']) }],
['another application', { identity: JSON.stringify(['http://proxy.test', 'other-app', 'prod', '1.2.3']) }],
['another env', { identity: JSON.stringify(['http://proxy.test', 'test-app-id', 'staging']) }],
['another intake', { identity: JSON.stringify(['http://other-proxy.test', 'test-app-id', 'prod']) }],
[
'a build that keyed it on the app version',
{ identity: JSON.stringify(['http://proxy.test', 'test-app-id', 'prod', '1.2.3']) },
],
['another application', { identity: JSON.stringify(['http://proxy.test', 'other-app', 'prod']) }],
['another file format', { format: 2 }],
['a rate out of range', { values: { sessionSampleRate: 200 } }],
['a switch that is not a boolean', { values: { sessionOnError: 'true' } }],
Expand All @@ -516,6 +520,34 @@ describe('RemoteConfiguration', () => {
expect(requestHeaders()).toEqual({});
});

it('applies a file written under another app version', async () => {
config = { ...config, version: '2.0.0' };
mfs.readFile.mockResolvedValue(storedFile());
fetchMock.mockReturnValue(new Promise(() => undefined));

const configuration = await start();

expect(configuration.getSampling()).toEqual({ sessionSampleRate: 0, sessionOnError: true, rcVersion: 2 });
expect(requestedUrl().searchParams.get('app_version')).toBe('2.0.0');
expect(requestedUrl().searchParams.get('applied_version')).toBe('2');
});

it('writes the same identity whatever the app version', async () => {
fetchMock.mockImplementation(() => Promise.resolve(ok(configurationBody())));
const identityWritten = async (version: string | undefined) => {
writtenFiles().length = 0;
mfs.writeFile.mockClear();
config = { ...config, version };
remote?.stop();
await start();
return (writtenFiles()[0] as { identity: string }).identity;
};

const first = await identityWritten('1.2.3');
expect(await identityWritten('2.0.0')).toBe(first);
expect(await identityWritten(undefined)).toBe(first);
});

it('reads a kept version 0 as no configuration, whatever it holds', async () => {
mfs.readFile.mockResolvedValue(storedFile({ version: 0, values: { sessionSampleRate: 0 } }));
fetchMock.mockReturnValue(new Promise(() => undefined));
Expand Down
11 changes: 7 additions & 4 deletions src/domain/RemoteConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ interface StoredConfiguration extends Omit<Delivered, 'activation' | 'refreshOnF
* console's values rather than back on the init ones.
*/
sdkVersion: string;
/** Who the answer was for: it only applies to the same endpoint, application, env and version. */
/** Who the answer was for: it only applies to the same endpoint, application and env, whatever the application version. */
identity: string;
etag?: string;
}
Expand Down Expand Up @@ -153,9 +153,12 @@ export class RemoteConfiguration {
private stopped = false;

private constructor(private readonly config: Configuration) {
// The client token is not part of it: the configuration belongs to the application, so a token
// rotated for the same application, env and version is answered the same.
this.identity = JSON.stringify([config.proxy ?? config.site, config.applicationId, config.env, config.version]);
// Neither the client token nor the application version is part of it. The configuration belongs
// to the application, so a token rotated for the same application and env is answered the same.
// An installed application runs one version at a time, and keying on it would put the first
// session after every upgrade back on the init values: until the new fetch is stored, the
// previous release's settings are the better answer.
this.identity = JSON.stringify([config.proxy ?? config.site, config.applicationId, config.env]);
}

/** Reads the configuration the previous launch kept, if remote configuration is on. */
Expand Down
Loading