diff --git a/dist/trystero-torrent.min.js b/dist/trystero-torrent.min.js new file mode 100644 index 0000000..bc2da4a --- /dev/null +++ b/dist/trystero-torrent.min.js @@ -0,0 +1 @@ +const MAX_BUFFERED_AMOUNT=65536,ICECOMPLETE_TIMEOUT=5e3,CHANNEL_CLOSING_TIMEOUT=5e3;function randombytes(e){const t=new Uint8Array(e);for(let s=0;se),this.streams=e.streams||(e.stream?[e.stream]:[]),this.trickle=void 0===e.trickle||e.trickle,this.allowHalfTrickle=void 0!==e.allowHalfTrickle&&e.allowHalfTrickle,this.iceCompleteTimeout=e.iceCompleteTimeout||5e3,this.destroyed=!1,this.destroying=!1,this._connected=!1,this.remoteAddress=void 0,this.remoteFamily=void 0,this.remotePort=void 0,this.localAddress=void 0,this.localFamily=void 0,this.localPort=void 0,this._wrtc=e.wrtc&&"object"==typeof e.wrtc?e.wrtc:getBrowserRTC(),!this._wrtc)throw"undefined"==typeof window?errCode(new Error("No WebRTC support: Specify `opts.wrtc` option in this environment"),"ERR_WEBRTC_SUPPORT"):errCode(new Error("No WebRTC support: Not a supported browser"),"ERR_WEBRTC_SUPPORT");this._pcReady=!1,this._channelReady=!1,this._iceComplete=!1,this._iceCompleteTimer=null,this._channel=null,this._pendingCandidates=[],this._isNegotiating=!1,this._firstNegotiation=!0,this._batchedNegotiation=!1,this._queuedNegotiation=!1,this._sendersAwaitingStable=[],this._senderMap=new Map,this._closingInterval=null,this._remoteTracks=[],this._remoteStreams=[],this._chunk=null,this._cb=null,this._interval=null;try{this._pc=new this._wrtc.RTCPeerConnection(this.config)}catch(e){return void this.destroy(errCode(e,"ERR_PC_CONSTRUCTOR"))}this._isReactNativeWebrtc="number"==typeof this._pc._peerConnectionId,this._pc.oniceconnectionstatechange=()=>{this._onIceStateChange()},this._pc.onicegatheringstatechange=()=>{this._onIceStateChange()},this._pc.onconnectionstatechange=()=>{this._onConnectionStateChange()},this._pc.onsignalingstatechange=()=>{this._onSignalingStateChange()},this._pc.onicecandidate=e=>{this._onIceCandidate(e)},"object"==typeof this._pc.peerIdentity&&this._pc.peerIdentity.catch((e=>{this.destroy(errCode(e,"ERR_PC_PEER_IDENTITY"))})),this.initiator||this.channelNegotiated?this._setupData({channel:this._pc.createDataChannel(this.channelName,this.channelConfig)}):this._pc.ondatachannel=e=>{this._setupData(e)},this.streams&&this.streams.forEach((e=>{this.addStream(e)})),this._pc.ontrack=e=>{this._onTrack(e)},this._debug("initial negotiation"),this._needsNegotiation()}get bufferSize(){return this._channel&&this._channel.bufferedAmount||0}get connected(){return this._connected&&"open"===this._channel.readyState}address(){return{port:this.localPort,family:this.localFamily,address:this.localAddress}}signal(e){if(!this.destroying){if(this.destroyed)throw errCode(new Error("cannot signal after peer is destroyed"),"ERR_DESTROYED");if("string"==typeof e)try{e=JSON.parse(e)}catch(t){e={}}this._debug("signal()"),e.renegotiate&&this.initiator&&(this._debug("got request to renegotiate"),this._needsNegotiation()),e.transceiverRequest&&this.initiator&&(this._debug("got request for transceiver"),this.addTransceiver(e.transceiverRequest.kind,e.transceiverRequest.init)),e.candidate&&(this._pc.remoteDescription&&this._pc.remoteDescription.type?this._addIceCandidate(e.candidate):this._pendingCandidates.push(e.candidate)),e.sdp&&this._pc.setRemoteDescription(new this._wrtc.RTCSessionDescription(e)).then((()=>{this.destroyed||(this._pendingCandidates.forEach((e=>{this._addIceCandidate(e)})),this._pendingCandidates=[],"offer"===this._pc.remoteDescription.type&&this._createAnswer())})).catch((e=>{this.destroy(errCode(e,"ERR_SET_REMOTE_DESCRIPTION"))})),e.sdp||e.candidate||e.renegotiate||e.transceiverRequest||this.destroy(errCode(new Error("signal() called with invalid signal data"),"ERR_SIGNALING"))}}_addIceCandidate(e){const t=new this._wrtc.RTCIceCandidate(e);this._pc.addIceCandidate(t).catch((e=>{!t.address||t.address.endsWith(".local")?warn("Ignoring unsupported ICE candidate."):this.destroy(errCode(e,"ERR_ADD_ICE_CANDIDATE"))}))}send(e){if(!this.destroying){if(this.destroyed)throw errCode(new Error("cannot send after peer is destroyed"),"ERR_DESTROYED");this._channel.send(e)}}addTransceiver(e,t){if(!this.destroying){if(this.destroyed)throw errCode(new Error("cannot addTransceiver after peer is destroyed"),"ERR_DESTROYED");if(this._debug("addTransceiver()"),this.initiator)try{this._pc.addTransceiver(e,t),this._needsNegotiation()}catch(e){this.destroy(errCode(e,"ERR_ADD_TRANSCEIVER"))}else this.emit("signal",{type:"transceiverRequest",transceiverRequest:{kind:e,init:t}})}}addStream(e){if(!this.destroying){if(this.destroyed)throw errCode(new Error("cannot addStream after peer is destroyed"),"ERR_DESTROYED");this._debug("addStream()"),e.getTracks().forEach((t=>{this.addTrack(t,e)}))}}addTrack(e,t){if(this.destroying)return;if(this.destroyed)throw errCode(new Error("cannot addTrack after peer is destroyed"),"ERR_DESTROYED");this._debug("addTrack()");const s=this._senderMap.get(e)||new Map;let i=s.get(t);if(i)throw i.removed?errCode(new Error("Track has been removed. You should enable/disable tracks that you want to re-add."),"ERR_SENDER_REMOVED"):errCode(new Error("Track has already been added to that stream."),"ERR_SENDER_ALREADY_ADDED");i=this._pc.addTrack(e,t),s.set(t,i),this._senderMap.set(e,s),this._needsNegotiation()}replaceTrack(e,t,s){if(this.destroying)return;if(this.destroyed)throw errCode(new Error("cannot replaceTrack after peer is destroyed"),"ERR_DESTROYED");this._debug("replaceTrack()");const i=this._senderMap.get(e),r=i?i.get(s):null;if(!r)throw errCode(new Error("Cannot replace track that was never added."),"ERR_TRACK_NOT_ADDED");t&&this._senderMap.set(t,i),null!=r.replaceTrack?r.replaceTrack(t):this.destroy(errCode(new Error("replaceTrack is not supported in this browser"),"ERR_UNSUPPORTED_REPLACETRACK"))}removeTrack(e,t){if(this.destroying)return;if(this.destroyed)throw errCode(new Error("cannot removeTrack after peer is destroyed"),"ERR_DESTROYED");this._debug("removeSender()");const s=this._senderMap.get(e),i=s?s.get(t):null;if(!i)throw errCode(new Error("Cannot remove track that was never added."),"ERR_TRACK_NOT_ADDED");try{i.removed=!0,this._pc.removeTrack(i)}catch(e){"NS_ERROR_UNEXPECTED"===e.name?this._sendersAwaitingStable.push(i):this.destroy(errCode(e,"ERR_REMOVE_TRACK"))}this._needsNegotiation()}removeStream(e){if(!this.destroying){if(this.destroyed)throw errCode(new Error("cannot removeStream after peer is destroyed"),"ERR_DESTROYED");this._debug("removeSenders()"),e.getTracks().forEach((t=>{this.removeTrack(t,e)}))}}_needsNegotiation(){this._debug("_needsNegotiation"),this._batchedNegotiation||(this._batchedNegotiation=!0,queueMicrotask((()=>{this._batchedNegotiation=!1,this.initiator||!this._firstNegotiation?(this._debug("starting batched negotiation"),this.negotiate()):this._debug("non-initiator initial negotiation request discarded"),this._firstNegotiation=!1})))}negotiate(){if(!this.destroying){if(this.destroyed)throw errCode(new Error("cannot negotiate after peer is destroyed"),"ERR_DESTROYED");this.initiator?this._isNegotiating?(this._queuedNegotiation=!0,this._debug("already negotiating, queueing")):(this._debug("start negotiation"),setTimeout((()=>{this._createOffer()}),0)):this._isNegotiating?(this._queuedNegotiation=!0,this._debug("already negotiating, queueing")):(this._debug("requesting negotiation from initiator"),this.emit("signal",{type:"renegotiate",renegotiate:!0})),this._isNegotiating=!0}}destroy(e){this.destroyed||this.destroying||(this.destroying=!0,this._debug("destroying (error: %s)",e&&(e.message||e)),queueMicrotask((()=>{if(this.destroyed=!0,this.destroying=!1,this._debug("destroy (error: %s)",e&&(e.message||e)),this._connected=!1,this._pcReady=!1,this._channelReady=!1,this._remoteTracks=null,this._remoteStreams=null,this._senderMap=null,clearInterval(this._closingInterval),this._closingInterval=null,clearInterval(this._interval),this._interval=null,this._chunk=null,this._cb=null,this._channel){try{this._channel.close()}catch(e){}this._channel.onmessage=null,this._channel.onopen=null,this._channel.onclose=null,this._channel.onerror=null}if(this._pc){try{this._pc.close()}catch(e){}this._pc.oniceconnectionstatechange=null,this._pc.onicegatheringstatechange=null,this._pc.onsignalingstatechange=null,this._pc.onicecandidate=null,this._pc.ontrack=null,this._pc.ondatachannel=null}this._pc=null,this._channel=null,e&&this.emit("error",e),this.emit("close")})))}_setupData(e){if(!e.channel)return this.destroy(errCode(new Error("Data channel event is missing `channel` property"),"ERR_DATA_CHANNEL"));this._channel=e.channel,this._channel.binaryType="arraybuffer","number"==typeof this._channel.bufferedAmountLowThreshold&&(this._channel.bufferedAmountLowThreshold=65536),this.channelName=this._channel.label,this._channel.onmessage=e=>{this._onChannelMessage(e)},this._channel.onbufferedamountlow=()=>{this._onChannelBufferedAmountLow()},this._channel.onopen=()=>{this._onChannelOpen()},this._channel.onclose=()=>{this._onChannelClose()},this._channel.onerror=e=>{this.destroy(errCode(e,"ERR_DATA_CHANNEL"))};let t=!1;this._closingInterval=setInterval((()=>{this._channel&&"closing"===this._channel.readyState?(t&&this._onChannelClose(),t=!0):t=!1}),5e3)}_startIceCompleteTimeout(){this.destroyed||this._iceCompleteTimer||(this._debug("started iceComplete timeout"),this._iceCompleteTimer=setTimeout((()=>{this._iceComplete||(this._iceComplete=!0,this._debug("iceComplete timeout completed"),this.emit("iceTimeout"),this.emit("_iceComplete"))}),this.iceCompleteTimeout))}_createOffer(){this.destroyed||this._pc.createOffer(this.offerOptions).then((e=>{if(this.destroyed)return;this.trickle||this.allowHalfTrickle||(e.sdp=filterTrickle(e.sdp)),e.sdp=this.sdpTransform(e.sdp);const t=()=>{if(this.destroyed)return;const t=this._pc.localDescription||e;this._debug("signal"),this.emit("signal",{type:t.type,sdp:t.sdp})};this._pc.setLocalDescription(e).then((()=>{this._debug("createOffer success"),this.destroyed||(this.trickle||this._iceComplete?t():this.once("_iceComplete",t))})).catch((e=>{this.destroy(errCode(e,"ERR_SET_LOCAL_DESCRIPTION"))}))})).catch((e=>{this.destroy(errCode(e,"ERR_CREATE_OFFER"))}))}_requestMissingTransceivers(){this._pc.getTransceivers&&this._pc.getTransceivers().forEach((e=>{e.mid||!e.sender.track||e.requested||(e.requested=!0,this.addTransceiver(e.sender.track.kind))}))}_createAnswer(){this.destroyed||this._pc.createAnswer(this.answerOptions).then((e=>{if(this.destroyed)return;this.trickle||this.allowHalfTrickle||(e.sdp=filterTrickle(e.sdp)),e.sdp=this.sdpTransform(e.sdp);const t=()=>{if(this.destroyed)return;const t=this._pc.localDescription||e;this._debug("signal"),this.emit("signal",{type:t.type,sdp:t.sdp}),this.initiator||this._requestMissingTransceivers()};this._pc.setLocalDescription(e).then((()=>{this.destroyed||(this.trickle||this._iceComplete?t():this.once("_iceComplete",t))})).catch((e=>{this.destroy(errCode(e,"ERR_SET_LOCAL_DESCRIPTION"))}))})).catch((e=>{this.destroy(errCode(e,"ERR_CREATE_ANSWER"))}))}_onConnectionStateChange(){this.destroyed||"failed"===this._pc.connectionState&&this.destroy(errCode(new Error("Connection failed."),"ERR_CONNECTION_FAILURE"))}_onIceStateChange(){if(this.destroyed)return;const e=this._pc.iceConnectionState,t=this._pc.iceGatheringState;this._debug("iceStateChange (connection: %s) (gathering: %s)",e,t),this.emit("iceStateChange",e,t),"connected"!==e&&"completed"!==e||(this._pcReady=!0,this._maybeReady()),"failed"===e&&this.destroy(errCode(new Error("Ice connection failed."),"ERR_ICE_CONNECTION_FAILURE")),"closed"===e&&this.destroy(errCode(new Error("Ice connection closed."),"ERR_ICE_CONNECTION_CLOSED"))}getStats(e){const t=e=>("[object Array]"===Object.prototype.toString.call(e.values)&&e.values.forEach((t=>{Object.assign(e,t)})),e);0===this._pc.getStats.length||this._isReactNativeWebrtc?this._pc.getStats().then((s=>{const i=[];s.forEach((e=>{i.push(t(e))})),e(null,i)}),(t=>e(t))):this._pc.getStats.length>0?this._pc.getStats((s=>{if(this.destroyed)return;const i=[];s.result().forEach((e=>{const s={};e.names().forEach((t=>{s[t]=e.stat(t)})),s.id=e.id,s.type=e.type,s.timestamp=e.timestamp,i.push(t(s))})),e(null,i)}),(t=>e(t))):e(null,[])}_maybeReady(){if(this._debug("maybeReady pc %s channel %s",this._pcReady,this._channelReady),this._connected||this._connecting||!this._pcReady||!this._channelReady)return;this._connecting=!0;const e=()=>{this.destroyed||this.getStats(((t,s)=>{if(this.destroyed)return;t&&(s=[]);const i={},r={},n={};let o=!1;s.forEach((e=>{"remotecandidate"!==e.type&&"remote-candidate"!==e.type||(i[e.id]=e),"localcandidate"!==e.type&&"local-candidate"!==e.type||(r[e.id]=e),"candidatepair"!==e.type&&"candidate-pair"!==e.type||(n[e.id]=e)}));const a=e=>{o=!0;let t=r[e.localCandidateId];t&&(t.ip||t.address)?(this.localAddress=t.ip||t.address,this.localPort=Number(t.port)):t&&t.ipAddress?(this.localAddress=t.ipAddress,this.localPort=Number(t.portNumber)):"string"==typeof e.googLocalAddress&&(t=e.googLocalAddress.split(":"),this.localAddress=t[0],this.localPort=Number(t[1])),this.localAddress&&(this.localFamily=this.localAddress.includes(":")?"IPv6":"IPv4");let s=i[e.remoteCandidateId];s&&(s.ip||s.address)?(this.remoteAddress=s.ip||s.address,this.remotePort=Number(s.port)):s&&s.ipAddress?(this.remoteAddress=s.ipAddress,this.remotePort=Number(s.portNumber)):"string"==typeof e.googRemoteAddress&&(s=e.googRemoteAddress.split(":"),this.remoteAddress=s[0],this.remotePort=Number(s[1])),this.remoteAddress&&(this.remoteFamily=this.remoteAddress.includes(":")?"IPv6":"IPv4"),this._debug("connect local: %s:%s remote: %s:%s",this.localAddress,this.localPort,this.remoteAddress,this.remotePort)};if(s.forEach((e=>{"transport"===e.type&&e.selectedCandidatePairId&&a(n[e.selectedCandidatePairId]),("googCandidatePair"===e.type&&"true"===e.googActiveConnection||("candidatepair"===e.type||"candidate-pair"===e.type)&&e.selected)&&a(e)})),o||Object.keys(n).length&&!Object.keys(r).length){if(this._connecting=!1,this._connected=!0,this._chunk){try{this.send(this._chunk)}catch(t){return this.destroy(errCode(t,"ERR_DATA_CHANNEL"))}this._chunk=null,this._debug('sent chunk from "write before connect"');const e=this._cb;this._cb=null,e(null)}"number"!=typeof this._channel.bufferedAmountLowThreshold&&(this._interval=setInterval((()=>this._onInterval()),150),this._interval.unref&&this._interval.unref()),this._debug("connect"),this.emit("connect")}else setTimeout(e,100)}))};e()}_onInterval(){!this._cb||!this._channel||this._channel.bufferedAmount>65536||this._onChannelBufferedAmountLow()}_onSignalingStateChange(){this.destroyed||("stable"===this._pc.signalingState&&(this._isNegotiating=!1,this._debug("flushing sender queue",this._sendersAwaitingStable),this._sendersAwaitingStable.forEach((e=>{this._pc.removeTrack(e),this._queuedNegotiation=!0})),this._sendersAwaitingStable=[],this._queuedNegotiation?(this._debug("flushing negotiation queue"),this._queuedNegotiation=!1,this._needsNegotiation()):(this._debug("negotiated"),this.emit("negotiated"))),this._debug("signalingStateChange %s",this._pc.signalingState),this.emit("signalingStateChange",this._pc.signalingState))}_onIceCandidate(e){this.destroyed||(e.candidate&&this.trickle?this.emit("signal",{type:"candidate",candidate:{candidate:e.candidate.candidate,sdpMLineIndex:e.candidate.sdpMLineIndex,sdpMid:e.candidate.sdpMid}}):e.candidate||this._iceComplete||(this._iceComplete=!0,this.emit("_iceComplete")),e.candidate&&this._startIceCompleteTimeout())}_onChannelMessage(e){if(this.destroyed)return;let t=e.data;t instanceof ArrayBuffer&&(t=new Uint8Array(t)),this.emit("data",t)}_onChannelBufferedAmountLow(){if(this.destroyed||!this._cb)return;this._debug("ending backpressure: bufferedAmount %d",this._channel.bufferedAmount);const e=this._cb;this._cb=null,e(null)}_onChannelOpen(){this._connected||this.destroyed||(this._debug("on channel open"),this._channelReady=!0,this._maybeReady())}_onChannelClose(){this.destroyed||(this._debug("on channel close"),this.destroy())}_onTrack(e){this.destroyed||e.streams.forEach((t=>{this._debug("on track"),this.emit("track",e.track,t),this._remoteTracks.push({track:e.track,stream:t}),this._remoteStreams.some((e=>e.id===t.id))||(this._remoteStreams.push(t),queueMicrotask((()=>{this._debug("on stream"),this.emit("stream",t)})))}))}_debug(...e){this._doDebug&&(e[0]="["+this._id+"] "+e[0],console.log(...e))}on(e,t){const s=this._map;s.has(e)||s.set(e,new Set),s.get(e).add(t)}off(e,t){const s=this._map,i=s.get(e);i&&(i.delete(t),0===i.size&&s.delete(e))}once(e,t){const s=(...i)=>{this.off(e,s),t(...i)};this.on(e,s)}emit(e,...t){const s=this._map;if(s.has(e))for(const i of s.get(e))try{i(...t)}catch(e){console.error(e)}}}Peer.WEBRTC_SUPPORT=!!getBrowserRTC(),Peer.config={iceServers:[{urls:["stun:stun.l.google.com:19302","stun:global.stun.twilio.com:3478"]}],sdpSemantics:"unified-plan"},Peer.channelConfig={};const charSet="0123456789AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz",initPeer=(e,t,s)=>{const i=new Peer({initiator:e,trickle:t,config:s}),r=e=>i.__earlyDataBuffer.push(e);return i.on(events.data,r),i.__earlyDataBuffer=[],i.__drainEarlyData=e=>{i.off(events.data,r),i.__earlyDataBuffer.forEach(e),delete i.__earlyDataBuffer,delete i.__drainEarlyData},i},genId=e=>Array(e).fill().map((()=>charSet[Math.floor(62*Math.random())])).join(""),initGuard=(e,t)=>(s,i)=>{if(e[i])return e[i];if(!s)throw mkErr("requires a config map as the first argument");if(!s.appId&&!s.firebaseApp)throw mkErr("config map is missing appId field");if(!i)throw mkErr("namespace argument required");return e[i]=t(s,i)},libName="Trystero",selfId=genId(20),{keys:keys,values:values,entries:entries,fromEntries:fromEntries}=Object,noOp=()=>{},mkErr=e=>new Error(`${libName}: ${e}`),encodeBytes=e=>(new TextEncoder).encode(e),decodeBytes=e=>(new TextDecoder).decode(e),events=fromEntries(["close","connect","data","error","signal","stream","track"].map((e=>[e,e]))),combineChunks=e=>{const t=new Uint8Array(e.reduce(((e,t)=>e+t.byteLength),0));return e.reduce(((e,s)=>(t.set(s,e),e+s.byteLength)),0),t},sleep=e=>new Promise((t=>setTimeout(t,e))),TypedArray=Object.getPrototypeOf(Uint8Array),typeByteLimit=12,typeIndex=0,nonceIndex=12,tagIndex=13,progressIndex=14,payloadIndex=15,chunkSize=16369,oneByteMax=255,buffLowEvent="bufferedamountlow";var room=(e,t)=>{const s={},i={},r={},n={},o={},a={},c=(e,t)=>(e?Array.isArray(e)?e:[e]:keys(s)).flatMap((e=>{const i=s[e];return i?t(e,i):(console.warn(`${libName}: no peer with id ${e} found`),[])})),d=e=>{if(i[e])return[i[e].send,i[e].setOnComplete,i[e].setOnProgress];if(!e)throw mkErr("action type argument is required");const t=encodeBytes(e);if(t.byteLength>12)throw mkErr(`action type string "${e}" (${t.byteLength}b) exceeds byte limit (12). Hint: choose a shorter name.`);const r=new Uint8Array(12);r.set(t);let n=0;return i[e]={onComplete:noOp,onProgress:noOp,setOnComplete:t=>i[e]={...i[e],onComplete:t},setOnProgress:t=>i[e]={...i[e],onProgress:t},send:async(e,t,i,o)=>{if(i&&"object"!=typeof i)throw mkErr("action meta argument must be an object");if(void 0===e)throw mkErr("action data cannot be undefined");const a="string"!=typeof e,d=e instanceof Blob,h=d||e instanceof ArrayBuffer||e instanceof TypedArray;if(i&&!h)throw mkErr("action meta argument can only be used with binary data");const l=h?new Uint8Array(d?await e.arrayBuffer():e):encodeBytes(a?JSON.stringify(e):e),_=i?encodeBytes(JSON.stringify(i)):null,p=Math.ceil(l.byteLength/16369)+(i?1:0),u=new Array(p).fill().map(((e,t)=>{const s=t===p-1,o=i&&0===t,c=new Uint8Array(15+(o?_.byteLength:s?l.byteLength-16369*(p-(i?2:1)):16369));return c.set(r),c.set([n],12),c.set([s|o<<1|h<<2|a<<3],13),c.set([Math.round((t+1)/p*255)],14),c.set(i?o?_:l.subarray(16369*(t-1),16369*t):l.subarray(16369*t,16369*(t+1)),15),c}));return n=n+1&255,Promise.all(c(t,(async(e,t)=>{const r=t._channel;let n=0;for(;nr.bufferedAmountLowThreshold&&await new Promise((e=>{const t=()=>{r.removeEventListener(buffLowEvent,t),e()};r.addEventListener(buffLowEvent,t)})),!s[e])break;t.send(a),n++,o&&o(a[14]/255,e,i)}})))}},[i[e].send,i[e].setOnComplete,i[e].setOnProgress]},h=(e,t)=>{const s=new Uint8Array(t),n=decodeBytes(s.subarray(0,12)).replaceAll("\0",""),[o]=s.subarray(12,13),[a]=s.subarray(13,14),[c]=s.subarray(14,15),d=s.subarray(15),h=!!(1&a),l=!!(2&a),_=!!(4&a),p=!!(8&a);if(!i[n])throw mkErr(`received message with unregistered type (${n})`);r[e]||(r[e]={}),r[e][n]||(r[e][n]={});let u=r[e][n][o];if(u||(u=r[e][n][o]={chunks:[]}),l?u.meta=JSON.parse(decodeBytes(d)):u.chunks.push(d),i[n].onProgress(c/255,e,u.meta),!h)return;const g=combineChunks(u.chunks);if(_)i[n].onComplete(g,e,u.meta);else{const t=decodeBytes(g);i[n].onComplete(p?JSON.parse(t):t,e)}delete r[e][n][o]},[l,_]=d("__91n6__"),[p,u]=d("__90n6__"),[g,f]=d("__516n4L__"),[m,y]=d("__57r34m__"),[C,E]=d("__7r4ck__");let b=noOp,w=noOp,R=noOp,T=noOp;return e(((e,t)=>{if(s[t])return;const i=h.bind(null,t);s[t]=e,e.on(events.signal,(e=>g(e,t))),e.on(events.close,(()=>(e=>{s[e]&&(delete s[e],delete r[e],delete n[e],w(e))})(t))),e.on(events.data,i),e.on(events.stream,(e=>{R(e,t,o[t]),delete o[t]})),e.on(events.track,((e,s)=>{T(e,s,t,a[t]),delete a[t]})),e.on(events.error,(e=>{"ERR_DATA_CHANNEL"!==e.code&&console.error(e)})),b(t),e.__drainEarlyData(i)})),_(((e,t)=>p(null,t))),u(((e,t)=>{n[t]&&(n[t](),delete n[t])})),f(((e,t)=>{s[t]&&s[t].signal(e)})),y(((e,t)=>o[t]=e)),E(((e,t)=>a[t]=e)),{makeAction:d,ping:async e=>{if(!e)throw mkErr("ping() must be called with target peer ID");const t=Date.now();return l(null,e),await new Promise((t=>n[e]=t)),Date.now()-t},leave:()=>{entries(s).forEach((([e,t])=>{t.destroy(),delete s[e]})),t()},getPeers:()=>fromEntries(entries(s).map((([e,t])=>[e,t._pc]))),addStream:(e,t,s)=>c(t,(async(t,i)=>{s&&await m(s,t),i.addStream(e)})),removeStream:(e,t)=>c(t,((t,s)=>s.removeStream(e))),addTrack:(e,t,s,i)=>c(s,(async(s,r)=>{i&&await C(i,s),r.addTrack(e,t)})),removeTrack:(e,t,s)=>c(s,((s,i)=>i.removeTrack(e,t))),replaceTrack:(e,t,s,i,r)=>c(i,(async(i,n)=>{r&&await C(r,i),n.replaceTrack(e,t,s)})),onPeerJoin:e=>b=e,onPeerLeave:e=>w=e,onPeerStream:e=>R=e,onPeerTrack:e=>T=e}};const algo="AES-CBC",pack=e=>btoa(String.fromCharCode.apply(null,new Uint8Array(e))),unpack=e=>{const t=atob(e);return new Uint8Array(t.length).map(((e,s)=>t.charCodeAt(s))).buffer},genKey=async(e,t)=>crypto.subtle.importKey("raw",await crypto.subtle.digest({name:"SHA-256"},encodeBytes(`${e}:${t}`)),{name:algo},!1,["encrypt","decrypt"]),encrypt=async(e,t)=>{const s=crypto.getRandomValues(new Uint8Array(16));return JSON.stringify({c:pack(await crypto.subtle.encrypt({name:algo,iv:s},await e,encodeBytes(t))),iv:[...s]})},decrypt=async(e,t)=>{const{c:s,iv:i}=JSON.parse(t);return decodeBytes(await crypto.subtle.decrypt({name:algo,iv:new Uint8Array(i)},await e,unpack(s)))},occupiedRooms={},socketPromises={},sockets={},socketRetryTimeouts={},socketListeners={},hashLimit=20,offerPoolSize=10,defaultRedundancy=2,defaultAnnounceSecs=33,maxAnnounceSecs=120,trackerRetrySecs=4,trackerAction="announce",defaultTrackerUrls=["wss://tracker.webtorrent.dev","wss://tracker.files.fm:7073/announce","wss://fediverse.tv/tracker/socket","wss://tracker.openwebtorrent.com","wss://tracker.btorrent.xyz","wss://qot.abiir.top:443/announce","wss://spacetradersapi-chatbox.herokuapp.com:443/announce"],joinRoom=initGuard(occupiedRooms,((e,t)=>{const s={},i=e.password&&genKey(e.password,t),r=(e.trackerUrls||defaultTrackerUrls).slice(0,e.trackerUrls?e.trackerUrls.length:e.trackerRedundancy||2);if(!r.length)throw mkErr("trackerUrls is empty");const n=crypto.subtle.digest("SHA-1",encodeBytes(`${libName}:${e.appId}:${t}`)).then((e=>Array.from(new Uint8Array(e)).map((e=>e.toString(36))).join("").slice(0,20))),o=t=>fromEntries(Array(t).fill().map((()=>{const t=initPeer(!0,!1,e.rtcConfig);return[genId(20),{peer:t,offerP:new Promise((e=>t.once(events.signal,e)))}]}))),a=async(t,r)=>{const o=await n;let a;try{a=JSON.parse(r.data)}catch(r){return void console.error(`${libName}: received malformed SDP JSON`)}if(a.info_hash!==o||a.peer_id&&a.peer_id===selfId)return;const c=a["failure reason"];if(c)console.warn(`${libName}: torrent tracker failure (${c})`);else{if(a.interval&&a.interval>g&&a.interval<=120&&(clearInterval(f),g=a.interval,f=setInterval(h,1e3*g)),a.offer&&a.offer_id){if(s[a.peer_id]||y[a.offer_id])return;y[a.offer_id]=!0;const r=initPeer(!1,!1,e.rtcConfig);return r.once(events.signal,(async e=>t.send(JSON.stringify({answer:i?{...e,sdp:await encrypt(i,e.sdp)}:e,action:"announce",info_hash:o,peer_id:selfId,to_peer_id:a.peer_id,offer_id:a.offer_id})))),r.on(events.connect,(()=>_(r,a.peer_id))),r.on(events.close,(()=>p(r,a.peer_id,a.offer_id))),void r.signal(i?{...a.offer,sdp:await decrypt(i,a.offer.sdp)}:a.offer)}if(a.answer){if(s[a.peer_id]||y[a.offer_id])return;const e=u[a.offer_id];if(e){const{peer:t}=e;if(t.destroyed)return;y[a.offer_id]=!0,t.on(events.connect,(()=>_(t,a.peer_id,a.offer_id))),t.on(events.close,(()=>p(t,a.peer_id,a.offer_id))),t.signal(i?{...a.answer,sdp:await decrypt(i,a.answer.sdp)}:a.answer)}}}},c=async(e,t)=>e.send(JSON.stringify({action:"announce",info_hash:t,numwant:10,peer_id:selfId,offers:await Promise.all(entries(u).map((async([e,{offerP:t}])=>{const s=await t;return{offer_id:e,offer:i?{...s,sdp:await encrypt(i,s.sdp)}:s}})))})),d=(e,t,s)=>(s||!socketPromises[e]?(socketListeners[e]={...socketListeners[e],[t]:a},socketPromises[e]=new Promise((s=>{const i=new WebSocket(e);sockets[e]=i,i.addEventListener("open",(()=>{socketRetryTimeouts[e]=4e3,s(i)})),i.addEventListener("message",(t=>values(socketListeners[e]).forEach((e=>e(i,t))))),i.addEventListener("close",(async()=>{socketRetryTimeouts[e]=socketRetryTimeouts[e]??4e3,await sleep(socketRetryTimeouts[e]),socketRetryTimeouts[e]*=2,d(e,t,!0)}))}))):socketListeners[e][t]=a,socketPromises[e]),h=async()=>{const e=await n;u&&l(),u=o(10),r.forEach((async t=>{const s=await d(t,e);s.readyState===WebSocket.OPEN?c(s,e):s.readyState!==WebSocket.CONNECTING&&c(await d(t,e,!0),e)}))},l=()=>{entries(u).forEach((([e,{peer:t}])=>{y[e]||s[e]||t.destroy()})),y={}},_=(e,t,i)=>{m(e,t),s[t]=!0,i&&(s[i]=!0)},p=(e,t,i)=>{delete s[t],e.destroy();i in u&&(delete u[i],u={...u,...o(1)})};let u,g=33,f=setInterval(h,1e3*g),m=noOp,y={};return h(),room((e=>m=e),(async()=>{const e=await n;r.forEach((t=>delete socketListeners[t][e])),delete occupiedRooms[t],clearInterval(f),l()}))})),getTrackers=()=>({...sockets});export{getTrackers,joinRoom,selfId}; diff --git a/dist/utils.js b/dist/utils.js index 10bb54c..4f27546 100644 --- a/dist/utils.js +++ b/dist/utils.js @@ -55,13 +55,17 @@ function setupConsole(el){ function setupUrlBar(el,XRF){ let inIframe = window.location !== window.parent.location - let ids = ['#overlay','a#embed','a#source','a#model','#qrcode'] - let showButtons = () => { - ids.map( (i) => $(i).style.display = 'block' ) - $('a#more').style.display = 'none' + let ids = ['#overlay','a#embed','a#clone','a#model','a#meeting','#qrcode'] + let showButtons = (state) => { + ids.map( (i) => $(i).style.display = state ? 'inline-block' : 'none' ) + $('a#more').style.display = state ? 'none' : 'inline-block' if( inIframe ) $('#uri').style.display = 'block' } - $('a#more').addEventListener('click', () => showButtons() ) + $('a#more').addEventListener('click', () => showButtons(true) ) + $('a#meeting').addEventListener('click', () => { + document.querySelector('a-scene').setAttribute('meeting', 'id: xrfragments') + showButtons(false) + }) XRF.addEventListener('hash', () => reflectUrl() ) const reflectUrl = window.reflectUrl = (url) => { diff --git a/dist/xrfragment.aframe.all.js b/dist/xrfragment.aframe.all.js index 0a81c81..2527186 100644 --- a/dist/xrfragment.aframe.all.js +++ b/dist/xrfragment.aframe.all.js @@ -1,5 +1,5 @@ /* - * v0.5.1 generated at Tue Dec 12 06:06:16 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 07:05:52 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ @@ -2381,13 +2381,17 @@ function setupConsole(el){ function setupUrlBar(el,XRF){ let inIframe = window.location !== window.parent.location - let ids = ['#overlay','a#embed','a#source','a#model','#qrcode'] - let showButtons = () => { - ids.map( (i) => $(i).style.display = 'block' ) - $('a#more').style.display = 'none' + let ids = ['#overlay','a#embed','a#clone','a#model','a#meeting','#qrcode'] + let showButtons = (state) => { + ids.map( (i) => $(i).style.display = state ? 'inline-block' : 'none' ) + $('a#more').style.display = state ? 'none' : 'inline-block' if( inIframe ) $('#uri').style.display = 'block' } - $('a#more').addEventListener('click', () => showButtons() ) + $('a#more').addEventListener('click', () => showButtons(true) ) + $('a#meeting').addEventListener('click', () => { + document.querySelector('a-scene').setAttribute('meeting', 'id: xrfragments') + showButtons(false) + }) XRF.addEventListener('hash', () => reflectUrl() ) const reflectUrl = window.reflectUrl = (url) => { @@ -2768,6 +2772,280 @@ window.AFRAME.registerComponent('xrf', { }, }) +AFRAME.registerComponent('meeting', { + schema:{ + id:{ required:true, type:'string'} + }, + init: function(){ + // embed https://github.com/dmotz/trystero (trystero-torrent.min.js build) + + // add css+html + let el = document.createElement("div") + el.innerHTML += ` +
+
+
+ +
` + document.body.appendChild(el) + this.trysteroInit() + }, + + trysteroInit: async function(){ + const { joinRoom } = await import("/dist/trystero-torrent.min.js"); + + const roomname = document.location.href.replace(/#.*/,'') + const config = this.config = {appId: this.data.id } + const room = this.room = joinRoom(config, roomname ) + console.log("starting webrtc room: "+roomname) + + const idsToNames = this.idsToNames = {} + const [sendName, getName] = room.makeAction('name') + const [sendChat, getChat] = this.room.makeAction('chat') + this.sendChat = sendChat + this.sendName = sendName + this.getChat = getChat + this.getName = getName + + // tell other peers currently in the room our name + let name = prompt('enter your name:') + idsToNames[ room.selfId ] = name.substr(0,15) + sendName( name ) + + // listen for peers naming themselves + getName((name, peerId) => (idsToNames[peerId] = name)) + + this.initChat() + + // this object can store audio instances for later + const peerAudios = this.peerAudios = {} + const peerVideos = this.peerVideos = {} + // get a local audio stream from the microphone + const selfStream = await navigator.mediaDevices.getUserMedia({ + audio: true, + video: { + width: 320, + height: 240, + frameRate: { + ideal: 30, + min: 10 + } + } + }) + let meVideo = this.addVideo(selfStream, room.selfId) + meVideo.muted = true + + // send stream to peers currently in the room + room.addStream(selfStream) + + // send stream + chatlog to peers who join later + room.onPeerJoin( (peerId) => { + console.log(`${idsToNames[peerId] || 'a visitor'} joined`) + room.addStream(selfStream, peerId) + sendName( name, peerId) + this.sendChat({prime: this.chat.log}, peerId ) + }) + + room.onPeerLeave( (peerId) => { + console.log(`${idsToNames[peerId] || 'a visitor'} left`) + if( peerVideos[peerId] ){ + peerVideos[peerId].remove() + delete peerVideos[peerId] + } + delete idsToNames[peerId] + }) + + // handle streams from other peers + room.onPeerStream((stream, peerId) => { + // create an audio instance and set the incoming stream + const audio = new Audio() + audio.srcObject = stream + audio.autoplay = true + + // add the audio to peerAudio object if you want to address it for something + // later (volume, etc.) + peerAudios[peerId] = audio + }) + + room.onPeerStream((stream, peerId) => { + this.addVideo(stream,peerId) + }) + + }, + + addVideo: function(stream,peerId){ + let video = this.peerVideos[peerId] + const videoContainer = document.getElementById('videos') + // if this peer hasn't sent a stream before, create a video element + if (!video) { + video = document.createElement('video') + video.autoplay = true + + // add video element to the DOM + videoContainer.appendChild(video) + } + + video.srcObject = stream + video.resize = (state) => { + if( video.resize.state == undefined ) video.resize.state = false + if( state == undefined ) state = (video.resize.state = !video.resize.state ) + video.style.width = state ? '320px' : '80px' + video.style.height = state ? '200px' : '60px' + } + + video.addEventListener('click', () => video.resize() ) + + this.peerVideos[peerId] = video + return video + }, + + createElement: function(str){ + let el = document.createElement("div") + el.className = "msg" + el.innerHTML = this.linkify(str) + return el + }, + + // central function to broadcast stuff to chat + send: function(str){ + if( !this.sendChat ) return + this.sendChat({content:str}) // send to network + this.chat.append(str) // send to screen + }, + + initChat: function(){ + + let chatline = this.chatline = document.querySelector("#chatline") + let chat = this.chat = document.querySelector("#chat") + chat.log = [] // save raw chatlog to prime new visitors + chat.append = (str) => { + if( !str ) return + str = str.replace('\n', "
") + this.chat.appendChild(this.createElement(str)) // send to screen + this.chat.innerHTML += '
' + this.chat.log.push(str) + } + + let send = () => { + let str = `${this.idsToNames[ this.room.selfId ]}: ${chatline.value.substr(0,65515).trim()}` + this.send(str) + } + + chatline.addEventListener("keydown", (e) => { + if( e.key !== "Enter" ) return + send() + chatline.value = '' + }) + + // listen for chatmsg + this.getChat((data, peerId) => { + if( data.prime ){ + if( this.chat.primed ) return // only prime once + console.log("receiving prime") + data.prime.map( (l) => chat.append(l) ) // send log to screen + this.chat.primed = true + } + chat.append(data.content) // send to screen + }) + + return this + }, + + linkify: function(t){ + const isValidHttpUrl = s => { + let u + try {u = new URL(s)} + catch (_) {return false} + return u.protocol.startsWith("http") + } + const m = t.match(/(?<=\s|^)[a-zA-Z0-9-:/]+\.[a-zA-Z0-9-].+?(?=[.,;:?!-]?(?:\s|$))/g) + if (!m) return t + const a = [] + m.forEach(x => { + const [t1, ...t2] = t.split(x) + a.push(t1) + t = t2.join(x) + const y = (!(x.match(/:\/\//)) ? 'https://' : '') + x + if (isNaN(x) && isValidHttpUrl(y)) + a.push('' + y.split('/')[2] + '') + else + a.push(x) + }) + a.push(t) + return a.join('') + } + +}); // look-controls turns off autoUpdateMatrix (of player) which // will break teleporting and other stuff // overriding this is easier then adding updateMatrixWorld() everywhere else diff --git a/dist/xrfragment.aframe.js b/dist/xrfragment.aframe.js index 15cebad..8f31f09 100644 --- a/dist/xrfragment.aframe.js +++ b/dist/xrfragment.aframe.js @@ -1,5 +1,5 @@ /* - * v0.5.1 generated at Tue Dec 12 06:06:16 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 07:05:52 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ @@ -2375,13 +2375,17 @@ function setupConsole(el){ function setupUrlBar(el,XRF){ let inIframe = window.location !== window.parent.location - let ids = ['#overlay','a#embed','a#source','a#model','#qrcode'] - let showButtons = () => { - ids.map( (i) => $(i).style.display = 'block' ) - $('a#more').style.display = 'none' + let ids = ['#overlay','a#embed','a#clone','a#model','a#meeting','#qrcode'] + let showButtons = (state) => { + ids.map( (i) => $(i).style.display = state ? 'inline-block' : 'none' ) + $('a#more').style.display = state ? 'none' : 'inline-block' if( inIframe ) $('#uri').style.display = 'block' } - $('a#more').addEventListener('click', () => showButtons() ) + $('a#more').addEventListener('click', () => showButtons(true) ) + $('a#meeting').addEventListener('click', () => { + document.querySelector('a-scene').setAttribute('meeting', 'id: xrfragments') + showButtons(false) + }) XRF.addEventListener('hash', () => reflectUrl() ) const reflectUrl = window.reflectUrl = (url) => { @@ -2762,6 +2766,280 @@ window.AFRAME.registerComponent('xrf', { }, }) +AFRAME.registerComponent('meeting', { + schema:{ + id:{ required:true, type:'string'} + }, + init: function(){ + // embed https://github.com/dmotz/trystero (trystero-torrent.min.js build) + + // add css+html + let el = document.createElement("div") + el.innerHTML += ` +
+
+
+ +
` + document.body.appendChild(el) + this.trysteroInit() + }, + + trysteroInit: async function(){ + const { joinRoom } = await import("/dist/trystero-torrent.min.js"); + + const roomname = document.location.href.replace(/#.*/,'') + const config = this.config = {appId: this.data.id } + const room = this.room = joinRoom(config, roomname ) + console.log("starting webrtc room: "+roomname) + + const idsToNames = this.idsToNames = {} + const [sendName, getName] = room.makeAction('name') + const [sendChat, getChat] = this.room.makeAction('chat') + this.sendChat = sendChat + this.sendName = sendName + this.getChat = getChat + this.getName = getName + + // tell other peers currently in the room our name + let name = prompt('enter your name:') + idsToNames[ room.selfId ] = name.substr(0,15) + sendName( name ) + + // listen for peers naming themselves + getName((name, peerId) => (idsToNames[peerId] = name)) + + this.initChat() + + // this object can store audio instances for later + const peerAudios = this.peerAudios = {} + const peerVideos = this.peerVideos = {} + // get a local audio stream from the microphone + const selfStream = await navigator.mediaDevices.getUserMedia({ + audio: true, + video: { + width: 320, + height: 240, + frameRate: { + ideal: 30, + min: 10 + } + } + }) + let meVideo = this.addVideo(selfStream, room.selfId) + meVideo.muted = true + + // send stream to peers currently in the room + room.addStream(selfStream) + + // send stream + chatlog to peers who join later + room.onPeerJoin( (peerId) => { + console.log(`${idsToNames[peerId] || 'a visitor'} joined`) + room.addStream(selfStream, peerId) + sendName( name, peerId) + this.sendChat({prime: this.chat.log}, peerId ) + }) + + room.onPeerLeave( (peerId) => { + console.log(`${idsToNames[peerId] || 'a visitor'} left`) + if( peerVideos[peerId] ){ + peerVideos[peerId].remove() + delete peerVideos[peerId] + } + delete idsToNames[peerId] + }) + + // handle streams from other peers + room.onPeerStream((stream, peerId) => { + // create an audio instance and set the incoming stream + const audio = new Audio() + audio.srcObject = stream + audio.autoplay = true + + // add the audio to peerAudio object if you want to address it for something + // later (volume, etc.) + peerAudios[peerId] = audio + }) + + room.onPeerStream((stream, peerId) => { + this.addVideo(stream,peerId) + }) + + }, + + addVideo: function(stream,peerId){ + let video = this.peerVideos[peerId] + const videoContainer = document.getElementById('videos') + // if this peer hasn't sent a stream before, create a video element + if (!video) { + video = document.createElement('video') + video.autoplay = true + + // add video element to the DOM + videoContainer.appendChild(video) + } + + video.srcObject = stream + video.resize = (state) => { + if( video.resize.state == undefined ) video.resize.state = false + if( state == undefined ) state = (video.resize.state = !video.resize.state ) + video.style.width = state ? '320px' : '80px' + video.style.height = state ? '200px' : '60px' + } + + video.addEventListener('click', () => video.resize() ) + + this.peerVideos[peerId] = video + return video + }, + + createElement: function(str){ + let el = document.createElement("div") + el.className = "msg" + el.innerHTML = this.linkify(str) + return el + }, + + // central function to broadcast stuff to chat + send: function(str){ + if( !this.sendChat ) return + this.sendChat({content:str}) // send to network + this.chat.append(str) // send to screen + }, + + initChat: function(){ + + let chatline = this.chatline = document.querySelector("#chatline") + let chat = this.chat = document.querySelector("#chat") + chat.log = [] // save raw chatlog to prime new visitors + chat.append = (str) => { + if( !str ) return + str = str.replace('\n', "
") + this.chat.appendChild(this.createElement(str)) // send to screen + this.chat.innerHTML += '
' + this.chat.log.push(str) + } + + let send = () => { + let str = `${this.idsToNames[ this.room.selfId ]}: ${chatline.value.substr(0,65515).trim()}` + this.send(str) + } + + chatline.addEventListener("keydown", (e) => { + if( e.key !== "Enter" ) return + send() + chatline.value = '' + }) + + // listen for chatmsg + this.getChat((data, peerId) => { + if( data.prime ){ + if( this.chat.primed ) return // only prime once + console.log("receiving prime") + data.prime.map( (l) => chat.append(l) ) // send log to screen + this.chat.primed = true + } + chat.append(data.content) // send to screen + }) + + return this + }, + + linkify: function(t){ + const isValidHttpUrl = s => { + let u + try {u = new URL(s)} + catch (_) {return false} + return u.protocol.startsWith("http") + } + const m = t.match(/(?<=\s|^)[a-zA-Z0-9-:/]+\.[a-zA-Z0-9-].+?(?=[.,;:?!-]?(?:\s|$))/g) + if (!m) return t + const a = [] + m.forEach(x => { + const [t1, ...t2] = t.split(x) + a.push(t1) + t = t2.join(x) + const y = (!(x.match(/:\/\//)) ? 'https://' : '') + x + if (isNaN(x) && isValidHttpUrl(y)) + a.push('' + y.split('/')[2] + '') + else + a.push(x) + }) + a.push(t) + return a.join('') + } + +}); // look-controls turns off autoUpdateMatrix (of player) which // will break teleporting and other stuff // overriding this is easier then adding updateMatrixWorld() everywhere else diff --git a/dist/xrfragment.module.js b/dist/xrfragment.module.js index b66ca5b..6a5924d 100644 --- a/dist/xrfragment.module.js +++ b/dist/xrfragment.module.js @@ -1,1741 +1,633 @@ /* - * v0.5.1 generated at Tue Dec 12 06:06:16 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 07:05:52 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 06:05:51 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 07:03:26 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 06:04:30 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 06:48:28 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 06:02:03 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 06:37:03 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 06:00:56 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 06:36:16 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:58:50 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 06:35:20 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:57:10 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 06:31:44 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:56:31 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 06:27:35 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:54:08 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 06:25:57 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:53:16 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 06:24:31 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:53:02 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 06:19:13 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:52:22 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 06:14:24 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:52:11 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 06:13:51 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:49:33 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 06:11:14 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:48:11 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 06:09:48 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:45:33 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 06:06:14 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:45:12 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 06:05:05 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:45:02 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 06:02:56 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:43:23 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 06:02:34 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:43:05 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 06:00:27 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:37:10 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 05:54:36 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:22:39 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 05:52:02 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:17:33 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 05:47:31 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:17:17 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 05:44:22 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:14:59 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 05:39:41 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:13:58 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 05:38:47 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:13:04 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 05:30:38 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:12:45 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 05:28:57 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:12:16 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 05:21:37 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:11:24 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 05:18:46 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:11:13 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 05:17:57 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:10:51 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 05:16:44 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:10:39 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 05:15:38 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:09:21 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 05:14:53 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:08:57 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 05:14:39 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:08:41 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 05:13:31 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:08:13 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 05:12:02 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:07:20 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:47:13 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:06:55 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:47:10 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:05:42 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:46:24 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 05:01:16 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:43:48 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 04:54:20 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:42:41 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 04:52:11 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:41:50 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 04:50:13 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:39:14 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 04:47:57 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:31:50 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 04:46:24 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:31:08 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 04:46:01 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:31:04 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 04:45:34 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:29:35 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 04:44:26 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:29:04 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 04:44:09 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:28:42 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 04:43:14 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:21:09 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 04:43:12 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:20:56 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 04:42:55 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:20:34 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 04:39:33 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:17:40 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 04:36:59 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:16:01 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 04:34:10 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:15:52 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 04:33:05 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:13:59 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 04:32:53 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:10:04 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 04:32:36 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:08:06 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Tue Dec 12 04:30:24 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:07:30 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Sun Dec 10 07:58:04 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:06:56 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Sun Dec 10 07:56:36 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:06:19 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Sun Dec 10 07:54:02 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:03:12 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 02:22:06 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 04:02:03 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 02:21:34 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:57:43 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 02:17:40 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:53:20 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 02:16:54 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:52:09 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 02:16:26 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:50:19 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 02:16:07 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:49:49 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 02:14:28 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:48:13 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 02:14:21 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:44:12 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 02:13:38 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:43:03 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 02:12:53 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:42:49 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 02:12:50 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:42:28 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 02:12:23 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:35:35 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 02:11:59 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:33:07 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 02:10:29 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:31:12 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 01:56:38 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:30:21 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 01:56:23 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:29:49 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 01:31:41 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:16:45 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 01:31:19 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:16:39 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 01:30:27 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:16:03 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 01:27:44 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:16:01 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 01:27:38 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:15:01 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 01:27:17 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:02:59 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 01:26:43 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 03:01:26 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 01:26:35 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 02:58:53 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 01:26:11 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 02:56:31 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 01:26:02 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 02:55:25 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 01:22:51 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 02:48:29 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 01:22:07 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 02:36:40 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 01:21:26 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 02:23:03 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 01:16:52 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 02:22:15 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 01:13:28 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 02:20:44 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 12:34:18 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 01:57:16 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 12:30:22 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 01:56:45 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 11:08:03 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 01:54:15 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 11:05:33 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 01:51:13 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 11:03:56 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 01:49:55 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 11:03:13 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 01:36:44 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 10:57:40 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 01:36:23 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 10:57:03 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 01:35:15 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 10:56:56 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 01:33:54 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 10:55:33 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 01:23:15 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 10:55:24 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 01:22:22 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 10:53:37 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 01:21:33 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 10:52:31 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 01:21:06 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 10:52:17 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 01:17:22 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 10:51:57 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 01:06:19 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 10:51:14 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 01:05:06 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 10:49:36 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 01:02:52 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 10:49:23 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 01:01:15 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 10:47:41 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 01:00:57 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 10:47:22 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 12:58:24 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 10:46:51 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 12:56:28 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 10:46:31 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 12:56:10 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 10:41:04 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 12:56:02 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Fri Dec 8 10:29:55 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 12:55:43 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Thu Dec 7 10:14:06 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 12:55:38 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Thu Dec 7 10:13:50 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 12:55:08 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Thu Dec 7 10:12:21 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 12:54:02 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Thu Dec 7 09:47:19 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 12:52:08 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Thu Dec 7 09:39:28 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 12:48:35 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Thu Dec 7 09:39:02 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 12:46:24 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Thu Dec 7 09:38:48 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 12:45:28 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ /* - * v0.5.1 generated at Thu Dec 7 09:38:16 AM CET 2023 + * v0.5.1 generated at Wed Dec 13 12:45:13 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ -/* - * v0.5.1 generated at Thu Dec 7 09:37:41 AM CET 2023 - * https://xrfragment.org - * SPDX-License-Identifier: MPL-2.0 - */ -/* - * v0.5.1 generated at Wed Dec 6 10:51:59 PM CET 2023 - * https://xrfragment.org - * SPDX-License-Identifier: MPL-2.0 - */ -var $hx_exports = typeof exports != "undefined" ? exports : typeof window != "undefined" ? window : typeof self != "undefined" ? self : this; -(function ($global) { "use strict"; -$hx_exports["xrfragment"] = $hx_exports["xrfragment"] || {}; -var EReg = function(r,opt) { - this.r = new RegExp(r,opt.split("u").join("")); -}; -EReg.__name__ = true; -EReg.prototype = { - match: function(s) { - if(this.r.global) { - this.r.lastIndex = 0; - } - this.r.m = this.r.exec(s); - this.r.s = s; - return this.r.m != null; - } - ,split: function(s) { - var d = "#__delim__#"; - return s.replace(this.r,d).split(d); - } -}; -var HxOverrides = function() { }; -HxOverrides.__name__ = true; -HxOverrides.cca = function(s,index) { - var x = s.charCodeAt(index); - if(x != x) { - return undefined; - } - return x; -}; -HxOverrides.substr = function(s,pos,len) { - if(len == null) { - len = s.length; - } else if(len < 0) { - if(pos == 0) { - len = s.length + len; - } else { - return ""; - } - } - return s.substr(pos,len); -}; -HxOverrides.now = function() { - return Date.now(); -}; -Math.__name__ = true; -var Reflect = function() { }; -Reflect.__name__ = true; -Reflect.field = function(o,field) { - try { - return o[field]; - } catch( _g ) { - return null; - } -}; -Reflect.fields = function(o) { - var a = []; - if(o != null) { - var hasOwnProperty = Object.prototype.hasOwnProperty; - for( var f in o ) { - if(f != "__id__" && f != "hx__closures__" && hasOwnProperty.call(o,f)) { - a.push(f); - } - } - } - return a; -}; -Reflect.deleteField = function(o,field) { - if(!Object.prototype.hasOwnProperty.call(o,field)) { - return false; - } - delete(o[field]); - return true; -}; -Reflect.copy = function(o) { - if(o == null) { - return null; - } - var o2 = { }; - var _g = 0; - var _g1 = Reflect.fields(o); - while(_g < _g1.length) { - var f = _g1[_g]; - ++_g; - o2[f] = Reflect.field(o,f); - } - return o2; -}; -var Std = function() { }; -Std.__name__ = true; -Std.string = function(s) { - return js_Boot.__string_rec(s,""); -}; -Std.parseInt = function(x) { - if(x != null) { - var _g = 0; - var _g1 = x.length; - while(_g < _g1) { - var i = _g++; - var c = x.charCodeAt(i); - if(c <= 8 || c >= 14 && c != 32 && c != 45) { - var nc = x.charCodeAt(i + 1); - var v = parseInt(x,nc == 120 || nc == 88 ? 16 : 10); - if(isNaN(v)) { - return null; - } else { - return v; - } - } - } - } - return null; -}; -var StringTools = function() { }; -StringTools.__name__ = true; -StringTools.isSpace = function(s,pos) { - var c = HxOverrides.cca(s,pos); - if(!(c > 8 && c < 14)) { - return c == 32; - } else { - return true; - } -}; -StringTools.ltrim = function(s) { - var l = s.length; - var r = 0; - while(r < l && StringTools.isSpace(s,r)) ++r; - if(r > 0) { - return HxOverrides.substr(s,r,l - r); - } else { - return s; - } -}; -StringTools.rtrim = function(s) { - var l = s.length; - var r = 0; - while(r < l && StringTools.isSpace(s,l - r - 1)) ++r; - if(r > 0) { - return HxOverrides.substr(s,0,l - r); - } else { - return s; - } -}; -StringTools.trim = function(s) { - return StringTools.ltrim(StringTools.rtrim(s)); -}; -var haxe_iterators_ArrayIterator = function(array) { - this.current = 0; - this.array = array; -}; -haxe_iterators_ArrayIterator.__name__ = true; -haxe_iterators_ArrayIterator.prototype = { - hasNext: function() { - return this.current < this.array.length; - } - ,next: function() { - return this.array[this.current++]; - } -}; -var js_Boot = function() { }; -js_Boot.__name__ = true; -js_Boot.__string_rec = function(o,s) { - if(o == null) { - return "null"; - } - if(s.length >= 5) { - return "<...>"; - } - var t = typeof(o); - if(t == "function" && (o.__name__ || o.__ename__)) { - t = "object"; - } - switch(t) { - case "function": - return ""; - case "object": - if(((o) instanceof Array)) { - var str = "["; - s += "\t"; - var _g = 0; - var _g1 = o.length; - while(_g < _g1) { - var i = _g++; - str += (i > 0 ? "," : "") + js_Boot.__string_rec(o[i],s); - } - str += "]"; - return str; - } - var tostr; - try { - tostr = o.toString; - } catch( _g ) { - return "???"; - } - if(tostr != null && tostr != Object.toString && typeof(tostr) == "function") { - var s2 = o.toString(); - if(s2 != "[object Object]") { - return s2; - } - } - var str = "{\n"; - s += "\t"; - var hasp = o.hasOwnProperty != null; - var k = null; - for( k in o ) { - if(hasp && !o.hasOwnProperty(k)) { - continue; - } - if(k == "prototype" || k == "__class__" || k == "__super__" || k == "__interfaces__" || k == "__properties__") { - continue; - } - if(str.length != 2) { - str += ", \n"; - } - str += s + k + " : " + js_Boot.__string_rec(o[k],s); - } - s = s.substring(1); - str += "\n" + s + "}"; - return str; - case "string": - return o; - default: - return String(o); - } -}; -var xrfragment_Filter = $hx_exports["xrfragment"]["Filter"] = function(str) { - this.q = { }; - this.str = ""; - if(str != null) { - this.parse(str); - } -}; -xrfragment_Filter.__name__ = true; -xrfragment_Filter.prototype = { - toObject: function() { - return Reflect.copy(this.q); - } - ,get: function() { - return Reflect.copy(this.q); - } - ,parse: function(str) { - var token = str.split(" "); - var q = { }; - var process = function(str,prefix) { - if(prefix == null) { - prefix = ""; - } - str = StringTools.trim(str); - var k = str.split("=")[0]; - var v = str.split("=")[1]; - var filter = { }; - if(q[prefix + k]) { - filter = q[prefix + k]; - } - if(xrfragment_XRF.isProp.match(str)) { - var oper = ""; - if(str.indexOf(">") != -1) { - oper = ">"; - } - if(str.indexOf("<") != -1) { - oper = "<"; - } - if(xrfragment_XRF.isExclude.match(k)) { - k = HxOverrides.substr(k,1,null); - } - v = HxOverrides.substr(v,oper.length,null); - if(oper.length == 0) { - oper = "="; - } - var rule = { }; - if(xrfragment_XRF.isNumber.match(v)) { - rule[oper] = parseFloat(v); - } else { - rule[oper] = v; - } - q["expr"] = rule; - } - var value = xrfragment_XRF.isDeep.match(str) ? k.split("*").length - 1 : 0; - q["deep"] = value; - var value = xrfragment_XRF.isExclude.match(str) ? false : true; - q["show"] = value; - var value = k.replace(xrfragment_XRF.operators.r,""); - q["key"] = value; - q["value"] = v; - }; - var _g = 0; - var _g1 = token.length; - while(_g < _g1) { - var i = _g++; - process(token[i]); - } - return this.q = q; - } - ,test: function(obj) { - var qualify = false; - var _g = 0; - var _g1 = Reflect.fields(obj); - while(_g < _g1.length) { - var k = _g1[_g]; - ++_g; - var v = Std.string(Reflect.field(obj,k)); - if(this.testProperty(k,v)) { - qualify = true; - } - } - var _g = 0; - var _g1 = Reflect.fields(obj); - while(_g < _g1.length) { - var k = _g1[_g]; - ++_g; - var v = Std.string(Reflect.field(obj,k)); - if(this.testProperty(k,v,true)) { - qualify = false; - } - } - return qualify; - } - ,testProperty: function(property,value,exclude) { - var conds = 0; - var fails = 0; - var qualify = 0; - var testprop = function(expr) { - conds += 1; - fails += expr ? 0 : 1; - return expr; - }; - if(this.q[value] != null) { - var v = this.q[value]; - if(v[property] != null) { - return v[property]; - } - } - if(Reflect.field(this.q,"expr")) { - var f = Reflect.field(this.q,"expr"); - if(!Reflect.field(this.q,"show")) { - if(Reflect.field(f,"!=") != null && testprop((value == null ? "null" : "" + value) == Std.string(Reflect.field(f,"!="))) && exclude) { - ++qualify; - } - } else { - if(Reflect.field(f,"*") != null && testprop(parseFloat(value) != null)) { - ++qualify; - } - if(Reflect.field(f,">") != null && testprop(parseFloat(value) >= parseFloat(Reflect.field(f,">")))) { - ++qualify; - } - if(Reflect.field(f,"<") != null && testprop(parseFloat(value) <= parseFloat(Reflect.field(f,"<")))) { - ++qualify; - } - if(Reflect.field(f,"=") != null && (testprop(value == Reflect.field(f,"=")) || testprop(parseFloat(value) == parseFloat(Reflect.field(f,"="))))) { - ++qualify; - } - } - } - return qualify > 0; - } -}; -var xrfragment_Parser = $hx_exports["xrfragment"]["Parser"] = function() { }; -xrfragment_Parser.__name__ = true; -xrfragment_Parser.parse = function(key,value,store,index) { - var Frag_h = Object.create(null); - Frag_h["#"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_PREDEFINED_VIEW | xrfragment_XRF.PV_EXECUTE; - Frag_h["src"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_URL; - Frag_h["href"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_URL | xrfragment_XRF.T_PREDEFINED_VIEW; - Frag_h["tag"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; - Frag_h["pos"] = xrfragment_XRF.PV_OVERRIDE | xrfragment_XRF.T_VECTOR3 | xrfragment_XRF.T_STRING | xrfragment_XRF.T_STRING_OBJ | xrfragment_XRF.METADATA | xrfragment_XRF.NAVIGATOR; - Frag_h["rot"] = xrfragment_XRF.QUERY_OPERATOR | xrfragment_XRF.PV_OVERRIDE | xrfragment_XRF.T_VECTOR3 | xrfragment_XRF.METADATA | xrfragment_XRF.NAVIGATOR; - Frag_h["t"] = xrfragment_XRF.ASSET | xrfragment_XRF.PV_OVERRIDE | xrfragment_XRF.T_FLOAT | xrfragment_XRF.T_VECTOR2 | xrfragment_XRF.T_STRING | xrfragment_XRF.NAVIGATOR | xrfragment_XRF.METADATA; - Frag_h["tv"] = xrfragment_XRF.ASSET | xrfragment_XRF.PV_OVERRIDE | xrfragment_XRF.T_FLOAT | xrfragment_XRF.T_VECTOR2 | xrfragment_XRF.T_VECTOR3 | xrfragment_XRF.NAVIGATOR | xrfragment_XRF.METADATA; - Frag_h["namespace"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; - Frag_h["SPDX"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; - Frag_h["unit"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; - Frag_h["description"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; - Frag_h["session"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_URL | xrfragment_XRF.PV_OVERRIDE | xrfragment_XRF.NAVIGATOR | xrfragment_XRF.METADATA | xrfragment_XRF.PROMPT; - var keyStripped = key.replace(xrfragment_XRF.operators.r,""); - var isPVDynamic = key.length > 0 && !Object.prototype.hasOwnProperty.call(Frag_h,key); - var isPVDefault = value.length == 0 && key.length > 0 && key == "#"; - if(isPVDynamic) { - var v = new xrfragment_XRF(key,xrfragment_XRF.PV_EXECUTE | xrfragment_XRF.NAVIGATOR,index); - v.validate(value); - store[keyStripped] = v; - return true; - } - var v = new xrfragment_XRF(key,Frag_h[key],index); - if(Object.prototype.hasOwnProperty.call(Frag_h,key)) { - if(!v.validate(value)) { - console.log("src/xrfragment/Parser.hx:66:","⚠ fragment '" + key + "' has incompatible value (" + value + ")"); - return false; - } - store[keyStripped] = v; - if(xrfragment_Parser.debug) { - console.log("src/xrfragment/Parser.hx:70:","✔ " + key + ": " + v.string); - } - } else { - if(typeof(value) == "string") { - v.guessType(v,value); - } - v.noXRF = true; - store[keyStripped] = v; - } - return true; -}; -var xrfragment_URI = $hx_exports["xrfragment"]["URI"] = function() { }; -xrfragment_URI.__name__ = true; -xrfragment_URI.parse = function(url,filter) { - var store = { }; - if(url == null || url.indexOf("#") == -1) { - return store; - } - var fragment = url.split("#"); - var splitArray = fragment[1].split("&"); - var _g = 0; - var _g1 = splitArray.length; - while(_g < _g1) { - var i = _g++; - var splitByEqual = splitArray[i].split("="); - var regexPlus = new EReg("\\+","g"); - var key = splitByEqual[0]; - var value = ""; - if(splitByEqual.length > 1) { - var s = regexPlus.split(splitByEqual[1]).join(" "); - value = decodeURIComponent(s.split("+").join(" ")); - } - var ok = xrfragment_Parser.parse(key,value,store,i); - } - if(filter != null && filter != 0) { - var _g = 0; - var _g1 = Reflect.fields(store); - while(_g < _g1.length) { - var key = _g1[_g]; - ++_g; - var xrf = store[key]; - if(!xrf.is(filter)) { - Reflect.deleteField(store,key); - } - } - } - return store; -}; -var xrfragment_XRF = $hx_exports["xrfragment"]["XRF"] = function(_fragment,_flags,_index) { - this.fragment = _fragment; - this.flags = _flags; - this.index = _index; -}; -xrfragment_XRF.__name__ = true; -xrfragment_XRF.set = function(flag,flags) { - return flags | flag; -}; -xrfragment_XRF.unset = function(flag,flags) { - return flags & ~flag; -}; -xrfragment_XRF.prototype = { - is: function(flag) { - var v = this.flags; - if(!(typeof(v) == "number" && ((v | 0) === v))) { - this.flags = 0; - } - return (this.flags & flag) != 0; - } - ,validate: function(value) { - this.guessType(this,value); - var ok = true; - if(!this.is(xrfragment_XRF.T_FLOAT) && this.is(xrfragment_XRF.T_VECTOR2) && !(typeof(this.x) == "number" && typeof(this.y) == "number")) { - ok = false; - } - if(!(this.is(xrfragment_XRF.T_VECTOR2) || this.is(xrfragment_XRF.T_STRING)) && this.is(xrfragment_XRF.T_VECTOR3) && !(typeof(this.x) == "number" && typeof(this.y) == "number" && typeof(this.z) == "number")) { - ok = false; - } - return ok; - } - ,guessType: function(v,str) { - v.string = str; - if(typeof(str) != "string") { - return; - } - if(str.length > 0) { - if(str.split(",").length > 1) { - var xyzw = str.split(","); - if(xyzw.length > 0) { - v.x = parseFloat(xyzw[0]); - } - if(xyzw.length > 1) { - v.y = parseFloat(xyzw[1]); - } - if(xyzw.length > 2) { - v.z = parseFloat(xyzw[2]); - } - if(xyzw.length > 3) { - v.w = parseFloat(xyzw[3]); - } - } - if(xrfragment_XRF.isColor.match(str)) { - v.color = str; - } - if(xrfragment_XRF.isFloat.match(str)) { - v.x = parseFloat(str); - v.float = v.x; - } - if(xrfragment_XRF.isInt.match(str)) { - v.int = Std.parseInt(str); - v.x = v.int; - } - v.filter = new xrfragment_Filter(v.fragment + "=" + v.string); - } else { - v.filter = new xrfragment_Filter(v.fragment); - } - } -}; -if(typeof(performance) != "undefined" ? typeof(performance.now) == "function" : false) { - HxOverrides.now = performance.now.bind(performance); -} -String.__name__ = true; -Array.__name__ = true; -js_Boot.__toStr = ({ }).toString; -xrfragment_Parser.error = ""; -xrfragment_Parser.debug = false; -xrfragment_XRF.ASSET = 1; -xrfragment_XRF.PROP_BIND = 2; -xrfragment_XRF.QUERY_OPERATOR = 4; -xrfragment_XRF.PROMPT = 8; -xrfragment_XRF.ROUNDROBIN = 16; -xrfragment_XRF.NAVIGATOR = 32; -xrfragment_XRF.METADATA = 64; -xrfragment_XRF.PV_OVERRIDE = 128; -xrfragment_XRF.PV_EXECUTE = 256; -xrfragment_XRF.T_COLOR = 8192; -xrfragment_XRF.T_INT = 16384; -xrfragment_XRF.T_FLOAT = 32768; -xrfragment_XRF.T_VECTOR2 = 65536; -xrfragment_XRF.T_VECTOR3 = 131072; -xrfragment_XRF.T_URL = 262144; -xrfragment_XRF.T_PREDEFINED_VIEW = 524288; -xrfragment_XRF.T_STRING = 1048576; -xrfragment_XRF.T_STRING_OBJ = 2097152; -xrfragment_XRF.T_STRING_OBJ_PROP = 4194304; -xrfragment_XRF.isColor = new EReg("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$",""); -xrfragment_XRF.isInt = new EReg("^[-0-9]+$",""); -xrfragment_XRF.isFloat = new EReg("^[-0-9]+\\.[0-9]+$",""); -xrfragment_XRF.isVector = new EReg("([,]+|\\w)",""); -xrfragment_XRF.isUrl = new EReg("(://)?\\..*",""); -xrfragment_XRF.isUrlOrPretypedView = new EReg("(^#|://)?\\..*",""); -xrfragment_XRF.isString = new EReg(".*",""); -xrfragment_XRF.operators = new EReg("(^-|[\\*]+)",""); -xrfragment_XRF.isProp = new EReg("^.*=[><=]?",""); -xrfragment_XRF.isExclude = new EReg("^-",""); -xrfragment_XRF.isDeep = new EReg("\\*",""); -xrfragment_XRF.isNumber = new EReg("^[0-9\\.]+$",""); -})({}); -var xrfragment = $hx_exports["xrfragment"]; -export default xrfragment; -var $hx_exports = typeof exports != "undefined" ? exports : typeof window != "undefined" ? window : typeof self != "undefined" ? self : this; -(function ($global) { "use strict"; -$hx_exports["xrfragment"] = $hx_exports["xrfragment"] || {}; -var EReg = function(r,opt) { - this.r = new RegExp(r,opt.split("u").join("")); -}; -EReg.__name__ = true; -EReg.prototype = { - match: function(s) { - if(this.r.global) { - this.r.lastIndex = 0; - } - this.r.m = this.r.exec(s); - this.r.s = s; - return this.r.m != null; - } - ,split: function(s) { - var d = "#__delim__#"; - return s.replace(this.r,d).split(d); - } -}; -var HxOverrides = function() { }; -HxOverrides.__name__ = true; -HxOverrides.cca = function(s,index) { - var x = s.charCodeAt(index); - if(x != x) { - return undefined; - } - return x; -}; -HxOverrides.substr = function(s,pos,len) { - if(len == null) { - len = s.length; - } else if(len < 0) { - if(pos == 0) { - len = s.length + len; - } else { - return ""; - } - } - return s.substr(pos,len); -}; -HxOverrides.now = function() { - return Date.now(); -}; -Math.__name__ = true; -var Reflect = function() { }; -Reflect.__name__ = true; -Reflect.field = function(o,field) { - try { - return o[field]; - } catch( _g ) { - return null; - } -}; -Reflect.fields = function(o) { - var a = []; - if(o != null) { - var hasOwnProperty = Object.prototype.hasOwnProperty; - for( var f in o ) { - if(f != "__id__" && f != "hx__closures__" && hasOwnProperty.call(o,f)) { - a.push(f); - } - } - } - return a; -}; -Reflect.deleteField = function(o,field) { - if(!Object.prototype.hasOwnProperty.call(o,field)) { - return false; - } - delete(o[field]); - return true; -}; -Reflect.copy = function(o) { - if(o == null) { - return null; - } - var o2 = { }; - var _g = 0; - var _g1 = Reflect.fields(o); - while(_g < _g1.length) { - var f = _g1[_g]; - ++_g; - o2[f] = Reflect.field(o,f); - } - return o2; -}; -var Std = function() { }; -Std.__name__ = true; -Std.string = function(s) { - return js_Boot.__string_rec(s,""); -}; -Std.parseInt = function(x) { - if(x != null) { - var _g = 0; - var _g1 = x.length; - while(_g < _g1) { - var i = _g++; - var c = x.charCodeAt(i); - if(c <= 8 || c >= 14 && c != 32 && c != 45) { - var nc = x.charCodeAt(i + 1); - var v = parseInt(x,nc == 120 || nc == 88 ? 16 : 10); - if(isNaN(v)) { - return null; - } else { - return v; - } - } - } - } - return null; -}; -var StringTools = function() { }; -StringTools.__name__ = true; -StringTools.isSpace = function(s,pos) { - var c = HxOverrides.cca(s,pos); - if(!(c > 8 && c < 14)) { - return c == 32; - } else { - return true; - } -}; -StringTools.ltrim = function(s) { - var l = s.length; - var r = 0; - while(r < l && StringTools.isSpace(s,r)) ++r; - if(r > 0) { - return HxOverrides.substr(s,r,l - r); - } else { - return s; - } -}; -StringTools.rtrim = function(s) { - var l = s.length; - var r = 0; - while(r < l && StringTools.isSpace(s,l - r - 1)) ++r; - if(r > 0) { - return HxOverrides.substr(s,0,l - r); - } else { - return s; - } -}; -StringTools.trim = function(s) { - return StringTools.ltrim(StringTools.rtrim(s)); -}; -var haxe_iterators_ArrayIterator = function(array) { - this.current = 0; - this.array = array; -}; -haxe_iterators_ArrayIterator.__name__ = true; -haxe_iterators_ArrayIterator.prototype = { - hasNext: function() { - return this.current < this.array.length; - } - ,next: function() { - return this.array[this.current++]; - } -}; -var js_Boot = function() { }; -js_Boot.__name__ = true; -js_Boot.__string_rec = function(o,s) { - if(o == null) { - return "null"; - } - if(s.length >= 5) { - return "<...>"; - } - var t = typeof(o); - if(t == "function" && (o.__name__ || o.__ename__)) { - t = "object"; - } - switch(t) { - case "function": - return ""; - case "object": - if(((o) instanceof Array)) { - var str = "["; - s += "\t"; - var _g = 0; - var _g1 = o.length; - while(_g < _g1) { - var i = _g++; - str += (i > 0 ? "," : "") + js_Boot.__string_rec(o[i],s); - } - str += "]"; - return str; - } - var tostr; - try { - tostr = o.toString; - } catch( _g ) { - return "???"; - } - if(tostr != null && tostr != Object.toString && typeof(tostr) == "function") { - var s2 = o.toString(); - if(s2 != "[object Object]") { - return s2; - } - } - var str = "{\n"; - s += "\t"; - var hasp = o.hasOwnProperty != null; - var k = null; - for( k in o ) { - if(hasp && !o.hasOwnProperty(k)) { - continue; - } - if(k == "prototype" || k == "__class__" || k == "__super__" || k == "__interfaces__" || k == "__properties__") { - continue; - } - if(str.length != 2) { - str += ", \n"; - } - str += s + k + " : " + js_Boot.__string_rec(o[k],s); - } - s = s.substring(1); - str += "\n" + s + "}"; - return str; - case "string": - return o; - default: - return String(o); - } -}; -var xrfragment_Filter = $hx_exports["xrfragment"]["Filter"] = function(str) { - this.q = { }; - this.str = ""; - if(str != null) { - this.parse(str); - } -}; -xrfragment_Filter.__name__ = true; -xrfragment_Filter.prototype = { - toObject: function() { - return Reflect.copy(this.q); - } - ,get: function() { - return Reflect.copy(this.q); - } - ,parse: function(str) { - var token = str.split(" "); - var q = { }; - var process = function(str,prefix) { - if(prefix == null) { - prefix = ""; - } - str = StringTools.trim(str); - var k = str.split("=")[0]; - var v = str.split("=")[1]; - var filter = { }; - if(q[prefix + k]) { - filter = q[prefix + k]; - } - if(xrfragment_XRF.isProp.match(str)) { - var oper = ""; - if(str.indexOf(">") != -1) { - oper = ">"; - } - if(str.indexOf("<") != -1) { - oper = "<"; - } - if(xrfragment_XRF.isExclude.match(k)) { - k = HxOverrides.substr(k,1,null); - } - v = HxOverrides.substr(v,oper.length,null); - if(oper.length == 0) { - oper = "="; - } - var rule = { }; - if(xrfragment_XRF.isNumber.match(v)) { - rule[oper] = parseFloat(v); - } else { - rule[oper] = v; - } - q["expr"] = rule; - } - var value = xrfragment_XRF.isDeep.match(str) ? k.split("*").length - 1 : 0; - q["deep"] = value; - var value = xrfragment_XRF.isExclude.match(str) ? false : true; - q["show"] = value; - var value = k.replace(xrfragment_XRF.operators.r,""); - q["key"] = value; - q["value"] = v; - }; - var _g = 0; - var _g1 = token.length; - while(_g < _g1) { - var i = _g++; - process(token[i]); - } - return this.q = q; - } - ,test: function(obj) { - var qualify = false; - var _g = 0; - var _g1 = Reflect.fields(obj); - while(_g < _g1.length) { - var k = _g1[_g]; - ++_g; - var v = Std.string(Reflect.field(obj,k)); - if(this.testProperty(k,v)) { - qualify = true; - } - } - var _g = 0; - var _g1 = Reflect.fields(obj); - while(_g < _g1.length) { - var k = _g1[_g]; - ++_g; - var v = Std.string(Reflect.field(obj,k)); - if(this.testProperty(k,v,true)) { - qualify = false; - } - } - return qualify; - } - ,testProperty: function(property,value,exclude) { - var conds = 0; - var fails = 0; - var qualify = 0; - var testprop = function(expr) { - conds += 1; - fails += expr ? 0 : 1; - return expr; - }; - if(this.q[value] != null) { - var v = this.q[value]; - if(v[property] != null) { - return v[property]; - } - } - if(Reflect.field(this.q,"expr")) { - var f = Reflect.field(this.q,"expr"); - if(!Reflect.field(this.q,"show")) { - if(Reflect.field(f,"!=") != null && testprop((value == null ? "null" : "" + value) == Std.string(Reflect.field(f,"!="))) && exclude) { - ++qualify; - } - } else { - if(Reflect.field(f,"*") != null && testprop(parseFloat(value) != null)) { - ++qualify; - } - if(Reflect.field(f,">") != null && testprop(parseFloat(value) >= parseFloat(Reflect.field(f,">")))) { - ++qualify; - } - if(Reflect.field(f,"<") != null && testprop(parseFloat(value) <= parseFloat(Reflect.field(f,"<")))) { - ++qualify; - } - if(Reflect.field(f,"=") != null && (testprop(value == Reflect.field(f,"=")) || testprop(parseFloat(value) == parseFloat(Reflect.field(f,"="))))) { - ++qualify; - } - } - } - return qualify > 0; - } -}; -var xrfragment_Parser = $hx_exports["xrfragment"]["Parser"] = function() { }; -xrfragment_Parser.__name__ = true; -xrfragment_Parser.parse = function(key,value,store,index) { - var Frag_h = Object.create(null); - Frag_h["#"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_PREDEFINED_VIEW | xrfragment_XRF.PV_EXECUTE; - Frag_h["src"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_URL; - Frag_h["href"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_URL | xrfragment_XRF.T_PREDEFINED_VIEW; - Frag_h["tag"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; - Frag_h["pos"] = xrfragment_XRF.PV_OVERRIDE | xrfragment_XRF.T_VECTOR3 | xrfragment_XRF.T_STRING | xrfragment_XRF.T_STRING_OBJ | xrfragment_XRF.METADATA | xrfragment_XRF.NAVIGATOR; - Frag_h["rot"] = xrfragment_XRF.QUERY_OPERATOR | xrfragment_XRF.PV_OVERRIDE | xrfragment_XRF.T_VECTOR3 | xrfragment_XRF.METADATA | xrfragment_XRF.NAVIGATOR; - Frag_h["t"] = xrfragment_XRF.ASSET | xrfragment_XRF.PV_OVERRIDE | xrfragment_XRF.T_FLOAT | xrfragment_XRF.T_VECTOR2 | xrfragment_XRF.T_STRING | xrfragment_XRF.NAVIGATOR | xrfragment_XRF.METADATA; - Frag_h["tv"] = xrfragment_XRF.ASSET | xrfragment_XRF.PV_OVERRIDE | xrfragment_XRF.T_FLOAT | xrfragment_XRF.T_VECTOR2 | xrfragment_XRF.T_VECTOR3 | xrfragment_XRF.NAVIGATOR | xrfragment_XRF.METADATA; - Frag_h["namespace"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; - Frag_h["SPDX"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; - Frag_h["unit"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; - Frag_h["description"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_STRING; - Frag_h["session"] = xrfragment_XRF.ASSET | xrfragment_XRF.T_URL | xrfragment_XRF.PV_OVERRIDE | xrfragment_XRF.NAVIGATOR | xrfragment_XRF.METADATA | xrfragment_XRF.PROMPT; - var keyStripped = key.replace(xrfragment_XRF.operators.r,""); - var isPVDynamic = key.length > 0 && !Object.prototype.hasOwnProperty.call(Frag_h,key); - var isPVDefault = value.length == 0 && key.length > 0 && key == "#"; - if(isPVDynamic) { - var v = new xrfragment_XRF(key,xrfragment_XRF.PV_EXECUTE | xrfragment_XRF.NAVIGATOR,index); - v.validate(value); - store[keyStripped] = v; - return true; - } - var v = new xrfragment_XRF(key,Frag_h[key],index); - if(Object.prototype.hasOwnProperty.call(Frag_h,key)) { - if(!v.validate(value)) { - console.log("src/xrfragment/Parser.hx:66:","⚠ fragment '" + key + "' has incompatible value (" + value + ")"); - return false; - } - store[keyStripped] = v; - if(xrfragment_Parser.debug) { - console.log("src/xrfragment/Parser.hx:70:","✔ " + key + ": " + v.string); - } - } else { - if(typeof(value) == "string") { - v.guessType(v,value); - } - v.noXRF = true; - store[keyStripped] = v; - } - return true; -}; -var xrfragment_URI = $hx_exports["xrfragment"]["URI"] = function() { }; -xrfragment_URI.__name__ = true; -xrfragment_URI.parse = function(url,filter) { - var store = { }; - if(url == null || url.indexOf("#") == -1) { - return store; - } - var fragment = url.split("#"); - var splitArray = fragment[1].split("&"); - var _g = 0; - var _g1 = splitArray.length; - while(_g < _g1) { - var i = _g++; - var splitByEqual = splitArray[i].split("="); - var regexPlus = new EReg("\\+","g"); - var key = splitByEqual[0]; - var value = ""; - if(splitByEqual.length > 1) { - var s = regexPlus.split(splitByEqual[1]).join(" "); - value = decodeURIComponent(s.split("+").join(" ")); - } - var ok = xrfragment_Parser.parse(key,value,store,i); - } - if(filter != null && filter != 0) { - var _g = 0; - var _g1 = Reflect.fields(store); - while(_g < _g1.length) { - var key = _g1[_g]; - ++_g; - var xrf = store[key]; - if(!xrf.is(filter)) { - Reflect.deleteField(store,key); - } - } - } - return store; -}; -var xrfragment_XRF = $hx_exports["xrfragment"]["XRF"] = function(_fragment,_flags,_index) { - this.fragment = _fragment; - this.flags = _flags; - this.index = _index; -}; -xrfragment_XRF.__name__ = true; -xrfragment_XRF.set = function(flag,flags) { - return flags | flag; -}; -xrfragment_XRF.unset = function(flag,flags) { - return flags & ~flag; -}; -xrfragment_XRF.prototype = { - is: function(flag) { - var v = this.flags; - if(!(typeof(v) == "number" && ((v | 0) === v))) { - this.flags = 0; - } - return (this.flags & flag) != 0; - } - ,validate: function(value) { - this.guessType(this,value); - var ok = true; - if(!this.is(xrfragment_XRF.T_FLOAT) && this.is(xrfragment_XRF.T_VECTOR2) && !(typeof(this.x) == "number" && typeof(this.y) == "number")) { - ok = false; - } - if(!(this.is(xrfragment_XRF.T_VECTOR2) || this.is(xrfragment_XRF.T_STRING)) && this.is(xrfragment_XRF.T_VECTOR3) && !(typeof(this.x) == "number" && typeof(this.y) == "number" && typeof(this.z) == "number")) { - ok = false; - } - return ok; - } - ,guessType: function(v,str) { - v.string = str; - if(typeof(str) != "string") { - return; - } - if(str.length > 0) { - if(str.split(",").length > 1) { - var xyzw = str.split(","); - if(xyzw.length > 0) { - v.x = parseFloat(xyzw[0]); - } - if(xyzw.length > 1) { - v.y = parseFloat(xyzw[1]); - } - if(xyzw.length > 2) { - v.z = parseFloat(xyzw[2]); - } - if(xyzw.length > 3) { - v.w = parseFloat(xyzw[3]); - } - } - if(xrfragment_XRF.isColor.match(str)) { - v.color = str; - } - if(xrfragment_XRF.isFloat.match(str)) { - v.x = parseFloat(str); - v.float = v.x; - } - if(xrfragment_XRF.isInt.match(str)) { - v.int = Std.parseInt(str); - v.x = v.int; - } - v.filter = new xrfragment_Filter(v.fragment + "=" + v.string); - } else { - v.filter = new xrfragment_Filter(v.fragment); - } - } -}; -if(typeof(performance) != "undefined" ? typeof(performance.now) == "function" : false) { - HxOverrides.now = performance.now.bind(performance); -} -String.__name__ = true; -Array.__name__ = true; -js_Boot.__toStr = ({ }).toString; -xrfragment_Parser.error = ""; -xrfragment_Parser.debug = false; -xrfragment_XRF.ASSET = 1; -xrfragment_XRF.PROP_BIND = 2; -xrfragment_XRF.QUERY_OPERATOR = 4; -xrfragment_XRF.PROMPT = 8; -xrfragment_XRF.ROUNDROBIN = 16; -xrfragment_XRF.NAVIGATOR = 32; -xrfragment_XRF.METADATA = 64; -xrfragment_XRF.PV_OVERRIDE = 128; -xrfragment_XRF.PV_EXECUTE = 256; -xrfragment_XRF.T_COLOR = 8192; -xrfragment_XRF.T_INT = 16384; -xrfragment_XRF.T_FLOAT = 32768; -xrfragment_XRF.T_VECTOR2 = 65536; -xrfragment_XRF.T_VECTOR3 = 131072; -xrfragment_XRF.T_URL = 262144; -xrfragment_XRF.T_PREDEFINED_VIEW = 524288; -xrfragment_XRF.T_STRING = 1048576; -xrfragment_XRF.T_STRING_OBJ = 2097152; -xrfragment_XRF.T_STRING_OBJ_PROP = 4194304; -xrfragment_XRF.isColor = new EReg("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$",""); -xrfragment_XRF.isInt = new EReg("^[-0-9]+$",""); -xrfragment_XRF.isFloat = new EReg("^[-0-9]+\\.[0-9]+$",""); -xrfragment_XRF.isVector = new EReg("([,]+|\\w)",""); -xrfragment_XRF.isUrl = new EReg("(://)?\\..*",""); -xrfragment_XRF.isUrlOrPretypedView = new EReg("(^#|://)?\\..*",""); -xrfragment_XRF.isString = new EReg(".*",""); -xrfragment_XRF.operators = new EReg("(^-|[\\*]+)",""); -xrfragment_XRF.isProp = new EReg("^.*=[><=]?",""); -xrfragment_XRF.isExclude = new EReg("^-",""); -xrfragment_XRF.isDeep = new EReg("\\*",""); -xrfragment_XRF.isNumber = new EReg("^[0-9\\.]+$",""); -})({}); -var xrfragment = $hx_exports["xrfragment"]; -export default xrfragment; var $hx_exports = typeof exports != "undefined" ? exports : typeof window != "undefined" ? window : typeof self != "undefined" ? self : this; (function ($global) { "use strict"; $hx_exports["xrfragment"] = $hx_exports["xrfragment"] || {}; diff --git a/dist/xrfragment.three.js b/dist/xrfragment.three.js index 31dfbcb..ee52960 100644 --- a/dist/xrfragment.three.js +++ b/dist/xrfragment.three.js @@ -1,5 +1,5 @@ /* - * v0.5.1 generated at Tue Dec 12 06:06:16 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 07:05:52 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ diff --git a/dist/xrfragment.three.module.js b/dist/xrfragment.three.module.js index 40b7360..d78d01b 100644 --- a/dist/xrfragment.three.module.js +++ b/dist/xrfragment.three.module.js @@ -1,5 +1,5 @@ /* - * v0.5.1 generated at Tue Dec 12 06:06:16 PM CET 2023 + * v0.5.1 generated at Wed Dec 13 07:05:52 PM CET 2023 * https://xrfragment.org * SPDX-License-Identifier: MPL-2.0 */ diff --git a/example/aframe/sandbox/index.html b/example/aframe/sandbox/index.html index cbcfc5a..b739f37 100644 --- a/example/aframe/sandbox/index.html +++ b/example/aframe/sandbox/index.html @@ -20,13 +20,7 @@ - - ➕ host - 🔗 share - ⬇️ scene - XRF - @@ -96,5 +90,15 @@ }) + + + diff --git a/example/assets/blocky.glb b/example/assets/blocky.glb new file mode 100644 index 0000000..7c6760c Binary files /dev/null and b/example/assets/blocky.glb differ diff --git a/example/assets/css/style.css b/example/assets/css/style.css index 3fed807..2b9e090 100644 --- a/example/assets/css/style.css +++ b/example/assets/css/style.css @@ -120,7 +120,7 @@ input[type="submit"] { text-decoration:none; } -a.btn-foot#embed{ +a.btn-foot#clone{ color: #888; bottom: 129px; } @@ -133,14 +133,20 @@ a.btn-foot#more{ bottom:72px; width: 37px; min-width: 37px; + font-size:23px; text-align: center; } -a.btn-foot#source{ +a.btn-foot#embed{ bottom:186px; } -a#source, +a.btn-foot#meeting{ + bottom:246px; +} + +a#meeting, +a#clone, a#embed, a#model{ display:none @@ -217,9 +223,9 @@ html{ transition: all ease .5s; border-radius: 3px; box-shadow: 0 0 4px 0 #0007; - left: 20px; + right: 20px; position: fixed; - bottom: 20px; + top: 20px; } .js-snackbar { @@ -291,18 +297,15 @@ html{ #qrcode{ z-index: 2000; - position: absolute; - border: 5px solid #1c1c3299; - top: 66px; - right: 20px; - width: 175px; + width: 111px; border-radius: 20px; background: #fff; - border: 5px solid #888; border-radius: 15px; - margin: 20px 20px; overflow: hidden; -webkit-mask-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAA5JREFUeNpiYGBgAAgwAAAEAAGbA+oJAAAAAElFTkSuQmCC); + bottom: 306px; + height: 121px; + right: 19px; } input#share{ diff --git a/example/assets/js/utils.js b/example/assets/js/utils.js index 03eff14..65516a5 100644 --- a/example/assets/js/utils.js +++ b/example/assets/js/utils.js @@ -55,13 +55,17 @@ export function setupConsole(el){ export function setupUrlBar(el,XRF){ let inIframe = window.location !== window.parent.location - let ids = ['#overlay','a#embed','a#source','a#model','#qrcode'] - let showButtons = () => { - ids.map( (i) => $(i).style.display = 'block' ) - $('a#more').style.display = 'none' + let ids = ['#overlay','a#embed','a#clone','a#model','a#meeting','#qrcode'] + let showButtons = (state) => { + ids.map( (i) => $(i).style.display = state ? 'inline-block' : 'none' ) + $('a#more').style.display = state ? 'none' : 'inline-block' if( inIframe ) $('#uri').style.display = 'block' } - $('a#more').addEventListener('click', () => showButtons() ) + $('a#more').addEventListener('click', () => showButtons(true) ) + $('a#meeting').addEventListener('click', () => { + document.querySelector('a-scene').setAttribute('meeting', 'id: xrfragments') + showButtons(false) + }) XRF.addEventListener('hash', () => reflectUrl() ) const reflectUrl = window.reflectUrl = (url) => { diff --git a/example/assets/sloth.glb b/example/assets/sloth.glb new file mode 100644 index 0000000..97bdb1d Binary files /dev/null and b/example/assets/sloth.glb differ diff --git a/src/3rd/js/aframe/meeting.js b/src/3rd/js/aframe/meeting.js new file mode 100644 index 0000000..d522114 --- /dev/null +++ b/src/3rd/js/aframe/meeting.js @@ -0,0 +1,274 @@ +AFRAME.registerComponent('meeting', { + schema:{ + id:{ required:true, type:'string'} + }, + init: function(){ + // embed https://github.com/dmotz/trystero (trystero-torrent.min.js build) + + // add css+html + let el = document.createElement("div") + el.innerHTML += ` +
+
+
+ +
` + document.body.appendChild(el) + this.trysteroInit() + }, + + trysteroInit: async function(){ + const { joinRoom } = await import("/dist/trystero-torrent.min.js"); + + const roomname = document.location.href.replace(/#.*/,'') + const config = this.config = {appId: this.data.id } + const room = this.room = joinRoom(config, roomname ) + console.log("starting webrtc room: "+roomname) + + const idsToNames = this.idsToNames = {} + const [sendName, getName] = room.makeAction('name') + const [sendChat, getChat] = this.room.makeAction('chat') + this.sendChat = sendChat + this.sendName = sendName + this.getChat = getChat + this.getName = getName + + // tell other peers currently in the room our name + let name = prompt('enter your name:') + idsToNames[ room.selfId ] = name.substr(0,15) + sendName( name ) + + // listen for peers naming themselves + getName((name, peerId) => (idsToNames[peerId] = name)) + + this.initChat() + + // this object can store audio instances for later + const peerAudios = this.peerAudios = {} + const peerVideos = this.peerVideos = {} + // get a local audio stream from the microphone + const selfStream = await navigator.mediaDevices.getUserMedia({ + audio: true, + video: { + width: 320, + height: 240, + frameRate: { + ideal: 30, + min: 10 + } + } + }) + let meVideo = this.addVideo(selfStream, room.selfId) + meVideo.muted = true + + // send stream to peers currently in the room + room.addStream(selfStream) + + // send stream + chatlog to peers who join later + room.onPeerJoin( (peerId) => { + console.log(`${idsToNames[peerId] || 'a visitor'} joined`) + room.addStream(selfStream, peerId) + sendName( name, peerId) + this.sendChat({prime: this.chat.log}, peerId ) + }) + + room.onPeerLeave( (peerId) => { + console.log(`${idsToNames[peerId] || 'a visitor'} left`) + if( peerVideos[peerId] ){ + peerVideos[peerId].remove() + delete peerVideos[peerId] + } + delete idsToNames[peerId] + }) + + // handle streams from other peers + room.onPeerStream((stream, peerId) => { + // create an audio instance and set the incoming stream + const audio = new Audio() + audio.srcObject = stream + audio.autoplay = true + + // add the audio to peerAudio object if you want to address it for something + // later (volume, etc.) + peerAudios[peerId] = audio + }) + + room.onPeerStream((stream, peerId) => { + this.addVideo(stream,peerId) + }) + + }, + + addVideo: function(stream,peerId){ + let video = this.peerVideos[peerId] + const videoContainer = document.getElementById('videos') + // if this peer hasn't sent a stream before, create a video element + if (!video) { + video = document.createElement('video') + video.autoplay = true + + // add video element to the DOM + videoContainer.appendChild(video) + } + + video.srcObject = stream + video.resize = (state) => { + if( video.resize.state == undefined ) video.resize.state = false + if( state == undefined ) state = (video.resize.state = !video.resize.state ) + video.style.width = state ? '320px' : '80px' + video.style.height = state ? '200px' : '60px' + } + + video.addEventListener('click', () => video.resize() ) + + this.peerVideos[peerId] = video + return video + }, + + createElement: function(str){ + let el = document.createElement("div") + el.className = "msg" + el.innerHTML = this.linkify(str) + return el + }, + + // central function to broadcast stuff to chat + send: function(str){ + if( !this.sendChat ) return + this.sendChat({content:str}) // send to network + this.chat.append(str) // send to screen + }, + + initChat: function(){ + + let chatline = this.chatline = document.querySelector("#chatline") + let chat = this.chat = document.querySelector("#chat") + chat.log = [] // save raw chatlog to prime new visitors + chat.append = (str) => { + if( !str ) return + str = str.replace('\n', "
") + this.chat.appendChild(this.createElement(str)) // send to screen + this.chat.innerHTML += '
' + this.chat.log.push(str) + } + + let send = () => { + let str = `${this.idsToNames[ this.room.selfId ]}: ${chatline.value.substr(0,65515).trim()}` + this.send(str) + } + + chatline.addEventListener("keydown", (e) => { + if( e.key !== "Enter" ) return + send() + chatline.value = '' + }) + + // listen for chatmsg + this.getChat((data, peerId) => { + if( data.prime ){ + if( this.chat.primed ) return // only prime once + console.log("receiving prime") + data.prime.map( (l) => chat.append(l) ) // send log to screen + this.chat.primed = true + } + chat.append(data.content) // send to screen + }) + + return this + }, + + linkify: function(t){ + const isValidHttpUrl = s => { + let u + try {u = new URL(s)} + catch (_) {return false} + return u.protocol.startsWith("http") + } + const m = t.match(/(?<=\s|^)[a-zA-Z0-9-:/]+\.[a-zA-Z0-9-].+?(?=[.,;:?!-]?(?:\s|$))/g) + if (!m) return t + const a = [] + m.forEach(x => { + const [t1, ...t2] = t.split(x) + a.push(t1) + t = t2.join(x) + const y = (!(x.match(/:\/\//)) ? 'https://' : '') + x + if (isNaN(x) && isValidHttpUrl(y)) + a.push('' + y.split('/')[2] + '') + else + a.push(x) + }) + a.push(t) + return a.join('') + } + +});