From eb9152aa9c9590ca8720496b22c6085776217428 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: --- packages/eas-cli/graphql-codegen.yml | 4 +- packages/eas-cli/graphql.schema.json | 154 ++++++++++++++++++ .../AssignGoogleServiceAccountKeyForFcmV1.ts | 33 ++++ .../CreateGoogleServiceAccountKeyForFcmV1.ts | 76 +++++++++ .../SetUpGoogleServiceAccountKeyForFcmV1.ts | 87 ++++++++++ ...pGoogleServiceAccountKeyForSubmissions.ts} | 2 +- .../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 | 40 +++-- packages/eas-cli/src/graphql/generated.ts | 41 ++++- .../credentials/AndroidAppCredentials.ts | 4 + .../submit/android/ServiceAccountSource.ts | 6 +- .../__tests__/ServiceAccountSource-test.ts | 4 +- 18 files changed, 585 insertions(+), 48 deletions(-) create mode 100644 packages/eas-cli/src/credentials/android/actions/AssignGoogleServiceAccountKeyForFcmV1.ts 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} (98%) diff --git a/packages/eas-cli/graphql-codegen.yml b/packages/eas-cli/graphql-codegen.yml index 373266f4cb..32741105eb 100644 --- a/packages/eas-cli/graphql-codegen.yml +++ b/packages/eas-cli/graphql-codegen.yml @@ -5,8 +5,6 @@ documents: - 'src/credentials/ios/api/graphql/**/!(*.d).{ts,tsx}' - 'src/credentials/android/api/graphql/**/!(*.d).{ts,tsx}' - 'src/commands/**/*.ts' - - 'src/branch/**/*.ts' - - 'src/channel/**/*.ts' generates: src/graphql/generated.ts: plugins: @@ -16,7 +14,7 @@ generates: dedupeOperationSuffix: true hooks: afterOneFileWrite: - - 'node ./scripts/annotate-graphql-codegen.js' + - ./annotate-graphql-codegen.sh ./graphql.schema.json: plugins: - 'introspection' diff --git a/packages/eas-cli/graphql.schema.json b/packages/eas-cli/graphql.schema.json index 3e04fcb88f..9d8f9dfaa2 100644 --- a/packages/eas-cli/graphql.schema.json +++ b/packages/eas-cli/graphql.schema.json @@ -4726,6 +4726,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "googleServiceAccountKeyForFcmV1", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "GoogleServiceAccountKey", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "googleServiceAccountKeyForSubmissions", "description": null, @@ -4829,6 +4841,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "googleServiceAccountKeyForFcmV1Id", + "description": null, + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "googleServiceAccountKeyForSubmissionsId", "description": null, @@ -4916,6 +4940,87 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "createFcmV1Credential", + "description": "Create a GoogleServiceAccountKeyEntity to store credential and\nconnect it with an edge from AndroidAppCredential", + "args": [ + { + "name": "accountId", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "androidAppCredentialsId", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "appFullName", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "credential", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "AndroidAppCredentials", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "deleteAndroidAppCredentials", "description": "Delete a set of credentials for an Android app", @@ -4998,6 +5103,55 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "setGoogleServiceAccountKeyForFcmV1", + "description": "Set the Google Service Account Key to be used for Firebase Cloud Messaging V1", + "args": [ + { + "name": "googleServiceAccountKeyId", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "AndroidAppCredentials", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "setGoogleServiceAccountKeyForSubmissions", "description": "Set the Google Service Account Key to be used for submitting an Android app", 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/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 98% rename from packages/eas-cli/src/credentials/android/actions/SetUpGoogleServiceAccountKey.ts rename to packages/eas-cli/src/credentials/android/actions/SetUpGoogleServiceAccountKeyForSubmissions.ts index 41c747b9f4..05df712f7d 100644 --- a/packages/eas-cli/src/credentials/android/actions/SetUpGoogleServiceAccountKey.ts +++ b/packages/eas-cli/src/credentials/android/actions/SetUpGoogleServiceAccountKeyForSubmissions.ts @@ -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 { 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..635aa7bc7d 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'; @@ -20,6 +22,7 @@ import { GradleBuildContext, resolveGradleBuildContextAsync } from '../../projec import { promptAsync } from '../../prompts'; import { AssignFcm } from '../android/actions/AssignFcm'; import { AssignGoogleServiceAccountKey } from '../android/actions/AssignGoogleServiceAccountKey'; +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,28 @@ 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); } + } 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 e7e6e9a6e6..b8b3728e14 100644 --- a/packages/eas-cli/src/graphql/generated.ts +++ b/packages/eas-cli/src/graphql/generated.ts @@ -759,6 +759,7 @@ export type AndroidAppCredentials = { androidFcm?: Maybe; app: App; applicationIdentifier?: Maybe; + googleServiceAccountKeyForFcmV1?: Maybe; googleServiceAccountKeyForSubmissions?: Maybe; id: Scalars['ID']['output']; isLegacy: Scalars['Boolean']['output']; @@ -771,6 +772,7 @@ export type AndroidAppCredentialsFilter = { export type AndroidAppCredentialsInput = { fcmId?: InputMaybe; + googleServiceAccountKeyForFcmV1Id?: InputMaybe; googleServiceAccountKeyForSubmissionsId?: InputMaybe; }; @@ -778,10 +780,17 @@ export type AndroidAppCredentialsMutation = { __typename?: 'AndroidAppCredentialsMutation'; /** Create a set of credentials for an Android app */ createAndroidAppCredentials: AndroidAppCredentials; + /** + * Create a GoogleServiceAccountKeyEntity to store credential and + * connect it with an edge from AndroidAppCredential + */ + createFcmV1Credential: AndroidAppCredentials; /** Delete a set of credentials for an Android app */ deleteAndroidAppCredentials: DeleteAndroidAppCredentialsResult; /** Set the FCM push key to be used in an Android app */ setFcm: AndroidAppCredentials; + /** Set the Google Service Account Key to be used for Firebase Cloud Messaging V1 */ + setGoogleServiceAccountKeyForFcmV1: AndroidAppCredentials; /** Set the Google Service Account Key to be used for submitting an Android app */ setGoogleServiceAccountKeyForSubmissions: AndroidAppCredentials; }; @@ -794,6 +803,14 @@ export type AndroidAppCredentialsMutationCreateAndroidAppCredentialsArgs = { }; +export type AndroidAppCredentialsMutationCreateFcmV1CredentialArgs = { + accountId: Scalars['ID']['input']; + androidAppCredentialsId: Scalars['String']['input']; + appFullName: Scalars['String']['input']; + credential: Scalars['String']['input']; +}; + + export type AndroidAppCredentialsMutationDeleteAndroidAppCredentialsArgs = { id: Scalars['ID']['input']; }; @@ -805,6 +822,12 @@ export type AndroidAppCredentialsMutationSetFcmArgs = { }; +export type AndroidAppCredentialsMutationSetGoogleServiceAccountKeyForFcmV1Args = { + googleServiceAccountKeyId: Scalars['ID']['input']; + id: Scalars['ID']['input']; +}; + + export type AndroidAppCredentialsMutationSetGoogleServiceAccountKeyForSubmissionsArgs = { googleServiceAccountKeyId: Scalars['ID']['input']; id: Scalars['ID']['input']; @@ -6110,7 +6133,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']; @@ -6118,7 +6141,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']; @@ -6126,7 +6149,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; @@ -6180,7 +6211,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']; @@ -6869,7 +6900,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(() => {