UNPKG

lightswitch-js-sdk

Version:

light switch javascript sdk

277 lines (276 loc) 12.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (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()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const types_1 = require("./types"); const LSLogger_1 = require("./LSLogger"); const utils_1 = require("./utils"); const reconnecting_eventsource_1 = __importDefault(require("reconnecting-eventsource")); const error_1 = require("./error"); const logger = (0, LSLogger_1.LSLogger)(types_1.LogLevel.DEBUG); const FLAG_NOT_FOUND = 1000; const VARIATION_NOT_FOUND = 1001; const MEMBER_NOT_FOUND = 2000; const SDK_KEY_ALREADY_EXISTS = 3000; const SDK_KEY_NOT_FOUND = 3001; class LightSwitch { constructor() { this.sdkKey = ''; this.logLevel = types_1.LogLevel.DEBUG; // 로그 레벨 // private lastUpdated = ''; // feature flag 데이터 갱신 날짜 this.flags = new Map(); this.onError = null; this.eventSource = null; this.onFlagChanged = null; this.userKey = ''; this.reconnectTime = 3000; this.SERVER_URL = 'http://localhost:8000'; this.INIT_REQUEST_PATH = this.SERVER_URL + '/api/v1/sdk/init'; this.SSE_CONNECT_PATH = this.SERVER_URL + '/api/v1/sse/subscribe'; } static getInstance() { if (!LightSwitch.instance) { LightSwitch.instance = new LightSwitch(); } return LightSwitch.instance; } init(config) { return __awaiter(this, void 0, void 0, function* () { if (LightSwitch.instance != null && LightSwitch.isInitialized) { logger.info('lightswitch is already initialized, skip init process'); return; } const { sdkKey, onError, onFlagChanged, reconnectTime, endpoint } = config; this.sdkKey = sdkKey; if (reconnectTime) { this.reconnectTime = reconnectTime; } if (onError) { this.onError = onError; } if (endpoint) { this.SERVER_URL = endpoint; this.INIT_REQUEST_PATH = this.SERVER_URL + '/api/v1/sdk/init'; this.SSE_CONNECT_PATH = this.SERVER_URL + '/api/v1/sse/subscribe'; } this.onFlagChanged = onFlagChanged; yield this.getInitData(); logger.debug('success to getInitData'); yield this.getUserKey(); logger.debug('success to getUserKey, start connecting SSE'); this.eventSource = this.getEventSource(this.userKey, this.reconnectTime); this.addSseListener(); LightSwitch.isInitialized = true; // logger.info('success to initialize client sdk'); }); } getVariationValue(flag, LSUser) { let percentage = (0, utils_1.getHashedPercentageForObjectIds)([LSUser.getUserId(), flag.title], 1); logger.info(percentage); if (flag.variations) { for (let i = 0; i < flag.variations.length; i++) { const variation = flag.variations[i]; percentage -= variation.portion; logger.info(`percentage : ${percentage}, portion : ${variation.portion}`); if (percentage < 0) { // logger.info(`value : ${variation.value}`); return variation.value; } } } return flag.defaultValue; } getTypedValue(flag, value) { if (flag.type == 'STRING') { return value; } else if (flag.type == 'BOOLEAN') { if (value == 'TRUE') return true; else return false; } else if (flag.type == 'INTEGER') { return parseInt(value); } } getFlag(name, LSUser, defaultVal) { var _a; const flag = this.flags.get(name); if (!flag) { logger.warning(`${name} 플래그가 존재하지 않습니다. 기본값을 이용합니다.`); return defaultVal; } if (!flag.active) { logger.info(`id : ${flag === null || flag === void 0 ? void 0 : flag.flagId} title : ${flag === null || flag === void 0 ? void 0 : flag.title} 플래그가 활성화 되어있지 않습니다. 기본값을 이용합니다.`); return defaultVal; // return this.getTypedValue(flag, flag.defaultValue) as T; } else { if (flag.keywords.length > 0 && ((_a = LSUser.properties) === null || _a === void 0 ? void 0 : _a.size) > 0) { logger.info(`id : ${flag === null || flag === void 0 ? void 0 : flag.flagId} title : ${flag.title} 플래그가 활성화 되어 있습니다. 키워드를 확인합니다.`); for (let i = 0; i < flag.keywords.length; i++) { const keyword = flag.keywords[i]; const isEqual = (0, utils_1.compareObjectsAndMaps)(keyword.properties, LSUser.properties); if (isEqual) { return this.getTypedValue(flag, keyword.value); } else { logger.info(`키워드 대상이 아닙니다. portion을 계산합니다.`); } } } logger.info(`id : ${flag === null || flag === void 0 ? void 0 : flag.flagId} title : ${flag.title} 플래그가 활성화 되어 있습니다. portion을 계산합니다.`); const evaluatedValue = this.getVariationValue(flag, LSUser); return this.getTypedValue(flag, evaluatedValue); } } handleError(error) { if (this.onError) { this.onError(error); } else { throw error; } } getBooleanFlag(name, LSUser, defaultVal) { const booleanFlag = this.getFlag(name, LSUser, defaultVal); if (typeof booleanFlag != 'boolean') { this.handleError(new error_1.LSTypeCastError(`${name} 플래그를 BOOLEAN 타입으로 캐스팅하는데 실패했습니다.`)); } return booleanFlag; } getIntegerFlag(name, LSUser, defaultVal) { const integerFlag = this.getFlag(name, LSUser, defaultVal); if (typeof integerFlag != 'number') { this.handleError(new error_1.LSTypeCastError(`${name} 플래그를 INTEGER 타입으로 캐스팅하는데 실패했습니다.`)); } return integerFlag; } getStringFlag(name, LSUser, defaultVal) { const stringFlag = this.getFlag(name, LSUser, defaultVal); if (typeof stringFlag != 'string') { this.handleError(new error_1.LSTypeCastError(`${name} 플래그를 STRING 타입으로 캐스팅하는데 실패했습니다.`)); } return stringFlag; } getEventSource(userKey, reconnectTime) { return new reconnecting_eventsource_1.default(this.SSE_CONNECT_PATH + '/' + userKey, { // indicating if CORS should be set to include credentials, default `false` withCredentials: true, // the maximum time to wait before attempting to reconnect in ms, default `3000` // note: wait time is randomised to prevent all clients from attempting to reconnect simultaneously max_retry_time: reconnectTime, // underlying EventSource class, default `EventSource` eventSourceClass: EventSource, }); } getInitData() { var _a; return __awaiter(this, void 0, void 0, function* () { const response = yield (0, utils_1.postRequest)(this.INIT_REQUEST_PATH, { sdkKey: this.sdkKey, }); if ((response === null || response === void 0 ? void 0 : response.code) == SDK_KEY_NOT_FOUND) { this.handleError(new error_1.LSServerError(response.message)); } const newFlags = response === null || response === void 0 ? void 0 : response.data; newFlags.forEach((flag) => { this.flags.set(flag.title, flag); }); (_a = this.onFlagChanged) === null || _a === void 0 ? void 0 : _a.call(this); }); } getUserKey() { return __awaiter(this, void 0, void 0, function* () { try { logger.info(this.sdkKey); const response = yield (0, utils_1.postRequest)(`${this.SSE_CONNECT_PATH}`, { sdkKey: this.sdkKey, }); this.userKey = response.data.userKey; // logger.info(`receive userKey data : ${JSON.stringify(response)}`); } catch (error) { if (error instanceof Error) { this.handleError(new error_1.LSServerError(error.message)); } } }); } getAllFlags() { return this.flags; } addSseListener() { var _a; (_a = this.eventSource) === null || _a === void 0 ? void 0 : _a.addEventListener('sse', (event) => { var _a; if (event.data === 'SSE connected') { return; } const data = JSON.parse(event.data); logger.info(data.type); switch (data.type) { case 'CREATE': const createData = data.data; this.addFlag(createData); break; case 'UPDATE': const updateData = data.data; this.updateFlag(updateData); break; case 'DELETE': const deleteData = data.data; this.deleteFlag(deleteData); break; case 'SWITCH': // not used const switchData = data.data; this.switchFlag(switchData); break; } (_a = this.onFlagChanged) === null || _a === void 0 ? void 0 : _a.call(this); }); } addFlag(flag) { logger.info('addFlag call'); this.flags.set(flag.title, flag); } updateFlag(newFlag) { logger.info('updateFlag call'); this.flags.delete(newFlag.title); this.flags.set(newFlag.title, newFlag); } deleteFlag(title) { logger.info('deleteFlag call'); this.flags.delete(title.title); } switchFlag(sw) { logger.info('switchFlag call'); const flag = this.flags.get(sw.title); const newFlag = JSON.parse(JSON.stringify(flag)); newFlag.active = sw.active; this.flags.set(sw.title, newFlag); } destroy() { var _a; if (!LightSwitch.isInitialized) { throw new Error('LightSwitch is not initialized.'); } (_a = this.eventSource) === null || _a === void 0 ? void 0 : _a.close(); LightSwitch.isInitialized = false; logger.debug('call destroy'); } } LightSwitch.instance = null; LightSwitch.isInitialized = false; exports.default = LightSwitch;