diff --git a/.firebaserc b/.firebaserc deleted file mode 100644 index 926fe51421..0000000000 --- a/.firebaserc +++ /dev/null @@ -1,45 +0,0 @@ -{ - "projects": { - "default": "plh-teens-app1" - }, - "targets": { - "plh-teens-app1": { - "hosting": { - "app": [ - "plh-teens-app1" - ], - "plh_global": [ - "plh-global" - ], - "plh_tz": [ - "plh-tz" - ], - "plh_za": [ - "plh-za" - ], - "early_family_math": [ - "early-family-math" - ], - "wash": [ - "wash" - ], - "perinatal": [ - "perinatal" - ], - "pfr": [ - "parenting-for-respectability" - ], - "debug": [ - "idems-debug" - ], - "plh_facilitator": [ - "plh-facilitator" - ], - "plh_kids": [ - "plh-kids" - ] - } - } - }, - "etags": {} -} \ No newline at end of file diff --git a/.github/workflows/reusable-app-build.yml b/.github/workflows/reusable-app-build.yml index 03188fe51c..8bcab1fbfa 100644 --- a/.github/workflows/reusable-app-build.yml +++ b/.github/workflows/reusable-app-build.yml @@ -80,10 +80,6 @@ jobs: if: env.DEPLOYMENT_PRIVATE_KEY != '' run: echo "${{env.DEPLOYMENT_PRIVATE_KEY}}" > ./.idems_app/deployments/${{env.DEPLOYMENT_NAME}}/encrypted/private.key - - name: Populate Firebase Config - if: env.FIREBASE_CONFIG != '' - run: echo 'export const firebaseConfig = ${{env.FIREBASE_CONFIG}}' > src/environments/firebaseConfig.ts - - name: Setup Node uses: actions/setup-node@v3 with: diff --git a/.github/workflows/reusable-content-sync.yml b/.github/workflows/reusable-content-sync.yml index 1e12e6cfca..c0f8c21f9a 100644 --- a/.github/workflows/reusable-content-sync.yml +++ b/.github/workflows/reusable-content-sync.yml @@ -72,10 +72,6 @@ jobs: if: env.DEPLOYMENT_PRIVATE_KEY != '' run: echo "${{env.DEPLOYMENT_PRIVATE_KEY}}" > ./.idems_app/deployments/${{env.DEPLOYMENT_NAME}}/encrypted/private.key - # TODO - populate firebase as part of deployment set - - name: Populate Firebase Config - if: env.FIREBASE_CONFIG != '' - run: echo 'export const firebaseConfig = ${{env.FIREBASE_CONFIG}}' > src/environments/firebaseConfig.ts - name: Setup Node uses: actions/setup-node@v3 with: diff --git a/.github/workflows/test-app-build.yml b/.github/workflows/test-app-build.yml index 2f10bd60f2..7da7db3915 100644 --- a/.github/workflows/test-app-build.yml +++ b/.github/workflows/test-app-build.yml @@ -81,10 +81,6 @@ jobs: if: env.DEPLOYMENT_PRIVATE_KEY != '' run: echo "${{env.DEPLOYMENT_PRIVATE_KEY}}" > ./.idems_app/deployments/${{env.DEPLOYMENT_NAME}}/encrypted/private.key - - name: Populate Firebase Config - if: env.FIREBASE_CONFIG != '' - run: echo 'export const firebaseConfig = ${{env.FIREBASE_CONFIG}}' > src/environments/firebaseConfig.ts - - name: Setup Node uses: actions/setup-node@v3 with: diff --git a/.github/workflows/web-build.yml b/.github/workflows/web-build.yml index abfb059921..565a90622e 100644 --- a/.github/workflows/web-build.yml +++ b/.github/workflows/web-build.yml @@ -156,11 +156,6 @@ jobs: # Exact population varies depending on whether is PR, release, dispatch etc. ############################################################################# - # TODO - populate firebase as part of deployment set - - name: Populate firebaseConfig.ts - if: ${{env.FIREBASE_CONFIG_TS}} - run: echo $FIREBASE_CONFIG_TS > src/environments/firebaseConfig.ts - - name: Populate git sha if: ${{env.GIT_SHA}} run: echo "export const GIT_SHA = \"$GIT_SHA\";" > src/environments/sha.ts diff --git a/.gitignore b/.gitignore index 42b46f5f72..f17b6d50e7 100644 --- a/.gitignore +++ b/.gitignore @@ -35,10 +35,10 @@ google-services.json resources/android resources/ios package-lock.json -src/environments/firebaseConfig.ts src/assets/app_data **/*.jks -.firebase + + /scripts/input /scripts/output @@ -73,6 +73,11 @@ yarn.auto-install # Generated by https://supabase.com/docs/reference/cli/supabase-init supabase +# Firebase +firebase.json +.firebase +.firebaserc + # Avoid comitting private keys private.key diff --git a/.template.firebaserc b/.template.firebaserc new file mode 100644 index 0000000000..38742f6ecb --- /dev/null +++ b/.template.firebaserc @@ -0,0 +1,15 @@ +{ + "projects": { + "default": "${FIREBASE_PROJECT_ID}" + }, + "targets": { + "${FIREBASE_PROJECT_ID}": { + "hosting": { + "${FIREBASE_HOSTING_TARGET}": [ + "${FIREBASE_PROJECT_ID}" + ] + } + } + }, + "etags": {} +} diff --git a/README.md b/README.md index 63097314f9..a8cb560cf9 100644 --- a/README.md +++ b/README.md @@ -47,16 +47,6 @@ This will present an interactive list of deployments to select from. See [Deployment Documentation](https://idemsinternational.github.io/parenting-app-ui/developers/deployments/) for information about creating and configuring deployments. -### Firebase -To be able to run the full project a specific configuration file needs to be included to access -the online database and authentication methods. -``` -$ cp src/environments/firebaseConfig.sample.ts src/environments/firebaseConfig.ts -``` -The default file is blank. It should be replaced with a version requested from the dev team. - -(Note - this process will likely be simplified in the future) - ## Running locally ### Start the local server diff --git a/documentation/docs/developers/web-previews.md b/documentation/docs/developers/web-previews.md index f8f4583e1e..ef14ef1464 100644 --- a/documentation/docs/developers/web-previews.md +++ b/documentation/docs/developers/web-previews.md @@ -1,5 +1,28 @@ # Web Previews +Web previews can be configured for a deployment using the [reusable-deploy-web-preview](../../../.github\workflows\reusable-deploy-web-preview.yml) github action. +The action requires a Firebase project to be setup, and environment variables set to specify the `FIREBASE_PROJECT_ID` and `FIREBASE_DEPLOYMENT_TARGET` + +This action should be called from a child content repo and configured as required. E.g. + +_.github/workflows/deploy-firebase.yml_ +```yml +name: Deploy - Firebase + +on: + push: + branches: + - "main" + +jobs: + web_preview: + uses: IDEMSInternational/parenting-app-ui/.github/workflows/reusable-deploy-web-preview.yml@master + secrets: inherit +``` + + +--- +# Legacy Docs A preview for a given app deployment, running in browser, can be generated via GitHub. The URL for this preview can then be shared. In general, these previews are accessible at `.web.app`, e.g. [wash.web.app](https://wash.web.app/). When viewing a web preview, it is recommended to [enable device mode] in Chrome DevTools in order to simulate the mobile experience. ## Updating an existing web preview diff --git a/documentation/docs/index.md b/documentation/docs/index.md index cb07363651..d9dece0264 100644 --- a/documentation/docs/index.md +++ b/documentation/docs/index.md @@ -41,17 +41,6 @@ You will be prompted to specify the deployment type, this should be a `New Local See [Deployment Documentation](./developers/deployments.md) for more information about configuring deployments -### Firebase -To be able to run the full project a specific configuration file needs to be included to access -the online database and authentication methods. -``` -$ cp src/environments/firebaseConfig.sample.ts src/environments/firebaseConfig.ts -``` -The default file is blank and so some features may not be available (e.g. testing google sign-in) -It can be replaced with a version requested from the dev team. - -(Note - this process will likely be simplified in the future) - ## Running locally ### Start the local server diff --git a/firebase.json b/firebase.json deleted file mode 100644 index a41271f6db..0000000000 --- a/firebase.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "hosting": [ - { - "target": "app", - "public": "www", - "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], - "rewrites": [ - { - "source": "**", - "destination": "/index.html" - } - ] - }, - { - "target": "plh_global", - "public": "www", - "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], - "rewrites": [ - { - "source": "**", - "destination": "/index.html" - } - ] - }, - { - "target": "plh_tz", - "public": "www", - "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], - "rewrites": [ - { - "source": "**", - "destination": "/index.html" - } - ] - }, - { - "target": "plh_za", - "public": "www", - "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], - "rewrites": [ - { - "source": "**", - "destination": "/index.html" - } - ] - }, - { - "target": "early_family_math", - "public": "www", - "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], - "rewrites": [ - { - "source": "**", - "destination": "/index.html" - } - ] - }, - { - "target": "wash", - "public": "www", - "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], - "rewrites": [ - { - "source": "**", - "destination": "/index.html" - } - ] - }, - { - "target": "perinatal", - "public": "www", - "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], - "rewrites": [ - { - "source": "**", - "destination": "/index.html" - } - ] - }, - { - "target": "pfr", - "public": "www", - "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], - "rewrites": [ - { - "source": "**", - "destination": "/index.html" - } - ] - }, - { - "target": "debug", - "public": "www", - "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], - "rewrites": [ - { - "source": "**", - "destination": "/index.html" - } - ] - }, - { - "target": "plh_facilitator", - "public": "www", - "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], - "rewrites": [ - { - "source": "**", - "destination": "/index.html" - } - ] - }, - { - "target": "plh_kids", - "public": "www", - "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], - "rewrites": [ - { - "source": "**", - "destination": "/index.html" - } - ] - } - ] -} diff --git a/firebase.template.json b/firebase.template.json new file mode 100644 index 0000000000..b3b7c6f711 --- /dev/null +++ b/firebase.template.json @@ -0,0 +1,15 @@ +{ + "hosting": [ + { + "target": "${FIREBASE_HOSTING_TARGET}", + "public": "www", + "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], + "rewrites": [ + { + "source": "**", + "destination": "/index.html" + } + ] + } + ] +} diff --git a/package.json b/package.json index ea18efbb5b..515153c352 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "@angular/common": "~17.2.2", "@angular/core": "~17.2.2", "@angular/elements": "^17.2.2", - "@angular/fire": "17.0.1", "@angular/forms": "~17.2.2", "@angular/platform-browser": "~17.2.2", "@angular/platform-browser-dynamic": "~17.2.2", diff --git a/packages/data-models/deployment.model.ts b/packages/data-models/deployment.model.ts index de3a9d1f81..0d52663754 100644 --- a/packages/data-models/deployment.model.ts +++ b/packages/data-models/deployment.model.ts @@ -2,7 +2,7 @@ import type { IGdriveEntry } from "../@idemsInternational/gdrive-tools"; import type { IAppConfig } from "./appConfig"; /** Update version to force recompile next time deployment set (e.g. after default config update) */ -export const DEPLOYMENT_CONFIG_VERSION = 20230930; +export const DEPLOYMENT_CONFIG_VERSION = 20240314.0; export interface IDeploymentConfig { /** Friendly name used to identify the deployment name */ @@ -60,6 +60,30 @@ export interface IDeploymentConfig { /** filter function that receives basic file info such as relativePath and size. Default `(fileEntry)=>true`*/ assets_filter_function: (fileEntry: IContentsEntry) => boolean; }; + /** + * Specify if using firebase for auth and crashlytics. + * Requires firebase config available through encrypted config */ + firebase: { + /** Project config as specified in firebase console (recommend loading from encrypted environment) */ + config?: { + apiKey: string; + authDomain: string; + databaseURL: string; + projectId: string; + storageBucket: string; + messagingSenderId: string; + appId: string; + measurementId: string; + }; + auth: { + /** Enables `auth` actions to allow user sign-in/out */ + enabled: boolean; + }; + crashlytics: { + /** Enables app crash reports to firebase crashlytics */ + enabled: boolean; + }; + }; git: { /** Url of external git repo to store content */ content_repo?: string; @@ -136,6 +160,11 @@ export const DEPLOYMENT_CONFIG_EXAMPLE_DEFAULTS: IDeploymentConfig = { sheets_filter_function: (flow) => true, assets_filter_function: (fileEntry) => true, }, + firebase: { + config: null, + auth: { enabled: false }, + crashlytics: { enabled: true }, + }, supabase: { enabled: false, }, diff --git a/packages/scripts/src/commands/deployment/common.ts b/packages/scripts/src/commands/deployment/common.ts index d935e0160c..efc584fbec 100644 --- a/packages/scripts/src/commands/deployment/common.ts +++ b/packages/scripts/src/commands/deployment/common.ts @@ -1,9 +1,13 @@ +import { existsSync } from "fs"; +import { logWarning } from "shared"; +import { readJSONSync } from "fs-extra"; +import path from "path"; + import { DEPLOYMENT_CONFIG_EXAMPLE_DEFAULTS, getDefaultAppConfig } from "data-models"; import type { IDeploymentConfig, IDeploymentConfigJson } from "data-models"; -import path from "path"; import { DEPLOYMENTS_PATH } from "../../paths"; -import { loadDeploymentJson } from "./utils"; +import { getStackFileNames, loadDeploymentJson } from "./utils"; // re-export of type for convenience export type { IDeploymentConfigJson }; @@ -16,6 +20,27 @@ export function generateDeploymentConfig(name: string): IDeploymentConfig { return config; } +/** + * Read a json file from deployment encrypted folder + * @param filename name of json to read config from (should already be decrypted) + * @returns parsed json if exists, null if does not exist + **/ +export function loadEncryptedConfig(filename: string) { + // Determine path to deployment config ts that called function for source of encrypted config + const configTsPath = getStackFileNames().find((filePath) => filePath.includes(".idems_app")); + const encryptedConfigPath = path.resolve(configTsPath, "../", "encrypted"); + const target = path.resolve(encryptedConfigPath, filename); + if (!existsSync(target)) { + logWarning({ + msg1: `Encrypted config does not exist,\nsome features may be unavailable`, + msg2: filename, + }); + console.log(target); + return null; + } + return readJSONSync(target); +} + /** Load and extend an existing deployment config **/ export function extendDeploymentConfig(options: { parent: string; diff --git a/packages/scripts/src/commands/deployment/utils.ts b/packages/scripts/src/commands/deployment/utils.ts index 51517dc8f7..42e84ed518 100644 --- a/packages/scripts/src/commands/deployment/utils.ts +++ b/packages/scripts/src/commands/deployment/utils.ts @@ -101,3 +101,25 @@ export function convertStringsToFunctions(data: T) { }); return data; } + +/** + * Utility function to determine the filename stack of current function invocation. + * Uses Node v8 stack trace api to determine call stacks (in same way error stacks are managed) + * Adapted from https://github.com/sindresorhus/callsites and https://v8.dev/docs/stack-trace-api + * @returns string[] list of filenames included in call stack + */ +export function getStackFileNames() { + // take a copy of default stack track prepare method to revert after use + const _prepareStackTrace = Error.prepareStackTrace; + try { + // alter stack trace method to remove self reference and return stack as filenames + Error.prepareStackTrace = (_, callSites) => { + return callSites.slice(1).map((callSite) => callSite.getFileName()); + }; + // create error to generate stacktrace and return (typed to match altered stack trace) + return new Error().stack as any as string[]; + } finally { + // revert stack trace method + Error.prepareStackTrace = _prepareStackTrace; + } +} diff --git a/packages/scripts/src/commands/index.ts b/packages/scripts/src/commands/index.ts index ac6b86b1bf..f971038274 100644 --- a/packages/scripts/src/commands/index.ts +++ b/packages/scripts/src/commands/index.ts @@ -50,8 +50,12 @@ export const parseCommand = async (cmd: string) => { }; // Additional exports for direct consumption -import { extendDeploymentConfig, generateDeploymentConfig } from "./deployment/common"; -export { extendDeploymentConfig, generateDeploymentConfig }; +import { + extendDeploymentConfig, + generateDeploymentConfig, + loadEncryptedConfig, +} from "./deployment/common"; +export { extendDeploymentConfig, generateDeploymentConfig, loadEncryptedConfig }; // Run the program directly when called via ts-node (e.g. start script) if (isTsNode) { diff --git a/src/app/app.component.ts b/src/app/app.component.ts index bac42ec6fe..9aab61b17c 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -112,11 +112,8 @@ export class AppComponent { this.hackSetDeveloperOptions(); const isDeveloperMode = this.templateFieldService.getField("user_mode") === false; const user = this.userMetaService.userMeta; - // Authentication requires verified domain and app ids populated to firebase console - // Currently only run on native where specified (but can comment out for testing locally) - if (this.appAuthenticationDefaults.enforceLogin && Capacitor.isNativePlatform()) { - await this.ensureUserSignedIn(); - } + await this.loadAuthConfig(); + if (!user.first_app_open) { await this.userMetaService.setUserMeta({ first_app_open: new Date().toISOString() }); } @@ -143,16 +140,26 @@ export class AppComponent { }); } - private async ensureUserSignedIn() { - const authUser = await this.authService.getCurrentUser(); - if (!authUser) { - const templatename = this.appAuthenticationDefaults.signInTemplate; - const { modal } = await this.templateService.runStandaloneTemplate(templatename, { - showCloseButton: false, - waitForDismiss: false, - }); - await this.authService.waitForSignInComplete(); - await modal.dismiss(); + /** + * Authentication requires verified domain and app ids populated to firebase console + * Currently only run on native where specified (but can comment out for testing locally) + */ + private async loadAuthConfig() { + const { firebase } = environment.deploymentConfig; + const { enforceLogin } = this.appAuthenticationDefaults; + const ensureLogin = firebase.config && enforceLogin && Capacitor.isNativePlatform(); + if (ensureLogin) { + this.authService.ready(); + const authUser = await this.authService.getCurrentUser(); + if (!authUser) { + const templatename = this.appAuthenticationDefaults.signInTemplate; + const { modal } = await this.templateService.runStandaloneTemplate(templatename, { + showCloseButton: false, + waitForDismiss: false, + }); + await this.authService.waitForSignInComplete(); + await modal.dismiss(); + } } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index aac76e6167..02bfbf6edb 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -4,8 +4,6 @@ import { FormsModule } from "@angular/forms"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { RouteReuseStrategy } from "@angular/router"; import { HttpClientModule } from "@angular/common/http"; -import { provideFirebaseApp, initializeApp } from "@angular/fire/app"; -import { getAuth, provideAuth } from "@angular/fire/auth"; import { IonicModule, IonicRouteStrategy } from "@ionic/angular"; // Libs @@ -43,9 +41,6 @@ export function lottiePlayerFactory() { AppRoutingModule, HttpClientModule, SharedModule, - // Firebase - provideFirebaseApp(() => initializeApp(environment.firebaseConfig)), - provideAuth(() => getAuth()), FormsModule, LottieModule.forRoot({ player: lottiePlayerFactory }), // NOTE CC 2021-11-04 not sure if cache causes issues or not https://github.com/ngx-lottie/ngx-lottie/issues/115 diff --git a/src/app/shared/services/auth/auth.service.ts b/src/app/shared/services/auth/auth.service.ts index ef9b1d8d46..5ee23a7e3e 100644 --- a/src/app/shared/services/auth/auth.service.ts +++ b/src/app/shared/services/auth/auth.service.ts @@ -1,12 +1,13 @@ import { Injectable } from "@angular/core"; -import { Auth } from "@angular/fire/auth"; import { FirebaseAuthentication, User } from "@capacitor-firebase/authentication"; import { BehaviorSubject, firstValueFrom } from "rxjs"; import { filter } from "rxjs/operators"; +import { environment } from "src/environments/environment"; import { IAppConfig } from "../../model"; import { AppConfigService } from "../app-config/app-config.service"; import { SyncServiceBase } from "../syncService.base"; import { TemplateActionRegistry } from "../../components/template/services/instance/template-action.registry"; +import { FirebaseService } from "../firebase/firebase.service"; @Injectable({ providedIn: "root", @@ -17,17 +18,20 @@ export class AuthService extends SyncServiceBase { // include auth import to ensure app registered constructor( - auth: Auth, private appConfigService: AppConfigService, - private templateActionRegistry: TemplateActionRegistry + private templateActionRegistry: TemplateActionRegistry, + private firebaseService: FirebaseService ) { super("Auth"); this.initialise(); } private initialise() { - this.subscribeToAppConfigChanges(); - this.addAuthListeners(); - this.registerTemplateActionHandlers(); + const { firebase } = environment.deploymentConfig; + if (firebase?.auth?.enabled && this.firebaseService.app) { + this.subscribeToAppConfigChanges(); + this.addAuthListeners(); + this.registerTemplateActionHandlers(); + } } /** Return a promise that resolves after a signed in user defined */ @@ -56,8 +60,6 @@ export class AuthService extends SyncServiceBase { sign_in_google: async () => await this.signInWithGoogle(), sign_out: async () => await this.signOut(), }; - // To support deprecated "share" action (previously used to share text only), - // assume text is being shared if first arg is not an actionId if (!(actionId in childActions)) { console.error(`[AUTH] - No action, "${actionId}"`); return; diff --git a/src/app/shared/services/crashlytics/crashlytics.service.ts b/src/app/shared/services/crashlytics/crashlytics.service.ts index f1dca1aa68..9e6b2f0ad7 100644 --- a/src/app/shared/services/crashlytics/crashlytics.service.ts +++ b/src/app/shared/services/crashlytics/crashlytics.service.ts @@ -3,6 +3,7 @@ import { FirebaseCrashlytics } from "@capacitor-firebase/crashlytics"; import { Capacitor } from "@capacitor/core"; import { Device } from "@capacitor/device"; import { AsyncServiceBase } from "../asyncService.base"; +import { environment } from "src/environments/environment"; @Injectable({ providedIn: "root", @@ -19,7 +20,10 @@ export class CrashlyticsService extends AsyncServiceBase { } private async initialise() { if (Capacitor.isNativePlatform()) { - await this.setEnabled({ enabled: true }); + const { firebase } = environment.deploymentConfig; + // Crashlytics is still supported on native device without firebase config (uses google-services.json) + // so use config property to toggle enabled instead + await this.setEnabled({ enabled: firebase?.crashlytics?.enabled }); const { identifier: uuid } = await Device.getId(); await this.setUserId({ userId: uuid }); // populate webview useragent info diff --git a/src/app/shared/services/error-handler/error-handler.service.ts b/src/app/shared/services/error-handler/error-handler.service.ts index a58e969a1e..5c7b55adce 100644 --- a/src/app/shared/services/error-handler/error-handler.service.ts +++ b/src/app/shared/services/error-handler/error-handler.service.ts @@ -6,6 +6,7 @@ import { environment } from "src/environments/environment"; import { GIT_SHA } from "src/environments/sha"; import { fromError as getStacktraceFromError } from "stacktrace-js"; import { CrashlyticsService } from "../crashlytics/crashlytics.service"; +import { FirebaseService } from "../firebase/firebase.service"; @Injectable({ providedIn: "root", @@ -17,7 +18,7 @@ export class ErrorHandlerService extends ErrorHandler { // Error handling is important and needs to be loaded first. // Because of this we should manually inject the services with Injector. - constructor(private injector: Injector) { + constructor(private injector: Injector, private firebaseService: FirebaseService) { super(); } @@ -29,14 +30,15 @@ export class ErrorHandlerService extends ErrorHandler { * (although workaround required as cannot extend multiple services) */ private async initialise() { - const { production, deploymentConfig, firebaseConfig } = environment; - if (production && deploymentConfig?.error_logging?.dsn) { + const { production, deploymentConfig } = environment; + const { error_logging, firebase } = deploymentConfig; + if (production && error_logging?.dsn) { await this.initialiseSentry(); this.sentryEnabled = true; } - if (production && firebaseConfig?.apiKey && Capacitor.isNativePlatform()) { + if (production && this.firebaseService.app && Capacitor.isNativePlatform()) { // crashlytics initialised in app component so omitted here - this.crashlyticsEnabled = true; + this.crashlyticsEnabled = firebase.crashlytics.enabled; } this.initialised = true; } diff --git a/src/app/shared/services/firebase/firebase.service.ts b/src/app/shared/services/firebase/firebase.service.ts new file mode 100644 index 0000000000..e17f6097c1 --- /dev/null +++ b/src/app/shared/services/firebase/firebase.service.ts @@ -0,0 +1,37 @@ +import { Injectable } from "@angular/core"; +import { initializeApp, FirebaseApp } from "firebase/app"; +import { environment } from "src/environments/environment"; +import { SyncServiceBase } from "../syncService.base"; + +/** Service used to configure initialize firebase app core configuration */ +@Injectable({ providedIn: "root" }) +export class FirebaseService extends SyncServiceBase { + /** Initialised firebase app. Will be undefined if firebase config unavailable */ + app: FirebaseApp | undefined; + + constructor() { + super("Firebase"); + this.initialise(); + } + + /** + * Configure app module imports dependent on what firebase features should be enabled + */ + private initialise() { + const { firebase } = environment.deploymentConfig; + + // Check if any services are enabled, simply return if not + const enabledServices = Object.entries(firebase) + .filter(([key, v]) => v && v.constructor === {}.constructor && v["enabled"]) + .map(([key]) => key); + if (enabledServices.length === 0) return; + + // Check config exists if services are enabled + if (!firebase.config) { + console.warn(`[Firebase] config missing, services disabled:\n`, enabledServices.join(", ")); + return; + } + + this.app = initializeApp(environment.deploymentConfig.firebase.config); + } +} diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 0d0b7d7fa7..161077181d 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -1,4 +1,3 @@ -import { firebaseConfig } from "./firebaseConfig"; import packageJson from "../../package.json"; import deploymentJson from "../../.idems_app/deployments/activeDeployment.json"; import type { IDeploymentConfig } from "data-models"; @@ -15,7 +14,6 @@ export const environment = { contactRegisterUrl: "https://rapidpro.idems.international/c/fcm/a459e9bf-6462-41fe-9bde-98dbed64e687/register", }, - firebaseConfig, domains: ["plh-demo1.idems.international", "plh-demo.idems.international"], chatNonNavigatePaths: ["/chat/action", "/chat/msg-info"], variableNameFlows: ["character_names"], diff --git a/src/environments/environment.ts b/src/environments/environment.ts index d27f784c68..27059abb45 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -1,4 +1,3 @@ -import { firebaseConfig } from "./firebaseConfig"; import packageJson from "../../package.json"; import deploymentJson from "../../.idems_app/deployments/activeDeployment.json"; import type { IDeploymentConfig } from "data-models"; @@ -16,7 +15,6 @@ export const environment = { contactRegisterUrl: "https://rapidpro.idems.international/c/fcm/a459e9bf-6462-41fe-9bde-98dbed64e687/register", }, - firebaseConfig, domains: ["plh-demo1.idems.international", "plh-demo.idems.international"], chatNonNavigatePaths: ["/chat/action", "/chat/msg-info"], variableNameFlows: ["character_names"], diff --git a/src/environments/firebaseConfig.sample.ts b/src/environments/firebaseConfig.sample.ts deleted file mode 100644 index a0fa615a78..0000000000 --- a/src/environments/firebaseConfig.sample.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copy this file to `firebaseConfig.ts` and populate with own firebase config - */ -export const firebaseConfig = { - apiKey: "", - authDomain: "", - databaseURL: "", - projectId: "", - storageBucket: "", - messagingSenderId: "", - appId: "", - measurementId: "", -}; diff --git a/yarn.lock b/yarn.lock index a8ab122e1d..a0b22f9843 100644 --- a/yarn.lock +++ b/yarn.lock @@ -325,7 +325,7 @@ __metadata: languageName: node linkType: hard -"@angular-devkit/schematics@npm:17.2.1, @angular-devkit/schematics@npm:^17.0.0, @angular-devkit/schematics@npm:~17.2.1": +"@angular-devkit/schematics@npm:17.2.1, @angular-devkit/schematics@npm:~17.2.1": version: 17.2.1 resolution: "@angular-devkit/schematics@npm:17.2.1" dependencies: @@ -563,40 +563,6 @@ __metadata: languageName: node linkType: hard -"@angular/fire@npm:17.0.1": - version: 17.0.1 - resolution: "@angular/fire@npm:17.0.1" - dependencies: - "@angular-devkit/schematics": ^17.0.0 - "@schematics/angular": ^17.0.0 - firebase: ^10.7.0 - fs-extra: ^8.0.1 - fuzzy: ^0.1.3 - inquirer: ^8.1.1 - inquirer-autocomplete-prompt: ^1.0.1 - jsonc-parser: ^3.0.0 - node-fetch: ^2.6.1 - open: ^8.0.0 - ora: ^5.3.0 - rxfire: ^6.0.5 - semver: ^7.1.3 - triple-beam: ^1.3.0 - tslib: ^2.3.0 - winston: ^3.0.0 - peerDependencies: - "@angular/common": ^17.0.0 - "@angular/core": ^17.0.0 - "@angular/platform-browser": ^17.0.0 - "@angular/platform-browser-dynamic": ^17.0.0 - firebase-tools: ^13.0.0 - rxjs: ~7.8.0 - peerDependenciesMeta: - firebase-tools: - optional: true - checksum: 677cb1bffef10cad9ca961688f5c7143e96993f1dfa5e0ada5400b57cd70e5fe7310d0ca8965ddaefb9e8cf3291082d4608e45a52d2899e3ac490334c3b9dcc9 - languageName: node - linkType: hard - "@angular/forms@npm:~17.2.2": version: 17.2.2 resolution: "@angular/forms@npm:17.2.2" @@ -6311,7 +6277,7 @@ __metadata: languageName: node linkType: hard -"@schematics/angular@npm:17.2.1, @schematics/angular@npm:^17.0.0": +"@schematics/angular@npm:17.2.1": version: 17.2.1 resolution: "@schematics/angular@npm:17.2.1" dependencies: @@ -8845,7 +8811,7 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^4.1.0, ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.0, ansi-escapes@npm:^4.3.1, ansi-escapes@npm:^4.3.2": +"ansi-escapes@npm:^4.1.0, ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.0, ansi-escapes@npm:^4.3.2": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" dependencies: @@ -14768,7 +14734,6 @@ __metadata: "@angular/compiler-cli": ~17.2.2 "@angular/core": ~17.2.2 "@angular/elements": ^17.2.2 - "@angular/fire": 17.0.1 "@angular/forms": ~17.2.2 "@angular/language-service": ~17.2.2 "@angular/platform-browser": ~17.2.2 @@ -14924,7 +14889,7 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^8.0.1, fs-extra@npm:^8.1.0": +"fs-extra@npm:^8.1.0": version: 8.1.0 resolution: "fs-extra@npm:8.1.0" dependencies: @@ -16276,21 +16241,6 @@ __metadata: languageName: node linkType: hard -"inquirer-autocomplete-prompt@npm:^1.0.1": - version: 1.4.0 - resolution: "inquirer-autocomplete-prompt@npm:1.4.0" - dependencies: - ansi-escapes: ^4.3.1 - chalk: ^4.0.0 - figures: ^3.2.0 - run-async: ^2.4.0 - rxjs: ^6.6.2 - peerDependencies: - inquirer: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 863d60d6beee2424d3fd9fbdbc027dcc36b20106509f56edd0aab49dea8b033451edec5936ca929b24b685a7137097c98c21757889df8a87cd3919a732483586 - languageName: node - linkType: hard - "inquirer-autocomplete-prompt@npm:^2.0.1": version: 2.0.1 resolution: "inquirer-autocomplete-prompt@npm:2.0.1" @@ -16396,7 +16346,7 @@ __metadata: languageName: node linkType: hard -"inquirer@npm:^8.1.1, inquirer@npm:^8.1.2, inquirer@npm:^8.2.6": +"inquirer@npm:^8.1.2, inquirer@npm:^8.2.6": version: 8.2.6 resolution: "inquirer@npm:8.2.6" dependencies: @@ -20879,7 +20829,7 @@ __metadata: languageName: node linkType: hard -"open@npm:8.4.2, open@npm:^8, open@npm:^8.0.0, open@npm:^8.0.9, open@npm:^8.4.0": +"open@npm:8.4.2, open@npm:^8, open@npm:^8.0.9, open@npm:^8.4.0": version: 8.4.2 resolution: "open@npm:8.4.2" dependencies: @@ -20955,7 +20905,7 @@ __metadata: languageName: node linkType: hard -"ora@npm:5.4.1, ora@npm:^5.3.0, ora@npm:^5.4.1": +"ora@npm:5.4.1, ora@npm:^5.4.1": version: 5.4.1 resolution: "ora@npm:5.4.1" dependencies: @@ -23180,17 +23130,7 @@ __metadata: languageName: node linkType: hard -"rxfire@npm:^6.0.5": - version: 6.0.5 - resolution: "rxfire@npm:6.0.5" - peerDependencies: - firebase: ^9.0.0 || ^10.0.0 - rxjs: ^6.0.0 || ^7.0.0 - checksum: 3df49e9453ed969452c03b164e82faaa0523621a72a5eb230bbe30c43f34fd63db0c421cf38bf0ad31016c88fb806180db5b4eac82a2e9236596bdaec0a04686 - languageName: node - linkType: hard - -"rxjs@npm:6.6.7, rxjs@npm:^6.4.0, rxjs@npm:^6.5.3, rxjs@npm:^6.6.0, rxjs@npm:^6.6.2, rxjs@npm:^6.6.3": +"rxjs@npm:6.6.7, rxjs@npm:^6.4.0, rxjs@npm:^6.5.3, rxjs@npm:^6.6.0, rxjs@npm:^6.6.3": version: 6.6.7 resolution: "rxjs@npm:6.6.7" dependencies: @@ -23461,7 +23401,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.6.0, semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.1.2, semver@npm:^7.1.3, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4": +"semver@npm:7.6.0, semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.1.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.5.2, semver@npm:^7.5.3, semver@npm:^7.5.4": version: 7.6.0 resolution: "semver@npm:7.6.0" dependencies: