move websocket to connection.

This commit is contained in:
Miroslav Šedivý 2021-06-18 00:31:03 +02:00
parent e958690e10
commit e0de57fc70
3 changed files with 86 additions and 118 deletions

View File

@ -10,61 +10,40 @@ export interface NekoConnectionEvents {
} }
export class NekoConnection extends EventEmitter<NekoConnectionEvents> { export class NekoConnection extends EventEmitter<NekoConnectionEvents> {
staysConnected = false private _url: string
private _token: string
websocket = new NekoWebSocket() public websocket = new NekoWebSocket()
webrtc = new NekoWebRTC() public webrtc = new NekoWebRTC()
constructor() { constructor() {
super() super()
// this._url = ''
// websocket events this._token = ''
// }
this.websocket.on('message', async (event: string, payload: any) => { public setUrl(url: string) {
this._url = url.replace(/^http/, 'ws').replace(/\/+$/, '') + '/api/ws'
}) }
this.websocket.on('connecting', () => {
})
this.websocket.on('connected', () => {
})
this.websocket.on('disconnected', () => {
})
//
// webrtc events
//
this.webrtc.on('track', (event: RTCTrackEvent) => {
})
this.webrtc.on('candidate', (candidate: RTCIceCandidateInit) => {
})
this.webrtc.on('stats', (stats: WebRTCStats) => {
})
this.webrtc.on('connecting', () => {
})
this.webrtc.on('connected', () => {
})
this.webrtc.on('disconnected', () => {
})
public setToken(token: string) {
this._token = token
} }
public async connect(): Promise<void> { public async connect(): Promise<void> {
this.staysConnected = true let url = this._url
if (this._token) {
url += '?token=' + encodeURIComponent(this._token)
} }
public async disconnect(): Promise<void> { await this.websocket.connect(url)
this.staysConnected = false
// TODO: connect to WebRTC
//this.websocket.send(EVENT.SIGNAL_REQUEST, { video: video })
}
public disconnect() {
this.websocket.disconnect()
} }
} }

View File

@ -4,7 +4,7 @@ import * as message from '../types/messages'
import EventEmitter from 'eventemitter3' import EventEmitter from 'eventemitter3'
import { Logger } from '../utils/logger' import { Logger } from '../utils/logger'
import { NekoWebSocket } from './websocket' import { NekoConnection } from './connection'
import NekoState from '../types/state' import NekoState from '../types/state'
export interface NekoEvents { export interface NekoEvents {
@ -42,18 +42,18 @@ export interface NekoEvents {
} }
export class NekoMessages extends EventEmitter<NekoEvents> { export class NekoMessages extends EventEmitter<NekoEvents> {
private _websocket: NekoWebSocket private _connection: NekoConnection
private _state: NekoState private _state: NekoState
private _log: Logger private _log: Logger
constructor(websocket: NekoWebSocket, state: NekoState) { constructor(connection: NekoConnection, state: NekoState) {
super() super()
this._log = new Logger('messages') this._log = new Logger('messages')
this._state = state this._state = state
this._websocket = websocket this._connection = connection
this._websocket.on('message', async (event: string, payload: any) => { this._connection.websocket.on('message', async (event: string, payload: any) => {
// @ts-ignore // @ts-ignore
if (typeof this[event] === 'function') { if (typeof this[event] === 'function') {
// @ts-ignore // @ts-ignore
@ -62,6 +62,11 @@ export class NekoMessages extends EventEmitter<NekoEvents> {
this._log.warn(`unhandled websocket event '${event}':`, payload) this._log.warn(`unhandled websocket event '${event}':`, payload)
} }
}) })
this._connection.webrtc.on('candidate', (candidate: RTCIceCandidateInit) => {
this._connection.websocket.send(EVENT.SIGNAL_CANDIDATE, candidate)
this.emit('connection.webrtc.sdp.candidate', 'local', candidate)
})
} }
///////////////////////////// /////////////////////////////
@ -101,7 +106,7 @@ export class NekoMessages extends EventEmitter<NekoEvents> {
protected [EVENT.SYSTEM_DISCONNECT]({ message }: message.SystemDisconnect) { protected [EVENT.SYSTEM_DISCONNECT]({ message }: message.SystemDisconnect) {
this._log.debug('EVENT.SYSTEM_DISCONNECT') this._log.debug('EVENT.SYSTEM_DISCONNECT')
this._websocket.disconnect(new Error(message)) this._connection.disconnect()
this.emit('connection.disconnect', message) this.emit('connection.disconnect', message)
} }
@ -109,20 +114,26 @@ export class NekoMessages extends EventEmitter<NekoEvents> {
// Signal Events // Signal Events
///////////////////////////// /////////////////////////////
protected [EVENT.SIGNAL_PROVIDE]({ event, sdp, video }: message.SignalProvide) { protected async [EVENT.SIGNAL_PROVIDE]({ sdp: remoteSdp, video, iceservers }: message.SignalProvide) {
this._log.debug('EVENT.SIGNAL_PROVIDE') this._log.debug('EVENT.SIGNAL_PROVIDE')
this.emit('connection.webrtc.sdp', 'remote', remoteSdp)
const localSdp = await this._connection.webrtc.connect(remoteSdp, iceservers)
this._connection.websocket.send(EVENT.SIGNAL_ANSWER, {
sdp: localSdp,
})
this.emit('connection.webrtc.sdp', 'local', localSdp)
Vue.set(this._state.connection.webrtc, 'video', video) Vue.set(this._state.connection.webrtc, 'video', video)
// TODO: Handle.
this.emit('connection.webrtc.sdp', 'remote', sdp)
} }
protected [EVENT.SIGNAL_CANDIDATE]({ event, ...candidate }: message.SignalCandidate) { protected [EVENT.SIGNAL_CANDIDATE]({ event, ...candidate }: message.SignalCandidate) {
this._log.debug('EVENT.SIGNAL_CANDIDATE') this._log.debug('EVENT.SIGNAL_CANDIDATE')
// TODO: Handle. this._connection.webrtc.setCandidate(candidate)
this.emit('connection.webrtc.sdp.candidate', 'remote', candidate) this.emit('connection.webrtc.sdp.candidate', 'remote', candidate)
} }
protected [EVENT.SIGNAL_VIDEO]({ event, video }: message.SignalVideo) { protected [EVENT.SIGNAL_VIDEO]({ video }: message.SignalVideo) {
this._log.debug('EVENT.SIGNAL_VIDEO') this._log.debug('EVENT.SIGNAL_VIDEO')
Vue.set(this._state.connection.webrtc, 'video', video) Vue.set(this._state.connection.webrtc, 'video', video)
} }

View File

@ -3,7 +3,7 @@
<div ref="container" class="neko-container"> <div ref="container" class="neko-container">
<video ref="video" :autoplay="autoplay" :muted="autoplay" playsinline /> <video ref="video" :autoplay="autoplay" :muted="autoplay" playsinline />
<neko-overlay <neko-overlay
:webrtc="webrtc" :webrtc="connection.webrtc"
:scroll="state.control.scroll" :scroll="state.control.scroll"
:screenSize="state.screen.size" :screenSize="state.screen.size"
:canvasSize="canvasSize" :canvasSize="canvasSize"
@ -14,8 +14,8 @@
: '' : ''
" "
:implicitControl="state.control.implicit_hosting && state.sessions[state.session_id].profile.can_host" :implicitControl="state.control.implicit_hosting && state.sessions[state.session_id].profile.can_host"
@implicit-control-request="websocket.send('control/request')" @implicit-control-request="connection.websocket.send('control/request')"
@implicit-control-release="websocket.send('control/release')" @implicit-control-release="connection.websocket.send('control/release')"
@update-kbd-modifiers="updateKeyboardModifiers($event)" @update-kbd-modifiers="updateKeyboardModifiers($event)"
@drop-files="uploadDrop($event)" @drop-files="uploadDrop($event)"
/> />
@ -57,8 +57,8 @@
import ResizeObserver from 'resize-observer-polyfill' import ResizeObserver from 'resize-observer-polyfill'
import { NekoApi, MembersApi, RoomApi } from './internal/api' import { NekoApi, MembersApi, RoomApi } from './internal/api'
import { NekoWebSocket } from './internal/websocket' import { NekoConnection } from './internal/connection'
import { NekoWebRTC, WebRTCStats } from './internal/webrtc' import { WebRTCStats } from './internal/webrtc'
import { NekoMessages } from './internal/messages' import { NekoMessages } from './internal/messages'
import { register as VideoRegister } from './internal/video' import { register as VideoRegister } from './internal/video'
@ -77,8 +77,6 @@
@Ref('video') readonly _video!: HTMLVideoElement @Ref('video') readonly _video!: HTMLVideoElement
api = new NekoApi() api = new NekoApi()
websocket = new NekoWebSocket()
webrtc = new NekoWebRTC()
observer = new ResizeObserver(this.onResize.bind(this)) observer = new ResizeObserver(this.onResize.bind(this))
canvasSize: { width: number; height: number } = { canvasSize: { width: number; height: number } = {
width: 0, width: 0,
@ -97,15 +95,20 @@
@Prop({ type: Boolean }) @Prop({ type: Boolean })
private readonly autoplay!: boolean private readonly autoplay!: boolean
/////////////////////////////
// Public connection manager
/////////////////////////////
public connection = new NekoConnection()
///////////////////////////// /////////////////////////////
// Public state // Public state
///////////////////////////// /////////////////////////////
public state = { public state = {
connection: { connection: {
authenticated: false, authenticated: false,
websocket: this.websocket.supported ? 'disconnected' : 'unavailable', websocket: this.connection.websocket.supported ? 'disconnected' : 'unavailable',
webrtc: { webrtc: {
status: this.webrtc.supported ? 'disconnected' : 'unavailable', status: this.connection.webrtc.supported ? 'disconnected' : 'unavailable',
stats: null, stats: null,
video: null, video: null,
videos: [], videos: [],
@ -167,7 +170,7 @@
///////////////////////////// /////////////////////////////
// Public events // Public events
///////////////////////////// /////////////////////////////
public events = new NekoMessages(this.websocket, this.state) public events = new NekoMessages(this.connection, this.state)
///////////////////////////// /////////////////////////////
// Public methods // Public methods
@ -180,14 +183,10 @@
const httpURL = url.replace(/^ws/, 'http').replace(/\/$|\/ws\/?$/, '') const httpURL = url.replace(/^ws/, 'http').replace(/\/$|\/ws\/?$/, '')
this.api.setUrl(httpURL) this.api.setUrl(httpURL)
this.websocket.setUrl(httpURL) this.connection.setUrl(httpURL)
if (this.connected) { if (this.connected) {
this.websocket.disconnect(new Error('url changed')) this.connection.disconnect()
}
if (this.watching) {
this.webrtc.disconnect()
} }
if (this.authenticated) { if (this.authenticated) {
@ -199,14 +198,14 @@
const token = localStorage.getItem('neko_session') const token = localStorage.getItem('neko_session')
if (token) { if (token) {
this.api.setToken(token) this.api.setToken(token)
this.websocket.setToken(token) this.connection.setToken(token)
} }
await this.api.session.whoami() await this.api.session.whoami()
Vue.set(this.state.connection, 'authenticated', true) Vue.set(this.state.connection, 'authenticated', true)
if (this.autoconnect) { if (this.autoconnect) {
await this.websocket.connect() await this.connection.connect()
} }
} }
} }
@ -219,7 +218,7 @@
const res = await this.api.session.login({ username, password }) const res = await this.api.session.login({ username, password })
if (res.data.token) { if (res.data.token) {
this.api.setToken(res.data.token) this.api.setToken(res.data.token)
this.websocket.setToken(res.data.token) this.connection.setToken(res.data.token)
if (this.autologin) { if (this.autologin) {
localStorage.setItem('neko_session', res.data.token) localStorage.setItem('neko_session', res.data.token)
@ -229,7 +228,7 @@
Vue.set(this.state.connection, 'authenticated', true) Vue.set(this.state.connection, 'authenticated', true)
if (this.autoconnect) { if (this.autoconnect) {
await this.websocket.connect() await this.connection.connect()
} }
} }
@ -239,14 +238,14 @@
} }
if (this.connected) { if (this.connected) {
this.websocket.disconnect(new Error('logged out')) this.connection.disconnect()
} }
try { try {
await this.api.session.logout() await this.api.session.logout()
} finally { } finally {
this.api.setToken('') this.api.setToken('')
this.websocket.setToken('') this.connection.setToken('')
if (this.autologin) { if (this.autologin) {
localStorage.removeItem('neko_session') localStorage.removeItem('neko_session')
@ -266,7 +265,7 @@
throw new Error('client already connected to websocket') throw new Error('client already connected to websocket')
} }
await this.websocket.connect() await this.connection.connect()
} }
// TODO: Refactor. // TODO: Refactor.
@ -275,7 +274,7 @@
throw new Error('client not connected to websocket') throw new Error('client not connected to websocket')
} }
this.websocket.disconnect(new Error('manual action')) this.connection.disconnect()
} }
// TODO: Refactor. // TODO: Refactor.
@ -292,7 +291,7 @@
throw new Error('video id not found') throw new Error('video id not found')
} }
this.websocket.send(EVENT.SIGNAL_REQUEST, { video: video }) this.connection.websocket.send(EVENT.SIGNAL_REQUEST, { video: video })
} }
// TODO: Refactor. // TODO: Refactor.
@ -301,7 +300,7 @@
throw new Error('client not connected to webrtc') throw new Error('client not connected to webrtc')
} }
this.webrtc.disconnect() this.connection.webrtc.disconnect()
} }
public play() { public play() {
@ -342,7 +341,7 @@
// TODO: Remove? Use REST API only? // TODO: Remove? Use REST API only?
public setScreenSize(width: number, height: number, rate: number) { public setScreenSize(width: number, height: number, rate: number) {
this.websocket.send(EVENT.SCREEN_SET, { width, height, rate }) this.connection.websocket.send(EVENT.SCREEN_SET, { width, height, rate })
} }
public setWebRTCVideo(video: string) { public setWebRTCVideo(video: string) {
@ -350,7 +349,7 @@
throw new Error('video id not found') throw new Error('video id not found')
} }
this.websocket.send(EVENT.SIGNAL_VIDEO, { video: video }) this.connection.websocket.send(EVENT.SIGNAL_VIDEO, { video: video })
} }
public setWebRTCAuto(auto: boolean = true) { public setWebRTCAuto(auto: boolean = true) {
@ -358,11 +357,11 @@
} }
public sendUnicast(receiver: string, subject: string, body: any) { public sendUnicast(receiver: string, subject: string, body: any) {
this.websocket.send(EVENT.SEND_UNICAST, { receiver, subject, body }) this.connection.websocket.send(EVENT.SEND_UNICAST, { receiver, subject, body })
} }
public sendBroadcast(subject: string, body: any) { public sendBroadcast(subject: string, body: any) {
this.websocket.send(EVENT.SEND_BROADCAST, { subject, body }) this.connection.websocket.send(EVENT.SEND_BROADCAST, { subject, body })
} }
public get room(): RoomApi { public get room(): RoomApi {
@ -400,28 +399,11 @@
VideoRegister(this._video, this.state.video) VideoRegister(this._video, this.state.video)
// websocket // websocket
this.websocket.on('message', async (event: string, payload: any) => { this.connection.websocket.on('connecting', () => {
switch (event) {
case EVENT.SIGNAL_PROVIDE:
try {
let sdp = await this.webrtc.connect(payload.sdp, payload.iceservers)
this.websocket.send(EVENT.SIGNAL_ANSWER, { sdp })
this.events.emit('connection.webrtc.sdp', 'local', sdp)
} catch (e) {}
break
case EVENT.SIGNAL_CANDIDATE:
this.webrtc.setCandidate(payload)
break
case EVENT.SYSTEM_DISCONNECT:
this.websocket.disconnect(new Error('disconnected by server'))
break
}
})
this.websocket.on('connecting', () => {
Vue.set(this.state.connection, 'websocket', 'connecting') Vue.set(this.state.connection, 'websocket', 'connecting')
this.events.emit('connection.websocket', 'connecting') this.events.emit('connection.websocket', 'connecting')
}) })
this.websocket.on('connected', () => { this.connection.websocket.on('connected', () => {
Vue.set(this.state.connection, 'websocket', 'connected') Vue.set(this.state.connection, 'websocket', 'connected')
this.events.emit('connection.websocket', 'connected') this.events.emit('connection.websocket', 'connected')
@ -430,7 +412,7 @@
this.webrtcConnect() this.webrtcConnect()
} }
}) })
this.websocket.on('disconnected', () => { this.connection.websocket.on('disconnected', () => {
Vue.set(this.state.connection, 'websocket', 'disconnected') Vue.set(this.state.connection, 'websocket', 'disconnected')
this.events.emit('connection.websocket', 'disconnected') this.events.emit('connection.websocket', 'disconnected')
@ -438,7 +420,7 @@
}) })
// webrtc // webrtc
this.webrtc.on('track', (event: RTCTrackEvent) => { this.connection.webrtc.on('track', (event: RTCTrackEvent) => {
const { track, streams } = event const { track, streams } = event
if (track.kind === 'audio') return if (track.kind === 'audio') return
@ -454,13 +436,9 @@
this._video.play() this._video.play()
} }
}) })
this.webrtc.on('candidate', (candidate: RTCIceCandidateInit) => {
this.websocket.send(EVENT.SIGNAL_CANDIDATE, candidate)
this.events.emit('connection.webrtc.sdp.candidate', 'local', candidate)
})
let webrtcCongestion: number = 0 let webrtcCongestion: number = 0
this.webrtc.on('stats', (stats: WebRTCStats) => { this.connection.webrtc.on('stats', (stats: WebRTCStats) => {
Vue.set(this.state.connection.webrtc, 'stats', stats) Vue.set(this.state.connection.webrtc, 'stats', stats)
// if automatic quality adjusting is turned off // if automatic quality adjusting is turned off
@ -493,18 +471,18 @@
webrtcCongestion = 0 webrtcCongestion = 0
} }
}) })
this.webrtc.on('connecting', () => { this.connection.webrtc.on('connecting', () => {
Vue.set(this.state.connection.webrtc, 'status', 'connecting') Vue.set(this.state.connection.webrtc, 'status', 'connecting')
this.events.emit('connection.webrtc', 'connecting') this.events.emit('connection.webrtc', 'connecting')
}) })
this.webrtc.on('connected', () => { this.connection.webrtc.on('connected', () => {
Vue.set(this.state.connection.webrtc, 'status', 'connected') Vue.set(this.state.connection.webrtc, 'status', 'connected')
Vue.set(this.state.connection, 'type', 'webrtc') Vue.set(this.state.connection, 'type', 'webrtc')
this.events.emit('connection.webrtc', 'connected') this.events.emit('connection.webrtc', 'connected')
}) })
let webrtcReconnect: any = null let webrtcReconnect: any = null
this.webrtc.on('disconnected', () => { this.connection.webrtc.on('disconnected', () => {
const lastVideo = this.state.connection.webrtc.video ?? undefined const lastVideo = this.state.connection.webrtc.video ?? undefined
this.events.emit('connection.webrtc', 'disconnected') this.events.emit('connection.webrtc', 'disconnected')
@ -548,8 +526,8 @@
beforeDestroy() { beforeDestroy() {
this.observer.disconnect() this.observer.disconnect()
this.webrtc.disconnect() this.connection.webrtc.disconnect()
this.websocket.disconnect() this.connection.websocket.disconnect()
// remove users first interaction event // remove users first interaction event
document.removeEventListener('click', this.unmute) document.removeEventListener('click', this.unmute)
@ -559,12 +537,12 @@
@Watch('state.control.keyboard') @Watch('state.control.keyboard')
updateKeyboard() { updateKeyboard() {
if (this.controlling && this.state.control.keyboard.layout) { if (this.controlling && this.state.control.keyboard.layout) {
this.websocket.send(EVENT.KEYBOARD_MAP, this.state.control.keyboard) this.connection.websocket.send(EVENT.KEYBOARD_MAP, this.state.control.keyboard)
} }
} }
updateKeyboardModifiers(modifiers: { capslock: boolean; numlock: boolean }) { updateKeyboardModifiers(modifiers: { capslock: boolean; numlock: boolean }) {
this.websocket.send(EVENT.KEYBOARD_MODIFIERS, modifiers) this.connection.websocket.send(EVENT.KEYBOARD_MODIFIERS, modifiers)
} }
@Watch('state.screen.size') @Watch('state.screen.size')