From 85af2da6ad3901a22b4f4aa5f7464d6679b448f8 Mon Sep 17 00:00:00 2001 From: lethdat Date: Sun, 8 Feb 2026 23:59:48 +0700 Subject: [PATCH] feat: Introduce TinyMqttClient interface and implementation, update auth store for MQTT connection management --- components.d.ts | 32 -------------------------------- src/lib/interface.ts | 4 ++++ src/lib/liteMqtt.ts | 4 +++- src/routes/index.ts | 9 --------- src/stores/auth.ts | 21 ++++++++++++++++++++- ssrPlugin.ts | 13 +++++++++++-- 6 files changed, 38 insertions(+), 45 deletions(-) create mode 100644 src/lib/interface.ts diff --git a/components.d.ts b/components.d.ts index cde6a65..ec3f2c2 100644 --- a/components.d.ts +++ b/components.d.ts @@ -17,9 +17,7 @@ declare module 'vue' { ArrowDownTray: typeof import('./src/components/icons/ArrowDownTray.vue')['default'] ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default'] Bell: typeof import('./src/components/icons/Bell.vue')['default'] - Button: typeof import('primevue/button')['default'] Chart: typeof import('./src/components/icons/Chart.vue')['default'] - Checkbox: typeof import('primevue/checkbox')['default'] CheckCircleIcon: typeof import('./src/components/icons/CheckCircleIcon.vue')['default'] CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default'] CheckMarkIcon: typeof import('./src/components/icons/CheckMarkIcon.vue')['default'] @@ -29,41 +27,27 @@ declare module 'vue' { DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default'] DashboardNav: typeof import('./src/components/DashboardNav.vue')['default'] EmptyState: typeof import('./src/components/dashboard/EmptyState.vue')['default'] - FloatLabel: typeof import('primevue/floatlabel')['default'] GlobalUploadIndicator: typeof import('./src/components/GlobalUploadIndicator.vue')['default'] HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default'] Home: typeof import('./src/components/icons/Home.vue')['default'] - IconField: typeof import('primevue/iconfield')['default'] InfoIcon: typeof import('./src/components/icons/InfoIcon.vue')['default'] - InputIcon: typeof import('primevue/inputicon')['default'] InputText: typeof import('primevue/inputtext')['default'] Layout: typeof import('./src/components/icons/Layout.vue')['default'] LinkIcon: typeof import('./src/components/icons/LinkIcon.vue')['default'] - Message: typeof import('primevue/message')['default'] NotificationDrawer: typeof import('./src/components/NotificationDrawer.vue')['default'] PageHeader: typeof import('./src/components/dashboard/PageHeader.vue')['default'] - Paginator: typeof import('primevue/paginator')['default'] PanelLeft: typeof import('./src/components/icons/PanelLeft.vue')['default'] - Password: typeof import('primevue/password')['default'] PencilIcon: typeof import('./src/components/icons/PencilIcon.vue')['default'] RootLayout: typeof import('./src/components/RootLayout.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] - Select: typeof import('primevue/select')['default'] SettingsIcon: typeof import('./src/components/icons/SettingsIcon.vue')['default'] - Skeleton: typeof import('primevue/skeleton')['default'] StatsCard: typeof import('./src/components/dashboard/StatsCard.vue')['default'] - Tag: typeof import('primevue/tag')['default'] TestIcon: typeof import('./src/components/icons/TestIcon.vue')['default'] TrashIcon: typeof import('./src/components/icons/TrashIcon.vue')['default'] Upload: typeof import('./src/components/icons/Upload.vue')['default'] Video: typeof import('./src/components/icons/Video.vue')['default'] - VideoEditForm: typeof import('./src/components/video/VideoEditForm.vue')['default'] - VideoHeader: typeof import('./src/components/video/VideoHeader.vue')['default'] VideoIcon: typeof import('./src/components/icons/VideoIcon.vue')['default'] - VideoInfoPanel: typeof import('./src/components/video/VideoInfoPanel.vue')['default'] - VideoPlayer: typeof import('./src/components/video/VideoPlayer.vue')['default'] - VideoSkeleton: typeof import('./src/components/video/VideoSkeleton.vue')['default'] VueHead: typeof import('./src/components/VueHead.tsx')['default'] XCircleIcon: typeof import('./src/components/icons/XCircleIcon.vue')['default'] } @@ -76,9 +60,7 @@ declare global { const ArrowDownTray: typeof import('./src/components/icons/ArrowDownTray.vue')['default'] const ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default'] const Bell: typeof import('./src/components/icons/Bell.vue')['default'] - const Button: typeof import('primevue/button')['default'] const Chart: typeof import('./src/components/icons/Chart.vue')['default'] - const Checkbox: typeof import('primevue/checkbox')['default'] const CheckCircleIcon: typeof import('./src/components/icons/CheckCircleIcon.vue')['default'] const CheckIcon: typeof import('./src/components/icons/CheckIcon.vue')['default'] const CheckMarkIcon: typeof import('./src/components/icons/CheckMarkIcon.vue')['default'] @@ -88,41 +70,27 @@ declare global { const DashboardLayout: typeof import('./src/components/DashboardLayout.vue')['default'] const DashboardNav: typeof import('./src/components/DashboardNav.vue')['default'] const EmptyState: typeof import('./src/components/dashboard/EmptyState.vue')['default'] - const FloatLabel: typeof import('primevue/floatlabel')['default'] const GlobalUploadIndicator: typeof import('./src/components/GlobalUploadIndicator.vue')['default'] const HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default'] const Home: typeof import('./src/components/icons/Home.vue')['default'] - const IconField: typeof import('primevue/iconfield')['default'] const InfoIcon: typeof import('./src/components/icons/InfoIcon.vue')['default'] - const InputIcon: typeof import('primevue/inputicon')['default'] const InputText: typeof import('primevue/inputtext')['default'] const Layout: typeof import('./src/components/icons/Layout.vue')['default'] const LinkIcon: typeof import('./src/components/icons/LinkIcon.vue')['default'] - const Message: typeof import('primevue/message')['default'] const NotificationDrawer: typeof import('./src/components/NotificationDrawer.vue')['default'] const PageHeader: typeof import('./src/components/dashboard/PageHeader.vue')['default'] - const Paginator: typeof import('primevue/paginator')['default'] const PanelLeft: typeof import('./src/components/icons/PanelLeft.vue')['default'] - const Password: typeof import('primevue/password')['default'] const PencilIcon: typeof import('./src/components/icons/PencilIcon.vue')['default'] const RootLayout: typeof import('./src/components/RootLayout.vue')['default'] const RouterLink: typeof import('vue-router')['RouterLink'] const RouterView: typeof import('vue-router')['RouterView'] - const Select: typeof import('primevue/select')['default'] const SettingsIcon: typeof import('./src/components/icons/SettingsIcon.vue')['default'] - const Skeleton: typeof import('primevue/skeleton')['default'] const StatsCard: typeof import('./src/components/dashboard/StatsCard.vue')['default'] - const Tag: typeof import('primevue/tag')['default'] const TestIcon: typeof import('./src/components/icons/TestIcon.vue')['default'] const TrashIcon: typeof import('./src/components/icons/TrashIcon.vue')['default'] const Upload: typeof import('./src/components/icons/Upload.vue')['default'] const Video: typeof import('./src/components/icons/Video.vue')['default'] - const VideoEditForm: typeof import('./src/components/video/VideoEditForm.vue')['default'] - const VideoHeader: typeof import('./src/components/video/VideoHeader.vue')['default'] const VideoIcon: typeof import('./src/components/icons/VideoIcon.vue')['default'] - const VideoInfoPanel: typeof import('./src/components/video/VideoInfoPanel.vue')['default'] - const VideoPlayer: typeof import('./src/components/video/VideoPlayer.vue')['default'] - const VideoSkeleton: typeof import('./src/components/video/VideoSkeleton.vue')['default'] const VueHead: typeof import('./src/components/VueHead.tsx')['default'] const XCircleIcon: typeof import('./src/components/icons/XCircleIcon.vue')['default'] } \ No newline at end of file diff --git a/src/lib/interface.ts b/src/lib/interface.ts new file mode 100644 index 0000000..91ec326 --- /dev/null +++ b/src/lib/interface.ts @@ -0,0 +1,4 @@ +export interface ITinyMqttClient { + connect(): void; + disconnect(): void; +} \ No newline at end of file diff --git a/src/lib/liteMqtt.ts b/src/lib/liteMqtt.ts index 9bcf41f..f793c56 100644 --- a/src/lib/liteMqtt.ts +++ b/src/lib/liteMqtt.ts @@ -1,5 +1,7 @@ +import { ITinyMqttClient } from "./interface"; + export type MessageCallback = (topic: string, payload: string) => void; -export class TinyMqttClient { +export class TinyMqttClient implements ITinyMqttClient { private ws: WebSocket | null = null; private encoder = new TextEncoder(); private decoder = new TextDecoder(); diff --git a/src/routes/index.ts b/src/routes/index.ts index 34d2ba2..1809d4f 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -195,20 +195,11 @@ const createAppRouter = () => { router.beforeEach((to, from, next) => { const auth = useAuthStore(); const head = inject(headSymbol); - let client: any; (head as any).push(to.meta.head || {}); if (to.matched.some((record) => record.meta.requiresAuth)) { if (!auth.user) { - if(client?.disconnect) (client as any)?.disconnect(); next({ name: "login" }); } else { - client = new TinyMqttClient( - 'wss://broker.emqx.io:8084/mqtt', - [['ecos1231231'+auth.user.id+'#'].join("/")], - (topic, msg) => console.log(`Tín hiệu nhận được [${topic}]:`, msg) - ); - - client.connect(); next(); } } else { diff --git a/src/stores/auth.ts b/src/stores/auth.ts index 7e3f334..3360d3c 100644 --- a/src/stores/auth.ts +++ b/src/stores/auth.ts @@ -2,6 +2,7 @@ import { defineStore } from 'pinia'; import { useRouter } from 'vue-router'; import { ref } from 'vue'; import { client, ResponseResponse, type ModelUser } from '@/api/client'; +import { TinyMqttClient } from '@/lib/liteMqtt'; export const useAuthStore = defineStore('auth', () => { const user = ref(null); @@ -9,7 +10,25 @@ export const useAuthStore = defineStore('auth', () => { const loading = ref(false); const error = ref(null); const initialized = ref(false); - + + watch(user, (newUser) => { + if (import.meta.env.SSR) return; + let client: TinyMqttClient | undefined; + if (newUser?.id) { + client = new TinyMqttClient( + // 'wss://broker.emqx.io:8084/mqtt', + 'wss://mqtt-dashboard.com:8884/mqtt', + [['ecos1231231',newUser.id,'#'].join("/")], + (topic, msg) => console.log(`Tín hiệu nhận được [${topic}]:`, msg) + ); + client.connect(); + // client.auth.clearToken(); + } + else { + if(client?.disconnect) client.disconnect(); + client = undefined; + } + }, { deep: true }); // Initial check for session could go here if there was a /me endpoint or token check async function init() { if (initialized.value) return; diff --git a/ssrPlugin.ts b/ssrPlugin.ts index 21dce38..26394be 100644 --- a/ssrPlugin.ts +++ b/ssrPlugin.ts @@ -109,14 +109,23 @@ export default function ssrPlugin(): Plugin[] { config.define = config.define || {}; }, resolveId(id, importer, options) { - if (!id.startsWith('@httpClientAdapter')) return - + if (!['@httpClientAdapter', '@liteMqtt'].includes(id)) return + switch (id) { + case '@httpClientAdapter': return path.resolve( __dirname, options?.ssr ? "./src/api/httpClientAdapter.server.ts" : "./src/api/httpClientAdapter.client.ts" ); + case '@liteMqtt': + return path.resolve( + __dirname, + options?.ssr + ? "./src/lib/liteMqtt.server.ts" + : "./src/lib/liteMqtt.ts" + ); + } }, async configResolved(config) { const viteConfig = config as any;