From 6ab4ffc092bd6e9f9923b154b4e5b3b13c2aae2f Mon Sep 17 00:00:00 2001 From: Arthur DANJOU Date: Sun, 2 Feb 2025 19:03:49 +0100 Subject: [PATCH] Refactor WebSocket handling and remove nuxt-visitors module Replaced nuxt-visitors module with a custom WebSocket implementation for location tracking. Removed redundant code and comments in `useVisitors` composable and introduced the `location.server` plugin to manage user location. Updated dependencies and configurations to reflect these changes, streamlining the approach. --- app/components/AppHeader.vue | 2 +- app/components/home/Globe.vue | 3 +- app/components/home/Stats.vue | 2 +- app/composables/visitors.ts | 47 +-------------- app/pages/index.vue | 2 - app/plugins/location.server.ts | 11 ++++ nuxt.config.ts | 5 +- package.json | 1 - pnpm-lock.yaml | 66 ++++++++------------- server/routes/{visitors.ts => locations.ts} | 15 +++-- 10 files changed, 53 insertions(+), 101 deletions(-) create mode 100644 app/plugins/location.server.ts rename server/routes/{visitors.ts => locations.ts} (52%) diff --git a/app/components/AppHeader.vue b/app/components/AppHeader.vue index 1190e0b..4e2fbce 100644 --- a/app/components/AppHeader.vue +++ b/app/components/AppHeader.vue @@ -57,7 +57,7 @@ async function toggleTheme() { } const { locale, setLocale, locales, t } = useI18n() -const currentLocale = computed(() => locales.filter(l => l.code === locale.value)[0]) +const currentLocale = computed(() => locales.value.filter(l => l.code === locale.value)[0]) const lang = ref(locale.value) watch(lang, () => changeLocale(lang.value)) diff --git a/app/components/home/Globe.vue b/app/components/home/Globe.vue index ac1d748..663357a 100644 --- a/app/components/home/Globe.vue +++ b/app/components/home/Globe.vue @@ -34,7 +34,7 @@ const DEFAULT_CONFIG: COBEOptions = { mapBrightness: 1, baseColor: [0.8, 0.8, 0.8], opacity: 0.7, - markerColor: [251 / 255, 100 / 255, 21 / 255], + markerColor: [160 / 255, 160 / 255, 160 / 255], glowColor: [1, 1, 1], markers: [], } @@ -90,7 +90,6 @@ function onRender(state: Record) { state.height = width.value * 2 state.markers = props.locations?.map(location => ({ location: [location.latitude, location.longitude], - // Set the size of the marker to 0.1 if it's the user's location, otherwise 0.05 size: props.myLocation?.latitude === location.latitude && props.myLocation?.longitude === location.longitude ? 0.1 : 0.05, })) } diff --git a/app/components/home/Stats.vue b/app/components/home/Stats.vue index 82675e7..9aa294b 100644 --- a/app/components/home/Stats.vue +++ b/app/components/home/Stats.vue @@ -2,7 +2,7 @@ import type { Stats } from '~~/types' const { locale, locales } = useI18n() -const currentLocale = computed(() => locales.find(l => l.code === locale.value)) +const currentLocale = computed(() => locales.value.find(l => l.code === locale.value)) const { data: stats } = await useFetch('/api/stats') const { t } = useI18n({ diff --git a/app/composables/visitors.ts b/app/composables/visitors.ts index 124e03b..790cd9e 100644 --- a/app/composables/visitors.ts +++ b/app/composables/visitors.ts @@ -1,25 +1,7 @@ import { useState } from '#imports' import { onBeforeUnmount, onMounted, ref } from 'vue' -/** - * Composable for tracking real-time website visitors count via WebSocket - * - * Features: - * - Real-time visitors count updates - * - Automatic WebSocket connection management - * - Connection status tracking - * - Error handling - * - Automatic cleanup on component unmount - * - * @returns {object} An object containing: - * - visitors: Ref - Current number of visitors - * - isLoading: Ref - Loading state indicator - * - error: Ref - Error message if any - * - isConnected: Ref - WebSocket connection status - * - reconnect: () => void - Function to manually reconnect - */ export function useVisitors() { - // State management const visitors = useState('visitors', () => 0) // Added default value const locations = ref>([]) const myLocation = useState('location', () => ({ @@ -32,23 +14,15 @@ export function useVisitors() { const isConnected = ref(false) const isMounted = ref(true) - // Constants const RECONNECTION_DELAY = 5000 // 5 seconds delay for reconnection const WS_NORMAL_CLOSURE = 1000 - /** - * Constructs the WebSocket URL based on the current protocol and host - * @returns {string} WebSocket URL - */ const getWebSocketUrl = (): string => { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:' const baseUrl = window.location.host.replace(/^(http|https):\/\//, '') - return `${protocol}//${baseUrl}/.nuxt-visitors/ws?latitude=${myLocation.value.latitude}&longitude=${myLocation.value.longitude}` + return `${protocol}//${baseUrl}/ws?latitude=${myLocation.value.latitude}&longitude=${myLocation.value.longitude}` } - /** - * Cleans up WebSocket connection and resets state - */ const cleanup = () => { if (wsRef.value) { wsRef.value.close() @@ -58,10 +32,6 @@ export function useVisitors() { isLoading.value = false } - /** - * Handles WebSocket messages - * @param {MessageEvent} event - WebSocket message event - */ const handleMessage = async (event: MessageEvent) => { if (!isMounted.value) return @@ -83,10 +53,6 @@ export function useVisitors() { } } - /** - * Handles WebSocket connection closure - * @param {CloseEvent} event - WebSocket close event - */ const handleClose = (event: CloseEvent) => { console.log('Visitors WebSocket closed:', event.code, event.reason) isConnected.value = false @@ -94,14 +60,11 @@ export function useVisitors() { if (isMounted.value && event.code !== WS_NORMAL_CLOSURE) { error.value = 'Connection lost' - // Attempt to reconnect after delay - setTimeout(() => reconnect(), RECONNECTION_DELAY) + // eslint-disable-next-line ts/no-use-before-define + setTimeout(reconnect, RECONNECTION_DELAY) } } - /** - * Initializes WebSocket connection - */ const initWebSocket = () => { if (!isMounted.value) return @@ -141,9 +104,6 @@ export function useVisitors() { } } - /** - * Manually triggers WebSocket reconnection - */ const reconnect = () => { if (!isMounted.value) return @@ -152,7 +112,6 @@ export function useVisitors() { initWebSocket() } - // Lifecycle hooks onMounted(() => { if (import.meta.client) { isMounted.value = true diff --git a/app/pages/index.vue b/app/pages/index.vue index c4071fd..a6c78f1 100644 --- a/app/pages/index.vue +++ b/app/pages/index.vue @@ -17,8 +17,6 @@ const { myLocation, locations } = useVisitors() - {{ locations }} - {{ myLocation }} { + const event = useRequestEvent() + console.log('event', event) + + useState('location', () => ({ + latitude: event?.context.cf?.latitude || Math.random() * 180 - 90, // default to random latitude (only in dev) + longitude: event?.context.cf?.longitude || Math.random() * 360 - 180, // default to random longitude (only in dev) + })) + + console.log(useState('location').value) +}) diff --git a/nuxt.config.ts b/nuxt.config.ts index 3d82ff6..adf8602 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -74,10 +74,7 @@ export default defineNuxtConfig({ }, }, - // Nuxt Visitors - visitors: { - locations: true, - }, + plugins: ['~/plugins/location.server'], // Nuxt Color Mode colorMode: { diff --git a/package.json b/package.json index 719d901..2de0787 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,6 @@ "drizzle-orm": "^0.33.0", "h3-zod": "^0.5.3", "nuxt": "^3.15.4", - "nuxt-visitors": "^1.1.2", "rehype-katex": "^7.0.1", "remark-math": "^6.0.0", "remark-parse": "^11.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 65ae638..92ff76c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,8 +24,8 @@ importers: specifier: ^3.2.0 version: 3.2.0(magicast@0.3.5)(rollup@4.32.1) '@nuxtjs/i18n': - specifier: 9.0.0-rc.2 - version: 9.0.0-rc.2(@vue/compiler-dom@3.5.13)(eslint@9.19.0(jiti@2.4.2))(magicast@0.3.5)(rollup@4.32.1)(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)) + specifier: 9.1.5 + version: 9.1.5(@vue/compiler-dom@3.5.13)(eslint@9.19.0(jiti@2.4.2))(magicast@0.3.5)(rollup@4.32.1)(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)) cobe: specifier: ^0.6.3 version: 0.6.3 @@ -38,9 +38,6 @@ importers: nuxt: specifier: ^3.15.4 version: 3.15.4(@libsql/client@0.5.6(encoding@0.1.13))(@parcel/watcher@2.4.1)(@types/node@22.12.0)(better-sqlite3@11.8.1)(db0@0.2.3(@libsql/client@0.5.6(encoding@0.1.13))(better-sqlite3@11.8.1)(drizzle-orm@0.33.0(@cloudflare/workers-types@4.20250124.3)(@libsql/client@0.5.6(encoding@0.1.13))(@opentelemetry/api@1.9.0)(better-sqlite3@11.8.1)(pg@8.13.1)))(drizzle-orm@0.33.0(@cloudflare/workers-types@4.20250124.3)(@libsql/client@0.5.6(encoding@0.1.13))(@opentelemetry/api@1.9.0)(better-sqlite3@11.8.1)(pg@8.13.1))(encoding@0.1.13)(eslint@9.19.0(jiti@2.4.2))(ioredis@5.4.1)(lightningcss@1.29.1)(magicast@0.3.5)(meow@9.0.0)(optionator@0.9.4)(rollup@4.32.1)(sass@1.77.6)(terser@5.31.6)(typescript@5.7.3)(vite@6.0.11(@types/node@22.12.0)(jiti@2.4.2)(lightningcss@1.29.1)(sass@1.77.6)(terser@5.31.6)(yaml@2.7.0))(vue-tsc@2.2.0(typescript@5.7.3))(yaml@2.7.0) - nuxt-visitors: - specifier: ^1.1.2 - version: 1.1.2(magicast@0.3.5)(rollup@4.32.1) rehype-katex: specifier: ^7.0.1 version: 7.0.1 @@ -1123,8 +1120,8 @@ packages: '@internationalized/number@3.6.0': resolution: {integrity: sha512-PtrRcJVy7nw++wn4W2OuePQQfTqDzfusSuY1QTtui4wa7r+rGVtR75pO8CyKvHvzyQYi3Q1uO5sY0AsB4e65Bw==} - '@intlify/bundle-utils@9.0.0': - resolution: {integrity: sha512-19dunbgM4wuCvi2xSai2PKhXkcKGjlbJhNWm9BCQWkUYcPmXwzptNWOE0O7OSrhNlEDxwpkHsJzZ/vLbCkpElw==} + '@intlify/bundle-utils@10.0.0': + resolution: {integrity: sha512-BR5yLOkF2dzrARTbAg7RGAIPcx9Aark7p1K/0O285F7rfzso9j2dsa+S4dA67clZ0rToZ10NSSTfbyUptVu7Bg==} engines: {node: '>= 18'} peerDependencies: petite-vue-i18n: '*' @@ -1167,8 +1164,8 @@ packages: resolution: {integrity: sha512-DvpNSxiMrFqYMaGSRDDnQgO/L0MqNH4KWw9CUx8LRHHIdWp08En9DpmSRNpauUOxKpHAhyJJxx92BHZk9J84EQ==} engines: {node: '>= 16'} - '@intlify/unplugin-vue-i18n@5.3.1': - resolution: {integrity: sha512-76huP8TpMOtBMLsYYIMLNbqMPXJ7+Q6xcjP6495h/pmbOQ7sw/DB8E0OFvDFeIZ2571a4ylzJnz+KMuYbAs1xA==} + '@intlify/unplugin-vue-i18n@6.0.3': + resolution: {integrity: sha512-9ZDjBlhUHtgjRl23TVcgfJttgu8cNepwVhWvOv3mUMRDAhjW0pur1mWKEUKr1I8PNwE4Gvv2IQ1xcl4RL0nG0g==} engines: {node: '>= 18'} peerDependencies: petite-vue-i18n: '*' @@ -1184,14 +1181,14 @@ packages: resolution: {integrity: sha512-8i3uRdAxCGzuHwfmHcVjeLQBtysQB2aXl/ojoagDut5/gY5lvWCQ2+cnl2TiqE/fXj/D8EhWG/SLKA7qz4a3QA==} engines: {node: '>= 18'} - '@intlify/vue-i18n-extensions@7.0.0': - resolution: {integrity: sha512-MtvfJnb4aklpCU5Q/dkWkBT/vGsp3qERiPIwtTq5lX4PCLHtUprAJZp8wQj5ZcwDaFCU7+yVMjYbeXpIf927cA==} + '@intlify/vue-i18n-extensions@8.0.0': + resolution: {integrity: sha512-w0+70CvTmuqbskWfzeYhn0IXxllr6mU+IeM2MU0M+j9OW64jkrvqY+pYFWrUnIIC9bEdij3NICruicwd5EgUuQ==} engines: {node: '>= 18'} peerDependencies: - '@intlify/shared': ^9.0.0 || ^10.0.0 + '@intlify/shared': ^9.0.0 || ^10.0.0 || ^11.0.0 '@vue/compiler-dom': ^3.0.0 vue: ^3.0.0 - vue-i18n: ^9.0.0 || ^10.0.0 + vue-i18n: ^9.0.0 || ^10.0.0 || ^11.0.0 peerDependenciesMeta: '@intlify/shared': optional: true @@ -1427,8 +1424,8 @@ packages: '@nuxtjs/google-fonts@3.2.0': resolution: {integrity: sha512-cGAjDJoeQ2jm6VJCo4AtSmKO6KjsbO9RSLj8q261fD0lMVNMZCxkCxBkg8L0/2Vfgp+5QBHWVXL71p1tiybJFw==} - '@nuxtjs/i18n@9.0.0-rc.2': - resolution: {integrity: sha512-OIxPyci9Wvi/Ewhtc/xZ0XTsqqlm0KRQoY0JrwthdaSVKgyFcKRkP70YLpm7JmgcRDkQk/VkcCUFLDJoo3MiZw==} + '@nuxtjs/i18n@9.1.5': + resolution: {integrity: sha512-dFbo3etm5xqG3vF4sLeVrR+wXVcxBszDCds5xtJBSESS7riJBtW83BujSMUnalbRxvGOLhJj+b6Qb8vj7Im/9Q==} engines: {node: ^14.16.0 || >=16.11.0} '@nuxtjs/mdc@0.13.2': @@ -4774,9 +4771,6 @@ packages: resolution: {integrity: sha512-iq7hbSnfp4Ff/PTMYBF8pYabTQuF3u7HVN66Kb3hOnrnaPEdXEn/q6HkAn5V8UjOVSgXYpvycM0wSnwyADYNVA==} hasBin: true - nuxt-visitors@1.1.2: - resolution: {integrity: sha512-gUSHTNhH0XDwo5/Op/+MrZe/inrLN++nuwLlMgwgciZmCQVrIbj8RXmtGAncVmeLKQim/n7gZlKWJbFASCq/rA==} - nuxt@3.15.4: resolution: {integrity: sha512-hSbZO4mR0uAMJtZPNTnCfiAtgleoOu28gvJcBNU7KQHgWnNXPjlWgwMczko2O4Tmnv9zIe/CQged+2HsPwl2ZA==} engines: {node: ^18.20.5 || ^20.9.0 || >=22.0.0} @@ -6976,7 +6970,7 @@ snapshots: '@babel/parser': 7.26.2 '@babel/template': 7.25.9 '@babel/types': 7.26.0 - debug: 4.3.7 + debug: 4.4.0(supports-color@9.4.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -7512,7 +7506,7 @@ snapshots: dependencies: '@swc/helpers': 0.5.15 - '@intlify/bundle-utils@9.0.0(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3)))': + '@intlify/bundle-utils@10.0.0(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3)))': dependencies: '@intlify/message-compiler': 11.0.0-rc.1 '@intlify/shared': 11.0.0-rc.1 @@ -7557,12 +7551,12 @@ snapshots: '@intlify/shared@11.1.0': {} - '@intlify/unplugin-vue-i18n@5.3.1(@vue/compiler-dom@3.5.13)(eslint@9.19.0(jiti@2.4.2))(rollup@4.32.1)(typescript@5.7.3)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))': + '@intlify/unplugin-vue-i18n@6.0.3(@vue/compiler-dom@3.5.13)(eslint@9.19.0(jiti@2.4.2))(rollup@4.32.1)(typescript@5.7.3)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))': dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@9.19.0(jiti@2.4.2)) - '@intlify/bundle-utils': 9.0.0(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3))) + '@intlify/bundle-utils': 10.0.0(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3))) '@intlify/shared': 11.1.0 - '@intlify/vue-i18n-extensions': 7.0.0(@intlify/shared@11.1.0)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3)) + '@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.1.0)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3)) '@rollup/pluginutils': 5.1.4(rollup@4.32.1) '@typescript-eslint/scope-manager': 8.22.0 '@typescript-eslint/typescript-estree': 8.22.0(typescript@5.7.3) @@ -7586,7 +7580,7 @@ snapshots: '@intlify/utils@0.13.0': {} - '@intlify/vue-i18n-extensions@7.0.0(@intlify/shared@11.1.0)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))': + '@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.1.0)(@vue/compiler-dom@3.5.13)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3))': dependencies: '@babel/parser': 7.26.7 optionalDependencies: @@ -8390,11 +8384,11 @@ snapshots: - rollup - supports-color - '@nuxtjs/i18n@9.0.0-rc.2(@vue/compiler-dom@3.5.13)(eslint@9.19.0(jiti@2.4.2))(magicast@0.3.5)(rollup@4.32.1)(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3))': + '@nuxtjs/i18n@9.1.5(@vue/compiler-dom@3.5.13)(eslint@9.19.0(jiti@2.4.2))(magicast@0.3.5)(rollup@4.32.1)(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3))': dependencies: '@intlify/h3': 0.6.1 '@intlify/shared': 10.0.5 - '@intlify/unplugin-vue-i18n': 5.3.1(@vue/compiler-dom@3.5.13)(eslint@9.19.0(jiti@2.4.2))(rollup@4.32.1)(typescript@5.7.3)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3)) + '@intlify/unplugin-vue-i18n': 6.0.3(@vue/compiler-dom@3.5.13)(eslint@9.19.0(jiti@2.4.2))(rollup@4.32.1)(typescript@5.7.3)(vue-i18n@10.0.5(vue@3.5.13(typescript@5.7.3)))(vue@3.5.13(typescript@5.7.3)) '@intlify/utils': 0.13.0 '@miyaneee/rollup-plugin-json5': 1.2.0(rollup@4.32.1) '@nuxt/kit': 3.15.4(magicast@0.3.5)(rollup@4.32.1) @@ -11942,14 +11936,14 @@ snapshots: dependencies: micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-types: 2.0.1 micromark-factory-label@2.0.0: dependencies: devlop: 1.1.0 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-types: 2.0.1 micromark-factory-space@2.0.0: dependencies: @@ -11963,17 +11957,17 @@ snapshots: micromark-factory-title@2.0.0: dependencies: - micromark-factory-space: 2.0.0 + micromark-factory-space: 2.0.1 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-types: 2.0.1 micromark-factory-whitespace@2.0.0: dependencies: - micromark-factory-space: 2.0.0 + micromark-factory-space: 2.0.1 micromark-util-character: 2.1.1 micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-types: 2.0.1 micromark-factory-whitespace@2.0.1: dependencies: @@ -12445,14 +12439,6 @@ snapshots: - rollup - supports-color - nuxt-visitors@1.1.2(magicast@0.3.5)(rollup@4.32.1): - dependencies: - '@nuxt/kit': 3.15.2(magicast@0.3.5)(rollup@4.32.1) - transitivePeerDependencies: - - magicast - - rollup - - supports-color - nuxt@3.15.4(@libsql/client@0.5.6(encoding@0.1.13))(@parcel/watcher@2.4.1)(@types/node@22.12.0)(better-sqlite3@11.8.1)(db0@0.2.3(@libsql/client@0.5.6(encoding@0.1.13))(better-sqlite3@11.8.1)(drizzle-orm@0.33.0(@cloudflare/workers-types@4.20250124.3)(@libsql/client@0.5.6(encoding@0.1.13))(@opentelemetry/api@1.9.0)(better-sqlite3@11.8.1)(pg@8.13.1)))(drizzle-orm@0.33.0(@cloudflare/workers-types@4.20250124.3)(@libsql/client@0.5.6(encoding@0.1.13))(@opentelemetry/api@1.9.0)(better-sqlite3@11.8.1)(pg@8.13.1))(encoding@0.1.13)(eslint@9.19.0(jiti@2.4.2))(ioredis@5.4.1)(lightningcss@1.29.1)(magicast@0.3.5)(meow@9.0.0)(optionator@0.9.4)(rollup@4.32.1)(sass@1.77.6)(terser@5.31.6)(typescript@5.7.3)(vite@6.0.11(@types/node@22.12.0)(jiti@2.4.2)(lightningcss@1.29.1)(sass@1.77.6)(terser@5.31.6)(yaml@2.7.0))(vue-tsc@2.2.0(typescript@5.7.3))(yaml@2.7.0): dependencies: '@nuxt/cli': 3.21.1(magicast@0.3.5) diff --git a/server/routes/visitors.ts b/server/routes/locations.ts similarity index 52% rename from server/routes/visitors.ts rename to server/routes/locations.ts index bd22a39..b1cb9d8 100644 --- a/server/routes/visitors.ts +++ b/server/routes/locations.ts @@ -1,17 +1,20 @@ +import type { Peer } from 'crossws' +import { defineWebSocketHandler } from 'h3' import { getQuery } from 'ufo' export default defineWebSocketHandler({ - open(peer) { + open(peer: Peer) { const locations = Array.from(peer.peers.values()).map(peer => getQuery(peer.websocket.url!)) - peer.subscribe('visitors') - peer.publish('visitors', JSON.stringify(locations)) + peer.subscribe('nuxt-visitors') + peer.publish('nuxt-visitors', JSON.stringify(locations)) peer.send(JSON.stringify(locations)) }, - close(peer) { - peer.unsubscribe('visitors') + + close(peer: Peer) { + peer.unsubscribe('nuxt-visitors') setTimeout(() => { const locations = Array.from(peer.peers.values()).map(peer => getQuery(peer.websocket.url!)) - peer.publish('visitors', JSON.stringify(locations)) + peer.publish('nuxt-visitors', JSON.stringify(locations)) }, 500) }, })