You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

572 lines
23 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.UseExistingPushNotification = exports.UpdateIosPush = exports.RemoveIosPush = exports.CreateOrReusePushKey = exports.CreateIosPush = exports.CreateAndAssignIosPush = void 0;
exports.getPushKeyFromParams = getPushKeyFromParams;
exports.usePushKeyFromParams = usePushKeyFromParams;
exports.validatePushKey = validatePushKey;
function _chalk() {
const data = _interopRequireDefault(require("chalk"));
_chalk = function () {
return data;
};
return data;
}
function _fsExtra() {
const data = _interopRequireDefault(require("fs-extra"));
_fsExtra = function () {
return data;
};
return data;
}
function _terminalLink() {
const data = _interopRequireDefault(require("terminal-link"));
_terminalLink = function () {
return data;
};
return data;
}
function _CommandError() {
const data = _interopRequireDefault(require("../../CommandError"));
_CommandError = function () {
return data;
};
return data;
}
function _appleApi() {
const data = require("../../appleApi");
_appleApi = function () {
return data;
};
return data;
}
function _log() {
const data = _interopRequireDefault(require("../../log"));
_log = function () {
return data;
};
return data;
}
function _ora() {
const data = require("../../utils/ora");
_ora = function () {
return data;
};
return data;
}
function _prompts() {
const data = _interopRequireWildcard(require("../../utils/prompts"));
_prompts = function () {
return data;
};
return data;
}
function _list() {
const data = require("../actions/list");
_list = function () {
return data;
};
return data;
}
function _promptForCredentials() {
const data = require("../actions/promptForCredentials");
_promptForCredentials = function () {
return data;
};
return data;
}
function _IosApi() {
const data = require("../api/IosApi");
_IosApi = function () {
return data;
};
return data;
}
function _credentials() {
const data = require("../credentials");
_credentials = function () {
return data;
};
return data;
}
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const APPLE_KEYS_TOO_MANY_GENERATED_ERROR = `
You can have only ${_chalk().default.underline('two')} Push Notifactions Keys on your Apple Developer account.
Please revoke the old ones or reuse existing from your other apps.
Please remember that Apple Keys are not application specific!
`;
class CreateIosPush {
constructor(accountName) {
this.accountName = accountName;
}
async create(ctx) {
const newPushKey = await this.provideOrGenerate(ctx);
return await ctx.ios.createPushKey(this.accountName, newPushKey);
}
async open(ctx) {
const pushKey = await this.create(ctx);
_log().default.log('Successfully created Push Notification Key\n');
(0, _list().displayIosUserCredentials)(pushKey);
_log().default.log();
return null;
}
_getRequiredQuestions(ctx) {
const requiredQuestions = {
..._credentials().pushKeySchema
};
if (ctx.hasAppleCtx() && requiredQuestions.questions) {
requiredQuestions.required = requiredQuestions.required.filter(q => q !== 'teamId');
}
return requiredQuestions;
}
_ensurePushKey(ctx, partialKey) {
if (ctx.hasAppleCtx()) {
partialKey.teamId = ctx.appleCtx.team.id;
partialKey.teamName = ctx.appleCtx.team.name;
}
if (!(0, _appleApi().isPushKey)(partialKey)) {
throw new Error(`Not of type PushKey: ${partialKey}`);
}
return partialKey;
}
async provideOrGenerate(ctx) {
if (!ctx.nonInteractive) {
const requiredQuestions = this._getRequiredQuestions(ctx);
const userProvided = await (0, _promptForCredentials().askForUserProvided)(requiredQuestions);
if (userProvided) {
const pushKey = this._ensurePushKey(ctx, userProvided);
const isValid = await validatePushKey(ctx, pushKey);
return isValid ? userProvided : await this.provideOrGenerate(ctx);
}
}
return await generatePushKey(ctx, this.accountName);
}
}
exports.CreateIosPush = CreateIosPush;
class CreateAndAssignIosPush extends CreateIosPush {
async open(ctx) {
const pushKey = await super.create(ctx);
_log().default.log('Successfully created Push Notification Key\n');
(0, _list().displayIosUserCredentials)(pushKey);
_log().default.log();
if (ctx.hasProjectContext && pushKey) {
await this.assignToCurrentProject(ctx, pushKey.id);
_log().default.log();
}
return null;
}
async assignToCurrentProject(ctx, pushKeyId) {
var _ctx$manifest, _ctx$manifest$ios;
const experienceName = `@${ctx.projectOwner}/${ctx.manifest.slug}`;
const bundleIdentifier = (_ctx$manifest = ctx.manifest) === null || _ctx$manifest === void 0 ? void 0 : (_ctx$manifest$ios = _ctx$manifest.ios) === null || _ctx$manifest$ios === void 0 ? void 0 : _ctx$manifest$ios.bundleIdentifier;
if (!ctx.nonInteractive && bundleIdentifier) {
const confirm = await (0, _prompts().confirmAsync)({
message: `Would you like to use this push key for the current project: ${experienceName} (${bundleIdentifier})?`
});
if (!confirm) {
return;
}
const app = (0, _IosApi().getAppLookupParams)(experienceName, bundleIdentifier);
await ctx.ios.usePushKey(app, pushKeyId);
_log().default.log(_chalk().default.green(`Successfully assigned Push Key to ${experienceName} (${bundleIdentifier})`));
}
}
}
exports.CreateAndAssignIosPush = CreateAndAssignIosPush;
class RemoveIosPush {
constructor(accountName, shouldRevoke = false) {
this.accountName = accountName;
this.shouldRevoke = shouldRevoke;
}
async open(ctx) {
if (ctx.nonInteractive) {
throw new (_CommandError().default)('NON_INTERACTIVE', "Start the CLI without the '--non-interactive' flag to select a push notification credential to remove.");
}
const selected = await selectPushCredFromList(ctx, this.accountName);
if (selected) {
if (!('type' in selected)) {
const app = (0, _IosApi().getAppLookupParams)(selected.experienceName, selected.bundleIdentifier);
await this.removePushCert(ctx, app);
_log().default.log(_chalk().default.green('Successfully removed Push Certificate'));
} else {
await this.removeSpecific(ctx, selected);
_log().default.log(_chalk().default.green('Successfully removed Push Notification Key'));
}
}
return null;
}
async removePushCert(ctx, app) {
_log().default.log('Removing Push Certificate');
await ctx.ios.deletePushCert(app);
}
async removeSpecific(ctx, selected) {
const credentials = await ctx.ios.getAllCredentials(this.accountName);
const apps = getAppsUsingPushCred(credentials, selected);
const appsList = apps.map(appCred => appCred.experienceName).join(', ');
if (appsList && !ctx.nonInteractive) {
_log().default.log('Removing Push Key');
const confirm = await (0, _prompts().confirmAsync)({
message: `Removing this key/cert will disable notifications in ${appsList}. Do you want to continue?`
});
if (!confirm) {
_log().default.log('Aborting');
return;
}
}
_log().default.log('Removing Push Key...\n');
await ctx.ios.deletePushKey(selected.id, this.accountName);
let shouldRevoke = this.shouldRevoke;
if (!shouldRevoke && !ctx.nonInteractive) {
const revoke = await (0, _prompts().confirmAsync)({
message: `Do you also want to revoke it on Apple Developer Portal?`
});
shouldRevoke = revoke;
}
if (shouldRevoke) {
await ctx.ensureAppleCtx();
await new (_appleApi().PushKeyManager)(ctx.appleCtx).revoke([selected.apnsKeyId]);
}
}
}
exports.RemoveIosPush = RemoveIosPush;
class UpdateIosPush {
constructor(accountName) {
this.accountName = accountName;
}
async open(ctx) {
if (ctx.nonInteractive) {
throw new (_CommandError().default)('NON_INTERACTIVE', "Start the CLI without the '--non-interactive' flag to select a push notification credential to update.");
}
const selected = await selectPushCredFromList(ctx, this.accountName, {
allowLegacy: false
});
if (selected) {
await this.updateSpecific(ctx, selected);
_log().default.log(_chalk().default.green('Successfully updated Push Notification Key.\n'));
const credentials = await ctx.ios.getAllCredentials(this.accountName);
const updated = credentials.userCredentials.find(i => i.id === selected.id);
if (updated) {
(0, _list().displayIosUserCredentials)(updated);
}
_log().default.log();
}
return null;
}
async updateSpecific(ctx, selected) {
const credentials = await ctx.ios.getAllCredentials(this.accountName);
const apps = getAppsUsingPushCred(credentials, selected);
const appsList = apps.map(appCred => appCred.experienceName).join(', ');
if (apps.length > 1) {
if (ctx.nonInteractive) {
throw new (_CommandError().default)('NON_INTERACTIVE', `Updating credentials will affect all applications that are using this key (${appsList}). Start the CLI without the '--non-interactive' flag to confirm.`);
}
const confirm = await (0, _prompts().confirmAsync)({
message: `Update will affect all applications that are using this key (${appsList}). Do you want to continue?`
});
if (!confirm) {
_log().default.warn('Aborting update process');
return;
}
}
const newPushKey = await this.provideOrGenerate(ctx);
await ctx.ios.updatePushKey(selected.id, this.accountName, newPushKey);
}
async provideOrGenerate(ctx) {
const userProvided = await (0, _promptForCredentials().askForUserProvided)(_credentials().pushKeySchema);
if (userProvided) {
const isValid = await validatePushKey(ctx, userProvided);
return isValid ? userProvided : await this.provideOrGenerate(ctx);
}
return await generatePushKey(ctx, this.accountName);
}
}
exports.UpdateIosPush = UpdateIosPush;
class UseExistingPushNotification {
constructor(app) {
this.app = app;
}
async open(ctx) {
if (ctx.nonInteractive) {
throw new (_CommandError().default)('NON_INTERACTIVE', "Start the CLI without the '--non-interactive' flag to select a push notification credential to use.");
}
const selected = await selectPushCredFromList(ctx, this.app.accountName, {
allowLegacy: false
});
if (selected) {
await ctx.ios.usePushKey(this.app, selected.id);
_log().default.log(_chalk().default.green(`Successfully assigned Push Notifactions Key to ${this.app.accountName}/${this.app.projectName} (${this.app.bundleIdentifier})`));
}
return null;
}
}
exports.UseExistingPushNotification = UseExistingPushNotification;
class CreateOrReusePushKey {
constructor(app) {
this.app = app;
}
async assignPushKey(ctx, userCredentialsId) {
await ctx.ios.usePushKey(this.app, userCredentialsId);
_log().default.log(_chalk().default.green(`Successfully assigned Push Key to ${this.app.accountName}/${this.app.projectName} (${this.app.bundleIdentifier})`));
}
async open(ctx) {
if (!ctx.user) {
throw new Error(`This workflow requires you to be logged in.`);
}
const existingPushKeys = await getValidPushKeys(await ctx.ios.getAllCredentials(this.app.accountName), ctx);
if (existingPushKeys.length === 0) {
const pushKey = await new CreateIosPush(this.app.accountName).create(ctx);
await this.assignPushKey(ctx, pushKey.id);
return null;
}
// autoselect creds if we find valid keys
const autoselectedPushKey = existingPushKeys[0];
if (!ctx.nonInteractive) {
const confirm = await (0, _prompts().confirmAsync)({
message: `${formatPushKey(autoselectedPushKey, await ctx.ios.getAllCredentials(this.app.accountName), 'VALID')} \n Would you like to use this Push Key?`,
limit: Infinity
});
if (!confirm) {
return await this._createOrReuse(ctx);
}
}
// Use autosuggested push key
_log().default.log(`Using Push Key: ${autoselectedPushKey.apnsKeyId}`);
await this.assignPushKey(ctx, autoselectedPushKey.id);
return null;
}
async _createOrReuse(ctx) {
const choices = [{
title: '[Choose existing push key] (Recommended)',
value: 'CHOOSE_EXISTING'
}, {
title: '[Add a new push key]',
value: 'GENERATE'
}];
const question = {
type: 'select',
name: 'action',
message: 'Select an iOS push key to use for push notifications:',
choices
};
const {
action
} = await (0, _prompts().default)(question);
if (action === 'GENERATE') {
const pushKey = await new CreateIosPush(this.app.accountName).create(ctx);
await this.assignPushKey(ctx, pushKey.id);
return null;
} else if (action === 'CHOOSE_EXISTING') {
return new UseExistingPushNotification(this.app);
}
throw new Error('unsupported action');
}
}
exports.CreateOrReusePushKey = CreateOrReusePushKey;
async function getValidPushKeys(iosCredentials, ctx) {
const pushKeys = iosCredentials.userCredentials.filter(cred => cred.type === 'push-key');
if (!ctx.hasAppleCtx()) {
_log().default.log(_chalk().default.yellow(`Unable to determine validity of Push Keys due to insufficient Apple Credentials`));
return pushKeys;
}
const pushKeyManager = new (_appleApi().PushKeyManager)(ctx.appleCtx);
const pushInfoFromApple = await pushKeyManager.list();
return await filterRevokedPushKeys(pushInfoFromApple, pushKeys);
}
function getValidityStatus(pushKey, validPushKeys) {
if (!validPushKeys) {
return 'UNKNOWN';
}
return validPushKeys.includes(pushKey) ? 'VALID' : 'INVALID';
}
async function selectPushCredFromList(ctx, accountName, options = {}) {
const iosCredentials = await ctx.ios.getAllCredentials(accountName);
const allowLegacy = options.allowLegacy || true;
let pushKeys = iosCredentials.userCredentials.filter(cred => cred.type === 'push-key');
let validPushKeys = null;
if (ctx.hasAppleCtx()) {
const pushKeyManager = new (_appleApi().PushKeyManager)(ctx.appleCtx);
const pushInfoFromApple = await pushKeyManager.list();
validPushKeys = await filterRevokedPushKeys(pushInfoFromApple, pushKeys);
}
pushKeys = options.filterInvalid && validPushKeys ? validPushKeys : pushKeys;
const pushCerts = allowLegacy ? iosCredentials.appCredentials.filter(({
credentials
}) => credentials.pushP12 && credentials.pushPassword) : [];
const pushCredentials = [...pushCerts, ...pushKeys];
if (pushCredentials.length === 0) {
_log().default.warn('There are no push credentials available in your account');
return null;
}
const getName = pushCred => {
if ('type' in pushCred) {
return formatPushKey(pushCred, iosCredentials, getValidityStatus(pushCred, validPushKeys));
}
const pushCert = pushCred;
return `Push Certificate (PushId: ${pushCert.credentials.pushId || '------'}, TeamId: ${pushCert.credentials.teamId || '-------'} used in ${pushCert.experienceName})`;
};
const question = {
type: 'select',
name: 'credentialsIndex',
message: 'Select credentials from list',
choices: pushCredentials.map((entry, index) => ({
title: getName(entry),
value: index
}))
};
const {
credentialsIndex
} = await (0, _prompts().default)(question);
return pushCredentials[credentialsIndex];
}
function getAppsUsingPushCred(iosCredentials, pushCred) {
var _pushCred$credentials, _pushCred$credentials2;
if ('type' in pushCred) {
return iosCredentials.appCredentials.filter(cred => cred.pushCredentialsId === pushCred.id);
} else if ((_pushCred$credentials = pushCred.credentials) !== null && _pushCred$credentials !== void 0 && _pushCred$credentials.pushP12 && (_pushCred$credentials2 = pushCred.credentials) !== null && _pushCred$credentials2 !== void 0 && _pushCred$credentials2.pushPassword) {
return [pushCred];
}
return [];
}
function formatPushKeyFromApple(appleInfo, credentials) {
const userCredentials = credentials.userCredentials.filter(cred => cred.type === 'push-key' && cred.apnsKeyId === appleInfo.id);
const appCredentials = userCredentials.length !== 0 ? credentials.appCredentials.filter(cred => cred.pushCredentialsId === userCredentials[0].id) : [];
const joinApps = appCredentials.map(i => ` ${i.experienceName} (${i.bundleIdentifier})`).join('\n');
const usedByString = joinApps ? ` ${_chalk().default.gray(`used by\n${joinApps}`)}` : ` ${_chalk().default.gray(`not used by any apps`)}`;
const {
name,
id
} = appleInfo;
const pushKey = userCredentials[0];
const teamText = pushKey ? `, Team ID: ${pushKey.teamId || '---'}, Team name: ${pushKey.teamName || '---'}` : '';
return `${name} - KeyId: ${id}${teamText}\n${usedByString}`;
}
function formatPushKey(pushKey, credentials, validityStatus = 'UNKNOWN') {
const appCredentials = credentials.appCredentials.filter(cred => cred.pushCredentialsId === pushKey.id);
const joinApps = appCredentials.map(i => `${i.experienceName} (${i.bundleIdentifier})`).join(', ');
const usedByString = joinApps ? `\n ${_chalk().default.gray(`used by ${joinApps}`)}` : `\n ${_chalk().default.gray(`not used by any apps`)}`;
let validityText;
if (validityStatus === 'VALID') {
validityText = _chalk().default.gray("\n ✅ Currently valid on Apple's servers.");
} else if (validityStatus === 'INVALID') {
validityText = _chalk().default.gray("\n ❌ No longer valid on Apple's servers.");
} else {
validityText = _chalk().default.gray("\n ❓ Validity of this certificate on Apple's servers is unknown.");
}
return `Push Notifications Key (Key ID: ${pushKey.apnsKeyId}, Team ID: ${pushKey.teamId})${usedByString}${validityText}`;
}
async function generatePushKey(ctx, accountName) {
await ctx.ensureAppleCtx();
const manager = new (_appleApi().PushKeyManager)(ctx.appleCtx);
try {
return await manager.create();
} catch (e) {
if (e.code === 'APPLE_PUSH_KEYS_TOO_MANY_GENERATED_ERROR') {
const keys = await manager.list();
_log().default.warn('Maximum number of Push Notifications Keys generated on Apple Developer Portal.');
_log().default.warn(APPLE_KEYS_TOO_MANY_GENERATED_ERROR);
if (ctx.nonInteractive) {
throw new (_CommandError().default)('NON_INTERACTIVE', "Start the CLI without the '--non-interactive' to revoke push notification keys.");
}
const credentials = await ctx.ios.getAllCredentials(accountName);
const usedByExpo = credentials.userCredentials.filter(cert => cert.type === 'push-key').reduce((acc, cert) => ({
...acc,
[cert.apnsKeyId]: cert
}), {});
// https://docs.expo.dev/distribution/app-signing/#summary
const here = (0, _terminalLink().default)('here', 'https://bit.ly/3cfJJkQ');
_log().default.log(_chalk().default.grey(`⚠️ Revoking a Push Key will affect other apps that rely on it`));
_log().default.log(_chalk().default.grey(` Learn more ${here}`));
_log().default.log();
const {
revoke
} = await (0, _prompts().default)([{
type: 'multiselect',
name: 'revoke',
message: 'Select Push Notifications Key to revoke.',
choices: keys.map((key, index) => ({
value: index,
title: formatPushKeyFromApple(key, credentials)
})),
optionsPerPage: 20
}]);
for (const index of revoke) {
const certInfo = keys[index];
if (certInfo && usedByExpo[certInfo.id]) {
await new RemoveIosPush(accountName, true).removeSpecific(ctx, usedByExpo[certInfo.id]);
} else {
await manager.revoke([certInfo.id]);
}
}
} else {
throw e;
}
}
return await generatePushKey(ctx, accountName);
}
async function validatePushKey(ctx, pushKey) {
if (!ctx.hasAppleCtx()) {
_log().default.warn('Unable to validate Push Keys due to insufficient Apple Credentials');
return true;
}
const spinner = (0, _ora().ora)(`Checking validity of push key on Apple Developer Portal...`).start();
const pushKeyManager = new (_appleApi().PushKeyManager)(ctx.appleCtx);
const pushInfoFromApple = await pushKeyManager.list();
const filteredFormattedPushKeyArray = await filterRevokedPushKeys(pushInfoFromApple, [pushKey]);
const isValidPushKey = filteredFormattedPushKeyArray.length > 0;
if (isValidPushKey) {
const successMsg = `Successfully validated Push Key against Apple Servers`;
spinner.succeed(successMsg);
} else {
const failureMsg = `This Push Key is no longer valid on the Apple Developer Portal`;
spinner.fail(failureMsg);
}
return isValidPushKey;
}
async function filterRevokedPushKeys(pushInfoFromApple, pushKeys) {
// if the credentials are valid, check it against apple to make sure it hasnt been revoked
const validKeyIdsOnAppleServer = pushInfoFromApple.map(pushKey => pushKey.id);
const validPushKeysOnExpoServer = pushKeys.filter(pushKey => {
return validKeyIdsOnAppleServer.includes(pushKey.apnsKeyId);
});
return validPushKeysOnExpoServer;
}
async function getPushKeyFromParams(builderOptions) {
const {
pushId,
pushP8Path,
teamId
} = builderOptions;
// none of the pushKey params were set, assume user has no intention of passing it in
if (!pushId && !pushP8Path) {
return null;
}
// partial pushKey params were set, assume user has intention of passing it in
if (!(pushId && pushP8Path && teamId)) {
throw new Error('In order to provide a Push Key through the CLI parameters, you have to pass --push-id, --push-p8-path and --team-id parameters.');
}
return {
apnsKeyId: pushId,
apnsKeyP8: await _fsExtra().default.readFile(pushP8Path, 'utf8'),
teamId
};
}
async function usePushKeyFromParams(ctx, app, pushKey) {
const isValid = await validatePushKey(ctx, pushKey);
if (!isValid) {
throw new Error('Cannot validate uploaded Push Key');
}
const iosPushCredentials = await ctx.ios.createPushKey(app.accountName, pushKey);
await ctx.ios.usePushKey(app, iosPushCredentials.id);
_log().default.log(_chalk().default.green(`Successfully assigned Push Key to ${app.accountName}/${app.projectName} (${app.bundleIdentifier})`));
return iosPushCredentials;
}
//# sourceMappingURL=IosPushCredentials.js.map