init
This commit is contained in:
784
frontend/composables/usePush.ts
Normal file
784
frontend/composables/usePush.ts
Normal file
@@ -0,0 +1,784 @@
|
||||
import { App as CapacitorApp } from "@capacitor/app";
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
import { Device } from "@capacitor/device";
|
||||
import { Preferences } from "@capacitor/preferences";
|
||||
import { PushNotifications, type Token } from "@capacitor/push-notifications";
|
||||
|
||||
type PushSubscriptionsResponse = {
|
||||
items: Array<{
|
||||
id: string;
|
||||
active: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
endpoint?: string;
|
||||
platform?: string;
|
||||
}>;
|
||||
hasActiveSubscription: boolean;
|
||||
};
|
||||
|
||||
type BeforeInstallPromptEvent = Event & {
|
||||
prompt: () => Promise<void>;
|
||||
userChoice: Promise<{ outcome: "accepted" | "dismissed"; platform: string }>;
|
||||
};
|
||||
|
||||
type NotificationPermissionState = "default" | "denied" | "granted" | "prompt" | "unsupported";
|
||||
|
||||
const ANON_PUSH_CLIENT_ID_KEY = "anonymous-push-client-id";
|
||||
const PENDING_PUSH_ROUTE_KEY = "pending-push-route";
|
||||
|
||||
export function usePush() {
|
||||
const { user, token } = useAuth();
|
||||
const config = useRuntimeConfig();
|
||||
const installPromptEvent = useState<BeforeInstallPromptEvent | null>("pwa-install-prompt-event", () => null);
|
||||
const anonymousClientId = useState<string | null>("anonymous-push-client-id", () => null);
|
||||
const nativePushToken = useState<string | null>("native-push-token", () => null);
|
||||
const nativePushDeviceId = useState<string | null>("native-push-device-id", () => null);
|
||||
const nativePushInitialized = useState<boolean>("native-push-initialized", () => false);
|
||||
const webPushClickInitialized = useState<boolean>("web-push-click-initialized", () => false);
|
||||
const pendingPushRoute = useState<string | null>("pending-push-route", () => null);
|
||||
|
||||
const isNativeApp = () => process.client && Capacitor.isNativePlatform();
|
||||
|
||||
const getNativePlatform = () => {
|
||||
if (!isNativeApp()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const platform = Capacitor.getPlatform();
|
||||
if (platform === "android" || platform === "ios") {
|
||||
return platform;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const readStoredAnonymousClientId = async () => {
|
||||
if (!process.client) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isNativeApp()) {
|
||||
const result = await Preferences.get({ key: ANON_PUSH_CLIENT_ID_KEY });
|
||||
return result.value;
|
||||
}
|
||||
|
||||
return localStorage.getItem(ANON_PUSH_CLIENT_ID_KEY);
|
||||
};
|
||||
|
||||
const persistAnonymousClientId = async (value: string) => {
|
||||
if (!process.client) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNativeApp()) {
|
||||
await Preferences.set({ key: ANON_PUSH_CLIENT_ID_KEY, value });
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem(ANON_PUSH_CLIENT_ID_KEY, value);
|
||||
};
|
||||
|
||||
const clearAnonymousClientId = async () => {
|
||||
if (!process.client) {
|
||||
return;
|
||||
}
|
||||
|
||||
anonymousClientId.value = null;
|
||||
|
||||
if (isNativeApp()) {
|
||||
await Preferences.remove({ key: ANON_PUSH_CLIENT_ID_KEY });
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.removeItem(ANON_PUSH_CLIENT_ID_KEY);
|
||||
};
|
||||
|
||||
const readStoredPendingPushRoute = async () => {
|
||||
if (!process.client) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isNativeApp()) {
|
||||
const result = await Preferences.get({ key: PENDING_PUSH_ROUTE_KEY });
|
||||
return result.value;
|
||||
}
|
||||
|
||||
return localStorage.getItem(PENDING_PUSH_ROUTE_KEY);
|
||||
};
|
||||
|
||||
const persistPendingPushRoute = async (value: string | null) => {
|
||||
if (!process.client) {
|
||||
return;
|
||||
}
|
||||
|
||||
pendingPushRoute.value = value;
|
||||
|
||||
if (isNativeApp()) {
|
||||
if (value) {
|
||||
await Preferences.set({ key: PENDING_PUSH_ROUTE_KEY, value });
|
||||
} else {
|
||||
await Preferences.remove({ key: PENDING_PUSH_ROUTE_KEY });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
localStorage.setItem(PENDING_PUSH_ROUTE_KEY, value);
|
||||
} else {
|
||||
localStorage.removeItem(PENDING_PUSH_ROUTE_KEY);
|
||||
}
|
||||
};
|
||||
|
||||
const normalizeTargetRoute = (targetUrl: unknown) => {
|
||||
if (typeof targetUrl !== "string" || targetUrl.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (targetUrl.startsWith("/")) {
|
||||
return targetUrl;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = new URL(targetUrl);
|
||||
return `${parsed.pathname}${parsed.search}${parsed.hash}` || "/";
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const openPushTarget = async (targetUrl: unknown) => {
|
||||
const route = normalizeTargetRoute(targetUrl);
|
||||
if (!route) {
|
||||
return;
|
||||
}
|
||||
|
||||
await persistPendingPushRoute(route);
|
||||
|
||||
if (user.value) {
|
||||
await navigateTo(route);
|
||||
await persistPendingPushRoute(null);
|
||||
return;
|
||||
}
|
||||
|
||||
await navigateTo(`/login?redirect=${encodeURIComponent(route)}`);
|
||||
};
|
||||
|
||||
const consumePendingPushRoute = async () => {
|
||||
if (!process.client) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!pendingPushRoute.value) {
|
||||
pendingPushRoute.value = await readStoredPendingPushRoute();
|
||||
}
|
||||
|
||||
const route = pendingPushRoute.value;
|
||||
if (!route || !user.value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
await navigateTo(route);
|
||||
await persistPendingPushRoute(null);
|
||||
return route;
|
||||
};
|
||||
|
||||
const ensureAnonymousClientId = async () => {
|
||||
if (!process.client) {
|
||||
return "server-client";
|
||||
}
|
||||
|
||||
if (!anonymousClientId.value) {
|
||||
anonymousClientId.value = await readStoredAnonymousClientId();
|
||||
}
|
||||
|
||||
if (!anonymousClientId.value) {
|
||||
anonymousClientId.value = crypto.randomUUID().replace(/[^a-zA-Z0-9_-]/g, "");
|
||||
await persistAnonymousClientId(anonymousClientId.value);
|
||||
}
|
||||
|
||||
return anonymousClientId.value;
|
||||
};
|
||||
|
||||
const registerServiceWorker = async () => {
|
||||
if (!process.client || isNativeApp() || !("serviceWorker" in navigator)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return navigator.serviceWorker.register("/sw.js");
|
||||
};
|
||||
|
||||
const syncServiceWorkerContext = async () => {
|
||||
if (!process.client || isNativeApp() || !("serviceWorker" in navigator)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const registration = await registerServiceWorker();
|
||||
const readyRegistration = registration ?? (await navigator.serviceWorker.ready);
|
||||
const worker = readyRegistration.active ?? navigator.serviceWorker.controller;
|
||||
if (!worker) {
|
||||
return;
|
||||
}
|
||||
|
||||
worker.postMessage({
|
||||
type: "push-context-sync",
|
||||
payload: {
|
||||
apiBase: config.public.apiBase,
|
||||
isAuthenticated: Boolean(user.value),
|
||||
anonymousClientId: await ensureAnonymousClientId()
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const urlBase64ToUint8Array = (base64String: string) => {
|
||||
const normalizedInput = base64String.trim();
|
||||
if (!normalizedInput || normalizedInput.startsWith("replace_")) {
|
||||
throw new Error("Push-уведомления еще не настроены на сервере");
|
||||
}
|
||||
|
||||
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
|
||||
const base64 = (normalizedInput + padding).replace(/-/g, "+").replace(/_/g, "/");
|
||||
|
||||
try {
|
||||
const rawData = window.atob(base64);
|
||||
return Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)));
|
||||
} catch {
|
||||
throw new Error("На сервере задан некорректный VAPID public key");
|
||||
}
|
||||
};
|
||||
|
||||
const isMobileDevice = () => {
|
||||
if (!process.client) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isNativeApp()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
};
|
||||
|
||||
const isIosDevice = () => {
|
||||
if (!process.client) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isNativeApp()) {
|
||||
return Capacitor.getPlatform() === "ios";
|
||||
}
|
||||
|
||||
return /iPhone|iPad|iPod/i.test(navigator.userAgent);
|
||||
};
|
||||
|
||||
const isStandaloneMode = () => {
|
||||
if (!process.client) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isNativeApp()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const standaloneMedia = window.matchMedia?.("(display-mode: standalone)")?.matches ?? false;
|
||||
const standaloneNavigator =
|
||||
"standalone" in navigator ? Boolean((navigator as Navigator & { standalone?: boolean }).standalone) : false;
|
||||
return standaloneMedia || standaloneNavigator;
|
||||
};
|
||||
|
||||
const setInstallPromptEvent = (event: BeforeInstallPromptEvent | null) => {
|
||||
installPromptEvent.value = event;
|
||||
};
|
||||
|
||||
const canTriggerInstallPrompt = () => !isNativeApp() && Boolean(installPromptEvent.value);
|
||||
|
||||
const triggerInstallPrompt = async () => {
|
||||
if (!installPromptEvent.value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await installPromptEvent.value.prompt();
|
||||
const choice = await installPromptEvent.value.userChoice;
|
||||
installPromptEvent.value = null;
|
||||
return choice.outcome === "accepted";
|
||||
};
|
||||
|
||||
const getBrowserPermissionState = (): NotificationPermissionState => {
|
||||
if (!process.client || !("Notification" in window)) {
|
||||
return "unsupported";
|
||||
}
|
||||
|
||||
return Notification.permission;
|
||||
};
|
||||
|
||||
const getNativePermissionState = async (): Promise<NotificationPermissionState> => {
|
||||
if (!isNativeApp()) {
|
||||
return "unsupported";
|
||||
}
|
||||
|
||||
const permissions = await PushNotifications.checkPermissions();
|
||||
if (permissions.receive === "prompt") {
|
||||
return "default";
|
||||
}
|
||||
|
||||
return permissions.receive;
|
||||
};
|
||||
|
||||
const getPermissionState = () => {
|
||||
if (isNativeApp()) {
|
||||
return getNativePermissionState();
|
||||
}
|
||||
|
||||
return Promise.resolve(getBrowserPermissionState());
|
||||
};
|
||||
|
||||
const requestBrowserPermission = async (): Promise<NotificationPermissionState> => {
|
||||
if (!process.client || !("Notification" in window)) {
|
||||
return "unsupported";
|
||||
}
|
||||
|
||||
return Notification.requestPermission();
|
||||
};
|
||||
|
||||
const requestNativePermission = async (): Promise<NotificationPermissionState> => {
|
||||
if (!isNativeApp()) {
|
||||
return "unsupported";
|
||||
}
|
||||
|
||||
const permissions = await PushNotifications.requestPermissions();
|
||||
if (permissions.receive === "prompt") {
|
||||
return "default";
|
||||
}
|
||||
|
||||
return permissions.receive;
|
||||
};
|
||||
|
||||
const requestPermission = () => {
|
||||
if (isNativeApp()) {
|
||||
return requestNativePermission();
|
||||
}
|
||||
|
||||
return requestBrowserPermission();
|
||||
};
|
||||
|
||||
const syncWebSubscription = async (subscription: PushSubscription) => {
|
||||
const serializedSubscription = subscription.toJSON();
|
||||
if (!serializedSubscription.endpoint || !serializedSubscription.keys?.p256dh || !serializedSubscription.keys?.auth) {
|
||||
throw new Error("Браузер вернул неполную push-подписку");
|
||||
}
|
||||
|
||||
if (user.value) {
|
||||
await useApi("/me/push-subscriptions", {
|
||||
method: "POST",
|
||||
body: serializedSubscription
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await useApi("/public/push-subscriptions", {
|
||||
method: "POST",
|
||||
body: {
|
||||
clientId: await ensureAnonymousClientId(),
|
||||
...serializedSubscription
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const syncNativeSubscription = async (tokenValue = nativePushToken.value) => {
|
||||
const platform = getNativePlatform();
|
||||
if (!platform || !tokenValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!nativePushDeviceId.value) {
|
||||
const device = await Device.getId();
|
||||
nativePushDeviceId.value = device.identifier;
|
||||
}
|
||||
|
||||
const body = {
|
||||
token: tokenValue,
|
||||
platform,
|
||||
deviceId: nativePushDeviceId.value ?? undefined
|
||||
};
|
||||
|
||||
if (user.value) {
|
||||
await useApi("/me/native-push-subscriptions", {
|
||||
method: "POST",
|
||||
body
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await useApi("/public/native-push-subscriptions", {
|
||||
method: "POST",
|
||||
body: {
|
||||
clientId: await ensureAnonymousClientId(),
|
||||
...body
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const initializeNativePush = async () => {
|
||||
if (!isNativeApp() || nativePushInitialized.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
nativePushInitialized.value = true;
|
||||
|
||||
await PushNotifications.createChannel({
|
||||
id: "signals",
|
||||
name: "Signals",
|
||||
description: "Уведомления о новых сигналах",
|
||||
importance: 5,
|
||||
visibility: 1,
|
||||
sound: "default"
|
||||
}).catch(() => undefined);
|
||||
|
||||
await PushNotifications.addListener("registration", async (registrationToken: Token) => {
|
||||
nativePushToken.value = registrationToken.value;
|
||||
await syncNativeSubscription(registrationToken.value);
|
||||
});
|
||||
|
||||
await PushNotifications.addListener("registrationError", (error) => {
|
||||
console.error("Native push registration failed", error);
|
||||
});
|
||||
|
||||
await PushNotifications.addListener("pushNotificationActionPerformed", (notification) => {
|
||||
const targetUrl =
|
||||
notification.notification.data?.url ||
|
||||
notification.notification.data?.link ||
|
||||
notification.notification.data?.path;
|
||||
|
||||
void openPushTarget(targetUrl);
|
||||
});
|
||||
|
||||
await CapacitorApp.addListener("appUrlOpen", ({ url }) => {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
const route = `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
||||
void openPushTarget(route || "/");
|
||||
} catch {
|
||||
// Ignore invalid deep links.
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const initializeWebPushRouting = () => {
|
||||
if (!process.client || isNativeApp() || webPushClickInitialized.value || !("serviceWorker" in navigator)) {
|
||||
return;
|
||||
}
|
||||
|
||||
webPushClickInitialized.value = true;
|
||||
|
||||
navigator.serviceWorker.addEventListener("message", (event) => {
|
||||
if (event.data?.type !== "push-notification-click") {
|
||||
return;
|
||||
}
|
||||
|
||||
void openPushTarget(event.data.url);
|
||||
});
|
||||
};
|
||||
|
||||
const getCurrentBrowserSubscription = async () => {
|
||||
const registration = await registerServiceWorker();
|
||||
if (!registration || !("PushManager" in window)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return registration.pushManager.getSubscription();
|
||||
};
|
||||
|
||||
const claimAnonymousPushSubscriptions = async () => {
|
||||
if (!process.client || !user.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existingClientId = anonymousClientId.value ?? (await readStoredAnonymousClientId());
|
||||
if (!existingClientId) {
|
||||
await syncServiceWorkerContext();
|
||||
return;
|
||||
}
|
||||
|
||||
anonymousClientId.value = existingClientId;
|
||||
|
||||
if (isNativeApp()) {
|
||||
if (nativePushToken.value) {
|
||||
await syncNativeSubscription(nativePushToken.value);
|
||||
}
|
||||
|
||||
const anonymousSubscriptions = await useApi<PushSubscriptionsResponse>(
|
||||
`/public/native-push-subscriptions/${existingClientId}`
|
||||
).catch(() => null);
|
||||
|
||||
for (const subscription of anonymousSubscriptions?.items ?? []) {
|
||||
await useApi(`/public/native-push-subscriptions/${existingClientId}/${subscription.id}`, {
|
||||
method: "DELETE"
|
||||
}).catch(() => undefined);
|
||||
}
|
||||
} else {
|
||||
const currentSubscription = await getCurrentBrowserSubscription();
|
||||
if (currentSubscription) {
|
||||
await syncWebSubscription(currentSubscription);
|
||||
}
|
||||
|
||||
const anonymousSubscriptions = await useApi<PushSubscriptionsResponse>(
|
||||
`/public/push-subscriptions/${existingClientId}`
|
||||
).catch(() => null);
|
||||
|
||||
for (const subscription of anonymousSubscriptions?.items ?? []) {
|
||||
await useApi(`/public/push-subscriptions/${existingClientId}/${subscription.id}`, {
|
||||
method: "DELETE"
|
||||
}).catch(() => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
await clearAnonymousClientId();
|
||||
await syncServiceWorkerContext();
|
||||
};
|
||||
|
||||
const getCurrentSubscription = async () => {
|
||||
if (isNativeApp()) {
|
||||
return nativePushToken.value;
|
||||
}
|
||||
|
||||
return getCurrentBrowserSubscription();
|
||||
};
|
||||
|
||||
const deactivateCurrentPushSubscription = async () => {
|
||||
if (!process.client || !user.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNativeApp()) {
|
||||
if (!nativePushToken.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
await useApi("/me/native-push-subscriptions/deactivate", {
|
||||
method: "POST",
|
||||
body: {
|
||||
token: nativePushToken.value
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const currentSubscription = await getCurrentBrowserSubscription();
|
||||
if (!currentSubscription) {
|
||||
return;
|
||||
}
|
||||
|
||||
const serializedSubscription = currentSubscription.toJSON();
|
||||
if (!serializedSubscription.endpoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
await useApi("/me/push-subscriptions/deactivate", {
|
||||
method: "POST",
|
||||
body: {
|
||||
endpoint: serializedSubscription.endpoint
|
||||
}
|
||||
});
|
||||
|
||||
await currentSubscription.unsubscribe().catch(() => undefined);
|
||||
};
|
||||
|
||||
const subscribeToBrowserPush = async () => {
|
||||
const registration = await registerServiceWorker();
|
||||
if (!registration || !("PushManager" in window)) {
|
||||
throw new Error("Push не поддерживается в этом браузере");
|
||||
}
|
||||
|
||||
const vapid = await useApi<{ publicKey: string }>("/vapid-public-key");
|
||||
if (!vapid.publicKey || vapid.publicKey.startsWith("replace_")) {
|
||||
throw new Error("Push-уведомления еще не настроены на сервере");
|
||||
}
|
||||
|
||||
const subscription = await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: urlBase64ToUint8Array(vapid.publicKey)
|
||||
});
|
||||
|
||||
await syncWebSubscription(subscription);
|
||||
return subscription;
|
||||
};
|
||||
|
||||
const subscribeToNativePush = async () => {
|
||||
await initializeNativePush();
|
||||
|
||||
let permission = await getNativePermissionState();
|
||||
if (permission === "default") {
|
||||
permission = await requestNativePermission();
|
||||
}
|
||||
|
||||
if (permission !== "granted") {
|
||||
throw new Error("Разрешение на уведомления не выдано");
|
||||
}
|
||||
|
||||
const device = await Device.getId();
|
||||
nativePushDeviceId.value = device.identifier;
|
||||
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
let finished = false;
|
||||
|
||||
const finalize = (callback: () => void) => {
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
finished = true;
|
||||
callback();
|
||||
};
|
||||
|
||||
PushNotifications.addListener("registration", async (registrationToken: Token) => {
|
||||
finalize(() => {
|
||||
nativePushToken.value = registrationToken.value;
|
||||
void syncNativeSubscription(registrationToken.value);
|
||||
resolve(registrationToken.value);
|
||||
});
|
||||
}).catch(reject);
|
||||
|
||||
PushNotifications.addListener("registrationError", (error) => {
|
||||
finalize(() => {
|
||||
reject(new Error(error.error || "Не удалось зарегистрировать устройство для push"));
|
||||
});
|
||||
}).catch(reject);
|
||||
|
||||
PushNotifications.register().catch((error) => {
|
||||
finalize(() => {
|
||||
reject(error instanceof Error ? error : new Error("Не удалось зарегистрировать устройство для push"));
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const subscribeToPush = () => {
|
||||
if (isNativeApp()) {
|
||||
return subscribeToNativePush();
|
||||
}
|
||||
|
||||
return subscribeToBrowserPush();
|
||||
};
|
||||
|
||||
const ensurePushSubscription = async () => {
|
||||
const permission = await getPermissionState();
|
||||
if (permission === "unsupported") {
|
||||
throw new Error("Push не поддерживается на этом устройстве");
|
||||
}
|
||||
|
||||
let finalPermission = permission;
|
||||
if (finalPermission === "default") {
|
||||
finalPermission = await requestPermission();
|
||||
}
|
||||
|
||||
if (finalPermission !== "granted") {
|
||||
throw new Error("Разрешение на уведомления не выдано");
|
||||
}
|
||||
|
||||
if (isNativeApp()) {
|
||||
if (nativePushToken.value) {
|
||||
await syncNativeSubscription(nativePushToken.value);
|
||||
return nativePushToken.value;
|
||||
}
|
||||
|
||||
return subscribeToNativePush();
|
||||
}
|
||||
|
||||
const currentSubscription = await getCurrentBrowserSubscription();
|
||||
if (currentSubscription) {
|
||||
await syncWebSubscription(currentSubscription);
|
||||
return currentSubscription;
|
||||
}
|
||||
|
||||
return subscribeToBrowserPush();
|
||||
};
|
||||
|
||||
const getPushStatus = async () => {
|
||||
const permission = await getPermissionState();
|
||||
const isMobile = isMobileDevice();
|
||||
const isIos = isIosDevice();
|
||||
const isStandalone = isStandaloneMode();
|
||||
const installRequired = !isNativeApp() && isMobile && !isStandalone;
|
||||
|
||||
if (permission === "unsupported") {
|
||||
return {
|
||||
supported: false,
|
||||
permission,
|
||||
isMobile,
|
||||
isIos,
|
||||
isStandalone,
|
||||
installRequired,
|
||||
canInstall: canTriggerInstallPrompt(),
|
||||
hasBrowserSubscription: false,
|
||||
hasServerSubscription: false
|
||||
};
|
||||
}
|
||||
|
||||
let hasServerSubscription = false;
|
||||
|
||||
try {
|
||||
if (isNativeApp()) {
|
||||
if (user.value) {
|
||||
const serverSubscriptions = await useApi<PushSubscriptionsResponse>("/me/native-push-subscriptions");
|
||||
hasServerSubscription = serverSubscriptions.hasActiveSubscription;
|
||||
} else {
|
||||
const clientId = await ensureAnonymousClientId();
|
||||
const serverSubscriptions = await useApi<PushSubscriptionsResponse>(`/public/native-push-subscriptions/${clientId}`);
|
||||
hasServerSubscription = serverSubscriptions.hasActiveSubscription;
|
||||
}
|
||||
} else if (user.value) {
|
||||
const serverSubscriptions = await useApi<PushSubscriptionsResponse>("/me/push-subscriptions");
|
||||
hasServerSubscription = serverSubscriptions.hasActiveSubscription;
|
||||
} else {
|
||||
const clientId = await ensureAnonymousClientId();
|
||||
const serverSubscriptions = await useApi<PushSubscriptionsResponse>(`/public/push-subscriptions/${clientId}`);
|
||||
hasServerSubscription = serverSubscriptions.hasActiveSubscription;
|
||||
}
|
||||
} catch {
|
||||
hasServerSubscription = false;
|
||||
}
|
||||
|
||||
return {
|
||||
supported: true,
|
||||
permission,
|
||||
isMobile,
|
||||
isIos,
|
||||
isStandalone,
|
||||
installRequired,
|
||||
canInstall: canTriggerInstallPrompt(),
|
||||
hasBrowserSubscription: isNativeApp()
|
||||
? Boolean(nativePushToken.value)
|
||||
: Boolean(await getCurrentBrowserSubscription()),
|
||||
hasServerSubscription
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
registerServiceWorker,
|
||||
syncServiceWorkerContext,
|
||||
syncNativeSubscription,
|
||||
initializeNativePush,
|
||||
initializeWebPushRouting,
|
||||
isNativeApp,
|
||||
isMobileDevice,
|
||||
isIosDevice,
|
||||
isStandaloneMode,
|
||||
getPermissionState,
|
||||
requestPermission,
|
||||
getCurrentSubscription,
|
||||
deactivateCurrentPushSubscription,
|
||||
getPushStatus,
|
||||
subscribeToPush,
|
||||
ensurePushSubscription,
|
||||
claimAnonymousPushSubscriptions,
|
||||
consumePendingPushRoute,
|
||||
setInstallPromptEvent,
|
||||
canTriggerInstallPrompt,
|
||||
triggerInstallPrompt,
|
||||
ensureAnonymousClientId
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user