feat(dev): extend Notification Debug Panel for backend testing
Add backend URL, test mode, token re-register, refresh diagnostics, FCM token display, and a capped event log; expose refreshNotificationsWithDiagnostics and reregisterFcmTokenNow.
This commit is contained in:
@@ -1,5 +1,114 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="bg-slate-100 rounded-md overflow-hidden px-4 py-4">
|
<section class="bg-slate-100 rounded-md overflow-hidden px-4 py-4">
|
||||||
|
<!-- Backend testing -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<h2 class="mb-2 font-bold">Backend Testing</h2>
|
||||||
|
|
||||||
|
<label class="block text-sm font-medium text-slate-700 mb-1">
|
||||||
|
Notification Backend URL
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-model="backendUrlDraft"
|
||||||
|
type="url"
|
||||||
|
class="w-full text-sm px-3 py-2 rounded border border-slate-300 bg-white mb-1"
|
||||||
|
placeholder="Leave empty for default (APP_SERVER)"
|
||||||
|
:disabled="busy"
|
||||||
|
@keydown.enter="onSaveBackendUrl"
|
||||||
|
/>
|
||||||
|
<p class="text-xs text-slate-500 mb-2">
|
||||||
|
Active:
|
||||||
|
<code class="text-[11px] break-all">{{ activeBackendUrl }}</code>
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
class="w-full text-sm mb-4 px-3 py-2 rounded border border-slate-300 bg-white"
|
||||||
|
:disabled="busy"
|
||||||
|
:class="{ 'opacity-50 cursor-not-allowed': busy }"
|
||||||
|
@click="onSaveBackendUrl"
|
||||||
|
>
|
||||||
|
Save Backend URL
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<label class="flex items-center gap-2 text-sm mb-4 cursor-pointer">
|
||||||
|
<input
|
||||||
|
v-model="testModeEnabled"
|
||||||
|
type="checkbox"
|
||||||
|
class="rounded border-slate-300"
|
||||||
|
:disabled="busy"
|
||||||
|
@change="onTestModeChange"
|
||||||
|
/>
|
||||||
|
<span>Test Mode</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2 mb-4">
|
||||||
|
<button
|
||||||
|
class="w-full text-md bg-gradient-to-b from-emerald-400 to-emerald-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
|
||||||
|
:disabled="busy"
|
||||||
|
:class="{ 'opacity-50 cursor-not-allowed': busy }"
|
||||||
|
@click="onRegisterToken"
|
||||||
|
>
|
||||||
|
Register Token Now
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="w-full text-md bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
|
||||||
|
:disabled="busy"
|
||||||
|
:class="{ 'opacity-50 cursor-not-allowed': busy }"
|
||||||
|
@click="onBackendRefresh"
|
||||||
|
>
|
||||||
|
Refresh Notifications
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="w-full text-md bg-gradient-to-b from-violet-400 to-violet-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
|
||||||
|
:disabled="busy"
|
||||||
|
:class="{ 'opacity-50 cursor-not-allowed': busy }"
|
||||||
|
@click="onSimulateWakeupRefresh"
|
||||||
|
>
|
||||||
|
Simulate WAKEUP_PING
|
||||||
|
</button>
|
||||||
|
<p class="text-xs text-slate-500">
|
||||||
|
Local simulation only — calls the refresh API directly (no FCM push).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<h3 class="text-sm font-bold mb-1">Current FCM Token</h3>
|
||||||
|
<div
|
||||||
|
v-if="fcmToken"
|
||||||
|
class="bg-white rounded border border-slate-200 px-3 py-2 text-xs font-mono break-all flex gap-2 items-start"
|
||||||
|
>
|
||||||
|
<span class="min-w-0 flex-1">{{ truncatedFcmToken }}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="shrink-0 text-sm px-2 py-1 rounded border border-slate-300 bg-slate-50"
|
||||||
|
@click="onCopyFcmToken"
|
||||||
|
>
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p
|
||||||
|
v-else
|
||||||
|
class="text-sm text-slate-500 bg-white rounded px-3 py-2 border border-slate-200"
|
||||||
|
>
|
||||||
|
(not available — try Register Token Now on native)
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-2">
|
||||||
|
<h3 class="text-sm font-bold mb-1">Backend Status</h3>
|
||||||
|
<dl
|
||||||
|
class="bg-white rounded border border-slate-200 px-3 py-2 text-xs space-y-1"
|
||||||
|
>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<dt class="text-slate-500 shrink-0">URL</dt>
|
||||||
|
<dd class="break-all font-mono">{{ activeBackendUrl }}</dd>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<dt class="text-slate-500 shrink-0">testMode</dt>
|
||||||
|
<dd>{{ testModeEnabled ? "true" : "false" }}</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- SECTION F: Mock Timing Presets -->
|
<!-- SECTION F: Mock Timing Presets -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h2 class="mb-2 font-bold">Mock Timing Presets</h2>
|
<h2 class="mb-2 font-bold">Mock Timing Presets</h2>
|
||||||
@@ -37,6 +146,10 @@
|
|||||||
<!-- SECTION B: Wakeup Ping Simulator -->
|
<!-- SECTION B: Wakeup Ping Simulator -->
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h2 class="mb-2 font-bold">Wakeup Ping Simulator</h2>
|
<h2 class="mb-2 font-bold">Wakeup Ping Simulator</h2>
|
||||||
|
<p class="text-xs text-slate-500 mb-2">
|
||||||
|
Exercises the production push handler (not the refresh API shortcut
|
||||||
|
above).
|
||||||
|
</p>
|
||||||
<button
|
<button
|
||||||
class="w-full 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"
|
class="w-full 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"
|
||||||
:disabled="busy"
|
:disabled="busy"
|
||||||
@@ -165,6 +278,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from "vue";
|
import { computed, onMounted, ref } from "vue";
|
||||||
|
import { copyToClipboard } from "@/services/ClipboardService";
|
||||||
import { NotificationDebugService } from "@/services/notifications/NotificationDebugService";
|
import { NotificationDebugService } from "@/services/notifications/NotificationDebugService";
|
||||||
|
|
||||||
type PendingInfo = {
|
type PendingInfo = {
|
||||||
@@ -186,6 +300,24 @@ const intervalMs = ref<number>(60_000);
|
|||||||
const busy = ref(false);
|
const busy = ref(false);
|
||||||
const pending = ref<PendingInfo[]>([]);
|
const pending = ref<PendingInfo[]>([]);
|
||||||
const pendingInspectorMessage = ref<string | null>(null);
|
const pendingInspectorMessage = ref<string | null>(null);
|
||||||
|
const backendUrlDraft = ref("");
|
||||||
|
const testModeEnabled = ref(NotificationDebugService.isTestModeEnabled());
|
||||||
|
const fcmToken = ref<string | null>(NotificationDebugService.getFcmToken());
|
||||||
|
|
||||||
|
const activeBackendUrl = computed(() =>
|
||||||
|
NotificationDebugService.getActiveBackendUrl(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const truncatedFcmToken = computed(() => {
|
||||||
|
const t = fcmToken.value?.trim() ?? "";
|
||||||
|
if (!t) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (t.length <= 24) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
return `${t.slice(0, 12)}…${t.slice(-8)}`;
|
||||||
|
});
|
||||||
|
|
||||||
const eventLog = computed(() => NotificationDebugService.eventLog.value);
|
const eventLog = computed(() => NotificationDebugService.eventLog.value);
|
||||||
|
|
||||||
@@ -245,7 +377,58 @@ async function onClearNotifications(): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function syncBackendState(): void {
|
||||||
|
backendUrlDraft.value =
|
||||||
|
NotificationDebugService.getBackendUrlOverride() ?? "";
|
||||||
|
testModeEnabled.value = NotificationDebugService.isTestModeEnabled();
|
||||||
|
fcmToken.value = NotificationDebugService.getFcmToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onSaveBackendUrl(): void {
|
||||||
|
NotificationDebugService.saveBackendBaseUrl(backendUrlDraft.value);
|
||||||
|
syncBackendState();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onTestModeChange(): void {
|
||||||
|
NotificationDebugService.setTestModeEnabled(testModeEnabled.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onRegisterToken(): Promise<void> {
|
||||||
|
await withBusy(async () => {
|
||||||
|
try {
|
||||||
|
await NotificationDebugService.registerTokenNow();
|
||||||
|
} catch {
|
||||||
|
// logged in panel
|
||||||
|
} finally {
|
||||||
|
fcmToken.value = NotificationDebugService.getFcmToken();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onBackendRefresh(): Promise<void> {
|
||||||
|
await withBusy(async () => {
|
||||||
|
await NotificationDebugService.triggerBackendRefresh();
|
||||||
|
await refreshPending();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSimulateWakeupRefresh(): Promise<void> {
|
||||||
|
await withBusy(async () => {
|
||||||
|
await NotificationDebugService.simulateWakeupViaRefresh();
|
||||||
|
await refreshPending();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onCopyFcmToken(): Promise<void> {
|
||||||
|
const token = fcmToken.value?.trim();
|
||||||
|
if (!token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await copyToClipboard(token);
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
syncBackendState();
|
||||||
void refreshPending();
|
void refreshPending();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -549,13 +549,24 @@ export class NativeNotificationService implements NotificationServiceInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type RefreshNotificationsResult = {
|
||||||
|
ok: boolean;
|
||||||
|
scheduledCount: number;
|
||||||
|
status?: number;
|
||||||
|
errorMessage?: string;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Re-applies native API fetcher credentials (JWT pool, active DID) so background
|
* Re-applies native API fetcher credentials (JWT pool, active DID) so background
|
||||||
* notification workers can run. No UI; safe from push handlers while backgrounded.
|
* notification workers can run. No UI; safe from push handlers while backgrounded.
|
||||||
*/
|
*/
|
||||||
export async function refreshNotifications(): Promise<void> {
|
export async function refreshNotificationsWithDiagnostics(): Promise<RefreshNotificationsResult> {
|
||||||
if (!Capacitor.isNativePlatform()) {
|
if (!Capacitor.isNativePlatform()) {
|
||||||
return;
|
return {
|
||||||
|
ok: false,
|
||||||
|
scheduledCount: 0,
|
||||||
|
errorMessage: "not a native platform",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -574,16 +585,32 @@ export async function refreshNotifications(): Promise<void> {
|
|||||||
status: res.status,
|
status: res.status,
|
||||||
statusText: res.statusText,
|
statusText: res.statusText,
|
||||||
});
|
});
|
||||||
return;
|
return {
|
||||||
|
ok: false,
|
||||||
|
scheduledCount: 0,
|
||||||
|
status: res.status,
|
||||||
|
errorMessage: res.statusText || `HTTP ${res.status}`,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: unknown = await res.json();
|
const data: unknown = await res.json();
|
||||||
|
const payload = data as NotificationRefreshPayload;
|
||||||
|
const scheduledCount = Array.isArray(payload?.nextNotifications)
|
||||||
|
? payload.nextNotifications.length
|
||||||
|
: 0;
|
||||||
await applyNotificationRefreshPayload(data);
|
await applyNotificationRefreshPayload(data);
|
||||||
|
return { ok: true, scheduledCount };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error("[NativeNotificationService] Refresh failed", err);
|
logger.error("[NativeNotificationService] Refresh failed", err);
|
||||||
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
|
return { ok: false, scheduledCount: 0, errorMessage: message };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function refreshNotifications(): Promise<void> {
|
||||||
|
await refreshNotificationsWithDiagnostics();
|
||||||
|
}
|
||||||
|
|
||||||
export type NotificationRefreshPayload = {
|
export type NotificationRefreshPayload = {
|
||||||
shouldNotify?: boolean;
|
shouldNotify?: boolean;
|
||||||
nextNotifications?: Array<{ timestamp?: number }>;
|
nextNotifications?: Array<{ timestamp?: number }>;
|
||||||
|
|||||||
@@ -11,9 +11,21 @@ import { Capacitor } from "@capacitor/core";
|
|||||||
import { ref, readonly, type Ref } from "vue";
|
import { ref, readonly, type Ref } from "vue";
|
||||||
import type { PushNotificationSchema } from "@capacitor/push-notifications";
|
import type { PushNotificationSchema } from "@capacitor/push-notifications";
|
||||||
import { logger } from "@/utils/logger";
|
import { logger } from "@/utils/logger";
|
||||||
|
import {
|
||||||
|
getBackendBaseUrl,
|
||||||
|
getNotificationApiBaseUrl,
|
||||||
|
getTestMode,
|
||||||
|
setBackendBaseUrl,
|
||||||
|
setTestMode,
|
||||||
|
} from "./NotificationDebugConfig";
|
||||||
|
import {
|
||||||
|
getLastKnownFcmToken,
|
||||||
|
reregisterFcmTokenNow,
|
||||||
|
} from "./firebaseMessagingClient";
|
||||||
import {
|
import {
|
||||||
applyNotificationRefreshPayload,
|
applyNotificationRefreshPayload,
|
||||||
handleCapacitorPushNotificationReceived,
|
handleCapacitorPushNotificationReceived,
|
||||||
|
refreshNotificationsWithDiagnostics,
|
||||||
type NotificationRefreshPayload,
|
type NotificationRefreshPayload,
|
||||||
} from "./NativeNotificationService";
|
} from "./NativeNotificationService";
|
||||||
import { DailyNotification } from "@/plugins/DailyNotificationPlugin";
|
import { DailyNotification } from "@/plugins/DailyNotificationPlugin";
|
||||||
@@ -55,11 +67,21 @@ function logLine(message: string): string {
|
|||||||
return `[${formatTime(new Date())}] ${message}`;
|
return `[${formatTime(new Date())}] ${message}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function truncateToken(token: string): string {
|
||||||
|
const t = token.trim();
|
||||||
|
if (t.length <= 24) {
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
return `${t.slice(0, 12)}…${t.slice(-8)}`;
|
||||||
|
}
|
||||||
|
|
||||||
const _eventLog: Ref<string[]> = ref([]);
|
const _eventLog: Ref<string[]> = ref([]);
|
||||||
|
|
||||||
|
const EVENT_LOG_MAX = 100;
|
||||||
|
|
||||||
function append(message: string): void {
|
function append(message: string): void {
|
||||||
const line = logLine(message);
|
const line = logLine(message);
|
||||||
_eventLog.value = [..._eventLog.value, line].slice(-250);
|
_eventLog.value = [..._eventLog.value, line].slice(-EVENT_LOG_MAX);
|
||||||
logger.debug(`${LOG} ${message}`);
|
logger.debug(`${LOG} ${message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +92,67 @@ export const NotificationDebugService = {
|
|||||||
_eventLog.value = [];
|
_eventLog.value = [];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getActiveBackendUrl(): string {
|
||||||
|
return getNotificationApiBaseUrl();
|
||||||
|
},
|
||||||
|
|
||||||
|
getBackendUrlOverride(): string | null {
|
||||||
|
return getBackendBaseUrl();
|
||||||
|
},
|
||||||
|
|
||||||
|
saveBackendBaseUrl(url: string): void {
|
||||||
|
setBackendBaseUrl(url);
|
||||||
|
append(
|
||||||
|
url.trim()
|
||||||
|
? `Backend URL saved (${getNotificationApiBaseUrl()})`
|
||||||
|
: "Backend URL cleared (using default)",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
setTestModeEnabled(enabled: boolean): void {
|
||||||
|
setTestMode(enabled);
|
||||||
|
append(`Test mode ${enabled ? "enabled" : "disabled"}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
isTestModeEnabled(): boolean {
|
||||||
|
return getTestMode();
|
||||||
|
},
|
||||||
|
|
||||||
|
getFcmToken(): string | null {
|
||||||
|
return getLastKnownFcmToken();
|
||||||
|
},
|
||||||
|
|
||||||
|
async registerTokenNow(): Promise<void> {
|
||||||
|
append("Register token started");
|
||||||
|
try {
|
||||||
|
const token = await reregisterFcmTokenNow();
|
||||||
|
append(`Register token success (${truncateToken(token)})`);
|
||||||
|
} catch (e: unknown) {
|
||||||
|
const msg = e instanceof Error ? e.message : String(e);
|
||||||
|
append(`Register token failure: ${msg}`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async triggerBackendRefresh(): Promise<void> {
|
||||||
|
append("Refresh started");
|
||||||
|
const result = await refreshNotificationsWithDiagnostics();
|
||||||
|
if (result.ok) {
|
||||||
|
append(`Refresh success`);
|
||||||
|
append(`Scheduled ${result.scheduledCount} notifications`);
|
||||||
|
} else {
|
||||||
|
const detail = result.errorMessage ?? "unknown error";
|
||||||
|
const status = result.status != null ? ` (HTTP ${result.status})` : "";
|
||||||
|
append(`Refresh failure: ${detail}${status}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Local simulation: same API call as a WAKEUP_PING handler (no push payload). */
|
||||||
|
async simulateWakeupViaRefresh(): Promise<void> {
|
||||||
|
append("WAKEUP_PING simulation started (local refresh API only)");
|
||||||
|
await this.triggerBackendRefresh();
|
||||||
|
},
|
||||||
|
|
||||||
generateMockNotifications(
|
generateMockNotifications(
|
||||||
intervalMs: number = 60_000,
|
intervalMs: number = 60_000,
|
||||||
): NotificationRefreshPayload {
|
): NotificationRefreshPayload {
|
||||||
|
|||||||
@@ -30,19 +30,94 @@ let firebaseAppSingleton: FirebaseApp | null = null;
|
|||||||
let nativeInitPromise: Promise<void> | null = null;
|
let nativeInitPromise: Promise<void> | null = null;
|
||||||
/** Avoid duplicate POSTs when the same token is delivered more than once. */
|
/** Avoid duplicate POSTs when the same token is delivered more than once. */
|
||||||
let lastRegisteredFcmToken: string | null = null;
|
let lastRegisteredFcmToken: string | null = null;
|
||||||
|
/** Last token received from Capacitor/Firebase (may match registered). */
|
||||||
|
let lastSeenFcmToken: string | null = null;
|
||||||
|
|
||||||
async function registerRetrievedToken(token: string): Promise<void> {
|
async function registerRetrievedToken(
|
||||||
|
token: string,
|
||||||
|
options?: { force?: boolean },
|
||||||
|
): Promise<void> {
|
||||||
const trimmed = token.trim();
|
const trimmed = token.trim();
|
||||||
if (!trimmed) {
|
if (!trimmed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (trimmed === lastRegisteredFcmToken) {
|
lastSeenFcmToken = trimmed;
|
||||||
|
if (!options?.force && trimmed === lastRegisteredFcmToken) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await registerToken(trimmed);
|
await registerToken(trimmed);
|
||||||
lastRegisteredFcmToken = trimmed;
|
lastRegisteredFcmToken = trimmed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Most recent FCM token from native/web push registration (for debug UI). */
|
||||||
|
export function getLastKnownFcmToken(): string | null {
|
||||||
|
return lastSeenFcmToken ?? lastRegisteredFcmToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-runs token registration immediately (debug). Bypasses duplicate-token skip.
|
||||||
|
*/
|
||||||
|
export async function reregisterFcmTokenNow(): Promise<string> {
|
||||||
|
if (!Capacitor.isNativePlatform()) {
|
||||||
|
throw new Error("FCM registration is only available on native platforms");
|
||||||
|
}
|
||||||
|
|
||||||
|
lastRegisteredFcmToken = null;
|
||||||
|
|
||||||
|
const cached = lastSeenFcmToken?.trim();
|
||||||
|
if (cached) {
|
||||||
|
await registerRetrievedToken(cached, { force: true });
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = ensureFirebaseApp();
|
||||||
|
if (app && (await isSupported())) {
|
||||||
|
const messaging = getMessaging(app);
|
||||||
|
const vapidKey = import.meta.env.VITE_FIREBASE_VAPID_KEY as
|
||||||
|
| string
|
||||||
|
| undefined;
|
||||||
|
const token = await getToken(
|
||||||
|
messaging,
|
||||||
|
vapidKey ? { vapidKey } : undefined,
|
||||||
|
);
|
||||||
|
if (!token?.trim()) {
|
||||||
|
throw new Error("Firebase getToken returned an empty token");
|
||||||
|
}
|
||||||
|
await registerRetrievedToken(token, { force: true });
|
||||||
|
return token.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise<string>((resolve, reject) => {
|
||||||
|
const timeoutMs = 15_000;
|
||||||
|
const timeoutId = window.setTimeout(() => {
|
||||||
|
void listenerPromise.then((h) => h.remove());
|
||||||
|
reject(new Error("Timed out waiting for push registration token"));
|
||||||
|
}, timeoutMs);
|
||||||
|
|
||||||
|
const listenerPromise = PushNotifications.addListener(
|
||||||
|
"registration",
|
||||||
|
(token) => {
|
||||||
|
window.clearTimeout(timeoutId);
|
||||||
|
void listenerPromise.then((h) => h.remove());
|
||||||
|
const value = token.value?.trim() ?? "";
|
||||||
|
if (!value) {
|
||||||
|
reject(new Error("Capacitor registration returned an empty token"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
void registerRetrievedToken(value, { force: true })
|
||||||
|
.then(() => resolve(value))
|
||||||
|
.catch(reject);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
void PushNotifications.register().catch((err) => {
|
||||||
|
window.clearTimeout(timeoutId);
|
||||||
|
void listenerPromise.then((h) => h.remove());
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function readFirebaseOptions(): FirebaseOptions | null {
|
function readFirebaseOptions(): FirebaseOptions | null {
|
||||||
const env = import.meta.env;
|
const env = import.meta.env;
|
||||||
const apiKey = env.VITE_FIREBASE_API_KEY as string | undefined;
|
const apiKey = env.VITE_FIREBASE_API_KEY as string | undefined;
|
||||||
@@ -149,6 +224,9 @@ async function initializeNativePushAndFirebaseMessagingImpl(): Promise<void> {
|
|||||||
const app = ensureFirebaseApp();
|
const app = ensureFirebaseApp();
|
||||||
|
|
||||||
await PushNotifications.addListener("registration", (token) => {
|
await PushNotifications.addListener("registration", (token) => {
|
||||||
|
if (token.value?.trim()) {
|
||||||
|
lastSeenFcmToken = token.value.trim();
|
||||||
|
}
|
||||||
logger.info(`${LOG} Capacitor registration token`, {
|
logger.info(`${LOG} Capacitor registration token`, {
|
||||||
valuePrefix: token.value ? `${token.value.slice(0, 12)}…` : "(empty)",
|
valuePrefix: token.value ? `${token.value.slice(0, 12)}…` : "(empty)",
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user