rendezvous/www/main.js

1342 lines
29 KiB
JavaScript
Raw Permalink Normal View History

2023-06-21 15:33:47 +00:00
//
// 2023.06.18
// support
// - chrome
// - edge
// - ff
// - safari
// ...
// mobile / pc (linux/iOS/macOS/windows/android)
// ...
// cross device/platform
//
//
// next:
// - db
// - save user name (kv)
// - use username in messaging.
//
// .............................
//
//
// next: select cam
// 'c' to toggle. upg: ui (in user icon) .. see your stats and pick settings there.
//
// upg: remember specific mute mike setting (even if switch video input.)
// .......
//
// https://github.com/diafygi/webcrypto-examples/#ecdsa
//
// next: add db, c, input box, etc
//
// upg: better linky type things
//
// next: video display mangagement. choose based on environemnt. (see (with demo) what css can do automatically)
// - goal to keep full bleed.
//
// next: handle switch between audio/video mode.
// - tap video to toggle video off
//
// upg: copy/paste media
//
//
// upg: prn -- better input/output selection (when click user icon)
//
// upg: chat (with hyper and media links)
//
// upg: upload
// - media, video, pics special treated?
//
// upg: display media (screenshare) prn
//
//
// upg: HTMLCanvasElement.captureStream().
// - make a shared whiteboard thing (allow pple to 'draw' on the whiteboard they see) prn.
//
// next: toggle mute audio
// - spacebar 'm'
// - click mic button
//
// upg: automatically activate mic if allowed (check permissions/try?) or? prn.
//
// upg: support voice call?
// - toggle off video if pic audio?
// - toggle again to switch? or?
// - or put line under mode <-- this. underline mic or cam .. upg sliding animation on toggle.
//
// hide speaker by default once audio works (only have mute page option if user sets that as option in ui)
//
// hack reload the page every 5 seconds if no peers? (and no streams yet)
// > upg: review source to see why this might be.
//
// toggle cam switches off cam. (remove underline)
//
// toggle mic mutes audio out. (video or audio mode) .. have to turn off video mode to swich to audio only mode.
//
// click on cam when on mic mode .. turns off mic and turns on cam
// (upg: can/should we have different tracks, a video track and an audio track, yes that would be eaier/better if in sync?) .. but use the same ui.
//
// upg: tap to toggle peer view.
// - time on call, video or audio on. and/or
//
// upg: opt: integrate with nostr (share addr?)
// > with ai readable text messages?
//
// upg: add version number in uio.
//
// upg: display change on peers.
//
// upg: allow u=bob to suggest a username (but the user has to accept it)
import randStr from './lib/random-string.js'
//import NoSleep from './lib/nosleep.js' // try inlcuding in main or minifted version?
import {trystero,idb} from './lib/bundle.js'
const {openDB} = idb
const {joinRoom} = trystero
console.log(openDB)
let VER = '[5]'
console.log('VERSION 2023.06.20 #1945 '+VER)
const url = new URL(location.href)
const {searchParams} = url
let searchU = searchParams.get('u')
document.querySelector('.version').textContent = VER
let roomTag = location.hash
if(!roomTag){
roomTag = (await randStr(6)).toLowerCase()
location.hash = roomTag
roomTag = location.hash
}
console.log('roomTag',{roomTag})
//
// upg: api interface.
//
let tableName = n=>n
const db = await openDB('main',1,{upgrade:(...args)=>{
const [db,oldVersion,newVersion,transaction,e] = args
console.log('upgrade',{oldVersion,newVersion})
let t = tableName
let s
s = db.createObjectStore(t('kv'),{keyPath:'key',unique:true}) // {key:'bob',value:'12345'}
// s.createIndex('unitId','unitId')
}})
const kv = {
t:tableName,
get : async function(k){
const {t} = this
const r = await db.get(t('kv'),k)
return r?.value
},
set : async function(k,v){
const {t} = this
let m = {key:k,value:v}
const r = await db.put(t('kv'),m)
return r
}
}//kv
if(searchU){
await kv.set('username',searchU)
}
let username = await kv.get('username')
console.log('username',username)
const ce = (...args)=>{
const [type,value] = args
let d = cc(...args)
if(value) // upg: if str
d.textContent = value
return d
}//func
const dB = document.body
let q = n=>dB.querySelector(n)
let cc = n=>document.createElement(n||'div')
let ca = (a,b)=>a.appendChild(b)
const dM = q('main')
const dL = q('.log')
const dD = q('main.display')
const dPc = q('.peer-count')
const dPre = q('.preview')
const dI = q('footer .input input')
const dTl = q('.display .text-log')
// upg: support #spaces
//
// upg: lib compliler
//
// upg: notificaton option (offline and online)
//
// upg: bring in libraries (for working with data etc.)
const {subtle} = window.crypto
const genSignKey = async n=>{
let k = await subtle.generateKey({
name: "ECDSA",
namedCurve: "P-384"
},
true, //whether the key is extractable (i.e. can be used in exportKey)
["sign", "verify"] //can be any combination of "sign" and "verify"
)
const jPk = await subtle.exportKey('jwk',k.publicKey)
const jSk = await subtle.exportKey('jwk',k.privateKey)
return {key:k,jPk,jSk}
}//
let sigKey = await genSignKey()
console.log({sigKey})
// ----------------------------------
onhashchange = (e) => {
const {oldURL,newURL} = e
// upg: join nrew room and update display (and/or?) // nice if keep old room open till all clear sending (using room class abstraction)
setTimeout(n=>location.reload(),150) //upg: ask to reload or?
}
const room = joinRoom({appId:'--linky-rendezvous'},'alpha-'+roomTag)
// ----------------------
//
const [send,recv] = room.makeAction('message')
//upg: use sync ping/query.
recv((d,p)=>{
console.log('recived data from peer',p,d)
const {kind} = d|| {}
if(kind == 'message'){
d.from = 'them'
logMessage(d)
}
})
let peers = {}
room.onPeerJoin(p=>{
console.log('peer join',p)
// upg: update() // to set has-peer in body (with peer-join) only if 5 seconds peer.
//dB.classList.add('peer-join')// upg: timer to turn off peer join after 5 seconds.
//
// upg: do peer
// upg: put peer in a wait list if we've not got the stream yet...
if(stream){
console.log('adding existing stream to for new peer',p)
const {s,m} = stream
room.addStream(s,p,m)
}
let ping = async n=>{
let r = await room.ping(p)
console.log('ping update',p,r+'ms')
peers[p]._ping = setTimeout(ping,5000)
}
peers[p] = {
pid:p,
udate:Date.now()/1000,
ping
}
ping()
//upg: animate peer icon (flash)
update()
})
const removeMedia = n=>{
//upg: stop media first? prn.
dD.removeChild(n)
}
room.onPeerLeave(p=>{
console.log('peer leave',p,peerMedia)
dB.classList.remove('peer-join') //and/or? use has-peers set from update()
let l = []
peerMedia.forEach(v=>{ // or? (use index based lookup?) though there can be multiple.
console.log('review',v)
const {dom,meta,container} = v
if(p == v.p){
removeMedia(container)
}
else
l.push(v)
})
peerMedia = l // restore
clearTimeout(peers[p]._ping) // NOTE CONNECTED --- upg: call peer.done()
delete peers[p]
update()
})
//let _toAdd = []
let peerMedia = []
room.onPeerStream((s,p,m)=>{ // upg on peer track?
// s = stream
// p = peer
// m = metadata
// and/or detect media?
//
let {kind} = m||{}
console.log('peer stream: add stream',{kind},{s,p,m})
;{
// https://developer.mozilla.org/en-US/docs/Web/API/OfflineAudioContext
// https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Using_Web_Audio_API
// https://web.dev/webaudio-intro/
// https://developer.mozilla.org/en-US/docs/Web/API/GainNode/GainNode
// https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/AnalyserNode
// https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/createMediaElementSource
// https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/createMediaStreamSource
// https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/getFloatTimeDomainData
//
// upg: send what we're getting back to them?
// upg: process each input stream and display value
//
// > mainly for speaker detection for main view.
// - toggle views?
// - default full edge speaker view video with overlays.
// - second is full edge titled video.
// - and?
//
// (note will rairly be more than 3?)
//
//
// - might have screensharing too (it takes priorty) with pip speaker.
//
// what do we need to see? localy, what helps? prn.
// - just that we've got the stream and/or?
//
//
// >> WE PROB WANT TO DO THIS REMOTE SIDE AND SEND THE RESULT TO PEER
// - this will help early jump speaker selection.'
//
try{
// also send telmetry from the user (then we'll have details)
// - send on/off events.
// upg: make this a monitoring lib class with events
const ac = new AudioContext()
// const sampleRate = 44100
//const ac = new OfflineAudioContext(//new AudioContext() // see.
// {
// numberOfChannesl:2,
// length: sampleRate*40,
// sampleRate
// })
//console.log({ac})
//const options = {
// mediaStream: s
// }
const source = ac.createMediaStreamSource(s)
//console.log('remote SOURCE',source)
const gain = new GainNode(ac,{gain:0})
const meter = new AnalyserNode(ac) //upg: lower grained monitory.
const data = new Float32Array(meter.frequencyBinCount)
const byteData = new Uint8Array(meter.frequencyBinCount)
source.connect(meter).connect(gain).connect(ac.destination)
// https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/getByteFrequencyData
// upg: monitor the common (long time) high average for baseline to detect speech.
// - upg: use time domain to detect?
// - upg: libraries to detect speech
// upg: detect actual speech client side (using local detection) .. and send that also data telem to peers. .. eg and/or -- https://github.com/solyarisoftware/webad
setInterval(n=>{
return
//upg: put in groups and count groups and/or algo or?
meter.getFloatFrequencyData(data)
meter.getByteFrequencyData(byteData) //-0 ~ -165 etc. -0 is loud. -165 is soft.
//console.log(data)
//let x = byteData.join(', ')
//dB.querySelector('.debug').textContent = x
let a = 0
let {length} = data
let g = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
for(let i=0;i<length;i++){
let v = data[i]
let vv = Math.abs(v)
let vvv = Math.round(vv)
let gi = vvv%g.length
g[gi]++
a+=vv
if(i<3){
//console.log({v,vv,vvv,gi})
}
}
let aa = a/length
//console.log(g)
//dB.querySelector('.debug').textContent = aa
},3333)
}
catch(e){
//upg: cache till user gesture. (how to hook, hook our own and/or callfunction
console.log(e)
}
};
s.addEventListener('removetrack',e=>{
console.log('stream remove track',e)
//upg: smarter
let lt = s.getTracks()
if(lt.length == 0){
console.log('remove media display',{s,p,m,dc})
try{
dD.removeChild(dc)
}
catch(e){
console.log(e)
}
}
})
s.addEventListener('addtrack',e=>{
console.log('stream add track',e)
})
//upg: how to detect if video?
// -- upg: ignore if unknown kind
const d = document.createElement(kind)
d.srcObject = s
d.muted = !interacted // so video can play still.
d.autoplay = true
const onPlaying = e=>{
// try to unmute (video can play without audio)
console.log('onplaying peer stream')
d.removeEventListener('playing',onPlaying)
startWakeLock()
//setTimeout(n=>{
// console.log('trying umute.')
// d.muted = false //try to unmute .. this might pause things?
// },3500)
}//func
d.addEventListener('playing',onPlaying)
//setTimeout(n=>{
//try to play: upg: keep trying or? //upg: check global wantsMute if create that.
// if(interacted) // upg: or just try to unmute anyway? .. donno how long need to wait since autoplay
// d.muted = false
// },150)
let dc = d
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video
//
if(kind == 'video'){
//d.playsinline = true
d.setAttribute('playsinline','') // or = true ok?
dc = makeVideo(d)
}
dD.appendChild(dc)
peerMedia.push({p,dom:d,container:dc,meta:m}) //type:'audio'
//d.play()
update()
})
//const flushInteractions(){}
// upg: a generic class to do this.
// upg: fix this?
let hasPeersState = {
value: false,
limit:1,
ago:0,
try:function(...args){
const [v,cb,x] = args
const now = performance.now()/1000
console.log('this try',v,{x},this)
if(this.value !=v){
const delta = (now-this.ago)
console.log({delta},this.ago)
if(delta >= this.limit){ // = roughly
console.log('ok to change',this.value)
cb(this.value)
this.ago = now
}//if
else{
let next = ((this.ago+this.limit)-now)*1000 // +?
console.log(next)
clearTimeout(this._timeout)
this._timeout = setTimeout(n=>this.try(...args,'retry'),next)
}
}//if
}//fn
}//obj
let _update = 0
const update = n=>{
_update++
let now = performance.now()/1000
const l = room.getPeers()
console.log(l)
dPc.textContent = l.length+1 // +1 to count self.
let peerCount = l.length
let audioLive = false
let videoLive = false
if(stream){
const {s} = stream
const tl = s.getTracks()
console.log('update',{tl})
tl.forEach(v=>{
let {kind,enabled} = v
if(enabled && kind =='audio')
audioLive = true
if(enabled && kind =='video')
videoLive = true
})
}
dB.classList[audioLive?'add':'remove']('audio-is-live')
dB.classList[videoLive?'add':'remove']('video-is-live')
//upg: time trigger this (so only changes once per second)
let hasPeers = (peerCount>0)
hasPeersState.try(hasPeers,n=>{
console.log('has peers',n)
dB.classList[hasPeers?'add':'remove']('has-peers')
})
let hasPeerVideo = false // hack; upg: and/or ../ upg where delete peer video?
;(peerMedia||[]).forEach(v=>{
const {meta} = v
const {kind} = meta
console.log('checking peer media',v,{meta,kind})
if(kind == 'video')
hasPeerVideo = true
})
dB.classList[hasPeerVideo?'add':'remove']('has-peer-video') // and/or:
// and or this shouldn't be in update or why not here? (so long as don't quick move)
let state = {
audioLive,videoLive,peerCount,interacted,hasPeerVideo
}
//upg: send available media etc?
//upg: toggle button to call about
//
//upg: comfort level based sharing with who etc.
// - close friends share (cached?) about mee.
send({kind:'update',layer:_update,state})
}
//paint = intial create once
const makeVideo = n=>{
const d = document.createElement('div')
d.classList.add('video-wrapper')
d.appendChild(n)
return d
}
let stream = false
const addStream = async (s,m)=>{
//
// add stream to peers (and remember for other peers)
//
console.log('add stream',s)
//upg: switch to video audio if pick that, or seperate streams?
stream = {s,m}// and/or?
room.addStream(s,null,m) // and/or?
console.log('stream added for peers',s,m)
}//func
// -----------------------------
const toggleAudio = n=>{
if(stream){
let {s} = stream
console.log('is video, toggle audio')
const tl = s.getAudioTracks()
console.log("audo tracks",tl)
let vl = []
tl.forEach(v=>{
v.enabled = !v.enabled
vl.push(v.enabled)
})
console.log('new state',vl)
update()
}//if
}//func
let _toggleMic = false
const toggleMic = async n=>{
if(!_toggleMic){
console.log('toggle mic')
if(!stream){
//upg: switch to video audio if pick that, or seperate streams?
const {mediaDevices:m} = navigator
const l = await m.enumerateDevices()
console.log({l})
const s = await m.getUserMedia({video:false,audio:true})
addStream(s,{kind:'audio'})
dB.classList.add('mic-on')
}
else {
console.log('has stream',stream)
const {s,m} = stream
const {kind} = m
const isVideo = (kind == 'video')
const isAudio = (kind == 'audio')
if(isAudio){
console.log('stop audio',s)
//upg: use the same stream but add/remove tracks? or?
//room.removeStream(s)
const cl = s.getTracks()
console.log({cl})
cl.forEach(v=>{
console.log({v})
room.removeTrack(v,s)
stream.m.kind = 'empty'
})
stream = false
}
else {
toggleAudio()
}
}//else
update()
_toggleMic = false
}//if
}//func
const switchCam = async n=>{
// upg: debounce
//
// upg: review
//
// upg: alpha sort deviceId (or does it always report in the same order every query?)
if(stream){
console.log('switchCam')
const {s} = stream
const a = await aboutStream(s)
console.log({a})
let f = a.find(v=>v.kind=='video')
console.log({f})
// upg: what if more than one video? does that happen when we're screen sharing?
if(f){
const {mediaDevices:m} = navigator
const l = await m.enumerateDevices()
console.log({l})
//
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDeviceInfo
let ff = l.find(v=>v.deviceId == f.deviceId)
console.log({ff})
let i = l.indexOf(ff)
console.log({i})
let next = false
l.forEach((v,ii)=>{
const {deviceId,kind,label,groupId} = v
//groupId = same physical device
//kind = "videoinput", "audioinput" or "audiooutput"
//label = "External USB Webcam c201"
if(kind == 'videoinput' && ii > i){
console.log('found!',label,deviceId)
next = {deviceId,device:v}
}
})
if(!next){
const fff = l.find(v=>v.kind=='videoinput')
console.log({fff,f})
if(fff && fff.deviceId != f.deviceId){
next = fff
}
}//if
console.log('switch to',next)
}//if
}//if
else
console.log('no stream',stream)
}//func
const aboutStream = s=>{
let a = []
if(s){
const {mediaDevices:m} = navigator
//
// https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack
const tl = s.getTracks()
console.log({tl})
// upg: send device code etc to peers?
//
// https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings
tl.forEach((n,i)=>{
const {contentHint,kind,label,muted,readyState} = n
const s = n.getSettings()
const c = n.getConstraints()
const { deviceId,groupId, ///all
//some
aspectRatio,
cursor,
displaySurface,
frameRate,
height,
resizeMode,
width
} = s
console.log({s,c})
console.log(i+1,{deviceId,kind,label,contentHint})
const aa = {
contentHint,kind,label,muted,readyState,deviceId,groupId,
aspectRatio,cursor,displaySurface,frameRate,height,width,resizeMode,
track:n
}
a.push(aa)
})
}//if
return a
}//func
let _toggleCam = false
const toggleCam = async n=>{
if(!_toggleCam){
let dd // display to append
if(stream){
console.log('has stream',stream)
const {s,m} = stream
const {kind} = m
const isVideo = (kind == 'video')
const isAudio = (kind == 'audio')
if(isVideo){
console.log('stop video',s)
//upg: use the same stream but add/remove tracks? or?
//room.removeStream(s)
const cl = s.getTracks()
console.log({cl})
cl.forEach(v=>{
console.log({v})
room.removeTrack(v,s)
stream.m.kind = 'empty'
})
//// -- remove preview
dPre.innerHTML = '' // or?
stream = false
}
}
else {
//upg: show ui progress (cam might take a bit.)
// - pulse animation while is-enabling-cam.
_toggleCam = true
console.log('toggle cam')
//upg: switch to video audio if pick that, or seperate streams?
const {mediaDevices:m} = navigator
const l = await m.enumerateDevices()
console.log({l})
//upg : remember
const audio = true // upg: and/or?
const video = true
const media = {video,audio}
const s = await m.getUserMedia(media) // does it work if we send mic/cam sperately? in sync?
//
// https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack
const tl = s.getTracks()
console.log({tl})
// upg: send device code etc to peers?
//
// https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackSettings
tl.forEach((n,i)=>{
const {contentHint,kind,label,muted,readyState} = n
const s = n.getSettings()
const c = n.getConstraints()
const {deviceId,groupId} = s
console.log({s,c})
console.log(i+1,{deviceId,kind,label,contentHint})
})
//
// https://developer.mozilla.org/en-US/docs/Web/API/MediaStream
console.log('got media device stream',s)
addStream(s,{kind:'video',media})
// preview
const d = document.createElement('video')
//d.playsinline = true
d.setAttribute('playsinline','') // or = true ok?
dd = makeVideo(d)
d.muted = true // or?
d.autoplay = true
d.srcObject = s
//d.play()
dPre.innerHTML = ''
dPre.appendChild(dd)
dB.classList.add('cam-on')
}//else
update()
_toggleCam = false
}//if
}//func
const addDisplay = async n=>{
console.log('add display')
const {mediaDevices:m} = navigator
// https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia
const audio = false // upg: and/or?
const video = true
const media = {video,audio}
const s = await m.getDisplayMedia(media) // does it work if we send mic/cam sperately? in sync?
const a = await aboutStream(s)
console.log('display media',s,{a})
addStream(s,{kind:'video',media,displayMedia:true}) //or
// upg: how to display that you're sharing screen? (tile previews?)
}
dB.querySelector('.mic.button').onclick = e=>{
toggleMic()
}
dB.querySelector('.cam.button').onclick = e=>{
toggleCam()
}
dB.querySelector('.peers.button').onclick = e=>{
//hack
switchCam()
}
//
// https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API
//
// https://github.com/richtr/NoSleep.js
//
// upg: turn wakelock on when there's a stream, (incomming or out going?) at least for outgoing video prn
//
let wakeLock = null
const startWakeLock = async n=>{
//
// upg
// noSleep = new NoSleep()
// noSleep.enable() // on user interaction (so can play video)
// https://github.com/richtr/NoSleep.js
//
if('wakeLock' in navigator){
try{
wakeLock = await navigator.wakeLock.request('screen')
console.log('requested wakelock',wakeLock)
// upg: await awakeLock.release() -- when not sending stream
dB.classList.add('wakelock-active')
}
catch(e){
console.log(e,e,name,e.message)
}
}
else {
console.log('Wakelock not supported.')
// upg: move this indicator settings section (don't need to know in some envs?)
dB.classList.add('wakelock-not-supported')
}
}
document.addEventListener('release',e=>{
console.log('wake lock released')
dB.classList.remove('wakelock-active')
})
document.addEventListener('visibilitychange',async e=>{
const {visibilityState:v} = document // hidden, visible
console.log('new visibility',v,e)
// >> upg: send visibility state (per user's affiliation comfortabliiity)
if(v === 'visible'){
await startWakeLock()
}//if
})
dB.querySelector('.reload-button').addEventListener('click',e=>{
location.reload()
})
let interacted = false
const interactionEvent = e=>{
if(!interacted){
interacted = true
console.log('interacted!')
dB.removeEventListener('click',interactionEvent)
dB.classList.add('has-interacted') // upg: more complated than that for intential muting (set a default is_muted = true state. upg: remember it?
peerMedia.forEach(v=>{
console.log('need play?',v)
const {p,dom,meta} = v
console.log({p,dom,meta})
dom.muted = false
dom.play() // and/or? // if audio?
})
startWakeLock()
}//if
}//funt
dB.addEventListener('click',interactionEvent)
const goAudio = n=>{
//mute/unmute or start audio stream
if(stream)
toggleAudio()
else
toggleMic()
}
// upg: use db and sync method for catchup.
const logMessage = n=>{
let {value:v,from,name} = n || {}
username = username || ''
console.log('log messge',{n})
const d = ce()
const fromMe =(from=='me')
d.classList.add('item',fromMe?'from-me':'from-them')
let dA = ce() // upg: ce('text content' or something to appendChild)
dA.textContent = name
dA.classList.add('meta')
d.appendChild(dA)
//let dB = ce('div',(fromMe?">> ":'')+v)
let dB = ce('div',v) //{c:'abc'} ?? -c jfiwoefij -ce -fji jf) .. command line style and/or luange style interopated cc() or?
dB.classList = 'content'
ca(d,dB) // append dB to d
dTl.appendChild(d)
while(dTl.childElementCount > 10)
dTl.removeChild(dTl.firstChild)
}//func
dI.onfocus = e=>{
console.log('FOCUS!')
}
dI.onblur = e=>{
console.log('BLUR!')
}
//upg: use a tick and/or watch return from focus and a check focucs function?
dI.onkeydown = e=>{
const {key} = e
if(key == 'Enter'){
console.log({key})
const v = (dI.value).trim()
if(v){
// 'me' means use peer (peer's assocated cid)
const m = {
kind:'message',from:'me',value:v,name:username
}
dI.value = ''
logMessage(m)
send(m)
e.preventDefault()
}//if
}
}//func
//
// Hey bob, this is my secret message to you.
//
// I'm using your public key nicknamed 'xyz'.
//
// Here's the message, it's in base64url format.
//
// Reply back when you get this, thanks!
//
//
// note: i'm using protocol cirta 2023.06.15.
//
// ---------------------------
// FJIfIOJoifejwoifjieowsdsafijFJEIFjDFSK
// DSFJKEFOIFJOEjfwieoioejwfoiewifoewiofj
// etc...
//
//
//
//
//
const focusInput = n=>{
dI.focus()
}
// ------------------
document.addEventListener('keydown',e=>{
// only if not focused on typing input.
const {activeElement:aE} = document
const inputFocus = dI == aE
//console.log({inputFocus})
if(inputFocus) return // keys go to input box.
const {key} = e
if(key == 'c'){
switchCam()
}
else
if(key == 'd'){
addDisplay()
}
else
if(key == ' '){
goAudio()
}
else
if(key == 'Escape'){
focusInput()
}
else {
// or?
focusInput()
}
//if
interactionEvent()
})//func
// next generate ecdh https://github.com/diafygi/webcrypto-examples/#ecdh
// - this one per room? or?
//ugp: get key if not generated.. make each room have it's own pub sig key? or allow the same id between #rooms? (prn start with shared?)
//
//
// >> [new message]
// > encrypt [generate a new public/disposable key]
// > sign
// > make message to send {M}
// > (add delvery header (mainly the room id))
// > push to log
// - for later query.
//
// .. remember the rooms
//
//
//
// uid: x|y is domain.
//
//
// upg: db setup
//
// > messages {
// id,
// from:uid,
// to:uid,
// room,
// message,
// (message network sent,)
// udate
// }
// :room-id
// :room-udate
// :room-from-udate
// prn
//
// > system-log {
// id,
// from:uid
// to:uid
// udate,
// kind:'key-share',
// value:{} // and or.. make log include everything with search filterws... to start, yes prob that.
// }
//
// > rooms {
// id,
// name,
// (keys), //etc
// udate
// }
// :name
// //list of rooms
// use this and sync manager, to pull new messages (espl if visit room)
//
//
// upg: > search { ((re)buildable)
// word,
// ref:'room-id',
// from,
// to,
// udate
// }
// :word
// :from-word
// :to-word
// etc
//
// > inbox .. so can process even if not done and restart
// > outbox ..
//
// > [use a nostr type req query]
// > make fid = 000000000000000001 < prefix padding so can do..
// : since :room-000000000000003
//
//
//
// upg: could lock _message data with hashkey unlock when sign in. [most ppl this is not an issue.. pro mode feature?
//
let _n = 0
setInterval(n=>{
_n++
const {scrollHeight,clientHeight,scrollTop} = dM
let size = (scrollHeight-(clientHeight+scrollTop))
let isMaxScroll = size < 7
let doAutoScroll = isMaxScroll
const d = document.createElement('div')
d.classList.add('info')
d.textContent = 'uuu'
dL.appendChild(d)
const dd = document.createElement('div')
dd.classList.add('content')
dd.innerText = 'hello! ('+_n+')'
dL.appendChild(dd)
document.title = _n
//upg: wait for content to load if remote loaded content before scroll (need a event queue if then)
//upg detect if a snap to focus and jump to scroll positon
if(doAutoScroll){
dM.scrollTo({top:scrollHeight,left:0,behavior:'instant'})
}
},1000)
/*
let _doPing = false
const doPing = async n=>{
if(!_doPing){
_doPing = true
_sincePing = performance.now()/1000
const l = room.getPeers()
console.log('ping',l)
let ll = l.map(async v=>{
let p = await room.ping(v)
return {peerId:v,ping:p}
}
)
let lr = await Promise.all(ll)
console.log('ping result',{lr}) // upg: how to use?
_doPing = false
}//if
}//func
*/
let _sincePeers = 0 // upg: reset this if no peers again.
//let _sincePing = false
let _tickCount = 0
const tick = n=>{
_tickCount++
//console.log('check tick',_tickCount)
const now = performance.now()/1000
let pc = room.getPeers() //upg: on peer change run update to set dB.has-peers
let hasPeers = (pc.length > 0)
if(!hasPeers){
if(now-_sincePeers > 11){
dB.classList.add('long-time-no-peers')
//upg: offer link
//location.reload()
}
}//if
else {
dB.classList.remove('long-time-no-peers')
_sincePeers = performance.now()/1000
//console.log('done with tick',{hasPeers})
}
// if(_sincePing === false || now-_sincePing > 10){
// console.log('do ping test')
// doPing()
// }
setTimeout(tick,500)
}//func
tick()
console.log('ready.')
const me = async n=> {
const f = await fetch('https://mee.pages.dev')
const j = await f.json()
console.log({j})
}
// upg: optin to provide this (upg: as part of a resource package?)
// https://developer.mozilla.org/en-US/docs/Web/API/Navigator/userAgent
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Client_hints
// https://developer.mozilla.org/en-US/docs/Web/API/User-Agent_Client_Hints_API
//
// upg: ask user for name / icon.
//
// me()