init commit

This commit is contained in:
wenyifan 2023-06-02 15:41:51 +08:00
commit f0681b77bd
31 changed files with 4651 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.idea
parsec
parsec.exe
config.conf

32
9d18eed7.0 Normal file
View File

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFbjCCA1agAwIBAgIII1tVz2zmPdEwDQYJKoZIhvcNAQELBQAwTDELMAkGA1UE
BhMCQ04xDTALBgNVBAoTBEV2YW4xDTALBgNVBAsTBEV2YW4xHzAdBgNVBAMTFkV2
YW4gQXNzdXJhbmNlIFJvb3QgQ0EwIBcNMDgwMTAxMDAwMDAwWhgPMjA1MDEyMzEy
MzU5NTlaMEwxCzAJBgNVBAYTAkNOMQ0wCwYDVQQKEwRFdmFuMQ0wCwYDVQQLEwRF
dmFuMR8wHQYDVQQDExZFdmFuIEFzc3VyYW5jZSBSb290IENBMIICIjANBgkqhkiG
9w0BAQEFAAOCAg8AMIICCgKCAgEAq4vXbtUk8y6n3AgH7ApVQ7KOSwymwQnRg0HQ
iZJfamIWYsa1uN7n2/W3tJy82ij0JyIqqiUfTZu8dr6yd12Um7RTN1j32x+3hzSF
+2Hag08vd4ksrHAm61MjZVqdQdi5+EMwR5YlaEYFrgBYsKfo+B4vjFYz2PW7IFlI
SSeHBrtJdKf+pIr0+e2Sp7O91NwAp3JiDChqqyMQbKLaXag0G0o422Mj555L2k6r
E1lNm2JzMKaHqP2Ql/GtCaZXmsD4oRtBfMOr5OkNeGA0lA9OXkZxDXYTGrgGfaY+
kq7ig12eBwa2f09x++aPAYxci8I5fZGCpCPY5siAUpw7r9Tui8d2KOCVDGF4FZ3+
vDnLDPXrrN/2FYHt0p/kNqn9WINLOtfQyk8Ko+zicxjTA9DH5tNyKvxs5NncVXeH
Gjgmto3OAJcRNfu/NOo3IEHRmyOHCtMK/U1oR3ByYsRxc9raKrPI/CBcJQ48XIut
OxdQ+5cK+7IumEXqiAxnvKK1SMBVMnSav7jrfAfq66RNOOTvxng1p2W52dTJIIZB
KcEpzrnMGmLLDba9Zp7v/6PsXYLtwzg0oOJZcnnqOCMz2jMt9o06HFlqYke8Z8Nb
35njmwN4PWyHpEZhbC9VttXqVVD7UaAMStsP2yS2L6Wdt3Af7Mr/kurhLBwK6XOp
H403rdsCAwEAAaNSMFAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9GuH/q/
TaECv2bMT75Rrso+Hu4wCwYDVR0PBAQDAgGGMBEGCWCGSAGG+EIBAQQEAwIABzAN
BgkqhkiG9w0BAQsFAAOCAgEAjsWIGSK2ZJOiCBoH4CqMQNJa2nWOAAY99op8BXRi
Sx8tpbid+mZ6IOIBOY4GLT82lkbDbGmAOXhjDD9pMWYMbxko35MNX3j1/9BAIKU/
W4U5NEIEnWogJJirJjtW+3BGSrbtZyTODCGf2nuJQsXz+YnBAVUNjKILmAVR1bCx
KSXo9YJdrfroHTxk+TB6wewiO8cs5/YlMfKQEyUxTdMOEzRdGvl0dkw6t4346BcS
FqY0tpJ6tvbatVjc+ka//ZxBdKTHWJqkcR0f5g91L0AMllRnAUKAyXIzxUlMB8zN
zfKXhSce2Wk+39kBaDrw4YS6SiJTXVX5ID2Myz/NPDY3upbjPVtDABloF35PcKlr
lekQmYUtN+QoHCb+LEkmHn6/AUdUke7J0Vr1gtqEjqC4f5zPVAx81ZLoBU3FQvb/
DN8QLfdI2/qelfubCRv/XDH+ybhd9aQizKacbcEvpCCLCDkkDqhQyToVoJyeWlGY
s+v2oTcP2x+gPs/2uFGsCQE2U8re9B6BOwYaYaAhoPmHSz4hr7oYc45xop1B+70G
GTGHMHbrbKMPld66dKJQEJ9+mtkJbBrsX/ZHbRHCUjU2yMasSV/S9YHYWWqhC8E+
BSCwrlnMnu4TTxF7uHx0nzWOJFRVfmowsfN4ffHDVoAXfuN7NLc56JsJszVyY0ij
YY8=
-----END CERTIFICATE-----

View File

@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIIFbjCCA1agAwIBAgIII1tVz2zmPdEwDQYJKoZIhvcNAQELBQAwTDELMAkGA1UE
BhMCQ04xDTALBgNVBAoTBEV2YW4xDTALBgNVBAsTBEV2YW4xHzAdBgNVBAMTFkV2
YW4gQXNzdXJhbmNlIFJvb3QgQ0EwIBcNMDgwMTAxMDAwMDAwWhgPMjA1MDEyMzEy
MzU5NTlaMEwxCzAJBgNVBAYTAkNOMQ0wCwYDVQQKEwRFdmFuMQ0wCwYDVQQLEwRF
dmFuMR8wHQYDVQQDExZFdmFuIEFzc3VyYW5jZSBSb290IENBMIICIjANBgkqhkiG
9w0BAQEFAAOCAg8AMIICCgKCAgEAq4vXbtUk8y6n3AgH7ApVQ7KOSwymwQnRg0HQ
iZJfamIWYsa1uN7n2/W3tJy82ij0JyIqqiUfTZu8dr6yd12Um7RTN1j32x+3hzSF
+2Hag08vd4ksrHAm61MjZVqdQdi5+EMwR5YlaEYFrgBYsKfo+B4vjFYz2PW7IFlI
SSeHBrtJdKf+pIr0+e2Sp7O91NwAp3JiDChqqyMQbKLaXag0G0o422Mj555L2k6r
E1lNm2JzMKaHqP2Ql/GtCaZXmsD4oRtBfMOr5OkNeGA0lA9OXkZxDXYTGrgGfaY+
kq7ig12eBwa2f09x++aPAYxci8I5fZGCpCPY5siAUpw7r9Tui8d2KOCVDGF4FZ3+
vDnLDPXrrN/2FYHt0p/kNqn9WINLOtfQyk8Ko+zicxjTA9DH5tNyKvxs5NncVXeH
Gjgmto3OAJcRNfu/NOo3IEHRmyOHCtMK/U1oR3ByYsRxc9raKrPI/CBcJQ48XIut
OxdQ+5cK+7IumEXqiAxnvKK1SMBVMnSav7jrfAfq66RNOOTvxng1p2W52dTJIIZB
KcEpzrnMGmLLDba9Zp7v/6PsXYLtwzg0oOJZcnnqOCMz2jMt9o06HFlqYke8Z8Nb
35njmwN4PWyHpEZhbC9VttXqVVD7UaAMStsP2yS2L6Wdt3Af7Mr/kurhLBwK6XOp
H403rdsCAwEAAaNSMFAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9GuH/q/
TaECv2bMT75Rrso+Hu4wCwYDVR0PBAQDAgGGMBEGCWCGSAGG+EIBAQQEAwIABzAN
BgkqhkiG9w0BAQsFAAOCAgEAjsWIGSK2ZJOiCBoH4CqMQNJa2nWOAAY99op8BXRi
Sx8tpbid+mZ6IOIBOY4GLT82lkbDbGmAOXhjDD9pMWYMbxko35MNX3j1/9BAIKU/
W4U5NEIEnWogJJirJjtW+3BGSrbtZyTODCGf2nuJQsXz+YnBAVUNjKILmAVR1bCx
KSXo9YJdrfroHTxk+TB6wewiO8cs5/YlMfKQEyUxTdMOEzRdGvl0dkw6t4346BcS
FqY0tpJ6tvbatVjc+ka//ZxBdKTHWJqkcR0f5g91L0AMllRnAUKAyXIzxUlMB8zN
zfKXhSce2Wk+39kBaDrw4YS6SiJTXVX5ID2Myz/NPDY3upbjPVtDABloF35PcKlr
lekQmYUtN+QoHCb+LEkmHn6/AUdUke7J0Vr1gtqEjqC4f5zPVAx81ZLoBU3FQvb/
DN8QLfdI2/qelfubCRv/XDH+ybhd9aQizKacbcEvpCCLCDkkDqhQyToVoJyeWlGY
s+v2oTcP2x+gPs/2uFGsCQE2U8re9B6BOwYaYaAhoPmHSz4hr7oYc45xop1B+70G
GTGHMHbrbKMPld66dKJQEJ9+mtkJbBrsX/ZHbRHCUjU2yMasSV/S9YHYWWqhC8E+
BSCwrlnMnu4TTxF7uHx0nzWOJFRVfmowsfN4ffHDVoAXfuN7NLc56JsJszVyY0ij
YY8=
-----END CERTIFICATE-----

81
Evan_Timestamp.pem Normal file
View File

@ -0,0 +1,81 @@
-----BEGIN CERTIFICATE-----
MIIDlDCCAnygAwIBAgIIRgtihNJXdBgwDQYJKoZIhvcNAQELBQAwSjELMAkGA1UE
BhMCQ04xDTALBgNVBAoTBEV2YW4xDTALBgNVBAsTBEV2YW4xHTAbBgNVBAMTFEV2
YW4gVGltZVN0YW1waW5nIENBMB4XDTA5MDEwMTAwMDAwMFoXDTMwMTIzMTIzNTk1
OVowRDELMAkGA1UEBhMCQ04xDTALBgNVBAoTBEV2YW4xDTALBgNVBAsTBEV2YW4x
FzAVBgNVBAMTDkV2YW4gVGltZXN0YW1wMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEApfi74R3iPCAOs5mas6J/GYzmkJsgd9iqCZla55l9sDt3L+umpzuz
PbsVUMJ8P4+Px/qA4YAaSC7Srnrr8I1CpoMXv78pQcZ7HuKvy6aTuOcRpQP0CNBp
TRBjuWfsNZp++6dw0Vrh72/Ns4TwMQk5KoWt/+Z7uiCqHJfIss49QpLh3jBvoXeE
puLFSgRArI+5f2C6Rnu1jsVLDMUQIEeOFLixUFaSflyRRK3alrrkcjFmx48vGkjq
RK3o5RAk91jGh9LXxSOP9Z6b4ux+F19p1I0fNzPUBEcJpI2bxXlqQuNRzeqQVcV7
HhcqLHTAVdQU7TSJGGS07j1r8qqIcSiBLwIDAQABo4GDMIGAMAkGA1UdEwQCMAAw
HQYDVR0OBBYEFGSZBnFg3fovfS6FKbgM7RPcxfR6MB8GA1UdIwQYMBaAFGvYa8eJ
vx15Fl5lNKZH1gDJYhh1MAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcD
CDARBglghkgBhvhCAQEEBAMCBBAwDQYJKoZIhvcNAQELBQADggEBAJ30DQkU/ITD
BBdz6NWPUHL/s5J6y16KoU2jf0kquXo0srfv69QueEqHYrrwhBnbmUMP6hlva6iy
Jmi/7MxZmBZFLERzy7xoX6ilaZ/Cz+pfrZFG3yF7rysWCyzSC1FwGElShSxs0g7A
mvJkfdMI3ZRZQF666VGKugbv2Q2Xey8PieTpHPpWCChpu8+MHdEvcy6HzR88XHsZ
GXkEnbktKHIN7MWaBO51FeyrLO7zCYEHZbMeqZ+jgeKeGzzGfMEvt2BKsWGTyO4I
Cdoa2+1ZLAjY9O8MWJt5JkCVJYsvdI8dk38VgKdiP6Wf2MiOhBWo/T6lKnVY1EhI
dI0VxXLD4X4=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEojCCAoqgAwIBAgIIHJxNBAcVXhowDQYJKoZIhvcNAQELBQAwTDELMAkGA1UE
BhMCQ04xDTALBgNVBAoTBEV2YW4xDTALBgNVBAsTBEV2YW4xHzAdBgNVBAMTFkV2
YW4gQXNzdXJhbmNlIFJvb3QgQ0EwHhcNMDkwMTAxMDAwMDAwWhcNMzAxMjMxMjM1
OTU5WjBKMQswCQYDVQQGEwJDTjENMAsGA1UEChMERXZhbjENMAsGA1UECxMERXZh
bjEdMBsGA1UEAxMURXZhbiBUaW1lU3RhbXBpbmcgQ0EwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDlatIMGNtfKEToe7u19hC4lxHUS5HyWS5Utp7gQ/La
zE3sXYAhsuNdVsWmXWL6O4xU7P/kX2i7OJyumw63xPsqrSKq0l1vN4y5GHFK0XBj
wynyPbF9GaUMfKGio/Z3wt64UtIEc+LYi/IAzLOsr16fszwVUYNxDFkD6fPd27UT
Fh7Pk0rQufatQX9stBAD0A0zsAGhM83ID69q3eFahgCsBL45YRaHrVZ8TdavtM0M
DCiKC6qEGtiF/Px+OMyKMnxAKnzXMPDYHfzXttcW2PhsWVU7O7cn5Xwk2R17DhL/
p/s36rWWZRsaYL97iE35DherZIwYK2EUAFxTVfX1ibHZAgMBAAGjgYkwgYYwDwYD
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUa9hrx4m/HXkWXmU0pkfWAMliGHUwHwYD
VR0jBBgwFoAUK9GuH/q/TaECv2bMT75Rrso+Hu4wCwYDVR0PBAQDAgGGMBMGA1Ud
JQQMMAoGCCsGAQUFBwMIMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG9w0BAQsF
AAOCAgEAUcvv6I96lTEjWd4Q50Z6Sm82UDQ48ZCGvT6OuKpqJ3/Em6+dfAT6laT0
r0DSFEdqLNJVVHwqPN0FU48JVFAFu1UpZvc0YirsN1iBc/UTmtb6Z7F4NrjSz61/
PG2nKzonZO5jfYRFt80tyYCULbN1pTZ49P/sfwf0cDrSwWAtp/7B1H7wuzpIcuM2
XAJldRY3deGFIYWehMrvrgJZEJcRhDCpKk4mxQDnHWBXND8OPL+h0p4BgepT6jJv
u/mjy5C1cIFdvL1h/hSAv46TpdKp5M4FbB83LB9b4elChUzDW9a40Y5nxhjFPWJF
BbRxlKXTbcRseUe/FVf3EALU+hgw7B+cQ8LGmdfggb10DNe3hWiPATPmr1bYSNrv
WNj9Fz64UrENlfg4g+EL+4POfQTYbXfPwb4zFwxz79RkI/3T5WEsMDWzw2ReCz67
lSrHObeu2H1qt2jhQyS7kJD0IEsVQ1KhsPreXPYMDhxY7xAtJCsfxHlkrPRGj2ov
jnqbdbFBDBA0hBbi/RyMAezIxqD5q26JdrnwLLQ41L9+1wXk99UpGu8obh/STEL8
MvhHapDGh5u76DZMey47PrvTdzpoKxUxfetlRPD9rCdhmm8+k4Flb8knv09T8i01
BMVa7lCgGtrSJIRKmjl9FPrja9Jn6OYfSQHIioueDQDTtOmf734=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFbjCCA1agAwIBAgIII1tVz2zmPdEwDQYJKoZIhvcNAQELBQAwTDELMAkGA1UE
BhMCQ04xDTALBgNVBAoTBEV2YW4xDTALBgNVBAsTBEV2YW4xHzAdBgNVBAMTFkV2
YW4gQXNzdXJhbmNlIFJvb3QgQ0EwIBcNMDgwMTAxMDAwMDAwWhgPMjA1MDEyMzEy
MzU5NTlaMEwxCzAJBgNVBAYTAkNOMQ0wCwYDVQQKEwRFdmFuMQ0wCwYDVQQLEwRF
dmFuMR8wHQYDVQQDExZFdmFuIEFzc3VyYW5jZSBSb290IENBMIICIjANBgkqhkiG
9w0BAQEFAAOCAg8AMIICCgKCAgEAq4vXbtUk8y6n3AgH7ApVQ7KOSwymwQnRg0HQ
iZJfamIWYsa1uN7n2/W3tJy82ij0JyIqqiUfTZu8dr6yd12Um7RTN1j32x+3hzSF
+2Hag08vd4ksrHAm61MjZVqdQdi5+EMwR5YlaEYFrgBYsKfo+B4vjFYz2PW7IFlI
SSeHBrtJdKf+pIr0+e2Sp7O91NwAp3JiDChqqyMQbKLaXag0G0o422Mj555L2k6r
E1lNm2JzMKaHqP2Ql/GtCaZXmsD4oRtBfMOr5OkNeGA0lA9OXkZxDXYTGrgGfaY+
kq7ig12eBwa2f09x++aPAYxci8I5fZGCpCPY5siAUpw7r9Tui8d2KOCVDGF4FZ3+
vDnLDPXrrN/2FYHt0p/kNqn9WINLOtfQyk8Ko+zicxjTA9DH5tNyKvxs5NncVXeH
Gjgmto3OAJcRNfu/NOo3IEHRmyOHCtMK/U1oR3ByYsRxc9raKrPI/CBcJQ48XIut
OxdQ+5cK+7IumEXqiAxnvKK1SMBVMnSav7jrfAfq66RNOOTvxng1p2W52dTJIIZB
KcEpzrnMGmLLDba9Zp7v/6PsXYLtwzg0oOJZcnnqOCMz2jMt9o06HFlqYke8Z8Nb
35njmwN4PWyHpEZhbC9VttXqVVD7UaAMStsP2yS2L6Wdt3Af7Mr/kurhLBwK6XOp
H403rdsCAwEAAaNSMFAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9GuH/q/
TaECv2bMT75Rrso+Hu4wCwYDVR0PBAQDAgGGMBEGCWCGSAGG+EIBAQQEAwIABzAN
BgkqhkiG9w0BAQsFAAOCAgEAjsWIGSK2ZJOiCBoH4CqMQNJa2nWOAAY99op8BXRi
Sx8tpbid+mZ6IOIBOY4GLT82lkbDbGmAOXhjDD9pMWYMbxko35MNX3j1/9BAIKU/
W4U5NEIEnWogJJirJjtW+3BGSrbtZyTODCGf2nuJQsXz+YnBAVUNjKILmAVR1bCx
KSXo9YJdrfroHTxk+TB6wewiO8cs5/YlMfKQEyUxTdMOEzRdGvl0dkw6t4346BcS
FqY0tpJ6tvbatVjc+ka//ZxBdKTHWJqkcR0f5g91L0AMllRnAUKAyXIzxUlMB8zN
zfKXhSce2Wk+39kBaDrw4YS6SiJTXVX5ID2Myz/NPDY3upbjPVtDABloF35PcKlr
lekQmYUtN+QoHCb+LEkmHn6/AUdUke7J0Vr1gtqEjqC4f5zPVAx81ZLoBU3FQvb/
DN8QLfdI2/qelfubCRv/XDH+ybhd9aQizKacbcEvpCCLCDkkDqhQyToVoJyeWlGY
s+v2oTcP2x+gPs/2uFGsCQE2U8re9B6BOwYaYaAhoPmHSz4hr7oYc45xop1B+70G
GTGHMHbrbKMPld66dKJQEJ9+mtkJbBrsX/ZHbRHCUjU2yMasSV/S9YHYWWqhC8E+
BSCwrlnMnu4TTxF7uHx0nzWOJFRVfmowsfN4ffHDVoAXfuN7NLc56JsJszVyY0ij
YY8=
-----END CERTIFICATE-----

145
admin.go Normal file
View File

@ -0,0 +1,145 @@
package main
import "net/http"
func configHandler(w http.ResponseWriter, r *http.Request) {
w.Write(formatJson(parsecService))
}
func externalHandler(w http.ResponseWriter, r *http.Request) {
peerId := r.FormValue("peer")
addr := r.FormValue("addr")
if peerId == "" {
w.Write([]byte("Peer is empty"))
return
}
peer := parsecService.GetPeer(peerId)
if peer == nil {
w.Write([]byte("Peer not found"))
return
}
peer.External = addr
w.Write([]byte("Success"))
}
func addUserHandler(w http.ResponseWriter, r *http.Request) {
username := r.FormValue("user")
password := r.FormValue("pass")
if username == "" {
w.Write([]byte("user can not be empty"))
return
}
if password == "" {
w.Write([]byte("pass can not be empty"))
return
}
user := parsecService.GetUserByEmail(username)
if user != nil {
w.Write([]byte("user exist"))
return
}
parsecService.AddUser(username, password)
w.Write([]byte("Success"))
}
func editUserHandler(w http.ResponseWriter, r *http.Request) {
username := r.FormValue("user")
password := r.FormValue("pass")
if username == "" {
w.Write([]byte("user can not be empty"))
return
}
if password == "" {
w.Write([]byte("pass can not be empty"))
return
}
user := parsecService.GetUserByEmail(username)
if user == nil {
w.Write([]byte("user not exist"))
return
}
user.Password = password
w.Write([]byte("Success"))
}
func removeUserHandler(w http.ResponseWriter, r *http.Request) {
username := r.FormValue("user")
if username == "" {
w.Write([]byte("user can not be empty"))
return
}
user := parsecService.GetUserByEmail(username)
if user == nil {
w.Write([]byte("user not exist"))
return
}
parsecService.RemoveUser(username)
parsecService.RemovePeerByUser(username)
parsecService.RemoveSessionByUser(username)
parsecService.RemoveWsSessionByUser(username)
w.Write([]byte("Success"))
}
func publicHandler(w http.ResponseWriter, r *http.Request) {
peerId := r.FormValue("peer")
public := r.FormValue("public")
if peerId == "" {
w.Write([]byte("peer can not be empty"))
return
}
if public == "" {
w.Write([]byte("public can not be empty"))
return
}
peer := parsecService.GetPeer(peerId)
if peer == nil {
w.Write([]byte("peer not exist"))
return
}
peer.Public = public == "1"
w.Write([]byte("Success"))
}
func assignHandler(w http.ResponseWriter, r *http.Request) {
peer := r.FormValue("peer")
user := r.FormValue("user")
action := r.FormValue("action")
if peer == "" || action == "" {
w.Write([]byte("peer and action can not be empty"))
return
}
if action != "clear" && user == "" {
w.Write([]byte("user can not be empty"))
return
}
switch action {
case "add":
parsecService.AssignAdd(peer, user)
break
case "remove":
parsecService.AssignRemove(peer, user)
break
case "clear":
parsecService.AssignClear(peer)
break
}
w.Write([]byte("Success"))
}
func trimHandler(w http.ResponseWriter, r *http.Request) {
parsecService.RemoveOfflinePeers()
w.Write([]byte("Success"))
}

3
build.bat Normal file
View File

@ -0,0 +1,3 @@
packr build -ldflags "-s -w" -trimpath
set GOOS=linux
packr build -ldflags "-s -w" -trimpath

82
cert.crt Normal file
View File

@ -0,0 +1,82 @@
-----BEGIN CERTIFICATE-----
MIIDwjCCAqqgAwIBAgIIeR+AB+cH7DwwDQYJKoZIhvcNAQELBQAwRTELMAkGA1UE
BhMCQ04xDTALBgNVBAoTBEV2YW4xDTALBgNVBAsTBEV2YW4xGDAWBgNVBAMTD0V2
YW4gVExTIFJTQSBDQTAeFw0yMjAxMDEwMDAwMDBaFw0yNDEyMzEyMzU5NTlaMEUx
CzAJBgNVBAYTAkNOMQ0wCwYDVQQKEwRFdmFuMQ0wCwYDVQQLEwRFdmFuMRgwFgYD
VQQDEw9FdmFuIFBhcnNlYyBUTFMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQDg4Lt8vIfUjfeHJPaQSGpyMbxqtj4BUFMNCBqblIofcn3yVwanu0wCBVH+
k1bjmBp6PpQ8wR/UboMWPHoVsz0XkcyWKeLju+eY0mnTA7wpImVzar225xLDb1CZ
rMnhPwX+yALGv8bbJNaQZgHp5cO4EiGqdqg60Z9/Q8MtxVkoarxIxlMQoXe/MLwf
1o9h2hU3nsuXRp5TKrcyYWF6V+S0muIUMEp3ZcNcYKdCslO4087Xae1eGWo7lt4p
Vhynh5dHqmadfh7u+ME2D0dQBnTx/Zo8YRMf+OAQtCdfP7aLw3uYy4ZXiR75c1Xm
uJPAx1ihYH+QHQqixcgA6/5G4N0VAgMBAAGjgbUwgbIwDAYDVR0TAQH/BAIwADAd
BgNVHQ4EFgQUGrW3LHAdxjM6y7+vDUyRuB2k6cUwHwYDVR0jBBgwFoAU5MxP/8gg
1Xg99Ioo6XKieL4HPeYwCwYDVR0PBAQDAgPoMB0GA1UdJQQWMBQGCCsGAQUFBwMB
BggrBgEFBQcDAjAjBgNVHREEHDAaggpwYXJzZWMuYXBwggwqLnBhcnNlYy5hcHAw
EQYJYIZIAYb4QgEBBAQDAgbAMA0GCSqGSIb3DQEBCwUAA4IBAQCUpZpoUqTJ8TUO
Y0xzJpnm7CfcSqYNPQHO3dUJBqsOZqTyIFM0TQsjXUQFxA2ERqkxwovlk9d07ToV
7T8E6vw++SDkIuIpDALYaw0F00nUOTwMe86/Sa+vyZHTqn9ghrE4Sx7cOhkqyM4V
sHIjjDsypLpvuDRTNUY6g+DaCMhnKG27toAHYQHTcmsj/pM5yyrWsl6E+3TW89gk
T7tXrnKTex/p+ihpm7RTzcL5WKmfobmXwi9+qdmA08C53RPSG1CMwthY7tqTK0Q7
ugUMH9Ug15XPK96pCxcjOew4bEmfTIMXEGu63q+ZfvJFIko1ZrTLCB3RxvoNZ+P5
xh4HBIi1
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEhDCCAmygAwIBAgIIUw9sncwT5HcwDQYJKoZIhvcNAQELBQAwTDELMAkGA1UE
BhMCQ04xDTALBgNVBAoTBEV2YW4xDTALBgNVBAsTBEV2YW4xHzAdBgNVBAMTFkV2
YW4gQXNzdXJhbmNlIFJvb3QgQ0EwHhcNMjIwMTAxMDAwMDAwWhcNMzIxMjMxMjM1
OTU5WjBFMQswCQYDVQQGEwJDTjENMAsGA1UEChMERXZhbjENMAsGA1UECxMERXZh
bjEYMBYGA1UEAxMPRXZhbiBUTFMgUlNBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAox2P/00Y8RimWNxKi/iA3ERywf21imyNy+v8d47Bg2iH8PaZ
RAKYya19OkLQOsWFCS6L8/Bx/c/HPZfMOS62ro9IQ6D5UWP0rK0nSd7gvUU63f6K
6ZJ5LR1owAVx0xZ9b+sL89J+VlNuVR8+SjYx2OlOUonk3IPHkvm9WCtFMA2bkHjd
uG2KSJTn4roXr5nVhIi53RnSaiyBlRJ0OeJ2IPgmA/U5v/0Rr2I3YN7NJ/n+3bCO
kNeX/o3qa+C4PoedXfnlIGJyqcc3l6SCSsZ5IFyvQDgE8kuO9UYXk/jFQnYyHsNb
QA/ZiiR2mzQ12eeDxaqZAYYeC0ys/OL7ZvZt5QIDAQABo3EwbzAPBgNVHRMBAf8E
BTADAQH/MB0GA1UdDgQWBBTkzE//yCDVeD30iijpcqJ4vgc95jALBgNVHQ8EBAMC
AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBEGCWCGSAGG+EIBAQQE
AwIAxzANBgkqhkiG9w0BAQsFAAOCAgEAPcEmb/L749blVCP5+RDFN+lS78MYQyMT
ZikN3AJP6pV5pTM3uc+zD+sproRslJum7DkeuPn2WltRN3bcB4cemvFMZyx0luFc
XVJ3Gy+wx0L0JexBri5B9iFovZaHxKM0uaBmHDyHfGV6NhsFJWlPL9XQTRQD6EQ4
meWRNpEs+W5RCXMMQ7VqyRLovC4OdbnPkyZv+UGKajGzGPzm6hZfG3bA5TxSFney
ZerpxSnLASkzAe1w5G91RU3dPKPiULJq01l5uv2fy7p5KYDWSqxOrB0KvpZtAVRx
6VbmFEogmG66M2OGEm9ptz4FOh08KsTfcuS3KWiyVjBi/IcqzbOMWkkIrw0SM7Bu
tbgIrS4bbYUFfCJqYsRw0Nfmn1Ndll2sLD3iTqL5kqSaAsI9zGyEn5QPnZCGvx3R
ryAHAI+ImvfFMZjvbZ95++IBJ/7CDYlp25oVd/JBzxq2fpNqO+Z5r4Cg+UFve1uU
c6M1KGeKF15sQi69gkbcUPQpYaxT5+zTag88V0NIwhuGgmaMvvo1vN5O+tziL4B4
HxJNaUJbv2rolJ7stspw8C6L6daP+7qfyIaZONJCGEXt46V4mDH9vV5/JWyhhuSE
rL39I9OzjaUhsooodJwcKUuU1BAzg3oaXxANzEPMILDHo7dPDWkY+JoVa/VW5Huc
2kzz4/hrJdU=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIFbjCCA1agAwIBAgIII1tVz2zmPdEwDQYJKoZIhvcNAQELBQAwTDELMAkGA1UE
BhMCQ04xDTALBgNVBAoTBEV2YW4xDTALBgNVBAsTBEV2YW4xHzAdBgNVBAMTFkV2
YW4gQXNzdXJhbmNlIFJvb3QgQ0EwIBcNMDgwMTAxMDAwMDAwWhgPMjA1MDEyMzEy
MzU5NTlaMEwxCzAJBgNVBAYTAkNOMQ0wCwYDVQQKEwRFdmFuMQ0wCwYDVQQLEwRF
dmFuMR8wHQYDVQQDExZFdmFuIEFzc3VyYW5jZSBSb290IENBMIICIjANBgkqhkiG
9w0BAQEFAAOCAg8AMIICCgKCAgEAq4vXbtUk8y6n3AgH7ApVQ7KOSwymwQnRg0HQ
iZJfamIWYsa1uN7n2/W3tJy82ij0JyIqqiUfTZu8dr6yd12Um7RTN1j32x+3hzSF
+2Hag08vd4ksrHAm61MjZVqdQdi5+EMwR5YlaEYFrgBYsKfo+B4vjFYz2PW7IFlI
SSeHBrtJdKf+pIr0+e2Sp7O91NwAp3JiDChqqyMQbKLaXag0G0o422Mj555L2k6r
E1lNm2JzMKaHqP2Ql/GtCaZXmsD4oRtBfMOr5OkNeGA0lA9OXkZxDXYTGrgGfaY+
kq7ig12eBwa2f09x++aPAYxci8I5fZGCpCPY5siAUpw7r9Tui8d2KOCVDGF4FZ3+
vDnLDPXrrN/2FYHt0p/kNqn9WINLOtfQyk8Ko+zicxjTA9DH5tNyKvxs5NncVXeH
Gjgmto3OAJcRNfu/NOo3IEHRmyOHCtMK/U1oR3ByYsRxc9raKrPI/CBcJQ48XIut
OxdQ+5cK+7IumEXqiAxnvKK1SMBVMnSav7jrfAfq66RNOOTvxng1p2W52dTJIIZB
KcEpzrnMGmLLDba9Zp7v/6PsXYLtwzg0oOJZcnnqOCMz2jMt9o06HFlqYke8Z8Nb
35njmwN4PWyHpEZhbC9VttXqVVD7UaAMStsP2yS2L6Wdt3Af7Mr/kurhLBwK6XOp
H403rdsCAwEAAaNSMFAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9GuH/q/
TaECv2bMT75Rrso+Hu4wCwYDVR0PBAQDAgGGMBEGCWCGSAGG+EIBAQQEAwIABzAN
BgkqhkiG9w0BAQsFAAOCAgEAjsWIGSK2ZJOiCBoH4CqMQNJa2nWOAAY99op8BXRi
Sx8tpbid+mZ6IOIBOY4GLT82lkbDbGmAOXhjDD9pMWYMbxko35MNX3j1/9BAIKU/
W4U5NEIEnWogJJirJjtW+3BGSrbtZyTODCGf2nuJQsXz+YnBAVUNjKILmAVR1bCx
KSXo9YJdrfroHTxk+TB6wewiO8cs5/YlMfKQEyUxTdMOEzRdGvl0dkw6t4346BcS
FqY0tpJ6tvbatVjc+ka//ZxBdKTHWJqkcR0f5g91L0AMllRnAUKAyXIzxUlMB8zN
zfKXhSce2Wk+39kBaDrw4YS6SiJTXVX5ID2Myz/NPDY3upbjPVtDABloF35PcKlr
lekQmYUtN+QoHCb+LEkmHn6/AUdUke7J0Vr1gtqEjqC4f5zPVAx81ZLoBU3FQvb/
DN8QLfdI2/qelfubCRv/XDH+ybhd9aQizKacbcEvpCCLCDkkDqhQyToVoJyeWlGY
s+v2oTcP2x+gPs/2uFGsCQE2U8re9B6BOwYaYaAhoPmHSz4hr7oYc45xop1B+70G
GTGHMHbrbKMPld66dKJQEJ9+mtkJbBrsX/ZHbRHCUjU2yMasSV/S9YHYWWqhC8E+
BSCwrlnMnu4TTxF7uHx0nzWOJFRVfmowsfN4ffHDVoAXfuN7NLc56JsJszVyY0ij
YY8=
-----END CERTIFICATE-----

48
global.go Normal file
View File

@ -0,0 +1,48 @@
package main
import (
"encoding/json"
"io/ioutil"
"log"
"time"
)
const ROLE_CLIENT = "client"
const ROLE_HOST = "host"
var (
Logger *log.Logger
configPath string
LastUserId = 1000
parsecService *ParsecService
TimeLocation *time.Location = time.FixedZone("CST", 8*3600)
)
type ConfigData struct {
LastUserId int `json:"lastUserId"`
Users []*UserInfo `json:"users"`
Peers []*PeerInfo `json:"peers"`
Sessions []*SessionInfo `json:"sessions"`
}
func SaveConfig() {
config := &ConfigData{
Users: parsecService.Users,
Peers: parsecService.Peers,
Sessions: parsecService.Sessions,
LastUserId: LastUserId,
}
marshal, _ := json.Marshal(config)
ioutil.WriteFile(configPath, marshal, 0777)
}
func ReadConfig() {
b, _ := exists(configPath)
if b {
configData, _ := ioutil.ReadFile(configPath)
var config *ConfigData
json.Unmarshal(configData, &config)
parsecService.LoadConfig(config)
LastUserId = config.LastUserId
}
}

16
go.mod Normal file
View File

@ -0,0 +1,16 @@
module parsec
go 1.18
require (
github.com/gobuffalo/packr v1.30.1
github.com/gorilla/websocket v1.5.0
github.com/beevik/guid v0.0.0-20170504223318-d0ea8faecee0
)
require (
github.com/gobuffalo/envy v1.7.0 // indirect
github.com/gobuffalo/packd v0.3.0 // indirect
github.com/joho/godotenv v1.3.0 // indirect
github.com/rogpeppe/go-internal v1.3.0 // indirect
)

70
go.sum Normal file
View File

@ -0,0 +1,70 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beevik/guid v0.0.0-20170504223318-d0ea8faecee0 h1:oLd/YLOTOgA4D4aAUhIE8vhl/LAP1ZJrj0mDQpl7GB8=
github.com/beevik/guid v0.0.0-20170504223318-d0ea8faecee0/go.mod h1:XzXWuOd1wJ63MtICHh5+PnvCuxsB/d58T8TswEhI/9I=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4=
github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q=
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

74
parsec.go Normal file
View File

@ -0,0 +1,74 @@
package main
import (
"flag"
"github.com/gobuffalo/packr"
"io"
"log"
"net/http"
"os"
"os/signal"
"time"
)
func main() {
Logger = log.New(io.Writer(os.Stdout), "", log.Lshortfile|log.LstdFlags)
var port string
var address string
flag.StringVar(&port, "port", "443", "serve port")
flag.StringVar(&address, "listen", "", "listen address")
flag.StringVar(&configPath, "config", "config.conf", "config path")
flag.Parse()
parsecService = new(ParsecService)
ReadConfig()
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, os.Kill)
go func() {
<-c //阻塞直至有信号传入
Logger.Println("Saving config")
SaveConfig()
Logger.Println("Save config done,exit now.")
os.Exit(0)
}()
go func() {
ticker := time.NewTicker(time.Minute)
for range ticker.C {
<-ticker.C
SaveConfig()
Logger.Println("Saving config")
ticker.Reset(time.Minute)
}
}()
http.HandleFunc("/v1/auth", authHandler)
http.HandleFunc("/v2/auth", authHandler)
http.HandleFunc("/me", meHandler)
http.HandleFunc("/friend-requests", friendHandler)
http.HandleFunc("/v2/hosts", hostsHandler)
http.HandleFunc("/exit-codes", exitHandler)
http.HandleFunc("/metrics", metricsHandler)
http.HandleFunc("/events", eventsHandler)
http.HandleFunc("/", serviceHandler)
http.HandleFunc("/admin/config", configHandler)
http.HandleFunc("/admin/external", externalHandler)
http.HandleFunc("/admin/addUser", addUserHandler)
http.HandleFunc("/admin/editUser", editUserHandler)
http.HandleFunc("/admin/removeUser", removeUserHandler)
http.HandleFunc("/admin/public", publicHandler)
http.HandleFunc("/admin/assign", assignHandler)
http.HandleFunc("/admin/trim", trimHandler)
box := packr.NewBox("web")
http.Handle("/data/", http.FileServer(box))
err := http.ListenAndServeTLS(address+":"+port, "cert.crt", "private.key", nil)
//err := http.ListenAndServe(":"+port, nil)
if err != nil {
Logger.Println(err)
}
}

429
parsec_manager.go Normal file
View File

@ -0,0 +1,429 @@
package main
import (
"github.com/gorilla/websocket"
"math/rand"
"strconv"
"sync"
"time"
)
type UserInfo struct {
UserId int
Email string
Password string
}
type PeerInfo struct {
PeerId string
Owner string
Assign []string
Online bool `json:"-"`
Build string
Name string
Players int
Public bool
Secret string
External string
}
type SessionInfo struct {
SessionId string
Email string
UserId int
HostPeerId string
Role string
AttemptId string
Guid string
WsConn *websocket.Conn `json:"-"`
}
type ParsecService struct {
sessionLock sync.Mutex
wsSessionLock sync.Mutex
peerLock sync.Mutex
userLock sync.Mutex
Sessions []*SessionInfo `json:"sessions"`
WsSessions []*SessionInfo `json:"wsSessions"`
Users []*UserInfo `json:"users"`
Peers []*PeerInfo `json:"peers"`
}
func (p *ParsecService) LoadConfig(config *ConfigData) {
p.sessionLock.Lock()
p.userLock.Lock()
p.peerLock.Lock()
defer p.sessionLock.Unlock()
defer p.userLock.Unlock()
defer p.peerLock.Unlock()
p.Peers = config.Peers
p.Users = config.Users
p.Sessions = config.Sessions
}
func (p *ParsecService) Login(email string, password string) *UserInfo {
p.userLock.Lock()
defer p.userLock.Unlock()
for _, user := range p.Users {
if user.Email == email && user.Password == password {
return user
}
}
return nil
}
func (p *ParsecService) AddUser(email string, password string) *UserInfo {
p.userLock.Lock()
defer p.userLock.Unlock()
for _, user := range p.Users {
if user.Email == email {
return user
}
}
newUserId := 0
for i := LastUserId + 1; i < 10000; i++ {
exist := false
for _, user := range p.Users {
if user.UserId == i {
exist = true
break
}
}
if !exist {
newUserId = i
LastUserId = i
break
}
}
tmp := &UserInfo{
UserId: newUserId,
Email: email,
Password: password,
}
p.Users = append(p.Users, tmp)
return tmp
}
func (p *ParsecService) GetUserByEmail(email string) *UserInfo {
p.userLock.Lock()
defer p.userLock.Unlock()
for _, user := range p.Users {
if user.Email == email {
return user
}
}
return nil
}
func (p *ParsecService) CheckUserExist(email string) bool {
p.userLock.Lock()
defer p.userLock.Unlock()
for _, user := range p.Users {
if user.Email == email {
return true
}
}
return false
}
func (p *ParsecService) AddPeer(email string) *PeerInfo {
p.peerLock.Lock()
defer p.peerLock.Unlock()
newPeerId := GetRandomString(27)
for {
exist := false
for _, peer := range p.Peers {
if peer.PeerId == newPeerId {
exist = true
break
}
}
if exist {
newPeerId = GetRandomString(27)
} else {
break
}
}
peer := &PeerInfo{
PeerId: newPeerId,
Owner: email,
Assign: []string{},
}
p.Peers = append(p.Peers, peer)
return peer
}
func (p *ParsecService) GetPeer(peerId string) *PeerInfo {
p.peerLock.Lock()
defer p.peerLock.Unlock()
for _, peer := range p.Peers {
if peer.PeerId == peerId {
return peer
}
}
return nil
}
func (p *ParsecService) NewSession(email string, userId int, PeerId string) *SessionInfo {
p.sessionLock.Lock()
defer p.sessionLock.Unlock()
r := rand.New(rand.NewSource(time.Now().UnixNano()))
sessionId := GetSHA256HashCode([]byte(strconv.Itoa(r.Intn(999999))))
for {
exist := false
for _, session := range p.Sessions {
if session.SessionId == sessionId {
exist = true
break
}
}
if exist {
sessionId = GetSHA256HashCode([]byte(strconv.Itoa(r.Intn(999999))))
} else {
break
}
}
session := &SessionInfo{
SessionId: sessionId,
Email: email,
UserId: userId,
HostPeerId: PeerId,
}
p.Sessions = append(p.Sessions, session)
return session
}
func (p *ParsecService) NewWsSession(sessionId string, role string, guid string, wsConn *websocket.Conn) *SessionInfo {
p.wsSessionLock.Lock()
defer p.wsSessionLock.Unlock()
session := p.GetSession(sessionId)
if session != nil {
for _, wsSession := range p.WsSessions {
if wsSession.SessionId == sessionId && wsSession.Role == role {
wsSession.WsConn = wsConn
wsSession.Guid = guid
return wsSession
}
}
wsSession := &SessionInfo{
SessionId: session.SessionId,
Email: session.Email,
UserId: session.UserId,
HostPeerId: session.HostPeerId,
Role: role,
Guid: guid,
WsConn: wsConn,
}
p.WsSessions = append(p.WsSessions, wsSession)
return wsSession
}
return nil
}
func (p *ParsecService) RemoveWsSession(sessionId string, role string, sessionGuid string) {
p.wsSessionLock.Lock()
defer p.wsSessionLock.Unlock()
var tmp []*SessionInfo
for _, session := range p.WsSessions {
if !(session.SessionId == sessionId && session.Role == role && session.Guid == sessionGuid) {
tmp = append(tmp, session)
}
}
p.WsSessions = tmp
}
func (p *ParsecService) SetupOnlineStatus(hostPeer *PeerInfo) {
p.wsSessionLock.Lock()
defer p.wsSessionLock.Unlock()
for _, session := range p.WsSessions {
if session.HostPeerId == hostPeer.PeerId {
return
}
}
hostPeer.Online = false
}
func (p *ParsecService) GetWsSessionByPeerId(role string, peerId string) *SessionInfo {
p.wsSessionLock.Lock()
defer p.wsSessionLock.Unlock()
for _, session := range p.WsSessions {
if session.Role == role && session.HostPeerId == peerId {
return session
}
}
return nil
}
func (p *ParsecService) GetWsSessionByAttemptId(peerId string, attemptId string) *SessionInfo {
p.wsSessionLock.Lock()
defer p.wsSessionLock.Unlock()
for _, session := range p.WsSessions {
if session.AttemptId == attemptId && session.HostPeerId == peerId {
return session
}
}
return nil
}
func (p *ParsecService) GetSession(sessionId string) *SessionInfo {
p.sessionLock.Lock()
defer p.sessionLock.Unlock()
for _, session := range p.Sessions {
if session.SessionId == sessionId {
return session
}
}
return nil
}
func (p *ParsecService) GetPeersByEmail(email string) []*PeerInfo {
p.peerLock.Lock()
defer p.peerLock.Unlock()
var peers []*PeerInfo
for _, peer := range p.Peers {
if peer.Owner == email || StringListContain(peer.Assign, email) {
peers = append(peers, peer)
}
}
return peers
}
func (p *ParsecService) RemoveUser(email string) {
p.userLock.Lock()
defer p.userLock.Unlock()
var tmp []*UserInfo
for _, user := range p.Users {
if user.Email != email {
tmp = append(tmp, user)
}
}
p.Users = tmp
}
func (p *ParsecService) RemovePeerByUser(email string) {
p.peerLock.Lock()
defer p.peerLock.Unlock()
var tmp []*PeerInfo
for _, peer := range p.Peers {
if peer.Owner != email {
tmp = append(tmp, peer)
}
}
p.Peers = tmp
}
func (p *ParsecService) RemoveSessionByUser(email string) {
p.sessionLock.Lock()
defer p.sessionLock.Unlock()
var tmp []*SessionInfo
for _, session := range p.Sessions {
if session.Email != email {
tmp = append(tmp, session)
} else {
if session.WsConn != nil {
session.WsConn.Close()
}
}
}
p.Sessions = tmp
}
func (p *ParsecService) RemoveWsSessionByUser(email string) {
p.wsSessionLock.Lock()
defer p.wsSessionLock.Unlock()
var tmp []*SessionInfo
for _, session := range p.WsSessions {
if session.Email != email {
tmp = append(tmp, session)
} else {
if session.WsConn != nil {
session.WsConn.Close()
}
}
}
p.WsSessions = tmp
}
func (p *ParsecService) RemoveSessionByPeerId(peerId string) {
p.sessionLock.Lock()
defer p.sessionLock.Unlock()
var tmp []*SessionInfo
for _, session := range p.Sessions {
if session.HostPeerId != peerId {
tmp = append(tmp, session)
} else {
if session.WsConn != nil {
session.WsConn.Close()
}
}
}
p.Sessions = tmp
}
func (p *ParsecService) RemoveWsSessionByPeerId(peerId string) {
p.wsSessionLock.Lock()
defer p.wsSessionLock.Unlock()
var tmp []*SessionInfo
for _, session := range p.WsSessions {
if session.HostPeerId != peerId {
tmp = append(tmp, session)
} else {
if session.WsConn != nil {
session.WsConn.Close()
}
}
}
p.WsSessions = tmp
}
func (p *ParsecService) AssignAdd(peerId string, email string) {
peer := p.GetPeer(peerId)
if peer != nil {
if !StringListContain(peer.Assign, email) {
peer.Assign = append(peer.Assign, email)
}
}
}
func (p *ParsecService) AssignClear(peerId string) {
peer := p.GetPeer(peerId)
if peer != nil {
peer.Assign = []string{}
}
}
func (p *ParsecService) AssignRemove(peerId string, email string) {
peer := p.GetPeer(peerId)
if peer != nil {
var tmp []string
for _, s := range peer.Assign {
if s != email {
tmp = append(tmp, s)
}
}
peer.Assign = tmp
}
}
func (p *ParsecService) RemoveOfflinePeers() {
p.peerLock.Lock()
defer p.peerLock.Unlock()
var tmp []*PeerInfo
for _, peer := range p.Peers {
if peer.Online {
tmp = append(tmp, peer)
} else {
p.RemoveSessionByPeerId(peer.PeerId)
p.RemoveWsSessionByPeerId(peer.PeerId)
}
}
p.Peers = tmp
}

15
parsec_test.go Normal file
View File

@ -0,0 +1,15 @@
package main
import (
"encoding/json"
"fmt"
"testing"
)
func TestName(t *testing.T) {
var req *SocketRequest
str := "{\"version\":1,\"action\":\"conn_update\",\"payload\":{\"mode\":\"desktop\",\"name\":\"DESKTOP-4TKVI8T\",\"desc\":\"\",\"game_id\":\"\",\"secret\":\"\",\"max_players\":20,\"players\":0,\"public\":false,\"guests\":[]}}"
json.Unmarshal([]byte(str), &req)
fmt.Println(req.Version)
}

27
private.key Normal file
View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA4OC7fLyH1I33hyT2kEhqcjG8arY+AVBTDQgam5SKH3J98lcG
p7tMAgVR/pNW45gaej6UPMEf1G6DFjx6FbM9F5HMlini47vnmNJp0wO8KSJlc2q9
tucSw29QmazJ4T8F/sgCxr/G2yTWkGYB6eXDuBIhqnaoOtGff0PDLcVZKGq8SMZT
EKF3vzC8H9aPYdoVN57Ll0aeUyq3MmFhelfktJriFDBKd2XDXGCnQrJTuNPO12nt
XhlqO5beKVYcp4eXR6pmnX4e7vjBNg9HUAZ08f2aPGETH/jgELQnXz+2i8N7mMuG
V4ke+XNV5riTwMdYoWB/kB0KosXIAOv+RuDdFQIDAQABAoIBAEq03hRWXZmTgEP5
V6AfLp25QCsDWB3/nVea9ZvyAODpnEXB+4gFhP623cKBGECL61/pIj38uqJMBGiC
ttw2q3kFCr5oM+QMLKhsXpOnjf7sWl+5ekUlBuq+NDyZVofp9AfsUl/Mnjd3SYC3
IrOdjSO9gkmrGcBQm3gf/ttZ0IDINRhVpcK3mwfdlsmYaEnICoVrwe30xQK0GRfT
uSLiwn/RCKoeOS0aVL/LT9Qs9hKlMPBoV78PrwPzQ2JLbyyjpA+AUtXME8mS46Yb
UVFZnCzGpCPTVXp3pTAQcHAGUypKMlMp3i4deiz6UN4ESOcBMMqVlcwKwt4d+frC
P36ybgECgYEA/PV08Egl3sCK/nq2GPnpZ8kWQym4nrlFWw6nm/NR9wNpA3e/mWSP
/P2uGIafFeX8mii1eJlWcCW6tJC61wwIBSrrhoN4YH7Rj1Bplls4RD+AJlit2gg+
HT9j9dus7U0JvW+H7Uk7xZBshV1+pT+eirkXxvpUH4OlRc+ZHNZ/8nUCgYEA45TZ
eXLd7D/H2wDNqZleKp26QRsfD10FuI9f1VjKgnlTQdxG40P9fvLxxjFFllgX7YzH
dKJvL9kpKcQULRo4BoddWUfMWFt5ZN52njq1euMWxOXqtNywpypr489nqp6QBHWY
nhatkztaFpBopeTkO5c37QH0M5bEyxXCgpwMrCECgYEAuTU+mW85yw5OtmRCT6cr
LcIdeq9hbVVZYoIoVhahPKpSiSd0MWtfwWw7u9lVQUNS38xOki4zC4mUWgBdzHYS
qTXznFlGGeDArp3BsUS4vb+ApJLpN2oxkFiJZ8mfo190ci7m5uVnzg8gZcU+pN8f
xZIfxqAiV7CbobGN+X9TzsECgYAoRllOQuO/QXJO8Y9z6i5eAFfL2c7fWyj+BnGB
Qhtkh7ASQbdR1OBxrPDYkDOubZyeb4GExJJEt3uvZoHjkXZEwYPlnu0s3dNX5H69
dcpUGwgWhFHK/BtPGhTJ1hSUf0chYuZFY+IH4kMJJzk90ooJebNuACCFWLMu9YTc
tF0RwQKBgHvoxHCZ9QIXtBPj6GiKEzYaI6Jz2G52HPTsSdLJWNuCvuND94DZ78Eo
lP754ntgpatyPYm8uxZMKkY9YAkyth8KZngrhOcRitSY3PWkQgh+cGO3gAZ5o7Mc
N1lJjMHGDFvQHi+d/M17ZJb9/BaK49JH4LlPPptV5T+2RgUGZCKy
-----END RSA PRIVATE KEY-----

110
socket_model.go Normal file
View File

@ -0,0 +1,110 @@
package main
type SocketRequest struct {
Version int `json:"version"`
Action string `json:"action"`
}
type ConnUpdateRequest struct {
SocketRequest
Payload ConnUpdatePayload `json:"payload"`
}
type ConnUpdatePayload struct {
Mode string `json:"mode"`
Name string `json:"name"`
Desc string `json:"desc"`
GameId string `json:"game_id"`
Secret string `json:"secret"`
MaxPlayers int `json:"max_players"`
Players int `json:"players"`
Public bool `json:"public"`
Guests []Guest `json:"guests"`
}
type Guest struct {
GuestId int `json:"guest_id"`
UserId int `json:"user_id"`
Gamepad bool `json:"gamepad"`
Keyboard bool `json:"keyboard"`
Mouse bool `json:"mouse"`
}
type OfferModel struct {
SocketRequest
Payload OfferPayload `json:"payload"`
}
type OfferPayload struct {
To string `json:"to"`
Data any `json:"data"`
AttemptId string `json:"attempt_id"`
Secret string `json:"secret"`
AccessLinkId string `json:"access_link_id"`
From string `json:"from"`
IsOwner bool `json:"is_owner"`
SkipApproval bool `json:"skip_approval"`
Permissions Permission `json:"permissions"`
User OfferUser `json:"user"`
HostUser OfferUser `json:"host_user"`
}
type Permission struct {
Gamepad bool `json:"gamepad"`
Keyboard bool `json:"keyboard"`
Mouse bool `json:"mouse"`
}
type OfferUser struct {
Id int `json:"id"`
TeamId string `json:"team_id"`
Name string `json:"name"`
ExternalId string `json:"external_id"`
ExternalProvider string `json:"external_provider"`
}
type AnswerModel struct {
SocketRequest
Payload AnswerPayload `json:"payload"`
}
type AnswerPayload struct {
To string `json:"to"`
Data any `json:"data"`
AttemptId string `json:"attempt_id"`
Approved bool `json:"approved"`
From string `json:"from"`
UserId int `json:"user_id"`
}
type CandexModel struct {
SocketRequest
Payload CandexPayload `json:"payload"`
}
type CandexPayload struct {
To string `json:"to"`
Data CandexData `json:"data"`
AttemptId string `json:"attempt_id"`
From string `json:"from"`
}
type CandexData struct {
FromStun bool `json:"from_stun"`
Ip string `json:"ip"`
Lan bool `json:"lan"`
Port int `json:"port"`
Sync bool `json:"sync"`
VerData int `json:"ver_data"`
}
type OfferCancelModel struct {
SocketRequest
Payload OfferCancelPayload `json:"payload"`
}
type OfferCancelPayload struct {
To string `json:"to"`
AttemptId string `json:"attempt_id"`
From string `json:"from"`
UserId int `json:"user_id"`
}

262
socket_service.go Normal file
View File

@ -0,0 +1,262 @@
package main
import (
"encoding/json"
"fmt"
"github.com/beevik/guid"
"github.com/gorilla/websocket"
"net"
"net/http"
"strconv"
"sync"
"time"
)
var (
upgrader = websocket.Upgrader{
//允许跨域访问
CheckOrigin: func(r *http.Request) bool {
return true
},
EnableCompression: false,
}
)
func serviceHandler(w http.ResponseWriter, r *http.Request) {
var mutex sync.Mutex
sessionId := r.FormValue("session_id")
build := r.FormValue("build")
role := r.FormValue("role")
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if len(sessionId) < 10 {
sessionId = r.Header.Get("Sec-WebSocket-Protocol")
w.Header().Add("sec-websocket-protocol", sessionId)
}
session := parsecService.GetSession(sessionId)
if session == nil {
err := &ErrorResponse{
Error: "invalid session ID",
}
w.WriteHeader(401)
w.Write(formatJson(err))
return
}
wsConn, upgradeErr := upgrader.Upgrade(w, r, w.Header())
if upgradeErr != nil {
return
}
defer func() {
if reco := recover(); reco != any(nil) {
fmt.Printf("Service Runtime error caught: %v", reco)
}
}()
hostPeer := parsecService.GetPeer(session.HostPeerId)
hostPeer.Build = build
sessionGuid := guid.New().String()
wsSession := parsecService.NewWsSession(sessionId, role, sessionGuid, wsConn)
go func() {
for {
mutex.Lock()
err2 := wsConn.WriteMessage(websocket.PingMessage, []byte{0x70, 0x69, 0x6E, 0x67})
mutex.Unlock()
if err2 != nil {
parsecService.RemoveWsSession(sessionId, role, sessionGuid)
if role == ROLE_HOST {
parsecService.SetupOnlineStatus(hostPeer)
}
wsConn.Close()
return
}
time.Sleep(30 * time.Second)
}
}()
for {
//wsConn.SetReadDeadline(time.Now().In(TimeLocation).Add(time.Second * 60))
messageType, p, readError := wsConn.ReadMessage()
if readError != nil {
Logger.Println("Service WebSocket断开,PeerId=" + hostPeer.PeerId + " Role=" + role + ",信息:" + readError.Error())
parsecService.RemoveWsSession(sessionId, role, sessionGuid)
if role == ROLE_HOST {
parsecService.SetupOnlineStatus(hostPeer)
}
wsConn.Close()
return
}
if messageType == websocket.CloseMessage {
Logger.Println("Service WebSocket断开,PeerId=" + hostPeer.PeerId + " Role=" + role)
parsecService.RemoveWsSession(sessionId, role, sessionGuid)
if role == ROLE_HOST {
parsecService.SetupOnlineStatus(hostPeer)
}
wsConn.Close()
return
}
strData := string(p)
Logger.Println("DATA:" + strData + "\n")
var baseRequest *SocketRequest
json.Unmarshal(p, &baseRequest)
mutex.Lock()
switch baseRequest.Action {
case "conn_update":
var connUpdateRequest *ConnUpdateRequest
json.Unmarshal(p, &connUpdateRequest)
peer := parsecService.GetPeer(session.HostPeerId)
peer.Name = connUpdateRequest.Payload.Name
peer.Players = connUpdateRequest.Payload.Players
peer.Public = connUpdateRequest.Payload.Public
peer.Secret = connUpdateRequest.Payload.Secret
peer.Online = true
case "offer":
var offerRequest *OfferModel
json.Unmarshal(p, &offerRequest)
targetPeer := parsecService.GetPeer(offerRequest.Payload.To)
if targetPeer != nil {
wsSession.AttemptId = offerRequest.Payload.AttemptId
targetWsSession := parsecService.GetWsSessionByPeerId(ROLE_HOST, targetPeer.PeerId)
if targetWsSession != nil && targetWsSession.WsConn != nil {
targetUser := parsecService.GetUserByEmail(targetPeer.Owner)
offerRelay := &OfferModel{
SocketRequest: SocketRequest{
Version: 1,
Action: "offer_relay",
},
Payload: OfferPayload{
To: offerRequest.Payload.To,
Data: offerRequest.Payload.Data,
AttemptId: offerRequest.Payload.AttemptId,
Secret: offerRequest.Payload.Secret,
AccessLinkId: offerRequest.Payload.AccessLinkId,
From: wsSession.HostPeerId,
IsOwner: targetPeer.Owner == wsSession.Email,
SkipApproval: targetPeer.Owner == wsSession.Email || targetPeer.Public || StringListContain(targetPeer.Assign, wsSession.Email),
Permissions: Permission{
Gamepad: targetPeer.Owner == wsSession.Email || targetPeer.Public || offerRequest.Payload.Secret == targetPeer.Secret || StringListContain(targetPeer.Assign, wsSession.Email),
Keyboard: targetPeer.Owner == wsSession.Email || targetPeer.Public || offerRequest.Payload.Secret == targetPeer.Secret || StringListContain(targetPeer.Assign, wsSession.Email),
Mouse: targetPeer.Owner == wsSession.Email || targetPeer.Public || offerRequest.Payload.Secret == targetPeer.Secret || StringListContain(targetPeer.Assign, wsSession.Email),
},
User: OfferUser{
Id: wsSession.UserId,
TeamId: "",
Name: wsSession.Email,
ExternalId: "",
ExternalProvider: "",
},
HostUser: OfferUser{
Id: targetUser.UserId,
TeamId: "",
Name: "",
ExternalId: "",
ExternalProvider: "",
},
},
}
targetWsSession.WsConn.WriteMessage(websocket.TextMessage, formatJson(offerRelay))
}
}
case "answer":
var answerRequest *AnswerModel
json.Unmarshal(p, &answerRequest)
targetSession := parsecService.GetWsSessionByAttemptId(answerRequest.Payload.To, answerRequest.Payload.AttemptId)
if targetSession != nil && targetSession.WsConn != nil {
answerRelay := &AnswerModel{
SocketRequest: SocketRequest{
Version: 1,
Action: "answer_relay",
},
Payload: AnswerPayload{
To: answerRequest.Payload.To,
Data: answerRequest.Payload.Data,
AttemptId: answerRequest.Payload.AttemptId,
Approved: answerRequest.Payload.Approved,
From: wsSession.HostPeerId,
UserId: wsSession.UserId,
},
}
targetSession.WsConn.WriteMessage(websocket.TextMessage, formatJson(answerRelay))
}
case "candex":
var candexRequest *CandexModel
json.Unmarshal(p, &candexRequest)
targetSession := parsecService.GetWsSessionByAttemptId(candexRequest.Payload.To, candexRequest.Payload.AttemptId)
if targetSession == nil {
targetSession = parsecService.GetWsSessionByPeerId(ROLE_HOST, candexRequest.Payload.To)
}
if targetSession != nil && targetSession.WsConn != nil {
candexRelay := &CandexModel{
SocketRequest: SocketRequest{
Version: 1,
Action: "candex_relay",
},
Payload: CandexPayload{
To: candexRequest.Payload.To,
Data: candexRequest.Payload.Data,
AttemptId: candexRequest.Payload.AttemptId,
From: wsSession.HostPeerId,
},
}
targetSession.WsConn.WriteMessage(websocket.TextMessage, formatJson(candexRelay))
if hostPeer.External != "" {
addr, _ := net.ResolveIPAddr("ip", hostPeer.External)
candexRelayExternal := &CandexModel{
SocketRequest: SocketRequest{
Version: 1,
Action: "candex_relay",
},
Payload: CandexPayload{
To: candexRequest.Payload.To,
AttemptId: candexRequest.Payload.AttemptId,
From: wsSession.HostPeerId,
Data: CandexData{
FromStun: true,
Ip: "::ffff:" + addr.IP.String(),
Lan: false,
Port: candexRequest.Payload.Data.Port,
Sync: false,
VerData: candexRequest.Payload.Data.VerData,
},
},
}
Logger.Println("UseCustom External: IP=" + addr.IP.String() + " PORT=" + strconv.Itoa(candexRequest.Payload.Data.Port))
targetSession.WsConn.WriteMessage(websocket.TextMessage, formatJson(candexRelayExternal))
}
}
case "offer_cancel":
var offerCancelRequest *OfferCancelModel
json.Unmarshal(p, &offerCancelRequest)
targetSession := parsecService.GetWsSessionByAttemptId(offerCancelRequest.Payload.To, offerCancelRequest.Payload.AttemptId)
if targetSession != nil && targetSession.WsConn != nil {
offerCancelRelay := &OfferCancelModel{
SocketRequest: SocketRequest{
Version: 1,
Action: "offer_cancel_relay",
},
Payload: OfferCancelPayload{
To: offerCancelRequest.Payload.To,
AttemptId: offerCancelRequest.Payload.AttemptId,
From: wsSession.HostPeerId,
UserId: wsSession.UserId,
},
}
targetSession.WsConn.WriteMessage(websocket.TextMessage, formatJson(offerCancelRelay))
}
}
mutex.Unlock()
}
}

65
utils.go Normal file
View File

@ -0,0 +1,65 @@
package main
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"math/rand"
"os"
"time"
)
func exists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return true, err
}
func GetRandomString(l int) string {
str := "0123456789abcdefghijklmnopqrstuvwxyz"
bytes := []byte(str)
result := []byte{}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for i := 0; i < l; i++ {
result = append(result, bytes[r.Intn(len(bytes))])
}
return string(result)
}
func GetSHA256HashCode(message []byte) string {
hash := sha256.New()
//输入数据
hash.Write(message)
//计算哈希值
bytes := hash.Sum(nil)
//将字符串编码为16进制格式,返回字符串
hashCode := hex.EncodeToString(bytes)
//返回哈希值
return hashCode
}
func formatJson(v interface{}) []byte {
marshal, _ := json.Marshal(v)
return marshal
//var b bytes.Buffer
//json.Indent(&b, marshal, "", " ")
//
//return b.Bytes()
}
func StringListContain(list []string, target string) bool {
if list == nil {
return false
}
for _, item := range list {
if item == target {
return true
}
}
return false
}

View File

@ -0,0 +1,5 @@
{
"ver": 8,
"blurb": "- Client no longer disconnects when copying large amounts of text.\n- A new prompt to say Arcade goes away April 15, 2023.\n- [Windows] Windows 7 is now fully unsupported.\n- [Windows] Switch the WebSocket system to WinHTTP.\n- [Windows Paid] Virtual Tablets now works with up to 3 Screens.\n- [MacOS] Prompt to download the Apple Silicon build of Parsec.\n- [Ubuntu] We no longer block non text clipboard for other apps.",
"date": "150-87b"
}

445
web/data/errors/codes.json Normal file
View File

@ -0,0 +1,445 @@
{
"3": {
"url": "",
"type": "warning",
"title": "",
"desc": "The host OS closed the Parsec application due to a login/logout event, please reconnect."
},
"4": {
"url": "",
"type": "warning",
"title": "",
"desc": "The host shut down."
},
"5": {
"url": "https://support.parsec.app/hc/en-us/articles/115002625091",
"type": "warning",
"title": "",
"desc": "You have been kicked by the host."
},
"6": {
"url": "https://support.parsec.app/hc/en-us/articles/115002624531",
"type": "warning",
"title": "Your connection attempt wasn't approved in time",
"desc": "The host needs to approve your connection for you to join."
},
"8": {
"url": "",
"type": "warning",
"title": "Your connection attempt was rejected",
"desc": "The host declined your request or blocked you."
},
"9": {
"url": "",
"type": "warning",
"title": "",
"desc": "You canceled the connection attempt."
},
"11": {
"url": "",
"type": "warning",
"title": "The game or computer you tried to join is full",
"desc": "Wait for a free slot or ask the host to increase the number of players."
},
"12": {
"url": "https://support.parsec.app/hc/en-us/articles/6371362020365",
"type": "warning",
"title": "",
"desc": "You were disconnected from the host due to inactivity."
},
"30": {
"url": "https://parsec.app/downloads",
"label": "Download",
"type": "warning",
"title": "Your browser can not make connections in the web app",
"desc": "The Parsec web app only works in recent versions of Chrome and Edge, please use one of these browsers or install the Parsec app on your client to continue."
},
"99": {
"url": "https://support.parsec.app/hc/en-us/articles/115002601752",
"type": "warning",
"title": "The computer you're connecting to is no longer available",
"desc": "Check if the machine is online, get a new link if applicable, or try to restart Parsec on both ends."
},
"101": {
"url": "",
"type": "warning",
"title": "Multi-factor authentication is required",
"desc": "Your team requires that you enable multi-factor authentication before making connections."
},
"112": {
"url": "https://support.parsec.app/hc/en-us/articles/360049831391",
"type": "warning",
"title": "You don't have the permission to connect to this computer",
"desc": "You're not in the same Parsec Teams group as the computer you're attempting to connect to."
},
"-14": {
"url": "https://support.parsec.app/hc/en-us/articles/115002625712",
"type": "error",
"title": "Your device failed to decode the video stream",
"desc": "Lower the host computer's resolution or check our article for more information."
},
"-18": {
"url": "https://support.parsec.app/hc/en-us/articles/360001690972",
"type": "error",
"title": "Your device had issues decoding the video stream",
"desc": "This device may be incompatible with Parsec, please check our article for more information."
},
"-19": {
"url": "https://support.parsec.app/hc/en-us/articles/360002529252",
"type": "error",
"title": "Your device isn't supported by Parsec",
"desc": "This Android device does not work with Parsec currently."
},
"-1002": {
"url": "https://support.parsec.app/hc/en-us/articles/115002626391",
"type": "error",
"title": "You don't have access to a host computer",
"desc": "Running Parsec in CLI mode requires that you enable hosting on a Windows machine."
},
"-1003": {
"url": "https://support.parsec.app/hc/en-us/articles/115002626611",
"type": "error",
"title": "Parsec does not have sufficient permissions on this device",
"desc": "Parsec was unable to write required data to your device."
},
"-1400": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1401": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1402": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1403": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1404": {
"url": "",
"type": "error",
"title": "This game cannot be captured because it is running as administrator",
"desc": "Ensure your game and game store isn't running as administrator before trying to host."
},
"-1405": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1406": {
"url": "https://support.parsec.app/hc/en-us/articles/6367984442765",
"type": "error",
"title": "Arcade's attempt to capture the game timed out",
"desc": "Ensure the game is the active window, or check our article for more information."
},
"-1407": {
"url": "https://support.parsec.app/hc/en-us/articles/360059600491",
"type": "error",
"title": "Arcade is missing some important files to capture this game",
"desc": "Please check our article for more information."
},
"-1408": {
"url": "",
"type": "error",
"title": "Arcade already interacted with this game previously",
"desc": "Restart your game before trying to host it again."
},
"-1409": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1410": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1500": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1501": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1502": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1503": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1504": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1505": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1506": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1507": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1508": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1509": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1510": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1511": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1512": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1513": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1514": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1515": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-2001": {
"url": "https://support.parsec.app/hc/en-us/articles/115002626051",
"type": "error",
"title": "You do not have permission to make this connection",
"desc": "You can't connect with this peer_id."
},
"-6023": {
"url": "https://support.parsec.app/hc/en-us/articles/115002601011",
"type": "error",
"title": "The peer-to-peer network connection between you and the other computer failed",
"desc": "Something is preventing Parsec from making the connection, check our article for more information."
},
"-6024": {
"url": "https://support.parsec.app/hc/en-us/articles/115002601011",
"type": "error",
"title": "Failed to connect to Parsec STUN servers",
"desc": "Something may be blocking your connection to the Parsec STUN server, check our article for more information."
},
"-6101": {
"url": "https://support.parsec.app/hc/en-us/articles/360000059931",
"type": "error",
"title": "Parsec couldn't communicate with its servers",
"desc": "Something is preventing Parsec from making a websocket connection to its backend."
},
"-6112": {
"url": "https://support.parsec.app/hc/en-us/articles/360023178732",
"type": "error",
"title": "IPv6 is disabled or not supported",
"desc": "Your device must support IPv6 even if you're using IPv4."
},
"-6200": {
"url": "https://parsec.app/downloads",
"label": "Download",
"type": "error",
"title": "The network connection between you and the other computer failed with webRTC",
"desc": "Please install Parsec on your client which is more likely to work."
},
"-7000": {
"url": "https://support.parsec.app/hc/en-us/articles/115002626232",
"type": "error",
"title": "OpenGL was unable to be launched",
"desc": "Make sure your device meets our minimum requirements and is setup correctly."
},
"-7007": {
"url": "https://support.parsec.app/hc/en-us/articles/115002626272",
"type": "error",
"title": "This device has an incompatible version of OpenGL",
"desc": "Make sure your device meets our minimum requirements and is setup correctly."
},
"-6107": {
"url": "https://support.parsec.app/hc/en-us/articles/4410906958861",
"type": "error",
"title": "",
"desc": "You must re-authenticate."
},
"-12007": {
"url": "https://support.parsec.app/hc/en-us/articles/115003074512",
"type": "error",
"title": "The network connection was lost",
"desc": "The connection is unreliable, or the other computer has crashed."
},
"-12010": {
"url": "https://support.parsec.app/hc/en-us/articles/115003074532",
"type": "error",
"title": "The computer you were connected to disappeared",
"desc": "The computer may have crashed or lost the internet connection."
},
"-13000": {
"url": "https://support.parsec.app/hc/en-us/articles/115002623532",
"type": "error",
"title": "The computer you're joining is trying to use an unsupported resolution",
"desc": "Change the resolution to something else on the host computer's Parsec settings."
},
"-13008": {
"url": "https://support.parsec.app/hc/en-us/articles/115002623751",
"type": "error",
"title": "The computer you're joining is trying to use an unsupported resolution",
"desc": "Change the resolution to something else on the host computer's Parsec settings."
},
"-13009": {
"url": "https://support.parsec.app/hc/en-us/articles/360000159992",
"type": "error",
"title": "The host resolution is above our maximum supported resolution of 3840x2160",
"desc": "Try to lower the resolution of the display on the host computer."
},
"-13012": {
"url": "",
"type": "warning",
"title": "",
"desc": "The host is not allowing any more guests."
},
"-13015": {
"url": "https://support.parsec.app/hc/en-us/articles/360047224232",
"type": "error",
"title": "Something went wrong with the client device while initializing the stream",
"desc": "Your government or ISP may be blocking Parsec's encryption, you can try to use our web app or a VPN to get around this."
},
"-14003": {
"url": "https://support.parsec.app/hc/en-us/articles/360002165172",
"type": "error",
"title": "We were unable to capture the screen of the computer you were attempting to connect to",
"desc": "Check our article for more information."
},
"-15000": {
"url": "https://support.parsec.app/hc/en-us/articles/115002624051",
"type": "error",
"title": "The computer you are attempting connect to does not support hardware video encoding",
"desc": "The host computer may have unsupported hardware or its drivers need an update, check our article for more information."
},
"-15002": {
"url": "https://support.parsec.app/hc/en-us/articles/360000513331",
"type": "error",
"title": "The host encoder failed",
"desc": "This could be because on the host either HDR is active, the display resolution is too high, or the graphics driver needs an update."
},
"-15106": {
"url": "https://support.parsec.app/hc/en-us/articles/360033132792",
"type": "error",
"title": "The host encoder failed",
"desc": "This could be because on the host either HDR is active, or the display resolution is too high."
},
"-15107": {
"url": "https://support.parsec.app/hc/en-us/articles/360001383432",
"type": "error",
"title": "The resolution is too high on the host",
"desc": "Reduce the resolution on the host computer to connect."
},
"-17001": {
"url": "https://support.parsec.app/hc/en-us/articles/115002626412",
"type": "error",
"title": "Your Raspberry Pi is configured incorrectly",
"desc": "Disable the experimental OpenGL driver or check our article for more information about how to set up Raspberry Pi correctly."
},
"-18000": {
"url": "https://support.parsec.app/hc/en-us/articles/115002626352",
"type": "error",
"title": "Parsec couldn't communicate with its servers",
"desc": "Check if your internet is working, and if it is, check our article for more information."
},
"-22008": {
"url": "https://support.parsec.app/hc/en-us/articles/360002165172",
"type": "error",
"title": "",
"desc": "The host could not capture the screen."
},
"-32001": {
"url": "https://parsec.app/downloads",
"label": "Download",
"type": "error",
"title": "The web app failed to connect to your host",
"desc": "Please install the Parsec app on your client and retry the connection."
},
"-32002": {
"url": "https://parsec.app/downloads",
"label": "Download",
"type": "error",
"title": "The web app failed to connect to your host",
"desc": "Please install the Parsec app on your client and retry the connection."
},
"-32003": {
"url": "https://parsec.app/downloads",
"label": "Download",
"type": "error",
"title": "The web app failed to connect to your host",
"desc": "Please install the Parsec app on your client and retry the connection."
},
"-800097": {
"url": "https://support.parsec.app/hc/en-us/articles/360004310251",
"type": "error",
"title": "IPv6 is disabled",
"desc": "Your device must have IPv6 enabled even if you're using IPv4."
},
"-800098": {
"url": "https://support.parsec.app/hc/en-us/articles/360004310251",
"type": "error",
"title": "IPv6 is disabled",
"desc": "Your device must have IPv6 enabled even if you're using IPv4."
}
}

View File

@ -0,0 +1 @@
{"display":false,"title":"","message":"","type":"","link_title":"","link_url":""}

351
web_api.go Normal file
View File

@ -0,0 +1,351 @@
package main
import (
"encoding/json"
"io/ioutil"
"net/http"
)
type LoginRequest struct {
Email string `json:"email"`
Password string `json:"password"`
AuthType string `json:"auth_type"`
HostPeerId string `json:"host_peer_id"`
}
type LoginResponse struct {
UserId int `json:"user_id"`
SessionId string `json:"session_id"`
HostPeerId string `json:"host_peer_id"`
InstanceId string `json:"instance_id"`
}
type ErrorResponse struct {
Error string `json:"error"`
}
type ParsecCommonResponse struct {
Data interface{} `json:"data"`
}
type MeData struct {
Id int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Warp bool `json:"warp"`
Staff bool `json:"staff"`
TeamId string `json:"team_id"`
IsConfirmed bool `json:"is_confirmed"`
TeamIsActive bool `json:"team_is_active"`
IsSaml bool `json:"is_saml"`
IsGatewayEnabled bool `json:"is_gateway_enabled"`
IsRelayEnabled bool `json:"is_relay_enabled"`
HasTfa bool `json:"has_tfa"`
TeamEnforceTfa bool `json:"team_enforce_tfa"`
CanUpgradeSession bool `json:"can_upgrade_session"`
CanDowngradeSession bool `json:"can_downgrade_session"`
AppConfig AppConfig `json:"app_config"`
CohortChannel string `json:"cohort_channel"`
MarketingOptIn bool `json:"marketing_opt_in"`
}
type AppConfig struct {
}
type FriendResponse struct {
Data []FriendData `json:"data"`
HasMore bool `json:"has_more"`
}
type FriendData struct {
}
type HostData struct {
User UserData `json:"user"`
PeerId string `json:"peer_id"`
GameId string `json:"game_id"`
Build string `json:"build"`
Description string `json:"description"`
MaxPlayers int `json:"max_players"`
Mode string `json:"mode"`
Name string `json:"name"`
EventName string `json:"event_name"`
Players int `json:"players"`
Public bool `json:"public"`
GuestAccess bool `json:"guest_access"`
Online bool `json:"online"`
Self bool `json:"self"`
}
type UserData struct {
Id int `json:"id"`
Name string `json:"name"`
Warp bool `json:"warp"`
ExternalId string `json:"external_id"`
ExternalProvider string `json:"external_provider"`
TeamId string `json:"team_id"`
}
type HostResponse struct {
Data []HostData `json:"data"`
HasMore bool `json:"has_more"`
}
func authHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Add("access-control-allow-origin", "*")
if r.Method == "OPTIONS" {
w.Header().Add("access-control-allow-methods", "*")
w.Header().Add("access-control-allow-headers", "*")
w.Header().Add("access-control-max-age", "300")
w.WriteHeader(200)
return
}
data, _ := ioutil.ReadAll(r.Body)
defer r.Body.Close()
var login *LoginRequest
json.Unmarshal(data, &login)
Logger.Println("Auth:" + string(data) + "\n")
w.Header().Set("Content-Type", "application/json; charset=utf-8")
if login == nil {
Logger.Println("Error:Auth Failed:" + string(data) + "\n")
w.WriteHeader(403)
w.Write([]byte("{\n\t\"error\": \"Your email/password combination is incorrect.\"\n}"))
return
}
loginUser := parsecService.Login(login.Email, login.Password)
if loginUser == nil {
Logger.Println("Error:Auth Password Error:" + string(data) + "\n")
w.WriteHeader(403)
w.Write([]byte("{\n\t\"error\": \"Your email/password combination is incorrect.\"\n}"))
return
}
var newPeer *PeerInfo
if login.HostPeerId != "" {
peer := parsecService.GetPeer(login.HostPeerId)
if peer != nil && peer.Owner == loginUser.Email {
newPeer = peer
}
}
if newPeer == nil {
newPeer = parsecService.AddPeer(loginUser.Email)
}
session := parsecService.NewSession(loginUser.Email, loginUser.UserId, newPeer.PeerId)
response := &LoginResponse{
UserId: loginUser.UserId,
SessionId: session.SessionId,
HostPeerId: newPeer.PeerId,
InstanceId: "",
}
w.WriteHeader(201)
w.Write(formatJson(response))
}
func GetAuthorization(w http.ResponseWriter, r *http.Request) string {
auth := r.Header.Get("Authorization")
if len(auth) > 10 {
return auth[7:]
}
return ""
}
func meHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Add("access-control-allow-origin", "*")
if r.Method == "OPTIONS" {
w.Header().Add("access-control-allow-methods", "*")
w.Header().Add("access-control-allow-headers", "*")
w.Header().Add("access-control-max-age", "300")
w.WriteHeader(200)
return
}
authorization := GetAuthorization(w, r)
var user *UserInfo
session := parsecService.GetSession(authorization)
if session == nil {
err := &ErrorResponse{
Error: "invalid session ID",
}
w.WriteHeader(401)
w.Write(formatJson(err))
return
}
user = parsecService.GetUserByEmail(session.Email)
if user == nil {
err := &ErrorResponse{
Error: "invalid session ID",
}
w.WriteHeader(401)
w.Write(formatJson(err))
return
}
data := &MeData{
Id: user.UserId,
Name: user.Email,
Email: user.Email,
Warp: true,
Staff: true,
TeamId: "",
TeamIsActive: false,
IsSaml: false,
IsGatewayEnabled: false,
IsRelayEnabled: false,
HasTfa: false,
TeamEnforceTfa: false,
CanUpgradeSession: false,
CanDowngradeSession: false,
IsConfirmed: true,
CohortChannel: "release19",
MarketingOptIn: false,
AppConfig: AppConfig{},
}
response := ParsecCommonResponse{
Data: data,
}
Logger.Println("Me:" + string(formatJson(response)) + "\n")
w.Write(formatJson(response))
}
func friendHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Add("access-control-allow-origin", "*")
if r.Method == "OPTIONS" {
w.Header().Add("access-control-allow-methods", "*")
w.Header().Add("access-control-allow-headers", "*")
w.Header().Add("access-control-max-age", "300")
w.WriteHeader(200)
return
}
authorization := GetAuthorization(w, r)
session := parsecService.GetSession(authorization)
if session == nil {
err := &ErrorResponse{
Error: "invalid session ID",
}
w.WriteHeader(401)
w.Write(formatJson(err))
return
}
response := FriendResponse{
Data: make([]FriendData, 0),
HasMore: false,
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Write(formatJson(response))
}
func exitHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Add("access-control-allow-origin", "*")
if r.Method == "OPTIONS" {
w.Header().Add("access-control-allow-methods", "*")
w.Header().Add("access-control-allow-headers", "*")
w.Header().Add("access-control-max-age", "300")
w.WriteHeader(200)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(200)
}
func metricsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Add("access-control-allow-origin", "*")
if r.Method == "OPTIONS" {
w.Header().Add("access-control-allow-methods", "*")
w.Header().Add("access-control-allow-headers", "*")
w.Header().Add("access-control-max-age", "300")
w.WriteHeader(200)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(204)
}
func eventsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Add("access-control-allow-origin", "*")
if r.Method == "OPTIONS" {
w.Header().Add("access-control-allow-methods", "*")
w.Header().Add("access-control-allow-headers", "*")
w.Header().Add("access-control-max-age", "300")
w.WriteHeader(200)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(204)
}
func hostsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Add("access-control-allow-origin", "*")
if r.Method == "OPTIONS" {
w.Header().Add("access-control-allow-methods", "*")
w.Header().Add("access-control-allow-headers", "*")
w.Header().Add("access-control-max-age", "300")
w.WriteHeader(200)
return
}
authorization := GetAuthorization(w, r)
var user *UserInfo
session := parsecService.GetSession(authorization)
if session == nil {
err := &ErrorResponse{
Error: "invalid session ID",
}
w.WriteHeader(401)
w.Write(formatJson(err))
return
}
user = parsecService.GetUserByEmail(session.Email)
if user == nil {
err := &ErrorResponse{
Error: "invalid session ID",
}
w.WriteHeader(401)
w.Write(formatJson(err))
return
}
var hosts []HostData
if user != nil {
peers := parsecService.GetPeersByEmail(user.Email)
for _, peer := range peers {
if !peer.Online {
continue
}
host := &HostData{
User: UserData{
Id: user.UserId,
Name: user.Email,
Warp: true,
ExternalId: "",
ExternalProvider: "",
TeamId: "",
},
PeerId: peer.PeerId,
GameId: "",
Build: peer.Build,
Description: "",
MaxPlayers: 20,
Mode: "desktop",
Name: peer.Name,
Players: peer.Players,
Public: peer.Public,
Self: session.HostPeerId == peer.PeerId,
}
hosts = append(hosts, *host)
}
response := &HostResponse{
Data: hosts,
HasMore: false,
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Write(formatJson(response))
}
}

View File

@ -0,0 +1,5 @@
{
"ver": 8,
"blurb": "- Client no longer disconnects when copying large amounts of text.\n- A new prompt to say Arcade goes away April 15, 2023.\n- [Windows] Windows 7 is now fully unsupported.\n- [Windows] Switch the WebSocket system to WinHTTP.\n- [Windows Paid] Virtual Tablets now works with up to 3 Screens.\n- [MacOS] Prompt to download the Apple Silicon build of Parsec.\n- [Ubuntu] We no longer block non text clipboard for other apps.",
"date": "150-87b"
}

View File

@ -0,0 +1,445 @@
{
"3": {
"url": "",
"type": "warning",
"title": "",
"desc": "The host OS closed the Parsec application due to a login/logout event, please reconnect."
},
"4": {
"url": "",
"type": "warning",
"title": "",
"desc": "The host shut down."
},
"5": {
"url": "https://support.parsec.app/hc/en-us/articles/115002625091",
"type": "warning",
"title": "",
"desc": "You have been kicked by the host."
},
"6": {
"url": "https://support.parsec.app/hc/en-us/articles/115002624531",
"type": "warning",
"title": "Your connection attempt wasn't approved in time",
"desc": "The host needs to approve your connection for you to join."
},
"8": {
"url": "",
"type": "warning",
"title": "Your connection attempt was rejected",
"desc": "The host declined your request or blocked you."
},
"9": {
"url": "",
"type": "warning",
"title": "",
"desc": "You canceled the connection attempt."
},
"11": {
"url": "",
"type": "warning",
"title": "The game or computer you tried to join is full",
"desc": "Wait for a free slot or ask the host to increase the number of players."
},
"12": {
"url": "https://support.parsec.app/hc/en-us/articles/6371362020365",
"type": "warning",
"title": "",
"desc": "You were disconnected from the host due to inactivity."
},
"30": {
"url": "https://parsec.app/downloads",
"label": "Download",
"type": "warning",
"title": "Your browser can not make connections in the web app",
"desc": "The Parsec web app only works in recent versions of Chrome and Edge, please use one of these browsers or install the Parsec app on your client to continue."
},
"99": {
"url": "https://support.parsec.app/hc/en-us/articles/115002601752",
"type": "warning",
"title": "The computer you're connecting to is no longer available",
"desc": "Check if the machine is online, get a new link if applicable, or try to restart Parsec on both ends."
},
"101": {
"url": "",
"type": "warning",
"title": "Multi-factor authentication is required",
"desc": "Your team requires that you enable multi-factor authentication before making connections."
},
"112": {
"url": "https://support.parsec.app/hc/en-us/articles/360049831391",
"type": "warning",
"title": "You don't have the permission to connect to this computer",
"desc": "You're not in the same Parsec Teams group as the computer you're attempting to connect to."
},
"-14": {
"url": "https://support.parsec.app/hc/en-us/articles/115002625712",
"type": "error",
"title": "Your device failed to decode the video stream",
"desc": "Lower the host computer's resolution or check our article for more information."
},
"-18": {
"url": "https://support.parsec.app/hc/en-us/articles/360001690972",
"type": "error",
"title": "Your device had issues decoding the video stream",
"desc": "This device may be incompatible with Parsec, please check our article for more information."
},
"-19": {
"url": "https://support.parsec.app/hc/en-us/articles/360002529252",
"type": "error",
"title": "Your device isn't supported by Parsec",
"desc": "This Android device does not work with Parsec currently."
},
"-1002": {
"url": "https://support.parsec.app/hc/en-us/articles/115002626391",
"type": "error",
"title": "You don't have access to a host computer",
"desc": "Running Parsec in CLI mode requires that you enable hosting on a Windows machine."
},
"-1003": {
"url": "https://support.parsec.app/hc/en-us/articles/115002626611",
"type": "error",
"title": "Parsec does not have sufficient permissions on this device",
"desc": "Parsec was unable to write required data to your device."
},
"-1400": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1401": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1402": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1403": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1404": {
"url": "",
"type": "error",
"title": "This game cannot be captured because it is running as administrator",
"desc": "Ensure your game and game store isn't running as administrator before trying to host."
},
"-1405": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1406": {
"url": "https://support.parsec.app/hc/en-us/articles/6367984442765",
"type": "error",
"title": "Arcade's attempt to capture the game timed out",
"desc": "Ensure the game is the active window, or check our article for more information."
},
"-1407": {
"url": "https://support.parsec.app/hc/en-us/articles/360059600491",
"type": "error",
"title": "Arcade is missing some important files to capture this game",
"desc": "Please check our article for more information."
},
"-1408": {
"url": "",
"type": "error",
"title": "Arcade already interacted with this game previously",
"desc": "Restart your game before trying to host it again."
},
"-1409": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1410": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1500": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1501": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1502": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1503": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1504": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1505": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1506": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1507": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1508": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1509": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1510": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1511": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1512": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1513": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1514": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-1515": {
"url": "https://support.parsec.app/hc/en-us/articles/360037758052",
"type": "error",
"title": "Arcade had trouble capturing this game",
"desc": "Please check our article for more information."
},
"-2001": {
"url": "https://support.parsec.app/hc/en-us/articles/115002626051",
"type": "error",
"title": "You do not have permission to make this connection",
"desc": "You can't connect with this peer_id."
},
"-6023": {
"url": "https://support.parsec.app/hc/en-us/articles/115002601011",
"type": "error",
"title": "The peer-to-peer network connection between you and the other computer failed",
"desc": "Something is preventing Parsec from making the connection, check our article for more information."
},
"-6024": {
"url": "https://support.parsec.app/hc/en-us/articles/115002601011",
"type": "error",
"title": "Failed to connect to Parsec STUN servers",
"desc": "Something may be blocking your connection to the Parsec STUN server, check our article for more information."
},
"-6101": {
"url": "https://support.parsec.app/hc/en-us/articles/360000059931",
"type": "error",
"title": "Parsec couldn't communicate with its servers",
"desc": "Something is preventing Parsec from making a websocket connection to its backend."
},
"-6112": {
"url": "https://support.parsec.app/hc/en-us/articles/360023178732",
"type": "error",
"title": "IPv6 is disabled or not supported",
"desc": "Your device must support IPv6 even if you're using IPv4."
},
"-6200": {
"url": "https://parsec.app/downloads",
"label": "Download",
"type": "error",
"title": "The network connection between you and the other computer failed with webRTC",
"desc": "Please install Parsec on your client which is more likely to work."
},
"-7000": {
"url": "https://support.parsec.app/hc/en-us/articles/115002626232",
"type": "error",
"title": "OpenGL was unable to be launched",
"desc": "Make sure your device meets our minimum requirements and is setup correctly."
},
"-7007": {
"url": "https://support.parsec.app/hc/en-us/articles/115002626272",
"type": "error",
"title": "This device has an incompatible version of OpenGL",
"desc": "Make sure your device meets our minimum requirements and is setup correctly."
},
"-6107": {
"url": "https://support.parsec.app/hc/en-us/articles/4410906958861",
"type": "error",
"title": "",
"desc": "You must re-authenticate."
},
"-12007": {
"url": "https://support.parsec.app/hc/en-us/articles/115003074512",
"type": "error",
"title": "The network connection was lost",
"desc": "The connection is unreliable, or the other computer has crashed."
},
"-12010": {
"url": "https://support.parsec.app/hc/en-us/articles/115003074532",
"type": "error",
"title": "The computer you were connected to disappeared",
"desc": "The computer may have crashed or lost the internet connection."
},
"-13000": {
"url": "https://support.parsec.app/hc/en-us/articles/115002623532",
"type": "error",
"title": "The computer you're joining is trying to use an unsupported resolution",
"desc": "Change the resolution to something else on the host computer's Parsec settings."
},
"-13008": {
"url": "https://support.parsec.app/hc/en-us/articles/115002623751",
"type": "error",
"title": "The computer you're joining is trying to use an unsupported resolution",
"desc": "Change the resolution to something else on the host computer's Parsec settings."
},
"-13009": {
"url": "https://support.parsec.app/hc/en-us/articles/360000159992",
"type": "error",
"title": "The host resolution is above our maximum supported resolution of 3840x2160",
"desc": "Try to lower the resolution of the display on the host computer."
},
"-13012": {
"url": "",
"type": "warning",
"title": "",
"desc": "The host is not allowing any more guests."
},
"-13015": {
"url": "https://support.parsec.app/hc/en-us/articles/360047224232",
"type": "error",
"title": "Something went wrong with the client device while initializing the stream",
"desc": "Your government or ISP may be blocking Parsec's encryption, you can try to use our web app or a VPN to get around this."
},
"-14003": {
"url": "https://support.parsec.app/hc/en-us/articles/360002165172",
"type": "error",
"title": "We were unable to capture the screen of the computer you were attempting to connect to",
"desc": "Check our article for more information."
},
"-15000": {
"url": "https://support.parsec.app/hc/en-us/articles/115002624051",
"type": "error",
"title": "The computer you are attempting connect to does not support hardware video encoding",
"desc": "The host computer may have unsupported hardware or its drivers need an update, check our article for more information."
},
"-15002": {
"url": "https://support.parsec.app/hc/en-us/articles/360000513331",
"type": "error",
"title": "The host encoder failed",
"desc": "This could be because on the host either HDR is active, the display resolution is too high, or the graphics driver needs an update."
},
"-15106": {
"url": "https://support.parsec.app/hc/en-us/articles/360033132792",
"type": "error",
"title": "The host encoder failed",
"desc": "This could be because on the host either HDR is active, or the display resolution is too high."
},
"-15107": {
"url": "https://support.parsec.app/hc/en-us/articles/360001383432",
"type": "error",
"title": "The resolution is too high on the host",
"desc": "Reduce the resolution on the host computer to connect."
},
"-17001": {
"url": "https://support.parsec.app/hc/en-us/articles/115002626412",
"type": "error",
"title": "Your Raspberry Pi is configured incorrectly",
"desc": "Disable the experimental OpenGL driver or check our article for more information about how to set up Raspberry Pi correctly."
},
"-18000": {
"url": "https://support.parsec.app/hc/en-us/articles/115002626352",
"type": "error",
"title": "Parsec couldn't communicate with its servers",
"desc": "Check if your internet is working, and if it is, check our article for more information."
},
"-22008": {
"url": "https://support.parsec.app/hc/en-us/articles/360002165172",
"type": "error",
"title": "",
"desc": "The host could not capture the screen."
},
"-32001": {
"url": "https://parsec.app/downloads",
"label": "Download",
"type": "error",
"title": "The web app failed to connect to your host",
"desc": "Please install the Parsec app on your client and retry the connection."
},
"-32002": {
"url": "https://parsec.app/downloads",
"label": "Download",
"type": "error",
"title": "The web app failed to connect to your host",
"desc": "Please install the Parsec app on your client and retry the connection."
},
"-32003": {
"url": "https://parsec.app/downloads",
"label": "Download",
"type": "error",
"title": "The web app failed to connect to your host",
"desc": "Please install the Parsec app on your client and retry the connection."
},
"-800097": {
"url": "https://support.parsec.app/hc/en-us/articles/360004310251",
"type": "error",
"title": "IPv6 is disabled",
"desc": "Your device must have IPv6 enabled even if you're using IPv4."
},
"-800098": {
"url": "https://support.parsec.app/hc/en-us/articles/360004310251",
"type": "error",
"title": "IPv6 is disabled",
"desc": "Your device must have IPv6 enabled even if you're using IPv4."
}
}

View File

@ -0,0 +1 @@
{"display":false,"title":"","message":"","type":"","link_title":"","link_url":""}

BIN
webapp/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

47
webapp/index.html Normal file
View File

@ -0,0 +1,47 @@
<!doctype html>
<html lang='en-us'>
<head>
<meta charset='utf-8'/>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
<script src='static/lib/matoya.js'></script>
<script src='static/lib/weblib.js'></script>
<script src='static/lib/parsec.js'></script>
</head>
<body>
<script>
function endFunc() {
const sp = new URLSearchParams(window.location.search);
if (sp.get('session_id')) {
window.location = 'https://parsec.app/signup';
} else {
window.close();
}
}
(async function () {
if (!await MTY_Start('parsecd', PARSEC_ENV, endFunc)) {
document.body.style.fontFamily = 'sans-serif';
document.body.style.fontSize = '30px';
document.body.style.background = 'black';
document.body.style.color = 'white';
document.body.style.textAlign = 'center';
document.body.style.padding = '10% 30px 0 30px';
document.body.innerHTML =
'<div>Your browser does not support the Parsec web app. The web app requires WebGL and WebAssembly 64-bit support.</div>' +
'<div style="margin-top:30px">Check the <a href="https://parsec.app/downloads">Downloads</a> page for our native app.</div>';
}
window.history.replaceState(null, document.title, '/');
document.querySelector("body > textarea").remove();
})();
console.log('%cSTOP! ✋ This area is intended for developers. Pasting something here could give strangers access to your Parsec account.', 'font-size: 18px; font-weight: 700;');
</script>
</body>
</html>

46
webapp/index.html.bak Normal file
View File

@ -0,0 +1,46 @@
<!doctype html>
<html lang='en-us'>
<head>
<meta charset='utf-8'/>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
<script src='static/lib/matoya.js'></script>
<script src='static/lib/weblib.js'></script>
<script src='static/lib/parsec.js'></script>
</head>
<body>
<script>
function endFunc() {
const sp = new URLSearchParams(window.location.search);
if (sp.get('session_id')) {
window.location = 'https://parsec.app/signup';
} else {
window.close();
}
}
(async function () {
if (!await MTY_Start('parsecd', PARSEC_ENV, endFunc)) {
document.body.style.fontFamily = 'sans-serif';
document.body.style.fontSize = '30px';
document.body.style.background = 'black';
document.body.style.color = 'white';
document.body.style.textAlign = 'center';
document.body.style.padding = '10% 30px 0 30px';
document.body.innerHTML =
'<div>Your browser does not support the Parsec web app. The web app requires WebGL and WebAssembly 64-bit support.</div>' +
'<div style="margin-top:30px">Check the <a href="https://parsec.app/downloads">Downloads</a> page for our native app.</div>';
}
window.history.replaceState(null, document.title, '/');
})();
console.log('%cSTOP! ✋ This area is intended for developers. Pasting something here could give strangers access to your Parsec account.', 'font-size: 18px; font-weight: 700;');
</script>
</body>
</html>

BIN
webapp/parsecd Normal file

Binary file not shown.

1512
webapp/static/lib/matoya.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,46 @@
'use strict';var h;function q(a){var b=0;return function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}}}function r(a){var b="undefined"!=typeof Symbol&&Symbol.iterator&&a[Symbol.iterator];return b?b.call(a):{next:q(a)}}var w="undefined"!=typeof window&&window===this?this:"undefined"!=typeof global&&null!=global?global:this,x="function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,d){a!=Array.prototype&&a!=Object.prototype&&(a[b]=d.value)};
function y(a,b){if(b){var d=w;a=a.split(".");for(var c=0;c<a.length-1;c++){var g=a[c];g in d||(d[g]={});d=d[g]}a=a[a.length-1];c=d[a];b=b(c);b!=c&&null!=b&&x(d,a,{configurable:!0,writable:!0,value:b})}}
y("Promise",function(a){function b(e){this.c=0;this.h=void 0;this.b=[];var f=this.f();try{e(f.resolve,f.reject)}catch(m){f.reject(m)}}function d(){this.b=null}function c(e){return e instanceof b?e:new b(function(f){f(e)})}if(a)return a;d.prototype.c=function(e){if(null==this.b){this.b=[];var f=this;this.f(function(){f.h()})}this.b.push(e)};var g=w.setTimeout;d.prototype.f=function(e){g(e,0)};d.prototype.h=function(){for(;this.b&&this.b.length;){var e=this.b;this.b=[];for(var f=0;f<e.length;++f){var m=
e[f];e[f]=null;try{m()}catch(p){this.g(p)}}}this.b=null};d.prototype.g=function(e){this.f(function(){throw e;})};b.prototype.f=function(){function e(p){return function(t){m||(m=!0,p.call(f,t))}}var f=this,m=!1;return{resolve:e(this.o),reject:e(this.g)}};b.prototype.o=function(e){if(e===this)this.g(new TypeError("A Promise cannot resolve to itself"));else if(e instanceof b)this.s(e);else{a:switch(typeof e){case "object":var f=null!=e;break a;case "function":f=!0;break a;default:f=!1}f?this.m(e):this.i(e)}};
b.prototype.m=function(e){var f=void 0;try{f=e.then}catch(m){this.g(m);return}"function"==typeof f?this.B(f,e):this.i(e)};b.prototype.g=function(e){this.j(2,e)};b.prototype.i=function(e){this.j(1,e)};b.prototype.j=function(e,f){if(0!=this.c)throw Error("Cannot settle("+e+", "+f+"): Promise already settled in state"+this.c);this.c=e;this.h=f;this.l()};b.prototype.l=function(){if(null!=this.b){for(var e=0;e<this.b.length;++e)k.c(this.b[e]);this.b=null}};var k=new d;b.prototype.s=function(e){var f=this.f();
e.v(f.resolve,f.reject)};b.prototype.B=function(e,f){var m=this.f();try{e.call(f,m.resolve,m.reject)}catch(p){m.reject(p)}};b.prototype.then=function(e,f){function m(n,u){return"function"==typeof n?function(v){try{p(n(v))}catch(A){t(A)}}:u}var p,t,l=new b(function(n,u){p=n;t=u});this.v(m(e,p),m(f,t));return l};b.prototype.catch=function(e){return this.then(void 0,e)};b.prototype.v=function(e,f){function m(){switch(p.c){case 1:e(p.h);break;case 2:f(p.h);break;default:throw Error("Unexpected state: "+
p.c);}}var p=this;null==this.b?k.c(m):this.b.push(m)};b.resolve=c;b.reject=function(e){return new b(function(f,m){m(e)})};b.race=function(e){return new b(function(f,m){for(var p=r(e),t=p.next();!t.done;t=p.next())c(t.value).v(f,m)})};b.all=function(e){var f=r(e),m=f.next();return m.done?c([]):new b(function(p,t){function l(v){return function(A){n[v]=A;u--;0==u&&p(n)}}var n=[],u=0;do n.push(void 0),u++,c(m.value).v(l(n.length-1),t),m=f.next();while(!m.done)})};return b});
function z(){z=function(){};w.Symbol||(w.Symbol=aa)}function B(a,b){this.b=a;x(this,"description",{configurable:!0,writable:!0,value:b})}B.prototype.toString=function(){return this.b};var aa=function(){function a(d){if(this instanceof a)throw new TypeError("Symbol is not a constructor");return new B("jscomp_symbol_"+(d||"")+"_"+b++,d)}var b=0;return a}();
function C(){z();var a=w.Symbol.iterator;a||(a=w.Symbol.iterator=w.Symbol("Symbol.iterator"));"function"!=typeof Array.prototype[a]&&x(Array.prototype,a,{configurable:!0,writable:!0,value:function(){return ba(q(this))}});C=function(){}}function ba(a){C();a={next:a};a[w.Symbol.iterator]=function(){return this};return a}function D(){this.g=!1;this.c=null;this.i=void 0;this.b=1;this.l=this.h=0;this.f=null}function E(a){if(a.g)throw new TypeError("Generator is already running");a.g=!0}
D.prototype.j=function(a){this.i=a};function F(a,b){a.f={F:b,U:!0};a.b=a.h||a.l}D.prototype.return=function(a){this.f={return:a};this.b=this.l};function G(a,b,d){a.b=d;return{value:b}}function ca(a){this.b=new D;this.c=a}function da(a,b){E(a.b);var d=a.b.c;if(d)return H(a,"return"in d?d["return"]:function(c){return{value:c,done:!0}},b,a.b.return);a.b.return(b);return I(a)}
function H(a,b,d,c){try{var g=b.call(a.b.c,d);if(!(g instanceof Object))throw new TypeError("Iterator result "+g+" is not an object");if(!g.done)return a.b.g=!1,g;var k=g.value}catch(e){return a.b.c=null,F(a.b,e),I(a)}a.b.c=null;c.call(a.b,k);return I(a)}function I(a){for(;a.b.b;)try{var b=a.c(a.b);if(b)return a.b.g=!1,{value:b.value,done:!1}}catch(d){a.b.i=void 0,F(a.b,d)}a.b.g=!1;if(a.b.f){b=a.b.f;a.b.f=null;if(b.U)throw b.F;return{value:b.return,done:!0}}return{value:void 0,done:!0}}
function ea(a){this.next=function(b){E(a.b);a.b.c?b=H(a,a.b.c.next,b,a.b.j):(a.b.j(b),b=I(a));return b};this.throw=function(b){E(a.b);a.b.c?b=H(a,a.b.c["throw"],b,a.b.j):(F(a.b,b),b=I(a));return b};this.return=function(b){return da(a,b)};C();this[Symbol.iterator]=function(){return this}}function fa(a){function b(c){return a.next(c)}function d(c){return a.throw(c)}return new Promise(function(c,g){function k(e){e.done?c(e.value):Promise.resolve(e.value).then(b,d).then(k,g)}k(a.next())})}
function J(a){return fa(new ea(new ca(a)))}y("Object.entries",function(a){return a?a:function(b){var d=[],c;for(c in b)Object.prototype.hasOwnProperty.call(b,c)&&d.push([c,b[c]]);return d}});
y("Array.from",function(a){return a?a:function(b,d,c){d=null!=d?d:function(f){return f};var g=[],k="undefined"!=typeof Symbol&&Symbol.iterator&&b[Symbol.iterator];if("function"==typeof k){b=k.call(b);for(var e=0;!(k=b.next()).done;)g.push(d.call(c,k.value,e++))}else for(k=b.length,e=0;e<k;e++)g.push(d.call(c,b[e],e));return g}});function K(a,b){if(null==a)throw new TypeError("The 'this' value for String.prototype."+b+" must not be null or undefined");return a+""}
y("String.prototype.repeat",function(a){return a?a:function(b){var d=K(this,"repeat");if(0>b||1342177279<b)throw new RangeError("Invalid count value");b|=0;for(var c="";b;)if(b&1&&(c+=d),b>>>=1)d+=d;return c}});y("String.prototype.padStart",function(a){return a?a:function(b,d){var c=K(this,"padStart");b-=c.length;d=void 0!==d?String(d):" ";return(0<b&&d?d.repeat(Math.ceil(b/d.length)).substring(0,b):"")+c}});
var L={PARSEC_OK:0,PARSEC_CONNECTING:20,PARSEC_WRN_BROWSER:30,CONNECT_WRN_APPROVAL:6,CONNECT_WRN_DECLINED:8,CONNECT_WRN_PEER_GONE:99,CONNECT_WRN_UNCONFIRMED:100,PARSEC_NOT_RUNNING:-3,WS_ERR_CONNECT:-6101,WS_ERR_CLOSE:-6105,NAT_ERR_WEBRTC:-6200};function M(a,b,d,c){d=c?d.bind(c):d;a.addEventListener(b,d);return[a,b,d]}function ha(a){a=r(a);for(var b=a.next();!b.done;b=a.next())b=b.value,b[0].removeEventListener(b[1],b[2])}
function N(a,b,d,c,g){a=new DataView(a);a.setInt32(0,d);a.setInt32(4,c);a.setInt32(8,g);a.setInt8(12,b)}function O(a,b,d,c){var g=new ArrayBuffer(13);N(g,a,b,d,c);return g}function ia(a,b,d){var c=new ArrayBuffer(13+d.length+1);N(c,a,d.length+1,b,0);a=(new TextEncoder).encode(d);b=new Int8Array(c,13);for(var g=0;g<d.length;g++)b[g]=a[g];return c}
function ja(a,b){a=JSON.stringify({_version:1,_max_w:6E4,_max_h:6E4,_flags:0,resolutionX:a,resolutionY:b,refreshRate:60,mediaContainer:2});return ia(11,0,a)}
function ka(a,b){switch(a.type){case 4:if(!a.relative){var d=b.videoWidth,c=b.videoHeight,g=Math.round(b.offsetWidth*window.devicePixelRatio),k=Math.round(b.offsetHeight*window.devicePixelRatio),e=Math.min(g/d,k/c);b=d*e;e*=c;k=Math.max((k-e)/2,0);g=Math.round(d/b*(a.x-Math.max((g-b)/2,0)));g===d-1&&(g=d);g>d&&(g=d);0>g&&(g=0);a.x=g;d=Math.round(c/e*(a.y-k));d===c-1&&(d=c);d>c&&(d=c);0>d&&(d=0);a.y=d}return O(3,a.relative?1:0,a.x,a.y);case 8:return c=new ArrayBuffer(28),N(c,23,a.id,0,0),d=new DataView(c),
d.setUint16(16,a.buttons),d.setInt16(18,a.thumbLX),d.setInt16(20,a.thumbLY),d.setInt16(22,a.thumbRX),d.setInt16(24,a.thumbRY),d.setUint8(26,a.leftTrigger),d.setUint8(27,a.rightTrigger),c;case 2:return O(1,a.button,a.pressed?1:0,0);case 1:return O(0,a.code,a.mod,a.pressed?1:0);case 3:return O(2,a.x,a.y,0);case 5:return O(4,a.button,a.pressed?1:0,a.id);case 6:return O(5,a.axis,a.value,a.id);case 7:return O(6,0,0,a.id);case 9:return O(24,0,0,0)}}var P={},la=1;
function ma(a){var b=la++;P[b]=a;return b}function na(a){var b=a.getInt16(32),d=a.getInt32(16),c=0<d?new Uint8Array(a.buffer,34,d):null;c=c?ma(c):0;return{type:1,cursor:{size:d,positionX:a.getInt16(24),positionY:a.getInt16(26),width:a.getInt16(20),height:a.getInt16(22),hotX:a.getInt16(28),hotY:a.getInt16(30),imageUpdate:0<c,relative:!!(b&256),hidden:!!(b&512),stream:0},key:c}}function oa(a,b){b=ma(new Uint8Array(b.buffer,13,a.u));return{type:3,id:a.w,key:b}}
function pa(){var a=new Uint8Array(16);crypto.getRandomValues(a);return a.map(function(b){return b%10}).join("")}
function Q(a){var b=this;this.l=a;this.i=!1;this.j="";this.h=!1;this.b=this.sdp=null;this.c={};this.g=[];this.f=null;this.b=new RTCPeerConnection({iceServers:[{urls:"stun:stun.parsec.gg:3478"}]});this.b.onicecandidate=function(d){d.candidate&&(d=d.candidate.candidate.replace("candidate:","").split(" "),8<=d.length&&"udp"===d[2].toLowerCase()&&b.l(d[4],parseInt(d[5],10),!1,"srflx"===d[7],"host"===d[7]))}}
Q.prototype.close=function(){for(var a=r(Object.entries(this.c)),b=a.next();!b.done;b=a.next())b.value[1].close();this.b.close()};function R(a,b,d,c,g){a.c[d]=a.b.createDataChannel(b,{negotiated:!0,id:d});a.c[d].binaryType="arraybuffer";a.c[d].onopen=c;a.c[d].onmessage=g}
function qa(a){var b;return J(function(d){if(1==d.b)return b=a,G(d,a.b.createOffer(),2);b.f=d.i;for(var c=a.f.sdp.split("\n"),g={},k=0;k<c.length;k++){var e=c[k].split("="),f=e[0];e=e[1];f&&("a"===f?(g.a||(g.a={}),f=e.split(/:(.+)/),g.a[f[0]]=f[1]):g[f]=e)}a.sdp=g;return d.return({ice_ufrag:a.sdp.a["ice-ufrag"],ice_pwd:a.sdp.a["ice-pwd"],fingerprint:a.sdp.a.fingerprint})})}Q.prototype.send=function(a,b){"open"==this.c[b].readyState&&this.c[b].send(a)};
function ra(a){for(;0<a.g.length;){var b=a.g.shift();a.b.addIceCandidate(new RTCIceCandidate({candidate:"candidate:2395300328 1 udp 2113937151 "+b.ip+" "+(b.port+" typ "+(b.from_stun?"srflx":"host")+" generation 0 ufrag "+a.j+" network-cost 50"),sdpMid:a.sdp.a.mid,sdpMLineIndex:0}))}}
function sa(a,b,d,c){var g,k;J(function(e){switch(e.b){case 1:if(!a.f)throw"Offer is not set";if(a.h){e.b=0;break}a.j=b;return G(e,a.b.setLocalDescription(a.f),3);case 3:e.h=4;var f=a.sdp.a.mid;g="v=0\r\no=- "+(pa()+" 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE ")+(f+"\r\na=msid-semantic: WMS *\r\nm=application 9 DTLS/SCTP 5000\r\nc=IN IP4 0.0.0.0\r\nb=AS:30\r\na=ice-ufrag:")+(b+"\r\na=ice-pwd:")+(d+"\r\na=ice-options:trickle\r\na=fingerprint:")+(c+"\r\na=setup:active\r\na=mid:")+(f+"\r\na=sendrecv\r\na=sctpmap:5000 webrtc-datachannel 256\r\na=max-message-size:1073741823\r\n");
return G(e,a.b.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:g})),6);case 6:e.b=5;e.h=0;break;case 4:e.h=0,f=e.f.F,e.f=null,k=f,console.log(k);case 5:a.i&&ra(a),a.h=!0,e.b=0}})}function ta(a,b,d,c,g){c?(a.i=!0,setTimeout(function(){a.l("1.2.3.4",1234,!0,!1,!1)},500)):a.g.push({ip:b.replace("::ffff:",""),port:d,from_stun:g});a.h&&a.i&&ra(a)}function S(a,b){this.h={};this.c=b;this.j=a;this.i="";this.b=null;this.f=[];this.g=this.l=!1;this.timeout=null}
function ua(a,b,d,c,g,k,e){a.b=new WebSocket("wss://kessel-ws.evan.run:443/?session_id=_&role=client&version=1&sdk_version=0",b);a.i=d;a.b.onclose=function(){a.g||a.c(L.WS_ERR_CLOSE)};a.b.onerror=function(){a.c(L.WS_ERR_CONNECT)};a.b.onopen=function(){for(;0<a.f.length;)a.b.send(a.f.shift());a.send({action:"offer",version:1,payload:{to:a.i,attempt_id:a.j,secret:c?c:"",data:{ver_data:1,creds:g,mode:2,versions:{p2p:1,bud:1,init:1,video:1,audio:1,control:1}}}});a.timeout=setTimeout(function(){a.c(L.CONNECT_WRN_APPROVAL)},
3E4)};a.b.onmessage=function(f){f=JSON.parse(f.data);switch(f.action){case "answer_relay":a.l=!0;if(!f.payload.approved){a.c(L.CONNECT_WRN_DECLINED);break}a.timeout&&clearTimeout(a.timeout);a.timeout=setTimeout(function(){a.c(L.NAT_ERR_WEBRTC)},1E4);f=f.payload.data.creds;k(f.ice_ufrag,f.ice_pwd,f.fingerprint);break;case "candex_relay":f=f.payload.data;e(f.ip,f.port,f.sync,f.from_stun);break;case "close":var m=L.WS_ERR_CLOSE;switch(f.payload.reason){case "HOST_NOT_FOUND":m=L.CONNECT_WRN_PEER_GONE;
break;case "USER_UNCONFIRMED":m=L.CONNECT_WRN_UNCONFIRMED}a.c(m)}}}S.prototype.send=function(a){this.b&&(a=JSON.stringify(a),this.b.readyState==WebSocket.OPEN?this.b.send(a):this.f.push(a))};S.prototype.close=function(a){this.g=!0;this.b&&(this.b.close(a),this.b=null);this.timeout&&(clearTimeout(this.timeout),this.timeout=null)};var T=1E3/60/1E3,U=(/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)?200:100)/1E3;function V(a,b){a.playbackRate=b;a.play().catch(function(){})}
function W(a,b,d){var c=this;this.b=a;this.l=!0;this.type=b;this.o=d;this.f=this.c=null;this.m='video/mp4; codecs="avc1.64001e"'===this.type;this.g=[];this.j=[];this.h=null;this.i=0;this.B=function(){var g=c.b.seekable;if(g&&0<g.length){g=g.end(0);var k=g-c.b.currentTime;'video/mp4; codecs="avc1.64001e"'===c.type?(c.i=k>1.5*T?c.i+1:0,60<c.i&&1==c.b.playbackRate?V(c.b,k>3*T?10:1.25):k<T&&1!=c.b.playbackRate&&V(c.b,1)):k>3*U?c.b.currentTime=g+1E3:k>1.5*U&&1==c.b.playbackRate?V(c.b,10):k<U&&10==c.b.playbackRate&&
V(c.b,1)}}}W.prototype.s=function(){if(0<this.j.length&&this.c&&!this.c.updating)try{var a=this.j.shift();this.c.appendBuffer(a)}catch(b){console.warn(b),X(this),this.o&&this.o()}};
function va(a){a.f=new MediaSource;a.b.src=URL.createObjectURL(a.f);a.b.load();a.g.push(M(a.b,"error",function(){console.error(a.b.error.message)},null));a.g.push(M(a.f,"sourceopen",function(){a.c=a.f.addSourceBuffer(a.type);a.c.mode="sequence";a.g.push(M(a.c,"update",a.s,a));a.b.play().catch(function(){});a.h=setInterval(a.B,1)},null))}
function X(a){a.h&&clearInterval(a.h);a.b.pause();ha(a.g);a.f&&(a.c&&(a.f.removeSourceBuffer(a.c),a.c=null),a.f.endOfStream(),URL.revokeObjectURL(a.b.src),a.f=null);a.b.src="";'video/mp4; codecs="avc1.64001e"'===a.type&&a.b.load();a.l=!0;a.m='video/mp4; codecs="avc1.64001e"'===a.type;a.g=[];a.j=[];a.h=null;a.i=0}
function wa(a,b){b=new Uint8Array(b);'audio/mp4; codecs="opus"'===a.type&&a.l&&(a.l=!1,va(a));'video/mp4; codecs="avc1.64001e"'===a.type&&102===b[4]&&(X(a),va(a),a.m=!1);a.m||(a.j.push(b),a.s())}function xa(){for(var a=[],b=new Uint8Array(4),d=0;6>d;d++)crypto.getRandomValues(b),a.push(Array.from(b).map(function(c){return c.toString(16).padStart(2,"0")}).join(""));return a.join("-")}
function Y(a,b){var d=this;this.status=L.PARSEC_NOT_RUNNING;this.o=performance.now();this.s={encodeLatency:0,decodeLatency:0,networkLatency:0};this.c=[];this.signal=this.b=null;this.h=[];this.f="";this.video=a;this.l=[];this.j={};this.m=0;this.i=new W(b,'audio/mp4; codecs="opus"',null);this.g=new W(a,'video/mp4; codecs="avc1.64001e"',function(){console.warn("Attempting to recover stream...");d.b&&d.b.send(O(13,0,0,1),0)});this.h.push(M(window,"beforeunload",function(){d.C();return null},null))}
function Z(a,b){a.signal&&(a.signal.close(1E3),a.signal=null);b!=L.PARSEC_OK&&(a.b&&a.status==L.PARSEC_OK&&a.b.send(O(10,0,0,0),0),a.b&&(a.b.close(),a.b=null),X(a.g),X(a.i));a.status=b;a.l=[];a.j={};a.m=0}h=Y.prototype;h.C=function(){ha(this.h);this.D()};h.X=function(){};h.S=function(){return this.f};h.T=function(a){var b=P[a];void 0!=b&&delete P[a];return void 0==b?null:b};
h.G=function(a,b,d){var c=this,g,k,e,f,m,p;return J(function(t){if(1==t.b){g=/Chrome/.test(navigator.userAgent)&&/Google Inc/.test(navigator.vendor);if(!g)return c.status=L.PARSEC_WRN_BROWSER,t.return();if(c.status!=L.PARSEC_NOT_RUNNING)return t.return();c.status=L.PARSEC_CONNECTING;P={};la=1;X(c.i);X(c.g);c.c=[];c.f=xa();c.signal=new S(c.f,function(l){Z(c,l)});k=function(l,n,u,v,A){c.c.push({type:8,attemptID:c.f,ip:l,port:n,lan:A,fromStun:v,sync:u})};e=function(){c.h.push(M(document,"visibilitychange",
function(){document.hidden?(X(c.g),c.b&&c.b.send(O(19,0,0,0),0)):c.b&&c.b.send(O(13,0,0,0),0)},null));var l=window.screen.width,n=window.screen.height;if(800>l||600>n||1920<l||1080<n)l=1920,n=1080;c.b&&c.b.send(ja(l,n),0);Z(c,L.PARSEC_OK)};c.b=new Q(k);R(c.b,"control",0,e,function(l){c.o=performance.now();var n=new DataView(l.data);l={u:n.getInt32(0),w:n.getInt32(4),R:n.getInt32(8),type:n.getInt8(12)};switch(l.type){case 10:c.status=l.u;break;case 21:c.s={encodeLatency:parseFloat(l.w)/1E3,decodeLatency:0,
networkLatency:0,packetsSent:0,fastRTs:0,slowRTs:0,bitrate:0};break;case 20:c.c.push({type:2,gamepadID:l.u,motorBig:l.w,motorSmall:l.R});break;case 16:c.c.push({type:l.u?4:5});break;case 28:c.m=l.u;break;case 17:c.c.push(oa(l,n));break;case 9:c.c.push(na(n));break;case 25:n=JSON.parse((new TextDecoder("utf-8")).decode(new Uint8Array(n.buffer,13,l.u-1)));for(var u={},v=0;v<n.length;v++)n[v].id==l.w&&(u=n[v]);l={list:n,me:u};c.l=l.list;c.j=l.me}});R(c.b,"video",1,null,function(l){wa(c.g,l.data)});R(c.b,
"audio",2,null,function(l){wa(c.i,l.data)});return G(t,qa(c.b),2)}f=t.i;m=function(l,n,u){c.b&&sa(c.b,l,n,u)};p=function(l,n,u,v){c.b&&ta(c.b,l,n,u,v)};c.signal&&ua(c.signal,a,b,d,f,m,p);t.b=0})};h.L=function(){return this.status};h.K=function(){return this.j};h.I=function(){return this.m};h.H=function(){return this.l};h.D=function(){Z(this,L.PARSEC_NOT_RUNNING)};h.P=function(a,b){this.b&&this.status==L.PARSEC_OK&&this.b.send(ia(17,a,b),0)};
h.O=function(a){this.b&&this.status==L.PARSEC_OK&&this.b.send(ka(a,this.video),0)};h.N=function(){return this.c.shift()};h.J=function(){return this.s};h.M=function(){return this.status==L.PARSEC_OK&&5E3<performance.now()-this.o};h.W=function(a,b,d,c,g,k){if(this.signal){var e=this.A(a);e&&this.signal.send({action:"candex",version:1,payload:{to:e,attempt_id:a,data:{ver_data:1,ip:b,port:d,lan:k?!0:!1,from_stun:g?!0:!1,sync:c?!0:!1}}})}};
h.V=function(a){if(this.signal){var b=this.A(a);b&&this.signal.send({action:"offer_cancel",version:1,payload:{to:b,attempt_id:a}})}};h.Y=function(a,b){this.signal&&(this.signal.h[a]=b)};h.A=function(a){if(this.signal)return(a=this.signal.h[a])?a:""};Y.prototype.destroy=Y.prototype.C;Y.prototype.setLogCallback=Y.prototype.X;Y.prototype.getAttemptID=Y.prototype.S;Y.prototype.getBuffer=Y.prototype.T;Y.prototype.clientConnect=Y.prototype.G;Y.prototype.clientGetStatus=Y.prototype.L;
Y.prototype.clientGetSelf=Y.prototype.K;Y.prototype.clientGetHostMode=Y.prototype.I;Y.prototype.clientGetGuests=Y.prototype.H;Y.prototype.clientDisconnect=Y.prototype.D;Y.prototype.clientSendUserData=Y.prototype.P;Y.prototype.clientSendMessage=Y.prototype.O;Y.prototype.clientPollEvents=Y.prototype.N;Y.prototype.clientGetMetrics=Y.prototype.J;Y.prototype.clientNetworkFailure=Y.prototype.M;Y.prototype.sendCandidate=Y.prototype.W;Y.prototype.setPeerID=Y.prototype.Y;Y.prototype.getPeerID=Y.prototype.A;
Y.prototype.sendCancel=Y.prototype.V;Y.prototype.Status=L;window.Parsec=Y;

252
webapp/static/lib/weblib.js Normal file
View File

@ -0,0 +1,252 @@
let PARSEC;
let AUDIO;
let DAEMON_WAITING = false;
let PEER_ID_C;
const PARSEC_ENV = {
// user.bin
bin_user_bin_get: function (asset_dir_c, session_id_c, size) {
try {
const cookies = document.cookie.split(';');
for (let x = 0; x < cookies.length; x++) {
const cookie = cookies[x].trim();
const name = 'parsec_login=';
if (cookie.indexOf(name) == 0) {
const auth = JSON.parse(cookie.substring(name.length, cookie.length));
MTY_StrToC(auth['token'], session_id_c, size);
return 0;
}
}
} catch (e) {
console.error(e);
}
return -1;
},
bin_user_bin_set: function (asset_dir_c, session_id_c) {
const session_id = MTY_StrToJS(session_id_c);
const value = JSON.stringify({
'token': session_id,
'userId': 0,
});
const hostname = window.location.hostname.replace(/.*?\./, '');
const secure = window.location.protocol == 'https:';
document.cookie = 'parsec_login=' + value + ';domain=' + hostname + ';path=/;' +
(secure ? 'secure' : '') + ';max-age=31536000;samesite=strict;';
},
bin_user_bin_delete: function (asset_dir_c) {
const hostname = window.location.hostname.replace(/.*?\./, '');
document.cookie = 'parsec_login=;domain=' + hostname + ';path=/;expires=Thu, 01 Jan 1970 00:00:00 GMT;';
},
// Audio control
web_mute: function (muted) {
AUDIO.muted = muted;
},
// ws-api
signal_set_peer_id: function (s, attempt_id, peer_id) {
if (!PARSEC)
return;
PARSEC.setPeerID(MTY_StrToJS(attempt_id), MTY_StrToJS(peer_id));
},
signal_get_peer_id: function (s, attempt_id) {
if (!PARSEC)
return;
const peer_id = PARSEC.getPeerID(MTY_StrToJS(attempt_id));
if (!PEER_ID_C)
PEER_ID_C = MTY_Alloc(128);
MTY_StrToC(peer_id, PEER_ID_C, 128);
return PEER_ID_C;
},
signal_client_cancel: function (s, ps) {
if (!PARSEC)
return;
PARSEC.sendCancel(PARSEC.getAttemptID());
PARSEC_ENV.parsec_web_disconnect();
},
web_send_candex: function (attempt_id, ip, port, sync, fromStun, lan) {
if (!PARSEC)
return;
PARSEC.sendCandidate(MTY_StrToJS(attempt_id),
MTY_StrToJS(ip), port, sync, fromStun, lan);
},
signal_host_allow_guest: function (s, ps, attempt_id, allow) {
},
signal_host_stop: function (s, ps) {
},
signal_send_conn_update: function (s, ps) {
},
signal_host_set_permissions: function (s, ps, guest_id, perms) {
},
signal_host_start: function (s, ps, mode, cfg, priv, session_id) {
return -1;
},
signal_host_set_config: function (s, ps, cfg, priv) {
},
signal_client_connect: function (s, ps, cfg, peer_id_c, session_id_c, secret_c) {
//XXX TODO this should create a new attempt and send the offer
const session_id = MTY_StrToJS(session_id_c);
const peer_id = MTY_StrToJS(peer_id_c);
const secret = MTY_StrToJS(secret_c);
if (ps) {
const connect = () => {
if (!PARSEC) {
setTimeout(connect, 100);
} else {
PARSEC.clientConnect(session_id, peer_id, secret);
PARSEC.setPeerID(PARSEC.getAttemptID(), peer_id);
}
};
connect();
} else {
window.location.assign('parsec://peer_id=' + peer_id + '&host_secret=' + secret);
}
return 0;
},
signal_init: function (s_out, role, host, port, loader_v, service_v, device_id, cbs, opaque) {
const role_js = MTY_StrToJS(role);
const host_js = MTY_StrToJS(host);
// XXX TODO create context, but don't yet connect to websocket
},
signal_destroy: function (s_out) {
// XXX TODO full clean up and null out context
},
signal_update: function (s, host, session_id) {
const host_js = MTY_StrToJS(host);
const session_id_js = MTY_StrToJS(session_id);
// XXX TODO reconnect websocket with new session_id and websocket host
return false;
},
signal_stop: function (s) {
// XXX TODO Disconnect from websocket, clear message queue
},
// parsec SDK
parsec_web_init: function () {
if (PARSEC)
return;
AUDIO = document.createElement('audio');
document.body.appendChild(AUDIO);
const container = document.createElement('div');
container.style.zIndex = -1;
container.style.background = 'black';
container.style.position = 'fixed';
container.style.top = 0;
container.style.right = 0;
container.style.bottom = 0;
container.style.left = 0;
document.body.appendChild(container);
const video = document.createElement('video');
video.muted = true;
video.style.width = '100%';
video.style.height = '100%';
container.appendChild(video);
PARSEC = new Parsec(video, AUDIO);
},
parsec_web_destroy: function () {
if (!PARSEC)
return;
PARSEC.destroy();
PARSEC = undefined;
},
parsec_web_disconnect: function () {
PARSEC.clientDisconnect();
},
parsec_web_get_status: function () {
return DAEMON_WAITING ? PARSEC.Status.PARSEC_CONNECTING : PARSEC.clientGetStatus();
},
parsec_web_send_user_data: function (id, msg_c) {
PARSEC.clientSendUserData(id, MTY_StrToJS(msg_c));
},
parsec_web_get_guests: function (jstr_c, len) {
MTY_StrToC(JSON.stringify(PARSEC.clientGetGuests()), jstr_c, len);
},
parsec_web_get_attempt_id: function (attempt_id_c, len) {
MTY_StrToC(PARSEC.getAttemptID(), attempt_id_c, len);
},
parsec_web_poll_events: function (event_str_c, len) {
const event = PARSEC.clientPollEvents();
if (event) {
MTY_StrToC(JSON.stringify(event), event_str_c, len);
return true;
}
return false;
},
parsec_web_get_buffer: function (key) {
const buffer = PARSEC.getBuffer(key);
if (buffer) {
const ptr = MTY_Alloc(buffer.length);
MTY_Memcpy(ptr, buffer);
return ptr;
}
return 0;
},
parsec_web_set_log_callback: function (callback_c, opaque) {
PARSEC.setLogCallback((level, msg) => {
const msg_c = MTY_Alloc(1024);
MTY_StrToC(msg, msg_c, 1024);
MTY_CFunc(callback_c)(level, msg_c, opaque);
MTY_Free(msg_c);
});
},
parsec_web_send_message: function (msg_c) {
const msg = JSON.parse(MTY_StrToJS(msg_c));
PARSEC.clientSendMessage(msg);
},
parsec_web_get_metrics: function (decode_ptr, encode_ptr, network_ptr) {
const metrics = PARSEC.clientGetMetrics();
MTY_SetFloat(decode_ptr, metrics['decodeLatency']);
MTY_SetFloat(encode_ptr, metrics['encodeLatency']);
MTY_SetFloat(network_ptr, metrics['networkLatency']);
},
parsec_web_get_network_failure: function () {
return PARSEC.clientNetworkFailure();
},
parsec_web_get_self: function (owner_ptr, id_ptr) {
const me = PARSEC.clientGetSelf();
MTY_SetInt8(owner_ptr, me['owner']);
MTY_SetInt32(id_ptr, me['id']);
},
parsec_web_get_host_mode: function () {
return PARSEC.clientGetHostMode();
},
};