import * as Y from "yjs" ; import { WebrtcProvider } from "./y-webrtc" ; import { WebsocketProvider } from "./y-websocket" ; import { IndexeddbPersistence } from "./y-indexeddb" ; import { CodeMirrorBinding } from "./y-codemirror" ; import CodeMirror from "codemirror" ; export type IceServerType = { urls : string ; username ? : string ; credential ? : string ; } ; type SessionClientContext = { signalingServerUrl : string ; websocketServerUrl : string ; extraIceServers ? : IceServerType [ ] ; sessionName ? : string ; sessionPassword ? : string ; userName ? : string ; onJoin ? : Function ; onInitialSync ? : Function ; } ; class SessionClient { userName : string ; sessionName : string ; sessionPassword : string ; signalingServerUrl : string ; websocketServerUrl : string ; extraIceServers : IceServerType [ ] ; onJoin : Function ; onInitialSync : Function ; _doc : Y . Doc ; _provider : any ; _websocketProvider : any ; _indexedDbProvider : any ; _editorBindings : { [ editorId ] : CodeMirrorBinding ; } ; constructor param { Object } options return { SessionClient } constructor ( ctx ) { const { signalingServerUrl , websocketServerUrl , extraIceServers , sessionName , sessionPassword , userName , onJoin , onInitialSync , } = ctx ; this . signalingServerUrl = signalingServerUrl ; this . websocketServerUrl = websocketServerUrl ; this . extraIceServers = extraIceServers || [ ] ; this . userName = userName ; this . sessionName = sessionName || "default" ; this . sessionPassword = sessionPassword ; this . onJoin = onJoin || ( ( ) => { } ) ; this . onInitialSync = onInitialSync || ( ( ) => { } ) ; this . _doc = new Y . Doc ( ) ; this . _editorBindings = { } ; } join ( ) { const { signalingServerUrl , websocketServerUrl , extraIceServers , sessionName , sessionPassword , } = this ; console . debug ( "[WebRTC] ICE servers:" , extraIceServers ) ; const roomName = `flok:${ sessionName }` ; const provider = new WebrtcProvider ( roomName , this . _doc , { password : sessionPassword , signaling : [ signalingServerUrl ] , extraIceServers , } ) ; this . _provider = provider ; const websocketProvider = new WebsocketProvider ( websocketServerUrl , roomName , this . _doc ) ; this . _websocketProvider = websocketProvider ; const idbProvider = new IndexeddbPersistence ( roomName , this . _doc ) ; this . _indexedDbProvider = idbProvider ; if ( this . onInitialSync ) { websocketProvider . on ( "sync" , ( synced ) => { if ( synced ) this . _handleInitialSync ( "websocket" ) ; } ) ; provider . on ( "synced" , ( { synced } ) => { if ( synced ) this . _handleInitialSync ( "webrtc" ) ; } ) ; idbProvider . whenSynced . then ( ( ) => { this . _handleInitialSync ( "indexeddb" ) ; } ) ; } this . onJoin ( ) ; } isSignalingServerSecure ( ) { const protocol = this . signalingServerUrl . split ( ":" ) [ 0 ] ; return protocol === "wss:" ; } /**
     * Starts listening for changes from the CodeMirror editor instance and the
     * ShareDB document.
     *
     * For CodeMirror, it is necessary to register for both `beforeChange` and
     * `changes` events: the first one is the only one to report the positions in
     * the pre-change coordinate system, while the latter marks the end of the
     * batch of operations.
     */ attachEditor ( id , editor ) { const text = this . _doc . getText ( `editors:${ id }` ) ; const binding = new CodeMirrorBinding ( text , editor , this . userName , this . _provider ? this . _provider . awareness : this . _websocketProvider . awareness ) ; this . _editorBindings [ id ] = binding ; } /**
     * Stops listening for changes from the CodeMirror instance and the ShareDB document.
     */ release ( ) { for ( let i = 0 ; i < Object . keys ( this . _editorBindings ) . length ; i ++ ) { const binding = this . _editorBindings [ i ] ; binding . destroy ( ) ; } if ( this . _provider ) { this . _provider . destroy ( ) ; } if ( this . _websocketProvider ) { this . _websocketProvider . destroy ( ) ; } if ( this . _indexedDbProvider ) { this . _indexedDbProvider . destroy ( ) ; } } setUsername ( newName ) { if ( this . _provider ) { this . _provider . awareness . setLocalStateField ( "user" , { name : newName } ) ; } if ( this . _websocketProvider ) { this . _websocketProvider . awareness . setLocalStateField ( "user" , { name : newName , } ) ; } } flash ( editorId , fromLine , toLine ) { const editorBinding = this . _editorBindings [ editorId ] ; const editor = editorBinding . target ; const marker = editor . markText ( { line : fromLine , ch : 0 } , { line : toLine + 1 , ch : 0 } , { className : "flash-selection" } ) ; marker . className = "" ; setTimeout ( ( ) => { marker . clear ( ) ; } , 600 ) ; } _handleInitialSync = ( method ) => { let texts = { } ; Object . entries ( this . _editorBindings ) . forEach ( ( [ key , binding ] ) => { texts [ key ] = binding . target . getValue ( ) ; } ) ; this . onInitialSync ( method , texts ) ; } ; } export default SessionClient ;