teamsfx-extension
Version:
Create, debug, and deploy Teams apps with Teams Toolkit
275 lines • 13.1 kB
JavaScript
;
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebviewPanel = void 0;
const path = require("path");
const vscode = require("vscode");
const extensionVariables_1 = require("../extensionVariables");
const Commands_1 = require("./Commands");
const axios_1 = require("axios");
const AdmZip = require("adm-zip");
const fs = require("fs-extra");
const azureLogin_1 = require("../commonlib/azureLogin");
const appStudioLogin_1 = require("../commonlib/appStudioLogin");
const handlers_1 = require("../handlers");
const teamsfx_api_1 = require("@microsoft/teamsfx-api");
const PanelType_1 = require("./PanelType");
const child_process_1 = require("child_process");
const commonUtils_1 = require("../utils/commonUtils");
const userInterface_1 = require("../userInterface");
class WebviewPanel {
constructor(extensionPath, panelType, column) {
this.panelType = PanelType_1.PanelType.QuickStart;
this.disposables = [];
this.isValidNode = () => {
var _a;
try {
const supportedVersions = ["10", "12", "14"];
const output = child_process_1.execSync("node --version");
const regex = /v(?<major_version>\d+)\.(?<minor_version>\d+)\.(?<patch_version>\d+)/gm;
const match = regex.exec(output.toString());
if (!match) {
return false;
}
const majorVersion = (_a = match.groups) === null || _a === void 0 ? void 0 : _a.major_version;
if (!majorVersion) {
return false;
}
return supportedVersions.includes(majorVersion);
}
catch (e) { }
return false;
};
this.extensionPath = extensionPath;
this.panelType = panelType;
// Create and show a new webview panel
this.panel = vscode.window.createWebviewPanel(WebviewPanel.viewType, this.getWebpageTitle(panelType), column, {
// Enable javascript in the webview
enableScripts: true,
retainContextWhenHidden: true,
localResourceRoots: [vscode.Uri.file(path.join(this.extensionPath, "out"))],
});
// Listen for when the panel is disposed
// This happens when the user closes the panel or when the panel is closed programatically
this.panel.onDidDispose(() => this.dispose(), null, this.disposables);
// Handle messages from the webview
this.panel.webview.onDidReceiveMessage((msg) => __awaiter(this, void 0, void 0, function* () {
switch (msg.command) {
case Commands_1.Commands.OpenExternalLink:
vscode.env.openExternal(vscode.Uri.parse(msg.data));
break;
case Commands_1.Commands.CloneSampleApp:
const selection = yield vscode.window.showInformationMessage(`Download '${msg.data.appName}' from Github. This will download '${msg.data.appName}' repository and open to your local machine`, { modal: false }, "Download", "Cancel");
if (selection === "Download") {
const folder = yield vscode.window.showOpenDialog({
canSelectFiles: false,
canSelectFolders: true,
canSelectMany: false,
title: "Select folder to download the sample app",
});
if (folder !== undefined) {
const sampleAppPath = path.join(folder[0].fsPath, msg.data.appFolder);
if ((yield fs.pathExists(sampleAppPath)) &&
(yield fs.readdir(sampleAppPath)).length > 0) {
vscode.window.showErrorMessage(`Path ${sampleAppPath} alreay exists. Select a different folder.`);
return;
}
const dialogManager = userInterface_1.DialogManager.getInstance();
const progress = dialogManager.createProgressBar("Fetch sample app", 2);
progress.start();
try {
progress.next(`Downloading from '${msg.data.appUrl}'`);
const result = yield this.fetchCodeZip(msg.data.appUrl);
progress.next("Unzipping the sample package");
if (result !== undefined) {
yield this.saveFilesRecursively(new AdmZip(result.data), msg.data.appFolder, folder[0].fsPath);
vscode.commands.executeCommand("vscode.openFolder", vscode.Uri.file(sampleAppPath));
}
else {
vscode.window.showErrorMessage("Failed to download sample app");
}
}
finally {
progress.end();
}
}
}
break;
case Commands_1.Commands.DisplayCommandPalette:
break;
case Commands_1.Commands.DisplayCommands:
vscode.commands.executeCommand("workbench.action.quickOpen", `>${msg.data}`);
break;
case Commands_1.Commands.SigninM365:
yield appStudioLogin_1.default.getJsonObject(false);
break;
case Commands_1.Commands.SigninAzure:
vscode.commands.executeCommand("fx-extension.signinAzure", ["webview", false]);
break;
case Commands_1.Commands.CreateNewProject:
yield handlers_1.runCommand(teamsfx_api_1.Stage.create);
break;
case Commands_1.Commands.SwitchPanel:
WebviewPanel.createOrShow(this.extensionPath, msg.data);
break;
case Commands_1.Commands.InitAccountInfo:
this.setStatusChangeMap();
break;
default:
break;
}
}), undefined, extensionVariables_1.ext.context.subscriptions);
// Set the webview's initial html content
this.panel.webview.html = this.getHtmlForWebview(panelType);
}
static createOrShow(extensionPath, panelType) {
const column = vscode.window.activeTextEditor
? vscode.window.activeTextEditor.viewColumn
: undefined;
if (WebviewPanel.currentPanels &&
WebviewPanel.currentPanels.findIndex((panel) => panel.panelType === panelType) > -1) {
WebviewPanel.currentPanels
.find((panel) => panel.panelType === panelType)
.panel.reveal(column);
}
else {
WebviewPanel.currentPanels.push(new WebviewPanel(extensionPath, panelType, column || vscode.ViewColumn.One));
}
}
getWebpageTitle(panelType) {
switch (panelType) {
case PanelType_1.PanelType.QuickStart:
return "Quick Start";
case PanelType_1.PanelType.SampleGallery:
return "Samples";
}
}
setStatusChangeMap() {
appStudioLogin_1.default.setStatusChangeMap("quick-start-webview", (status, token, accountInfo) => {
let email = undefined;
if (status === "SignedIn") {
email = accountInfo.upn ? accountInfo.upn : undefined;
}
if (this.panel && this.panel.webview) {
this.panel.webview.postMessage({
message: "m365AccountChange",
data: email,
});
}
return Promise.resolve();
});
azureLogin_1.default.setStatusChangeMap("quick-start-webview", (status, token, accountInfo) => {
let email = undefined;
if (status === "SignedIn") {
const token = azureLogin_1.default.getAccountCredential();
if (token !== undefined) {
email = token.username ? token.username : undefined;
}
}
if (this.panel && this.panel.webview) {
this.panel.webview.postMessage({
message: "azureAccountChange",
data: email,
});
}
return Promise.resolve();
});
}
fetchCodeZip(url) {
return __awaiter(this, void 0, void 0, function* () {
let retries = 3;
let result = undefined;
while (retries > 0) {
retries--;
try {
result = yield axios_1.default.get(url, {
responseType: "arraybuffer",
});
if (result.status === 200 || result.status === 201) {
return result;
}
}
catch (e) {
yield new Promise((resolve) => setTimeout(resolve, 10000));
}
}
return result;
});
}
saveFilesRecursively(zip, appFolder, dstPath) {
return __awaiter(this, void 0, void 0, function* () {
yield Promise.all(zip
.getEntries()
.filter((entry) => !entry.isDirectory && entry.entryName.includes(appFolder))
.map((entry) => __awaiter(this, void 0, void 0, function* () {
const data = entry.getData().toString();
const entryPath = entry.entryName.substring(entry.entryName.indexOf("/") + 1);
const filePath = path.join(dstPath, entryPath);
yield fs.ensureDir(path.dirname(filePath));
yield fs.writeFile(filePath, data);
})));
});
}
getHtmlForWebview(panelType) {
const scriptBasePathOnDisk = vscode.Uri.file(path.join(this.extensionPath, "out/"));
const scriptBaseUri = scriptBasePathOnDisk.with({ scheme: "vscode-resource" });
const scriptPathOnDisk = vscode.Uri.file(path.join(this.extensionPath, "out/src", "client.js"));
const scriptUri = scriptPathOnDisk.with({ scheme: "vscode-resource" });
// Use a nonce to to only allow specific scripts to be run
const nonce = this.getNonce();
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ms-teams</title>
<base href='${scriptBaseUri}' />
</head>
<body>
<div id="root"></div>
<script>
const vscode = acquireVsCodeApi();
const panelType = '${panelType}';
const isSupportedNode = ${this.isValidNode()};
const isMacPlatform = ${commonUtils_1.isMacOS()};
</script>
<script nonce="${nonce}" type="module" src="${scriptUri}"></script>
</body>
</html>`;
}
getNonce() {
let text = "";
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
dispose() {
WebviewPanel.currentPanels.splice(WebviewPanel.currentPanels.indexOf(this), 1);
appStudioLogin_1.default.removeStatusChangeMap("quick-start-webview");
azureLogin_1.default.removeStatusChangeMap("quick-start-webview");
// Clean up our resources
this.panel.dispose();
while (this.disposables.length) {
const x = this.disposables.pop();
if (x) {
x.dispose();
}
}
}
}
exports.WebviewPanel = WebviewPanel;
WebviewPanel.viewType = "react";
WebviewPanel.currentPanels = [];
//# sourceMappingURL=webviewPanel.js.map