fix(dev): clarify Android pending inspector and harden debug entry guard
- Report UNIMPLEMENTED from Android NotificationInspector instead of empty pending - Surface iOS-only inspector message in NotificationDebugPanel without noisy errors - Gate Account debug link with import.meta.env.DEV and document intent - Add architecture comments on NotificationDebugService, inspector plugin, and native exports
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
package app.timesafari.notifications;
|
||||
|
||||
import com.getcapacitor.JSArray;
|
||||
import com.getcapacitor.JSObject;
|
||||
import com.getcapacitor.Plugin;
|
||||
import com.getcapacitor.PluginCall;
|
||||
import com.getcapacitor.PluginMethod;
|
||||
@@ -11,9 +9,8 @@ import com.getcapacitor.annotation.CapacitorPlugin;
|
||||
public class NotificationInspectorPlugin extends Plugin {
|
||||
@PluginMethod
|
||||
public void getPendingNotifications(PluginCall call) {
|
||||
JSObject result = new JSObject();
|
||||
result.put("pending", new JSArray());
|
||||
call.resolve(result);
|
||||
call.unimplemented(
|
||||
"Pending notification inspection is currently implemented on iOS only");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@ import Foundation
|
||||
import Capacitor
|
||||
import UserNotifications
|
||||
|
||||
// DEV-only diagnostic plugin.
|
||||
// Kept separate from DailyNotificationPlugin intentionally
|
||||
// to avoid altering production notification scheduling behavior.
|
||||
|
||||
@objc(NotificationInspector)
|
||||
public class NotificationInspectorPlugin: CAPPlugin, CAPBridgedPlugin {
|
||||
public var identifier: String { "NotificationInspector" }
|
||||
|
||||
@@ -75,7 +75,14 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="pending.length === 0"
|
||||
v-if="pendingInspectorMessage"
|
||||
class="text-sm text-amber-900 bg-amber-50 rounded px-3 py-2 border border-amber-200"
|
||||
role="status"
|
||||
>
|
||||
{{ pendingInspectorMessage }}
|
||||
</div>
|
||||
<div
|
||||
v-else-if="pending.length === 0"
|
||||
class="text-sm text-slate-500 bg-white rounded px-3 py-2 border border-slate-200"
|
||||
>
|
||||
(none)
|
||||
@@ -152,6 +159,7 @@ const presets = [
|
||||
const intervalMs = ref<number>(60_000);
|
||||
const busy = ref(false);
|
||||
const pending = ref<PendingInfo[]>([]);
|
||||
const pendingInspectorMessage = ref<string | null>(null);
|
||||
|
||||
const eventLog = computed(() => NotificationDebugService.eventLog.value);
|
||||
|
||||
@@ -172,7 +180,9 @@ async function withBusy(fn: () => Promise<void>): Promise<void> {
|
||||
|
||||
async function refreshPending(): Promise<void> {
|
||||
await withBusy(async () => {
|
||||
pending.value = await NotificationDebugService.getPendingNotifications();
|
||||
const result = await NotificationDebugService.getPendingNotifications();
|
||||
pending.value = result.pending;
|
||||
pendingInspectorMessage.value = result.inspectorUnavailableMessage ?? null;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -584,6 +584,10 @@ export type NotificationRefreshPayload = {
|
||||
nextNotifications?: Array<{ timestamp?: number }>;
|
||||
};
|
||||
|
||||
// `handleCapacitorPushNotificationReceived` and `applyNotificationRefreshPayload` are used by
|
||||
// DEV notification simulation tooling; they must stay production-safe because that tooling
|
||||
// exercises real flows. (`applyNotificationRefreshPayload` is also used by production refresh.)
|
||||
|
||||
/**
|
||||
* Apply a "refresh notifications" payload by clearing and scheduling timestamps via the native plugin.
|
||||
*
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/**
|
||||
* DEV-only notification testing utilities.
|
||||
*
|
||||
* IMPORTANT:
|
||||
* This service intentionally routes through the same production notification
|
||||
* orchestration paths used by refresh flows, wakeup pushes, and replacement.
|
||||
* Avoid adding duplicate scheduling logic here.
|
||||
*/
|
||||
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
import { ref, readonly, type Ref } from "vue";
|
||||
import type { PushNotificationSchema } from "@capacitor/push-notifications";
|
||||
@@ -16,6 +25,21 @@ type PendingNotificationInfo = {
|
||||
triggerType?: string | null;
|
||||
};
|
||||
|
||||
export type PendingNotificationsResult = {
|
||||
pending: PendingNotificationInfo[];
|
||||
/** Native layer does not implement inspection on this platform (e.g. Android). */
|
||||
inspectorUnavailableMessage?: string;
|
||||
};
|
||||
|
||||
function isUnimplementedError(e: unknown): boolean {
|
||||
return (
|
||||
typeof e === "object" &&
|
||||
e !== null &&
|
||||
"code" in e &&
|
||||
(e as { code?: string }).code === "UNIMPLEMENTED"
|
||||
);
|
||||
}
|
||||
|
||||
const LOG = "[NotificationDebugService]";
|
||||
|
||||
function formatTime(d: Date): string {
|
||||
@@ -131,23 +155,30 @@ export const NotificationDebugService = {
|
||||
append("Cleared notifications");
|
||||
},
|
||||
|
||||
async getPendingNotifications(): Promise<PendingNotificationInfo[]> {
|
||||
async getPendingNotifications(): Promise<PendingNotificationsResult> {
|
||||
append("Fetching pending notifications");
|
||||
|
||||
if (!Capacitor.isNativePlatform()) {
|
||||
append("Skipped: not running on native platform");
|
||||
return [];
|
||||
return { pending: [] };
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await NotificationInspector.getPendingNotifications();
|
||||
const items = (res?.pending ?? []) as PendingNotificationInfo[];
|
||||
append(`Pending fetched (${items.length})`);
|
||||
return items;
|
||||
} catch (e) {
|
||||
return { pending: items };
|
||||
} catch (e: unknown) {
|
||||
if (isUnimplementedError(e)) {
|
||||
return {
|
||||
pending: [],
|
||||
inspectorUnavailableMessage:
|
||||
"Pending notification inspection is currently supported on iOS only.",
|
||||
};
|
||||
}
|
||||
append("Pending fetch failed");
|
||||
logger.warn(`${LOG} getPendingNotifications failed`, e);
|
||||
return [];
|
||||
return { pending: [] };
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -742,8 +742,9 @@
|
||||
>
|
||||
Logs
|
||||
</router-link>
|
||||
<!-- DEV-only: never render in production (`import.meta.env.DEV` is false there). -->
|
||||
<router-link
|
||||
v-if="isDev"
|
||||
v-if="import.meta.env.DEV"
|
||||
:to="{ name: 'dev-notifications' }"
|
||||
class="block w-fit text-center text-md bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md mt-2"
|
||||
>
|
||||
@@ -895,7 +896,6 @@ export default class AccountViewView extends Vue {
|
||||
readonly DEFAULT_IMAGE_API_SERVER: string = DEFAULT_IMAGE_API_SERVER;
|
||||
readonly DEFAULT_PARTNER_API_SERVER: string = DEFAULT_PARTNER_API_SERVER;
|
||||
readonly PASSKEYS_ENABLED: boolean = PASSKEYS_ENABLED;
|
||||
readonly isDev: boolean = import.meta.env.DEV === true;
|
||||
|
||||
// Identity and settings properties
|
||||
activeDid: string = "";
|
||||
|
||||
Reference in New Issue
Block a user