valaxy-addon-vercount
Version:
A Vercount API based counting plugin for Valaxy, serving as an alternative to Busuanzi counting
137 lines (115 loc) • 3.82 kB
text/typescript
/**
* -----------------------------------------------------------------------------
* A real-time website visit statistics tool using WebRTC and Peer.js.
* Modified from the original oneline.js.
* -----------------------------------------------------------------------------
*
* Source: https://github.com/ezshine/oneline.js
* License: MIT - see LICENSE for details.
*
* -----------------------------------------------------------------------------
*/
import type { DataConnection } from 'peerjs'
import Peer from 'peerjs'
import { v5 as uuidv5 } from 'uuid'
export type EventCallback = (event: { detail: any }) => void
export class Oneline {
#peerId: string = ''
#visitorCount: number = 1
#peer: Peer | null = null
#connections: Map<string, DataConnection> = new Map()
#eventListeners: Record<string, EventCallback[]> = {}
constructor(url: string) {
const peerId = uuidv5(url, uuidv5.URL)
this.init(peerId)
}
async init(peerId: string): Promise<void> {
this.#peerId = peerId
this.#peer = new Peer()
this.#peer.on('open', (id: string) => {
if (id !== this.#peerId) {
this.#connectToPeer(this.#peerId)
}
this.#updateVisitorCount()
})
this.#peer.on('connection', (conn: DataConnection) => {
this.#setupConnection(conn)
})
this.#peer.on('error', (err: any) => {
if (err.type === 'peer-unavailable') {
this.#peer?.destroy()
this.#peer = new Peer(this.#peerId)
this.#peer.on('open', () => {
this.#updateVisitorCount()
})
this.#peer.on('connection', (conn: DataConnection) => {
this.#setupConnection(conn)
})
}
else {
console.error('PeerJS error:', err)
}
})
window.addEventListener('beforeunload', () => this.#cleanup())
}
on(eventName: string, callback: EventCallback): void {
if (!this.#eventListeners[eventName]) {
this.#eventListeners[eventName] = []
}
this.#eventListeners[eventName].push(callback)
}
#triggerEvent(eventName: string, detail: any): void {
if (this.#eventListeners[eventName]) {
this.#eventListeners[eventName].forEach((callback) => {
callback({ detail })
})
}
}
#updateVisitorCount(): void {
this.#triggerEvent('OnelineUpdate', {
count: this.#visitorCount,
})
}
#broadcastVisitorCount(): void {
this.#connections.forEach(conn => conn.send({ type: 'count', count: this.#visitorCount }))
}
#setupConnection(conn: DataConnection): void {
conn.on('open', () => {
this.#connections.set(conn.peer, conn)
this.#visitorCount = this.#connections.size + 1
this.#updateVisitorCount()
this.#broadcastVisitorCount()
conn.on('data', (data: any) => {
if (data.type === 'count') {
this.#visitorCount = Math.max(data.count, this.#visitorCount)
this.#updateVisitorCount()
}
})
conn.on('close', () => {
this.#connections.delete(conn.peer)
this.#visitorCount = this.#connections.size + 1
this.#updateVisitorCount()
this.#broadcastVisitorCount()
})
})
}
#connectToPeer(peerId: string): void {
if (!this.#connections.has(peerId)) {
const conn = this.#peer?.connect(peerId)
if (conn) {
this.#setupConnection(conn)
}
}
}
#cleanup(): void {
this.#connections.forEach(conn => conn.close())
this.#connections.clear()
this.#peer?.destroy()
this.#peer = null
window.removeEventListener('beforeunload', this.#cleanup.bind(this))
}
destroy(): void {
this.#cleanup()
}
}
export default Oneline