forked from oddbit/firebase-alerts
-
Notifications
You must be signed in to change notification settings - Fork 0
/
crashlytics.ts
155 lines (125 loc) · 4.79 KB
/
crashlytics.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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";
import {SlackWebhook} from "../webhook-plugins/slack";
const functionOpts = {
region: process.env.LOCATION,
secrets: ["WEBHOOK_MANDATORY", "WEBHOOK_OPTIONAL"],
};
/**
* Factory method for returning a Webhook plugin based on the URL
*
* @param {string} url Webhook URL
* @return {Webhook} plugin
*/
function webhookPluginFromUrl(url: string): Webhook {
if (url?.startsWith("https://chat.googleapis.com")) {
logger.debug("[handleCrashlyticsEvent] Found Google Chat webhook");
return new GoogleChatWebhook({url, language: EnvConfig.language});
} else if (url?.startsWith("https://discord.com")) {
logger.debug("[handleCrashlyticsEvent] Found Discord webhook");
return new DiscordWebhook({url, language: EnvConfig.language});
} else if (url?.startsWith("https://hooks.slack.com")) {
logger.debug("[handleCrashlyticsEvent] Found slack webhook");
return new SlackWebhook({url, language: EnvConfig.language});
} else {
throw new Error("Unknown webhook type: " + url);
}
}
/**
* Handle crashlytics event
*
* @param {AppCrash} appCrash
* @return {Promise}
*/
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),
});
}
const webhooks: Webhook[] = EnvConfig.webhooks.map(webhookPluginFromUrl);
if (webhooks.length === 0) {
throw new Error("No webhooks defined. Please reconfigure the extension!");
}
const promises = [];
for (const webhook of webhooks) {
logger.debug("[handleCrashlyticsEvent] Webhook", webhook);
const webhookPayload = {
body: JSON.stringify(webhook.createCrashlyticsMessage(appInfo, appCrash)),
};
try {
logger.info("[handleCrashlyticsEvent] Calling webhook", webhookPayload);
promises.push(post(webhook.url, webhookPayload, (err, res) => {
if (err) throw err;
logger.info("[handleCrashlyticsEvent] Webhook call OK", res);
}));
} catch (error) {
logger.error("[handleCrashlyticsEvent] Failed posting webhook.", {
error,
webhook,
appCrash,
webhookPayload,
});
}
}
return Promise.all(promises);
}
export const anr =
crashlytics.onNewAnrIssuePublished(functionOpts, async (event) => {
logger.debug("onNewAnrIssuePublished", event);
const appCrash = AppCrash.fromCrashlytics(event);
appCrash.tags.push("critical");
return handleCrashlyticsEvent(appCrash);
});
export const fatal =
crashlytics.onNewFatalIssuePublished(functionOpts, (event) => {
logger.debug("onNewFatalIssuePublished", event);
const appCrash = AppCrash.fromCrashlytics(event);
appCrash.tags.push("critical");
return handleCrashlyticsEvent(appCrash);
});
export const nonfatal =
crashlytics.onNewNonfatalIssuePublished(functionOpts, (event) => {
logger.debug("onNewNonfatalIssuePublished", event);
const appCrash = AppCrash.fromCrashlytics(event);
return handleCrashlyticsEvent(appCrash);
});
export const regression =
crashlytics.onRegressionAlertPublished(functionOpts, (event) => {
logger.debug("onRegressionAlertPublished", event);
const appCrash = AppCrash.fromCrashlytics(event);
appCrash.tags.push("regression");
return handleCrashlyticsEvent(appCrash);
});