Add channel support
This commit is contained in:
parent
d2aecadbc4
commit
32c12d6250
|
@ -1,5 +1,6 @@
|
|||
0.1.2:
|
||||
- Add channel support
|
||||
- Improve interface on mobile devices
|
||||
- Notify user when browser does not support WebRTC
|
||||
|
||||
0.1.1:
|
||||
|
|
37
HOSTING.md
37
HOSTING.md
|
@ -20,9 +20,9 @@ or [Apache](https://httpd.apache.org/) via
|
|||
|
||||
### Caddy example
|
||||
|
||||
Launch harmony-server with the following flag:
|
||||
Launch harmony-server with the following configuration option:
|
||||
|
||||
```-web-address=localhost:19000```
|
||||
```webaddress: localhost:19000```
|
||||
|
||||
Add the following to the relevant site directive in your Caddyfile:
|
||||
|
||||
|
@ -58,10 +58,39 @@ Execute harmony-server with the ```-help``` flag for more information:
|
|||
If the command was not found, add ```~/go/bin``` to your
|
||||
[PATH variable](https://en.wikipedia.org/wiki/PATH_%28variable%29).
|
||||
|
||||
# Configuration
|
||||
|
||||
The path to a configuration file may be specified with ```--config```, or the
|
||||
default path of ```~/.config/harmony-server/config.yaml``` may be used.
|
||||
|
||||
Example configuration:
|
||||
|
||||
```
|
||||
webaddress: localhost:19000
|
||||
webpath: /
|
||||
channels:
|
||||
Lobby-Text:
|
||||
type: text
|
||||
topic: Example text channel
|
||||
Lobby-Voice:
|
||||
type: voice
|
||||
topic: Example voice channel
|
||||
Lobby-Voice2:
|
||||
type: voice
|
||||
topic: Another voice channel
|
||||
```
|
||||
|
||||
## Hosting the web client on a different path
|
||||
|
||||
The web client expects to be hosted at the root of the domain, ```/```.
|
||||
|
||||
To host the web client on a different path, use the ```-web-path``` flag:
|
||||
To host the web client on a different path, such as ```/chat/```, set the
|
||||
```webpath``` configuration option:
|
||||
|
||||
```harmony-server -web-path=/chat/```
|
||||
```webpath: /chat/```
|
||||
|
||||
## Hosting the web client on multiple addresses
|
||||
|
||||
Multiple address may be specified:
|
||||
|
||||
```webaddress: localhost:19000,192.168.0.101:19010```
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type ChannelConfig struct {
|
||||
Type string
|
||||
Topic string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
WebAddress string
|
||||
WebPath string
|
||||
Channels map[string]ChannelConfig
|
||||
}
|
||||
|
||||
var config = Config{WebPath: "/"}
|
||||
|
||||
func readconfig(configPath string) error {
|
||||
if configPath == "" {
|
||||
return errors.New("file unspecified")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
return errors.New("file not found")
|
||||
}
|
||||
|
||||
configData, err := ioutil.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(configData, &config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = validateConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(config.Channels) == 0 {
|
||||
addPlaceholderchannels()
|
||||
}
|
||||
|
||||
newChannels := make(map[string]ChannelConfig)
|
||||
for channelName, ch := range config.Channels {
|
||||
newChannels[strings.TrimSpace(channelName)] = ChannelConfig{Type: strings.TrimSpace(ch.Type), Topic: strings.TrimSpace(ch.Topic)}
|
||||
}
|
||||
config.Channels = newChannels
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateConfig() error {
|
||||
if config.Channels == nil {
|
||||
config.Channels = make(map[string]ChannelConfig)
|
||||
}
|
||||
|
||||
if config.WebAddress == "" {
|
||||
return errors.New("WebAddress is required")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addPlaceholderchannels() {
|
||||
config.Channels["Lobby-Text"] = ChannelConfig{Type: "text"}
|
||||
config.Channels["Lobby-Voice"] = ChannelConfig{Type: "voice"}
|
||||
}
|
|
@ -5,36 +5,55 @@ import (
|
|||
"log"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"git.sr.ht/~tslocum/harmony/pkg/agent"
|
||||
"git.sr.ht/~tslocum/harmony/pkg/web"
|
||||
)
|
||||
|
||||
var (
|
||||
configPath string
|
||||
debugAddress string
|
||||
webAddress string
|
||||
webPath string
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.StringVar(&configPath, "config", "", "path to configuration file")
|
||||
flag.StringVar(&debugAddress, "debug-address", "", "address to serve debug info")
|
||||
flag.StringVar(&webAddress, "web-address", "", "address to serve web client")
|
||||
flag.StringVar(&webPath, "web-path", "/", "path to serve web client")
|
||||
flag.Parse()
|
||||
|
||||
if webAddress == "" {
|
||||
log.Fatal("Argument -web-address is required")
|
||||
}
|
||||
|
||||
if debugAddress != "" {
|
||||
go func() {
|
||||
log.Fatal(http.ListenAndServe(debugAddress, nil))
|
||||
}()
|
||||
}
|
||||
|
||||
w := web.NewWebInterface(webAddress, webPath)
|
||||
_ = w
|
||||
if configPath == "" {
|
||||
homedir, err := os.UserHomeDir()
|
||||
if err == nil && homedir != "" {
|
||||
configPath = path.Join(homedir, ".config", "harmony-server", "config.yaml")
|
||||
}
|
||||
}
|
||||
|
||||
err := readconfig(configPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read configuration file %s: %v\nSee HOSTING.md for information on how to configure harmony-server", configPath, err)
|
||||
}
|
||||
|
||||
w := web.NewWebInterface(config.WebAddress, config.WebPath)
|
||||
|
||||
for channelName, ch := range config.Channels {
|
||||
t := agent.ChannelText
|
||||
if ch.Type != "" && strings.ToLower(ch.Type)[0] == 'v' {
|
||||
t = agent.ChannelVoice
|
||||
}
|
||||
|
||||
w.AddChannel(t, channelName, ch.Topic)
|
||||
}
|
||||
|
||||
log.Println("harmony-server started")
|
||||
|
||||
_ = w
|
||||
select {}
|
||||
}
|
||||
|
|
2
go.mod
2
go.mod
|
@ -15,5 +15,5 @@ require (
|
|||
github.com/pkg/errors v0.8.1
|
||||
gitlab.com/golang-commonmark/markdown v0.0.0-20191127184510-91b5b3c99c19
|
||||
golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.4 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.7
|
||||
)
|
||||
|
|
2
go.sum
2
go.sum
|
@ -146,3 +146,5 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package agent
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
@ -8,9 +9,8 @@ type ChannelType int
|
|||
|
||||
const (
|
||||
ChannelUnknown ChannelType = 0
|
||||
ChannelAll ChannelType = 1
|
||||
ChannelText ChannelType = 2
|
||||
ChannelVoice ChannelType = 3
|
||||
ChannelText ChannelType = 1
|
||||
ChannelVoice ChannelType = 2
|
||||
)
|
||||
|
||||
type Channel struct {
|
||||
|
@ -36,4 +36,14 @@ type ChannelListing struct {
|
|||
Topic string
|
||||
}
|
||||
|
||||
type ChannelList map[int]*ChannelListing
|
||||
type ChannelList []*ChannelListing
|
||||
|
||||
func (c ChannelList) Len() int { return len(c) }
|
||||
func (c ChannelList) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c ChannelList) Less(i, j int) bool {
|
||||
if c[i] == nil || c[j] == nil {
|
||||
return c[j] != nil
|
||||
}
|
||||
|
||||
return c[i].Type < c[j].Type || (c[i].Type == c[j].Type && strings.ToLower(c[i].Name) < strings.ToLower(c[j].Name))
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
nav ul, aside ul {
|
||||
|
@ -16,11 +16,104 @@ nav ul, aside ul {
|
|||
margin: 0 0 0 5px;
|
||||
}
|
||||
|
||||
.content {
|
||||
grid-area: content;
|
||||
button {
|
||||
border: 1px solid black;
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
.buffer {
|
||||
grid-area: buffer;
|
||||
}
|
||||
|
||||
.mobileside {
|
||||
grid-area: mobileside;
|
||||
|
||||
overflow-x: auto;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.sidemenu > li {
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
.menulink {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menulinkfloat {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menulinkinline {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menulinkinactive {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.menulink, .menulink:link, .menulink:visited, .menulink:active {
|
||||
color: #0000EE;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.menulink:hover {
|
||||
color: #551A8B;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.menulinkinactive, .menulinkinactive:link, .menulinkinactive:visited, .menulinkinactive:active, .menulinkinactive:hover {
|
||||
color: #000000;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.status {
|
||||
grid-area: status;
|
||||
|
||||
overflow-x: auto;
|
||||
overflow-y: scroll;
|
||||
|
||||
border-bottom: 1px solid #cecece;
|
||||
}
|
||||
|
||||
.footer {
|
||||
grid-area: footer;
|
||||
}
|
||||
|
||||
.wrapper, .mobilewrapper {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: grid;
|
||||
grid-gap: 0;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: min-content 1fr min-content min-content;
|
||||
grid-template-areas: "header" "buffer" "status" "footer";
|
||||
}
|
||||
|
||||
.mobilewrapper {
|
||||
display: none;
|
||||
grid-gap: 0;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: min-content 1fr;
|
||||
grid-template-areas: "header" "mobileside";
|
||||
}
|
||||
|
||||
.sideleft {
|
||||
display: none;
|
||||
grid-area: sideleft;
|
||||
|
||||
overflow-x: auto;
|
||||
|
@ -30,6 +123,7 @@ nav ul, aside ul {
|
|||
}
|
||||
|
||||
.sideright {
|
||||
display: none;
|
||||
grid-area: sideright;
|
||||
|
||||
overflow-x: auto;
|
||||
|
@ -38,123 +132,100 @@ nav ul, aside ul {
|
|||
border-bottom: 1px solid #cecece;
|
||||
}
|
||||
|
||||
.status {
|
||||
grid-area: status;
|
||||
|
||||
overflow-x: auto;
|
||||
overflow-y: scroll;
|
||||
.mobilesideleft {
|
||||
border-bottom: 1px solid #cecece;
|
||||
}
|
||||
|
||||
.footer {
|
||||
grid-area: footer;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
margin: 0;
|
||||
width: 100vw;
|
||||
min-height: 100vh;
|
||||
|
||||
display: grid;
|
||||
grid-gap: 0;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: min-content min-content min-content 1fr min-content min-content;
|
||||
grid-template-areas: "sideleft" "sideright" "header" "content" "status" "footer";
|
||||
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
@media (min-width: 500px) {
|
||||
.wrapper {
|
||||
height: 100vh;
|
||||
grid-template-columns: 1fr 4fr;
|
||||
grid-template-rows: min-content 1fr 1fr min-content min-content;
|
||||
grid-template-areas: "sideleft header" "sideleft content" "sideright content" "status content" "footer footer";
|
||||
}
|
||||
|
||||
#voiceptt {
|
||||
height: 100px !important;
|
||||
}
|
||||
|
||||
.sideleft, .sideright, .status {
|
||||
min-width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 750px) {
|
||||
.wrapper {
|
||||
height: 100vh;
|
||||
grid-template-columns: 1fr 5fr 1fr;
|
||||
grid-template-rows: min-content 1fr min-content min-content;
|
||||
grid-template-areas: "sideleft header sideright" "sideleft content sideright" "status content sideright" "footer footer footer"
|
||||
}
|
||||
|
||||
#voiceptt {
|
||||
height: 100px !important;
|
||||
}
|
||||
|
||||
.sideleft, .sideright, .status {
|
||||
min-width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.sideleft, .sideright, .status, .content {
|
||||
.sideleft, .sideright, .mobilesideleft, .mobilesideright, #voicestatus, #userstatus, .buffer {
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
#voicestatus {
|
||||
border-bottom: 1px solid #cecece;
|
||||
}
|
||||
|
||||
.menucontainer, .mobilemenucontainer {
|
||||
display: inline-block;
|
||||
width: 37px;
|
||||
padding: 2px 7px 2px 2px;
|
||||
cursor: pointer;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.hamburger {
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
background-color: black;
|
||||
margin-top: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hamburgerfirst {
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 10px;
|
||||
|
||||
overflow-x: auto;
|
||||
overflow-y: scroll;
|
||||
|
||||
border-bottom: 1px solid #cecece;
|
||||
}
|
||||
|
||||
.content {
|
||||
.buffer {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
|
||||
border-bottom: 1px solid #cecece;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 1px solid black;
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
#mainheader {
|
||||
.mainheader {
|
||||
display: inline-block;
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
button, #subheader, #chathistory, #chatinput, .status {
|
||||
.subheader {
|
||||
font-size: 1em;
|
||||
color: #444444;
|
||||
}
|
||||
|
||||
button, .buffer, #chatinput, .status {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
#header, #status {
|
||||
vertical-align: center;
|
||||
.sideheading {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.header {
|
||||
border-right: 1px solid #cecece;
|
||||
.header, .header > *, .status, .status > * {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.headericon {
|
||||
display: inline-block;
|
||||
width: 25px;
|
||||
font-size: 1.15em;
|
||||
}
|
||||
|
||||
.widelinks a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#chathistory {
|
||||
.sideheading {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.buffer {
|
||||
overflow-x: auto;
|
||||
overflow-y: scroll;
|
||||
|
||||
min-height: 150px;
|
||||
|
||||
border: 1px solid #cecece;
|
||||
}
|
||||
|
||||
#chatinput {
|
||||
display:block;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
resize: none;
|
||||
|
@ -180,3 +251,61 @@ button, #subheader, #chathistory, #chatinput, .status {
|
|||
width: 27px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
@media (min-width: 500px) {
|
||||
.wrapper {
|
||||
height: 100vh;
|
||||
grid-template-columns: 1fr 4fr;
|
||||
grid-template-rows: min-content 1fr 1fr min-content min-content;
|
||||
grid-template-areas: "sideleft header" "sideleft buffer" "sideright buffer" "status buffer" "footer footer";
|
||||
}
|
||||
|
||||
#voiceptt {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.sideleft, .sideright, .status {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.sideleft {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.sideright {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.menucontainer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 750px) {
|
||||
.wrapper {
|
||||
height: 100vh;
|
||||
grid-template-columns: 1fr 5fr 1fr;
|
||||
grid-template-rows: min-content 1fr min-content min-content;
|
||||
grid-template-areas: "sideleft header sideright" "sideleft buffer sideright" "status buffer sideright" "footer footer footer"
|
||||
}
|
||||
|
||||
#voiceptt {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.sideleft, .sideright, .status {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.sideleft {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.sideright {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.menucontainer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ var ReconnectDelay = 0;
|
|||
var reconnectTimeout;
|
||||
var connected;
|
||||
|
||||
var chatprefix = "";
|
||||
var voice = false;
|
||||
var ptt = false;
|
||||
var nickname = "Anonymous";
|
||||
|
@ -33,13 +32,16 @@ var RTCICEServers = [{urls: 'stun:stun.l.google.com:19302'}];
|
|||
var audioTrack;
|
||||
|
||||
var shownPTTHelp = false;
|
||||
var muteOnMouseUp = true;
|
||||
var menuOpen = false;
|
||||
|
||||
var lastPing = 0;
|
||||
var userPing = 0;
|
||||
var userPing = -1;
|
||||
|
||||
var allChannels;
|
||||
var voiceChannel = 0;
|
||||
var allChannelsSorted;
|
||||
var channelBuffers = [];
|
||||
var textChannel = -1;
|
||||
var voiceChannel = -1;
|
||||
var disableVoice = false;
|
||||
var voiceCompatibilityNotice = "Sorry, your browser does not support WebRTC. This is required join voice channels. Firefox (recommended) and Chrome support WebRTC.";
|
||||
|
||||
|
@ -69,9 +71,8 @@ var MessageChannels = 131;
|
|||
var MessageUsers = 132;
|
||||
|
||||
var ChannelUnknown = 0;
|
||||
var ChannelAll = 1;
|
||||
var ChannelText = 2;
|
||||
var ChannelVoice = 3;
|
||||
var ChannelText = 1;
|
||||
var ChannelVoice = 2;
|
||||
|
||||
var tagsToReplace = {
|
||||
'&': '&',
|
||||
|
@ -79,6 +80,8 @@ var tagsToReplace = {
|
|||
'>': '>'
|
||||
};
|
||||
|
||||
var clickEventType = ((document.ontouchstart !== null) ? 'mousedown' : 'touchstart');
|
||||
|
||||
$(document).keydown(HandleInput);
|
||||
$(document).keyup(HandleInput);
|
||||
|
||||
|
@ -91,6 +94,9 @@ function HandleInput(e) {
|
|||
}
|
||||
|
||||
e.preventDefault();
|
||||
} else if (e.which >= 32 && e.which <= 126) {
|
||||
$("#chatinput").focus();
|
||||
// Do not prevent default
|
||||
} else if ((e.which == 13 || e.which == 176) && e.type == "keydown" && !e.shiftKey) {
|
||||
if (!$("#chatinput").is(":focus")) {
|
||||
return;
|
||||
|
@ -100,7 +106,7 @@ function HandleInput(e) {
|
|||
if ($("#chatinput").val() == "/debugstats") {
|
||||
enableStats();
|
||||
} else {
|
||||
w(MessageChat, $("#chatinput").val());
|
||||
writeSocket({T: MessageChat, C: textChannel, M: btoa($("#chatinput").val())});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,31 +124,19 @@ $(document).ready(function () {
|
|||
disableVoice = true;
|
||||
}
|
||||
|
||||
$("#voiceptt").on("touchstart", function (e) {
|
||||
muteOnMouseUp = false;
|
||||
|
||||
$("#voiceptt").on(clickEventType, function (e) {
|
||||
StartPTT();
|
||||
|
||||
$("#chatinput").focus();
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$("#voiceptt").on("mousedown", function (e) {
|
||||
muteOnMouseUp = true;
|
||||
|
||||
StartPTT();
|
||||
|
||||
$("#chatinput").focus();
|
||||
$("#voiceptt").on("mouseup touchend", function (e) {
|
||||
StopPTT();
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$(document).on("mouseup", function (e) {
|
||||
if (!muteOnMouseUp) {
|
||||
return;
|
||||
}
|
||||
|
||||
StopPTT();
|
||||
|
||||
return false;
|
||||
|
@ -154,18 +148,33 @@ $(document).ready(function () {
|
|||
return false;
|
||||
});
|
||||
|
||||
$("#chatinputcontainer,#voicepttcontainer").on("click touchstart", function (e) {
|
||||
$("#chatinputcontainer,#voicepttcontainer").on(clickEventType, function (e) {
|
||||
$("#chatinput").focus();
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$("#chatinput").on("touchstart", function (e) {
|
||||
$("#chatinput").on(clickEventType, function (e) {
|
||||
$("#chatinput").focus();
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$(".menucontainer,.mobilemenucontainer").on(clickEventType, function (e) {
|
||||
menuOpen = !menuOpen;
|
||||
if (menuOpen) {
|
||||
$(".wrapper").css('display', 'none');
|
||||
$(".mobilewrapper").css('display', 'grid');
|
||||
} else {
|
||||
$(".mobilewrapper").css('display', 'none');
|
||||
$(".wrapper").css('display', 'grid');
|
||||
}
|
||||
|
||||
updateHeader();
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
window.setInterval(() => {
|
||||
if (!webSocketReady()) {
|
||||
return;
|
||||
|
@ -195,7 +204,7 @@ function createPeerConnection(id) {
|
|||
pc.ontrack = function (event) {
|
||||
console.log(`PC ${id} onTrack ${event.streams.length} ${event.streams[0].id} ${event.streams[0].getAudioTracks().length}`);
|
||||
|
||||
if (id > 0) {
|
||||
if (id == 0) {
|
||||
pc.addTransceiver(event.streams[0].getAudioTracks()[0], {'direction': 'sendrecv'});
|
||||
}
|
||||
|
||||
|
@ -219,7 +228,7 @@ function createPeerConnection(id) {
|
|||
|
||||
if (id == 0) {
|
||||
navigator.mediaDevices.getUserMedia(RTCConstraints).then(localStream => {
|
||||
audioTracks = localStream.getAudioTracks();
|
||||
var audioTracks = localStream.getAudioTracks();
|
||||
if (audioTracks.length > 0) {
|
||||
console.log(`PC ${id} Using Audio device: ${audioTracks[0].label} - tracks available: ${audioTracks}`);
|
||||
}
|
||||
|
@ -231,7 +240,7 @@ function createPeerConnection(id) {
|
|||
|
||||
pc.createOffer(RTCOfferOptions)
|
||||
.then(onRTCDescription(id), onRTCDescriptionError(id));
|
||||
}).catch(Log);
|
||||
}).catch(FatalAudioError);
|
||||
} else {
|
||||
pc.createOffer(RTCOfferOptions)
|
||||
.then(onRTCDescription(id), onRTCDescriptionError(id));
|
||||
|
@ -250,6 +259,10 @@ function onRTCDescription(id) {
|
|||
return function (desc) {
|
||||
console.log(`PC ${id} Offer received \n${desc.sdp}`);
|
||||
|
||||
if (peerConnections.length < 3) {
|
||||
peerConnections.push(createPeerConnection(peerConnections.length));
|
||||
}
|
||||
|
||||
if (peerConnections.length < id) {
|
||||
return;
|
||||
}
|
||||
|
@ -272,7 +285,7 @@ function Connect() {
|
|||
return;
|
||||
}
|
||||
|
||||
userPing = 0;
|
||||
userPing = -1;
|
||||
userListStatus = 'Connecting...';
|
||||
updateStatus();
|
||||
|
||||
|
@ -295,14 +308,17 @@ function Connect() {
|
|||
socket.onerror = function (e) {
|
||||
Log("Connection error");
|
||||
console.log(e);
|
||||
QuitVoice();
|
||||
};
|
||||
socket.onopen = function (e) {
|
||||
if (reconnectTimeout != null) {
|
||||
clearTimeout(reconnectTimeout);
|
||||
}
|
||||
|
||||
// TODO Update after receiving channels
|
||||
updateHeader();
|
||||
|
||||
userListStatus = "";
|
||||
updateStatus();
|
||||
|
||||
w(MessageNick, nickname);
|
||||
|
||||
|
@ -320,6 +336,8 @@ function Connect() {
|
|||
p.M = atob(p.M);
|
||||
}
|
||||
|
||||
console.log("Read ", p);
|
||||
|
||||
if (p.T == MessagePong) {
|
||||
if (parseInt(p.M, 10) == lastPing) {
|
||||
userPing = Date.now() - lastPing;
|
||||
|
@ -333,10 +351,6 @@ function Connect() {
|
|||
|
||||
var pc = peerConnections[p.PC];
|
||||
pc.setRemoteDescription(new RTCSessionDescription({type: 'answer', sdp: p.M}));
|
||||
|
||||
if (peerConnections.length < 3) {
|
||||
peerConnections.push(createPeerConnection(peerConnections.length));
|
||||
}
|
||||
} else if (p.T == MessageConnect) {
|
||||
if (p.N === undefined) {
|
||||
return;
|
||||
|
@ -372,9 +386,14 @@ function Connect() {
|
|||
return;
|
||||
}
|
||||
|
||||
Log("<" + escapeEntities(p.N) + "> " + p.M);
|
||||
LogChannel(p.C, "<" + escapeEntities(p.N) + "> " + p.M);
|
||||
} else if (p.T == MessageChannels) {
|
||||
allChannels = JSON.parse(p.M);
|
||||
allChannelsSorted = JSON.parse(p.M);
|
||||
|
||||
allChannels = [];
|
||||
for (let i = 0; i < allChannelsSorted.length; i++) {
|
||||
allChannels[allChannelsSorted[i].ID] = allChannelsSorted[i];
|
||||
}
|
||||
|
||||
updateChannelList();
|
||||
} else if (p.T == MessageUsers) {
|
||||
|
@ -390,35 +409,32 @@ function Connect() {
|
|||
|
||||
userList = userListNew;
|
||||
|
||||
var userListVoiceNew = '';
|
||||
|
||||
var userListVoiceNew;
|
||||
var u = JSON.parse(p.M);
|
||||
for (let i in allChannels) {
|
||||
$("#userscontainer" + i).remove();
|
||||
}
|
||||
for (let ci in allChannels) {
|
||||
$(".channelvoiceusers" + ci).html('');
|
||||
|
||||
for (let i = 0; i < u.length; i++) {
|
||||
if (u[i].C == 0) {
|
||||
continue;
|
||||
userListVoiceNew = '<ul>';
|
||||
;
|
||||
for (let i = 0; i < u.length; i++) {
|
||||
if (u[i].C != ci) {
|
||||
continue;
|
||||
}
|
||||
|
||||
userListVoiceNew += '<li>';
|
||||
if (voice && u[i].C == voiceChannel) {
|
||||
userListVoiceNew += '<span id="voiceindicator' + u[i].ID + '"><div class="voiceinactive">🔈</div></span> ';
|
||||
}
|
||||
userListVoiceNew += u[i].N + '</li>';
|
||||
}
|
||||
|
||||
if ($("#userscontainer" + u[i].C).length == 0) {
|
||||
$('<li/>')
|
||||
.attr('id', 'userscontainer' + u[i].C)
|
||||
.insertAfter($("#channelvoice" + u[i].C));
|
||||
if (userListVoiceNew != '<ul>') {
|
||||
userListVoiceNew += '<li style="margin: 0 !important;height: 7px;"> </li></ul>';
|
||||
|
||||
$('<ul/>')
|
||||
.attr('id', 'users' + u[i].C)
|
||||
.appendTo($("#userscontainer" + u[i].C));
|
||||
$(".channelvoiceusers" + ci).each(function (index) {
|
||||
$(this).html(userListVoiceNew);
|
||||
});
|
||||
}
|
||||
|
||||
userListVoiceNew = '<li>';
|
||||
if (voice && u[i].C == voiceChannel) {
|
||||
userListVoiceNew += '<span id="voiceindicator' + u[i].ID + '"><div class="voiceinactive">🔈</div></span> ';
|
||||
}
|
||||
userListVoiceNew += u[i].N + '</li>';
|
||||
|
||||
$("#users" + u[i].C).append(userListVoiceNew);
|
||||
}
|
||||
|
||||
updateUserList();
|
||||
|
@ -439,6 +455,8 @@ function Connect() {
|
|||
socket.onclose = function (e) {
|
||||
connected = false;
|
||||
Log("Disconnected");
|
||||
QuitVoice();
|
||||
|
||||
if (ReconnectDelay < 0 || reconnectTimeout != null) {
|
||||
return;
|
||||
}
|
||||
|
@ -477,17 +495,41 @@ function waitForSocketConnection(socket, callback) {
|
|||
}
|
||||
|
||||
function Log(msg) {
|
||||
if (msg.charAt(0) != '<' && msg.charAt(0) != '&' && msg.charAt(1) != '&' && msg.charAt(2) != 't' && msg.charAt(3) != ';') {
|
||||
LogChannel(textChannel, msg);
|
||||
}
|
||||
|
||||
function LogChannel(ch, msg) {
|
||||
if (msg.charAt && msg.charAt(0) != '<' && msg.charAt(0) != '&' && msg.charAt(1) != '&' && msg.charAt(2) != 't' && msg.charAt(3) != ';') {
|
||||
msg = '* ' + msg;
|
||||
}
|
||||
|
||||
var date = new Date();
|
||||
$('#chathistory').append(chatprefix + date.getHours() + ":" + (date.getMinutes() < 10 ? "0" : "") + date.getMinutes() + " " + msg + "\n")
|
||||
$('#chathistory').scrollTop($('#chathistory').prop("scrollHeight"));
|
||||
msg = date.getHours() + ":" + (date.getMinutes() < 10 ? "0" : "") + date.getMinutes() + " " + msg + "\n";
|
||||
|
||||
if (chatprefix == "") {
|
||||
chatprefix = "<br>";
|
||||
if (ch >= 0) {
|
||||
if (channelBuffers[ch] === undefined) {
|
||||
channelBuffers[ch] = msg;
|
||||
} else {
|
||||
channelBuffers[ch] += '<br>' + msg;
|
||||
}
|
||||
|
||||
if (ch != textChannel) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ($('.buffer').html().trim() != "") {
|
||||
$('.buffer').append("<br>");
|
||||
}
|
||||
$('.buffer').append(msg);
|
||||
$('.buffer').scrollTop($('.buffer').prop("scrollHeight"));
|
||||
}
|
||||
|
||||
function FatalAudioError(msg) {
|
||||
QuitVoice();
|
||||
|
||||
alert(msg);
|
||||
Log(msg);
|
||||
}
|
||||
|
||||
function StartPTT() {
|
||||
|
@ -499,8 +541,11 @@ function StartPTT() {
|
|||
|
||||
$("#voiceptt").html('Transmitting...');
|
||||
|
||||
var sender = peerConnections[0].getSenders()[0];
|
||||
sender.replaceTrack(audioTrack);
|
||||
var senders = peerConnections[0].getSenders();
|
||||
if (senders.length == 0) {
|
||||
return;
|
||||
}
|
||||
senders[0].replaceTrack(audioTrack);
|
||||
|
||||
updateStatus();
|
||||
}
|
||||
|
@ -514,12 +559,45 @@ function StopPTT() {
|
|||
|
||||
$("#voiceptt").html('Push-To-Talk');
|
||||
|
||||
var sender = peerConnections[0].getSenders()[0];
|
||||
sender.replaceTrack(null);
|
||||
var senders = peerConnections[0].getSenders();
|
||||
if (senders.length == 0) {
|
||||
return;
|
||||
}
|
||||
senders[0].replaceTrack(null);
|
||||
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
function ToggleVoiceChannel(channelID) {
|
||||
if (voiceChannel == channelID) {
|
||||
QuitVoice();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (disableVoice) {
|
||||
alert(voiceCompatibilityNotice);
|
||||
return false;
|
||||
}
|
||||
|
||||
voiceChannel = channelID;
|
||||
JoinVoice(voiceChannel);
|
||||
return false;
|
||||
}
|
||||
|
||||
function JoinText(channelID) {
|
||||
textChannel = parseInt(channelID);
|
||||
updateHeader();
|
||||
updateBuffer();
|
||||
|
||||
for (let i in allChannels) {
|
||||
$(".jointext" + i).each(function (index) {
|
||||
$(this).removeClass('menulink');
|
||||
$(this).removeClass('menulinkinactive');
|
||||
$(this).addClass(i != textChannel ? 'menulink' : 'menulinkinactive');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function JoinVoice(channelID) {
|
||||
if (!webSocketReady()) {
|
||||
return;
|
||||
|
@ -527,7 +605,14 @@ function JoinVoice(channelID) {
|
|||
|
||||
voiceChannel = parseInt(channelID);
|
||||
for (let i in allChannels) {
|
||||
$("#joinvoice" + i).html(i != voiceChannel ? 'Join' : 'Quit');
|
||||
$(".joinvoice" + i).each(function (index) {
|
||||
$(this).removeClass('menulink');
|
||||
$(this).removeClass('menulinkinactive');
|
||||
$(this).addClass(i != voiceChannel ? 'menulink' : 'menulinkinactive');
|
||||
});
|
||||
$("#voicestatus").each(function (index) {
|
||||
$(this).css('display', 'block');
|
||||
});
|
||||
}
|
||||
|
||||
voice = true;
|
||||
|
@ -536,12 +621,19 @@ function JoinVoice(channelID) {
|
|||
peerConnections.push(createPeerConnection(0));
|
||||
}
|
||||
|
||||
socket.send(JSON.stringify({T: MessageJoin, C: parseInt(channelID)}));
|
||||
writeSocket({T: MessageJoin, C: parseInt(channelID)});
|
||||
}
|
||||
|
||||
function QuitVoice() {
|
||||
for (let i in allChannels) {
|
||||
$("#joinvoice" + i).html('Join');
|
||||
$(".joinvoice" + i).each(function (index) {
|
||||
$(this).removeClass('menulink');
|
||||
$(this).removeClass('menulinkinactive');
|
||||
$(this).addClass('menulink');
|
||||
});
|
||||
$("#voicestatus").each(function (index) {
|
||||
$(this).css('display', 'none');
|
||||
});
|
||||
}
|
||||
|
||||
voice = false;
|
||||
|
@ -559,9 +651,31 @@ function QuitVoice() {
|
|||
updateStatus();
|
||||
}
|
||||
|
||||
function updateHeader() {
|
||||
if (menuOpen) {
|
||||
$(".mainheader").html('Menu');
|
||||
$(".subheader").html('');
|
||||
} else if (allChannels !== undefined) {
|
||||
$(".mainheader").html(allChannels[textChannel].Name);
|
||||
$(".subheader").html(allChannels[textChannel].Topic);
|
||||
} else {
|
||||
$(".mainheader").html('Loading...');
|
||||
$(".subheader").html('');
|
||||
}
|
||||
}
|
||||
|
||||
function updateBuffer() {
|
||||
if (textChannel == -1 || channelBuffers[textChannel] === undefined) {
|
||||
$('.buffer').html('');
|
||||
return;
|
||||
}
|
||||
|
||||
$('.buffer').html(channelBuffers[textChannel]);
|
||||
}
|
||||
|
||||
function updateStatus() {
|
||||
var out = '';
|
||||
if (userPing > 0) {
|
||||
if (userPing >= 0) {
|
||||
out += userPing + 'ms ping';
|
||||
}
|
||||
|
||||
|
@ -574,85 +688,74 @@ function updateStatus() {
|
|||
}
|
||||
|
||||
$('#userstatus').html(out);
|
||||
|
||||
$('#togglevoice').html(peerConnections.length > 0 ? 'Quit' : 'Join');
|
||||
}
|
||||
|
||||
function updateChannelList() {
|
||||
var c;
|
||||
var channelListNew = '<ul>';
|
||||
var channelListNew = '<ul class="sidemenu">';
|
||||
|
||||
channelListNew += '<li style="margin-bottom: 10px;"><div class="headericon">📰</div> Text Channels</li>';
|
||||
for (let i in allChannels) {
|
||||
c = allChannels[i];
|
||||
for (let i in allChannelsSorted) {
|
||||
c = allChannelsSorted[i];
|
||||
|
||||
if (c.Type != ChannelAll && c.Type != ChannelText) {
|
||||
if (c.Type != ChannelText) {
|
||||
continue;
|
||||
}
|
||||
|
||||
channelListNew += '<li id="channeltext' + c.ID + '">' + c.Name + '</li>';
|
||||
if (textChannel == -1) {
|
||||
JoinText(parseInt(c.ID));
|
||||
}
|
||||
|
||||
channelListNew += '<li class="sideheading channeltext' + c.ID + '">';
|
||||
channelListNew += '<a href="#" onclick="JoinText(' + parseInt(c.ID) + ');return false;" ontouchstart="JoinText(' + parseInt(c.ID) + ');return false;" class="menulink jointext' + c.ID + '">📰 ';
|
||||
channelListNew += c.Name;
|
||||
channelListNew += '</a>';
|
||||
channelListNew += '</li>';
|
||||
}
|
||||
|
||||
channelListNew += '<li style="margin-bottom: 10px;"> </li><li style="margin-bottom: 10px;"><div class="headericon">🔊</div> Voice Channels</li>';
|
||||
for (let i in allChannels) {
|
||||
c = allChannels[i];
|
||||
channelListNew += '<li> </li>';
|
||||
for (let i in allChannelsSorted) {
|
||||
c = allChannelsSorted[i];
|
||||
|
||||
if (c.Type != ChannelAll && c.Type != ChannelVoice) {
|
||||
if (c.Type != ChannelVoice) {
|
||||
continue;
|
||||
}
|
||||
|
||||
channelListNew += '<li id="channelvoice' + c.ID + '">' + c.Name + ' <a href="#" id="joinvoice' + c.ID + '">' + (parseInt(c.ID) != voiceChannel ? 'Join' : 'Quit') + '</a></li>';
|
||||
channelListNew += '<li class="sideheading channelvoice' + c.ID + '">';
|
||||
channelListNew += '<a href="#" onclick="JoinVoice(' + parseInt(c.ID) + ');return false;" ontouchstart="JoinVoice(' + parseInt(c.ID) + ');return false;" class="menulink joinvoice' + c.ID + '">🔊 ';
|
||||
channelListNew += c.Name;
|
||||
channelListNew += '</a>';
|
||||
channelListNew += '</li>';
|
||||
channelListNew += '<li class="channelvoiceusers' + c.ID + '"></li>';
|
||||
}
|
||||
|
||||
channelListNew += '</ul>';
|
||||
|
||||
channelList = channelListNew;
|
||||
|
||||
$("#sideleft").html(channelList);
|
||||
|
||||
for (let i in allChannels) {
|
||||
c = allChannels[i];
|
||||
|
||||
if (c.Type != ChannelAll && c.Type != ChannelVoice) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$("#joinvoice" + c.ID).on("click touchstart", function (e) {
|
||||
if (voiceChannel == parseInt($(this).attr('id').substring(9))) {
|
||||
QuitVoice();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (disableVoice) {
|
||||
alert(voiceCompatibilityNotice);
|
||||
return false;
|
||||
}
|
||||
|
||||
voiceChannel = parseInt($(this).attr('id').substring(9));
|
||||
JoinVoice(voiceChannel);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
$(".sideleft,.mobilesideleft").html(channelList);
|
||||
JoinText(textChannel);
|
||||
}
|
||||
|
||||
function updateUserList() {
|
||||
$('#sideright').html(userList);
|
||||
$('.sideright,.mobilesideright').html(userList);
|
||||
}
|
||||
|
||||
function w(t, m) {
|
||||
if (!webSocketReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
socket.send(JSON.stringify({T: t, M: btoa(m)}));
|
||||
writeSocket({T: t, M: btoa(m)});
|
||||
}
|
||||
|
||||
function wpc(pc, t, m) {
|
||||
writeSocket({PC: parseInt(pc), T: t, M: btoa(m)});
|
||||
}
|
||||
|
||||
function writeSocket(p) {
|
||||
console.log("Write ", p);
|
||||
|
||||
if (!webSocketReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
socket.send(JSON.stringify({PC: parseInt(pc), T: t, M: btoa(m)}));
|
||||
socket.send(JSON.stringify(p))
|
||||
}
|
||||
|
||||
function escapeEntitiesCallback(tag) {
|
||||
|
|
|
@ -9,17 +9,23 @@
|
|||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<nav class="sideleft" id="sideleft">
|
||||
<nav class="sideleft">
|
||||
</nav>
|
||||
<article class="content" id="chathistory">
|
||||
<article class="buffer">
|
||||
</article>
|
||||
<aside class="sideright" id="sideright">
|
||||
<aside class="sideright">
|
||||
</aside>
|
||||
<div class="header" id="header">
|
||||
<div style="display: inline-block;float: left;" id="mainheader">harmony</div>
|
||||
<div style="display: inline-block;float: right;" id="subheader">Loading...</div>
|
||||
<div class="header">
|
||||
<div class="menucontainer">
|
||||
<div class="hamburger hamburgerfirst"></div>
|
||||
<div class="hamburger"></div>
|
||||
<div class="hamburger"></div>
|
||||
</div>
|
||||
<span class="mainheader">Loading...</span>
|
||||
<span class="subheader"></span>
|
||||
</div>
|
||||
<div class="status">
|
||||
<div id="voicestatus" style="display: none;"><a href="#" class="menulink menulinkinline quitvoice" onclick="QuitVoice();return false;">Quit voice chat</a></div>
|
||||
<div id="userstatus">Loading...</div>
|
||||
</div>
|
||||
<footer class="footer">
|
||||
|
@ -31,6 +37,22 @@
|
|||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<div class="mobilewrapper">
|
||||
<div class="header">
|
||||
<div class="mobilemenucontainer">
|
||||
<div class="hamburger hamburgerfirst"></div>
|
||||
<div class="hamburger"></div>
|
||||
<div class="hamburger"></div>
|
||||
</div>
|
||||
<span class="mainheader">Loading...</span>
|
||||
<span class="subheader"></span>
|
||||
</div>
|
||||
<nav class="mobileside">
|
||||
<aside class="mobilesideleft"></aside>
|
||||
<aside class="mobilesideright"></aside>
|
||||
</nav>
|
||||
</nav>
|
||||
</div>
|
||||
<div style="display: none" id="hidden">
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
@ -98,14 +98,17 @@ func (w *WebInterface) createChannels() {
|
|||
w.ChannelsLock.Lock()
|
||||
defer w.ChannelsLock.Unlock()
|
||||
|
||||
ch := agent.NewChannel(w.nextChannelID(), agent.ChannelAll)
|
||||
ch.Name = "lobby"
|
||||
ch.Topic = "harmony demo server"
|
||||
w.Channels[ch.ID] = ch
|
||||
// TODO Load channels from database
|
||||
}
|
||||
|
||||
func (w *WebInterface) AddChannel(t agent.ChannelType, name string, topic string) {
|
||||
w.ChannelsLock.Lock()
|
||||
defer w.ChannelsLock.Unlock()
|
||||
|
||||
ch := agent.NewChannel(w.nextChannelID(), t)
|
||||
ch.Name = name
|
||||
ch.Topic = topic
|
||||
|
||||
ch = agent.NewChannel(w.nextChannelID(), agent.ChannelAll)
|
||||
ch.Name = "alt"
|
||||
ch.Topic = "alt demo channel"
|
||||
w.Channels[ch.ID] = ch
|
||||
}
|
||||
|
||||
|
@ -203,7 +206,7 @@ func (w *WebInterface) handleRead(c *agent.Client) {
|
|||
w.ClientsLock.Lock()
|
||||
|
||||
for _, wc := range w.Clients {
|
||||
wc.Out <- &agent.Message{S: c.ID, N: c.Name, T: agent.MessageChat, M: msg.M}
|
||||
wc.Out <- &agent.Message{S: c.ID, C: msg.C, N: c.Name, T: agent.MessageChat, M: msg.M}
|
||||
}
|
||||
|
||||
w.ClientsLock.Unlock()
|
||||
|
@ -384,11 +387,13 @@ func (w *WebInterface) updateUserList() {
|
|||
}
|
||||
|
||||
func (w *WebInterface) sendChannelList(c *agent.Client) {
|
||||
var channelList = make(agent.ChannelList)
|
||||
var channelList agent.ChannelList
|
||||
for _, ch := range w.Channels {
|
||||
channelList[ch.ID] = &agent.ChannelListing{ID: ch.ID, Type: ch.Type, Name: ch.Name, Topic: ch.Topic}
|
||||
channelList = append(channelList, &agent.ChannelListing{ID: ch.ID, Type: ch.Type, Name: ch.Name, Topic: ch.Topic})
|
||||
}
|
||||
|
||||
sort.Sort(channelList)
|
||||
|
||||
msg := agent.Message{T: agent.MessageChannels}
|
||||
|
||||
var err error
|
||||
|
|
Loading…
Reference in New Issue