@tableau/taco-toolkit
Version:
Tableau Connector Toolkit
567 lines • 38.5 kB
JavaScript
"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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