163 lines
4 KiB
JavaScript
163 lines
4 KiB
JavaScript
"use strict"
|
|
|
|
/* global document, window */
|
|
const location = document.location
|
|
const performance = window.performance
|
|
|
|
import config from "./config.js"
|
|
|
|
export function isString(x) {
|
|
return typeof x === "string"
|
|
}
|
|
|
|
export function isEmpty(x) {
|
|
return (Array.isArray(x) ? x : Object.keys(x)).length === 0
|
|
}
|
|
|
|
export const q = (selector, root = document) => root.querySelector(selector)
|
|
export const qs = (selector, root = document) => root.querySelectorAll(selector)
|
|
|
|
const _evlisteners = {}
|
|
|
|
/**
|
|
* @param {String} event [description]
|
|
* @param {Function} cb [description]
|
|
* @param {?{once: bool, target: Element = document}} options
|
|
*/
|
|
export function on(event, cb, options = {}) {
|
|
const target = options.target || document
|
|
if ((_evlisteners[target] ?? {})[event]) {
|
|
console.warn(`Replacing previous event listener for '${event}' on target:`, target)
|
|
off(event, target)
|
|
}
|
|
target.addEventListener(event, cb, { once: !!options.once })
|
|
if (!_evlisteners[target]) {
|
|
_evlisteners[target] = {}
|
|
}
|
|
_evlisteners[target][event] = cb
|
|
}
|
|
|
|
export function off(event, target = document) {
|
|
if (!_evlisteners[target] || !_evlisteners[target][event]) {
|
|
return
|
|
}
|
|
target.removeEventListener(event, _evlisteners[target][event])
|
|
delete _evlisteners[target][event]
|
|
if (isEmpty(_evlisteners[target])) {
|
|
delete _evlisteners[target]
|
|
}
|
|
}
|
|
|
|
export function node(type, { appendTo, cls, text, data, style, ...attrs } = {}) {
|
|
let elem = isString(type) ? document.createElement(type) : type
|
|
if (cls) {
|
|
if (isString(cls)) {
|
|
elem.className = cls
|
|
} else {
|
|
for (const [name, on] of Object.entries(cls)) {
|
|
if (on) {
|
|
elem.classList.add(name)
|
|
} else {
|
|
elem.classList.remove(name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (text) {
|
|
elem.textContent = text
|
|
}
|
|
Object.assign(elem.dataset, data ?? {})
|
|
Object.assign(elem.style, style ?? {})
|
|
for (const name in attrs) {
|
|
elem.setAttribute(name, attrs[name])
|
|
}
|
|
if (appendTo) {
|
|
elem = appendTo.appendChild(elem)
|
|
}
|
|
return elem
|
|
}
|
|
|
|
/**
|
|
* Some duration conversion constants.
|
|
*/
|
|
export const ms_ns = 1_000_000 // nanoseconds in a millisecond
|
|
export const s_ms = 1_000 // milliseconds in a second
|
|
export const s_ns = 1_000_000_000 // nanoseconds in a second
|
|
|
|
export function session_url(sid) {
|
|
return `${config.wsurl}/${sid}`
|
|
}
|
|
|
|
export function session_id_from_url(url) {
|
|
const wsurl = new URL(config.wsurl)
|
|
const match = RegExp(`${wsurl.pathname}/([^/]+)$`).exec(url)
|
|
return !match ? null : match[1]
|
|
}
|
|
|
|
export function clear(container) {
|
|
while (container.children.length > 0) {
|
|
const child = container.children[0]
|
|
child.remove()
|
|
}
|
|
}
|
|
|
|
export function session_id() {
|
|
const match = /^#?(.+)/.exec(location.hash)
|
|
return match ? match[1] : null
|
|
}
|
|
|
|
export class Connection {
|
|
constructor() {
|
|
this.socket = null
|
|
this.toffset_ms = null
|
|
this.servertime = null
|
|
this.handlers = {
|
|
time: this._handler_time.bind(this),
|
|
error: this._handler_error.bind(this),
|
|
}
|
|
}
|
|
|
|
_handler_time({ value: { time } }) {
|
|
this.servertime = time
|
|
this.toffset_ms = performance.now()
|
|
}
|
|
|
|
_handler_error({ value: { reason } }) {
|
|
console.error(`Error: ${reason}`)
|
|
}
|
|
|
|
connect(url) {
|
|
this.socket = new WebSocket(url)
|
|
this.socket.addEventListener("open", (event) => {
|
|
if ("helo" in this.handlers) {
|
|
this.handlers["helo"](event)
|
|
}
|
|
})
|
|
this.socket.addEventListener("message", (event) => {
|
|
const msg = JSON.parse(event.data)
|
|
if (msg.type in this.handlers) {
|
|
this.handlers[msg.type](msg)
|
|
} else {
|
|
console.error(`Unhandled message: ${event.data}`)
|
|
}
|
|
})
|
|
}
|
|
|
|
on(type, callback) {
|
|
this.handlers[type] = callback
|
|
}
|
|
|
|
/**
|
|
* Guess the exact current server time.
|
|
*/
|
|
servertime_now_ns() {
|
|
const now_ms = performance.now()
|
|
const delta_ns = ms_ns * (now_ms - this.toffset_ms)
|
|
return this.servertime + delta_ns
|
|
}
|
|
|
|
send(type, value) {
|
|
// console.debug('sending', value)
|
|
this.socket.send(JSON.stringify({ type, value }))
|
|
}
|
|
}
|