UNPKG

@tableau/taco-toolkit

Version:
567 lines 38.5 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 __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 (g && (g = 0, op[0] && (_ = 0)), _) 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 }; } }; var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TacoInitializer = void 0; var adm_zip_1 = __importDefault(require("adm-zip")); var crypto_js_1 = __importDefault(require("crypto-js")); var fs_extra_1 = __importDefault(require("fs-extra")); var node_fs_1 = __importDefault(require("node:fs")); var node_path_1 = __importDefault(require("node:path")); var uuid_1 = require("uuid"); var caller_type_1 = require("../../../shared/enums/caller-type"); var error_codes_1 = require("../../../shared/errors/error-codes"); var error_source_1 = require("../../../shared/errors/error-source"); var status_code_1 = require("../../../shared/errors/status-code"); var eps_logger_1 = require("../../logging/eps-logger"); var localization_adapter_1 = require("./localization-adapter"); var connector_config_1 = require("./connector-config"); var TacoInitializer = /** @class */ (function () { function TacoInitializer(connectionManager, config) { // TODO: hmm, it never evicts any entry? The taco init is on connection basis. // It is trying assign an unique name to connector for each connection. // Basically, this guy has memory leak, since the set and maps below never remove anything. /** Set of distinct connector names that have been initialized */ this.initializedSet = new Set(); /** Distinct name by path. Used only on server because path cannot be reused. */ this.initializedMap = new Map(); /** map Distinct connector name to its dest app path to adapt to localization context from the monolith */ this.connectorAppPathMap = new Map(); this.supportedLanguages = [ 'ar_YE', 'da_DK', 'de_DE', 'en_GB', 'en_US', 'es_ES', 'es_MX', 'fi_FI', 'fr_CA', 'fr_FR', 'ga_IE', 'it_IT', 'ja_JP', 'ko_KR', 'nb_NO', 'nl_NL', 'pt_BR', 'ru_RU', 'sv_SE', 'th_TH', 'zh_CN', 'zh_TW', ]; this.supportedLocales = [ 'ar_AE', 'ar_BH', 'ar_DZ', 'ar_EG', 'ar_IQ', 'ar_JO', 'ar_KW', 'ar_LB', 'ar_LY', 'ar_MA', 'ar_OM', 'ar_QA', 'ar_SA', 'ar_SD', 'ar_SY', 'ar_TN', 'ar_YE', 'be_BY', 'bg_BG', 'ca_ES', 'cs_CZ', 'da_DK', 'de_AT', 'de_CH', 'de_DE', 'de_LU', 'el_CY', 'el_GR', 'en_AU', 'en_CA', 'en_GB', 'en_IE', 'en_IN', 'en_MT', 'en_NZ', 'en_PH', 'en_SG', 'en_US', 'en_ZA', 'es_AR', 'es_BO', 'es_CL', 'es_CO', 'es_CR', 'es_DO', 'es_EC', 'es_ES', 'es_GT', 'es_HN', 'es_MX', 'es_NI', 'es_PA', 'es_PE', 'es_PR', 'es_PY', 'es_SV', 'es_US', 'es_UY', 'es_VE', 'et_EE', 'fi_FI', 'fr_BE', 'fr_CA', 'fr_CH', 'fr_FR', 'fr_LU', 'ga_IE', 'he_IL', 'hi_IN', 'hr_HR', 'hu_HU', 'id_ID', 'in_ID', 'is_IS', 'it_CH', 'it_IT', 'iw_IL', 'ja_JP', 'ko_KR', 'lt_LT', 'lv_LV', 'mk_MK', 'ms_MY', 'mt_MT', 'nb_NO', 'nl_BE', 'nl_NL', 'no_NO', 'pl_PL', 'pt_BR', 'pt_PT', 'ro_RO', 'ru_RU', 'sk_SK', 'sl_SI', 'sq_AL', 'sr_BA', 'sr_ME', 'sr_RS', 'sv_SE', 'th_TH', 'tr_TR', 'uk_UA', 'vi_VN', 'zh_CN', 'zh_HK', 'zh_SG', 'zh_TW', ]; this.connectionManager = connectionManager; this.i18nConfig = config.i18n; this.callerType = config.callerType; this.appDir = resolveAndResetPath(config.appDir); this.handlersDir = resolveAndResetPath(config.handlersDir); } TacoInitializer.prototype.makeDistinctConnectorName = function (connectorClass, sourcePath) { return __awaiter(this, void 0, void 0, function () { var hashCode; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.getHashCode(sourcePath)]; case 1: hashCode = _a.sent(); return [2 /*return*/, "".concat(connectorClass, "-").concat(hashCode)]; } }); }); }; TacoInitializer.prototype.getHashCode = function (sourcePath) { return __awaiter(this, void 0, void 0, function () { var raw, arr, wordArray, hashCode, err_1; return __generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 5, , 6]); raw = void 0; return [4 /*yield*/, node_fs_1.default.promises.stat(sourcePath)]; case 1: if (!(_a.sent()).isDirectory()) return [3 /*break*/, 2]; raw = Buffer.from(sourcePath, 'utf-8'); // if not a file (dev mode), we hash the path. return [3 /*break*/, 4]; case 2: return [4 /*yield*/, fs_extra_1.default.readFile(sourcePath)]; case 3: raw = _a.sent(); _a.label = 4; case 4: arr = new Int32Array(raw); wordArray = crypto_js_1.default.lib.WordArray.create(Array.from(arr)); hashCode = crypto_js_1.default.SHA256(wordArray).toString(); return [2 /*return*/, hashCode]; case 5: err_1 = _a.sent(); throw new Error("Error reading ".concat(sourcePath, ": ").concat(err_1)); case 6: return [2 /*return*/]; } }); }); }; TacoInitializer.prototype.getOrInitializeDistinctConnectorName = function (_a) { var connectionId = _a.connectionId, connectorClass = _a.connectorClass, tacoPath = _a.tacoPath; return __awaiter(this, void 0, void 0, function () { var distinctConnectorName; return __generator(this, function (_b) { switch (_b.label) { case 0: distinctConnectorName = this.connectionManager.distinctConnectorName(connectionId); if (!(!distinctConnectorName || this.isDirectoryPurged(distinctConnectorName))) return [3 /*break*/, 2]; return [4 /*yield*/, this.ensureTacoInitialized(connectionId, connectorClass, tacoPath)]; case 1: distinctConnectorName = _b.sent(); _b.label = 2; case 2: return [2 /*return*/, distinctConnectorName]; } }); }); }; TacoInitializer.prototype.getConnectorJsonPath = function (distinctConnectorName) { var handlersPath = this.getConnectorHandlersPath(distinctConnectorName); return node_path_1.default.join(handlersPath, 'connector.json'); }; TacoInitializer.prototype.getConnectorHandlersPath = function (distinctConnectorName) { return node_path_1.default.join(this.handlersDir, distinctConnectorName); }; TacoInitializer.prototype.getConnectorConfig = function (distinctConnectorName) { var connectorJsonPath = this.getConnectorJsonPath(distinctConnectorName); return (0, connector_config_1.getConnectorConfig)(connectorJsonPath); }; /** * Ensure localized connectors are adapted for localization resources. * - no-opt for non-localized connectors * - process localized connectors for calls: * 1) /launch * 2) /web-authoring/app-url * @param distinctConnectorName * @param language * @param locale */ TacoInitializer.prototype.adaptLocalizedConnector = function (distinctConnectorName, _a) { var language = _a.language, locale = _a.locale; // [i18n] adapt connectors based on user runtime localization context passed from the monolith var connectorAppPath = this.connectorAppPathMap.get(distinctConnectorName); if (connectorAppPath === undefined) { eps_logger_1.Logger.error("[i18n] cannot find dest app folder of distinct connector wtih name ".concat(distinctConnectorName)); return ''; } else { return (0, localization_adapter_1.adaptConnectorForLocalization)(distinctConnectorName, connectorAppPath, language, locale, this.i18nConfig.language, this.i18nConfig.locale); } }; /** * Ensure connector initialized. * @param connectionId * @param connectorClass * @param tacoPath * @returns distinct connector name, i.e., connectorClass-SHA */ TacoInitializer.prototype.ensureTacoInitialized = function (connectionId, connectorClass, tacoPath) { return __awaiter(this, void 0, void 0, function () { var distinctName; return __generator(this, function (_a) { switch (_a.label) { case 0: // TODO: why are we doing this? From the code, initializedMap is never used. // The distinctName will always be undefined from the if block... if (this.callerType === caller_type_1.CallerType.vizQLServer) { distinctName = this.initializedMap.get(tacoPath); } else { if (!connectionId) { throw new Error("Connection id should not be null or empty when caller type is ".concat(this.callerType)); } distinctName = this.connectionManager.distinctConnectorName(connectionId); } if (!!distinctName) return [3 /*break*/, 2]; return [4 /*yield*/, this.makeDistinctConnectorName(connectorClass, tacoPath)]; case 1: distinctName = _a.sent(); if (connectionId) { this.connectionManager.setDistinctConnectorNameAndPath(connectionId, distinctName, tacoPath); } _a.label = 2; case 2: if (!(!this.initializedSet.has(distinctName) || this.isDirectoryPurged(distinctName))) return [3 /*break*/, 4]; return [4 /*yield*/, this.initialize({ connectorClass: connectorClass, distinctName: distinctName, sourcePath: tacoPath, })]; case 3: _a.sent(); _a.label = 4; case 4: eps_logger_1.Logger.info("TACO ".concat(connectorClass, " is initialized")); return [2 /*return*/, distinctName]; } }); }); }; TacoInitializer.prototype.extractLocales = function (connectorName) { var e_1, _a, e_2, _b; var connectorAppPath = this.connectorAppPathMap.get(connectorName); if (connectorAppPath === undefined) { eps_logger_1.Logger.error("[i18n] cannot find dest app folder of distinct connector wtih name ".concat(connectorName)); } else { try { for (var _c = __values(this.supportedLanguages), _d = _c.next(); !_d.done; _d = _c.next()) { var supportedLanguage = _d.value; try { for (var _e = (e_2 = void 0, __values(this.supportedLocales)), _f = _e.next(); !_f.done; _f = _e.next()) { var supportedLocale = _f.value; var index = (0, localization_adapter_1.adaptConnectorForLocalization)(connectorName, connectorAppPath, supportedLanguage, supportedLocale, this.i18nConfig.language, this.i18nConfig.locale); eps_logger_1.Logger.info('extracted locale to ' + index); } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (_f && !_f.done && (_b = _e.return)) _b.call(_e); } finally { if (e_2) throw e_2.error; } } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (_d && !_d.done && (_a = _c.return)) _a.call(_c); } finally { if (e_1) throw e_1.error; } } } }; TacoInitializer.prototype.initialize = function (_a) { var connectorClass = _a.connectorClass, sourcePath = _a.sourcePath, distinctName = _a.distinctName; return __awaiter(this, void 0, void 0, function () { var appConnDest, handlersConnDest, srcDestPairs, isTaco, err, tempDir, zip, srcDestPairs_1, srcDestPairs_1_1, _b, srcTail, destDir, srcDir, e_3_1, e_4, err, srcDestPairs_2, srcDestPairs_2_1, _c, srcTail, destDir, srcDir, err, e_5_1, e_6, err; var e_3, _d, e_5, _e; return __generator(this, function (_f) { switch (_f.label) { case 0: eps_logger_1.Logger.info("TacoInitializer initializing '".concat(connectorClass, "' from '").concat(sourcePath, "'")); appConnDest = node_path_1.default.join(this.appDir, distinctName); handlersConnDest = node_path_1.default.join(this.handlersDir, distinctName); srcDestPairs = [ ['dist/app', appConnDest], ['dist/handlers', handlersConnDest], ]; isTaco = node_path_1.default.extname(sourcePath).toLowerCase() === '.taco'; if (!isTaco) return [3 /*break*/, 13]; eps_logger_1.Logger.info('Connector is a packaged .taco file'); if (!node_fs_1.default.existsSync(sourcePath)) { err = new Error("The '".concat(connectorClass, "' taco could not be found at '").concat(sourcePath, "'.")); eps_logger_1.Logger.logEPSError(status_code_1.StatusCode.NotFound, error_source_1.ErrorSources.Configuration, error_codes_1.ErrorCodes.IYZVJGZ2, err); throw err; } eps_logger_1.Logger.info("Path found ".concat(sourcePath)); _f.label = 1; case 1: _f.trys.push([1, 11, , 12]); tempDir = node_path_1.default.join(this.appDir, '..', connectorClass + '-' + (0, uuid_1.v4)()); zip = new adm_zip_1.default(sourcePath); zip.extractAllTo(tempDir, true); _f.label = 2; case 2: _f.trys.push([2, 7, 8, 9]); srcDestPairs_1 = __values(srcDestPairs), srcDestPairs_1_1 = srcDestPairs_1.next(); _f.label = 3; case 3: if (!!srcDestPairs_1_1.done) return [3 /*break*/, 6]; _b = __read(srcDestPairs_1_1.value, 2), srcTail = _b[0], destDir = _b[1]; srcDir = node_path_1.default.join(tempDir, srcTail); return [4 /*yield*/, fs_extra_1.default.move(srcDir, destDir, { overwrite: true })]; case 4: _f.sent(); _f.label = 5; case 5: srcDestPairs_1_1 = srcDestPairs_1.next(); return [3 /*break*/, 3]; case 6: return [3 /*break*/, 9]; case 7: e_3_1 = _f.sent(); e_3 = { error: e_3_1 }; return [3 /*break*/, 9]; case 8: try { if (srcDestPairs_1_1 && !srcDestPairs_1_1.done && (_d = srcDestPairs_1.return)) _d.call(srcDestPairs_1); } finally { if (e_3) throw e_3.error; } return [7 /*endfinally*/]; case 9: return [4 /*yield*/, fs_extra_1.default.rm(tempDir, { recursive: true })]; case 10: _f.sent(); return [3 /*break*/, 12]; case 11: e_4 = _f.sent(); err = new Error("Failed to extract '".concat(connectorClass, "' taco at '").concat(sourcePath, "': ").concat(e_4)); eps_logger_1.Logger.logEPSError(status_code_1.StatusCode.Aborted, error_source_1.ErrorSources.Client, error_codes_1.ErrorCodes.A0WRFSSS, err); throw err; case 12: return [3 /*break*/, 24]; case 13: eps_logger_1.Logger.info('Connector is an unpacked taco folder'); _f.label = 14; case 14: _f.trys.push([14, 23, , 24]); _f.label = 15; case 15: _f.trys.push([15, 20, 21, 22]); srcDestPairs_2 = __values(srcDestPairs), srcDestPairs_2_1 = srcDestPairs_2.next(); _f.label = 16; case 16: if (!!srcDestPairs_2_1.done) return [3 /*break*/, 19]; _c = __read(srcDestPairs_2_1.value, 2), srcTail = _c[0], destDir = _c[1]; srcDir = node_path_1.default.join(sourcePath, srcTail); if (!node_fs_1.default.existsSync(srcDir)) { err = new Error("'".concat(srcTail, "' resources not found at '").concat(srcDir, "'")); eps_logger_1.Logger.logEPSError(status_code_1.StatusCode.NotFound, error_source_1.ErrorSources.Client, error_codes_1.ErrorCodes.OHD0EC76, err); throw err; } eps_logger_1.Logger.info("Path found: ".concat(srcDir)); return [4 /*yield*/, fs_extra_1.default.copy(srcDir, destDir)]; case 17: _f.sent(); _f.label = 18; case 18: srcDestPairs_2_1 = srcDestPairs_2.next(); return [3 /*break*/, 16]; case 19: return [3 /*break*/, 22]; case 20: e_5_1 = _f.sent(); e_5 = { error: e_5_1 }; return [3 /*break*/, 22]; case 21: try { if (srcDestPairs_2_1 && !srcDestPairs_2_1.done && (_e = srcDestPairs_2.return)) _e.call(srcDestPairs_2); } finally { if (e_5) throw e_5.error; } return [7 /*endfinally*/]; case 22: return [3 /*break*/, 24]; case 23: e_6 = _f.sent(); err = new Error("Could not process '".concat(connectorClass, "' taco: ").concat(e_6)); eps_logger_1.Logger.logEPSError(status_code_1.StatusCode.Aborted, error_source_1.ErrorSources.Client, error_codes_1.ErrorCodes.H4XI3IY9, err); throw err; case 24: this.initializedSet.add(distinctName); this.connectorAppPathMap.set(distinctName, appConnDest); this.extractLocales(distinctName); return [2 /*return*/]; } }); }); }; /** Checks if the server has deleted the files in the leaf directories used by the connector. */ TacoInitializer.prototype.isDirectoryPurged = function (distinctName) { var e_7, _a; try { for (var _b = __values([this.appDir, this.handlersDir]), _c = _b.next(); !_c.done; _c = _b.next()) { var baseDir = _c.value; var requiredFile = node_path_1.default.join(baseDir, distinctName, 'connector.json'); // TODO: the logic does not seem right. Don't we need app and handlers both exist? if (!node_fs_1.default.existsSync(requiredFile)) { return true; } } } catch (e_7_1) { e_7 = { error: e_7_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_7) throw e_7.error; } } return false; }; return TacoInitializer; }()); exports.TacoInitializer = TacoInitializer; /** * Resolve the dirPath, and removes the existing dir from the disk. */ function resolveAndResetPath(dirPath) { var absPath = node_path_1.default.resolve(dirPath); if (node_fs_1.default.existsSync(absPath)) { node_fs_1.default.rmSync(absPath, { recursive: true }); } return absPath; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGFjby1pbml0aWFsaXplci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9lcHMvbWFpbi9jb21wb25lbnRzL3RhY28taW5pdGlhbGl6ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsb0RBQTRCO0FBQzVCLHdEQUFnQztBQUNoQyxzREFBMEI7QUFDMUIsb0RBQXdCO0FBQ3hCLHdEQUE0QjtBQUM1Qiw2QkFBeUI7QUFFekIsaUVBQThEO0FBQzlELGtFQUErRDtBQUMvRCxvRUFBa0U7QUFDbEUsa0VBQStEO0FBQy9ELHVEQUFpRDtBQUdqRCwrREFBc0U7QUFDdEUsdURBQXVEO0FBU3ZEO0lBMkpFLHlCQUFZLGlCQUFvQyxFQUFFLE1BQTZCO1FBbEovRSw4RUFBOEU7UUFDOUUsdUVBQXVFO1FBQ3ZFLDJGQUEyRjtRQUUzRixpRUFBaUU7UUFDekQsbUJBQWMsR0FBRyxJQUFJLEdBQUcsRUFBVSxDQUFBO1FBQzFDLGdGQUFnRjtRQUN4RSxtQkFBYyxHQUFHLElBQUksR0FBRyxFQUFrQixDQUFBO1FBQ2xELDBHQUEwRztRQUNsRyx3QkFBbUIsR0FBRyxJQUFJLEdBQUcsRUFBa0IsQ0FBQTtRQUUvQyx1QkFBa0IsR0FBa0I7WUFDMUMsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztTQUNSLENBQUE7UUFDTyxxQkFBZ0IsR0FBa0I7WUFDeEMsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQLE9BQU87WUFDUCxPQUFPO1lBQ1AsT0FBTztZQUNQ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