Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: switch to builder API for declarative agent apps #13056

Draft
wants to merge 17 commits into
base: dev
Choose a base branch
from
88 changes: 85 additions & 3 deletions packages/fx-core/src/component/m365/packageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT license.

import { hooks } from "@feathersjs/hooks";
import { LogProvider, SystemError, UserError } from "@microsoft/teamsfx-api";
import { Err, LogProvider, SystemError, TeamsAppManifest, UserError } from "@microsoft/teamsfx-api";
Fixed Show fixed Hide fixed
import AdmZip from "adm-zip";
import FormData from "form-data";
import fs from "fs-extra";
Expand All @@ -20,6 +20,9 @@
import { WrappedAxiosClient } from "../../common/wrappedAxiosClient";
import { NotExtendedToM365Error } from "./errors";
import { MosServiceEndpoint } from "./serviceConstant";
import { manifestUtils } from "../driver/teamsApp/utils/ManifestUtils";
Fixed Show fixed Hide fixed
import { IsDeclarativeAgentManifest } from "../../common/projectTypeChecker";
import stripBom from "strip-bom";

const M365ErrorSource = "M365";
const M365ErrorComponent = "PackageService";
Expand Down Expand Up @@ -139,14 +142,82 @@
}

@hooks([ErrorContextMW({ source: M365ErrorSource, component: M365ErrorComponent })])
public async sideLoading(token: string, manifestPath: string): Promise<[string, string]> {
public async sideLoading(token: string, packagePath: string): Promise<[string, string]> {
const manifest = this.getManifestFromZip(packagePath);
if (!manifest) {
throw new Error("Invalid app package zip. manifest.json is missing");
}
const isDelcarativeAgentApp = IsDeclarativeAgentManifest(manifest);
if (isDelcarativeAgentApp) {
return await this.sideLoadingV2(token, packagePath);
} else {
return await this.sideLoadingV1(token, packagePath);
}
}
// Side loading using Builder API
@hooks([ErrorContextMW({ source: M365ErrorSource, component: M365ErrorComponent })])
public async sideLoadingV2(token: string, manifestPath: string): Promise<[string, string]> {
try {
this.checkZip(manifestPath);
const data = await fs.readFile(manifestPath);
const content = new FormData();
content.append("package", data);
const serviceUrl = await this.getTitleServiceUrl(token);
this.logger?.debug("Uploading package with sideLoading V2 ...");
const uploadHeaders = content.getHeaders();
uploadHeaders["Authorization"] = `Bearer ${token}`;
const uploadResponse = await this.axiosInstance.post(
"/builder/v1/users/packages",
content.getBuffer(),
{
baseURL: serviceUrl,
headers: uploadHeaders,
params: {
scope: "Personal",
},
}
);

const statusId = uploadResponse.data.statusId;
this.logger?.debug(`Acquiring package with statusId: ${statusId as string} ...`);

do {
const statusResponse = await this.axiosInstance.get(
`/builder/v1/users/packages/status/${statusId as string}`,
{
baseURL: serviceUrl,
headers: { Authorization: `Bearer ${token}` },
}
);
const resCode = statusResponse.status;
this.logger?.debug(`Package status: ${resCode} ...`);
if (resCode === 200) {
const titleId: string = statusResponse.data.titleId;
const appId: string = statusResponse.data.appId;
this.logger?.info(`TitleId: ${titleId}`);
this.logger?.info(`AppId: ${appId}`);
this.logger?.verbose("Sideloading done.");
return [titleId, appId];
} else {
await waitSeconds(2);
}
} while (true);
} catch (error: any) {
if (error.response) {
error = this.traceError(error);
}
throw assembleError(error, M365ErrorSource);
}
}
@hooks([ErrorContextMW({ source: M365ErrorSource, component: M365ErrorComponent })])
public async sideLoadingV1(token: string, manifestPath: string): Promise<[string, string]> {
try {
this.checkZip(manifestPath);
const data = await fs.readFile(manifestPath);
const content = new FormData();
content.append("package", data);
const serviceUrl = await this.getTitleServiceUrl(token);
this.logger?.verbose("Uploading package ...");
this.logger?.debug("Uploading package with sideLoading V1 ...");
const uploadHeaders = content.getHeaders();
uploadHeaders["Authorization"] = `Bearer ${token}`;
const uploadResponse = await this.axiosInstance.post(
Expand Down Expand Up @@ -440,4 +511,15 @@
this.logger?.warning(`Please make sure input path is a valid app package zip. ${path}`);
}
}

private getManifestFromZip(path: string): TeamsAppManifest | undefined {
const zip = new AdmZip(path);
const manifestEntry = zip.getEntry("manifest.json");
if (!manifestEntry) {
return undefined;
}
let manifestContent = manifestEntry.getData().toString("utf8");
manifestContent = stripBom(manifestContent);
return JSON.parse(manifestContent) as TeamsAppManifest;
}
}
Loading