-
Notifications
You must be signed in to change notification settings - Fork 85
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[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:
- Loading branch information
1 parent
0b71812
commit b096105
Showing
19 changed files
with
427 additions
and
55 deletions.
There are no files selected for viewing
33 changes: 33 additions & 0 deletions
33
packages/eas-cli/src/credentials/android/actions/AssignGoogleServiceAccountKeyForFcmV1.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<CommonAndroidAppCredentialsFragment> { | ||
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
76 changes: 76 additions & 0 deletions
76
packages/eas-cli/src/credentials/android/actions/CreateGoogleServiceAccountKeyForFcmV1.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<GoogleServiceAccountKeyFragment> { | ||
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<GoogleServiceAccountKey> { | ||
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<string> { | ||
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; | ||
} | ||
} |
87 changes: 87 additions & 0 deletions
87
packages/eas-cli/src/credentials/android/actions/SetUpGoogleServiceAccountKeyForFcmV1.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<CommonAndroidAppCredentialsFragment> { | ||
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<boolean> { | ||
const appCredentials = await ctx.android.getAndroidAppCredentialsWithCommonFieldsAsync( | ||
ctx.graphqlClient, | ||
this.app | ||
); | ||
return !!appCredentials?.googleServiceAccountKeyForFcmV1; | ||
} | ||
|
||
private async createOrUseExistingKeyAsync( | ||
ctx: CredentialsContext | ||
): Promise<GoogleServiceAccountKeyFragment> { | ||
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)) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.