Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v2.0.2 #370

Merged
merged 5 commits into from
Dec 3, 2024
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
4 changes: 4 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
2.0.2 (December 3, 2024)
- Updated the factory `init` and `destroy` methods to support re-initialization after destruction. This update ensures compatibility of the React SDK with React Strict Mode, where the factory's `init` and `destroy` effects are executed an extra time to validate proper resource cleanup.
- Bugfixing - Sanitize the `SplitSDKMachineName` header value to avoid exceptions on HTTP/S requests when it contains non ISO-8859-1 characters (Related to issue https://github.com/splitio/javascript-client/issues/847).

2.0.1 (November 25, 2024)
- Bugfixing - Fixed an issue with the SDK_UPDATE event on server-side, where it was not being emitted if there was an empty segment and the SDK received a feature flag update notification.

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@splitsoftware/splitio-commons",
"version": "2.0.1",
"version": "2.0.2",
"description": "Split JavaScript SDK common components",
"main": "cjs/index.js",
"module": "esm/index.js",
Expand Down
31 changes: 15 additions & 16 deletions src/readiness/readinessManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ function segmentsEventEmitterFactory(EventEmitter: new () => SplitIO.IEventEmitt
export function readinessManagerFactory(
EventEmitter: new () => SplitIO.IEventEmitter,
settings: ISettings,
splits: ISplitsEventEmitter = splitsEventEmitterFactory(EventEmitter)): IReadinessManager {
splits: ISplitsEventEmitter = splitsEventEmitterFactory(EventEmitter),
isShared?: boolean
): IReadinessManager {

const readyTimeout = settings.startup.readyTimeout;

Expand Down Expand Up @@ -66,23 +68,26 @@ export function readinessManagerFactory(
gate.emit(SDK_READY_TIMED_OUT, 'Split SDK emitted SDK_READY_TIMED_OUT event.');
}

let readyTimeoutId: ReturnType<typeof setTimeout>;
if (readyTimeout > 0) {
if (splits.hasInit) readyTimeoutId = setTimeout(timeout, readyTimeout);
else splits.initCallbacks.push(() => { readyTimeoutId = setTimeout(timeout, readyTimeout); });
}

// emit SDK_READY and SDK_UPDATE
let isReady = false;
splits.on(SDK_SPLITS_ARRIVED, checkIsReadyOrUpdate);
segments.on(SDK_SEGMENTS_ARRIVED, checkIsReadyOrUpdate);

let isDestroyed = false;
let readyTimeoutId: ReturnType<typeof setTimeout>;
function __init() {
isDestroyed = false;
if (readyTimeout > 0 && !isReady) readyTimeoutId = setTimeout(timeout, readyTimeout);
}

splits.initCallbacks.push(__init);
if (splits.hasInit) __init();

function checkIsReadyFromCache() {
isReadyFromCache = true;
// Don't emit SDK_READY_FROM_CACHE if SDK_READY has been emitted
if (!isReady) {
if (!isReady && !isDestroyed) {
try {
syncLastUpdate();
gate.emit(SDK_READY_FROM_CACHE);
Expand All @@ -94,6 +99,7 @@ export function readinessManagerFactory(
}

function checkIsReadyOrUpdate(diff: any) {
if (isDestroyed) return;
if (isReady) {
try {
syncLastUpdate();
Expand All @@ -117,16 +123,13 @@ export function readinessManagerFactory(
}
}

let refCount = 1;

return {
splits,
segments,
gate,

shared() {
refCount++;
return readinessManagerFactory(EventEmitter, settings, splits);
return readinessManagerFactory(EventEmitter, settings, splits, true);
},

// @TODO review/remove next methods when non-recoverable errors are reworked
Expand All @@ -145,13 +148,9 @@ export function readinessManagerFactory(
destroy() {
isDestroyed = true;
syncLastUpdate();

segments.removeAllListeners();
gate.removeAllListeners();
clearTimeout(readyTimeoutId);

if (refCount > 0) refCount--;
if (refCount === 0) splits.removeAllListeners();
if (!isShared) splits.hasInit = false;
},

isReady() { return isReady; },
Expand Down
1 change: 1 addition & 0 deletions src/sdkFactory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
settings,

destroy() {
hasInit = false;
return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => { });
}
}, extraProps && extraProps(ctx), lazyInit ? { init } : init());
Expand Down
9 changes: 8 additions & 1 deletion src/services/__tests__/decorateHeaders.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ISettings } from '../../types';
import { decorateHeaders } from '../decorateHeaders';
import { decorateHeaders, removeNonISO88591 } from '../decorateHeaders';

const HEADERS = {
Authorization: 'Bearer SDK-KEY',
Expand Down Expand Up @@ -48,3 +48,10 @@ describe('decorateHeaders', () => {
expect(settings.log.error).toHaveBeenCalledWith('Problem adding custom headers to request decorator: Error: Unexpected error');
});
});

test('removeNonISO88591', () => {
expect(removeNonISO88591('')).toBe('');
expect(removeNonISO88591('This is a test')).toBe('This is a test');
expect(removeNonISO88591('This is a test ó \u0FFF 你')).toBe('This is a test ó ');
expect(removeNonISO88591('Emiliano’s-MacBook-Pro')).toBe('Emilianos-MacBook-Pro');
});
5 changes: 5 additions & 0 deletions src/services/decorateHeaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,8 @@ export function decorateHeaders(settings: ISettings, headers: Record<string, str
}
return headers;
}

export function removeNonISO88591(input: string) {
// eslint-disable-next-line no-control-regex
return input.replace(/[^\x00-\xFF]/g, '');
}
4 changes: 2 additions & 2 deletions src/services/splitHttpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { objectAssign } from '../utils/lang/objectAssign';
import { ERROR_HTTP, ERROR_CLIENT_CANNOT_GET_READY } from '../logger/constants';
import { ISettings } from '../types';
import { IPlatform } from '../sdkFactory/types';
import { decorateHeaders } from './decorateHeaders';
import { decorateHeaders, removeNonISO88591 } from './decorateHeaders';

const messageNoFetch = 'Global fetch API is not available.';

Expand All @@ -30,7 +30,7 @@ export function splitHttpClientFactory(settings: ISettings, { getOptions, getFet
};

if (ip) commonHeaders['SplitSDKMachineIP'] = ip;
if (hostname) commonHeaders['SplitSDKMachineName'] = hostname;
if (hostname) commonHeaders['SplitSDKMachineName'] = removeNonISO88591(hostname);

return function httpClient(url: string, reqOpts: IRequestOptions = {}, latencyTracker: (error?: NetworkError) => void = () => { }, logErrorsAsInfo: boolean = false): Promise<IResponse> {

Expand Down
20 changes: 3 additions & 17 deletions src/storages/inLocalStorage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import { KeyBuilderCS, myLargeSegmentsKeyBuilder } from '../KeyBuilderCS';
import { isLocalStorageAvailable } from '../../utils/env/isLocalStorageAvailable';
import { SplitsCacheInLocal } from './SplitsCacheInLocal';
import { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal';
import { MySegmentsCacheInMemory } from '../inMemory/MySegmentsCacheInMemory';
import { SplitsCacheInMemory } from '../inMemory/SplitsCacheInMemory';
import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../../utils/constants/browser';
import { InMemoryStorageCSFactory } from '../inMemory/InMemoryStorageCS';
import { LOG_PREFIX } from './constants';
Expand Down Expand Up @@ -36,7 +34,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
return InMemoryStorageCSFactory(params);
}

const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode, __splitFiltersValidation } } } = params;
const { settings, settings: { log, scheduler: { impressionsQueueSize, eventsQueueSize, }, sync: { impressionsMode } } } = params;
const matchingKey = getMatching(settings.core.key);
const keys = new KeyBuilderCS(prefix, matchingKey);
const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
Expand All @@ -55,15 +53,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,

destroy() {
this.splits = new SplitsCacheInMemory(__splitFiltersValidation);
this.segments = new MySegmentsCacheInMemory();
this.largeSegments = new MySegmentsCacheInMemory();
this.impressions.clear();
this.impressionCounts && this.impressionCounts.clear();
this.events.clear();
this.uniqueKeys?.clear();
},
destroy() { },

// When using shared instantiation with MEMORY we reuse everything but segments (they are customer per key).
shared(matchingKey: string) {
Expand All @@ -77,11 +67,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
events: this.events,
telemetry: this.telemetry,

destroy() {
this.splits = new SplitsCacheInMemory(__splitFiltersValidation);
this.segments = new MySegmentsCacheInMemory();
this.largeSegments = new MySegmentsCacheInMemory();
}
destroy() { }
};
},
};
Expand Down
10 changes: 1 addition & 9 deletions src/storages/inMemory/InMemoryStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,7 @@ export function InMemoryStorageFactory(params: IStorageFactoryParams): IStorageS
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemory() : undefined,

// When using MEMORY we should clean all the caches to leave them empty
destroy() {
this.splits.clear();
this.segments.clear();
this.impressions.clear();
this.impressionCounts && this.impressionCounts.clear();
this.events.clear();
this.uniqueKeys && this.uniqueKeys.clear();
}
destroy() { }
};

// @TODO revisit storage logic in localhost mode
Expand Down
18 changes: 2 additions & 16 deletions src/storages/inMemory/InMemoryStorageCS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
telemetry: shouldRecordTelemetry(params) ? new TelemetryCacheInMemory(splits, segments) : undefined,
uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined,

// When using MEMORY we should clean all the caches to leave them empty
destroy() {
this.splits.clear();
this.segments.clear();
this.largeSegments.clear();
this.impressions.clear();
this.impressionCounts && this.impressionCounts.clear();
this.events.clear();
this.uniqueKeys && this.uniqueKeys.clear();
},
destroy() { },

// When using shared instantiation with MEMORY we reuse everything but segments (they are unique per key)
shared() {
Expand All @@ -52,12 +43,7 @@ export function InMemoryStorageCSFactory(params: IStorageFactoryParams): IStorag
events: this.events,
telemetry: this.telemetry,

// Set a new splits cache to clean it for the client without affecting other clients
destroy() {
this.splits = new SplitsCacheInMemory(__splitFiltersValidation);
this.segments.clear();
this.largeSegments.clear();
}
destroy() { }
};
},
};
Expand Down
Loading