Skip to content

Commit 220d8ca

Browse files
committed
Introduce ConfigFetched hook
1 parent 648ecc1 commit 220d8ca

6 files changed

+26
-11
lines changed

src/AutoPollConfigService.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { AutoPollOptions } from "./ConfigCatClientOptions";
22
import type { LoggerWrapper } from "./ConfigCatLogger";
3-
import type { IConfigFetcher } from "./ConfigFetcher";
3+
import type { FetchResult, IConfigFetcher } from "./ConfigFetcher";
44
import type { IConfigService, RefreshResult } from "./ConfigServiceBase";
55
import { ClientCacheState, ConfigServiceBase } from "./ConfigServiceBase";
66
import type { ProjectConfig } from "./ProjectConfig";
@@ -115,9 +115,9 @@ export class AutoPollConfigService extends ConfigServiceBase<AutoPollOptions> im
115115
}
116116
}
117117

118-
protected onConfigFetched(newConfig: ProjectConfig): void {
118+
protected onConfigFetched(fetchResult: FetchResult, isInitiatedByUser: boolean): void {
119119
this.signalInitialization();
120-
super.onConfigFetched(newConfig);
120+
super.onConfigFetched(fetchResult, isInitiatedByUser);
121121
}
122122

123123
protected goOnline(): void {
@@ -169,7 +169,7 @@ export class AutoPollConfigService extends ConfigServiceBase<AutoPollOptions> im
169169
// client.dispose();
170170
// ```
171171
if (initialCacheSyncUp ? !this.isOfflineExactly : !this.isOffline) {
172-
await this.refreshConfigCoreAsync(latestConfig);
172+
await this.refreshConfigCoreAsync(latestConfig, false);
173173
return; // postpone signalling initialization until `onConfigFetched`
174174
}
175175
}

src/ConfigServiceBase.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export abstract class ConfigServiceBase<TOptions extends OptionsBase> {
108108
async refreshConfigAsync(): Promise<[RefreshResult, ProjectConfig]> {
109109
const latestConfig = await this.syncUpWithCache();
110110
if (!this.isOffline) {
111-
const [fetchResult, config] = await this.refreshConfigCoreAsync(latestConfig);
111+
const [fetchResult, config] = await this.refreshConfigCoreAsync(latestConfig, true);
112112
return [RefreshResult.from(fetchResult), config];
113113
} else if (this.options.cache instanceof ExternalConfigCache) {
114114
return [RefreshResult.success(), latestConfig];
@@ -118,7 +118,7 @@ export abstract class ConfigServiceBase<TOptions extends OptionsBase> {
118118
}
119119
}
120120

121-
protected async refreshConfigCoreAsync(latestConfig: ProjectConfig): Promise<[FetchResult, ProjectConfig]> {
121+
protected async refreshConfigCoreAsync(latestConfig: ProjectConfig, isInitiatedByUser: boolean): Promise<[FetchResult, ProjectConfig]> {
122122
const fetchResult = await this.fetchAsync(latestConfig);
123123

124124
let configChanged = false;
@@ -131,7 +131,7 @@ export abstract class ConfigServiceBase<TOptions extends OptionsBase> {
131131
latestConfig = fetchResult.config;
132132
}
133133

134-
this.onConfigFetched(fetchResult.config);
134+
this.onConfigFetched(fetchResult, isInitiatedByUser);
135135

136136
if (configChanged) {
137137
this.onConfigChanged(fetchResult.config);
@@ -140,8 +140,9 @@ export abstract class ConfigServiceBase<TOptions extends OptionsBase> {
140140
return [fetchResult, latestConfig];
141141
}
142142

143-
protected onConfigFetched(newConfig: ProjectConfig): void {
143+
protected onConfigFetched(fetchResult: FetchResult, isInitiatedByUser: boolean): void {
144144
this.options.logger.debug("config fetched");
145+
this.options.hooks.emit("configFetched", RefreshResult.from(fetchResult), isInitiatedByUser);
145146
}
146147

147148
protected onConfigChanged(newConfig: ProjectConfig): void {

src/Hooks.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { ClientCacheState } from "./ConfigServiceBase";
1+
import type { ClientCacheState, RefreshResult } from "./ConfigServiceBase";
22
import type { IEventEmitter, IEventProvider } from "./EventEmitter";
33
import { NullEventEmitter } from "./EventEmitter";
44
import type { IConfig } from "./ProjectConfig";
@@ -19,6 +19,10 @@ export type HookEvents = {
1919
clientReady: [cacheState: ClientCacheState];
2020
/** Occurs after the value of a feature flag of setting has been evaluated. */
2121
flagEvaluated: [evaluationDetails: IEvaluationDetails];
22+
/**
23+
* Occurs after attempting to refresh the locally cached config by fetching the latest version from the remote server.
24+
*/
25+
configFetched: [result: RefreshResult, isInitiatedByUser: boolean];
2226
/**
2327
* Occurs after the locally cached config has been updated to a newer version, either as a result of synchronization
2428
* with the external cache, or as a result of fetching a newer version from the remote server.

src/LazyLoadConfigService.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export class LazyLoadConfigService extends ConfigServiceBase<LazyLoadOptions> im
3232
if (cachedConfig.isExpired(this.cacheTimeToLiveMs)) {
3333
if (!this.isOffline) {
3434
logExpired(this.options.logger, ", calling refreshConfigCoreAsync()");
35-
[, cachedConfig] = await this.refreshConfigCoreAsync(cachedConfig);
35+
[, cachedConfig] = await this.refreshConfigCoreAsync(cachedConfig, false);
3636
} else {
3737
logExpired(this.options.logger);
3838
}

test/ConfigCatClientTests.ts

+10
Original file line numberDiff line numberDiff line change
@@ -1355,17 +1355,20 @@ describe("ConfigCatClient", () => {
13551355
for (const addListenersViaOptions of [false, true]) {
13561356
it(`ConfigCatClient should emit events, which listeners added ${addListenersViaOptions ? "via options" : "directly on the client"} should get notified of`, async () => {
13571357
let clientReadyEventCount = 0;
1358+
const configFetchedEvents: [RefreshResult, boolean][] = [];
13581359
const configChangedEvents: IConfig[] = [];
13591360
const flagEvaluatedEvents: IEvaluationDetails[] = [];
13601361
const errorEvents: [string, any][] = [];
13611362

13621363
const handleClientReady = () => clientReadyEventCount++;
1364+
const handleConfigFetched = (result: RefreshResult, isInitiatedByUser: boolean) => configFetchedEvents.push([result, isInitiatedByUser]);
13631365
const handleConfigChanged = (pc: IConfig) => configChangedEvents.push(pc);
13641366
const handleFlagEvaluated = (ed: IEvaluationDetails) => flagEvaluatedEvents.push(ed);
13651367
const handleClientError = (msg: string, err: any) => errorEvents.push([msg, err]);
13661368

13671369
function setupHooks(hooks: IProvidesHooks) {
13681370
hooks.on("clientReady", handleClientReady);
1371+
hooks.on("configFetched", handleConfigFetched);
13691372
hooks.on("configChanged", handleConfigChanged);
13701373
hooks.on("flagEvaluated", handleFlagEvaluated);
13711374
hooks.on("clientError", handleClientError);
@@ -1391,6 +1394,7 @@ describe("ConfigCatClient", () => {
13911394

13921395
assert.equal(state, ClientCacheState.NoFlagData);
13931396
assert.equal(clientReadyEventCount, 1);
1397+
assert.equal(configFetchedEvents.length, 0);
13941398
assert.equal(configChangedEvents.length, 0);
13951399
assert.equal(flagEvaluatedEvents.length, 0);
13961400
assert.equal(errorEvents.length, 0);
@@ -1410,6 +1414,7 @@ describe("ConfigCatClient", () => {
14101414

14111415
await client.forceRefreshAsync();
14121416

1417+
assert.equal(configFetchedEvents.length, 0);
14131418
assert.equal(configChangedEvents.length, 0);
14141419
assert.equal(errorEvents.length, 1);
14151420
const [actualErrorMessage, actualErrorException] = errorEvents[0];
@@ -1422,6 +1427,10 @@ describe("ConfigCatClient", () => {
14221427
await client.forceRefreshAsync();
14231428
const cachedPc = await configCache.get("");
14241429

1430+
assert.equal(configFetchedEvents.length, 1);
1431+
const [refreshResult, isInitiatedByUser] = configFetchedEvents[0];
1432+
assert.isTrue(isInitiatedByUser);
1433+
assert.isTrue(refreshResult.isSuccess);
14251434
assert.equal(configChangedEvents.length, 1);
14261435
assert.strictEqual(configChangedEvents[0], cachedPc.config);
14271436

@@ -1438,6 +1447,7 @@ describe("ConfigCatClient", () => {
14381447
// 5. Client gets disposed
14391448
client.dispose();
14401449

1450+
assert.equal(configFetchedEvents.length, 1);
14411451
assert.equal(clientReadyEventCount, 1);
14421452
assert.equal(configChangedEvents.length, 1);
14431453
assert.equal(evaluationDetails.length, flagEvaluatedEvents.length);

test/DataGovernanceTests.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ export class FakeConfigServiceBase extends ConfigServiceBase<FakeOptions> {
293293
getConfig(): Promise<ProjectConfig> { return Promise.resolve(ProjectConfig.empty); }
294294

295295
refreshLogicAsync(): Promise<[FetchResult, ProjectConfig]> {
296-
return this.refreshConfigCoreAsync(ProjectConfig.empty);
296+
return this.refreshConfigCoreAsync(ProjectConfig.empty, false);
297297
}
298298

299299
prepareResponse(baseUrl: string, jsonBaseUrl: string, jsonRedirect: number, jsonFeatureFlags: any): void {

0 commit comments

Comments
 (0)