From b096105c4505704f357c48a35ee78b5a30290ec2 Mon Sep 17 00:00:00 2001 From: Chris Walter Date: Mon, 22 Jan 2024 22:38:26 -0800 Subject: [PATCH] [fcmv1] Manage FCM V1 Google Service Account Key in CLI # Why NOTE: DO NOT LAND THIS UNTIL GRAPHQL CHANGES LAND IN WWW We're adding support for FCM V1 credentials, as Google is shutting down the FCM Legacy API for sending Android notifications in June. # How Add new prompts and refactor existing prompts to match this schematic: # Test Plan Verified all functionality e2e: --- .../AssignGoogleServiceAccountKeyForFcmV1.ts | 33 +++++++ ...nGoogleServiceAccountKeyForSubmissions.ts} | 2 +- .../actions/CreateGoogleServiceAccountKey.ts | 2 +- .../CreateGoogleServiceAccountKeyForFcmV1.ts | 76 ++++++++++++++++ .../SetUpGoogleServiceAccountKeyForFcmV1.ts | 87 +++++++++++++++++++ ...pGoogleServiceAccountKeyForSubmissions.ts} | 9 +- .../AssignGoogleServiceAccountKey-test.ts | 12 ++- .../SetUpGoogleServiceAccountKey-test.ts | 16 ++-- .../credentials/android/api/GraphqlClient.ts | 10 +++ .../AndroidAppCredentialsMutation.ts | 39 +++++++++ .../android/utils/googleServiceAccountKey.ts | 2 +- .../android/utils/printCredentials.ts | 43 ++++++++- .../src/credentials/manager/Actions.ts | 12 ++- .../src/credentials/manager/AndroidActions.ts | 60 ++++++++++--- .../src/credentials/manager/ManageAndroid.ts | 47 +++++++--- packages/eas-cli/src/graphql/generated.ts | 18 ++-- .../credentials/AndroidAppCredentials.ts | 4 + .../submit/android/ServiceAccountSource.ts | 6 +- .../__tests__/ServiceAccountSource-test.ts | 4 +- 19 files changed, 427 insertions(+), 55 deletions(-) create mode 100644 packages/eas-cli/src/credentials/android/actions/AssignGoogleServiceAccountKeyForFcmV1.ts rename packages/eas-cli/src/credentials/android/actions/{AssignGoogleServiceAccountKey.ts => AssignGoogleServiceAccountKeyForSubmissions.ts} (94%) create mode 100644 packages/eas-cli/src/credentials/android/actions/CreateGoogleServiceAccountKeyForFcmV1.ts create mode 100644 packages/eas-cli/src/credentials/android/actions/SetUpGoogleServiceAccountKeyForFcmV1.ts rename packages/eas-cli/src/credentials/android/actions/{SetUpGoogleServiceAccountKey.ts => SetUpGoogleServiceAccountKeyForSubmissions.ts} (90%) diff --git a/packages/eas-cli/src/credentials/android/actions/AssignGoogleServiceAccountKeyForFcmV1.ts b/packages/eas-cli/src/credentials/android/actions/AssignGoogleServiceAccountKeyForFcmV1.ts new file mode 100644 index 0000000000..95ca349863 --- /dev/null +++ b/packages/eas-cli/src/credentials/android/actions/AssignGoogleServiceAccountKeyForFcmV1.ts @@ -0,0 +1,33 @@ +import { + CommonAndroidAppCredentialsFragment, + GoogleServiceAccountKeyFragment, +} from '../../../graphql/generated'; +import Log from '../../../log'; +import { CredentialsContext } from '../../context'; +import { AppLookupParams } from '../api/GraphqlClient'; + +export class AssignGoogleServiceAccountKeyForFcmV1 { + constructor(private app: AppLookupParams) {} + + public async runAsync( + ctx: CredentialsContext, + googleServiceAccountKey: GoogleServiceAccountKeyFragment + ): Promise { + const appCredentials = + await ctx.android.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync( + ctx.graphqlClient, + this.app + ); + const updatedAppCredentials = await ctx.android.updateAndroidAppCredentialsAsync( + ctx.graphqlClient, + appCredentials, + { + googleServiceAccountKeyForFcmV1Id: googleServiceAccountKey.id, + } + ); + Log.succeed( + `Google Service Account Key assigned to ${this.app.androidApplicationIdentifier} for FCM V1` + ); + return updatedAppCredentials; + } +} diff --git a/packages/eas-cli/src/credentials/android/actions/AssignGoogleServiceAccountKey.ts b/packages/eas-cli/src/credentials/android/actions/AssignGoogleServiceAccountKeyForSubmissions.ts similarity index 94% rename from packages/eas-cli/src/credentials/android/actions/AssignGoogleServiceAccountKey.ts rename to packages/eas-cli/src/credentials/android/actions/AssignGoogleServiceAccountKeyForSubmissions.ts index 5fd45fe4cd..629ad6d72d 100644 --- a/packages/eas-cli/src/credentials/android/actions/AssignGoogleServiceAccountKey.ts +++ b/packages/eas-cli/src/credentials/android/actions/AssignGoogleServiceAccountKeyForSubmissions.ts @@ -6,7 +6,7 @@ import Log from '../../../log'; import { CredentialsContext } from '../../context'; import { AppLookupParams } from '../api/GraphqlClient'; -export class AssignGoogleServiceAccountKey { +export class AssignGoogleServiceAccountKeyForSubmissions { constructor(private app: AppLookupParams) {} public async runAsync( diff --git a/packages/eas-cli/src/credentials/android/actions/CreateGoogleServiceAccountKey.ts b/packages/eas-cli/src/credentials/android/actions/CreateGoogleServiceAccountKey.ts index 65a4d4ed04..27fb7a27e1 100644 --- a/packages/eas-cli/src/credentials/android/actions/CreateGoogleServiceAccountKey.ts +++ b/packages/eas-cli/src/credentials/android/actions/CreateGoogleServiceAccountKey.ts @@ -46,7 +46,7 @@ export class CreateGoogleServiceAccountKey { Log.log( `${chalk.bold( - 'A Google Service Account JSON key is required to upload your app to Google Play Store' + 'A Google Service Account JSON key is required for uploading your app to Google Play Store, and for sending Android Notifications via FCM V1.' )}.\n` + `If you're not sure what this is or how to create one, ${learnMore( 'https://expo.fyi/creating-google-service-account', diff --git a/packages/eas-cli/src/credentials/android/actions/CreateGoogleServiceAccountKeyForFcmV1.ts b/packages/eas-cli/src/credentials/android/actions/CreateGoogleServiceAccountKeyForFcmV1.ts new file mode 100644 index 0000000000..85ff81bd59 --- /dev/null +++ b/packages/eas-cli/src/credentials/android/actions/CreateGoogleServiceAccountKeyForFcmV1.ts @@ -0,0 +1,76 @@ +import chalk from 'chalk'; +import fs from 'fs-extra'; + +import { AccountFragment, GoogleServiceAccountKeyFragment } from '../../../graphql/generated'; +import Log, { learnMore } from '../../../log'; +import { promptAsync } from '../../../prompts'; +import { CredentialsContext } from '../../context'; +import { GoogleServiceAccountKey } from '../credentials'; +import { + detectGoogleServiceAccountKeyPathAsync, + readAndValidateServiceAccountKey, +} from '../utils/googleServiceAccountKey'; + +export class CreateGoogleServiceAccountKeyForFcmV1 { + constructor(private account: AccountFragment) {} + + public async runAsync(ctx: CredentialsContext): Promise { + if (ctx.nonInteractive) { + throw new Error(`New FCM V1 Service Account Key cannot be created in non-interactive mode.`); + } + const jsonKeyObject = await this.provideAsync(ctx); + const gsaKeyFragment = await ctx.android.createGoogleServiceAccountKeyAsync( + ctx.graphqlClient, + this.account, + jsonKeyObject + ); + Log.succeed('Uploaded FCM V1 Service Account Key.'); + return gsaKeyFragment; + } + + private async provideAsync(ctx: CredentialsContext): Promise { + try { + const keyJsonPath = await this.provideKeyJsonPathAsync(ctx); + return readAndValidateServiceAccountKey(keyJsonPath); + } catch (e) { + Log.error(e); + return await this.provideAsync(ctx); + } + } + + private async provideKeyJsonPathAsync(ctx: CredentialsContext): Promise { + const detectedPath = await detectGoogleServiceAccountKeyPathAsync(ctx.projectDir); + if (detectedPath) { + return detectedPath; + } + + Log.log( + `${chalk.bold( + 'A Google Service Account JSON key is required to send Android notifications via FCM V1' + )}.\n` + + `If you're not sure what this is or how to create one, ${learnMore( + 'https://expo.fyi/creating-google-service-account-for-fcm-v1', + { learnMoreMessage: 'learn more' } + )}` + ); + const { filePath } = await promptAsync({ + name: 'filePath', + message: 'Path to Google Service Account file:', + initial: 'api-0000000000000000000-111111-aaaaaabbbbbb.json', + type: 'text', + // eslint-disable-next-line async-protect/async-suffix + validate: async (filePath: string) => { + try { + const stats = await fs.stat(filePath); + if (stats.isFile()) { + return true; + } + return 'Input is not a file.'; + } catch { + return 'File does not exist.'; + } + }, + }); + return filePath; + } +} diff --git a/packages/eas-cli/src/credentials/android/actions/SetUpGoogleServiceAccountKeyForFcmV1.ts b/packages/eas-cli/src/credentials/android/actions/SetUpGoogleServiceAccountKeyForFcmV1.ts new file mode 100644 index 0000000000..d8fabe1656 --- /dev/null +++ b/packages/eas-cli/src/credentials/android/actions/SetUpGoogleServiceAccountKeyForFcmV1.ts @@ -0,0 +1,87 @@ +import nullthrows from 'nullthrows'; + +import { AssignGoogleServiceAccountKeyForFcmV1 } from './AssignGoogleServiceAccountKeyForFcmV1'; +import { CreateGoogleServiceAccountKeyForFcmV1 } from './CreateGoogleServiceAccountKeyForFcmV1'; +import { UseExistingGoogleServiceAccountKey } from './UseExistingGoogleServiceAccountKey'; +import { + CommonAndroidAppCredentialsFragment, + GoogleServiceAccountKeyFragment, +} from '../../../graphql/generated'; +import Log from '../../../log'; +import { promptAsync } from '../../../prompts'; +import { CredentialsContext } from '../../context'; +import { MissingCredentialsNonInteractiveError } from '../../errors'; +import { AppLookupParams } from '../api/GraphqlClient'; + +export class SetUpGoogleServiceAccountKeyForFcmV1 { + constructor(private app: AppLookupParams) {} + + public async runAsync(ctx: CredentialsContext): Promise { + const isKeySetup = await this.isGoogleServiceAccountKeySetupAsync(ctx); + if (isKeySetup) { + Log.succeed('Google Service Account Key for FCM V1 already set up.'); + return nullthrows( + await ctx.android.getAndroidAppCredentialsWithCommonFieldsAsync( + ctx.graphqlClient, + this.app + ), + 'androidAppCredentials cannot be null if google service account key is already set up' + ); + } + if (ctx.nonInteractive) { + throw new MissingCredentialsNonInteractiveError( + 'Google Service Account Keys cannot be set up in --non-interactive mode.' + ); + } + + const keysForAccount = await ctx.android.getGoogleServiceAccountKeysForAccountAsync( + ctx.graphqlClient, + this.app.account + ); + let googleServiceAccountKey = null; + if (keysForAccount.length === 0) { + googleServiceAccountKey = await new CreateGoogleServiceAccountKeyForFcmV1( + this.app.account + ).runAsync(ctx); + } else { + googleServiceAccountKey = await this.createOrUseExistingKeyAsync(ctx); + } + return await new AssignGoogleServiceAccountKeyForFcmV1(this.app).runAsync( + ctx, + googleServiceAccountKey + ); + } + + private async isGoogleServiceAccountKeySetupAsync(ctx: CredentialsContext): Promise { + const appCredentials = await ctx.android.getAndroidAppCredentialsWithCommonFieldsAsync( + ctx.graphqlClient, + this.app + ); + return !!appCredentials?.googleServiceAccountKeyForFcmV1; + } + + private async createOrUseExistingKeyAsync( + ctx: CredentialsContext + ): Promise { + const { action } = await promptAsync({ + type: 'select', + name: 'action', + message: 'Select the Google Service Account Key to use for FCM V1:', + choices: [ + { + title: '[Choose an existing key]', + value: 'CHOOSE_EXISTING', + }, + { title: '[Upload a new service account key]', value: 'GENERATE' }, + ], + }); + + if (action === 'GENERATE') { + return await new CreateGoogleServiceAccountKeyForFcmV1(this.app.account).runAsync(ctx); + } + return ( + (await new UseExistingGoogleServiceAccountKey(this.app.account).runAsync(ctx)) ?? + (await this.createOrUseExistingKeyAsync(ctx)) + ); + } +} diff --git a/packages/eas-cli/src/credentials/android/actions/SetUpGoogleServiceAccountKey.ts b/packages/eas-cli/src/credentials/android/actions/SetUpGoogleServiceAccountKeyForSubmissions.ts similarity index 90% rename from packages/eas-cli/src/credentials/android/actions/SetUpGoogleServiceAccountKey.ts rename to packages/eas-cli/src/credentials/android/actions/SetUpGoogleServiceAccountKeyForSubmissions.ts index 41c747b9f4..22b3c539c9 100644 --- a/packages/eas-cli/src/credentials/android/actions/SetUpGoogleServiceAccountKey.ts +++ b/packages/eas-cli/src/credentials/android/actions/SetUpGoogleServiceAccountKeyForSubmissions.ts @@ -1,6 +1,6 @@ import nullthrows from 'nullthrows'; -import { AssignGoogleServiceAccountKey } from './AssignGoogleServiceAccountKey'; +import { AssignGoogleServiceAccountKeyForSubmissions } from './AssignGoogleServiceAccountKeyForSubmissions'; import { CreateGoogleServiceAccountKey } from './CreateGoogleServiceAccountKey'; import { UseExistingGoogleServiceAccountKey } from './UseExistingGoogleServiceAccountKey'; import { @@ -13,7 +13,7 @@ import { CredentialsContext } from '../../context'; import { MissingCredentialsNonInteractiveError } from '../../errors'; import { AppLookupParams } from '../api/GraphqlClient'; -export class SetUpGoogleServiceAccountKey { +export class SetUpGoogleServiceAccountKeyForSubmissions { constructor(private app: AppLookupParams) {} public async runAsync(ctx: CredentialsContext): Promise { @@ -46,7 +46,10 @@ export class SetUpGoogleServiceAccountKey { } else { googleServiceAccountKey = await this.createOrUseExistingKeyAsync(ctx); } - return await new AssignGoogleServiceAccountKey(this.app).runAsync(ctx, googleServiceAccountKey); + return await new AssignGoogleServiceAccountKeyForSubmissions(this.app).runAsync( + ctx, + googleServiceAccountKey + ); } private async isGoogleServiceAccountKeySetupAsync(ctx: CredentialsContext): Promise { diff --git a/packages/eas-cli/src/credentials/android/actions/__tests__/AssignGoogleServiceAccountKey-test.ts b/packages/eas-cli/src/credentials/android/actions/__tests__/AssignGoogleServiceAccountKey-test.ts index 7265664a82..170fa13095 100644 --- a/packages/eas-cli/src/credentials/android/actions/__tests__/AssignGoogleServiceAccountKey-test.ts +++ b/packages/eas-cli/src/credentials/android/actions/__tests__/AssignGoogleServiceAccountKey-test.ts @@ -2,12 +2,12 @@ import { AppQuery } from '../../../../graphql/queries/AppQuery'; import { testGoogleServiceAccountKeyFragment } from '../../../__tests__/fixtures-android'; import { testAppQueryByIdResponse } from '../../../__tests__/fixtures-constants'; import { createCtxMock } from '../../../__tests__/fixtures-context'; -import { AssignGoogleServiceAccountKey } from '../AssignGoogleServiceAccountKey'; +import { AssignGoogleServiceAccountKeyForSubmissions } from '../AssignGoogleServiceAccountKey'; import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils'; jest.mock('../../../../graphql/queries/AppQuery'); -describe(AssignGoogleServiceAccountKey, () => { +describe(AssignGoogleServiceAccountKeyForSubmissions, () => { beforeEach(() => { jest.mocked(AppQuery.byIdAsync).mockResolvedValue(testAppQueryByIdResponse); }); @@ -16,7 +16,9 @@ describe(AssignGoogleServiceAccountKey, () => { nonInteractive: false, }); const appLookupParams = await getAppLookupParamsFromContextAsync(ctx); - const assignGoogleServiceAccountKeyAction = new AssignGoogleServiceAccountKey(appLookupParams); + const assignGoogleServiceAccountKeyAction = new AssignGoogleServiceAccountKeyForSubmissions( + appLookupParams + ); await assignGoogleServiceAccountKeyAction.runAsync(ctx, testGoogleServiceAccountKeyFragment); // expect app credentials to be fetched/created, then updated @@ -30,7 +32,9 @@ describe(AssignGoogleServiceAccountKey, () => { nonInteractive: true, }); const appLookupParams = await getAppLookupParamsFromContextAsync(ctx); - const assignGoogleServiceAccountKeyAction = new AssignGoogleServiceAccountKey(appLookupParams); + const assignGoogleServiceAccountKeyAction = new AssignGoogleServiceAccountKeyForSubmissions( + appLookupParams + ); // dont fail if users are running in non-interactive mode await expect( diff --git a/packages/eas-cli/src/credentials/android/actions/__tests__/SetUpGoogleServiceAccountKey-test.ts b/packages/eas-cli/src/credentials/android/actions/__tests__/SetUpGoogleServiceAccountKey-test.ts index c0fb3201f4..498487a63e 100644 --- a/packages/eas-cli/src/credentials/android/actions/__tests__/SetUpGoogleServiceAccountKey-test.ts +++ b/packages/eas-cli/src/credentials/android/actions/__tests__/SetUpGoogleServiceAccountKey-test.ts @@ -11,7 +11,7 @@ import { testAppQueryByIdResponse } from '../../../__tests__/fixtures-constants' import { createCtxMock } from '../../../__tests__/fixtures-context'; import { MissingCredentialsNonInteractiveError } from '../../../errors'; import { getAppLookupParamsFromContextAsync } from '../BuildCredentialsUtils'; -import { SetUpGoogleServiceAccountKey } from '../SetUpGoogleServiceAccountKey'; +import { SetUpGoogleServiceAccountKeyForSubmissions } from '../SetUpGoogleServiceAccountKeyForSubmissions'; jest.mock('../../../../prompts'); jest.mock('fs'); @@ -24,7 +24,7 @@ beforeEach(() => { vol.reset(); }); -describe(SetUpGoogleServiceAccountKey, () => { +describe(SetUpGoogleServiceAccountKeyForSubmissions, () => { beforeEach(() => { jest.mocked(AppQuery.byIdAsync).mockResolvedValue(testAppQueryByIdResponse); }); @@ -39,7 +39,9 @@ describe(SetUpGoogleServiceAccountKey, () => { }, }); const appLookupParams = await getAppLookupParamsFromContextAsync(ctx); - const setupGoogleServiceAccountKeyAction = new SetUpGoogleServiceAccountKey(appLookupParams); + const setupGoogleServiceAccountKeyAction = new SetUpGoogleServiceAccountKeyForSubmissions( + appLookupParams + ); await setupGoogleServiceAccountKeyAction.runAsync(ctx); expect(ctx.android.createGoogleServiceAccountKeyAsync).not.toHaveBeenCalled(); @@ -62,7 +64,9 @@ describe(SetUpGoogleServiceAccountKey, () => { }, }); const appLookupParams = await getAppLookupParamsFromContextAsync(ctx); - const setupGoogleServiceAccountKeyAction = new SetUpGoogleServiceAccountKey(appLookupParams); + const setupGoogleServiceAccountKeyAction = new SetUpGoogleServiceAccountKeyForSubmissions( + appLookupParams + ); await setupGoogleServiceAccountKeyAction.runAsync(ctx); expect(ctx.android.createGoogleServiceAccountKeyAsync).toHaveBeenCalledTimes(1); @@ -73,7 +77,9 @@ describe(SetUpGoogleServiceAccountKey, () => { nonInteractive: true, }); const appLookupParams = await getAppLookupParamsFromContextAsync(ctx); - const setupGoogleServiceAccountKeyAction = new SetUpGoogleServiceAccountKey(appLookupParams); + const setupGoogleServiceAccountKeyAction = new SetUpGoogleServiceAccountKeyForSubmissions( + appLookupParams + ); await expect(setupGoogleServiceAccountKeyAction.runAsync(ctx)).rejects.toThrowError( MissingCredentialsNonInteractiveError ); diff --git a/packages/eas-cli/src/credentials/android/api/GraphqlClient.ts b/packages/eas-cli/src/credentials/android/api/GraphqlClient.ts index 83615bb2fc..f6627ac324 100644 --- a/packages/eas-cli/src/credentials/android/api/GraphqlClient.ts +++ b/packages/eas-cli/src/credentials/android/api/GraphqlClient.ts @@ -106,9 +106,11 @@ export async function updateAndroidAppCredentialsAsync( { androidFcmId, googleServiceAccountKeyForSubmissionsId, + googleServiceAccountKeyForFcmV1Id, }: { androidFcmId?: string; googleServiceAccountKeyForSubmissionsId?: string; + googleServiceAccountKeyForFcmV1Id?: string; } ): Promise { let updatedAppCredentials = appCredentials; @@ -127,6 +129,14 @@ export async function updateAndroidAppCredentialsAsync( googleServiceAccountKeyForSubmissionsId ); } + if (googleServiceAccountKeyForFcmV1Id) { + updatedAppCredentials = + await AndroidAppCredentialsMutation.setGoogleServiceAccountKeyForFcmV1Async( + graphqlClient, + appCredentials.id, + googleServiceAccountKeyForFcmV1Id + ); + } return updatedAppCredentials; } diff --git a/packages/eas-cli/src/credentials/android/api/graphql/mutations/AndroidAppCredentialsMutation.ts b/packages/eas-cli/src/credentials/android/api/graphql/mutations/AndroidAppCredentialsMutation.ts index e69d8e66c9..d7c22a88ff 100644 --- a/packages/eas-cli/src/credentials/android/api/graphql/mutations/AndroidAppCredentialsMutation.ts +++ b/packages/eas-cli/src/credentials/android/api/graphql/mutations/AndroidAppCredentialsMutation.ts @@ -8,6 +8,7 @@ import { CommonAndroidAppCredentialsFragment, CreateAndroidAppCredentialsMutation, SetFcmMutation, + SetGoogleServiceAccountKeyForFcmV1Mutation, SetGoogleServiceAccountKeyForSubmissionsMutation, } from '../../../../../graphql/generated'; import { CommonAndroidAppCredentialsFragmentNode } from '../../../../../graphql/types/credentials/AndroidAppCredentials'; @@ -124,4 +125,42 @@ export const AndroidAppCredentialsMutation = { ); return data.androidAppCredentials.setGoogleServiceAccountKeyForSubmissions; }, + async setGoogleServiceAccountKeyForFcmV1Async( + graphqlClient: ExpoGraphqlClient, + androidAppCredentialsId: string, + googleServiceAccountKeyId: string + ): Promise { + const data = await withErrorHandlingAsync( + graphqlClient + .mutation( + gql` + mutation SetGoogleServiceAccountKeyForFcmV1Mutation( + $androidAppCredentialsId: ID! + $googleServiceAccountKeyId: ID! + ) { + androidAppCredentials { + setGoogleServiceAccountKeyForFcmV1( + id: $androidAppCredentialsId + googleServiceAccountKeyId: $googleServiceAccountKeyId + ) { + id + ...CommonAndroidAppCredentialsFragment + } + } + } + ${print(CommonAndroidAppCredentialsFragmentNode)} + `, + { + androidAppCredentialsId, + googleServiceAccountKeyId, + } + ) + .toPromise() + ); + assert( + data.androidAppCredentials.setGoogleServiceAccountKeyForFcmV1, + 'GraphQL: `setGoogleServiceAccountKeyForFcmV1` not defined in server response' + ); + return data.androidAppCredentials.setGoogleServiceAccountKeyForFcmV1; + }, }; diff --git a/packages/eas-cli/src/credentials/android/utils/googleServiceAccountKey.ts b/packages/eas-cli/src/credentials/android/utils/googleServiceAccountKey.ts index c659c95e64..8ba0803287 100644 --- a/packages/eas-cli/src/credentials/android/utils/googleServiceAccountKey.ts +++ b/packages/eas-cli/src/credentials/android/utils/googleServiceAccountKey.ts @@ -96,7 +96,7 @@ export async function detectGoogleServiceAccountKeyPathAsync( ): Promise { const foundFilePaths = await glob('**/*.json', { cwd: projectDir, - ignore: ['app.json', 'package*.json', 'tsconfig.json', 'node_modules'], + ignore: ['app.json', 'package*.json', 'tsconfig.json', 'node_modules', 'google-services.json'], }); const googleServiceFiles = foundFilePaths diff --git a/packages/eas-cli/src/credentials/android/utils/printCredentials.ts b/packages/eas-cli/src/credentials/android/utils/printCredentials.ts index dae3b1d586..894bbdeb4d 100644 --- a/packages/eas-cli/src/credentials/android/utils/printCredentials.ts +++ b/packages/eas-cli/src/credentials/android/utils/printCredentials.ts @@ -29,7 +29,7 @@ export function displayEmptyAndroidCredentials(appLookupParams: AppLookupParams) function displayAndroidFcmCredentials(appCredentials: CommonAndroidAppCredentialsFragment): void { const maybeFcm = appCredentials.androidFcm; Log.log( - formatFields([{ label: 'Push Notifications (FCM)', value: '' }], { + formatFields([{ label: 'Push Notifications (FCM Legacy)', value: '' }], { labelFormat: chalk.cyan.bold, }) ); @@ -60,9 +60,43 @@ function displayGoogleServiceAccountKeyForSubmissions( ): void { const maybeGsaKey = appCredentials.googleServiceAccountKeyForSubmissions; Log.log( - formatFields([{ label: 'Google Service Account Key For Submissions', value: '' }], { - labelFormat: chalk.cyan.bold, - }) + formatFields( + [{ label: 'Submissions: Google Service Account Key for Play Store Submissions', value: '' }], + { + labelFormat: chalk.cyan.bold, + } + ) + ); + if (!maybeGsaKey) { + Log.log(formatFields([{ label: '', value: 'None assigned yet' }])); + Log.newLine(); + return; + } + const { projectIdentifier, privateKeyIdentifier, clientEmail, clientIdentifier, updatedAt } = + maybeGsaKey; + + const fields = [ + { label: 'Project ID', value: projectIdentifier }, + { label: 'Client Email', value: clientEmail }, + { label: 'Client ID', value: clientIdentifier }, + { label: 'Private Key ID', value: privateKeyIdentifier }, + { label: 'Updated', value: `${fromNow(new Date(updatedAt))} ago` }, + ]; + Log.log(formatFields(fields, { labelFormat: chalk.cyan.bold })); + Log.newLine(); +} + +function displayGoogleServiceAccountKeyForFcmV1( + appCredentials: CommonAndroidAppCredentialsFragment +): void { + const maybeGsaKey = appCredentials.googleServiceAccountKeyForFcmV1; + Log.log( + formatFields( + [{ label: 'Push Notifications (FCM V1): Google Service Account Key For FCM V1', value: '' }], + { + labelFormat: chalk.cyan.bold, + } + ) ); if (!maybeGsaKey) { Log.log(formatFields([{ label: '', value: 'None assigned yet' }])); @@ -87,6 +121,7 @@ function displayEASAndroidAppCredentials( appCredentials: CommonAndroidAppCredentialsFragment ): void { displayAndroidFcmCredentials(appCredentials); + displayGoogleServiceAccountKeyForFcmV1(appCredentials); displayGoogleServiceAccountKeyForSubmissions(appCredentials); const sortedBuildCredentialsList = sortBuildCredentials( appCredentials.androidAppBuildCredentialsList diff --git a/packages/eas-cli/src/credentials/manager/Actions.ts b/packages/eas-cli/src/credentials/manager/Actions.ts index 6d9b3fc57c..dfea7a0ffa 100644 --- a/packages/eas-cli/src/credentials/manager/Actions.ts +++ b/packages/eas-cli/src/credentials/manager/Actions.ts @@ -13,7 +13,9 @@ export enum Scope { export enum AndroidActionType { ManageBuildCredentials, ManageFcm, - ManageGoogleServiceAccountKey, + ManageGoogleServiceAccountKeyForSubmissions, + ManageGoogleServiceAccount, + ManageGoogleServiceAccountKeyForFcmV1, ManageCredentialsJson, GoBackToCaller, GoBackToHighLevelActions, @@ -24,9 +26,13 @@ export enum AndroidActionType { CreateFcm, RemoveFcm, CreateGsaKey, - UseExistingGsaKey, + UseExistingGsaKeyForSubmissions, RemoveGsaKey, - SetUpGsaKey, + SetUpGsaKeyForSubmissions, + CreateGsaKeyForFcmV1, + UseExistingGsaKeyForFcmV1, + RemoveGsaKeyForFcmV1, + SetUpGsaKeyForFcmV1, UpdateCredentialsJson, SetUpBuildCredentialsFromCredentialsJson, } diff --git a/packages/eas-cli/src/credentials/manager/AndroidActions.ts b/packages/eas-cli/src/credentials/manager/AndroidActions.ts index 644ab67cb0..ab760d3c50 100644 --- a/packages/eas-cli/src/credentials/manager/AndroidActions.ts +++ b/packages/eas-cli/src/credentials/manager/AndroidActions.ts @@ -7,13 +7,13 @@ export const highLevelActions: ActionInfo[] = [ scope: Scope.Manager, }, { - value: AndroidActionType.ManageFcm, - title: 'Push Notifications: Manage your FCM API Key', + value: AndroidActionType.ManageGoogleServiceAccount, + title: 'Google Service Account', scope: Scope.Manager, }, { - value: AndroidActionType.ManageGoogleServiceAccountKey, - title: 'Google Service Account: Manage your Service Account Key', + value: AndroidActionType.ManageFcm, + title: 'Push Notifications (Legacy): Manage your FCM (Legacy) API Key', scope: Scope.Manager, }, { @@ -92,25 +92,61 @@ export const fcmActions: ActionInfo[] = [ }, ]; -export const gsaKeyActions: ActionInfo[] = [ +export const gsaKeyActionsForFcmV1: ActionInfo[] = [ { - value: AndroidActionType.SetUpGsaKey, - title: 'Set up a Google Service Account Key', + value: AndroidActionType.SetUpGsaKeyForFcmV1, + title: 'Set up a Google Service Account Key for Push Notifications (FCM V1)', scope: Scope.Project, }, { - value: AndroidActionType.CreateGsaKey, - title: 'Upload a Google Service Account Key', + value: AndroidActionType.UseExistingGsaKeyForFcmV1, + title: 'Select an existing Google Service Account Key for Push Notifications (FCM V1)', + scope: Scope.Project, + }, + { + value: AndroidActionType.GoBackToHighLevelActions, + title: 'Go back', + scope: Scope.Manager, + }, +]; + +export const gsaKeyActionsForSubmissions: ActionInfo[] = [ + { + value: AndroidActionType.SetUpGsaKeyForSubmissions, + title: 'Set up a Google Service Account Key for Play Store Submissions', + scope: Scope.Project, + }, + { + value: AndroidActionType.UseExistingGsaKeyForSubmissions, + title: 'Select an existing Google Service Account Key for Play Store Submissions', scope: Scope.Project, }, { - value: AndroidActionType.UseExistingGsaKey, - title: 'Use an existing Google Service Account Key', + value: AndroidActionType.GoBackToHighLevelActions, + title: 'Go back', + scope: Scope.Manager, + }, +]; + +export const gsaActions: ActionInfo[] = [ + { + value: AndroidActionType.ManageGoogleServiceAccountKeyForSubmissions, + title: 'Manage your Google Service Account Key for Play Store Submissions', + scope: Scope.Manager, + }, + { + value: AndroidActionType.ManageGoogleServiceAccountKeyForFcmV1, + title: 'Manage your Google Service Account Key for Push Notifications (FCM V1)', + scope: Scope.Manager, + }, + { + value: AndroidActionType.CreateGsaKey, + title: 'Upload a Google Service Account Key to the Keystore', scope: Scope.Project, }, { value: AndroidActionType.RemoveGsaKey, - title: 'Delete a Google Service Account Key', + title: 'Delete a Google Service Account Key from the Keystore', scope: Scope.Project, }, { diff --git a/packages/eas-cli/src/credentials/manager/ManageAndroid.ts b/packages/eas-cli/src/credentials/manager/ManageAndroid.ts index f1d71267d1..fdc7a1b1e2 100644 --- a/packages/eas-cli/src/credentials/manager/ManageAndroid.ts +++ b/packages/eas-cli/src/credentials/manager/ManageAndroid.ts @@ -7,7 +7,9 @@ import { buildCredentialsActions, credentialsJsonActions, fcmActions, - gsaKeyActions, + gsaActions, + gsaKeyActionsForFcmV1, + gsaKeyActionsForSubmissions, highLevelActions, } from './AndroidActions'; import { CreateAndroidBuildCredentials } from './CreateAndroidBuildCredentials'; @@ -19,7 +21,8 @@ import Log, { learnMore } from '../../log'; import { GradleBuildContext, resolveGradleBuildContextAsync } from '../../project/android/gradle'; import { promptAsync } from '../../prompts'; import { AssignFcm } from '../android/actions/AssignFcm'; -import { AssignGoogleServiceAccountKey } from '../android/actions/AssignGoogleServiceAccountKey'; +import { AssignGoogleServiceAccountKeyForSubmissions } from '../android/actions/AssignGoogleServiceAccountKeyForSubmissions'; +import { AssignGoogleServiceAccountKeyForFcmV1 } from '../android/actions/AssignGoogleServiceAccountKeyForFcmV1'; import { canCopyLegacyCredentialsAsync, getAppLookupParamsFromContextAsync, @@ -32,7 +35,8 @@ import { RemoveFcm } from '../android/actions/RemoveFcm'; import { SelectAndRemoveGoogleServiceAccountKey } from '../android/actions/RemoveGoogleServiceAccountKey'; import { RemoveKeystore } from '../android/actions/RemoveKeystore'; import { SetUpBuildCredentialsFromCredentialsJson } from '../android/actions/SetUpBuildCredentialsFromCredentialsJson'; -import { SetUpGoogleServiceAccountKey } from '../android/actions/SetUpGoogleServiceAccountKey'; +import { SetUpGoogleServiceAccountKeyForFcmV1 } from '../android/actions/SetUpGoogleServiceAccountKeyForFcmV1'; +import { SetUpGoogleServiceAccountKeyForSubmissions } from '../android/actions/SetUpGoogleServiceAccountKeyForSubmissions'; import { UpdateCredentialsJson } from '../android/actions/UpdateCredentialsJson'; import { UseExistingGoogleServiceAccountKey } from '../android/actions/UseExistingGoogleServiceAccountKey'; import { @@ -120,8 +124,16 @@ export class ManageAndroid { } else if (chosenAction === AndroidActionType.ManageFcm) { currentActions = fcmActions; continue; - } else if (chosenAction === AndroidActionType.ManageGoogleServiceAccountKey) { - currentActions = gsaKeyActions; + } else if (chosenAction === AndroidActionType.ManageGoogleServiceAccount) { + currentActions = gsaActions; + continue; + } else if ( + chosenAction === AndroidActionType.ManageGoogleServiceAccountKeyForSubmissions + ) { + currentActions = gsaKeyActionsForSubmissions; + continue; + } else if (chosenAction === AndroidActionType.ManageGoogleServiceAccountKeyForFcmV1) { + currentActions = gsaKeyActionsForFcmV1; continue; } else if (chosenAction === AndroidActionType.ManageCredentialsJson) { currentActions = credentialsJsonActions; @@ -193,20 +205,31 @@ export class ManageAndroid { await new AssignFcm(appLookupParams).runAsync(ctx, fcm); } else if (action === AndroidActionType.RemoveFcm) { await new RemoveFcm(appLookupParams).runAsync(ctx); - } else if (action === AndroidActionType.CreateGsaKey) { - const gsaKey = await new CreateGoogleServiceAccountKey(appLookupParams.account).runAsync(ctx); - await new AssignGoogleServiceAccountKey(appLookupParams).runAsync(ctx, gsaKey); - } else if (action === AndroidActionType.UseExistingGsaKey) { + } else if (action === AndroidActionType.SetUpGsaKeyForSubmissions) { + await new SetUpGoogleServiceAccountKeyForSubmissions(appLookupParams).runAsync(ctx); + } else if (action === AndroidActionType.UseExistingGsaKeyForSubmissions) { const gsaKey = await new UseExistingGoogleServiceAccountKey(appLookupParams.account).runAsync( ctx ); if (gsaKey) { - await new AssignGoogleServiceAccountKey(appLookupParams).runAsync(ctx, gsaKey); + await new AssignGoogleServiceAccountKeyForSubmissions(appLookupParams).runAsync( + ctx, + gsaKey + ); } + } else if (action === AndroidActionType.SetUpGsaKeyForFcmV1) { + await new SetUpGoogleServiceAccountKeyForFcmV1(appLookupParams).runAsync(ctx); + } else if (action === AndroidActionType.UseExistingGsaKeyForFcmV1) { + const gsaKey = await new UseExistingGoogleServiceAccountKey(appLookupParams.account).runAsync( + ctx + ); + if (gsaKey) { + await new AssignGoogleServiceAccountKeyForFcmV1(appLookupParams).runAsync(ctx, gsaKey); + } + } else if (action === AndroidActionType.CreateGsaKey) { + await new CreateGoogleServiceAccountKey(appLookupParams.account).runAsync(ctx); } else if (action === AndroidActionType.RemoveGsaKey) { await new SelectAndRemoveGoogleServiceAccountKey(appLookupParams.account).runAsync(ctx); - } else if (action === AndroidActionType.SetUpGsaKey) { - await new SetUpGoogleServiceAccountKey(appLookupParams).runAsync(ctx); } else if (action === AndroidActionType.UpdateCredentialsJson) { const buildCredentials = await new SelectExistingAndroidBuildCredentials( appLookupParams diff --git a/packages/eas-cli/src/graphql/generated.ts b/packages/eas-cli/src/graphql/generated.ts index 764034d281..17d4d4c2a8 100644 --- a/packages/eas-cli/src/graphql/generated.ts +++ b/packages/eas-cli/src/graphql/generated.ts @@ -6143,7 +6143,7 @@ export type CreateAndroidAppCredentialsMutationVariables = Exact<{ }>; -export type CreateAndroidAppCredentialsMutation = { __typename?: 'RootMutation', androidAppCredentials: { __typename?: 'AndroidAppCredentialsMutation', createAndroidAppCredentials: { __typename?: 'AndroidAppCredentials', id: string, applicationIdentifier?: string | null, isLegacy: boolean, app: { __typename?: 'App', id: string, fullName: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string, ownerUserActor?: { __typename?: 'SSOUser', id: string, username: string } | { __typename?: 'User', id: string, username: string } | null, users: Array<{ __typename?: 'UserPermission', role: Role, actor: { __typename?: 'Robot', id: string } | { __typename?: 'SSOUser', id: string } | { __typename?: 'User', id: string } }> } }, androidFcm?: { __typename?: 'AndroidFcm', id: string, credential: any, version: AndroidFcmVersion, createdAt: any, updatedAt: any, snippet: { __typename?: 'FcmSnippetLegacy', firstFourCharacters: string, lastFourCharacters: string } | { __typename?: 'FcmSnippetV1', projectId: string, keyId: string, serviceAccountEmail: string, clientId?: string | null } } | null, googleServiceAccountKeyForSubmissions?: { __typename?: 'GoogleServiceAccountKey', id: string, projectIdentifier: string, privateKeyIdentifier: string, clientEmail: string, clientIdentifier: string, createdAt: any, updatedAt: any } | null, androidAppBuildCredentialsList: Array<{ __typename?: 'AndroidAppBuildCredentials', id: string, isDefault: boolean, isLegacy: boolean, name: string, androidKeystore?: { __typename?: 'AndroidKeystore', id: string, type: AndroidKeystoreType, keystore: string, keystorePassword: string, keyAlias: string, keyPassword?: string | null, md5CertificateFingerprint?: string | null, sha1CertificateFingerprint?: string | null, sha256CertificateFingerprint?: string | null, createdAt: any, updatedAt: any } | null }> } } }; +export type CreateAndroidAppCredentialsMutation = { __typename?: 'RootMutation', androidAppCredentials: { __typename?: 'AndroidAppCredentialsMutation', createAndroidAppCredentials: { __typename?: 'AndroidAppCredentials', id: string, applicationIdentifier?: string | null, isLegacy: boolean, app: { __typename?: 'App', id: string, fullName: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string, ownerUserActor?: { __typename?: 'SSOUser', id: string, username: string } | { __typename?: 'User', id: string, username: string } | null, users: Array<{ __typename?: 'UserPermission', role: Role, actor: { __typename?: 'Robot', id: string } | { __typename?: 'SSOUser', id: string } | { __typename?: 'User', id: string } }> } }, androidFcm?: { __typename?: 'AndroidFcm', id: string, credential: any, version: AndroidFcmVersion, createdAt: any, updatedAt: any, snippet: { __typename?: 'FcmSnippetLegacy', firstFourCharacters: string, lastFourCharacters: string } | { __typename?: 'FcmSnippetV1', projectId: string, keyId: string, serviceAccountEmail: string, clientId?: string | null } } | null, googleServiceAccountKeyForFcmV1?: { __typename?: 'GoogleServiceAccountKey', id: string, projectIdentifier: string, privateKeyIdentifier: string, clientEmail: string, clientIdentifier: string, createdAt: any, updatedAt: any } | null, googleServiceAccountKeyForSubmissions?: { __typename?: 'GoogleServiceAccountKey', id: string, projectIdentifier: string, privateKeyIdentifier: string, clientEmail: string, clientIdentifier: string, createdAt: any, updatedAt: any } | null, androidAppBuildCredentialsList: Array<{ __typename?: 'AndroidAppBuildCredentials', id: string, isDefault: boolean, isLegacy: boolean, name: string, androidKeystore?: { __typename?: 'AndroidKeystore', id: string, type: AndroidKeystoreType, keystore: string, keystorePassword: string, keyAlias: string, keyPassword?: string | null, md5CertificateFingerprint?: string | null, sha1CertificateFingerprint?: string | null, sha256CertificateFingerprint?: string | null, createdAt: any, updatedAt: any } | null }> } } }; export type SetFcmMutationVariables = Exact<{ androidAppCredentialsId: Scalars['ID']['input']; @@ -6151,7 +6151,7 @@ export type SetFcmMutationVariables = Exact<{ }>; -export type SetFcmMutation = { __typename?: 'RootMutation', androidAppCredentials: { __typename?: 'AndroidAppCredentialsMutation', setFcm: { __typename?: 'AndroidAppCredentials', id: string, applicationIdentifier?: string | null, isLegacy: boolean, app: { __typename?: 'App', id: string, fullName: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string, ownerUserActor?: { __typename?: 'SSOUser', id: string, username: string } | { __typename?: 'User', id: string, username: string } | null, users: Array<{ __typename?: 'UserPermission', role: Role, actor: { __typename?: 'Robot', id: string } | { __typename?: 'SSOUser', id: string } | { __typename?: 'User', id: string } }> } }, androidFcm?: { __typename?: 'AndroidFcm', id: string, credential: any, version: AndroidFcmVersion, createdAt: any, updatedAt: any, snippet: { __typename?: 'FcmSnippetLegacy', firstFourCharacters: string, lastFourCharacters: string } | { __typename?: 'FcmSnippetV1', projectId: string, keyId: string, serviceAccountEmail: string, clientId?: string | null } } | null, googleServiceAccountKeyForSubmissions?: { __typename?: 'GoogleServiceAccountKey', id: string, projectIdentifier: string, privateKeyIdentifier: string, clientEmail: string, clientIdentifier: string, createdAt: any, updatedAt: any } | null, androidAppBuildCredentialsList: Array<{ __typename?: 'AndroidAppBuildCredentials', id: string, isDefault: boolean, isLegacy: boolean, name: string, androidKeystore?: { __typename?: 'AndroidKeystore', id: string, type: AndroidKeystoreType, keystore: string, keystorePassword: string, keyAlias: string, keyPassword?: string | null, md5CertificateFingerprint?: string | null, sha1CertificateFingerprint?: string | null, sha256CertificateFingerprint?: string | null, createdAt: any, updatedAt: any } | null }> } } }; +export type SetFcmMutation = { __typename?: 'RootMutation', androidAppCredentials: { __typename?: 'AndroidAppCredentialsMutation', setFcm: { __typename?: 'AndroidAppCredentials', id: string, applicationIdentifier?: string | null, isLegacy: boolean, app: { __typename?: 'App', id: string, fullName: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string, ownerUserActor?: { __typename?: 'SSOUser', id: string, username: string } | { __typename?: 'User', id: string, username: string } | null, users: Array<{ __typename?: 'UserPermission', role: Role, actor: { __typename?: 'Robot', id: string } | { __typename?: 'SSOUser', id: string } | { __typename?: 'User', id: string } }> } }, androidFcm?: { __typename?: 'AndroidFcm', id: string, credential: any, version: AndroidFcmVersion, createdAt: any, updatedAt: any, snippet: { __typename?: 'FcmSnippetLegacy', firstFourCharacters: string, lastFourCharacters: string } | { __typename?: 'FcmSnippetV1', projectId: string, keyId: string, serviceAccountEmail: string, clientId?: string | null } } | null, googleServiceAccountKeyForFcmV1?: { __typename?: 'GoogleServiceAccountKey', id: string, projectIdentifier: string, privateKeyIdentifier: string, clientEmail: string, clientIdentifier: string, createdAt: any, updatedAt: any } | null, googleServiceAccountKeyForSubmissions?: { __typename?: 'GoogleServiceAccountKey', id: string, projectIdentifier: string, privateKeyIdentifier: string, clientEmail: string, clientIdentifier: string, createdAt: any, updatedAt: any } | null, androidAppBuildCredentialsList: Array<{ __typename?: 'AndroidAppBuildCredentials', id: string, isDefault: boolean, isLegacy: boolean, name: string, androidKeystore?: { __typename?: 'AndroidKeystore', id: string, type: AndroidKeystoreType, keystore: string, keystorePassword: string, keyAlias: string, keyPassword?: string | null, md5CertificateFingerprint?: string | null, sha1CertificateFingerprint?: string | null, sha256CertificateFingerprint?: string | null, createdAt: any, updatedAt: any } | null }> } } }; export type SetGoogleServiceAccountKeyForSubmissionsMutationVariables = Exact<{ androidAppCredentialsId: Scalars['ID']['input']; @@ -6159,7 +6159,15 @@ export type SetGoogleServiceAccountKeyForSubmissionsMutationVariables = Exact<{ }>; -export type SetGoogleServiceAccountKeyForSubmissionsMutation = { __typename?: 'RootMutation', androidAppCredentials: { __typename?: 'AndroidAppCredentialsMutation', setGoogleServiceAccountKeyForSubmissions: { __typename?: 'AndroidAppCredentials', id: string, applicationIdentifier?: string | null, isLegacy: boolean, app: { __typename?: 'App', id: string, fullName: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string, ownerUserActor?: { __typename?: 'SSOUser', id: string, username: string } | { __typename?: 'User', id: string, username: string } | null, users: Array<{ __typename?: 'UserPermission', role: Role, actor: { __typename?: 'Robot', id: string } | { __typename?: 'SSOUser', id: string } | { __typename?: 'User', id: string } }> } }, androidFcm?: { __typename?: 'AndroidFcm', id: string, credential: any, version: AndroidFcmVersion, createdAt: any, updatedAt: any, snippet: { __typename?: 'FcmSnippetLegacy', firstFourCharacters: string, lastFourCharacters: string } | { __typename?: 'FcmSnippetV1', projectId: string, keyId: string, serviceAccountEmail: string, clientId?: string | null } } | null, googleServiceAccountKeyForSubmissions?: { __typename?: 'GoogleServiceAccountKey', id: string, projectIdentifier: string, privateKeyIdentifier: string, clientEmail: string, clientIdentifier: string, createdAt: any, updatedAt: any } | null, androidAppBuildCredentialsList: Array<{ __typename?: 'AndroidAppBuildCredentials', id: string, isDefault: boolean, isLegacy: boolean, name: string, androidKeystore?: { __typename?: 'AndroidKeystore', id: string, type: AndroidKeystoreType, keystore: string, keystorePassword: string, keyAlias: string, keyPassword?: string | null, md5CertificateFingerprint?: string | null, sha1CertificateFingerprint?: string | null, sha256CertificateFingerprint?: string | null, createdAt: any, updatedAt: any } | null }> } } }; +export type SetGoogleServiceAccountKeyForSubmissionsMutation = { __typename?: 'RootMutation', androidAppCredentials: { __typename?: 'AndroidAppCredentialsMutation', setGoogleServiceAccountKeyForSubmissions: { __typename?: 'AndroidAppCredentials', id: string, applicationIdentifier?: string | null, isLegacy: boolean, app: { __typename?: 'App', id: string, fullName: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string, ownerUserActor?: { __typename?: 'SSOUser', id: string, username: string } | { __typename?: 'User', id: string, username: string } | null, users: Array<{ __typename?: 'UserPermission', role: Role, actor: { __typename?: 'Robot', id: string } | { __typename?: 'SSOUser', id: string } | { __typename?: 'User', id: string } }> } }, androidFcm?: { __typename?: 'AndroidFcm', id: string, credential: any, version: AndroidFcmVersion, createdAt: any, updatedAt: any, snippet: { __typename?: 'FcmSnippetLegacy', firstFourCharacters: string, lastFourCharacters: string } | { __typename?: 'FcmSnippetV1', projectId: string, keyId: string, serviceAccountEmail: string, clientId?: string | null } } | null, googleServiceAccountKeyForFcmV1?: { __typename?: 'GoogleServiceAccountKey', id: string, projectIdentifier: string, privateKeyIdentifier: string, clientEmail: string, clientIdentifier: string, createdAt: any, updatedAt: any } | null, googleServiceAccountKeyForSubmissions?: { __typename?: 'GoogleServiceAccountKey', id: string, projectIdentifier: string, privateKeyIdentifier: string, clientEmail: string, clientIdentifier: string, createdAt: any, updatedAt: any } | null, androidAppBuildCredentialsList: Array<{ __typename?: 'AndroidAppBuildCredentials', id: string, isDefault: boolean, isLegacy: boolean, name: string, androidKeystore?: { __typename?: 'AndroidKeystore', id: string, type: AndroidKeystoreType, keystore: string, keystorePassword: string, keyAlias: string, keyPassword?: string | null, md5CertificateFingerprint?: string | null, sha1CertificateFingerprint?: string | null, sha256CertificateFingerprint?: string | null, createdAt: any, updatedAt: any } | null }> } } }; + +export type SetGoogleServiceAccountKeyForFcmV1MutationVariables = Exact<{ + androidAppCredentialsId: Scalars['ID']['input']; + googleServiceAccountKeyId: Scalars['ID']['input']; +}>; + + +export type SetGoogleServiceAccountKeyForFcmV1Mutation = { __typename?: 'RootMutation', androidAppCredentials: { __typename?: 'AndroidAppCredentialsMutation', setGoogleServiceAccountKeyForFcmV1: { __typename?: 'AndroidAppCredentials', id: string, applicationIdentifier?: string | null, isLegacy: boolean, app: { __typename?: 'App', id: string, fullName: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string, ownerUserActor?: { __typename?: 'SSOUser', id: string, username: string } | { __typename?: 'User', id: string, username: string } | null, users: Array<{ __typename?: 'UserPermission', role: Role, actor: { __typename?: 'Robot', id: string } | { __typename?: 'SSOUser', id: string } | { __typename?: 'User', id: string } }> } }, androidFcm?: { __typename?: 'AndroidFcm', id: string, credential: any, version: AndroidFcmVersion, createdAt: any, updatedAt: any, snippet: { __typename?: 'FcmSnippetLegacy', firstFourCharacters: string, lastFourCharacters: string } | { __typename?: 'FcmSnippetV1', projectId: string, keyId: string, serviceAccountEmail: string, clientId?: string | null } } | null, googleServiceAccountKeyForFcmV1?: { __typename?: 'GoogleServiceAccountKey', id: string, projectIdentifier: string, privateKeyIdentifier: string, clientEmail: string, clientIdentifier: string, createdAt: any, updatedAt: any } | null, googleServiceAccountKeyForSubmissions?: { __typename?: 'GoogleServiceAccountKey', id: string, projectIdentifier: string, privateKeyIdentifier: string, clientEmail: string, clientIdentifier: string, createdAt: any, updatedAt: any } | null, androidAppBuildCredentialsList: Array<{ __typename?: 'AndroidAppBuildCredentials', id: string, isDefault: boolean, isLegacy: boolean, name: string, androidKeystore?: { __typename?: 'AndroidKeystore', id: string, type: AndroidKeystoreType, keystore: string, keystorePassword: string, keyAlias: string, keyPassword?: string | null, md5CertificateFingerprint?: string | null, sha1CertificateFingerprint?: string | null, sha256CertificateFingerprint?: string | null, createdAt: any, updatedAt: any } | null }> } } }; export type CreateAndroidFcmMutationVariables = Exact<{ androidFcmInput: AndroidFcmInput; @@ -6213,7 +6221,7 @@ export type CommonAndroidAppCredentialsWithBuildCredentialsByApplicationIdentifi }>; -export type CommonAndroidAppCredentialsWithBuildCredentialsByApplicationIdentifierQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byFullName: { __typename?: 'App', id: string, androidAppCredentials: Array<{ __typename?: 'AndroidAppCredentials', id: string, applicationIdentifier?: string | null, isLegacy: boolean, app: { __typename?: 'App', id: string, fullName: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string, ownerUserActor?: { __typename?: 'SSOUser', id: string, username: string } | { __typename?: 'User', id: string, username: string } | null, users: Array<{ __typename?: 'UserPermission', role: Role, actor: { __typename?: 'Robot', id: string } | { __typename?: 'SSOUser', id: string } | { __typename?: 'User', id: string } }> } }, androidFcm?: { __typename?: 'AndroidFcm', id: string, credential: any, version: AndroidFcmVersion, createdAt: any, updatedAt: any, snippet: { __typename?: 'FcmSnippetLegacy', firstFourCharacters: string, lastFourCharacters: string } | { __typename?: 'FcmSnippetV1', projectId: string, keyId: string, serviceAccountEmail: string, clientId?: string | null } } | null, googleServiceAccountKeyForSubmissions?: { __typename?: 'GoogleServiceAccountKey', id: string, projectIdentifier: string, privateKeyIdentifier: string, clientEmail: string, clientIdentifier: string, createdAt: any, updatedAt: any } | null, androidAppBuildCredentialsList: Array<{ __typename?: 'AndroidAppBuildCredentials', id: string, isDefault: boolean, isLegacy: boolean, name: string, androidKeystore?: { __typename?: 'AndroidKeystore', id: string, type: AndroidKeystoreType, keystore: string, keystorePassword: string, keyAlias: string, keyPassword?: string | null, md5CertificateFingerprint?: string | null, sha1CertificateFingerprint?: string | null, sha256CertificateFingerprint?: string | null, createdAt: any, updatedAt: any } | null }> }> } } }; +export type CommonAndroidAppCredentialsWithBuildCredentialsByApplicationIdentifierQuery = { __typename?: 'RootQuery', app: { __typename?: 'AppQuery', byFullName: { __typename?: 'App', id: string, androidAppCredentials: Array<{ __typename?: 'AndroidAppCredentials', id: string, applicationIdentifier?: string | null, isLegacy: boolean, app: { __typename?: 'App', id: string, fullName: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string, ownerUserActor?: { __typename?: 'SSOUser', id: string, username: string } | { __typename?: 'User', id: string, username: string } | null, users: Array<{ __typename?: 'UserPermission', role: Role, actor: { __typename?: 'Robot', id: string } | { __typename?: 'SSOUser', id: string } | { __typename?: 'User', id: string } }> } }, androidFcm?: { __typename?: 'AndroidFcm', id: string, credential: any, version: AndroidFcmVersion, createdAt: any, updatedAt: any, snippet: { __typename?: 'FcmSnippetLegacy', firstFourCharacters: string, lastFourCharacters: string } | { __typename?: 'FcmSnippetV1', projectId: string, keyId: string, serviceAccountEmail: string, clientId?: string | null } } | null, googleServiceAccountKeyForFcmV1?: { __typename?: 'GoogleServiceAccountKey', id: string, projectIdentifier: string, privateKeyIdentifier: string, clientEmail: string, clientIdentifier: string, createdAt: any, updatedAt: any } | null, googleServiceAccountKeyForSubmissions?: { __typename?: 'GoogleServiceAccountKey', id: string, projectIdentifier: string, privateKeyIdentifier: string, clientEmail: string, clientIdentifier: string, createdAt: any, updatedAt: any } | null, androidAppBuildCredentialsList: Array<{ __typename?: 'AndroidAppBuildCredentials', id: string, isDefault: boolean, isLegacy: boolean, name: string, androidKeystore?: { __typename?: 'AndroidKeystore', id: string, type: AndroidKeystoreType, keystore: string, keystorePassword: string, keyAlias: string, keyPassword?: string | null, md5CertificateFingerprint?: string | null, sha1CertificateFingerprint?: string | null, sha256CertificateFingerprint?: string | null, createdAt: any, updatedAt: any } | null }> }> } } }; export type GoogleServiceAccountKeyByAccountQueryVariables = Exact<{ accountName: Scalars['String']['input']; @@ -6902,7 +6910,7 @@ export type WebhookFragment = { __typename?: 'Webhook', id: string, event: Webho export type AndroidAppBuildCredentialsFragment = { __typename?: 'AndroidAppBuildCredentials', id: string, isDefault: boolean, isLegacy: boolean, name: string, androidKeystore?: { __typename?: 'AndroidKeystore', id: string, type: AndroidKeystoreType, keystore: string, keystorePassword: string, keyAlias: string, keyPassword?: string | null, md5CertificateFingerprint?: string | null, sha1CertificateFingerprint?: string | null, sha256CertificateFingerprint?: string | null, createdAt: any, updatedAt: any } | null }; -export type CommonAndroidAppCredentialsFragment = { __typename?: 'AndroidAppCredentials', id: string, applicationIdentifier?: string | null, isLegacy: boolean, app: { __typename?: 'App', id: string, fullName: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string, ownerUserActor?: { __typename?: 'SSOUser', id: string, username: string } | { __typename?: 'User', id: string, username: string } | null, users: Array<{ __typename?: 'UserPermission', role: Role, actor: { __typename?: 'Robot', id: string } | { __typename?: 'SSOUser', id: string } | { __typename?: 'User', id: string } }> } }, androidFcm?: { __typename?: 'AndroidFcm', id: string, credential: any, version: AndroidFcmVersion, createdAt: any, updatedAt: any, snippet: { __typename?: 'FcmSnippetLegacy', firstFourCharacters: string, lastFourCharacters: string } | { __typename?: 'FcmSnippetV1', projectId: string, keyId: string, serviceAccountEmail: string, clientId?: string | null } } | null, googleServiceAccountKeyForSubmissions?: { __typename?: 'GoogleServiceAccountKey', id: string, projectIdentifier: string, privateKeyIdentifier: string, clientEmail: string, clientIdentifier: string, createdAt: any, updatedAt: any } | null, androidAppBuildCredentialsList: Array<{ __typename?: 'AndroidAppBuildCredentials', id: string, isDefault: boolean, isLegacy: boolean, name: string, androidKeystore?: { __typename?: 'AndroidKeystore', id: string, type: AndroidKeystoreType, keystore: string, keystorePassword: string, keyAlias: string, keyPassword?: string | null, md5CertificateFingerprint?: string | null, sha1CertificateFingerprint?: string | null, sha256CertificateFingerprint?: string | null, createdAt: any, updatedAt: any } | null }> }; +export type CommonAndroidAppCredentialsFragment = { __typename?: 'AndroidAppCredentials', id: string, applicationIdentifier?: string | null, isLegacy: boolean, app: { __typename?: 'App', id: string, fullName: string, slug: string, ownerAccount: { __typename?: 'Account', id: string, name: string, ownerUserActor?: { __typename?: 'SSOUser', id: string, username: string } | { __typename?: 'User', id: string, username: string } | null, users: Array<{ __typename?: 'UserPermission', role: Role, actor: { __typename?: 'Robot', id: string } | { __typename?: 'SSOUser', id: string } | { __typename?: 'User', id: string } }> } }, androidFcm?: { __typename?: 'AndroidFcm', id: string, credential: any, version: AndroidFcmVersion, createdAt: any, updatedAt: any, snippet: { __typename?: 'FcmSnippetLegacy', firstFourCharacters: string, lastFourCharacters: string } | { __typename?: 'FcmSnippetV1', projectId: string, keyId: string, serviceAccountEmail: string, clientId?: string | null } } | null, googleServiceAccountKeyForFcmV1?: { __typename?: 'GoogleServiceAccountKey', id: string, projectIdentifier: string, privateKeyIdentifier: string, clientEmail: string, clientIdentifier: string, createdAt: any, updatedAt: any } | null, googleServiceAccountKeyForSubmissions?: { __typename?: 'GoogleServiceAccountKey', id: string, projectIdentifier: string, privateKeyIdentifier: string, clientEmail: string, clientIdentifier: string, createdAt: any, updatedAt: any } | null, androidAppBuildCredentialsList: Array<{ __typename?: 'AndroidAppBuildCredentials', id: string, isDefault: boolean, isLegacy: boolean, name: string, androidKeystore?: { __typename?: 'AndroidKeystore', id: string, type: AndroidKeystoreType, keystore: string, keystorePassword: string, keyAlias: string, keyPassword?: string | null, md5CertificateFingerprint?: string | null, sha1CertificateFingerprint?: string | null, sha256CertificateFingerprint?: string | null, createdAt: any, updatedAt: any } | null }> }; export type AndroidFcmFragment = { __typename?: 'AndroidFcm', id: string, credential: any, version: AndroidFcmVersion, createdAt: any, updatedAt: any, snippet: { __typename?: 'FcmSnippetLegacy', firstFourCharacters: string, lastFourCharacters: string } | { __typename?: 'FcmSnippetV1', projectId: string, keyId: string, serviceAccountEmail: string, clientId?: string | null } }; diff --git a/packages/eas-cli/src/graphql/types/credentials/AndroidAppCredentials.ts b/packages/eas-cli/src/graphql/types/credentials/AndroidAppCredentials.ts index ab1f8eb8db..bef4d2ff6d 100644 --- a/packages/eas-cli/src/graphql/types/credentials/AndroidAppCredentials.ts +++ b/packages/eas-cli/src/graphql/types/credentials/AndroidAppCredentials.ts @@ -18,6 +18,10 @@ export const CommonAndroidAppCredentialsFragmentNode = gql` id ...AndroidFcmFragment } + googleServiceAccountKeyForFcmV1 { + id + ...GoogleServiceAccountKeyFragment + } googleServiceAccountKeyForSubmissions { id ...GoogleServiceAccountKeyFragment diff --git a/packages/eas-cli/src/submit/android/ServiceAccountSource.ts b/packages/eas-cli/src/submit/android/ServiceAccountSource.ts index 74f1608b25..e226e2905e 100644 --- a/packages/eas-cli/src/submit/android/ServiceAccountSource.ts +++ b/packages/eas-cli/src/submit/android/ServiceAccountSource.ts @@ -3,7 +3,7 @@ import chalk from 'chalk'; import fs from 'fs-extra'; import nullthrows from 'nullthrows'; -import { SetUpGoogleServiceAccountKey } from '../../credentials/android/actions/SetUpGoogleServiceAccountKey'; +import { SetUpGoogleServiceAccountKeyForSubmissions } from '../../credentials/android/actions/SetUpGoogleServiceAccountKeyForSubmissions'; import { readAndValidateServiceAccountKey } from '../../credentials/android/utils/googleServiceAccountKey'; import Log, { learnMore } from '../../log'; import { @@ -126,7 +126,9 @@ export async function getServiceAccountFromCredentialsServiceAsync( Log.log( `Looking up credentials configuration for ${appLookupParams.androidApplicationIdentifier}...` ); - const setupGoogleServiceAccountKeyAction = new SetUpGoogleServiceAccountKey(appLookupParams); + const setupGoogleServiceAccountKeyAction = new SetUpGoogleServiceAccountKeyForSubmissions( + appLookupParams + ); const androidAppCredentials = await setupGoogleServiceAccountKeyAction.runAsync( ctx.credentialsCtx ); diff --git a/packages/eas-cli/src/submit/android/__tests__/ServiceAccountSource-test.ts b/packages/eas-cli/src/submit/android/__tests__/ServiceAccountSource-test.ts index 5af20d5f42..74350e86ca 100644 --- a/packages/eas-cli/src/submit/android/__tests__/ServiceAccountSource-test.ts +++ b/packages/eas-cli/src/submit/android/__tests__/ServiceAccountSource-test.ts @@ -11,7 +11,7 @@ import { jester as mockJester, testProjectId, } from '../../../credentials/__tests__/fixtures-constants'; -import { SetUpGoogleServiceAccountKey } from '../../../credentials/android/actions/SetUpGoogleServiceAccountKey'; +import { SetUpGoogleServiceAccountKeyForSubmissions } from '../../../credentials/android/actions/SetUpGoogleServiceAccountKeyForSubmissions'; import { createTestProject } from '../../../project/__tests__/project-utils'; import { getOwnerAccountForProjectIdAsync } from '../../../project/projectUtils'; import { promptAsync } from '../../../prompts'; @@ -55,7 +55,7 @@ beforeAll(() => { '/other_dir/invalid_file.txt': 'this is not even a JSON', }); jest - .spyOn(SetUpGoogleServiceAccountKey.prototype, 'runAsync') + .spyOn(SetUpGoogleServiceAccountKeyForSubmissions.prototype, 'runAsync') .mockImplementation(async () => testAndroidAppCredentialsFragment); }); afterAll(() => {