add original version of old crummy monitor
This commit is contained in:
parent
b00f8a357c
commit
ab7f6014a4
7 changed files with 368 additions and 114 deletions
|
|
@ -42,8 +42,7 @@
|
||||||
<li class="player" data-cid="client ID">
|
<li class="player" data-cid="client ID">
|
||||||
<span class="name">player name</span>
|
<span class="name">player name</span>
|
||||||
<div class="admin-only">
|
<div class="admin-only">
|
||||||
(<span class="points">points</span> pts)
|
(<span class="points">points</span> pts) (<span class="tokens">tokens</span> tks)
|
||||||
(<span class="tokens">tokens</span> tks)
|
|
||||||
<button data-eval="send('control', {target: client.id, action: 'monitor'})">
|
<button data-eval="send('control', {target: client.id, action: 'monitor'})">
|
||||||
monitor
|
monitor
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
129
public/buzzer.js
129
public/buzzer.js
|
|
@ -10,89 +10,27 @@ const storage = window.sessionStorage
|
||||||
// - measure/report latency
|
// - measure/report latency
|
||||||
// - use server reported time to find winner
|
// - use server reported time to find winner
|
||||||
|
|
||||||
import config from "./config.js"
|
import {
|
||||||
|
clear,
|
||||||
|
isEmpty,
|
||||||
|
isString,
|
||||||
|
ms_ns,
|
||||||
|
node,
|
||||||
|
off,
|
||||||
|
on,
|
||||||
|
q,
|
||||||
|
s_ns,
|
||||||
|
servertime_now_ns,
|
||||||
|
session_id,
|
||||||
|
session_id_from_url,
|
||||||
|
session_url,
|
||||||
|
} from "./shared.js"
|
||||||
|
|
||||||
const buzzer_key = {
|
const buzzer_key = {
|
||||||
code: 0x20,
|
code: 0x20,
|
||||||
name: "space bar",
|
name: "space bar",
|
||||||
}
|
}
|
||||||
|
|
||||||
function isString(x) {
|
|
||||||
return typeof x === "string"
|
|
||||||
}
|
|
||||||
|
|
||||||
function isEmpty(x) {
|
|
||||||
return (Array.isArray(x) ? x : Object.keys(x)).length === 0
|
|
||||||
}
|
|
||||||
|
|
||||||
const q = (selector, root = document) => root.querySelector(selector)
|
|
||||||
|
|
||||||
const _evlisteners = {}
|
|
||||||
/**
|
|
||||||
* @param {String} event [description]
|
|
||||||
* @param {Function} cb [description]
|
|
||||||
* @param {?{once: bool, target: Element = document}} options
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
const ms_ns = 1_000_000 // nanoseconds in a millisecond
|
|
||||||
const s_ms = 1_000 // milliseconds in a second
|
|
||||||
const s_ns = 1_000_000_000 // nanoseconds in a second
|
|
||||||
|
|
||||||
let socket,
|
let socket,
|
||||||
servertime,
|
servertime,
|
||||||
toffset_ms,
|
toffset_ms,
|
||||||
|
|
@ -114,11 +52,6 @@ function find_client(client_id) {
|
||||||
return clients.find((c) => c.id === client_id)
|
return clients.find((c) => c.id === client_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
function session_id() {
|
|
||||||
const match = /^#?(.+)/.exec(document.location.hash)
|
|
||||||
return match ? match[1] : null
|
|
||||||
}
|
|
||||||
|
|
||||||
function new_session_id() {
|
function new_session_id() {
|
||||||
if (!crypto) {
|
if (!crypto) {
|
||||||
return Math.random().toString(36).substr(2)
|
return Math.random().toString(36).substr(2)
|
||||||
|
|
@ -130,7 +63,7 @@ function new_session_id() {
|
||||||
|
|
||||||
function setup_url() {
|
function setup_url() {
|
||||||
const sid = session_id() || new_session_id()
|
const sid = session_id() || new_session_id()
|
||||||
document.location.hash = sid
|
location.hash = sid
|
||||||
}
|
}
|
||||||
|
|
||||||
function send(type, value) {
|
function send(type, value) {
|
||||||
|
|
@ -138,13 +71,6 @@ function send(type, value) {
|
||||||
socket.send(JSON.stringify({ type, value }))
|
socket.send(JSON.stringify({ type, value }))
|
||||||
}
|
}
|
||||||
|
|
||||||
function clear(container) {
|
|
||||||
while (container.children.length > 0) {
|
|
||||||
const child = container.children[0]
|
|
||||||
child.remove()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const player_list = q("#info ul")
|
const player_list = q("#info ul")
|
||||||
function redraw_clients(me, clients) {
|
function redraw_clients(me, clients) {
|
||||||
if (!me) {
|
if (!me) {
|
||||||
|
|
@ -196,15 +122,6 @@ function highlight(client_id, until_ns) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Guess the exact current server time.
|
|
||||||
*/
|
|
||||||
function servertime_now_ns() {
|
|
||||||
const now_ms = performance.now()
|
|
||||||
const delta_ns = ms_ns * (now_ms - toffset_ms)
|
|
||||||
return servertime + delta_ns
|
|
||||||
}
|
|
||||||
|
|
||||||
function setup_ui() {
|
function setup_ui() {
|
||||||
on("focus", (event) => {
|
on("focus", (event) => {
|
||||||
hide("active")
|
hide("active")
|
||||||
|
|
@ -306,20 +223,10 @@ function set_name(name) {
|
||||||
send("name", name)
|
send("name", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
function session_url(sid) {
|
|
||||||
return `${config.wsurl}/${sid}`
|
|
||||||
}
|
|
||||||
|
|
||||||
function session_id_from_url(url) {
|
|
||||||
const wsurl = new URL(config.wsurl)
|
|
||||||
const match = RegExp(`${wsurl.pathname}/([^/]+)$`).exec(url)
|
|
||||||
return !match ? null : match[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
function setup_ws() {
|
function setup_ws() {
|
||||||
const sid = session_id()
|
const sid = session_id()
|
||||||
const credentials = { id: storage["my_uid"], key: storage["my_key"] }
|
const credentials = { id: storage["my_uid"], key: storage["my_key"] }
|
||||||
socket = new WebSocket(`${session_url(sid)}`)
|
socket = new WebSocket(session_url(sid))
|
||||||
socket.addEventListener("open", function (event) {
|
socket.addEventListener("open", function (event) {
|
||||||
if (sid === storage["my_sid"]) {
|
if (sid === storage["my_sid"]) {
|
||||||
send("login", credentials)
|
send("login", credentials)
|
||||||
|
|
@ -362,6 +269,8 @@ function setup_ws() {
|
||||||
clients.push(client)
|
clients.push(client)
|
||||||
}
|
}
|
||||||
redraw_clients(me, clients)
|
redraw_clients(me, clients)
|
||||||
|
} else if (type === "control") {
|
||||||
|
// ignore
|
||||||
} else if (type === "error") {
|
} else if (type === "error") {
|
||||||
console.error(`Error: ${value.reason}`)
|
console.error(`Error: ${value.reason}`)
|
||||||
const errorbox = q("#error")
|
const errorbox = q("#error")
|
||||||
|
|
|
||||||
60
public/monitor.css
Normal file
60
public/monitor.css
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
body {
|
||||||
|
font-family: "Arial Rounded MT Bold", sans-serif;
|
||||||
|
background-color: black;
|
||||||
|
margin: 0px auto;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
# colors
|
||||||
|
citrus: c2d72f
|
||||||
|
bleue: 0093ff
|
||||||
|
*/
|
||||||
|
#info {
|
||||||
|
background-color: #c2d72f;
|
||||||
|
padding: 1em 0 0 1em;
|
||||||
|
width: 1280px;
|
||||||
|
height: 720px;
|
||||||
|
outline: 10px solid gold;
|
||||||
|
}
|
||||||
|
.player {
|
||||||
|
width: 336px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.player + .player {
|
||||||
|
margin-top: 1.5em;
|
||||||
|
}
|
||||||
|
.box {
|
||||||
|
background-color: #0f0;
|
||||||
|
height: 189px;
|
||||||
|
outline: 0.3em solid grey;
|
||||||
|
-moz-outline-radius: 1em;
|
||||||
|
}
|
||||||
|
.points {
|
||||||
|
position: absolute;
|
||||||
|
top: 1em;
|
||||||
|
right: 1em;
|
||||||
|
background-color: #c2d72f;
|
||||||
|
width: 3em;
|
||||||
|
padding: 0.2em;
|
||||||
|
text-align: right;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
box-shadow: 0 0 0.3em #c2d72f, 0 0 0.8em #c2d72f, 0 0 1em #c2d72f, 0 0 1.2em #c2d72f;
|
||||||
|
}
|
||||||
|
.player.buzzing .box {
|
||||||
|
outline: 0.7em solid #0093ff;
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: 0.3em;
|
||||||
|
font-variant: small-caps;
|
||||||
|
/*font-weight: bold;*/
|
||||||
|
}
|
||||||
|
/*1280 x 720*/
|
||||||
|
/*1920 x 1080*/
|
||||||
24
public/monitor.html
Normal file
24
public/monitor.html
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="monitor.css" />
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="info">
|
||||||
|
<div class="player">
|
||||||
|
<div class="points">-36'000</div>
|
||||||
|
<div class="box"></div>
|
||||||
|
<div class="name">Player 1</div>
|
||||||
|
</div>
|
||||||
|
<div class="player buzzing">
|
||||||
|
<div class="points">0</div>
|
||||||
|
<div class="box"></div>
|
||||||
|
<div class="name">Player 2</div>
|
||||||
|
</div>
|
||||||
|
<div class="player">
|
||||||
|
<div class="points">900</div>
|
||||||
|
<div class="box"></div>
|
||||||
|
<div class="name">Player 3</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script type="module" src="./monitor.js"></script>
|
||||||
149
public/monitor.js
Normal file
149
public/monitor.js
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
"use strict"
|
||||||
|
|
||||||
|
/* global document, window */
|
||||||
|
const location = document.location
|
||||||
|
const performance = window.performance
|
||||||
|
|
||||||
|
import {
|
||||||
|
clear,
|
||||||
|
ms_ns,
|
||||||
|
node,
|
||||||
|
q,
|
||||||
|
s_ns,
|
||||||
|
servertime_now_ns,
|
||||||
|
session_id,
|
||||||
|
session_url,
|
||||||
|
} from "./shared.js"
|
||||||
|
|
||||||
|
let socket,
|
||||||
|
servertime,
|
||||||
|
toffset_ms,
|
||||||
|
clients = {},
|
||||||
|
me
|
||||||
|
|
||||||
|
function setup_url() {
|
||||||
|
const sid = session_id() || ""
|
||||||
|
location.hash = sid
|
||||||
|
}
|
||||||
|
|
||||||
|
function send(type, value) {
|
||||||
|
// console.debug('sending', value)
|
||||||
|
socket.send(JSON.stringify({ type, value }))
|
||||||
|
}
|
||||||
|
|
||||||
|
function prettynum(n) {
|
||||||
|
let i = Math.abs(n) | 0
|
||||||
|
let s = []
|
||||||
|
while (true) {
|
||||||
|
const m = i % 1000
|
||||||
|
i = (i / 1000) | 0
|
||||||
|
if (i === 0) {
|
||||||
|
s.unshift(String(m))
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
s.unshift(String(m).padStart(3, "0"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (n < 0 ? "-" : "") + s.join("'")
|
||||||
|
}
|
||||||
|
|
||||||
|
function assert(expected, got) {
|
||||||
|
if (expected !== got) {
|
||||||
|
throw Error("Assertion failed.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function test_prettynum() {
|
||||||
|
assert("0", prettynum(0))
|
||||||
|
assert("1", prettynum(1))
|
||||||
|
assert("1'000", prettynum(1000))
|
||||||
|
assert("36'000", prettynum(36000))
|
||||||
|
assert("0", prettynum(-0))
|
||||||
|
assert("-1", prettynum(-1))
|
||||||
|
assert("-1'000", prettynum(-1000))
|
||||||
|
assert("-36'000", prettynum(-36000))
|
||||||
|
assert("-36'600", prettynum(-36600))
|
||||||
|
}
|
||||||
|
test_prettynum()
|
||||||
|
|
||||||
|
const player_list = q("#info")
|
||||||
|
function redraw_clients(me, clients) {
|
||||||
|
if (!me) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clear(player_list)
|
||||||
|
for (const c of Object.values(clients)) {
|
||||||
|
if (c.id === me.id) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const player = node("div", { data: { cid: c.id }, cls: "player" })
|
||||||
|
node("div", { cls: "points", text: prettynum(c.points), appendTo: player })
|
||||||
|
node("div", { cls: "box", appendTo: player })
|
||||||
|
node("div", { cls: "name", text: c.name, appendTo: player })
|
||||||
|
player_list.appendChild(player)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let highlighted = false
|
||||||
|
function highlight(client_id, until_ns) {
|
||||||
|
if (highlighted) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const timeout_ms = (until_ns - servertime_now_ns()) / ms_ns
|
||||||
|
if (timeout_ms <= 10) {
|
||||||
|
console.warn("That highlight timeout was ridiculously low:", client_id, timeout_ms)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const li of player_list.children) {
|
||||||
|
if (li.dataset.cid === client_id) {
|
||||||
|
li.classList.add("buzzing")
|
||||||
|
highlighted = true
|
||||||
|
setTimeout(() => {
|
||||||
|
highlighted = false
|
||||||
|
li.classList.remove("buzzing")
|
||||||
|
}, timeout_ms)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setup_ws() {
|
||||||
|
const sid = session_id()
|
||||||
|
socket = new WebSocket(session_url(sid))
|
||||||
|
socket.addEventListener("open", function (event) {
|
||||||
|
send("name", "Monitor")
|
||||||
|
})
|
||||||
|
socket.addEventListener("message", function (event) {
|
||||||
|
const msg = JSON.parse(event.data)
|
||||||
|
const { type, value } = msg
|
||||||
|
if (msg.type === "time") {
|
||||||
|
servertime = value.time
|
||||||
|
toffset_ms = performance.now()
|
||||||
|
} else if (msg.type === "id") {
|
||||||
|
me = value
|
||||||
|
redraw_clients(me, clients)
|
||||||
|
} else if (msg.type === "buzz") {
|
||||||
|
const buzztime_ns = value.time
|
||||||
|
const client_id = value.client
|
||||||
|
const duration_ns = 12 * s_ns
|
||||||
|
const until_ns = buzztime_ns + duration_ns
|
||||||
|
highlight(client_id, until_ns)
|
||||||
|
} else if (msg.type === "clients") {
|
||||||
|
clients = Object.fromEntries(value.clients.map((c) => [c.id, c]))
|
||||||
|
redraw_clients(me, clients)
|
||||||
|
} else if (msg.type === "client") {
|
||||||
|
const client = value.client
|
||||||
|
clients[client.id] = client
|
||||||
|
redraw_clients(me, clients)
|
||||||
|
} else if (type === "session_key") {
|
||||||
|
// ignore
|
||||||
|
} else if (type === "error") {
|
||||||
|
console.error(`Error: ${value.reason}`)
|
||||||
|
} else {
|
||||||
|
console.error(`Unknown message: ${event.data}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setup_url()
|
||||||
|
setup_ws()
|
||||||
115
public/shared.js
Normal file
115
public/shared.js
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
"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)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guess the exact current server time.
|
||||||
|
*/
|
||||||
|
export function servertime_now_ns() {
|
||||||
|
const now_ms = performance.now()
|
||||||
|
const delta_ns = ms_ns * (now_ms - toffset_ms)
|
||||||
|
return servertime + delta_ns
|
||||||
|
}
|
||||||
|
|
@ -104,5 +104,3 @@ class Serve:
|
||||||
__iter__ = __await__
|
__iter__ = __await__
|
||||||
|
|
||||||
serve = Serve
|
serve = Serve
|
||||||
|
|
||||||
WebSocketServerProtocol
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue