@kameleoon/javascript-sdk-core
Version:
Kameleoon JS SDK Core
1,225 lines (1,188 loc) • 374 kB
JavaScript
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
function getAugmentedNamespace(n) {
if (Object.prototype.hasOwnProperty.call(n, '__esModule')) return n;
var f = n.default;
if (typeof f == "function") {
var a = function a () {
var isInstance = false;
try {
isInstance = this instanceof a;
} catch {}
if (isInstance) {
return Reflect.construct(f, arguments, this.constructor);
}
return f.apply(this, arguments);
};
a.prototype = f.prototype;
} else a = {};
Object.defineProperty(a, '__esModule', {value: true});
Object.keys(n).forEach(function (k) {
var d = Object.getOwnPropertyDescriptor(n, k);
Object.defineProperty(a, k, d.get ? d : {
enumerable: true,
get: function () {
return n[k];
}
});
});
return a;
}
var build = {};
var main = {};
var hasRequiredMain;
function requireMain () {
if (hasRequiredMain) return main;
hasRequiredMain = 1;
Object.defineProperty(main, "__esModule", { value: true });
main.Err = main.Ok = void 0;
// --- Note ---
// `throw` is a reserved keyword
function throwErr(message) {
if (!this.ok) {
if (typeof this.error === "string" || typeof this.error === "undefined") {
var defaultMessage = "There was an error! No specific error message was provided.";
throw new Error(message || this.error || defaultMessage);
}
if (message) {
this.error.message = message;
}
throw this.error;
}
return this.data;
}
// --- Note ---
// `else` is a reserved keyword
function elseDo(callback) {
if (this.ok) {
return this.data;
}
// TODO:
// - Tackle the problem of callback return type not being enforced when using `void` | `undefined` as `T`
// - In the same situation `or` works well
return callback(this.error);
}
function or(orValue) {
if (this.ok) {
return this.data;
}
return orValue;
}
function and(callback) {
if (this.ok) {
callback(this.data);
}
return this;
}
function Ok(data) {
return { ok: true, data: data, throw: throwErr, else: elseDo, or: or, and: and };
}
main.Ok = Ok;
function Err(error) {
return { ok: false, error: error, throw: throwErr, else: elseDo, or: or, and: and };
}
main.Err = Err;
return main;
}
var hasRequiredBuild;
function requireBuild () {
if (hasRequiredBuild) return build;
hasRequiredBuild = 1;
(function (exports) {
Object.defineProperty(exports, "__esModule", { value: true });
exports.Err = exports.Ok = void 0;
var main_1 = requireMain();
Object.defineProperty(exports, "Ok", { enumerable: true, get: function () { return main_1.Ok; } });
Object.defineProperty(exports, "Err", { enumerable: true, get: function () { return main_1.Err; } });
} (build));
return build;
}
var buildExports = requireBuild();
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
/**
* @readonly
* @enum {string} an Enum containing all possible variants of exception types `KameleoonError`
* */
var KameleoonException;
(function (KameleoonException) {
KameleoonException["Credentials"] = "Credentials";
KameleoonException["EventSourceInitialization"] = "EventSourceInitialization";
KameleoonException["FeatureFlagConfigurationNotFound"] = "FeatureFlagConfigurationNotFound";
KameleoonException["FeatureFlagVariableNotFound"] = "FeatureFlagVariableNotFound";
KameleoonException["FeatureFlagVariationNotFound"] = "FeatureFlagVariationNotFound";
KameleoonException["FeatureFlagExperimentNotFound"] = "FeatureFlagExperimentNotFound";
KameleoonException["FeatureFlagEnvironmentDisabled"] = "FeatureFlagEnvironmentDisabled";
KameleoonException["VisitAmount"] = "VisitAmount";
KameleoonException["VisitorCodeMaxLength"] = "VisitorCodeMaxLength";
KameleoonException["VisitorCodeEmpty"] = "VisitorCodeEmpty";
KameleoonException["StorageInitialization"] = "StorageInitialization";
KameleoonException["StorageWrite"] = "StorageWrite";
KameleoonException["StorageRead"] = "StorageRead";
KameleoonException["StorageParse"] = "StorageParse";
KameleoonException["StorageEmpty"] = "StorageEmpty";
KameleoonException["ClientConfiguration"] = "ClientConfiguration";
KameleoonException["TargetingCondition"] = "TargetingCondition";
KameleoonException["AmongValuesCheck"] = "AmongValuesCheck";
KameleoonException["RangeCheck"] = "RangeCheck";
KameleoonException["Initialization"] = "Initialization";
KameleoonException["JSONParse"] = "JSONParse";
KameleoonException["NumberParse"] = "NumberParse";
KameleoonException["VersionParse"] = "VersionParse";
KameleoonException["CookieParse"] = "CookieParse";
KameleoonException["SemanticVersionParse"] = "SemanticVersionParse";
KameleoonException["RemoteData"] = "RemoteData";
KameleoonException["MaximumRetriesReached"] = "MaximumRetriesReached";
})(KameleoonException || (KameleoonException = {}));
const ERROR_MESSAGES = {
[KameleoonException.CookieParse]: (reason) => `Couldn't parse cookie string: ${reason}`,
[KameleoonException.JSONParse]: (err) => `Couldn't parse JSON variable: ${err}`,
[KameleoonException.NumberParse]: (value) => `It's not possible to parse value ${value} to Number`,
[KameleoonException.VersionParse]: (value) => `It's not possible to parse a version value ${value} to Number, version should be in format x.x`,
[KameleoonException.SemanticVersionParse]: (value) => `It's not possible to parse a version value ${value} to Number, version should be in format x.x.x`,
[KameleoonException.Initialization]: () => "It seems that the client wasn't properly initialized, make sure to run `initialize` method before invoking other methods",
[KameleoonException.Credentials]: () => 'KameleoonClient can not be created without credentials',
[KameleoonException.StorageInitialization]: () => "There was an error while initializing React Native SDK storage, it seems that the storage library dependency wasn't installed",
[KameleoonException.EventSourceInitialization]: () => "There was an error while initializing Real Time Update service, it seems that the event source library dependency wasn't installed",
[KameleoonException.FeatureFlagConfigurationNotFound]: (featureKey) => `No feature flag with key ${featureKey} was found.`,
[KameleoonException.FeatureFlagEnvironmentDisabled]: (featureKey, environment) => `Feature flag with key ${featureKey} is disabled in ${environment} environment.`,
[KameleoonException.FeatureFlagVariableNotFound]: (variableKey, visitorCode) => `No feature flag variable with key ${variableKey} was found for ${visitorCode} visitorCode.`,
[KameleoonException.FeatureFlagVariationNotFound]: (variationKey, visitorCode) => `No feature flag variation with key ${variationKey} was found for ${visitorCode} visitorCode.`,
[KameleoonException.FeatureFlagExperimentNotFound]: (experimentId, visitorCode) => `No feature flag experiment with id ${experimentId} was found for ${visitorCode} visitorCode.`,
[KameleoonException.VisitAmount]: () => 'Visit amount must be a number between 1 and 25',
[KameleoonException.VisitorCodeMaxLength]: () => 'Visitor code can not be more than 255 characters long',
[KameleoonException.VisitorCodeEmpty]: () => 'Visitor code can not be empty',
[KameleoonException.StorageWrite]: (err) => `Couldn't update storage for kameleoonClient: ${err}`,
[KameleoonException.StorageRead]: (key) => `No data found in storage under ${key} key`,
[KameleoonException.StorageEmpty]: () => 'No data found in storage',
[KameleoonException.StorageParse]: (err, key) => `Couldn't parse ${key} storage data, the data may be corrupted. Error: ${err}`,
[KameleoonException.ClientConfiguration]: (err) => `Couldn't retrieve client configuration from Kameleoon Api. Error: ${err}`,
[KameleoonException.TargetingCondition]: (targetingType) => `${targetingType} targeting condition is not yet supported.`,
[KameleoonException.AmongValuesCheck]: (err, value) => `Couldn't parse value "${value}": ${err}`,
[KameleoonException.RangeCheck]: (value) => `Couldn't parse value "${value}" as a range, value should be in format [x: number, y: number]`,
[KameleoonException.RemoteData]: (err) => `Couldn't retrieve data from Kameleoon server. Error: ${err}`,
[KameleoonException.MaximumRetriesReached]: (err) => `Maximum retries reached, request failed. Reason: ${err}`,
};
function exhaustCheck(option) {
throw new Error(`Reaching an impossible state because of ${option}`);
}
/**
* @class
* KameleoonError - extends standard `Error` by adding custom `type` property with the type of `KameleoonException`
* */
class KameleoonError extends Error {
constructor(type, secondParam, thirdParam) {
super(`Error: ${type}`);
this.name = 'KameleoonError';
this.errorType = type;
switch (type) {
case KameleoonException.Initialization:
case KameleoonException.Credentials:
case KameleoonException.VisitorCodeMaxLength:
case KameleoonException.VisitorCodeEmpty:
case KameleoonException.StorageInitialization:
case KameleoonException.VisitAmount:
case KameleoonException.EventSourceInitialization:
case KameleoonException.StorageEmpty:
this.message = ERROR_MESSAGES[type]();
break;
case KameleoonException.CookieParse:
case KameleoonException.FeatureFlagConfigurationNotFound:
case KameleoonException.NumberParse:
case KameleoonException.ClientConfiguration:
case KameleoonException.MaximumRetriesReached:
case KameleoonException.RemoteData:
case KameleoonException.VersionParse:
case KameleoonException.SemanticVersionParse:
this.message = ERROR_MESSAGES[type](secondParam);
break;
case KameleoonException.FeatureFlagExperimentNotFound:
this.message = ERROR_MESSAGES[type](secondParam, thirdParam);
break;
case KameleoonException.FeatureFlagVariationNotFound:
case KameleoonException.FeatureFlagVariableNotFound:
this.message = ERROR_MESSAGES[type](secondParam, thirdParam);
break;
case KameleoonException.FeatureFlagEnvironmentDisabled:
if (thirdParam !== undefined) {
this.message = ERROR_MESSAGES[type](secondParam, thirdParam);
}
else {
this.message = secondParam;
}
break;
case KameleoonException.StorageWrite:
case KameleoonException.JSONParse:
this.message = ERROR_MESSAGES[type](secondParam);
break;
case KameleoonException.StorageRead:
this.message = ERROR_MESSAGES[type](secondParam);
break;
case KameleoonException.StorageParse:
this.message = ERROR_MESSAGES[type](secondParam, thirdParam);
break;
case KameleoonException.TargetingCondition:
this.message = ERROR_MESSAGES[type](secondParam);
break;
case KameleoonException.AmongValuesCheck:
this.message = ERROR_MESSAGES[type](secondParam, thirdParam);
break;
case KameleoonException.RangeCheck:
this.message = ERROR_MESSAGES[type](secondParam);
break;
default:
exhaustCheck(type);
}
}
get type() {
return this.errorType;
}
}
var UrlType;
(function (UrlType) {
UrlType["DataApi"] = "dataApi";
UrlType["Events"] = "events";
UrlType["ClientConfiguration"] = "clientConfiguration";
})(UrlType || (UrlType = {}));
var HttpMethod;
(function (HttpMethod) {
HttpMethod["Get"] = "GET";
HttpMethod["Post"] = "POST";
})(HttpMethod || (HttpMethod = {}));
var DataApiQuery;
(function (DataApiQuery) {
DataApiQuery[DataApiQuery["VisitEvent"] = 0] = "VisitEvent";
DataApiQuery[DataApiQuery["VisitData"] = 1] = "VisitData";
DataApiQuery[DataApiQuery["DataMap"] = 2] = "DataMap";
})(DataApiQuery || (DataApiQuery = {}));
/**
* @enum `RequestType` - an enum of available request types
* */
var RequestType;
(function (RequestType) {
RequestType["Configuration"] = "configuration";
RequestType["Tracking"] = "tracking";
RequestType["RemoteData"] = "remoteData";
})(RequestType || (RequestType = {}));
const NUMBER_OF_RETRIES = 1;
var Header;
(function (Header) {
Header["UserAgent"] = "User-Agent";
Header["ContentType"] = "Content-Type";
Header["SdkVersion"] = "X-Kameleoon-SDK-Version";
Header["SdkType"] = "X-Kameleoon-SDK-Type";
Header["Authorization"] = "Authorization";
Header["AcceptEncoding"] = "Accept-Encoding";
Header["IfModifiedSince"] = "If-Modified-Since";
Header["LastModified"] = "Last-Modified";
})(Header || (Header = {}));
const UrlEventType = {
CustomData: 'eventType=customData',
StaticData: 'eventType=staticData',
Page: 'eventType=page',
Conversion: 'eventType=conversion',
Activity: 'eventType=activity',
Experiment: 'eventType=experiment',
Geolocation: 'eventType=geolocation',
TargetingSegment: 'eventType=targetingSegment',
};
const UrlParameter = {
Title: '&title=',
ReferrersIndices: '&referrersIndices=',
Negative: '&negative=',
Revenue: '&revenue=',
Overwrite: '&overwrite=',
Index: '&index=',
BrowserIndex: '&browserIndex=',
BrowserVersion: '&browserVersion=',
Href: '&href=',
DeviceType: '&deviceType=',
GoalId: '&goalId=',
VisitorCode: '&visitorCode=',
VariationId: '&variationId=',
Ts: '&ts=',
Key: '&key=',
SdkName: '&sdkName=',
SdkVersion: '&sdkVersion=',
ValuesCountMap: '&valuesCountMap=',
Nonce: '&nonce=',
Id: '&id=',
CustomData: '&customData=',
CurrentVisit: '¤tVisit=',
MaxNumberPreviousVisits: '&maxNumberPreviousVisits=',
Os: '&os=',
OsIndex: '&osIndex=',
Country: '&country=',
City: '&city=',
Region: '®ion=',
Latitude: '&latitude=',
Longitude: '&longitude=',
PostalCode: '&postalCode=',
Conversion: '&conversion=',
StaticData: '&staticData=',
Geolocation: '&geolocation=',
Page: '&page=',
Experiment: '&experiment=',
Browser: '&browser=',
MappingIdentifier: '&mappingIdentifier=',
MappingValue: '&mappingValue=',
Kcs: '&kcs=',
Personalization: '&personalization=',
UserAgent: '&userAgent=',
BodyUa: '&bodyUa=',
Cbs: '&cbs=',
Metadata: '&metadata=',
VisitNumber: '&visitNumber=',
TimeSincePreviousVisit: '&timeSincePreviousVisit=',
};
const UrlFirstParameter = {
Environment: '?environment=',
Ts: '?ts='};
const UrlQuery = {
Sse: 'sse?siteCode=',
Map: 'map?siteCode=',
Events: 'events?siteCode=',
Visitor: 'visitor?siteCode=',
};
const UrlTracking = {
Visit: 'visit/',
Map: 'map/',
};
const DEFAULT_DOMAINS = {
[UrlType.DataApi]: 'data.kameleoon.io',
[UrlType.Events]: 'events.kameleoon.eu',
[UrlType.ClientConfiguration]: 'sdk-config.kameleoon.eu',
};
/**
* Enumeration of log levels.
*/
var LogLevel;
(function (LogLevel) {
LogLevel[LogLevel["NONE"] = 0] = "NONE";
LogLevel[LogLevel["ERROR"] = 1] = "ERROR";
LogLevel[LogLevel["WARNING"] = 2] = "WARNING";
LogLevel[LogLevel["INFO"] = 3] = "INFO";
LogLevel[LogLevel["DEBUG"] = 4] = "DEBUG";
})(LogLevel || (LogLevel = {}));
/**
* Mapping of log levels to their string representations.
*/
const LOG_LEVEL_NAMES = {
[LogLevel.NONE]: 'NONE',
[LogLevel.ERROR]: 'ERROR',
[LogLevel.WARNING]: 'WARNING',
[LogLevel.INFO]: 'INFO',
[LogLevel.DEBUG]: 'DEBUG',
};
/**
* A simple implementation of a logger that prints log messages to the console. This logger
* implements the {@code IExternalLogger} interface.
*/
class DefaultLogger {
/**
* @method log - logs a message at the specified log level.
*
* @param {LogLevel} level - the log level
* @param {string} message - the log message
*/
log(level, message) {
switch (level) {
case LogLevel.DEBUG:
console.debug(message);
break;
case LogLevel.INFO:
console.info(message);
break;
case LogLevel.WARNING:
console.warn(message);
break;
case LogLevel.ERROR:
console.error(message);
break;
}
}
}
const replacer = (key, value) => {
if (key === 'credentials') {
return {
clientId: `****`,
clientSecret: `****`,
};
}
if (value instanceof Map) {
return Array.from(value.entries());
}
return value;
};
function template(strings, keys) {
let res = '';
if (keys) {
for (let i = 0; i < keys.length; i++) {
let key = keys[i];
if (typeof key === 'object') {
try {
res += strings[i] + JSON.stringify(key, replacer);
}
catch (error) {
res += strings[i] + "{object}";
}
}
else if (typeof key === 'string') {
res += strings[i] + "'" + key + "'";
}
else {
res += strings[i] + key;
}
}
}
res += strings[strings.length - 1];
return res;
}
class KameleoonLogger {
static setLogger(logger) {
KameleoonLogger.logger = logger;
}
static setLogLevel(logLevel) {
KameleoonLogger.logLevel = logLevel;
}
static log({ level, strings, keys }) {
if (!KameleoonLogger.checkLevel(level))
return;
let message;
if (typeof strings === 'string') {
message = strings;
}
else if (typeof strings === 'function') {
try {
message = strings();
}
catch (error) {
message = 'Failed to log message';
}
}
else {
message = template(strings, keys);
}
KameleoonLogger.writeMessage(level, message);
}
static info(strings, ...keys) {
KameleoonLogger.log({ level: LogLevel.INFO, strings, keys });
}
static error(strings, ...keys) {
KameleoonLogger.log({ level: LogLevel.ERROR, strings, keys });
}
static warning(strings, ...keys) {
KameleoonLogger.log({ level: LogLevel.WARNING, strings, keys });
}
static debug(strings, ...keys) {
KameleoonLogger.log({ level: LogLevel.DEBUG, strings, keys });
}
static checkLevel(level) {
return level <= KameleoonLogger.logLevel && level !== LogLevel.NONE;
}
static writeMessage(level, message) {
KameleoonLogger.logger.log(level, `Kameleoon [${LOG_LEVEL_NAMES[level]}]: ${message}`);
}
}
KameleoonLogger.logger = new DefaultLogger();
KameleoonLogger.logLevel = LogLevel.WARNING;
class Requester {
constructor({ urlProvider, packageInfo, externalRequester, requestTimeout, trackRetryDelay, useAbortController, }) {
KameleoonLogger.debug `CALL: new Requester(urlProvider, packageInfo: ${packageInfo}, externalRequester, requestTimeout: ${requestTimeout}, trackRetryDelay: ${trackRetryDelay}, useAbortController: ${useAbortController})`;
this.urlProvider = urlProvider;
this.useAbortController = useAbortController;
this.externalRequester = externalRequester;
this.trackRetryDelay = trackRetryDelay;
this.packageInfo = packageInfo;
this.timeout = requestTimeout;
KameleoonLogger.debug `RETURN: new Requester(urlProvider, packageInfo: ${packageInfo}, externalRequester, requestTimeout: ${requestTimeout}, trackRetryDelay: ${trackRetryDelay}, useAbortController: ${useAbortController})`;
}
getClientConfiguration(timeStamp, modifiedSince) {
return __awaiter(this, void 0, void 0, function* () {
const requestUrl = this.urlProvider.getClientConfigurationUrl(timeStamp);
let headers = Object.assign({ [Header.SdkType]: this.packageInfo.type.toLowerCase(), [Header.SdkVersion]: this.packageInfo.version }, (modifiedSince && { [Header.IfModifiedSince]: modifiedSince }));
// --- Note ---
// Initial request + NUMBER_OF_RETRIES
const requests = NUMBER_OF_RETRIES + 1;
try {
let responseInfo = { message: '' };
KameleoonLogger.debug `Running configuration request Method: ${HttpMethod.Get}, Url: ${requestUrl}, Headers: ${headers}, with retry limit ${requests}`;
for (let i = 0; i < requests; i++) {
const response = yield this.sendRequest({
url: requestUrl,
requestType: RequestType.Configuration,
retryCount: i,
parameters: {
method: HttpMethod.Get,
headers,
},
});
if (response.ok) {
const data = yield response.json();
this.logReceivedConfigurationResponse(response.status, requestUrl, headers);
const lastModified = this.getLastModifiedHeader(response);
const configurationData = data;
return buildExports.Ok({
configuration: configurationData,
lastModified: lastModified,
});
}
if (response.status === 304) {
this.logReceivedConfigurationResponse(response.status, requestUrl, headers);
return buildExports.Ok({});
}
if (i === NUMBER_OF_RETRIES) {
if (response.text) {
const text = yield response.text();
responseInfo = JSON.parse(text);
}
KameleoonLogger.error `Failed to get configuration response ${{
status: response.status,
message: responseInfo.message,
}} for request Method: ${HttpMethod.Get}, Url: ${requestUrl}, Headers: ${headers}`;
}
else {
yield this.logRequestError({
logLevel: LogLevel.WARNING,
message: 'Failed to get configuration response',
response: response,
method: HttpMethod.Get,
url: requestUrl,
});
}
}
return buildExports.Err(new KameleoonError(KameleoonException.MaximumRetriesReached, responseInfo.message));
}
catch (err) {
yield this.logRequestError({
logLevel: LogLevel.ERROR,
message: 'Failed to get configuration response',
error: err,
method: HttpMethod.Get,
url: requestUrl,
headers,
});
return buildExports.Err(new KameleoonError(KameleoonException.ClientConfiguration, err));
}
});
}
getRemoteData(key) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const requestUrl = this.urlProvider.getRemoteDataUrl(key);
KameleoonLogger.debug `Running remote data request Method: ${HttpMethod.Get}, Url: ${requestUrl}`;
try {
const response = yield this.sendRequest({
url: requestUrl,
requestType: RequestType.RemoteData,
retryCount: 0,
parameters: {
method: HttpMethod.Get,
},
});
if (!response.ok) {
yield this.logRequestError({
logLevel: LogLevel.ERROR,
message: 'Failed to get remote data response',
response: response,
method: HttpMethod.Get,
url: requestUrl,
});
if (response.text) {
const text = yield response.text();
const message = (_a = JSON.parse(text)) === null || _a === void 0 ? void 0 : _a.message;
return buildExports.Err(new KameleoonError(KameleoonException.RemoteData, message));
}
return buildExports.Err(new KameleoonError(KameleoonException.RemoteData, 'Unknown Reason - no `text()` was found for a response'));
}
const data = yield response.json();
KameleoonLogger.debug `Received remote data response: ${{
status: response.status,
}} for request Method: ${HttpMethod.Get}, Url: ${requestUrl}`;
return buildExports.Ok(data);
}
catch (err) {
yield this.logRequestError({
logLevel: LogLevel.ERROR,
message: 'Failed to get remote data response',
error: err,
method: HttpMethod.Get,
url: requestUrl,
});
return buildExports.Err(new KameleoonError(KameleoonException.RemoteData, err));
}
});
}
getVisitorData({ visitorCode, filters, isMappingIdentifier, }) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const requestUrl = this.urlProvider.getVisitorDataUrl({
visitorCode,
filters,
isMappingIdentifier,
});
KameleoonLogger.debug `Running visitor data request Method: ${HttpMethod.Get}, Url: ${requestUrl}`;
try {
const response = yield this.sendRequest({
url: requestUrl,
requestType: RequestType.RemoteData,
retryCount: 0,
parameters: {
method: HttpMethod.Get,
},
});
if (!response.ok) {
yield this.logRequestError({
logLevel: LogLevel.ERROR,
message: 'Failed to get visitor data response',
response: response,
method: HttpMethod.Get,
url: requestUrl,
});
if (typeof response.text === 'function') {
const text = yield response.text();
if (text) {
const message = (_a = JSON.parse(text)) === null || _a === void 0 ? void 0 : _a.message;
return buildExports.Err(new KameleoonError(KameleoonException.RemoteData, message));
}
}
if (response.status) {
return buildExports.Err(new KameleoonError(KameleoonException.RemoteData, `No error message. Error status: ${response.status}`));
}
return buildExports.Err(new KameleoonError(KameleoonException.RemoteData, 'Unknown Reason - no text message or error status was found for a response'));
}
const data = yield response.json();
KameleoonLogger.debug `Received visitor data response: ${{
status: response.status,
}} for request Method: ${HttpMethod.Get}, Url: ${requestUrl}`;
return buildExports.Ok(data);
}
catch (err) {
yield this.logRequestError({
logLevel: LogLevel.ERROR,
message: 'Failed to get visitor data response',
error: err,
method: HttpMethod.Get,
url: requestUrl,
});
return buildExports.Err(new KameleoonError(KameleoonException.RemoteData, err));
}
});
}
track(body, isBodyUserAgent) {
return __awaiter(this, void 0, void 0, function* () {
// --- Note ---
// Initial request + NUMBER_OF_RETRIES
const requests = NUMBER_OF_RETRIES + 1;
const url = this.urlProvider.getTrackingUrl(isBodyUserAgent);
KameleoonLogger.debug `Running tracking request Method: ${HttpMethod.Post}, Url: ${url}, Body: ${body}, with retry limit ${requests}, retry delay ${this.trackRetryDelay} ms`;
for (let i = 0; i < requests; i++) {
let response;
try {
response = yield this.sendRequest({
url,
retryCount: i,
requestType: RequestType.Tracking,
parameters: {
body,
method: HttpMethod.Post,
headers: {
[Header.ContentType]: '*/*',
},
},
});
if (response.ok) {
KameleoonLogger.debug `Received tracking response: ${{
status: response.status,
}} for request Method: ${HttpMethod.Post}, Url: ${url}, Body: ${body}`;
return buildExports.Ok();
}
yield this.logRequestError({
logLevel: i == NUMBER_OF_RETRIES ? LogLevel.ERROR : LogLevel.WARNING,
message: 'Failed to get tracking response',
response: response,
method: HttpMethod.Post,
url,
});
}
catch (error) {
yield this.logRequestError({
logLevel: i == NUMBER_OF_RETRIES ? LogLevel.ERROR : LogLevel.WARNING,
message: 'Failed to get tracking response',
error,
method: HttpMethod.Post,
url,
});
}
if (i < requests) {
yield new Promise((resolve) => setTimeout(resolve, this.trackRetryDelay));
}
}
return buildExports.Err();
});
}
sendRequest({ url, requestType, retryCount, parameters, }) {
return __awaiter(this, void 0, void 0, function* () {
let response;
if (this.useAbortController) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
response = yield this.externalRequester.sendRequest({
url,
retryCount: retryCount,
requestType,
parameters: Object.assign(Object.assign({}, parameters), { signal: controller.signal }),
});
clearTimeout(timeoutId);
}
else {
response = yield this.externalRequester.sendRequest({
url,
retryCount: retryCount,
requestType,
parameters: parameters,
});
}
return response;
});
}
logRequestError({ logLevel, message, error, method, url, response, headers, }) {
return __awaiter(this, void 0, void 0, function* () {
if (error) {
let errorText = 'Unknown error';
if (error instanceof Error) {
errorText = error.message;
}
KameleoonLogger.log({
level: logLevel,
strings: () => `${message} with error: ${errorText} for request Method: ${method}, Url: ${url}`,
});
}
else if (response) {
if (KameleoonLogger.checkLevel(logLevel)) {
const logResponse = yield this.parseResponse(response);
KameleoonLogger.log({
level: logLevel,
strings: () => `${message} response: Status: '${logResponse.status}' Message: '${logResponse.message}' for request Method: ${method}, Url: '${url}'` +
(headers ? `, Headers: ${headers}` : ''),
});
}
}
});
}
parseResponse(response) {
return __awaiter(this, void 0, void 0, function* () {
let message = '';
try {
if (typeof response.text === 'function') {
const text = yield response.text();
if (text) {
message = text;
}
}
}
catch (error) { }
return {
status: response.status,
message: message,
};
});
}
getLastModifiedHeader(response) {
if (response.headers) {
const targetKey = Header.LastModified.toLowerCase();
for (const [key, value] of response.headers) {
if (key.toLowerCase() === targetKey) {
return value;
}
}
}
return undefined;
}
logReceivedConfigurationResponse(responseStatus, requestUrl, headers) {
KameleoonLogger.debug `Received configuration response: ${{
status: responseStatus,
}} for request Method: ${HttpMethod.Get}, Url: ${requestUrl}, Headers: ${headers}`;
}
}
/**
* @readonly
* @enum {string} an Enum containing all possible variants of environment, passed as an argument to KameleoonClient `configuration`s `environment` field
* */
var Environment;
(function (Environment) {
Environment["Production"] = "production";
Environment["Staging"] = "staging";
Environment["Development"] = "development";
})(Environment || (Environment = {}));
class UrlProvider {
constructor() {
this.ready = false;
this.isCustomDomain = false;
this.domains = DEFAULT_DOMAINS;
}
initialize({ domain, siteCode, packageInfo, environment, }) {
this.siteCode = siteCode;
this.environment = environment;
this.packageInfo = packageInfo;
this.ready = true;
this.setDomains(domain);
}
set dataApiDomain(domain) {
if (!this.isCustomDomain) {
this.domains[UrlType.DataApi] = domain;
return;
}
const subDomain = domain.split('.')[0];
const currentDomain = this.domains[UrlType.DataApi];
this.domains[UrlType.DataApi] = currentDomain.replace(/^[^.]+/, subDomain);
}
get dataApiDomain() {
return this.domains[UrlType.DataApi];
}
getClientConfigurationUrl(timeStamp) {
this.isInitialized();
const baseUrl = `https://${this.domains[UrlType.ClientConfiguration]}/v3/`;
const environmentParam = this.environment === Environment.Production
? ''
: UrlFirstParameter.Environment + encodeURIComponent(this.environment);
let timeStampParam = '';
if (timeStamp) {
if (environmentParam) {
timeStampParam = UrlParameter.Ts + timeStamp;
}
else {
timeStampParam = UrlFirstParameter.Ts + timeStamp;
}
}
return baseUrl + this.siteCode + environmentParam + timeStampParam;
}
getEventSourceUrl() {
this.isInitialized();
return `https://${this.domains[UrlType.Events]}:8110/${UrlQuery.Sse}${this.siteCode}`;
}
getRemoteDataUrl(key) {
this.isInitialized();
return (this.getDataApiUrl(DataApiQuery.DataMap) +
this.siteCode +
UrlParameter.Key +
encodeURI(key));
}
getVisitorDataUrl({ visitorCode, filters, isMappingIdentifier, }) {
this.isInitialized();
const { customData, previousVisitAmount, currentVisit, conversions, geolocation, experiments, pageViews, device, browser, operatingSystem, kcs, personalization, visitorCode: visitorCodeFilter, cbs, } = filters;
const identifierParameter = isMappingIdentifier
? UrlParameter.MappingValue
: UrlParameter.VisitorCode;
const customDataParameter = customData || visitorCodeFilter ? UrlParameter.CustomData + true : '';
const conversionsParameter = conversions
? UrlParameter.Conversion + true
: '';
const geolocationParameter = geolocation
? UrlParameter.Geolocation + true
: '';
const experimentsParameter = experiments
? UrlParameter.Experiment + true
: '';
const pageViewsParameter = pageViews ? UrlParameter.Page + true : '';
const staticDataParameter = UrlParameter.StaticData + true;
const currentVisitParameter = currentVisit
? UrlParameter.CurrentVisit + true
: '';
const kcsParameter = kcs ? UrlParameter.Kcs + true : '';
const personalizationParameter = personalization
? UrlParameter.Personalization + true
: '';
const previousVisitAmountParam = typeof previousVisitAmount !== 'number'
? UrlParameter.MaxNumberPreviousVisits + 1
: UrlParameter.MaxNumberPreviousVisits + previousVisitAmount;
const cbsParameter = cbs ? UrlParameter.Cbs + true : '';
return (this.getDataApiUrl(DataApiQuery.VisitData) +
this.siteCode +
identifierParameter +
visitorCode +
previousVisitAmountParam +
customDataParameter +
conversionsParameter +
geolocationParameter +
experimentsParameter +
pageViewsParameter +
staticDataParameter +
currentVisitParameter +
kcsParameter +
personalizationParameter +
cbsParameter);
}
getTrackingUrl(isBodyUserAgent) {
this.isInitialized();
const { type, version } = this.packageInfo;
let url = this.getDataApiUrl(DataApiQuery.VisitEvent) +
this.siteCode +
UrlParameter.SdkName +
type.toLowerCase() +
UrlParameter.SdkVersion +
version;
if (isBodyUserAgent) {
url += UrlParameter.BodyUa + true;
}
return url;
}
isInitialized() {
if (!this.ready) {
throw new Error('UrlProvider is not initialized');
}
}
getDataApiUrl(query) {
if (!this.domains[UrlType.DataApi]) {
throw new Error('Data API domain is not set');
}
const domain = `https://${this.domains[UrlType.DataApi]}`;
switch (query) {
case DataApiQuery.VisitEvent:
return `${domain}/${UrlTracking.Visit + UrlQuery.Events}`;
case DataApiQuery.VisitData:
return `${domain}/${UrlTracking.Visit + UrlQuery.Visitor}`;
case DataApiQuery.DataMap:
return `${domain}/${UrlTracking.Map + UrlQuery.Map}`;
}
}
setDomains(domain) {
if (!domain) {
return;
}
this.isCustomDomain = true;
this.domains[UrlType.DataApi] = 'data.' + domain;
this.domains[UrlType.Events] = 'events.' + domain;
this.domains[UrlType.ClientConfiguration] = 'sdk-config.' + domain;
}
}
/**
* @readonly
* @enum {string} an Enum containing all possible variants of feature variable types
* */
var VariableType;
(function (VariableType) {
VariableType["BOOLEAN"] = "BOOLEAN";
VariableType["NUMBER"] = "NUMBER";
VariableType["STRING"] = "STRING";
VariableType["JSON"] = "JSON";
VariableType["JS"] = "JS";
VariableType["CSS"] = "CSS";
})(VariableType || (VariableType = {}));
/**
* @readonly
* @enum {string} an Enum containing all possible variants for data status
* */
var TrackingStatus;
(function (TrackingStatus) {
TrackingStatus["Sent"] = "sent";
TrackingStatus["Unsent"] = "unsent";
TrackingStatus["Pending"] = "pending";
})(TrackingStatus || (TrackingStatus = {}));
/**
* @readonly
* @enum {number} a helper Enum for getting milliseconds for a second/minute/hour/day/week/month.
* Month is considered as an average of 30 days
* */
var Milliseconds;
(function (Milliseconds) {
Milliseconds[Milliseconds["Second"] = 1000] = "Second";
Milliseconds[Milliseconds["Minute"] = 60000] = "Minute";
Milliseconds[Milliseconds["Hour"] = 3600000] = "Hour";
Milliseconds[Milliseconds["Day"] = 86400000] = "Day";
Milliseconds[Milliseconds["Week"] = 604800000] = "Week";
Milliseconds[Milliseconds["Month"] = 2592000000] = "Month";
})(Milliseconds || (Milliseconds = {}));
/**
* @class
* CacheManager - a class for managing cache
*/
class CacheManager {
/**
* @param {number} cleanupTimeout - timeout for cleaning cache in seconds
* generic type `T` is a type of data that will be stored in cache
* @example
* ```typescript
* const cacheManager = new CacheManager<number>(60);
* ```
*/
constructor(cleanupTimeout) {
this.cacheMap = new Map();
this.intervalId = null;
this.cleanupTimeout = cleanupTimeout * Milliseconds.Second;
}
add({ key, data, lifetime }) {
KameleoonLogger.debug `CALL: CacheManager.add(key: ${key}, data: ${data}, lifetime: ${lifetime})`;
if (this.intervalId === null) {
this.activate();
}
const expirationTime = lifetime * Milliseconds.Second;
const cacheItem = {
data,
expirationTime: Date.now() + expirationTime,
};
this.cacheMap.set(key, cacheItem);
KameleoonLogger.debug `RETURN: CacheManager.add(key: ${key}, data: ${data}, lifetime: ${lifetime})`;
}
getAliveItem(key) {
KameleoonLogger.debug `CALL: CacheManager.getAliveItem(key: ${key})`;
const cacheItem = this.cacheMap.get(key);
if (cacheItem && cacheItem.expirationTime >= Date.now()) {
const data = cacheItem.data;
KameleoonLogger.debug `RETURN: CacheManager.getAliveItem(key: ${key}) -> (item: ${data})`;
return data;
}
cacheItem && this.cacheMap.delete(key);
KameleoonLogger.debug `RETURN: CacheManager.getAliveItem(key: ${key}) -> (item: null)`;
return null;
}
activate() {
this.intervalId = setInterval(() => {
const now = Date.now();
for (const [key, item] of this.cacheMap) {
if (item.expirationTime < now) {
this.cacheMap.delete(key);
}
}
if (!this.cacheMap.size) {
this.cleanupInterval();
}
}, this.cleanupTimeout);
}
cleanupInterval() {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
}
var UpdateType;
(function (UpdateType) {
UpdateType[UpdateType["Polling"] = 0] = "Polling";
UpdateType[UpdateType["RealTime"] = 1] = "RealTime";
})(UpdateType || (UpdateType = {}));
var RuleType;
(function (RuleType) {
RuleType["TARGETED_DELIVERY"] = "TARGETED_DELIVERY";
RuleType["EXPERIMENTATION"] = "EXPERIMENTATION";
})(RuleType || (RuleType = {}));
var FeatureFlagSdkLanguageType;
(function (FeatureFlagSdkLanguageType) {
FeatureFlagSdkLanguageType["ANDROID"] = "ANDROID";
FeatureFlagSdkLanguageType["SWIFT"] = "SWIFT";
FeatureFlagSdkLanguageType["JAVA"] = "JAVA";
FeatureFlagSdkLanguageType["CSHARP"] = "CSHARP";
FeatureFlagSdkLanguageType["NODEJS"] = "NODEJS";
FeatureFlagSdkLanguageType["PHP"] = "PHP";
FeatureFlagSdkLanguageType["RUBY"] = "RUBY";
FeatureFlagSdkLanguageType["GO"] = "GO";
FeatureFlagSdkLanguageType["FLUTTER"] = "FLUTTER";
FeatureFlagSdkLanguageType["REACTJS"] = "REACTJS";
})(FeatureFlagSdkLanguageType || (FeatureFlagSdkLanguageType = {}));
var FeatureStatus;
(function (FeatureStatus) {
FeatureStatus["ACTIVATED"] = "ACTIVATED";
FeatureStatus["DEACTIVATED"] = "DEACTIVATED";
FeatureStatus["SCHEDULED"] = "SCHEDULED";
})(FeatureStatus || (FeatureStatus = {}));
var CustomDataScope;
(function (CustomDataScope) {
CustomDataScope["Visit"] = "VISIT";
CustomDataScope["Visitor"] = "VISITOR";
CustomDataScope["Page"] = "Page";
})(CustomDataScope || (CustomDataScope = {}));
var ConsentType;
(function (ConsentType) {
ConsentType["Required"] = "REQUIRED";
ConsentType["NotRequired"] = "NOT_REQUIRED";
})(ConsentType || (ConsentType = {}));
var ConsentBlockingBehaviour;
(function (ConsentBlockingBehaviour) {
ConsentBlockingBehaviour["PartiallyBlocked"] = "PARTIALLY_BLOCK";
ConsentBlockingBehaviour["CompletelyBlocked"] = "FULLY_BLOCK";
})(ConsentBlockingBehaviour || (ConsentBlockingBehaviour = {}));
class EventManager {
addEventHandler(eventType, callback) {
if (!this.eventHandlers) {
this.eventHandlers = new Map();
}
this.eventHandlers.set(eventType, callback);
}
fireEvent(eventType, data) {
var _a;
const eventHandler = (_a = this.eventHandlers) === null || _a === void 0 ? void 0 : _a.get(eventType);
if (eventHandler && data) {
eventHandler(data);
}
}
}
/**
* @readonly
* @enum {string} an Enum containing all possible variants of event types
* */
var EventType;
(function (EventType) {
EventType["Evaluation"] = "evaluation";
EventType["ConfigurationUpdate"] = "configurationUpdate";
})(EventType || (EventType = {}));
const DEFAULT_DATA_FILE_CONFIGURATION = {
customData: [],
featureFlags: [],
configuration: {
realTimeUpdate: false,
consentType: ConsentType.NotRequired,
dataApiDomain: 'data.kameleoon.io',
consentOptOutBehavior: ConsentBlockingBehaviour.PartiallyBlocked,
},
};
class MEGroup {
constructor(featureFlags) {
this._featureFlags = featureFlags
.slice()
.sort((ff1, ff2) => ff1.id - ff2.id);
}
getFeatureFlagByHash(hash) {
let idx = Math.floor(hash * this._featureFlags.length);
idx = Math.min(idx, this._featureFlags.length - 1);
return this._featureFlags[idx];
}
get featureFlags() {
return this._featureFlags;
}
}
class ClientConfiguration {
constructor({ updateInterval, urlProvider, storage, requester, dataManager, eventSource, externalVisitorCodeManager, eventManager, externalPackageInfo, defaultDataFile, }) {
this.updateConfigurationIntervalId = null;
this.updateType = UpdateType.Polling;
this.configurationData = DEFAULT_DATA_FILE_CONFIGURATION;
this.featureFlagsData = new Map();
this.isTargetedDeliveryRule = null;
this.segmentsData = null;
this.holdoutData = null;
this.meGroupsData = new Map()