| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- import { defineStore } from "pinia";
- import { ref } from "vue";
- import { getCurrentUser } from "@/lib/api";
- import { toast } from "vue-sonner";
- export const useAuthStore = defineStore("auth", () => {
- const user = ref<any>(null);
- const isLoading = ref(true);
- const showCompleteProfile = ref(false);
- let initialized = false;
- const wsAuthFailed = ref(false);
- async function refreshUser() {
- try {
- const userData = await getCurrentUser();
- user.value = userData;
- wsAuthFailed.value = false; // Reset on successful auth
- showCompleteProfile.value = !!(
- userData && (!userData.phone || !userData.shipping_address)
- );
- } catch {
- user.value = null;
- wsAuthFailed.value = true;
- showCompleteProfile.value = false;
- stopPing();
- } finally {
- isLoading.value = false;
- if (user.value && !pingInterval && !wsAuthFailed.value) startPing();
- }
- }
- let pingInterval: number | null = null;
- const unreadMessagesCount = ref(0);
- let globalWs: WebSocket | null = null;
- let reconnectTimer: number | null = null;
- const WS_BASE_URL = import.meta.env.VITE_WS_URL || "ws://localhost:8000";
- // ── Audio ──────────────────────────────────────────────────────────────────
- // Single shared AudioContext, created lazily on first user gesture.
- let sharedAudioCtx: AudioContext | null = null;
- function getAudioCtx(): AudioContext | null {
- const AudioCtx = window.AudioContext || (window as any).webkitAudioContext;
- if (!AudioCtx) return null;
- if (!sharedAudioCtx) sharedAudioCtx = new AudioCtx();
- return sharedAudioCtx;
- }
- // Unlock AudioContext as soon as the user interacts with the page
- function unlockAudio() {
- const ctx = getAudioCtx();
- if (ctx && ctx.state === 'suspended') ctx.resume().catch(() => {});
- }
- window.addEventListener('click', unlockAudio, { passive: true });
- window.addEventListener('keydown', unlockAudio, { passive: true });
- window.addEventListener('touchstart', unlockAudio, { passive: true });
- async function playUpdateSound() {
- try {
- const ctx = getAudioCtx();
- if (!ctx) return;
- if (ctx.state === 'suspended') await ctx.resume();
- const osc = ctx.createOscillator();
- const gainNode = ctx.createGain();
- osc.type = 'sine';
- osc.frequency.setValueAtTime(180, ctx.currentTime);
- osc.frequency.exponentialRampToValueAtTime(140, ctx.currentTime + 0.3);
- gainNode.gain.setValueAtTime(0, ctx.currentTime);
- gainNode.gain.linearRampToValueAtTime(0.15, ctx.currentTime + 0.05);
- gainNode.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.35);
- osc.connect(gainNode);
- gainNode.connect(ctx.destination);
- osc.start();
- osc.stop(ctx.currentTime + 0.35);
- } catch (e) {
- console.warn("Audio update sound error", e);
- }
- }
- async function playNotificationSound() {
- try {
- const ctx = getAudioCtx();
- if (!ctx) return;
- if (ctx.state === 'suspended') await ctx.resume();
- const osc = ctx.createOscillator();
- const gainNode = ctx.createGain();
-
- osc.type = 'sine';
- osc.frequency.setValueAtTime(880, ctx.currentTime); // A5
- osc.frequency.exponentialRampToValueAtTime(1760, ctx.currentTime + 0.1); // Up to A6
-
- gainNode.gain.setValueAtTime(0, ctx.currentTime);
- gainNode.gain.linearRampToValueAtTime(0.1, ctx.currentTime + 0.05);
- gainNode.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.2);
-
- osc.connect(gainNode);
- gainNode.connect(ctx.destination);
- osc.start();
- osc.stop(ctx.currentTime + 0.2);
- } catch (e) {
- console.warn("Audio notification sound error", e);
- }
- }
- async function playChatSound() {
- try {
- const ctx = getAudioCtx();
- if (!ctx) return;
- if (ctx.state === 'suspended') await ctx.resume();
- const osc = ctx.createOscillator();
- const gainNode = ctx.createGain();
-
- osc.type = "sine";
- osc.frequency.setValueAtTime(880, ctx.currentTime);
- osc.frequency.exponentialRampToValueAtTime(440, ctx.currentTime + 0.1);
-
- gainNode.gain.setValueAtTime(0, ctx.currentTime);
- gainNode.gain.linearRampToValueAtTime(0.3, ctx.currentTime + 0.05);
- gainNode.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.3);
-
- osc.connect(gainNode);
- gainNode.connect(ctx.destination);
-
- osc.start();
- osc.stop(ctx.currentTime + 0.3);
- } catch (e) {
- console.warn("Audio chat sound error", e);
- }
- }
- // ───────────────────────────────────────────────────────────────────────────
- function startPing() {
- stopPing(); // Ensure fresh start
- const token = localStorage.getItem("token");
- if (!token || wsAuthFailed.value) return;
- globalWs = new WebSocket(`${WS_BASE_URL}/global?token=${encodeURIComponent(token)}`);
- globalWs.onopen = () => {
- // Send ping every 25 seconds to keep connection alive and update online status in Redis
- pingInterval = window.setInterval(() => {
- if (globalWs && globalWs.readyState === WebSocket.OPEN) {
- globalWs.send("ping");
- }
- }, 25000);
- };
- globalWs.onmessage = (event) => {
- try {
- const msg = JSON.parse(event.data);
- if (msg.type === "unread_count" && msg.count !== undefined) {
- if (msg.count > unreadMessagesCount.value) {
- playNotificationSound();
- }
- unreadMessagesCount.value = msg.count;
- } else if (msg.type === "new_chat_message") {
- toast(`Message for Order #${msg.order_id}`, {
- description: msg.text,
- action: {
- label: "View",
- onClick: () => {
- window.location.href = `/admin?order=${msg.order_id}`;
- }
- }
- });
- } else if (msg.type === "account_suspended") {
- toast.error("Your account has been suspended by an administrator.", { duration: 10000 });
- logout();
- } else if (msg.type === "order_read") {
- window.dispatchEvent(new CustomEvent("radionica:order_read", { detail: { order_id: msg.order_id } }));
- } else if (msg.type === "order_updated") {
- playUpdateSound();
- window.dispatchEvent(new CustomEvent("radionica:order_updated", { detail: { order_id: msg.order_id } }));
- }
- } catch (e) {
- console.error("WS Parse error", e);
- }
- };
- globalWs.onclose = (event) => {
- if (pingInterval) clearInterval(pingInterval);
-
- // 4001: Auth fail, 4003: Suspended, 1008: Policy
- if (event.code === 4001 || event.code === 1008 || event.code === 4003) {
- console.warn(`WS Connection terminated (code ${event.code}).`);
- wsAuthFailed.value = true;
- if (event.code === 4003) {
- user.value = null; // Instant clear
- localStorage.removeItem("token");
- }
- return;
- }
- if (user.value && !wsAuthFailed.value) { // If still logged in and no auth error, try reconnecting
- reconnectTimer = window.setTimeout(startPing, 5000);
- }
- };
- globalWs.onerror = () => {
- // On error, the close event will follow
- console.error("WS Connection error occurred");
- };
- }
- function stopPing() {
- if (pingInterval) {
- clearInterval(pingInterval);
- pingInterval = null;
- }
- if (reconnectTimer) {
- clearTimeout(reconnectTimer);
- reconnectTimer = null;
- }
- if (globalWs) {
- globalWs.onclose = null; // Disable auto reconnect
- globalWs.onerror = null;
- globalWs.close();
- globalWs = null;
- }
- }
- let initPromise: Promise<void> | null = null;
- function init(): Promise<void> {
- if (!initPromise) {
- initPromise = refreshUser();
- }
- return initPromise;
- }
- function setUser(u: any) {
- user.value = u;
- wsAuthFailed.value = false;
- if (u && !pingInterval) startPing();
- }
- function onProfileComplete() {
- showCompleteProfile.value = false;
- refreshUser();
- }
- async function logout() {
- const { logoutUser } = await import("@/lib/api");
- try {
- await logoutUser();
- } catch (e) {
- console.error("Logout API call failed, continuing local cleanup", e);
- }
- localStorage.removeItem("token");
- user.value = null;
- unreadMessagesCount.value = 0;
- stopPing();
- }
- async function refreshUnreadCount() {
- // Left empty or removed, as WS handles updates now.
- // If you need manual force, you could re-trigger connect or add an endpoint,
- // but the WS pushes update automatically.
- }
- return {
- user,
- isLoading,
- showCompleteProfile,
- unreadMessagesCount,
- init,
- setUser,
- refreshUser,
- onProfileComplete,
- logout,
- refreshUnreadCount,
- playChatSound
- };
- });
|