mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
common errors as variable.
This commit is contained in:
parent
530cc04805
commit
343b0c562a
@ -2,6 +2,7 @@ package members
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
@ -68,14 +69,21 @@ func GetMember(r *http.Request) MemberData {
|
||||
func (h *MembersHandler) ExtractMember(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
memberId := chi.URLParam(r, "memberId")
|
||||
|
||||
profile, err := h.members.Select(memberId)
|
||||
if err != nil {
|
||||
utils.HttpNotFound(w, err)
|
||||
} else {
|
||||
next.ServeHTTP(w, SetMember(r, MemberData{
|
||||
ID: memberId,
|
||||
Profile: profile,
|
||||
}))
|
||||
if errors.Is(err, types.ErrMemberDoesNotExist) {
|
||||
utils.HttpNotFound(w, err)
|
||||
} else {
|
||||
utils.HttpInternalServerError(w, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, SetMember(r, MemberData{
|
||||
ID: memberId,
|
||||
Profile: profile,
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
package capture
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
@ -9,6 +8,7 @@ import (
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"demodesk/neko/internal/capture/gst"
|
||||
"demodesk/neko/internal/types"
|
||||
)
|
||||
|
||||
type BroacastManagerCtx struct {
|
||||
@ -67,7 +67,7 @@ func (manager *BroacastManagerCtx) Url() string {
|
||||
|
||||
func (manager *BroacastManagerCtx) createPipeline() error {
|
||||
if manager.pipeline != nil {
|
||||
return fmt.Errorf("pipeline already exists")
|
||||
return types.ErrCapturePipelineAlreadyExists
|
||||
}
|
||||
|
||||
var err error
|
||||
|
@ -1,7 +1,7 @@
|
||||
package capture
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@ -93,12 +93,12 @@ func (manager *ScreencastManagerCtx) Image() ([]byte, error) {
|
||||
case sample := <-manager.sample:
|
||||
return sample.Data, nil
|
||||
case <-time.After(1 * time.Second):
|
||||
return nil, fmt.Errorf("timeouted")
|
||||
return nil, errors.New("timeouted")
|
||||
}
|
||||
}
|
||||
|
||||
if manager.image.Data == nil {
|
||||
return nil, fmt.Errorf("image sample not found")
|
||||
return nil, errors.New("image sample not found")
|
||||
}
|
||||
|
||||
return manager.image.Data, nil
|
||||
@ -109,7 +109,7 @@ func (manager *ScreencastManagerCtx) start() error {
|
||||
defer manager.mu.Unlock()
|
||||
|
||||
if !manager.enabled {
|
||||
return fmt.Errorf("screenshot pipeline not enabled")
|
||||
return errors.New("screenshot pipeline not enabled")
|
||||
}
|
||||
|
||||
err := manager.createPipeline()
|
||||
@ -131,7 +131,7 @@ func (manager *ScreencastManagerCtx) stop() {
|
||||
|
||||
func (manager *ScreencastManagerCtx) createPipeline() error {
|
||||
if manager.pipeline != nil {
|
||||
return fmt.Errorf("pipeline already exists")
|
||||
return types.ErrCapturePipelineAlreadyExists
|
||||
}
|
||||
|
||||
var err error
|
||||
|
@ -1,7 +1,6 @@
|
||||
package capture
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
@ -141,7 +140,7 @@ func (manager *StreamManagerCtx) Started() bool {
|
||||
|
||||
func (manager *StreamManagerCtx) createPipeline() error {
|
||||
if manager.pipeline != nil {
|
||||
return fmt.Errorf("pipeline already exists")
|
||||
return types.ErrCapturePipelineAlreadyExists
|
||||
}
|
||||
|
||||
var err error
|
||||
|
@ -1,7 +1,7 @@
|
||||
package desktop
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
@ -46,7 +46,7 @@ func (manager *DesktopManagerCtx) HandleFileChooserDialog(uri string) error {
|
||||
|
||||
// if last command didn't return error, consider dialog as still open
|
||||
if err2 == nil {
|
||||
return fmt.Errorf("unable to select files in dialog")
|
||||
return errors.New("unable to select files in dialog")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -1,7 +1,7 @@
|
||||
package dummy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
|
||||
"demodesk/neko/internal/types"
|
||||
)
|
||||
@ -37,7 +37,7 @@ func (provider *MemberProviderCtx) Authenticate(username string, password string
|
||||
}
|
||||
|
||||
func (provider *MemberProviderCtx) Insert(username string, password string, profile types.MemberProfile) (string, error) {
|
||||
return "", fmt.Errorf("not implemented")
|
||||
return "", errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (provider *MemberProviderCtx) Select(id string) (types.MemberProfile, error) {
|
||||
@ -54,7 +54,7 @@ func (provider *MemberProviderCtx) UpdateProfile(id string, profile types.Member
|
||||
}
|
||||
|
||||
func (provider *MemberProviderCtx) UpdatePassword(id string, password string) error {
|
||||
return fmt.Errorf("not implemented")
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (provider *MemberProviderCtx) Delete(id string) error {
|
||||
|
@ -2,7 +2,6 @@ package file
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
@ -38,7 +37,7 @@ func (provider *MemberProviderCtx) Authenticate(username string, password string
|
||||
|
||||
// TODO: Use hash function.
|
||||
if entry.Password != password {
|
||||
return "", types.MemberProfile{}, fmt.Errorf("invalid password")
|
||||
return "", types.MemberProfile{}, types.ErrMemberInvalidPassword
|
||||
}
|
||||
|
||||
return id, entry.Profile, nil
|
||||
@ -55,7 +54,7 @@ func (provider *MemberProviderCtx) Insert(username string, password string, prof
|
||||
|
||||
_, ok := entries[id]
|
||||
if ok {
|
||||
return "", fmt.Errorf("member ID already exists")
|
||||
return "", types.ErrMemberAlreadyExists
|
||||
}
|
||||
|
||||
entries[id] = MemberEntry{
|
||||
@ -75,7 +74,7 @@ func (provider *MemberProviderCtx) UpdateProfile(id string, profile types.Member
|
||||
|
||||
entry, ok := entries[id]
|
||||
if !ok {
|
||||
return fmt.Errorf("member ID does not exist")
|
||||
return types.ErrMemberDoesNotExist
|
||||
}
|
||||
|
||||
entry.Profile = profile
|
||||
@ -92,7 +91,7 @@ func (provider *MemberProviderCtx) UpdatePassword(id string, password string) er
|
||||
|
||||
entry, ok := entries[id]
|
||||
if !ok {
|
||||
return fmt.Errorf("member ID does not exist")
|
||||
return types.ErrMemberDoesNotExist
|
||||
}
|
||||
|
||||
// TODO: Use hash function.
|
||||
@ -139,7 +138,7 @@ func (provider *MemberProviderCtx) Delete(id string) error {
|
||||
|
||||
_, ok := entries[id]
|
||||
if !ok {
|
||||
return fmt.Errorf("member ID does not exist")
|
||||
return types.ErrMemberDoesNotExist
|
||||
}
|
||||
|
||||
delete(entries, id)
|
||||
@ -178,7 +177,7 @@ func (provider *MemberProviderCtx) getEntry(id string) (MemberEntry, error) {
|
||||
|
||||
entry, ok := entries[id]
|
||||
if !ok {
|
||||
return MemberEntry{}, fmt.Errorf("member ID does not exist")
|
||||
return MemberEntry{}, types.ErrMemberDoesNotExist
|
||||
}
|
||||
|
||||
return entry, nil
|
||||
|
@ -1,7 +1,7 @@
|
||||
package member
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
@ -41,7 +41,7 @@ type MemberManagerCtx struct {
|
||||
config *config.Member
|
||||
providerMu sync.Mutex
|
||||
provider types.MemberProvider
|
||||
sessionMu sync.Mutex
|
||||
loginMu sync.Mutex
|
||||
}
|
||||
|
||||
func (manager *MemberManagerCtx) Connect() error {
|
||||
@ -91,13 +91,10 @@ func (manager *MemberManagerCtx) UpdateProfile(id string, profile types.MemberPr
|
||||
defer manager.providerMu.Unlock()
|
||||
|
||||
// update corresponding session, if exists
|
||||
manager.sessionMu.Lock()
|
||||
if _, ok := manager.sessions.Get(id); ok {
|
||||
if err := manager.sessions.Update(id, profile); err != nil {
|
||||
manager.logger.Err(err).Msg("error while updating session")
|
||||
}
|
||||
err := manager.sessions.Update(id, profile)
|
||||
if err != nil && !errors.Is(err, types.ErrSessionNotFound) {
|
||||
manager.logger.Err(err).Msg("error while updating session")
|
||||
}
|
||||
manager.sessionMu.Unlock()
|
||||
|
||||
return manager.provider.UpdateProfile(id, profile)
|
||||
}
|
||||
@ -114,13 +111,10 @@ func (manager *MemberManagerCtx) Delete(id string) error {
|
||||
defer manager.providerMu.Unlock()
|
||||
|
||||
// destroy corresponding session, if exists
|
||||
manager.sessionMu.Lock()
|
||||
if _, ok := manager.sessions.Get(id); ok {
|
||||
if err := manager.sessions.Delete(id); err != nil {
|
||||
manager.logger.Err(err).Msg("error while deleting session")
|
||||
}
|
||||
err := manager.sessions.Delete(id)
|
||||
if err != nil && !errors.Is(err, types.ErrSessionNotFound) {
|
||||
manager.logger.Err(err).Msg("error while deleting session")
|
||||
}
|
||||
manager.sessionMu.Unlock()
|
||||
|
||||
return manager.provider.Delete(id)
|
||||
}
|
||||
@ -130,8 +124,8 @@ func (manager *MemberManagerCtx) Delete(id string) error {
|
||||
//
|
||||
|
||||
func (manager *MemberManagerCtx) Login(username string, password string) (types.Session, string, error) {
|
||||
manager.sessionMu.Lock()
|
||||
defer manager.sessionMu.Unlock()
|
||||
manager.loginMu.Lock()
|
||||
defer manager.loginMu.Unlock()
|
||||
|
||||
id, profile, err := manager.provider.Authenticate(username, password)
|
||||
if err != nil {
|
||||
@ -141,7 +135,7 @@ func (manager *MemberManagerCtx) Login(username string, password string) (types.
|
||||
session, ok := manager.sessions.Get(id)
|
||||
if ok {
|
||||
if session.State().IsConnected {
|
||||
return nil, "", fmt.Errorf("session is already connected")
|
||||
return nil, "", types.ErrSessionAlreadyConnected
|
||||
}
|
||||
|
||||
// TODO: Replace session.
|
||||
@ -154,12 +148,8 @@ func (manager *MemberManagerCtx) Login(username string, password string) (types.
|
||||
}
|
||||
|
||||
func (manager *MemberManagerCtx) Logout(id string) error {
|
||||
manager.sessionMu.Lock()
|
||||
defer manager.sessionMu.Unlock()
|
||||
|
||||
if _, ok := manager.sessions.Get(id); !ok {
|
||||
return fmt.Errorf("session not found")
|
||||
}
|
||||
manager.loginMu.Lock()
|
||||
defer manager.loginMu.Unlock()
|
||||
|
||||
return manager.sessions.Delete(id)
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"demodesk/neko/internal/types"
|
||||
)
|
||||
|
||||
@ -60,12 +58,12 @@ func (provider *MemberProviderCtx) Authenticate(username string, password string
|
||||
|
||||
entry, ok := provider.entries[id]
|
||||
if !ok {
|
||||
return "", types.MemberProfile{}, fmt.Errorf("member ID does not exist")
|
||||
return "", types.MemberProfile{}, types.ErrMemberDoesNotExist
|
||||
}
|
||||
|
||||
// TODO: Use hash function.
|
||||
if entry.Password != password {
|
||||
return "", types.MemberProfile{}, fmt.Errorf("invalid password")
|
||||
return "", types.MemberProfile{}, types.ErrMemberInvalidPassword
|
||||
}
|
||||
|
||||
return id, entry.Profile, nil
|
||||
@ -77,7 +75,7 @@ func (provider *MemberProviderCtx) Insert(username string, password string, prof
|
||||
|
||||
_, ok := provider.entries[id]
|
||||
if ok {
|
||||
return "", fmt.Errorf("member ID already exists")
|
||||
return "", types.ErrMemberAlreadyExists
|
||||
}
|
||||
|
||||
provider.entries[id] = &MemberEntry{
|
||||
@ -92,7 +90,7 @@ func (provider *MemberProviderCtx) Insert(username string, password string, prof
|
||||
func (provider *MemberProviderCtx) UpdateProfile(id string, profile types.MemberProfile) error {
|
||||
entry, ok := provider.entries[id]
|
||||
if !ok {
|
||||
return fmt.Errorf("member ID does not exist")
|
||||
return types.ErrMemberDoesNotExist
|
||||
}
|
||||
|
||||
entry.Profile = profile
|
||||
@ -103,7 +101,7 @@ func (provider *MemberProviderCtx) UpdateProfile(id string, profile types.Member
|
||||
func (provider *MemberProviderCtx) UpdatePassword(id string, password string) error {
|
||||
entry, ok := provider.entries[id]
|
||||
if !ok {
|
||||
return fmt.Errorf("member ID does not exist")
|
||||
return types.ErrMemberDoesNotExist
|
||||
}
|
||||
|
||||
// TODO: Use hash function.
|
||||
@ -115,7 +113,7 @@ func (provider *MemberProviderCtx) UpdatePassword(id string, password string) er
|
||||
func (provider *MemberProviderCtx) Select(id string) (types.MemberProfile, error) {
|
||||
entry, ok := provider.entries[id]
|
||||
if !ok {
|
||||
return types.MemberProfile{}, fmt.Errorf("member ID does not exist")
|
||||
return types.MemberProfile{}, types.ErrMemberDoesNotExist
|
||||
}
|
||||
|
||||
return entry.Profile, nil
|
||||
@ -139,7 +137,7 @@ func (provider *MemberProviderCtx) SelectAll(limit int, offset int) (map[string]
|
||||
func (provider *MemberProviderCtx) Delete(id string) error {
|
||||
_, ok := provider.entries[id]
|
||||
if !ok {
|
||||
return fmt.Errorf("member ID does not exist")
|
||||
return types.ErrMemberDoesNotExist
|
||||
}
|
||||
|
||||
delete(provider.entries, id)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@ -39,16 +39,16 @@ func (manager *SessionManagerCtx) CookieClearToken(w http.ResponseWriter, r *htt
|
||||
func (manager *SessionManagerCtx) Authenticate(r *http.Request) (types.Session, error) {
|
||||
token, ok := manager.getToken(r)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no authentication provided")
|
||||
return nil, errors.New("no authentication provided")
|
||||
}
|
||||
|
||||
session, ok := manager.GetByToken(token)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("session not found")
|
||||
return nil, types.ErrSessionNotFound
|
||||
}
|
||||
|
||||
if !session.Profile().CanLogin {
|
||||
return nil, fmt.Errorf("login disabled")
|
||||
return nil, types.ErrSessionLoginDisabled
|
||||
}
|
||||
|
||||
return session, nil
|
||||
|
@ -1,7 +1,7 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/kataras/go-events"
|
||||
@ -68,12 +68,12 @@ func (manager *SessionManagerCtx) Create(id string, profile types.MemberProfile)
|
||||
manager.sessionsMu.Lock()
|
||||
if _, ok := manager.sessions[id]; ok {
|
||||
manager.sessionsMu.Unlock()
|
||||
return nil, "", fmt.Errorf("session id already exists")
|
||||
return nil, "", types.ErrSessionAlreadyExists
|
||||
}
|
||||
|
||||
if _, ok := manager.tokens[token]; ok {
|
||||
manager.sessionsMu.Unlock()
|
||||
return nil, "", fmt.Errorf("session token already exists")
|
||||
return nil, "", errors.New("session token already exists")
|
||||
}
|
||||
|
||||
session := &SessionCtx{
|
||||
@ -98,7 +98,7 @@ func (manager *SessionManagerCtx) Update(id string, profile types.MemberProfile)
|
||||
session, ok := manager.sessions[id]
|
||||
if !ok {
|
||||
manager.sessionsMu.Unlock()
|
||||
return fmt.Errorf("session id not found")
|
||||
return types.ErrSessionNotFound
|
||||
}
|
||||
|
||||
session.profile = profile
|
||||
@ -114,7 +114,7 @@ func (manager *SessionManagerCtx) Delete(id string) error {
|
||||
session, ok := manager.sessions[id]
|
||||
if !ok {
|
||||
manager.sessionsMu.Unlock()
|
||||
return fmt.Errorf("session id not found")
|
||||
return types.ErrSessionNotFound
|
||||
}
|
||||
|
||||
delete(manager.tokens, session.token)
|
||||
|
@ -2,6 +2,7 @@ package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
@ -12,6 +13,10 @@ import (
|
||||
"demodesk/neko/internal/types/codec"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCapturePipelineAlreadyExists = errors.New("capture pipeline already exists")
|
||||
)
|
||||
|
||||
type Sample media.Sample
|
||||
|
||||
type BroadcastManager interface {
|
||||
|
@ -1,5 +1,13 @@
|
||||
package types
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrMemberAlreadyExists = errors.New("member already exists")
|
||||
ErrMemberDoesNotExist = errors.New("member does not exist")
|
||||
ErrMemberInvalidPassword = errors.New("invalid password")
|
||||
)
|
||||
|
||||
type MemberProfile struct {
|
||||
Name string `json:"name"`
|
||||
IsAdmin bool `json:"is_admin"`
|
||||
|
@ -1,6 +1,16 @@
|
||||
package types
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrSessionNotFound = errors.New("session not found")
|
||||
ErrSessionAlreadyExists = errors.New("session already exists")
|
||||
ErrSessionAlreadyConnected = errors.New("session is already connected")
|
||||
ErrSessionLoginDisabled = errors.New("session login disabled")
|
||||
)
|
||||
|
||||
type SessionState struct {
|
||||
IsConnected bool `json:"is_connected"`
|
||||
|
@ -1,6 +1,15 @@
|
||||
package types
|
||||
|
||||
import "github.com/pion/webrtc/v3"
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/pion/webrtc/v3"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrWebRTCVideoNotFound = errors.New("webrtc video not found")
|
||||
ErrWebRTCDataChannelNotFound = errors.New("webrtc data channel not found")
|
||||
)
|
||||
|
||||
type ICEServer struct {
|
||||
URLs []string `mapstructure:"urls" json:"urls"`
|
||||
|
@ -66,7 +66,7 @@ func (manager *WebRTCManagerCtx) Start() {
|
||||
}
|
||||
|
||||
audioListener := func(sample types.Sample) {
|
||||
if err := manager.audioTrack.WriteSample(media.Sample(sample)); err != nil && err != io.ErrClosedPipe {
|
||||
if err := manager.audioTrack.WriteSample(media.Sample(sample)); err != nil {
|
||||
if errors.Is(err, io.ErrClosedPipe) {
|
||||
// The peerConnection has been closed.
|
||||
return
|
||||
@ -166,7 +166,7 @@ func (manager *WebRTCManagerCtx) CreatePeer(session types.Session, videoID strin
|
||||
}
|
||||
|
||||
videoListener := func(sample types.Sample) {
|
||||
if err := videoTrack.WriteSample(media.Sample(sample)); err != nil && err != io.ErrClosedPipe {
|
||||
if err := videoTrack.WriteSample(media.Sample(sample)); err != nil {
|
||||
if errors.Is(err, io.ErrClosedPipe) {
|
||||
// The peerConnection has been closed.
|
||||
return
|
||||
@ -200,7 +200,7 @@ func (manager *WebRTCManagerCtx) CreatePeer(session types.Session, videoID strin
|
||||
changeVideo := func(videoID string) error {
|
||||
newVideoStream, ok := manager.capture.Video(videoID)
|
||||
if !ok {
|
||||
return fmt.Errorf("video stream not found")
|
||||
return types.ErrWebRTCVideoNotFound
|
||||
}
|
||||
|
||||
// should be new stream started
|
||||
@ -357,7 +357,7 @@ func (manager *WebRTCManagerCtx) mediaEngine(videoID string) (*webrtc.MediaEngin
|
||||
// all videos must have the same codec
|
||||
video, ok := manager.capture.Video(videoID)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("selected video track not found")
|
||||
return nil, types.ErrWebRTCVideoNotFound
|
||||
}
|
||||
|
||||
videoCodec := video.Codec()
|
||||
|
@ -3,7 +3,6 @@ package webrtc
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"demodesk/neko/internal/types"
|
||||
)
|
||||
@ -28,8 +27,11 @@ type PayloadCursorImage struct {
|
||||
}
|
||||
|
||||
func (peer *WebRTCPeerCtx) SendCursorPosition(x, y int) error {
|
||||
peer.mu.Lock()
|
||||
defer peer.mu.Unlock()
|
||||
|
||||
if peer.dataChannel == nil {
|
||||
return fmt.Errorf("no data channel")
|
||||
return types.ErrWebRTCDataChannelNotFound
|
||||
}
|
||||
|
||||
data := PayloadCursorPosition{
|
||||
@ -50,8 +52,11 @@ func (peer *WebRTCPeerCtx) SendCursorPosition(x, y int) error {
|
||||
}
|
||||
|
||||
func (peer *WebRTCPeerCtx) SendCursorImage(cur *types.CursorImage, img []byte) error {
|
||||
peer.mu.Lock()
|
||||
defer peer.mu.Unlock()
|
||||
|
||||
if peer.dataChannel == nil {
|
||||
return fmt.Errorf("no data channel")
|
||||
return types.ErrWebRTCDataChannelNotFound
|
||||
}
|
||||
|
||||
data := PayloadCursorImage{
|
||||
|
Loading…
Reference in New Issue
Block a user