ask-cli
Version:
Alexa Skills Kit (ASK) Command Line Interfaces
240 lines (239 loc) • 12.1 kB
JavaScript
;
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));
}
}
}