window.trystero = (opts) => new Proxy({ profile:{ type: 'network', name: 'Peer2Peer', description: 'WebRTC over bittorrent for signaling & encryption', url: 'https://github.com/dmotz/trystero', protocol: 'trystero://', video: true, audio: true, chat: true, scene: true }, html: { generic: (opts) => `
` }, room: null, // { selfId: .... } when connected link: '', selfId: null, selfStream: null, nickname: '', connected: false, useWebcam: false, useChat: false, useScene: false, videos: {}, names: {}, ping: { send: null, get: null }, chat: { send: null, get: null }, name: { send: null, get: null }, href: { send: null, get: null }, init(){ frontend.plugin['trystero'] = this $connections.webcam = $connections.webcam.concat([this]) $connections.chatnetwork = $connections.chatnetwork.concat([this]) $connections.scene = $connections.scene.concat([this]) if( localStorage.getItem("selfId") ){ this.selfId = localStorage.getItem("selfId") }else{ this.selfId = String(Math.random()).substr(2) localStorage.setItem("selfId",this.selfId) } this.reactToConnectionHrefs() this.nickname = localStorage.getItem("nickname") || `human${String(Math.random()).substr(5,4)}` document.addEventListener('network.connect', (e) => this.connect(e.detail) ) document.addEventListener('network.init', () => { let meeting = network.getMeetingFromUrl(document.location.href) if( meeting.match(this.profile.protocol) ){ this.parseLink( meeting ) } }) }, confirmConnected(){ if( !this.connected ){ this.connected = true frontend.emit('network.connected',{plugin:this,username: this.nickname}) this.names[ this.selfId ] = this.nickname } }, async connect(opts){ // embedded https://github.com/dmotz/trystero (trystero-torrent.min.js build) if( opts.selectedWebcam == this.profile.name ) this.useWebcam = true if( opts.selectedChatnetwork == this.profile.name ) this.useChat = true if( opts.selectedScene == this.profile.name ) this.useScene = true if( this.useWebcam || this.useChat || this.useScene ){ this.createLink() // ensure link console.log("connecting "+this.profile.name) console.log("trystero link: "+this.link) this.room = joinRoom( {appId: 'xrfragment'}, this.link ) $chat.send({message:`Share the meeting link by clicking here`,class:['info'],timeout:10000}) $chat.send({message:"waiting for other humans..",class:['info'], timeout:5000}) // setup trystero events const [sendPing, getPing] = this.room.makeAction('ping') this.ping.send = sendPing this.ping.get = getPing const [sendName, getName] = this.room.makeAction('name') this.name.send = sendName this.name.get = getName // start pinging this.ping.pinger = setInterval( () => this.ping.send({ping:true}), 3000 ) this.ping.get((data,peerId) => this.confirmConnected() ) // listen for peers naming themselves this.name.get((name, peerId) => { this.confirmConnected() this.names[peerId] = name }) // send name to peers who join later this.room.onPeerJoin( (peerId) => { this.confirmConnected() this.names[peerId] = name this.name.send(this.nickname, peerId ) $chat.send({message:"a new human joined",class:['info']}) }) // delete name of people leaving this.room.onPeerLeave( (peerId) => delete this.names[peerId] ) if( this.useWebcam ) this.initWebcam() if( this.useChat ) this.initChat() if( this.useScene ) this.initScene() } }, initChat(){ const [sendChat, getChat] = this.room.makeAction('chat') this.chat.send = sendChat this.chat.get = getChat document.addEventListener('network.send', (e) => { this.chat.send({...e.detail, from: this.nickname, pos: network.pos }) // send to P2P network }) // prime chatlog of other people joining this.room.onPeerJoin( (peerId) => { if( $chat.getChatLog().length > 0 ) this.chat.send({prime: $chat.getChatLog() }, peerId ) }) // listen for chatmsg this.chat.get((data, peerId) => { if( data.prime ){ // first prime is 'truth' if( this.chat.primed || $chat.getChatLog().length > 0 ) return // only prime once $chat.$messages.innerHTML += data.prime $chat.$messages.scrollTop = $chat.$messages.scrollHeight // scroll down this.chat.primed = true }else $chat.send({ ...data}) // send to screen }) }, async initWebcam(){ if( !$connections.$audioInput.value && !$connections.$videoInput.value ) return // nothing to do // get a local audio stream from the microphone this.selfStream = await navigator.mediaDevices.getUserMedia({ audio: $connections.$audioInput.value, video: $connections.$videoInput.value }) this.room.addStream(this.selfStream) this.videos[ this.selfId ] = this.getVideo(this.selfId,{stream: this.selfStream}) // send stream + chatlog to peers who join later this.room.onPeerJoin( (peerId) => this.room.addStream( this.selfStream, peerId)) this.room.onPeerStream((stream, peerId) => { let video = this.getVideo(peerId,{create:true, stream}) this.videos[ this.names[peerId] || peerId ] = video }) this.room.onPeerLeave( (peerId) => { let video = this.getVideo(peerId) if( video ){ video.remove() delete this.videos[peerId] } }) }, initScene(){ // setup trystero events const [sendHref, getHref] = this.room.makeAction('name') this.href.send = sendHref this.href.get = getHref this.href.get((data,peerId) => { xrf.hashbus.pub(data.href) }) }, getVideo(peerId,opts){ opts = opts || {} let video = this.videos[ this.names[peerId] ] || this.videos[ peerId ] if (!video && opts.create) { video = document.createElement('video') video.autoplay = true // add video element to the DOM if( opts.stream ) video.srcObject = opts.stream console.log("creating video for peerId") $chat.$videos.appendChild(video) } }, send(opts){ $chat.send({...opts, source: 'trystero'}) }, createLink(opts){ let hash = document.location.hash if( !this.link ){ const meeting = network.getMeetingFromUrl(document.location.href) this.link = meeting.match("trystero://") ? meeting : `trystero://r/${network.randomRoom()}:bittorrent` } if( !hash.match('meet=') ) document.location.hash += `${hash.length > 1 ? '&' : '#'}meet=${this.link}` }, config(opts){ opts = {...opts, ...this.profile } this.el = document.createElement('div') this.el.innerHTML = this.html.generic(opts) this.el.querySelector('#nickname').value = this.nickname this.el.querySelector('#nickname').addEventListener('change', (e) => localStorage.setItem("nickname",e.target.value) ) // resolve ip return this.el }, info(opts){ window.notify(`${this.profile.name} is ${this.profile.description}