UNPKG

firebase-app-distribution

Version:
746 lines (738 loc) 25 kB
"use strict"; var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __typeError = (msg) => { throw TypeError(msg); }; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method); var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/index.ts var index_exports = {}; __export(index_exports, { FirebaseAppDistribution: () => FirebaseAppDistribution }); module.exports = __toCommonJS(index_exports); var import_google_auth_library = require("google-auth-library"); // src/utils.ts var APP_DISTRIBUTION_ENDPOINT = "https://firebaseappdistribution.googleapis.com"; var ENDPOINT_VERSION = "v1"; var AUTH_SCOPES = [ "https://www.googleapis.com/auth/cloud-platform", "https://www.googleapis.com/auth/firebase" ]; function makeRequest(input, init) { return __async(this, null, function* () { const response = yield fetch(input, init); if (response.status !== 200) { const errMessage = yield response.text(); throw new Error( `Request failed with status code ${response.status} and message: ${errMessage}` ); } const responseText = yield response.text(); const responseObj = JSON.parse(responseText); return responseObj; }); } // src/testers.ts function getEmailFromName(testerName) { return testerName.split("/").pop(); } var Testers = class { constructor(parent) { this.parent = parent; } add(emails) { return __async(this, null, function* () { const accessToken = yield this.parent.getAccessToken(); const projectNumber = yield this.parent.getProjectNumber(); const testers = []; const url = new URL( `${APP_DISTRIBUTION_ENDPOINT}/${ENDPOINT_VERSION}/projects/${projectNumber}/testers:batchAdd` ); const requestBody = JSON.stringify({ emails }); const response = yield makeRequest(url.toString(), { headers: { authorization: `Bearer ${accessToken}` }, body: requestBody, method: "POST" }); for (let tester of response.testers) { testers.push(__spreadProps(__spreadValues({}, tester), { email: getEmailFromName(tester.name) })); } return testers; }); } remove(emails) { return __async(this, null, function* () { const accessToken = yield this.parent.getAccessToken(); const projectNumber = yield this.parent.getProjectNumber(); const url = new URL( `${APP_DISTRIBUTION_ENDPOINT}/${ENDPOINT_VERSION}/projects/${projectNumber}/testers:batchRemove` ); const requestBody = JSON.stringify({ emails }); const response = yield makeRequest(url.toString(), { headers: { authorization: `Bearer ${accessToken}` }, body: requestBody, method: "POST" }); return response.emails || []; }); } list() { return __async(this, arguments, function* ({ pageSize = 50, email, displayName, groups, maxPages = 10 } = {}) { const accessToken = yield this.parent.getAccessToken(); const projectNumber = yield this.parent.getProjectNumber(); const testerList = []; const url = new URL( `${APP_DISTRIBUTION_ENDPOINT}/${ENDPOINT_VERSION}/projects/${projectNumber}/testers` ); url.searchParams.set("pageSize", pageSize.toString()); let filterParts = []; if (email) filterParts.push(`name="projects/-/testers/${email}"`); if (displayName) filterParts.push(`displayName="${displayName}"`); if (groups) filterParts.push(`groups="projects/*/groups/${groups}"`); if (filterParts.length > 0) { url.searchParams.set("filter", filterParts.join(" ")); } let nextPageToken = ""; for (let page = 0; page < maxPages; page++) { if (nextPageToken) { url.searchParams.set("pageToken", nextPageToken); } else { url.searchParams.delete("pageToken"); } const response = yield makeRequest(url.toString(), { headers: { authorization: `Bearer ${accessToken}` }, body: null, method: "GET" }); if (response.testers === void 0) { break; } for (let tester of response.testers) { testerList.push(__spreadProps(__spreadValues({}, tester), { email: getEmailFromName(tester.name) })); } if (response.nextPageToken !== void 0) { nextPageToken = response.nextPageToken; } else { break; } } return testerList; }); } get(email) { return __async(this, null, function* () { const accessToken = yield this.parent.getAccessToken(); const projectNumber = yield this.parent.getProjectNumber(); const url = new URL( `${APP_DISTRIBUTION_ENDPOINT}/${ENDPOINT_VERSION}/projects/${projectNumber}/testers` ); url.searchParams.append("pageSize", "1"); url.searchParams.append("filter", `name="projects/-/testers/${email}"`); const response = yield makeRequest(url.toString(), { headers: { authorization: `Bearer ${accessToken}` }, body: null, method: "GET" }); return response.testers !== void 0 ? __spreadProps(__spreadValues({}, response.testers[0]), { email: getEmailFromName(response.testers[0].name) }) : null; }); } update(_0) { return __async(this, arguments, function* (email, { displayName, groups } = {}) { const accessToken = yield this.parent.getAccessToken(); const projectNumber = yield this.parent.getProjectNumber(); const url = new URL( `${APP_DISTRIBUTION_ENDPOINT}/${ENDPOINT_VERSION}/projects/${projectNumber}/testers/${email}` ); let updateMaskParts = []; if (displayName !== void 0) { updateMaskParts.push("displayName"); } if (groups !== void 0) { updateMaskParts.push("groups"); } if (updateMaskParts.length > 0) { url.searchParams.append("updateMask", updateMaskParts.join(",")); } const formattedGroups = groups == null ? void 0 : groups.map( (group) => `projects/${projectNumber}/groups/${group}` ); const response = yield makeRequest(url.toString(), { headers: { authorization: `Bearer ${accessToken}` }, body: JSON.stringify({ displayName: displayName || void 0, groups: formattedGroups || void 0 }), method: "PATCH" }); return __spreadProps(__spreadValues({}, response), { email: getEmailFromName(response.name) }); }); } }; // src/groups.ts function getGroupIdFromName(groupName) { return groupName.split("/").pop(); } var Groups = class { constructor(parent) { this.parent = parent; } create(displayName, groupId) { return __async(this, null, function* () { const accessToken = yield this.parent.getAccessToken(); const projectNumber = yield this.parent.getProjectNumber(); const url = new URL( `${APP_DISTRIBUTION_ENDPOINT}/${ENDPOINT_VERSION}/projects/${projectNumber}/groups` ); if (groupId) { url.searchParams.set("groupId", groupId); } const requestBody = JSON.stringify({ displayName }); const response = yield makeRequest(url, { headers: { authorization: `Bearer ${accessToken}` }, body: requestBody, method: "POST" }); return __spreadProps(__spreadValues({}, response), { id: getGroupIdFromName(response.name) }); }); } delete(groupId) { return __async(this, null, function* () { const accessToken = yield this.parent.getAccessToken(); const projectNumber = yield this.parent.getProjectNumber(); const url = new URL( `${APP_DISTRIBUTION_ENDPOINT}/${ENDPOINT_VERSION}/projects/${projectNumber}/groups/${groupId}` ); const response = yield makeRequest(url, { headers: { authorization: `Bearer ${accessToken}` }, body: null, method: "DELETE" }); return response; }); } get(groupId) { return __async(this, null, function* () { const accessToken = yield this.parent.getAccessToken(); const projectNumber = yield this.parent.getProjectNumber(); const url = new URL( `${APP_DISTRIBUTION_ENDPOINT}/${ENDPOINT_VERSION}/projects/${projectNumber}/groups/${groupId}` ); const response = yield makeRequest(url, { headers: { authorization: `Bearer ${accessToken}` }, body: null, method: "GET" }); if (response.name === void 0) { return null; } return __spreadProps(__spreadValues({}, response), { id: getGroupIdFromName(response.name) }); }); } list() { return __async(this, arguments, function* ({ pageSize = 25, maxPages = 10 } = {}) { const accessToken = yield this.parent.getAccessToken(); const projectNumber = yield this.parent.getProjectNumber(); const groupList = []; const url = new URL( `${APP_DISTRIBUTION_ENDPOINT}/${ENDPOINT_VERSION}/projects/${projectNumber}/groups` ); url.searchParams.set("pageSize", pageSize.toString()); let nextPageToken = ""; for (let page = 0; page < maxPages; page++) { if (nextPageToken) { url.searchParams.set("pageToken", nextPageToken); } else { url.searchParams.delete("pageToken"); } const response = yield makeRequest(url, { headers: { authorization: `Bearer ${accessToken}` }, body: null, method: "GET" }); if (response.groups === void 0) { break; } for (let group of response.groups) { groupList.push(__spreadProps(__spreadValues({}, group), { id: getGroupIdFromName(group.name) })); } if (response.nextPageToken !== void 0) { nextPageToken = response.nextPageToken; } else { break; } } return groupList; }); } update(_0, _1) { return __async(this, arguments, function* (groupId, { displayName }) { const accessToken = yield this.parent.getAccessToken(); const projectNumber = yield this.parent.getProjectNumber(); const url = new URL( `${APP_DISTRIBUTION_ENDPOINT}/${ENDPOINT_VERSION}/projects/${projectNumber}/groups/${groupId}` ); if (displayName) { url.searchParams.set("updateMask", "displayName"); } const requestBody = JSON.stringify({ displayName }); const response = yield makeRequest(url, { headers: { authorization: `Bearer ${accessToken}` }, body: requestBody, method: "PATCH" }); return __spreadProps(__spreadValues({}, response), { id: getGroupIdFromName(response.name) }); }); } removeTesters(groupId, emails) { return __async(this, null, function* () { const accessToken = yield this.parent.getAccessToken(); const projectNumber = yield this.parent.getProjectNumber(); const url = new URL( `${APP_DISTRIBUTION_ENDPOINT}/${ENDPOINT_VERSION}/projects/${projectNumber}/groups/${groupId}:batchLeave` ); const reuqestBody = JSON.stringify({ emails }); const response = yield makeRequest(url, { headers: { authorization: `Bearer ${accessToken}` }, body: reuqestBody, method: "POST" }); return response; }); } addTesters(groupId, emails) { return __async(this, null, function* () { const accessToken = yield this.parent.getAccessToken(); const projectNumber = yield this.parent.getProjectNumber(); const url = new URL( `${APP_DISTRIBUTION_ENDPOINT}/${ENDPOINT_VERSION}/projects/${projectNumber}/groups/${groupId}:batchJoin` ); const reuqestBody = JSON.stringify({ emails, createMissingTesters: true }); const response = yield makeRequest(url, { headers: { authorization: `Bearer ${accessToken}` }, body: reuqestBody, method: "POST" }); return response; }); } }; // src/releases/index.ts var import_node_fs = require("fs"); // src/releases/operations.ts function isValidOperationName(operationName) { const regex = /^projects\/[^/]+\/apps\/[^/]+\/releases\/[^/]+\/operations\/[^/]+$/; return regex.test(operationName); } var Operations = class { constructor(parent) { this.parent = parent; } get(operationName) { return __async(this, null, function* () { if (!isValidOperationName(operationName)) { throw new Error(`Invalid operation name: ${operationName}`); } const accessToken = yield this.parent.getAccessToken(); const url = new URL( `${APP_DISTRIBUTION_ENDPOINT}/${ENDPOINT_VERSION}/${operationName}` ); const response = yield makeRequest(url.toString(), { headers: { authorization: `Bearer ${accessToken}` } }); return response; }); } }; // src/releases/index.ts function isValidReleaseName(releaseName) { const regex = /^projects\/[^/]+\/apps\/[^/]+\/releases\/[^/]+$/; return regex.test(releaseName); } function validateReleaseName(releaseName) { if (!isValidReleaseName(releaseName)) { throw new Error( `Invalid release name: ${releaseName}. Expected 'projects/*/apps/*/releases/*'` ); } return; } var _Releases_instances, update_fn; var Releases = class { constructor(parent) { __privateAdd(this, _Releases_instances); this.parent = parent; this.operations = new Operations(this.parent); } upload(appId, binaryPath, fileName) { return __async(this, null, function* () { const accessToken = yield this.parent.getAccessToken(); const projectNumber = yield this.parent.getProjectNumber(); const url = new URL( `${APP_DISTRIBUTION_ENDPOINT}/upload/${ENDPOINT_VERSION}/projects/${projectNumber}/apps/${appId}/releases:upload` ); const fileBuffer = (0, import_node_fs.readFileSync)(binaryPath); const response = yield makeRequest(url.toString(), { method: "POST", headers: { Authorization: `Bearer ${accessToken}`, "X-Goog-Upload-File-Name": encodeURIComponent(fileName), "X-Goog-Upload-Protocol": "raw" }, body: new Uint8Array(fileBuffer) }); return response; }); } delete(appId, releaseNames) { return __async(this, null, function* () { for (let releaseName of releaseNames) { validateReleaseName(releaseName); } const accessToken = yield this.parent.getAccessToken(); const projectNumber = yield this.parent.getProjectNumber(); if (releaseNames.length > 100) { throw new Error("Cannot delete more than 100 releases at once"); } const url = new URL( `${APP_DISTRIBUTION_ENDPOINT}/${ENDPOINT_VERSION}/projects/${projectNumber}/apps/${appId}/releases:batchDelete` ); const response = yield makeRequest(url.toString(), { method: "POST", headers: { Authorization: `Bearer ${accessToken}` }, body: JSON.stringify({ names: releaseNames }) }); return response; }); } distribute(_0, _1) { return __async(this, arguments, function* (releaseName, { testerEmails = [], groupAliases = [] }) { validateReleaseName(releaseName); if (testerEmails.length === 0 && groupAliases.length === 0) { throw new Error("Must specify at least one tester or group"); } const accessToken = yield this.parent.getAccessToken(); const url = new URL( `${APP_DISTRIBUTION_ENDPOINT}/${ENDPOINT_VERSION}/${releaseName}:distribute` ); const response = yield makeRequest(url.toString(), { method: "POST", headers: { Authorization: `Bearer ${accessToken}` }, body: JSON.stringify({ testerEmails, groupAliases }) }); return response; }); } get(releaseName) { return __async(this, null, function* () { validateReleaseName(releaseName); const accessToken = yield this.parent.getAccessToken(); const url = new URL( `${APP_DISTRIBUTION_ENDPOINT}/${ENDPOINT_VERSION}/${releaseName}` ); const response = yield makeRequest(url.toString(), { method: "GET", headers: { Authorization: `Bearer ${accessToken}` } }); return response; }); } list(_0) { return __async(this, arguments, function* (appId, { pageSize, orderBy, filter, maxPages } = {}) { const accessToken = yield this.parent.getAccessToken(); const projectNumber = yield this.parent.getProjectNumber(); const url = new URL( `${APP_DISTRIBUTION_ENDPOINT}/${ENDPOINT_VERSION}/projects/${projectNumber}/apps/${appId}/releases` ); if (pageSize) { url.searchParams.set("pageSize", (pageSize == null ? void 0 : pageSize.toString()) || "100"); } if (orderBy) { url.searchParams.set("orderBy", orderBy); } if (filter) { url.searchParams.set("filter", filter); } let nextPageToken = ""; let currentPage = 0; const releases = []; do { if (nextPageToken) { url.searchParams.set("pageToken", nextPageToken); } else { url.searchParams.delete("pageToken"); } const response = yield makeRequest(url.toString(), { method: "GET", headers: { Authorization: `Bearer ${accessToken}` } }); if (response.releases === void 0) { break; } releases.push(...response.releases); if (response.nextPageToken !== void 0) { nextPageToken = response.nextPageToken; } else { break; } currentPage++; if (maxPages && currentPage >= maxPages) { break; } } while (nextPageToken); return releases; }); } addReleaseNotes(releaseName, releaseNotes) { return __async(this, null, function* () { return __privateMethod(this, _Releases_instances, update_fn).call(this, releaseName, { releaseNotes }); }); } }; _Releases_instances = new WeakSet(); update_fn = function(_0) { return __async(this, arguments, function* (releaseName, { releaseNotes } = {}) { validateReleaseName(releaseName); const accessToken = yield this.parent.getAccessToken(); const url = new URL( `${APP_DISTRIBUTION_ENDPOINT}/${ENDPOINT_VERSION}/${releaseName}` ); if (releaseNotes) { url.searchParams.set("updateMask", "releaseNotes.text"); } const response = yield makeRequest(url.toString(), { method: "PATCH", headers: { Authorization: `Bearer ${accessToken}` }, body: JSON.stringify({ releaseNotes: { text: releaseNotes } }) }); return response; }); }; // src/index.ts var FirebaseAppDistribution = class { constructor(authOptions) { this.projectNumber = null; this.projectId = null; this.googleAuth = new import_google_auth_library.GoogleAuth(__spreadProps(__spreadValues({}, authOptions), { scopes: AUTH_SCOPES })); this.testers = new Testers(this); this.groups = new Groups(this); this.releases = new Releases(this); } getProjectId() { return __async(this, null, function* () { if (this.projectId) { return this.projectId; } yield this.getAccessToken(); this.projectId = yield this.googleAuth.getProjectId(); return this.projectId; }); } getProjectNumber() { return __async(this, null, function* () { if (this.projectNumber) { return this.projectNumber; } const projectId = yield this.getProjectId(); const client = yield this.googleAuth.getClient(); const url = `https://cloudresourcemanager.googleapis.com/v1/projects/${projectId}`; const res = yield client.request({ url }); this.projectNumber = res.data.projectNumber; if (!this.projectNumber) { throw new Error("Failed to retrieve project number"); } return this.projectNumber; }); } getAccessToken() { return __async(this, null, function* () { if (this.accessToken) { return this.accessToken; } const accessToken = yield this.googleAuth.getAccessToken(); if (!accessToken) { throw new Error("Failed to retrieve access token"); } this.accessToken = accessToken; return this.accessToken; }); } uploadAndDistribute(_0) { return __async(this, arguments, function* ({ appId, binaryPath, fileName, testerEmails = [], groupAliases = [], releaseNotes, uploadOperationInteval = 500, uploadOperationTimeout = 5e3 }) { const operation = yield this.releases.upload(appId, binaryPath, fileName); const operationName = operation.name; const uploadTimeout = setTimeout(() => { throw new Error("Upload operation timed out"); }, uploadOperationTimeout); let doneUploading = false; let releaseName = null; while (!doneUploading) { const operationData = yield this.releases.operations.get(operationName); if (operationData.done) { doneUploading = true; releaseName = operationData.response.release.name; clearTimeout(uploadTimeout); } if (operationData.error) { throw new Error( `Upload operation failed with code ${operationData.error.code} and error: ${operationData.error.message}` ); } yield new Promise((res) => setTimeout(res, uploadOperationInteval)); } if (testerEmails.length !== 0 || groupAliases.length !== 0) { yield this.releases.distribute(releaseName, { testerEmails, groupAliases }); } if (releaseNotes) { yield this.releases.addReleaseNotes(releaseName, releaseNotes); } const finalReleaseData = yield this.releases.get(releaseName); return finalReleaseData; }); } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { FirebaseAppDistribution }); //# sourceMappingURL=index.js.map