UNPKG

@bxjs/base

Version:

bxjs base framework & api

659 lines 76.3 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); var _ = require('lodash'); var moment = require('moment'); // const shortid = require('shortid') var uuid1 = require('uuid/v1'); var uuid4 = require('uuid/v4'); var aliyun_ots_1 = require("./driver/aliyun_ots"); var SESSION_KEY = 'XJSSESSID'; // AXJS SESSION ID的缩写,BXJSSESSID为BXJS SESSION ID类似方法进行区分。 // node-uuid作者对于UUID算法唯一性的解释 https://github.com/kelektiv/node-uuid/issues/82 function generate_session_id() { // 实践中发现shortid算法存在冲突的可能性为了保险期间确保唯一性增加算法复杂度 // 文中解释了为什么shortid会冲突了不支持多线程和多机器并发处理导致的 https://stackoverflow.com/questions/42844152/what-is-the-guarantee-of-uniqueness-of-shortid // 改进算法必须要使用uuid或snakeflow等随机算法实现(snakeflow算法也不适合) // 对于uuid1和4两个版本使用的介绍 https://cloud.tencent.com/developer/ask/28130 // const id = shortid.generate() // return xnow('YYYYMMDDHHmmss') + id + uuid('v4') // 确保100%不冲突以及不可预测性(uuid v1确保分布式唯一性,uuidv4确保随机性不可预测) return (uuid1({ nsecs: Math.floor(Math.random() * 10000) }) + uuid4()).replace(/\-/g, ''); } // framework内部使用 function __framework_session_init__(header) { return __awaiter(this, void 0, void 0, function () { var param; return __generator(this, function (_a) { switch (_a.label) { case 0: // 在框架请求初始化的时候自动生成一个唯一的会话请求标识并保存到全局变量__session__之中,此处仅仅读取数据。 // 在框架请求初始化的时候根据框架协议约定从cookie或header头部取到客户端的会话标识,如果取不到自动重新生成一个新标识。 // 子进程方式加载处理服务隔离全局变量的影响模拟唯一请求问题 // 发布bxjs包支持tag打包beta版本避免线上发布影响问题 // FIXME 此处写法不正确测试出线上FC环境的缓存状态是不清空的下次请求过来会复用环境 // 强制清空服务端缓存信息避免上次请求的残留数据 global['__session__'] = {}; // 强制重置全局变量缓存中的用户信息 global['__user__'] = {}; if (header && header[SESSION_KEY]) { // 对于REST类型请求从HEADER头上获取会话标识(等同于客户端OAUTH2登录认证对应的ACCESSS_TOKEN数据是类似的) global['__session__'].id = header[SESSION_KEY]; } else { // 对于WEB类型请求的COOKIES会话标识的统一获取参数配置 console.log('web请求设置sessionid'); global['__session__'].id = xcookie(SESSION_KEY); } console.log('设置完成后的sessionid:', global['__session__'].id); if (!global['__session__'].id) { // 新请求临时生成一个会话标识 global['__session__'].id = generate_session_id(); } return [4 /*yield*/, xsession.get('__user__')]; case 1: param = _a.sent(); if (param) { global['__user__'].id = param.id; global['__user__'].param = param; } // 刷新客户端保存在COOKIES之中的会话标识到期时间 xcookie(SESSION_KEY, global['__session__'].id); return [2 /*return*/]; } }); }); } exports.__framework_session_init__ = __framework_session_init__; // 全局静态类导出给应用开发者使用(调用应用代码之前需要确保框架已经正确执行__framework_session_init__方法) var xsession = /** @class */ (function () { function xsession() { } Object.defineProperty(xsession, "id", { // 会话id唯一标识,基于FC的特点单进程启动执行不常驻内存的特点,可用静态全局变量简化实现代表当前用户请求信息。 // 通过web端的cookie或者rest端的header头部信息,统一取到客户端对应的会话标识码。 get: function () { xassert(global['__session__'] && global['__session__'].id); return global['__session__'].id; }, enumerable: true, configurable: true }); // 检查会话信息是否有效 xsession._check = function (saved, requested) { return __awaiter(this, void 0, void 0, function () { var saved_time, now_time, timeout; return __generator(this, function (_a) { // console.log('获取session中的数据userid',JSON.parse(saved['__user__']).id) // 检查OTS中是否存在对应的记录值 if (!saved || !saved['__updated_at__']) return [2 /*return*/, false // 检查会话是否超时 ]; saved_time = moment(saved['__updated_at__']); now_time = moment(requested['__updated_at__']); timeout = 24 * 60 * 60 * 1000 // 默认session超时时间是24小时 ; if (now_time.diff(saved_time) > timeout) { return [2 /*return*/, false]; } // 检查客户端请求的特征参数是否发生变化防止盗用会话标识 if (saved['__user_agent__'] != requested['__user_agent__'] || saved['__client_ip__'] != requested['__client_ip__']) { return [2 /*return*/, false]; } // 进行单端登录处理 // if (saved['__user__'] && saved['__user__'].id) { // console.log('正在修改上次session') // // if(!global['__user__']){ // // global['__user__'] = {} // // } // if (!global['__user__'].ots) { // global['__user__'].ots = new NoSqlAliyunTablestore('__user__') // } // const ots = global['__user__'].ots as NoSqlAliyunTablestore // const xuserSaved = await ots.query(saved['__user__'].id, ['sessionId']) // console.log('查询到的sessionId', xuserSaved) // const KEYS = ['__created_at__', '__updated_at__', '__user_agent__', '__client_ip__'] // let xsessionSaved = await ots.query(xuserSaved, KEYS) // console.log('获取到session中信息', xsessionSaved) // const saved_time = moment(xsessionSaved['__updated_at__']) // const now_time = moment(xsessionSaved['__updated_at__']) // const timeout = 24 * 60 * 60 * 1000 // 默认session超时时间是24小时 // if (now_time.diff(saved_time) < timeout) { // let newTime = moment(now_time + timeout).format('YYYY-MM-DD HH:mm:ss') // await ots.update(global['__session__'].id, { // '__updated_at__': newTime // }) // } // } return [2 /*return*/, true]; }); }); }; // 仅在初次使用的时候按需初始化OTS的会话记录信息(会话持久化数据仅仅在首次被使用的时候才开始正式计时处理) xsession._init = function (key) { return __awaiter(this, void 0, void 0, function () { var ots, now, user_agent, client_ip, KEYS, requested, saved, status; return __generator(this, function (_a) { switch (_a.label) { case 0: xassert(global['__session__'] && global['__session__'].id); if (!global['__session__'].ots) { global['__session__'].ots = new aliyun_ots_1.NoSqlAliyunTablestore('__session__'); } ots = global['__session__'].ots; now = moment().format('YYYY-MM-DD HH:mm:ss'); user_agent = global['__user_agent__'] // 框架在启动的时候正确获取到此信息 ; client_ip = global['__client_ip__'] // 框架在启动的时候正确获取到此信息 ; xassert(user_agent && client_ip); KEYS = ['__created_at__', '__updated_at__', '__user_agent__', '__client_ip__']; if (key) { KEYS.push(key); } console.log('_init查询创建session记录值:', JSON.stringify(KEYS)); requested = { __created_at__: now, __updated_at__: now, __user_agent__: user_agent, __client_ip__: client_ip, }; return [4 /*yield*/, ots.query(global['__session__'].id, KEYS)]; case 1: saved = _a.sent(); console.log('创建记录的返回结果:', saved); if (!!saved) return [3 /*break*/, 3]; // BUG 直接原因在此,为什么会话标识此处会报错冲突??rlrSvOZv9 F1bHMGU83 return [4 /*yield*/, ots.insert(global['__session__'].id, requested)]; case 2: // BUG 直接原因在此,为什么会话标识此处会报错冲突??rlrSvOZv9 F1bHMGU83 _a.sent(); _a.label = 3; case 3: if (!saved) return [3 /*break*/, 5]; if (!!(saved['__created_at__'] && saved['__updated_at__'] && saved['__user_agent__'] && saved['__client_ip__'])) return [3 /*break*/, 5]; return [4 /*yield*/, xwarn('OTS保存SESSION信息出错!!!', { saved: saved, requested: requested }) // 会话无效更换一个新的会话标识 ]; case 4: _a.sent(); // 会话无效更换一个新的会话标识 global['__session__'].id = generate_session_id(); // 通过COOKIE将变更的会话标识在当前请求结束后同步到客户端缓存起来 xcookie(SESSION_KEY, global['__session__'].id); // 重置全局变量缓存中的用户信息 global['__user__'] = {}; return [2 /*return*/, { saved: undefined, requested: requested, ots: ots, status: false }]; case 5: return [4 /*yield*/, xsession._check(saved, requested)]; case 6: status = _a.sent(); console.log('检测会话是否有效:', status); if (!status) { console.log('会话无效更换一个新的会话标识'); // 会话无效更换一个新的会话标识 global['__session__'].id = generate_session_id(); // 通过COOKIE将变更的会话标识在当前请求结束后同步到客户端缓存起来 xcookie(SESSION_KEY, global['__session__'].id); // 重置全局变量缓存中的用户信息 global['__user__'] = {}; } return [2 /*return*/, { saved: saved, requested: requested, ots: ots, status: status }]; } }); }); }; xsession.get = function (key, defaultValue) { return __awaiter(this, void 0, void 0, function () { var _a, saved, requested, ots, status; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, xsession._init(key)]; case 1: _a = _b.sent(), saved = _a.saved, requested = _a.requested, ots = _a.ots, status = _a.status; if (!!status) return [3 /*break*/, 3]; // 插入一条新会话纪录 if (defaultValue !== undefined) { requested[key] = JSON.stringify(defaultValue); } return [4 /*yield*/, ots.replace(global['__session__'].id, requested)]; case 2: _b.sent(); return [2 /*return*/, defaultValue]; case 3: return [2 /*return*/, _.isString(saved[key]) ? JSON.parse(saved[key]) : defaultValue]; } }); }); }; xsession.set = function (key, value) { return __awaiter(this, void 0, void 0, function () { var _a, _b, requested, ots, status; return __generator(this, function (_c) { switch (_c.label) { case 0: return [4 /*yield*/, xsession._init(key)]; case 1: _b = _c.sent(), requested = _b.requested, ots = _b.ots, status = _b.status; console.log('xsession中required', JSON.stringify(requested)); console.log('xsession中status', status); if (!!status) return [3 /*break*/, 3]; // 插入一条新会话纪录 requested[key] = JSON.stringify(value); return [4 /*yield*/, ots.replace(global['__session__'].id, requested)]; case 2: _c.sent(); return [3 /*break*/, 5]; case 3: // 更新已存在的记录一个字段 return [4 /*yield*/, ots.update(global['__session__'].id, (_a = { '__updated_at__': requested.__updated_at__ }, _a[key] = JSON.stringify(value), _a))]; case 4: // 更新已存在的记录一个字段 _c.sent(); _c.label = 5; case 5: return [2 /*return*/]; } }); }); }; xsession.delete = function (key) { return __awaiter(this, void 0, void 0, function () { var _a, _b, requested, ots, status; return __generator(this, function (_c) { switch (_c.label) { case 0: return [4 /*yield*/, xsession._init()]; case 1: _b = _c.sent(), requested = _b.requested, ots = _b.ots, status = _b.status; if (!status) return [3 /*break*/, 3]; return [4 /*yield*/, ots.update(global['__session__'].id, (_a = { '__updated_at__': requested.__updated_at__ }, _a[key] = null, _a))]; case 2: _c.sent(); _c.label = 3; case 3: return [2 /*return*/]; } }); }); }; // 用户登出的时候需要调用此接口删除服务端缓存的会话状态信息 xsession.destroy = function () { return __awaiter(this, void 0, void 0, function () { var _a, ots, status; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, xsession._init()]; case 1: _a = _b.sent(), ots = _a.ots, status = _a.status; console.log('退出登录:状态', status); if (!status) return [3 /*break*/, 3]; return [4 /*yield*/, ots.delete(global['__session__'].id)]; case 2: _b.sent(); _b.label = 3; case 3: return [2 /*return*/]; } }); }); }; return xsession; }()); exports.xsession = xsession; // 用户级全局持久化缓存(应用于rest或web场景下)当前用户登录信息以及配置信息持久化存储。 // 直接从当前会话中得到用户id(持久化缓存到静态全局变量之中加速信息的获取,每次调用的时候进行复用处理加速数据请求) // 每个登录成功后的用户对应一条持久化的记录。 var xuser = /** @class */ (function () { function xuser() { } Object.defineProperty(xuser, "id", { get: function () { if (!global['__user__']) { global['__user__'] = {}; } return global['__user__'].id; }, enumerable: true, configurable: true }); Object.defineProperty(xuser, "param", { get: function () { if (!global['__user__']) { global['__user__'] = {}; } return global['__user__'].param; }, enumerable: true, configurable: true }); // 登录成功之后需要从会话中得到当前登录的用户标识信息,以此准确的判定获取用户身份数据信息,从而得到用户运行配置数据。 // 框架层面上在登录完成之后需要正确设置唯一用户标识,并在用户登出的时候取消掉对应的用户标识信息。 xuser._init = function () { return __awaiter(this, void 0, void 0, function () { var ots; return __generator(this, function (_a) { if (!global['__user__']) { global['__user__'] = {}; } xassert(global['__user__'].id, ERR$UNAUTHORIZED); if (!global['__user__'].ots) { global['__user__'].ots = new aliyun_ots_1.NoSqlAliyunTablestore('__user__'); } ots = global['__user__'].ots; return [2 /*return*/, { ots: ots }]; }); }); }; xuser.get = function (key, defaultValue) { return __awaiter(this, void 0, void 0, function () { var ots, saved; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, xuser._init()]; case 1: ots = (_a.sent()).ots; return [4 /*yield*/, ots.query(global['__user__'].id, [key])]; case 2: saved = _a.sent(); if (!saved || !saved[key]) { return [2 /*return*/, defaultValue]; } return [2 /*return*/, JSON.parse(saved[key])]; } }); }); }; xuser.set = function (key, value) { return __awaiter(this, void 0, void 0, function () { var _a, ots; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, xuser._init() // 替换记录对应的字段(如果记录不存在插入一条新记录) ]; case 1: ots = (_b.sent()).ots; // 替换记录对应的字段(如果记录不存在插入一条新记录) return [4 /*yield*/, ots.replace(global['__user__'].id, (_a = {}, _a[key] = JSON.stringify(value), _a))]; case 2: // 替换记录对应的字段(如果记录不存在插入一条新记录) _b.sent(); return [2 /*return*/]; } }); }); }; xuser.delete = function (key) { return __awaiter(this, void 0, void 0, function () { var _a, ots; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, xuser._init()]; case 1: ots = (_b.sent()).ots; return [4 /*yield*/, ots.replace(global['__user__'].id, (_a = {}, _a[key] = null, _a))]; case 2: _b.sent(); return [2 /*return*/]; } }); }); }; // 用户恢复出厂缺省设置的时候需要调用此接口 xuser.destroy = function () { return __awaiter(this, void 0, void 0, function () { var ots; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, xuser._init()]; case 1: ots = (_a.sent()).ots; return [4 /*yield*/, ots.delete(global['__user__'].id)]; case 2: _a.sent(); return [2 /*return*/]; } }); }); }; return xuser; }()); exports.xuser = xuser; // 应用级全局持久化缓存,系统全局持久化缓存数据。取代redis类似的DB数据查询结果的中间缓存。全部自动转换为JSON内部与OTS通信缓存数据。 // 主键id为group分组标识名称,一般只有一条记录即可记录全局持久化数据字段结构。 var xcache = /** @class */ (function () { function xcache() { } xcache._init = function () { return __awaiter(this, void 0, void 0, function () { var ots; return __generator(this, function (_a) { if (!global['__cache__']) { global['__cache__'] = {}; } if (!global['__cache__'].id) { // 全局cache仅仅一条记录值对应的ots表的id固定写死掉即可 global['__cache__'].id = '-'; } if (!global['__cache__'].ots) { global['__cache__'].ots = new aliyun_ots_1.NoSqlAliyunTablestore('__cache__'); } ots = global['__cache__'].ots; return [2 /*return*/, { ots: ots }]; }); }); }; xcache.get = function (key, defaultValue) { return __awaiter(this, void 0, void 0, function () { var ots, saved; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, xcache._init()]; case 1: ots = (_a.sent()).ots; return [4 /*yield*/, ots.query(global['__cache__'].id, [key])]; case 2: saved = _a.sent(); if (!saved || !saved[key]) { return [2 /*return*/, defaultValue]; } return [2 /*return*/, JSON.parse(saved[key])]; } }); }); }; xcache.set = function (key, value) { return __awaiter(this, void 0, void 0, function () { var _a, ots; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, xcache._init() // 替换记录对应的字段(如果记录不存在插入一条新记录) ]; case 1: ots = (_b.sent()).ots; // 替换记录对应的字段(如果记录不存在插入一条新记录) return [4 /*yield*/, ots.replace(global['__cache__'].id, (_a = {}, _a[key] = JSON.stringify(value), _a))]; case 2: // 替换记录对应的字段(如果记录不存在插入一条新记录) _b.sent(); return [2 /*return*/]; } }); }); }; xcache.delete = function (key) { return __awaiter(this, void 0, void 0, function () { var _a, ots; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, xcache._init()]; case 1: ots = (_b.sent()).ots; return [4 /*yield*/, ots.replace(global['__cache__'].id, (_a = {}, _a[key] = null, _a))]; case 2: _b.sent(); return [2 /*return*/]; } }); }); }; // 系统升级清空临时缓存数据值使用方法 xcache.destroy = function () { return __awaiter(this, void 0, void 0, function () { var ots; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, xcache._init()]; case 1: ots = (_a.sent()).ots; return [4 /*yield*/, ots.delete(global['__cache__'].id)]; case 2: _a.sent(); return [2 /*return*/]; } }); }); }; return xcache; }()); exports.xcache = xcache; // TODO 将这部分代码改进到单元测试框架中 // 系统全局辅助方法实现 TODO 缺命令行工具链以及系统升级迁移脚本功能写线上运维环境的代码升级脚本 // export class xcmd { // // 系统首次安装方法 // static async install_ots() { // const ots_tbl_session = new NoSqlAliyunTablestore('__session__') // const ots_tbl_user = new NoSqlAliyunTablestore('__user__') // const ots_tbl_cache = new NoSqlAliyunTablestore('__cache__') // // await ots_tbl_session.destroy() // await ots_tbl_cache.destroy() // await ots_tbl_user.destroy() // // await ots_tbl_session.create(24 * 3600, 1) // await ots_tbl_cache.create(-1, 1) // await ots_tbl_user.create(-1, 1) // } // // // static async test() { // // const otstbl = new NoSqlAliyunTablestore('test') // // // const destroy_tmp_tables = ['test', 'test1', 'test2', 'test3', 'session_test1', 'session_test2'] // // // for(let tbl of destroy_tmp_tables){ // // // otstbl.table = tbl // // // await otstbl.destroy() // // // } // // // // // 无限属性个数的设置OTS的管理后台已经不支持显示不全需要自己开发额外的程序进行管理(控制台上只能显示20个属性数据) // // // await xcache.set('a', {a: 1, b: 2}) // // // await xcache.set('b', 12345) // // // // 测试设置256个属性是否全部操作正常? // // // for (let i = 0; i < 256; i++) { // // // await xcache.set(`p${i}`, i) // // // } // // // // // 属性读取测试 // // // let keys = ['a','b','p0','p255'] // // // let out = [] // // // for(let k of keys){ // // // let o = await xcache.get(k) // // // out.push(o) // // // } // // // // // 字段以及表删除测试 // // // let out = await xcache.delete('b') // // // let out = await xcache.destroy() // // // // // user缓存模拟测试 // // let out // // global['__user__'] = {id: 'abcd'} // // // let out = await xuser.set('a', {a: 1, b: 2}) // // // await xuser.set('b', 12345) // // // // // 测试设置256个属性是否全部操作正常? // // // for (let i = 0; i < 256; i++) { // // // await xuser.set(`p${i}`, i) // // // } // // // // // let keys = ['a','b','p0','p255'] // // // out = [] // // // for(let k of keys){ // // // let o = await xuser.get(k) // // // out.push(o) // // // } // // // // // out = await xuser.delete('b') // // // out = await xuser.destroy() // // // // // session缓存模拟测试 // // // global['__user_agent__'] = 'user_agent' // // // global['__client_ip__'] = 'client_ip11111122223333' // // // await __framework_session_init__() // // // out = await xsession.set('a', {a: 1, b: 2}) // // // await xsession.set('b', 12345) // // // // // // 测试设置256个属性是否全部操作正常? // // // for (let i = 0; i < 256; i++) { // // // await xsession.set(`p${i}`, i) // // // } // // // // // // let keys = ['a','b','p0','p255'] // // // out = [] // // // for(let k of keys){ // // // let o = await xsession.get(k) // // // out.push(o) // // // } // // // out = await xsession.delete('b') // // // out = await xsession.destroy() // // // // // out = xassign({}, global['__session__'], out) // // return out // // } // } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2Vzc2lvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInNlc3Npb24udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLElBQU0sQ0FBQyxHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQTtBQUMzQixJQUFNLE1BQU0sR0FBRyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUE7QUFDaEMscUNBQXFDO0FBQ3JDLElBQU0sS0FBSyxHQUFHLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQTtBQUNoQyxJQUFNLEtBQUssR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUE7QUFDaEMsa0RBQXlEO0FBRXpELElBQU0sV0FBVyxHQUFHLFdBQVcsQ0FBQSxDQUFDLHlEQUF5RDtBQUV6Riw0RUFBNEU7QUFDNUU7SUFDSSwyQ0FBMkM7SUFDM0MsbUlBQW1JO0lBQ25JLGtEQUFrRDtJQUNsRCxtRUFBbUU7SUFDbkUsZ0NBQWdDO0lBQ2hDLGtEQUFrRDtJQUNsRCxvREFBb0Q7SUFDcEQsT0FBTyxDQUFDLEtBQUssQ0FBQyxFQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsR0FBRyxLQUFLLENBQUMsRUFBQyxDQUFDLEdBQUcsS0FBSyxFQUFFLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFBO0FBQzNGLENBQUM7QUFFRCxnQkFBZ0I7QUFDaEIsb0NBQWlELE1BQWU7Ozs7OztvQkFDNUQsNERBQTREO29CQUM1RCxrRUFBa0U7b0JBRWxFLCtCQUErQjtvQkFDL0IsaUNBQWlDO29CQUVqQyw4Q0FBOEM7b0JBQzlDLHlCQUF5QjtvQkFDekIsTUFBTSxDQUFDLGFBQWEsQ0FBQyxHQUFHLEVBQUUsQ0FBQTtvQkFDMUIsbUJBQW1CO29CQUNuQixNQUFNLENBQUMsVUFBVSxDQUFDLEdBQUcsRUFBRSxDQUFBO29CQUV2QixJQUFJ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NUMsU0FBUztBQUNULDhCQUE4QjtBQUM5Qix3REFBd0Q7QUFDeEQsb0VBQW9FO0FBQ3BFLG1EQUFtRDtBQUNuRCw0REFBNEQ7QUFDNUQsK0NBQStDO0FBQy9DLFNBQVM7QUFDVCx1Q0FBdUM7QUFDdkMsZ0RBQWdEO0FBQ2hELG1EQUFtRDtBQUNuRCxrQkFBa0I7QUFDbEIsZ0JBQWdCO0FBQ2hCLGlEQUFpRDtBQUNqRCx5QkFBeUI7QUFDekIsb0NBQW9DO0FBQ3BDLGtEQUFrRDtBQUNsRCxnQ0FBZ0M7QUFDaEMsa0JBQWtCO0FBQ2xCLGlEQUFpRDtBQUNqRCwrQ0FBK0M7QUFDL0MsU0FBUztBQUNULDhEQUE4RDtBQUM5RCx3QkFBd0I7QUFDeEIsV0FBVztBQUNYLElBQUkiLCJzb3VyY2VzQ29udGVudCI6WyJjb25zdCBfID0gcmVxdWlyZSgnbG9kYXNoJylcbmNvbnN0IG1vbWVudCA9IHJlcXVpcmUoJ21vbWVudCcpXG4vLyBjb25zdCBzaG9ydGlkID0gcmVxdWlyZSgnc2hvcnRpZCcpXG5jb25zdCB1dWlkMSA9IHJlcXVpcmUoJ3V1aWQvdjEnKVxuY29uc3QgdXVpZDQgPSByZXF1aXJlKCd1dWlkL3Y0JylcbmltcG9ydCB7Tm9TcWxBbGl5dW5UYWJsZXN0b3JlfSBmcm9tICcuL2RyaXZlci9hbGl5dW5fb3RzJ1xuXG5jb25zdCBTRVNTSU9OX0tFWSA9ICdYSlNTRVNTSUQnIC8vIEFYSlMgU0VTU0lPTiBJROeahOe8qeWGme+8jEJYSlNTRVNTSUTkuLpCWEpTIFNFU1NJT04gSUTnsbvkvLzmlrnms5Xov5vooYzljLrliIbjgIJcblxuLy8gbm9kZS11dWlk5L2c6ICF5a+55LqOVVVJROeul+azleWUr+S4gOaAp+eahOino+mHiiBodHRwczovL2dpdGh1Yi5jb20va2VsZWt0aXYvbm9kZS11dWlkL2lzc3Vlcy84MlxuZnVuY3Rpb24gZ2VuZXJhdGVfc2Vzc2lvbl9pZCgpIHtcbiAgICAvLyDlrp7ot7XkuK3lj5HnjrBzaG9ydGlk566X5rOV5a2Y5Zyo5Yay56qB55qE5Y+v6IO95oCn5Li65LqG5L+d6Zmp5pyf6Ze056Gu5L+d5ZSv5LiA5oCn5aKe5Yqg566X5rOV5aSN5p2C5bqmXG4gICAgLy8g5paH5Lit6Kej6YeK5LqG5Li65LuA5LmIc2hvcnRpZOS8muWGsueqgeS6huS4jeaUr+aMgeWkmue6v+eoi+WSjOWkmuacuuWZqOW5tuWPkeWkhOeQhuWvvOiHtOeahCBodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy80Mjg0NDE1Mi93aGF0LWlzLXRoZS1ndWFyYW50ZWUtb2YtdW5pcXVlbmVzcy1vZi1zaG9ydGlkXG4gICAgLy8g5pS56L+b566X5rOV5b+F6aG76KaB5L2/55SodXVpZOaIlnNuYWtlZmxvd+etiemaj+acuueul+azleWunueOsChzbmFrZWZsb3fnrpfms5XkuZ/kuI3pgILlkIgpXG4gICAgLy8g5a+55LqOdXVpZDHlkow05Lik5Liq54mI5pys5L2/55So55qE5LuL57uNIGh0dHBzOi8vY2xvdWQudGVuY2VudC5jb20vZGV2ZWxvcGVyL2Fzay8yODEzMFxuICAgIC8vIGNvbnN0IGlkID0gc2hvcnRpZC5nZW5lcmF0ZSgpXG4gICAgLy8gcmV0dXJuIHhub3coJ1lZWVlNTURESEhtbXNzJykgKyBpZCArIHV1aWQoJ3Y0JylcbiAgICAvLyDnoa7kv50xMDAl5LiN5Yay56qB5Lul5Y+K5LiN5Y+v6aKE5rWL5oCn77yIdXVpZCB2MeehruS/neWIhuW4g+W8j+WUr+S4gOaAp++8jHV1aWR2NOehruS/nemaj+acuuaAp+S4jeWPr+mihOa1i++8iVxuICAgIHJldHVybiAodXVpZDEoe25zZWNzOiBNYXRoLmZsb29yKE1hdGgucmFuZG9tKCkgKiAxMDAwMCl9KSArIHV1aWQ0KCkpLnJlcGxhY2UoL1xcLS9nLCAnJylcbn1cblxuLy8gZnJhbWV3b3Jr5YaF6YOo5L2/55SoXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gX19mcmFtZXdvcmtfc2Vzc2lvbl9pbml0X18oaGVhZGVyPzogT2JqZWN0KSB7XG4gICAgLy8g5Zyo5qGG5p626K+35rGC5Yid5aeL5YyW55qE5pe25YCZ6Ieq5Yqo55Sf5oiQ5LiA5Liq5ZSv5LiA55qE5Lya6K+d6K+35rGC5qCH6K+G5bm25L+d5a2Y5Yiw5YWo5bGA5Y+Y6YePX19zZXNzaW9uX1/kuYvkuK3vvIzmraTlpITku4Xku4Xor7vlj5bmlbDmja7jgIJcbiAgICAvLyDlnKjmoYbmnrbor7fmsYLliJ3lp4vljJbnmoTml7blgJnmoLnmja7moYbmnrbljY/orq7nuqblrprku45jb29raWXmiJZoZWFkZXLlpLTpg6jlj5bliLDlrqLmiLfnq6/nmoTkvJror53moIfor4bvvIzlpoLmnpzlj5bkuI3liLDoh6rliqjph43mlrDnlJ/miJDkuIDkuKrmlrDmoIfor4bjgIJcblxuICAgIC8vIOWtkOi/m+eoi+aWueW8j+WKoOi9veWkhOeQhuacjeWKoemalOemu+WFqOWxgOWPmOmHj+eahOW9seWTjeaooeaLn+WUr+S4gOivt+axgumXrumimFxuICAgIC8vIOWPkeW4g2J4anPljIXmlK/mjIF0YWfmiZPljIViZXRh54mI5pys6YG/5YWN57q/5LiK5Y+R5biD5b2x5ZON6Zeu6aKYXG5cbiAgICAvLyBGSVhNRSDmraTlpITlhpnms5XkuI3mraPnoa7mtYvor5Xlh7rnur/kuIpGQ+eOr+Wig+eahOe8k+WtmOeKtuaAgeaYr+S4jea4heepuueahOS4i+asoeivt+axgui/h+adpeS8muWkjeeUqOeOr+Wig1xuICAgIC8vIOW8uuWItua4heepuuacjeWKoeerr+e8k+WtmOS/oeaBr+mBv+WFjeS4iuasoeivt+axgueahOaui+eVmeaVsOaNrlxuICAgIGdsb2JhbFsnX19zZXNzaW9uX18nXSA9IHt9XG4gICAgLy8g5by65Yi26YeN572u5YWo5bGA5Y+Y6YeP57yT5a2Y5Lit55qE55So5oi35L+h5oGvXG4gICAgZ2xvYmFsWydfX3VzZXJfXyddID0ge31cblxuICAgIGlmIChoZWFkZXIgJiYgaGVhZGVyW1NFU1NJT05fS0VZXSkge1xuICAgICAgICAvLyDlr7nkuo5SRVNU57G75Z6L6K+35rGC5LuOSEVBREVS5aS05LiK6I635Y+W5Lya6K+d5qCH6K+G77yI562J5ZCM5LqO5a6i5oi356uvT0FVVEgy55m75b2V6K6k6K+B5a+55bqU55qEQUNDRVNTU19UT0tFTuaVsOaNruaYr+exu+S8vOeahO+8iVxuICAgICAgICBnbG9iYWxbJ19fc2Vzc2lvbl9fJ10uaWQgPSBoZWFkZXJbU0VTU0lPTl9LRVldXG4gICAgfSBlbHNlIHtcbiAgICAgICAgLy8g5a+55LqOV0VC57G75Z6L6K+35rGC55qEQ09PS0lFU+S8muivneagh+ivhueahOe7n+S4gOiOt+WPluWPguaVsOmFjee9rlxuICAgICAgICBjb25zb2xlLmxvZygnd2Vi6K+35rGC6K6+572uc2Vzc2lvbmlkJylcbiAgICAgICAgZ2xvYmFsWydfX3Nlc3Npb25fXyddLmlkID0geGNvb2tpZShTRVNTSU9OX0tFWSlcbiAgICB9XG4gICAgY29uc29sZS5sb2coJ+iuvue9ruWujOaIkOWQjueahHNlc3Npb25pZDonLGdsb2JhbFsnX19zZXNzaW9uX18nXS5pZClcbiAgICBpZiAoIWdsb2JhbFsnX19zZXNzaW9uX18nXS5pZCkge1xuICAgICAgICAvLyDmlrDor7fmsYLkuLTml7bnlJ/miJDkuIDkuKrkvJror53moIfor4ZcbiAgICAgICAgZ2xvYmFsWydfX3Nlc3Npb25fXyddLmlkID0gZ2VuZXJhdGVfc2Vzc2lvbl9pZCgpXG4gICAgfVxuXG4gICAgLy8g5a+55LqO6ICB6K+35rGC6aKE5YWI6K+75Y+W5Ye6X191c2VyX1/kuK3nmoTkvJror53kv6Hmga/liLDlhoXlrZjkuK3mlrnkvr/lhajlsYDor7fmsYLlpITnkIZcbiAgICBjb25zdCBwYXJhbSA9IGF3YWl0IHhzZXNzaW9uLmdldCgnX191c2VyX18nKVxuICAgIGlmIChwYXJhbSkge1xuICAgICAgICBnbG9iYWxbJ19fdXNlcl9fJ10uaWQgPSBwYXJhbS5pZFxuICAgICAgICBnbG9iYWxbJ19fdXNlcl9fJ10ucGFyYW0gPSBwYXJhbVxuICAgIH1cblxuICAgIC8vIOWIt+aWsOWuouaIt+err+S/neWtmOWcqENPT0tJRVPkuYvkuK3nmoTkvJror53moIfor4bliLDmnJ/ml7bpl7RcbiAgICB4Y29va2llKFNFU1NJT05fS0VZLCBnbG9iYWxbJ19fc2Vzc2lvbl9fJ10uaWQpXG59XG5cbi8vIOWFqOWxgOmdmeaAgeexu+WvvOWHuue7meW6lOeUqOW8gOWPkeiAheS9v+eUqO+8iOiwg+eUqOW6lOeUqOS7o