UNPKG

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
/** * ----------------------------------------------------------------------------- * 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