UNPKG

ask-cli

Version:

Alexa Skills Kit (ASK) Command Line Interfaces

240 lines (239 loc) 12.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createHostedSkill = exports.validateUserQualification = void 0; const fs_1 = __importDefault(require("fs")); const open_1 = __importDefault(require("open")); const path_1 = __importDefault(require("path")); const url_1 = require("url"); const constants_1 = require("../../utils/constants"); const local_host_server_1 = __importDefault(require("../../utils/local-host-server")); const string_utils_1 = __importDefault(require("../../utils/string-utils")); const messenger_1 = __importDefault(require("../../view/messenger")); const spinner_view_1 = __importDefault(require("../../view/spinner-view")); const skill_metadata_controller_1 = __importDefault(require("../../controllers/skill-metadata-controller")); const portscanner_1 = __importDefault(require("portscanner")); const permissionStatusMap = new Map(); permissionStatusMap.set(constants_1.HOSTED_SKILL.PERMISSION_CHECK_RESULT.NEW_USER_REGISTRATION_REQUIRED, new Error("Failed to validate CAPTCHA. Please try again.")); permissionStatusMap.set(constants_1.HOSTED_SKILL.PERMISSION_CHECK_RESULT.RESOURCE_LIMIT_EXCEEDED, new Error("Your hosted skills account is limited to a certain number of new hosted skills per minute. Please try again later.")); permissionStatusMap.set(constants_1.HOSTED_SKILL.PERMISSION_CHECK_RESULT.ALLOWED, undefined); /** * To manage check permission and captcha validation for first time user * @param {string} vendorId The vendor ID * @param {Object} hostedSkillController The controller containing hosted skill main functionalities * @param {callback} callback { error, response } */ function validateUserQualification(vendorId, hostedSkillController, callback) { _checkPermission(hostedSkillController, vendorId, false, (error, actionUrl) => { if (error) { callback(error); } else { !actionUrl ? callback(null) : _solveCaptcha(vendorId, actionUrl, (captchaValidateError) => { captchaValidateError ? callback(captchaValidateError) : _checkPermission(hostedSkillController, vendorId, true, (reCheckPermissionError) => { if (reCheckPermissionError) { callback(reCheckPermissionError); } else { messenger_1.default.getInstance().info("CAPTCHA validation was successfully completed. You are able to create a Alexa hosted skill."); callback(null); } }); }); } }); } exports.validateUserQualification = validateUserQualification; /** * To create an Alexa hosted skill and clone it into local machine * @param {Object} hostedSkillController The controller containing hosted skill main functionalities * @param {NewSkillUserInput} userInput The user input { deploymentType, language, projectFolderName, skillName } * @param {string} vendorId The vendor ID * @param {callback} callback { error, response } */ function createHostedSkill(hostedSkillController, userInput, vendorId, callback) { const rootPath = process.cwd(); const projectPath = path_1.default.join(rootPath, userInput.projectFolderName); if (fs_1.default.existsSync(projectPath)) { callback(new Error(`${projectPath} directory already exists.`)); } else { const { profile, doDebug } = hostedSkillController; const skillMetadataController = new skill_metadata_controller_1.default({ profile, doDebug }); const manifest = _updateManifest(userInput); const input = { vendorId, manifest, runtime: userInput.language, region: userInput.region, }; const listenSpinner = new spinner_view_1.default(); listenSpinner.start("Creating your Alexa hosted skill. It will take about a minute."); hostedSkillController.createSkill(input, (createErr, skillId) => { listenSpinner.terminate(); if (createErr) { !skillId ? callback(createErr) : hostedSkillController.deleteSkill(skillId, callback(createErr)); } else { hostedSkillController.updateAskSystemScripts((scriptErr) => { scriptErr ? callback(scriptErr) : hostedSkillController.clone(skillId, userInput.skillName, projectPath, (cloneErr) => { cloneErr ? callback(cloneErr) : skillMetadataController.enableSkill((enableErr) => { enableErr ? callback(enableErr) : hostedSkillController.updateSkillPrePushScript(userInput.projectFolderName, (hooksErr) => { hooksErr ? callback(hooksErr) : callback(null, skillId); }); }); }); }); } }); } } exports.createHostedSkill = createHostedSkill; /** * To update hosted skill manifest template with user input * and return the manifest object. * @param {Object} userInput object containing skillName * @returns {Manifest} an updated manifest including default apis and updated publishingInformation */ function _updateManifest(userInput) { const skillName = userInput.skillName; const locale = userInput.locale || constants_1.HOSTED_SKILL.DEFAULT_LOCALE; const manifest = constants_1.HOSTED_SKILL.MANIFEST; manifest.publishingInformation.locales[locale] = {}; manifest.publishingInformation.locales[locale].name = skillName; return manifest; } /** * Check user's permission to access hosted skill and handle error messages * @param {Object} hostedSkillController The controller containing hosted skill main functionalities * @param {boolean} isReCheck the recheck flag * @param {callback} callback { error, response } */ function _checkPermission(hostedSkillController, vendorId, isReCheck, callback) { hostedSkillController.getHostedSkillPermission(vendorId, constants_1.HOSTED_SKILL.PERMISSION_ENUM.NEW_SKILL, (permissionError, permissionRes) => { if (permissionError) { callback(permissionError); } else if (!isReCheck && permissionRes.status === constants_1.HOSTED_SKILL.PERMISSION_CHECK_RESULT.NEW_USER_REGISTRATION_REQUIRED) { messenger_1.default.getInstance().info("CAPTCHA validation is required for a new hosted skill user."); callback(null, permissionRes.actionUrl); } else { callback(permissionStatusMap.get(permissionRes.status) || null); } }); } /** * To navigate user to the captcha validation page * @param {string} vendorId The Vendor ID * @param {string} captchaUrl The captcha url * @param {callback} callback { error, response } */ function _solveCaptcha(vendorId, captchaUrl, callback) { messenger_1.default.getInstance().info("Go to the CAPTCHA page, confirm that you are signed into the correct developer account, and solve the CAPTCHA.\n" + "If your browser does not open the page, quit this process, paste the following url into your browser, " + `and complete the CAPTCHA.\n${captchaUrl}`); portscanner_1.default.checkPortStatus(Number.parseInt(constants_1.LOCALHOST_PORT), (err, status) => { if (err) { callback(err); } else { if (status === "closed") { _openLoginUrlWithRedirectLink(captchaUrl, vendorId); _listenResponseFromCaptchaServer(constants_1.LOCALHOST_PORT, (error) => { callback(error); }); } else { callback(new Error(`${constants_1.LOCALHOST_PORT} port on localhost has been occupied, ` + "ask-cli cannot start a local server for receiving authorization code.\n" + `Please either abort any processes running on port ${constants_1.LOCALHOST_PORT} ` + "or add `--no-browser` flag to the command as an alternative approach.")); } } }); } /** * Build and open the url that navigates user to captcha validation page after login. * @param captcha the validation url * @param vendorId the vendor Id */ function _openLoginUrlWithRedirectLink(captchaUrl, vendorId) { const envVarSignInHost = process.env.ASK_LWA_AUTHORIZE_HOST; const loginUrl = new url_1.URL(envVarSignInHost && string_utils_1.default.isNonBlankString(envVarSignInHost) ? envVarSignInHost + constants_1.HOSTED_SKILL.SIGNIN_PATH : constants_1.LWA.SIGNIN_URL); loginUrl.search = new url_1.URLSearchParams([ ["openid.ns", "http://specs.openid.net/auth/2.0"], ["openid.mode", "checkid_setup"], ["openid.claimed_id", "http://specs.openid.net/auth/2.0/identifier_select"], ["openid.identity", "http://specs.openid.net/auth/2.0/identifier_select"], ["openid.assoc_handle", "amzn_dante_us"], ["openid.return_to", `${captchaUrl}?vendor_id=${vendorId}&redirect_url=http://127.0.0.1:9090/captcha`], ["openid.pape.max_auth_age", "7200"], ]).toString(); (0, open_1.default)(loginUrl.href); } /** * Start a local server and listen the response from the captcha validation server, * then extract validation result from it. * @param PORT * @param callback with error */ function _listenResponseFromCaptchaServer(PORT, callback) { const listenSpinner = new spinner_view_1.default(); const server = new local_host_server_1.default(PORT); server.create(handleServerRequest); server.listen(() => { listenSpinner.start(` Listening on http://localhost:${PORT}...`); }); server.registerEvent("connection", (socket) => { socket.unref(); }); function handleServerRequest(request, response) { var _a, _b, _c, _d; response.on("close", () => { request.socket.destroy(); }); listenSpinner.terminate(); server.destroy(); if ((_a = request.url) === null || _a === void 0 ? void 0 : _a.startsWith("/captcha?success")) { response.end("CAPTCHA validation was successful. Please close the browser and return to the command line interface."); callback(null); } else if ((_b = request.url) === null || _b === void 0 ? void 0 : _b.startsWith("/captcha?error")) { const errorMsg = "Failed to validate the CAPTCHA with internal service error. Please try again later."; response.statusCode = 500; response.end(errorMsg); callback(new Error(errorMsg)); } else if ((_c = request.url) === null || _c === void 0 ? void 0 : _c.startsWith("/captcha?vendorId")) { const errorMsg = "The Vendor ID in the browser session does not match the one associated with your CLI profile. \n" + "Please sign into the correct developer account in your browser before completing the CAPTCHA."; response.statusCode = 400; response.end(errorMsg); callback(new Error(errorMsg)); } else if ((_d = request.url) === null || _d === void 0 ? void 0 : _d.startsWith("/favicon.ico")) { request.socket.destroy(); response.statusCode = 204; response.end(); } else { const errorMsg = "Failed to validate the CAPTCHA. Please try again."; response.statusCode = 404; response.end(errorMsg); callback(new Error(errorMsg)); } } }