2023-12-27 17:25:49 +00:00
window . matrix = ( opts ) => new Proxy ( {
2023-12-27 21:31:42 +00:00
el : null , // HTML element
2024-01-10 22:01:21 +01:00
profile : {
2023-12-27 17:25:49 +00:00
type : 'network' ,
2024-01-03 14:23:34 +00:00
name : '[Matrix]' ,
2023-12-27 21:31:42 +00:00
description : 'a standardized decentralized privacy-friendly protocol' ,
2023-12-27 17:25:49 +00:00
url : 'https://matrix.org' ,
2023-12-27 21:31:42 +00:00
protocol : 'matrix://' ,
2023-12-27 17:25:49 +00:00
video : false ,
audio : false ,
chat : true ,
2024-01-26 16:21:46 +00:00
scene : true
2023-12-27 17:25:49 +00:00
} ,
2024-01-10 22:01:21 +01:00
useWebcam : false ,
useChat : false ,
useScene : false ,
2024-01-09 11:05:13 +00:00
channel : '#xrfragment-test:matrix.org' ,
server : 'https://matrix.org' ,
username : '' ,
auth : 'via password' ,
authkey : '' ,
client : null ,
roomid : '' ,
2024-01-10 22:01:21 +01:00
// Matrix-CRDT
ydoc : null ,
yhref : null ,
2023-12-27 17:25:49 +00:00
html : {
2023-12-27 21:31:42 +00:00
generic : ( opts ) => ` <div>
2024-01-09 11:05:13 +00:00
< div target = "_blank" class = "badge ruler" > matrix < a onclick = "frontend.plugin.matrix.info()" > < i class = "gg-info right" > < / i > < / a > < / d i v >
< table id = "matrix" >
2023-12-27 21:31:42 +00:00
< tr >
< td > channel < / t d >
< td >
2024-01-26 16:21:46 +00:00
< input type = "text" id = "channel" placeholder = "${opts.plugin.channel}" value = "${opts.plugin.channel}" / >
2023-12-27 21:31:42 +00:00
< / t d >
< / t r >
< tr >
< td > server < / t d >
2024-01-09 11:05:13 +00:00
< td >
< input type = "text" id = "server" placeholder = "https://matrix.org" value = "${opts.plugin.server}" / >
2023-12-27 21:31:42 +00:00
< / t d >
< / t r >
< tr >
< td > user < / t d >
< td >
< input type = "text" id = "username" placeholder = "@you:matrix.org" / >
< / t d >
< / t r >
< tr >
2023-12-28 09:22:54 +00:00
< td > auth < / t d >
2023-12-27 21:31:42 +00:00
< td >
2023-12-28 09:22:54 +00:00
< select id = "auth" >
2023-12-27 21:31:42 +00:00
< option > via password < / o p t i o n >
< option > via access token < / o p t i o n >
< / s e l e c t >
< / t d >
< / t r >
2023-12-27 17:25:49 +00:00
< tr >
2023-12-27 21:31:42 +00:00
< td > < / t d >
2023-12-27 17:25:49 +00:00
< td >
2024-01-09 11:05:13 +00:00
< input type = "password" id = "secret" placeholder = "enter password" / >
2023-12-27 17:25:49 +00:00
< / t d >
< / t r >
< / t a b l e >
2024-01-03 14:23:34 +00:00
< br >
2023-12-27 17:25:49 +00:00
< / d i v >
`
} ,
init ( ) {
2024-01-03 14:23:34 +00:00
frontend . plugin [ 'matrix' ] = this
2023-12-27 17:25:49 +00:00
$connections . chatnetwork = $connections . chatnetwork . concat ( [ this ] )
2024-01-26 16:21:46 +00:00
$connections . scene = $connections . chatnetwork . concat ( [ this ] )
2023-12-27 21:31:42 +00:00
this . reactToConnectionHrefs ( )
2024-01-09 11:05:13 +00:00
document . addEventListener ( 'network.connect' , ( e ) => this . connect ( e . detail ) )
document . addEventListener ( 'network.init' , ( ) => {
let meeting = network . getMeetingFromUrl ( document . location . href )
2024-01-10 22:01:21 +01:00
if ( meeting . match ( this . profile . protocol ) ) {
2024-01-09 11:05:13 +00:00
this . parseLink ( meeting )
}
} )
2023-12-27 21:31:42 +00:00
} ,
connect ( opts ) {
2024-01-10 22:01:21 +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 ) {
2024-01-26 16:21:46 +00:00
this . createLink ( ) // ensure link
2024-01-10 22:01:21 +01:00
this . link = ` matrix://r/ ${ this . channel . replace ( /^#/ , '' ) } `
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
2024-01-26 16:21:46 +00:00
localStorage . setItem ( "matrix.username" , this . username )
let secret = document . querySelector ( "#matrix input#secret" ) . value
2024-01-10 22:01:21 +01:00
document . querySelector ( "#matrix input#secret" ) . value = ''
2024-01-26 16:21:46 +00:00
let clientOpts = {
baseUrl : this . server . match ( /^http/ ) ? this . server : ` https:// ${ this . server } ` ,
lazyLoadMembers : true ,
}
2024-01-10 22:01:21 +01:00
if ( this . auth == 'via access token' ) {
2024-01-26 16:21:46 +00:00
clientOpts . accessToken = secret
clientOpts . userId = this . username
2024-01-10 22:01:21 +01:00
}
2024-01-26 16:21:46 +00:00
this . client = Matrix . sdk . createClient ( clientOpts )
2024-01-10 22:01:21 +01:00
// auth
if ( this . auth == 'via password' ) {
2024-01-26 16:21:46 +00:00
//this.client.loginWithPassword(this.username, secret)
2024-01-10 22:01:21 +01:00
this . client . login ( "m.login.password" , { "user" : this . username , password : secret } )
. then ( ( ) => this . onMatrixConnect ( ) )
2024-01-26 16:21:46 +00:00
. 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 😞")
//})
}
2024-01-09 11:05:13 +00:00
}
} ,
onMatrixConnect ( ) {
2024-01-26 16:21:46 +00:00
// 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" )
2024-01-10 22:01:21 +01:00
// token: this.matrixclient.getAccessToken()
frontend . emit ( 'network.info' , { message : '🛰 syncing with Matrix (might take a while)' , plugin : this } )
2024-01-26 16:21:46 +00:00
//this.client.once("sync", function (state, prevState, res) { });
2024-01-10 22:01:21 +01:00
frontend . emit ( 'network.connected' , { plugin : this , username : this . username } )
// get roomId of channel
this . client . getRoomIdForAlias ( this . channel )
. then ( ( o ) => {
2024-01-26 16:21:46 +00:00
console . log ( ` ${ this . channel } has id ${ o . room _id } ` )
2024-01-10 22:01:21 +01:00
this . roomId = o . room _id
this . setupListeners ( )
} )
. catch ( ( e ) => {
2024-01-26 16:21:46 +00:00
console . error ( e )
2024-01-10 22:01:21 +01:00
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
2024-01-26 16:21:46 +00:00
var Buffer = window . Buffer = Matrix . Buffer // expose to MatrixProvider
2024-01-10 22:01:21 +01:00
this . ydoc = new Matrix . Y . Doc ( ) ;
const provider = new Matrix . MatrixProvider ( this . ydoc , this . client , {
type : "alias" ,
alias : this . channel
2024-01-09 11:05:13 +00:00
} ) ;
2024-01-10 22:01:21 +01:00
provider . initialize ( ) ;
2024-01-26 16:21:46 +00:00
this . ydoc . scene = this . ydoc . getMap ( 'scene' )
2024-01-10 22:01:21 +01:00
// observe changes of the sum
2024-01-26 16:21:46 +00:00
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 } ". ` )
}
} )
} )
2024-01-10 22:01:21 +01:00
} ,
getNormalizedName ( ) {
return this . channel . replace ( /(^#|:.*)/ , '' )
2024-01-09 11:05:13 +00:00
} ,
setupListeners ( ) {
2024-01-10 22:01:21 +01:00
if ( this . useChat ) this . setupChat ( )
2024-01-26 16:21:46 +00:00
if ( this . useScene ) this . setupCRDT ( ) /* throws weird errors, perhaps matrix-sdk-js is too new */
2024-01-10 22:01:21 +01:00
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 ( ) } )
2024-01-09 11:05:13 +00:00
}
} ) ;
2024-01-10 22:01:21 +01:00
// 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 ) )
} )
2023-12-27 17:25:49 +00:00
} ,
config ( opts ) {
2024-01-10 22:01:21 +01:00
opts = { ... opts , ... this . profile }
2023-12-27 21:31:42 +00:00
this . el = document . createElement ( 'div' )
2023-12-27 17:25:49 +00:00
let html = this . html . generic ( opts )
for ( let i in opts ) {
if ( this . html [ i ] ) html += this . html [ i ] ( opts )
}
2023-12-27 21:31:42 +00:00
this . el . innerHTML = html
2023-12-28 09:22:54 +00:00
this . el . querySelector ( '#auth' ) . addEventListener ( 'change' , ( e ) => {
this . el . querySelector ( '#secret' ) . setAttribute ( 'placeholder' , ` enter ${ e . target . value . replace ( /.* / , '' ) } ` )
} )
2023-12-27 21:31:42 +00:00
return this . el
} ,
2024-01-09 11:05:13 +00:00
info ( opts ) {
2024-01-10 22:01:21 +01:00
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 ` )
2024-01-09 11:05:13 +00:00
} ,
parseLink ( url ) {
2024-01-10 22:01:21 +01:00
if ( ! url . match ( this . profile . protocol ) ) return
2024-01-26 16:21:46 +00:00
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"
} )
2024-01-09 11:05:13 +00:00
this . el . querySelector ( '#channel' ) . value = ` # ${ channel } : ${ server } `
this . el . querySelector ( '#server' ) . value = server
2024-01-26 16:21:46 +00:00
if ( window . localStorage . getItem ( "matrix.username" ) ) {
this . el . querySelector ( '#username' ) . value = window . localStorage . getItem ( "matrix.username" )
}
2024-01-09 11:05:13 +00:00
console . log ( "configured matrix" )
2024-01-26 16:21:46 +00:00
return true
2024-01-09 11:05:13 +00:00
}
return false
} ,
2024-01-23 16:45:07 +00:00
createLink ( opts ) {
2024-01-10 22:01:21 +01:00
let hash = document . location . hash
if ( ! this . link ) {
const meeting = network . getMeetingFromUrl ( document . location . href )
this . link = meeting . match ( "matrix://" ) ? meeting : ''
}
if ( ! hash . match ( 'meet=' ) ) document . location . hash += ` ${ hash . length > 1 ? '&' : '#' } meet= ${ this . link } `
} ,
2023-12-27 21:31:42 +00:00
reactToConnectionHrefs ( ) {
xrf . addEventListener ( 'href' , ( opts ) => {
let { mesh } = opts
if ( ! opts . click ) return
2024-01-26 16:21:46 +00:00
let detected = this . parseLink ( mesh . userData . href )
if ( detected ) opts . promise ( ) // don't resolve, ignore other listeners
2024-01-09 11:05:13 +00:00
let href = mesh . userData . href
let isLocal = href [ 0 ] == '#'
let isTeleport = href . match ( /(pos=|http:)/ )
2024-01-26 16:21:46 +00:00
if ( isLocal && ! isTeleport && this . client && this . useScene && this . ydoc ) {
this . ydoc . scene . set ( 'href' , document . location . hash )
2024-01-10 22:01:21 +01:00
}
2023-12-27 17:25:49 +00:00
} )
2024-01-09 11:05:13 +00:00
let hashvars = xrf . URI . parse ( document . location . hash )
if ( hashvars . meet ) this . parseLink ( hashvars . meet . string )
2023-12-27 17:25:49 +00:00
}
} ,
{
// auto-trigger events on changes
get ( data , k , receiver ) { return data [ k ] } ,
set ( data , k , v ) {
let from = data [ k ]
data [ k ] = v
2023-12-27 21:31:42 +00:00
//switch( k ){
// default: matrix.opts.scene.dispatchEvent({type:`matrix.${k}.change`, from, to:v})
//}
2023-12-27 17:25:49 +00:00
}
} )
document . addEventListener ( '$connections:ready' , ( e ) => {
matrix ( e . detail ) . init ( )
} )