Skip to content

Commit

Permalink
Merge pull request #12 from oddbit/app-configuration
Browse files Browse the repository at this point in the history
App configuration
  • Loading branch information
DennisAlund authored Feb 7, 2024
2 parents 6ae4ce8 + 22f2c35 commit de17a79
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 370 deletions.
29 changes: 25 additions & 4 deletions extension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,35 @@ params:
required: true
immutable: false

- param: APP_CONFIG_COLLECTION
label: Apps configuration collection
description: The Firestore collection to keep apps configuration data.
- param: APP_ID
label: Firebase App ID
description: The Firebase app ID from console (e.g `1:269808624035:android:296863cf1f5b6817c87a16`)
type: string
default: firebase-alerts-apps-config
required: true
immutable: false

- param: APP_BUNDLE_ID
label: Bundle ID
description: |
The app bundle ID as identified by your build configuration (e.g `id.oddbit.hello_world`)
For web apps this is a random string that you will have to look for in
URL when you are selecting the web app in the Firebase settings page.
Look for the last part of the URL where `your-web-app-bundle-id` is your
web app's "bundle id". Go to the console to find something like this:
https://console.firebase.google.com/project/${PROJECT_ID}/settings/general/web:{your-web-app-bundle-id}
type: string
required: true
immutable: false

- param: REPOSITORY_URL
label: Repository URL
description: URL to your git repository
type: string
required: false
immutable: false

- param: WEBHOOK_MANDATORY
label: Mandatory webhook
description: Mandatory webhook for Slack, Discord, or Google Chat
Expand Down
40 changes: 8 additions & 32 deletions functions/src/alerts/crashlytics.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@

import {firestore} from "firebase-admin";
import {logger} from "firebase-functions/v2";
import {crashlytics} from "firebase-functions/v2/alerts";
import {post} from "request";
import {AppCrash} from "../models/app-crash";
import {AppInfo, IAppInfo} from "../models/app-info";
import {Webhook} from "../models/webhook";
import {makeFirebaseAppsSettingsUrl, makeFirestoreAppInfoUrl} from "../urls";
import {EnvConfig} from "../utils/env-config";
import {DiscordWebhook} from "../webhook-plugins/discord";
import {GoogleChatWebhook} from "../webhook-plugins/google-chat";
Expand Down Expand Up @@ -48,35 +45,13 @@ async function handleCrashlyticsEvent(appCrash: AppCrash):
Promise<object | void> {
logger.debug("[handleCrashlyticsEvent]", appCrash);

// Update and ensure that there is a Firestore document for this app id
await firestore()
.collection("apps")
.doc(appCrash.appId)
.set({
appId: appCrash.appId,
lastIssue: firestore.FieldValue.serverTimestamp(),
issueCount: firestore.FieldValue.increment(1),
}, {merge: true});


const appInfoSnap = await firestore()
.collection("apps")
.doc(appCrash.appId)
.get();

logger.debug("[handleCrashlyticsEvent] App info", appInfoSnap.data());

const appInfo = new AppInfo(appInfoSnap.data() as IAppInfo);
if (!appInfo.bundleId) {
// Will need to add this information explicitly by copying the bundle id
// from Firebase Console project overview. The console log below will
// provide links to add the configuration.
logger.warn(
"[handleCrashlyticsEvent] No bundle id for app. Fix it manually", {
appInfo,
settings: makeFirebaseAppsSettingsUrl(),
firestore: makeFirestoreAppInfoUrl(appInfo),
if (appCrash.appId !== EnvConfig.appId) {
logger.debug(
"[handleCrashlyticsEvent] Skipping crash for different app", {
appId: appCrash.appId,
expectedAppId: EnvConfig.appId,
});
return;
}

const webhooks: Webhook[] = EnvConfig.webhooks.map(webhookPluginFromUrl);
Expand All @@ -88,8 +63,9 @@ async function handleCrashlyticsEvent(appCrash: AppCrash):
const promises = [];
for (const webhook of webhooks) {
logger.debug("[handleCrashlyticsEvent] Webhook", webhook);
const crashlyticsMessage = webhook.createCrashlyticsMessage(appCrash);
const webhookPayload = {
body: JSON.stringify(webhook.createCrashlyticsMessage(appInfo, appCrash)),
body: JSON.stringify(crashlyticsMessage),
};

try {
Expand Down
6 changes: 6 additions & 0 deletions functions/src/definitions/app-platform.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export enum AppPlatform {
iOS = "ios",
Android = "android",
Web = "web",
Unknown = "unknown",
}
88 changes: 0 additions & 88 deletions functions/src/models/app-info.ts

This file was deleted.

29 changes: 0 additions & 29 deletions functions/src/models/github.ts

This file was deleted.

3 changes: 0 additions & 3 deletions functions/src/models/webhook.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {AppInfo} from "./app-info";
import {AppCrash} from "./app-crash";
import {Localization} from "../utils/localization";

Expand Down Expand Up @@ -27,12 +26,10 @@ export abstract class Webhook implements IWebhook {
/**
* Create message payload for the webhook to send a crashlytics message
*
* @param {AppInfo} appInfo
* @param {AppCrash} appCrash
* @return {object} Webhook body payload
*/
public abstract createCrashlyticsMessage(
appInfo: AppInfo,
appCrash: AppCrash,
): object;
}
56 changes: 23 additions & 33 deletions functions/src/urls.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {AppInfo} from "./models/app-info";
import {AppCrash} from "./models/app-crash";
import {EnvConfig} from "./utils/env-config";

Expand All @@ -7,14 +6,11 @@ export const crashlyticsImgUrl = "https://github.com/oddbit/firebase-alerts/raw/
/**
* Generate a URL to Firebase Console for the issue
*
* @param {AppInfo} appInfo
* @param {AppCrash} appCrash
* @return {string} URL to Firebase console
*/
export function makeCrashlyticsIssueUrl(
appInfo: AppInfo,
appCrash: AppCrash,): string {
return `https://console.firebase.google.com/project/${EnvConfig.projectId}/crashlytics/app/${appInfo.platform}:${appInfo.bundleId}/issues/${appCrash.issueId}`;
export function makeCrashlyticsIssueUrl(appCrash: AppCrash): string {
return `https://console.firebase.google.com/project/${EnvConfig.projectId}/crashlytics/app/${EnvConfig.platform}:${EnvConfig.bundleId}/issues/${appCrash.issueId}`;
}

/**
Expand All @@ -27,43 +23,37 @@ export function makeFirebaseAppsSettingsUrl(): string {
}

/**
* Generate a URL to Firestore app info document
* Make a repository URL to create an issue from this app crash
*
* @param {AppInfo} appInfo
* @return {string} URL to Firebase console
*/
export function makeFirestoreAppInfoUrl(appInfo: AppInfo): string {
return `https://console.firebase.google.com/project/${EnvConfig.projectId}/firestore/data/~2F${process.env.EXT_INSTANCE_ID}-apps~2F${appInfo.appId}`;
}

/**
* Make an Github URL to create an issue from this app crash
*
* @param {AppInfo} appInfo
* @param {AppCrash} appCrash
* @return {string} URL to create a github issue
*/
export function makeGithubIssueUrl(
appInfo: AppInfo,
appCrash: AppCrash,): string {
const attributes = [
`title=${encodeURI(appCrash.issueTitle)}`,
`labels=${appCrash.tags.map((tag) => encodeURI(tag)).join(",")}`,
];

return `https://github.com/${appInfo.github?.repo}/issues/new?${attributes.join("&")}`;
export function makeRepositoryIssueUrl(appCrash: AppCrash): string {
const repositoryUrl = EnvConfig.repositoryUrl;
if (repositoryUrl.startsWith("https://github.com")) {
const attributes = [
`title=${encodeURI(appCrash.issueTitle)}`,
`labels=${appCrash.tags.map((tag) => encodeURI(tag)).join(",")}`,
];

return `${repositoryUrl}/issues/new?${attributes.join("&")}`;
}

throw new Error("Unknown repository type: " + repositoryUrl);
}

/**
* Make an Github URL to search for issues from this app crash
* Make a repository URL to search for issues from this app crash
*
* @param {AppInfo} appInfo
* @param {AppCrash} appCrash
* @return {string} URL to search for github issues
*/
export function makeGithubSearchUrl(
appInfo: AppInfo,
appCrash: AppCrash,): string {
return `https://github.com/${appInfo.github?.repo}/issues?q=${encodeURI(appCrash.issueTitle)}`;
export function makeRepositorySearchUrl(appCrash: AppCrash): string {
const repositoryUrl = EnvConfig.repositoryUrl;
if (repositoryUrl.startsWith("https://github.com")) {
return `${repositoryUrl}/issues?q=${encodeURI(appCrash.issueTitle)}`;
}

throw new Error("Unknown repository type: " + repositoryUrl);
}

41 changes: 41 additions & 0 deletions functions/src/utils/env-config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { AppPlatform } from "../definitions/app-platform.enum";

/**
* Environment configuration settings and variables
* This class abstracts the source of getting the values and provides helpful
Expand Down Expand Up @@ -32,6 +34,45 @@ export class EnvConfig {
return projectId;
}

/**
* Get repository URL
*/
static get repositoryUrl(): string {
const repo = EnvConfig.getEnv("REPOSITORY_URL");
const repoWithHttps = repo.startsWith("https://") ? repo : `https://${repo}`;
return repoWithHttps.endsWith("/") ? repoWithHttps.slice(0, -1) : repoWithHttps;
}

/**
* Get bundle ID
*/
static get bundleId(): string {
return EnvConfig.getEnv("APP_BUNDLE_ID");
}

/**
* Get app ID
*/
static get appId(): string {
return EnvConfig.getEnv("APP_ID");
}

/**
* Get platform
*/
static get platform(): AppPlatform {
const appId = EnvConfig.getEnv("APP_ID");
if (appId.includes("android")) {
return AppPlatform.Android;
} else if (appId.includes("ios")) {
return AppPlatform.iOS;
} else if (appId.includes("web")) {
return AppPlatform.Web;
}

return AppPlatform.Unknown;
}

/**
* Get language for webhooks
*/
Expand Down
Loading

0 comments on commit de17a79

Please sign in to comment.