From f44e307ed6abd4f35a3aab0f8425d3528fbd5da3 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 9 May 2025 18:47:25 -0300 Subject: [PATCH 1/3] Bug fixes --- CHANGES.txt | 2 +- .../matchers/__tests__/rbsegment.spec.ts | 18 ++++++++++++++++-- src/evaluator/matchers/rbsegment.ts | 3 +++ src/services/splitHttpClient.ts | 4 +++- .../polling/fetchers/splitChangesFetcher.ts | 6 +++--- 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index ebe50d6b..24a9471e 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,4 @@ -2.3.0 (May XXX, 2025) +2.3.0 (May 12, 2025) - Added support for targeting rules based on rule-based segments. - Updated the Redis storage to: - Avoid lazy require of the `ioredis` dependency when the SDK is initialized, and diff --git a/src/evaluator/matchers/__tests__/rbsegment.spec.ts b/src/evaluator/matchers/__tests__/rbsegment.spec.ts index 32b8f843..4ab02438 100644 --- a/src/evaluator/matchers/__tests__/rbsegment.spec.ts +++ b/src/evaluator/matchers/__tests__/rbsegment.spec.ts @@ -94,8 +94,8 @@ const STORED_RBSEGMENTS: Record = { name: 'depend_on_always_on', changeNumber: 123, status: 'ACTIVE', - excluded: { - keys: [], + excluded: { // @ts-ignore + keys: null, segments: [] }, conditions: [{ @@ -167,6 +167,12 @@ const STORED_RBSEGMENTS: Record = { } } ], + }, + 'rule_based_segment_without_conditions': { + name: 'rule_based_segment_without_conditions', + changeNumber: 123, + status: 'ACTIVE', + conditions: [] } }; @@ -291,6 +297,14 @@ describe.each([ // should support feature flag dependency matcher expect(await matcherTrueAlwaysOn({ key: 'a-key' }, evaluateFeature)).toBe(true); // Parent split returns one of the expected treatments, so the matcher returns true + + const matcherTrueRuleBasedSegmentWithoutConditions = matcherFactory(loggerMock, { + type: matcherTypes.IN_RULE_BASED_SEGMENT, + value: 'rule_based_segment_without_conditions' + } as IMatcherDto, mockStorageSync)!; + + // should support rule-based segment without conditions + expect(await matcherTrueRuleBasedSegmentWithoutConditions({ key: 'a-key' }, evaluateFeature)).toBe(false); }); }); diff --git a/src/evaluator/matchers/rbsegment.ts b/src/evaluator/matchers/rbsegment.ts index 3e974fde..2bab7388 100644 --- a/src/evaluator/matchers/rbsegment.ts +++ b/src/evaluator/matchers/rbsegment.ts @@ -15,6 +15,9 @@ export function ruleBasedSegmentMatcherContext(segmentName: string, storage: ISt function matchConditions(rbsegment: IRBSegment) { const conditions = rbsegment.conditions || []; + + if (!conditions.length) return false; + const evaluator = parser(log, conditions, storage); const evaluation = evaluator( diff --git a/src/services/splitHttpClient.ts b/src/services/splitHttpClient.ts index 19329dc2..3a6b3594 100644 --- a/src/services/splitHttpClient.ts +++ b/src/services/splitHttpClient.ts @@ -4,6 +4,7 @@ import { ERROR_HTTP, ERROR_CLIENT_CANNOT_GET_READY } from '../logger/constants'; import { ISettings } from '../types'; import { IPlatform } from '../sdkFactory/types'; import { decorateHeaders, removeNonISO88591 } from './decorateHeaders'; +import { timeout } from '../utils/promise/timeout'; const messageNoFetch = 'Global fetch API is not available.'; @@ -45,7 +46,8 @@ export function splitHttpClientFactory(settings: ISettings, { getOptions, getFet // https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful .then(response => { if (!response.ok) { - return response.text().then(message => Promise.reject({ response, message })); + // `text()` promise might not settle in some implementations and cases (e.g. no content) + return timeout(100, response.text()).then(message => Promise.reject({ response, message }), () => Promise.reject({ response })); } latencyTracker(); return response; diff --git a/src/sync/polling/fetchers/splitChangesFetcher.ts b/src/sync/polling/fetchers/splitChangesFetcher.ts index c0fb9816..8f7ab143 100644 --- a/src/sync/polling/fetchers/splitChangesFetcher.ts +++ b/src/sync/polling/fetchers/splitChangesFetcher.ts @@ -10,7 +10,7 @@ import { LOG_PREFIX_SYNC_SPLITS } from '../../../logger/constants'; const PROXY_CHECK_INTERVAL_MILLIS_CS = 60 * 60 * 1000; // 1 hour in Client Side const PROXY_CHECK_INTERVAL_MILLIS_SS = 24 * PROXY_CHECK_INTERVAL_MILLIS_CS; // 24 hours in Server Side -function sdkEndpointOverriden(settings: ISettings) { +function sdkEndpointOverridden(settings: ISettings) { return settings.urls.sdk !== base.urls.sdk; } @@ -42,8 +42,8 @@ export function splitChangesFetcherFactory(fetchSplitChanges: IFetchSplitChanges let splitsPromise = fetchSplitChanges(since, noCache, till, settings.sync.flagSpecVersion === FLAG_SPEC_VERSION ? rbSince : undefined) // Handle proxy error with spec 1.3 .catch((err) => { - if (err.statusCode === 400 && sdkEndpointOverriden(settings) && settings.sync.flagSpecVersion === FLAG_SPEC_VERSION) { - log.error(LOG_PREFIX_SYNC_SPLITS + 'Proxy error detected. If you are using Split Proxy, please upgrade to latest version'); + if (err.statusCode === 400 && sdkEndpointOverridden(settings) && settings.sync.flagSpecVersion === FLAG_SPEC_VERSION) { + log.error(LOG_PREFIX_SYNC_SPLITS + 'Proxy error detected. Retrying with spec 1.2. If you are using Split Proxy, please upgrade to latest version'); lastProxyCheckTimestamp = Date.now(); settings.sync.flagSpecVersion = '1.2'; // fallback to 1.2 spec return fetchSplitChanges(since, noCache, till); // retry request without rbSince From 2a0851c30861ff13b936bd40e26ec5f36926b8c8 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 9 May 2025 18:55:34 -0300 Subject: [PATCH 2/3] Polishing --- src/evaluator/matchers/__tests__/rbsegment.spec.ts | 4 ++-- src/services/splitHttpClient.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/evaluator/matchers/__tests__/rbsegment.spec.ts b/src/evaluator/matchers/__tests__/rbsegment.spec.ts index 4ab02438..9c991ad7 100644 --- a/src/evaluator/matchers/__tests__/rbsegment.spec.ts +++ b/src/evaluator/matchers/__tests__/rbsegment.spec.ts @@ -95,8 +95,8 @@ const STORED_RBSEGMENTS: Record = { changeNumber: 123, status: 'ACTIVE', excluded: { // @ts-ignore - keys: null, - segments: [] + keys: null, // @ts-ignore + segments: null, }, conditions: [{ matcherGroup: { diff --git a/src/services/splitHttpClient.ts b/src/services/splitHttpClient.ts index 3a6b3594..c81644e7 100644 --- a/src/services/splitHttpClient.ts +++ b/src/services/splitHttpClient.ts @@ -46,7 +46,7 @@ export function splitHttpClientFactory(settings: ISettings, { getOptions, getFet // https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful .then(response => { if (!response.ok) { - // `text()` promise might not settle in some implementations and cases (e.g. no content) + // timeout after 100ms because `text()` promise doesn't settle in some implementations and cases (e.g. no content) return timeout(100, response.text()).then(message => Promise.reject({ response, message }), () => Promise.reject({ response })); } latencyTracker(); From b8bb5df972ba2d0447bc0628578f59a18e2d3b69 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 9 May 2025 18:57:29 -0300 Subject: [PATCH 3/3] Polishing --- src/dtos/types.ts | 4 ++-- src/evaluator/matchers/__tests__/rbsegment.spec.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dtos/types.ts b/src/dtos/types.ts index e38d125a..077bdd0e 100644 --- a/src/dtos/types.ts +++ b/src/dtos/types.ts @@ -210,8 +210,8 @@ export interface IRBSegment { status: 'ACTIVE' | 'ARCHIVED', conditions?: ISplitCondition[], excluded?: { - keys?: string[], - segments?: IExcludedSegment[] + keys?: string[] | null, + segments?: IExcludedSegment[] | null } } diff --git a/src/evaluator/matchers/__tests__/rbsegment.spec.ts b/src/evaluator/matchers/__tests__/rbsegment.spec.ts index 9c991ad7..db597738 100644 --- a/src/evaluator/matchers/__tests__/rbsegment.spec.ts +++ b/src/evaluator/matchers/__tests__/rbsegment.spec.ts @@ -94,8 +94,8 @@ const STORED_RBSEGMENTS: Record = { name: 'depend_on_always_on', changeNumber: 123, status: 'ACTIVE', - excluded: { // @ts-ignore - keys: null, // @ts-ignore + excluded: { + keys: null, segments: null, }, conditions: [{