fix(android): skip push registration in builds without Firebase, which crashed the app at launch
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
28ee81b9d3
commit
2fd5549098
@@ -54,5 +54,9 @@
|
|||||||
{
|
{
|
||||||
"pkg": "SharedImage",
|
"pkg": "SharedImage",
|
||||||
"classpath": "app.timesafari.sharedimage.SharedImagePlugin"
|
"classpath": "app.timesafari.sharedimage.SharedImagePlugin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pkg": "NotificationInspector",
|
||||||
|
"classpath": "app.timesafari.notifications.NotificationInspectorPlugin"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+17
-1
@@ -1,5 +1,6 @@
|
|||||||
package app.timesafari.notifications;
|
package app.timesafari.notifications;
|
||||||
|
|
||||||
|
import com.getcapacitor.JSObject;
|
||||||
import com.getcapacitor.Plugin;
|
import com.getcapacitor.Plugin;
|
||||||
import com.getcapacitor.PluginCall;
|
import com.getcapacitor.PluginCall;
|
||||||
import com.getcapacitor.PluginMethod;
|
import com.getcapacitor.PluginMethod;
|
||||||
@@ -12,5 +13,20 @@ public class NotificationInspectorPlugin extends Plugin {
|
|||||||
call.unimplemented(
|
call.unimplemented(
|
||||||
"Pending notification inspection is currently implemented on iOS only");
|
"Pending notification inspection is currently implemented on iOS only");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this build carries a Firebase config, i.e. was built with
|
||||||
|
* -PfirebaseEnabled. Without it, PushNotifications.register() throws on the
|
||||||
|
* Capacitor plugin thread and kills the app, so JS must check this first.
|
||||||
|
* The google-services Gradle plugin generates the google_app_id resource,
|
||||||
|
* which is what Firebase's own auto-initialization looks for.
|
||||||
|
*/
|
||||||
|
@PluginMethod
|
||||||
|
public void isFirebaseConfigured(PluginCall call) {
|
||||||
|
int resId = getContext().getResources().getIdentifier(
|
||||||
|
"google_app_id", "string", getContext().getPackageName());
|
||||||
|
JSObject ret = new JSObject();
|
||||||
|
ret.put("configured", resId != 0);
|
||||||
|
call.resolve(ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
/**
|
/**
|
||||||
* Restore Local Capacitor Plugins
|
* Restore Local Capacitor Plugins
|
||||||
*
|
*
|
||||||
* This script ensures that local custom plugins (SafeArea and SharedImage)
|
* This script ensures that local custom plugins (SafeArea, SharedImage, NotificationInspector)
|
||||||
* are present in capacitor.plugins.json after `npx cap sync` runs.
|
* are present in capacitor.plugins.json after `npx cap sync` runs.
|
||||||
*
|
*
|
||||||
* The capacitor.plugins.json file is auto-generated by Capacitor and gets
|
* The capacitor.plugins.json file is auto-generated by Capacitor and gets
|
||||||
@@ -28,6 +28,10 @@ const LOCAL_PLUGINS = [
|
|||||||
{
|
{
|
||||||
pkg: 'SharedImage',
|
pkg: 'SharedImage',
|
||||||
classpath: 'app.timesafari.sharedimage.SharedImagePlugin'
|
classpath: 'app.timesafari.sharedimage.SharedImagePlugin'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pkg: 'NotificationInspector',
|
||||||
|
classpath: 'app.timesafari.notifications.NotificationInspectorPlugin'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export type PendingNotificationInfo = {
|
|||||||
|
|
||||||
export interface NotificationInspectorPlugin {
|
export interface NotificationInspectorPlugin {
|
||||||
getPendingNotifications(): Promise<{ pending: PendingNotificationInfo[] }>;
|
getPendingNotifications(): Promise<{ pending: PendingNotificationInfo[] }>;
|
||||||
|
/** Android only: whether the build has a Firebase config (-PfirebaseEnabled). */
|
||||||
|
isFirebaseConfigured(): Promise<{ configured: boolean }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NotificationInspector =
|
export const NotificationInspector =
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
onMessage,
|
onMessage,
|
||||||
} from "firebase/messaging";
|
} from "firebase/messaging";
|
||||||
import { logger } from "@/utils/logger";
|
import { logger } from "@/utils/logger";
|
||||||
|
import { NotificationInspector } from "@/plugins/NotificationInspectorPlugin";
|
||||||
import { handleCapacitorPushNotificationReceived } from "./NativeNotificationService";
|
import { handleCapacitorPushNotificationReceived } from "./NativeNotificationService";
|
||||||
import { getNotificationApiHeaders } from "./notificationApiAuth";
|
import { getNotificationApiHeaders } from "./notificationApiAuth";
|
||||||
import { deferFcmRegistration } from "./notificationAuthLifecycle";
|
import { deferFcmRegistration } from "./notificationAuthLifecycle";
|
||||||
@@ -67,6 +68,24 @@ async function registerRetrievedToken(
|
|||||||
lastRegisteredFcmToken = trimmed;
|
lastRegisteredFcmToken = trimmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether PushNotifications.register() is safe to call. Android builds without
|
||||||
|
* -PfirebaseEnabled (FOSS/F-Droid/APK) have no Firebase config, and register()
|
||||||
|
* there throws natively, crashing the app before any JS catch can run.
|
||||||
|
*/
|
||||||
|
async function canRegisterNativePush(): Promise<boolean> {
|
||||||
|
if (Capacitor.getPlatform() !== "android") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { configured } = await NotificationInspector.isFirebaseConfigured();
|
||||||
|
return configured;
|
||||||
|
} catch (err) {
|
||||||
|
logger.warn(`${LOG} Could not check Firebase config; skipping push`, err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Most recent FCM token from native/web push registration (for debug UI). */
|
/** Most recent FCM token from native/web push registration (for debug UI). */
|
||||||
export function getLastKnownFcmToken(): string | null {
|
export function getLastKnownFcmToken(): string | null {
|
||||||
return lastSeenFcmToken ?? lastRegisteredFcmToken;
|
return lastSeenFcmToken ?? lastRegisteredFcmToken;
|
||||||
@@ -80,6 +99,12 @@ export async function reregisterFcmTokenNow(): Promise<string> {
|
|||||||
throw new Error("FCM registration is only available on native platforms");
|
throw new Error("FCM registration is only available on native platforms");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(await canRegisterNativePush())) {
|
||||||
|
throw new Error(
|
||||||
|
"Push is unavailable: this Android build has no Firebase config (built without -PfirebaseEnabled)",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
lastRegisteredFcmToken = null;
|
lastRegisteredFcmToken = null;
|
||||||
|
|
||||||
const cached = lastSeenFcmToken?.trim();
|
const cached = lastSeenFcmToken?.trim();
|
||||||
@@ -290,6 +315,13 @@ async function initializeNativePushAndFirebaseMessagingImpl(): Promise<void> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(await canRegisterNativePush())) {
|
||||||
|
logger.info(
|
||||||
|
`${LOG} No Firebase config in this Android build; skipping push registration`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await PushNotifications.register();
|
await PushNotifications.register();
|
||||||
|
|
||||||
if (app) {
|
if (app) {
|
||||||
|
|||||||
Reference in New Issue
Block a user