A composable for accessing real-time statistics about active SSE/WebSocket connections and their version distribution.
connectionTracking: true in your module config and only works with sse or ws update strategies. It does not support Pusher/Ably adapters.skew:authorize-stats hook to authorize specific connections.Enable connection tracking in your Nuxt config:
export default defineNuxtConfig({
skewProtection: {
connectionTracking: true,
routeTracking: true, // optional: track current routes
ipTracking: true, // optional: track IP addresses
}
})
Stats are only sent to authorized connections. Implement the skew:authorize-stats hook in a server plugin:
export default defineNitroPlugin((nitroApp) => {
nitroApp.hooks.hook('skew:authorize-stats', async ({ event, authorize }) => {
// With nuxt-auth-utils
const session = await getUserSession(event)
if (session.user?.role === 'admin') {
authorize()
}
})
})
const { total, versions, routes, connections, yourId, authorized } = useActiveConnections()
totalComputedRef<number>The total number of active connections across all versions.
const { total } = useActiveConnections()
console.log(total.value) // 42
versionsComputedRef<Record<string, number>>A map of build IDs to connection counts, showing how many users are on each version.
const { versions } = useActiveConnections()
console.log(versions.value)
// { "abc123": 35, "def456": 7 }
routesComputedRef<Record<string, number>>A map of route paths to connection counts. Requires routeTracking: true in config.
const { routes } = useActiveConnections()
console.log(routes.value)
// { "/": 20, "/products": 15, "/checkout": 7 }
connectionsComputedRef<ConnectionInfo[]>Individual connection details including ID, version, route, and IP (when ipTracking: true).
const { connections } = useActiveConnections()
console.log(connections.value)
// [{ id: "abc123", version: "def456", route: "/", ip: "192.168.1.1" }]
yourIdComputedRef<string | undefined>Your connection ID to identify yourself in the connections list.
const { connections, yourId } = useActiveConnections()
const isMe = conn => conn.id === yourId.value
authorizedComputedRef<boolean | null>Whether the current connection is authorized to receive stats. Returns null while pending, false if denied, true if authorized.
const { authorized, total } = useActiveConnections()
// total.value will be 0 until authorized becomes true
interface ConnectionInfo {
id: string
version: string
route: string
ip?: string // requires ipTracking: true
}
interface ConnectionStats {
total: number
versions: Record<string, number>
routes: Record<string, number>
connections: ConnectionInfo[]
yourId?: string
}
<script setup>
const { total, versions, routes, authorized } = useActiveConnections()
const versionList = computed(() => {
return Object.entries(versions.value)
.sort(([, a], [, b]) => b - a)
.map(([id, count]) => ({
id: id.slice(0, 8),
count,
percentage: Math.round((count / total.value) * 100)
}))
})
const topRoutes = computed(() => {
return Object.entries(routes.value)
.sort(([, a], [, b]) => b - a)
.slice(0, 5)
})
</script>
<template>
<div v-if="authorized" class="connections-widget">
<h3>Active Users: {{ total }}</h3>
<div class="versions">
<h4>By Version</h4>
<ul>
<li v-for="v in versionList" :key="v.id">
{{ v.id }}: {{ v.count }} ({{ v.percentage }}%)
</li>
</ul>
</div>
<div class="routes">
<h4>Top Pages</h4>
<ul>
<li v-for="[route, count] in topRoutes" :key="route">
{{ route }}: {{ count }}
</li>
</ul>
</div>
</div>
<div v-else>
Not authorized for live stats
</div>
</template>
const { total, versions } = useActiveConnections()
const usersOnOldVersions = computed(() => {
const currentBuildId = useRuntimeConfig().app.buildId
return Object.entries(versions.value)
.filter(([id]) => id !== currentBuildId)
.reduce((sum, [, count]) => sum + count, 0)
})
watch(usersOnOldVersions, (count) => {
if (count === 0) {
console.log('All users migrated to latest version!')
}
})
<script setup>
const { total, versions } = useActiveConnections()
const buildId = useRuntimeConfig().app.buildId
const stats = computed(() => {
const current = versions.value[buildId] || 0
const outdated = total.value - current
return {
current,
outdated,
percentCurrent: total.value ? Math.round((current / total.value) * 100) : 0
}
})
</script>
<template>
<div class="stats">
<span>{{ stats.current }} current</span>
<span>{{ stats.outdated }} outdated</span>
<span>{{ stats.percentCurrent }}% up to date</span>
</div>
</template>
updateStrategy: 'sse' or updateStrategy: 'ws'