xrfragment/dist/xrfragment.plugin.matrix.js

344 lines
1.1 MiB
JavaScript
Raw Normal View History

/*
* v0.5.1 generated at Wed Jan 15 10:52:05 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
2024-01-03 15:23:34 +01:00
(function(){
/*! For license information please see matrix-crdt.js.LICENSE.txt */
2024-01-29 21:19:04 +01:00
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Matrix=t():e.Matrix=t()}(self,(()=>(()=>{var e={9141:e=>{"use strict";for(var t=/[\\\"\x00-\x1F]/g,n={},r=0;r<32;++r)n[String.fromCharCode(r)]="\\U"+("0000"+r.toString(16)).slice(-4).toUpperCase();function i(e){return t.lastIndex=0,e.replace(t,(function(e){return n[e]}))}n["\b"]="\\b",n["\t"]="\\t",n["\n"]="\\n",n["\f"]="\\f",n["\r"]="\\r",n['"']='\\"',n["\\"]="\\\\",e.exports={stringify:function e(t){switch(typeof t){case"string":return'"'+i(t)+'"';case"number":return isFinite(t)?t:"null";case"boolean":return t;case"object":return null===t?"null":Array.isArray(t)?function(t){for(var n="[",r="",i=0;i<t.length;++i)r+=n,n=",",r+=e(t[i]);return","!=n?"[]":r+"]"}(t):function(t){var n="{",r="",s=Object.keys(t);s.sort();for(var o=0;o<s.length;++o){var a=s[o];r+=n+'"'+i(a)+'":',n=",",r+=e(t[a])}return","!=n?"{}":r+"}"}(t);default:throw new Error("Cannot stringify: "+typeof t)}}}},8162:e=>{"use strict";e.exports=function(e){if(e.length>=255)throw new TypeError("Alphabet too long");for(var t=new Uint8Array(256),n=0;n<t.length;n++)t[n]=255;for(var r=0;r<e.length;r++){var i=e.charAt(r),s=i.charCodeAt(0);if(255!==t[s])throw new TypeError(i+" is ambiguous");t[s]=r}var o=e.length,a=e.charAt(0),c=Math.log(o)/Math.log(256),l=Math.log(256)/Math.log(o);function d(e){if("string"!=typeof e)throw new TypeError("Expected String");if(0===e.length)return new Uint8Array;for(var n=0,r=0,i=0;e[n]===a;)r++,n++;for(var s=(e.length-n)*c+1>>>0,l=new Uint8Array(s);e[n];){var d=t[e.charCodeAt(n)];if(255===d)return;for(var u=0,h=s-1;(0!==d||u<i)&&-1!==h;h--,u++)d+=o*l[h]>>>0,l[h]=d%256>>>0,d=d/256>>>0;if(0!==d)throw new Error("Non-zero carry");i=u,n++}for(var f=s-i;f!==s&&0===l[f];)f++;for(var g=new Uint8Array(r+(s-f)),p=r;f!==s;)g[p++]=l[f++];return g}return{encode:function(t){if(t instanceof Uint8Array||(ArrayBuffer.isView(t)?t=new Uint8Array(t.buffer,t.byteOffset,t.byteLength):Array.isArray(t)&&(t=Uint8Array.from(t))),!(t instanceof Uint8Array))throw new TypeError("Expected Uint8Array");if(0===t.length)return"";for(var n=0,r=0,i=0,s=t.length;i!==s&&0===t[i];)i++,n++;for(var c=(s-i)*l+1>>>0,d=new Uint8Array(c);i!==s;){for(var u=t[i],h=0,f=c-1;(0!==u||h<r)&&-1!==f;f--,h++)u+=256*d[f]>>>0,d[f]=u%o>>>0,u=u/o>>>0;if(0!==u)throw new Error("Non-zero carry");r=h,i++}for(var g=c-r;g!==c&&0===d[g];)g++;for(var p=a.repeat(n);g<c;++g)p+=e.charAt(d[g]);return p},decodeUnsafe:d,decode:function(e){var t=d(e);if(t)return t;throw new Error("Non-base"+o+" character")}}}},9742:(e,t)=>{"use strict";t.byteLength=function(e){var t=a(e),n=t[0],r=t[1];return 3*(n+r)/4-r},t.toByteArray=function(e){var t,n,s=a(e),o=s[0],c=s[1],l=new i(function(e,t,n){return 3*(t+n)/4-n}(0,o,c)),d=0,u=c>0?o-4:o;for(n=0;n<u;n+=4)t=r[e.charCodeAt(n)]<<18|r[e.charCodeAt(n+1)]<<12|r[e.charCodeAt(n+2)]<<6|r[e.charCodeAt(n+3)],l[d++]=t>>16&255,l[d++]=t>>8&255,l[d++]=255&t;return 2===c&&(t=r[e.charCodeAt(n)]<<2|r[e.charCodeAt(n+1)]>>4,l[d++]=255&t),1===c&&(t=r[e.charCodeAt(n)]<<10|r[e.charCodeAt(n+1)]<<4|r[e.charCodeAt(n+2)]>>2,l[d++]=t>>8&255,l[d++]=255&t),l},t.fromByteArray=function(e){for(var t,r=e.length,i=r%3,s=[],o=16383,a=0,l=r-i;a<l;a+=o)s.push(c(e,a,a+o>l?l:a+o));return 1===i?(t=e[r-1],s.push(n[t>>2]+n[t<<4&63]+"==")):2===i&&(t=(e[r-2]<<8)+e[r-1],s.push(n[t>>10]+n[t>>4&63]+n[t<<2&63]+"=")),s.join("")};for(var n=[],r=[],i="undefined"!=typeof Uint8Array?Uint8Array:Array,s="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",o=0;o<64;++o)n[o]=s[o],r[s.charCodeAt(o)]=o;function a(e){var t=e.length;if(t%4>0)throw new Error("Invalid string. Length must be a multiple of 4");var n=e.indexOf("=");return-1===n&&(n=t),[n,n===t?0:4-n%4]}function c(e,t,r){for(var i,s,o=[],a=t;a<r;a+=3)i=(e[a]<<16&16711680)+(e[a+1]<<8&65280)+(255&e[a+2]),o.push(n[(s=i)>>18&63]+n[s>>12&63]+n[s>>6&63]+n[63&s]);return o.join("")}r["-".charCodeAt(0)]=62,r["_".charCodeAt(0)]=63},4770:function(e,t){var n,r;n=function(){var e=XMLHttpRequest;if(!e)thr
2024-01-03 15:23:34 +01:00
el: null, // HTML element
2024-01-29 21:19:04 +01:00
profile:{
2024-01-03 15:23:34 +01:00
type: 'network',
name: '[Matrix]',
description: 'a standardized decentralized privacy-friendly protocol',
url: 'https://matrix.org',
protocol: 'matrix://',
video: false,
audio: false,
chat: true,
scene: true
},
2024-01-29 21:19:04 +01:00
useWebcam: false,
useChat: false,
useScene: false,
channel: '#xrfragment-test:matrix.org',
server: 'https://matrix.org',
username:'',
auth: 'via password',
authkey: '',
client: null,
roomid: '',
// Matrix-CRDT
ydoc: null,
yhref: null,
2024-01-03 15:23:34 +01:00
html: {
generic: (opts) => `<div>
2024-01-29 21:19:04 +01:00
<div target="_blank" class="badge ruler">matrix <a onclick="frontend.plugin.matrix.info()"><i class="gg-info right"></i></a></div>
<table id="matrix">
2024-01-03 15:23:34 +01:00
<tr>
<td>channel</td>
<td>
2024-01-29 21:19:04 +01:00
<input type="text" id="channel" placeholder="${opts.plugin.channel}" value="${opts.plugin.channel}"/>
2024-01-03 15:23:34 +01:00
</td>
</tr>
<tr>
<td>server</td>
2024-01-29 21:19:04 +01:00
<td>
<input type="text" id="server" placeholder="https://matrix.org" value="${opts.plugin.server}"/>
2024-01-03 15:23:34 +01:00
</td>
</tr>
<tr>
<td>user</td>
<td>
2024-01-29 21:19:04 +01:00
<input type="text" id="username" placeholder="@you:matrix.org" value="${opts.plugin.username}"/>
2024-01-03 15:23:34 +01:00
</td>
</tr>
<tr>
<td>auth</td>
<td>
<select id="auth">
<option>via password</option>
<option>via access token</option>
</select>
</td>
</tr>
<tr>
<td></td>
<td>
2024-01-29 21:19:04 +01:00
<input type="password" id="secret" placeholder="enter password"/>
2024-01-03 15:23:34 +01:00
</td>
</tr>
</table>
<br>
</div>
`
},
init(){
frontend.plugin['matrix'] = this
$connections.chatnetwork = $connections.chatnetwork.concat([this])
$connections.scene = $connections.scene.concat([this])
2024-01-29 21:19:04 +01:00
if( window.localStorage.getItem("username") ) this.username = window.localStorage.getItem("username")
2024-01-03 15:23:34 +01:00
this.reactToConnectionHrefs()
2024-01-29 21:19:04 +01:00
document.addEventListener('network.connect', (e) => this.connect(e.detail) )
document.addEventListener('network.init', () => {
let meeting = network.getMeetingFromUrl(document.location.href)
if( meeting.match(this.profile.protocol) ){
this.parseLink( meeting )
}
})
2024-01-03 15:23:34 +01:00
},
connect(opts){
2024-01-29 21:19:04 +01:00
if( opts.selectedWebcam == this.profile.name ) this.useWebcam = true
if( opts.selectedChatnetwork == this.profile.name ) this.useChat = true
if( opts.selectedScene == this.profile.name ) this.useScene = true
if( this.useWebcam || this.useScene || this.useChat ){
this.link = `matrix://r/${this.channel.replace(/^#/,'')}`
this.createLink() // ensure link
this.channel = document.querySelector("#matrix input#channel").value
this.server = document.querySelector("#matrix input#server").value
this.username = document.querySelector("#matrix input#username").value
this.auth = document.querySelector("#matrix select#auth").value
localStorage.setItem("matrix.username",this.username)
let secret = document.querySelector("#matrix input#secret").value
document.querySelector("#matrix input#secret").value = ''
let clientOpts = {
baseUrl: this.server.match(/^http/) ? this.server : `https://${this.server}`,
lazyLoadMembers: true,
}
if( this.auth == 'via access token'){
clientOpts.accessToken = secret
clientOpts.userId = this.username
}
this.client = Matrix.sdk.createClient(clientOpts)
// auth
if( this.auth == 'via password'){
//this.client.loginWithPassword(this.username, secret)
this.client.login("m.login.password",{"user": this.username, password: secret})
.then( () => this.onMatrixConnect() )
.catch( () => {
window.notify("authentication was not succesful 😞")
})
}else {
this.onMatrixConnect()
//this.client.loginWithToken(clientOpts.accessToken)
//.then( () => this.onMatrixConnect() )
//.catch( () => {
// window.notify("authentication was not succesful 😞")
//})
}
}
},
onMatrixConnect(){
// Extra configuration needed for certain matrix-js-sdk (we don't call start)
// calls to work without calling sync start functions
this.client.canSupportVoip = false;
this.client.clientOpts = {
lazyLoadMembers: true
}
//this.client.startClient({ initialSyncLimit: 4 }) // show last 4 messages?
//.then( () => {
//.catch( (e) => window.notify("could not start matrix client 😞"))
console.log("onmatrix connect")
// token: this.matrixclient.getAccessToken()
frontend.emit('network.info',{message:'🛰 syncing with Matrix (might take a while)',plugin:this})
//this.client.once("sync", function (state, prevState, res) { });
frontend.emit('network.connected',{plugin:this,username: this.username})
// get roomId of channel
this.client.getRoomIdForAlias(this.channel)
.then( (o) => {
console.log(`${this.channel} has id ${o.room_id}`)
this.roomId = o.room_id
// join room if we haven't already
this.client.joinRoom(this.roomId)
.then( () => this.setupListeners() )
.catch( () => this.setupListeners() )
})
.catch((e) => {
console.error(e)
frontend.emit('network.error',{plugin:this,message:`channel ${this.channel} cannot be joined: `+String(e)})
})
},
setupCRDT(){
// Create a new Y.Doc and connect the MatrixProvider
var Buffer = window.Buffer = Matrix.Buffer // expose to MatrixProvider
this.ydoc = new Matrix.Y.Doc();
const provider = new Matrix.MatrixProvider(this.ydoc, this.client, {
type: "alias",
alias: this.channel
});
provider.initialize();
this.ydoc.scene = this.ydoc.getMap('scene')
// observe changes of the sum
this.ydoc.scene.observe(ymapEvent => {
// Find out what changed:
// Option 1: A set of keys that changed
ymapEvent.keysChanged // => Set<strings>
// Option 2: Compute the differences
ymapEvent.changes.keys // => Map<string, { action: 'add'|'update'|'delete', oldValue: any}>
// sample code.
ymapEvent.changes.keys.forEach((change, key) => {
console.dir({key,change})
if ( key == 'href' && change.action != "delete" ){
let href = this.ydoc.scene.get('href')
if( href.match(/pos=/) ) return // no shared teleporting
xrf.hashbus.pub(href)
} else if (change.action === 'delete') {
console.log(`Property "${key}" was deleted. New value: undefined. Previous value: "${change.oldValue}".`)
}
})
})
},
getNormalizedName(){
return this.channel.replace(/(^#|:.*)/,'')
},
setupListeners(){
if( this.useChat ) this.setupChat()
if( this.useScene ) this.setupCRDT() /* throws weird errors, perhaps matrix-sdk-js is too new */
return this
},
setupChat(){
// receive receivemessages
this.client.on("Room.timeline", (event, room, toStartOfTimeline) => {
if (event.getType() !== "m.room.message") return // only print messages
if( room.roomId == this.roomId ){
$chat.send({message: event.getContent().body, from: event.getSender()})
}
});
// send chatmessages
document.addEventListener('network.send', (e) => {
let {message} = e.detail
let href = frontend.share({linkonly:true}) // get our meetinglink in there
// convert to absolute links
if( message.match(/href="#/) ){
message = message.replace(/href=['"]#.*?['"]/g, `href="${href}"`)
}else{
let pos = []
if( network.posName ){
pos.push(`<a href="${href}">#${network.posName}</a>`)
}
if( network.pos ){
pos.push(`<a href="${href}">#${`pos=${network.pos}`}</a>`)
}
if( pos.length ) message += `<br>📍 ${pos.join(' ')}`
}
let content = {
body: message,
format: "org.matrix.custom.html",
formatted_body: message,
msgtype:"m.text"
}
this.client.sendEvent( this.roomId, "m.room.message", content, "", (err,res) => console.error({err,res}) )
})
2024-01-03 15:23:34 +01:00
},
config(opts){
2024-01-29 21:19:04 +01:00
opts = {...opts, ...this.profile }
2024-01-03 15:23:34 +01:00
this.el = document.createElement('div')
let html = this.html.generic(opts)
for( let i in opts ){
if( this.html[i] ) html += this.html[i](opts)
}
this.el.innerHTML = html
this.el.querySelector('#auth').addEventListener('change', (e) => {
this.el.querySelector('#secret').setAttribute('placeholder', `enter ${e.target.value.replace(/.* /,'')}`)
})
return this.el
},
2024-01-29 21:19:04 +01:00
info(opts){
window.notify(`${this.profile.name} is ${this.profile.description}, it is the hottest internet technology available at this moment.<br>Read more about it <a href="${this.profile.url}" target="_blank">here</a>.<br>You can basically make up a new channelname or use an existing one`)
},
parseLink(url){
if( !url.match(this.profile.protocol) ) return
if( url.match('/r/') ){
this.link = url
let parts = url.split("/r/")
let channel = parts[1].replace(/:.*/,'')
let server = parts[1].replace(/.*:/,'')
$connections.show({
chatnetwork:this.profile.name,
scene: this.profile.name,
webcam: "No thanks"
})
this.el.querySelector('#channel').value = `#${channel}:${server}`
this.el.querySelector('#server').value = server
if( window.localStorage.getItem("matrix.username") ){
this.el.querySelector('#username').value = window.localStorage.getItem("matrix.username")
}
console.log("configured matrix")
return true
}
return false
},
createLink(opts){
if( !this.link ){
const meeting = network.getMeetingFromUrl(document.location.href)
this.link = network.meetingLink = meeting.match("matrix://") ? meeting : ''
}
2024-04-16 18:44:55 +02:00
if( !xrf.navigator.URI.hash.meet ) xrf.navigator.URI.hash.meet = this.link
2024-01-29 21:19:04 +01:00
},
2024-01-03 15:23:34 +01:00
reactToConnectionHrefs(){
xrf.addEventListener('href', (opts) => {
let {mesh} = opts
if( !opts.click ) return
2024-01-29 21:19:04 +01:00
let detected = this.parseLink(mesh.userData.href)
if( detected ) opts.promise() // don't resolve, ignore other listeners
let href = mesh.userData.href
let isLocal = href[0] == '#'
let isTeleport = href.match(/(pos=|http:)/)
if( isLocal && !isTeleport && this.client && this.useScene && this.ydoc ){
this.ydoc.scene.set('href',document.location.hash )
}
2024-01-03 15:23:34 +01:00
})
2024-04-16 15:19:08 +02:00
let hashvars = xrf.URI.parse( document.location.hash ).XRF
2024-01-29 21:19:04 +01:00
if( hashvars.meet ) this.parseLink(hashvars.meet.string)
2024-01-03 15:23:34 +01:00
}
},
{
// auto-trigger events on changes
get(data,k,receiver){ return data[k] },
set(data,k,v){
let from = data[k]
data[k] = v
//switch( k ){
// default: matrix.opts.scene.dispatchEvent({type:`matrix.${k}.change`, from, to:v})
//}
}
})
document.addEventListener('$connections:ready', (e) => {
matrix(e.detail).init()
})
}).apply({})