@tableau/taco-toolkit
Version:
Tableau Connector Toolkit
189 lines • 17.8 kB
JavaScript
;
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 __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getLaunchURL = exports.handleLaunch = exports.onLaunchExtracted = void 0;
var http_status_codes_1 = require("http-status-codes");
var node_url_1 = require("node:url");
var connector_launch_status_1 = require("../../../../shared/enums/connector-launch-status");
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_error_1 = require("../../../types/eps-error");
var eps_error_info_1 = require("../../../types/eps-error-info");
var eps_logger_1 = require("../../../logging/eps-logger");
var extractor_launch_options_1 = require("../../../types/extractor-launch-options");
var position_size_1 = require("../../../types/position-size");
var api_utils_1 = require("../../../utils/api-utils");
var render_ui_launcher_1 = require("../../components/render-ui-launcher");
var app_global_1 = __importDefault(require("../../core/app-global"));
var handler_utils_1 = require("./handler-utils");
function onLaunchExtracted(options) {
if ((0, extractor_launch_options_1.isExtractorLaunchFailureOptions)(options)) {
return {
httpStatusCode: http_status_codes_1.StatusCodes.OK,
response: new eps_error_1.EPSError(options.error, new eps_error_info_1.EPSErrorInfo(handler_utils_1.ERROR_CODE_GENERAL_EXTRACTOR), connector_launch_status_1.ConnectorLaunchStatus.Error),
};
}
var extractorType = options.extractorType, extractorMessage = options.extractorMessage;
eps_logger_1.Logger.info(['Extractor Launched', extractorMessage]);
return {
httpStatusCode: http_status_codes_1.StatusCodes.CREATED,
response: {
extractorType: extractorType,
port: extractorMessage.port,
extractorId: extractorMessage.extractorId,
status: connector_launch_status_1.ConnectorLaunchStatus.Extract,
connectionData: options.connectionData,
secrets: options.secrets,
},
};
}
exports.onLaunchExtracted = onLaunchExtracted;
function handleLaunch(extractorLaunchedEvent, launchHandlerRequest) {
return __awaiter(this, void 0, void 0, function () {
var epsServiceInfo, launchUIOptions, connectionId, launchExtractReponse_1, extractorLaunchedConnectionId, err_1, errString, error;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
epsServiceInfo = launchHandlerRequest.epsServiceInfo, launchUIOptions = launchHandlerRequest.launchUIOptions;
connectionId = launchUIOptions.connectionId;
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
launchExtractReponse_1 = undefined;
extractorLaunchedConnectionId = (0, handler_utils_1.getExtractorLaunchedConnectionId)(connectionId);
eps_logger_1.Logger.info("Registering event listener ".concat(extractorLaunchedConnectionId));
extractorLaunchedEvent.once(extractorLaunchedConnectionId, function (options) {
launchExtractReponse_1 = onLaunchExtracted(options);
});
app_global_1.default.connectionManager.setIsSubmitted(connectionId, false);
return [4 /*yield*/, launchUI(launchUIOptions, epsServiceInfo)
// Test if NOT submitted and then respond with canceled
];
case 2:
_a.sent();
// Test if NOT submitted and then respond with canceled
if (!app_global_1.default.connectionManager.isSubmitted(connectionId)) {
// If not, user has canceled by closing browser tab.
// Remove extract event listener (prevent mem. leak).
extractorLaunchedEvent.removeAllListeners(extractorLaunchedConnectionId);
return [2 /*return*/, {
httpStatusCode: http_status_codes_1.StatusCodes.OK,
response: { status: connector_launch_status_1.ConnectorLaunchStatus.Canceled },
}];
}
return [2 /*return*/, launchExtractReponse_1];
case 3:
err_1 = _a.sent();
errString = "Error occurred while launching connector UI: ".concat(err_1);
error = errString;
if (err_1 instanceof Error) {
error = err_1;
error.message = errString;
}
eps_logger_1.Logger.logEPSError(status_code_1.StatusCode.Cancelled, error_source_1.ErrorSources.Client, error_codes_1.ErrorCodes.WT2WHBA0, error);
// This always hits at least once because reject() called on exit.
return [2 /*return*/, {
httpStatusCode: http_status_codes_1.StatusCodes.INTERNAL_SERVER_ERROR,
response: new eps_error_1.EPSError(errString, new eps_error_info_1.EPSErrorInfo(handler_utils_1.ERROR_CODE_GENERAL_EXTRACTOR), connector_launch_status_1.ConnectorLaunchStatus.Error, launchUIOptions.connectorClass),
}];
case 4: return [2 /*return*/];
}
});
});
}
exports.handleLaunch = handleLaunch;
function launchUI(options, epsServiceInfo) {
return __awaiter(this, void 0, void 0, function () {
var distinctConnectorName, url, urlString, windowSize, position, appLauncher;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, app_global_1.default.tacoInitializer.getOrInitializeDistinctConnectorName({
connectionId: options.connectionId,
connectorClass: options.connectorClass,
tacoPath: options.tacoPath,
})];
case 1:
distinctConnectorName = _a.sent();
app_global_1.default.tacoInitializer.adaptLocalizedConnector(distinctConnectorName, options.i18n);
url = getLaunchURL(distinctConnectorName, options, epsServiceInfo);
urlString = url.toString();
eps_logger_1.Logger.info("Static Server Url: ".concat((0, api_utils_1.maskURL)(urlString)));
windowSize = app_global_1.default.tacoInitializer.getConnectorConfig(distinctConnectorName).windowSize;
position = (0, position_size_1.centerOnParent)(windowSize, options.parentPositionSize);
appLauncher = new render_ui_launcher_1.ConnectorAppLauncher(options.connectionId, urlString, position, windowSize);
return [4 /*yield*/, appLauncher.start()];
case 2:
_a.sent();
return [2 /*return*/];
}
});
});
}
function getLaunchURL(distinctConnectorName, options, epsServiceInfo) {
var callerId = epsServiceInfo.callerId, epsInstanceId = epsServiceInfo.epsInstanceId, staticServerId = epsServiceInfo.staticServerId, staticServerPort = epsServiceInfo.staticServerPort, epsApiPort = epsServiceInfo.epsApiPort, browserWatcherPort = epsServiceInfo.browserWatcherPort;
var url = new node_url_1.URL("http://localhost:".concat(staticServerPort, "/").concat(staticServerId, "/").concat(distinctConnectorName, "/"));
url.searchParams.append('connectionId', options.connectionId);
url.searchParams.append('callerId', callerId);
url.searchParams.append('epsInstanceId', epsInstanceId);
url.searchParams.append('connectorClass', options.connectorClass);
url.searchParams.append('serverUrl', "http://localhost:".concat(epsApiPort));
url.searchParams.append('browserWatcherUrl', "ws://localhost:".concat(browserWatcherPort));
// tacoPath may contain unicode chars on non-english machine.
// encode all unicode chars here, so that it can be added into the header on browsers.
var tacoPath = encodeURIComponent(options.tacoPath);
url.searchParams.append('tacoPath', tacoPath);
if (options.connectionData) {
url.searchParams.append('connectionData', JSON.stringify(options.connectionData));
}
if (options.secrets) {
url.searchParams.append('secrets', JSON.stringify(options.secrets));
}
// [i18n] pass user localization runtime info for client-side sdk usages
if (options.i18n) {
var _a = options.i18n, language = _a.language, locale = _a.locale;
url.searchParams.append('language', language);
url.searchParams.append('locale', locale);
}
return url;
}
exports.getLaunchURL = getLaunchURL;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGF1bmNoLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vc3JjL2Vwcy9tYWluL2FwaS9lcHMtaGFuZGxlcnMvbGF1bmNoLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLHVEQUErQztBQUUvQyxxQ0FBOEI7QUFFOUIsNEZBQXdGO0FBQ3hGLHFFQUFrRTtBQUNsRSx1RUFBcUU7QUFDckUscUVBQWtFO0FBRWxFLHNEQUFtRDtBQUNuRCxnRUFBNEQ7QUFHNUQsMERBQW9EO0FBRXBELG9GQUlnRDtBQUVoRCw4REFBMkU7QUFFM0Usc0RBQWtEO0FBQ2xELDBFQUEwRTtBQUMxRSxxRUFBNkM7QUFDN0MsaURBQWdHO0FBa0JoRyxTQUFnQixpQkFBaUIsQ0FDL0IsT0FBc0U7SUFFdEUsSUFBSSxJQUFBLDBEQUErQixFQUFDLE9BQU8sQ0FBQyxFQUFFO1FBQzVDLE9BQU87WUFDTCxjQUFjLEVBQUUsK0JBQVcsQ0FBQyxFQUFFO1lBQzlCLFFBQVEsRUFBRSxJQUFJLG9CQUFRLENBQ3BCLE9BQU8sQ0FBQyxLQUFLLEVBQ2IsSUFBSSw2QkFBWSxDQUFDLDRDQUE0QixDQUFDLEVBQzlDLCtDQUFxQixDQUFDLEtBQUssQ0FDNUI7U0FDRixDQUFBO0tBQ0Y7SUFFTyxJQUFBLGFBQWEsR0FBdUIsT0FBTyxjQUE5QixFQUFFLGdCQUFnQixHQUFLLE9BQU8saUJBQVosQ0FBWTtJQUNuRCxtQkFBTSxDQUFDLElBQUksQ0FBQyxDQUFDLG9CQUFvQixFQUFFLGdCQUFnQixDQUFDLENBQUMsQ0FBQTtJQUVyRCxPQUFPO1FBQ0wsY0FBYyxFQUFFLCtCQUFXLENBQUMsT0FBTztRQUNuQyxRQUFRLEVBQUU7WUFDUixhQUFhLGVBQUE7WUFDYixJQUFJLEVBQUUsZ0JBQWdCLENBQUMsSUFBSTtZQUMzQixXQUFXLEVBQUUsZ0JBQWdCLENBQUMsV0FBVztZQUN6QyxNQUFNLEVB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