Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c9bacba
commit 2cb956e
Showing
15 changed files
with
476 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,21 @@ | ||
# draw | ||
Real-time collaborative whiteboard on the web | ||
|
||
draw is a tiny in-memory collaborative whiteboard. It's built on... | ||
|
||
- [Gorilla WebSocket](https://github.com/gorilla/websocket) for initiating and managing WebSocket connections | ||
- [Torus](https://github.com/thesephist/torus) as a light frontend UI library | ||
- My own [blocks.css](https://thesephist.github.io/blocks.css/) to add some spice to the UI design | ||
|
||
## Deploy | ||
|
||
Deployment is managed by systemd. Copy the `draw.service` file to `/etc/systemd/system/draw.service` and update: | ||
|
||
- replace `draw-user` with your Linux user | ||
- replace `/home/draw-user/draw` with your working directory (path to repository or a copy of `static/`) | ||
|
||
Then start draw as a service: | ||
|
||
```sh | ||
systemctl daemon-reload # reload systemd script | ||
systemctl start draw # start draw server as a service | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/thesephist/draw/pkg/draw" | ||
) | ||
|
||
func main() { | ||
fmt.Println("Starting draw server...") | ||
|
||
draw.StartServer() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/thesephist/draw/pkg/draw" | ||
) | ||
|
||
func main() { | ||
fmt.Println("Starting draw server...") | ||
|
||
draw.StartServer() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
[Unit] | ||
Description=draw server | ||
ConditionPathExists=/home/draw-user/draw/draw | ||
After=network.target | ||
|
||
[Service] | ||
Type=simple | ||
User=draw-user | ||
LimitNOFILE=256 | ||
|
||
Restart=on-failure | ||
RestartSec=10 | ||
StartLimitIntervalSec=60 | ||
|
||
WorkingDirectory=/home/draw-user/draw/ | ||
ExecStart=/home/draw-user/draw/draw | ||
|
||
# make sure log directory exists and owned by syslog | ||
PermissionsStartOnly=true | ||
ExecStartPre=/bin/mkdir -p /var/log/draw | ||
ExecStartPre=/bin/chown syslog:adm /var/log/draw | ||
ExecStartPre=/bin/chmod 755 /var/log/draw | ||
StandardOutput=syslog | ||
StandardError=syslog | ||
SyslogIdentifier=draw | ||
|
||
[Install] | ||
WantedBy=multi-user.target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
module github.com/thesephist/plume | ||
|
||
go 1.13 | ||
|
||
require ( | ||
github.com/google/uuid v1.1.1 // indirect | ||
github.com/gorilla/mux v1.7.3 | ||
github.com/gorilla/websocket v1.4.1 | ||
github.com/mailgun/mailgun-go/v3 v3.6.3 // indirect | ||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= | ||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= | ||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= | ||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= | ||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y= | ||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= | ||
github.com/go-chi/chi v4.0.0+incompatible h1:SiLLEDyAkqNnw+T/uDTf3aFB9T4FTrwMpuYrgaRcnW4= | ||
github.com/go-chi/chi v4.0.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= | ||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= | ||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= | ||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= | ||
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= | ||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | ||
github.com/mailgun/mailgun-go/v3 v3.6.3 h1:dkPP10w15Igt0uM313CStG9+gd/XqdiqO0862zNAXbs= | ||
github.com/mailgun/mailgun-go/v3 v3.6.3/go.mod h1:ZjVnH8S0dR2BLjvkZc/rxwerdcirzlA12LQDuGAadR0= | ||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= | ||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= | ||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= | ||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= | ||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package draw | ||
|
||
// Client represents an abstract client of a chat room | ||
type Client struct { | ||
User User | ||
Room *Room | ||
OnMessage func(Message) | ||
|
||
receiver chan Message | ||
} | ||
|
||
// Send sends a new message to the room to which the client | ||
// cl belongs, under the client user's name. | ||
func (cl *Client) Send(text string) error { | ||
if cl.Room == nil { | ||
return Error{"client is not in a room yet"} | ||
} | ||
|
||
cl.Room.Broadcast(Message{ | ||
Type: msgText, | ||
User: cl.User, | ||
Text: text, | ||
}) | ||
|
||
return nil | ||
} | ||
|
||
// Leave lets the client leave the room and cleans up. | ||
func (cl *Client) Leave() error { | ||
if cl.Room == nil { | ||
return Error{"client is not in a room yet"} | ||
} | ||
|
||
delete(cl.Room.clientReceivers, cl) | ||
close(cl.receiver) | ||
cl.Room = nil | ||
|
||
return nil | ||
} | ||
|
||
// StartListening enters an indefinite loop listening | ||
// for new messages for the client and responds with cl.OnMessage. | ||
func (cl *Client) StartListening() { | ||
for { | ||
msg, open := <-cl.receiver | ||
if !open { | ||
return | ||
} | ||
|
||
if cl.OnMessage == nil { | ||
continue | ||
} | ||
|
||
cl.OnMessage(msg) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package draw | ||
|
||
// Error represents any error originating | ||
// from unexpected states in the draw server. | ||
type Error struct { | ||
reason string | ||
} | ||
|
||
func (err Error) Error() string { | ||
return err.reason | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package draw | ||
|
||
const ( | ||
// msgHello is used to first connect and request authentication | ||
msgHello = iota | ||
// msgText is used for all normal text messages | ||
msgText | ||
|
||
// msgAuth represents an attempt to authenticate with a token | ||
msgAuth | ||
// msgAuthAck is sent by the server to approve authentication attempt | ||
msgAuthAck | ||
// msgAuthRst is sent by the server to reject authentication attemp | ||
msgAuthRst | ||
|
||
// msgMayNotEnter is sent by the server to reject entry attempt, usually | ||
// means the username is taken | ||
msgMayNotEnter | ||
|
||
// In the future, we can support things like presence | ||
// by using additional codes like MsgTypingStart/Stop | ||
) | ||
|
||
// Message represents any atomic communication between a draw client | ||
// and server. | ||
type Message struct { | ||
Type int `json:"type"` | ||
User User `json:"user"` | ||
Text string `json:"text"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package draw | ||
|
||
import ( | ||
"strings" | ||
) | ||
|
||
// Room represents a collection of draw clients all | ||
// sending each other messages. | ||
type Room struct { | ||
Sender chan<- Message | ||
// map of usernames to emails | ||
verifiedNames map[string]string | ||
clientReceivers map[*Client]chan Message | ||
} | ||
|
||
// NewRoom allocates, creates, and returns a new Room | ||
// ready to be used | ||
func NewRoom() *Room { | ||
return &Room{ | ||
Sender: make(chan Message), | ||
verifiedNames: make(map[string]string), | ||
clientReceivers: make(map[*Client]chan Message), | ||
} | ||
} | ||
|
||
// Enter creates a new Client for a given user ready | ||
// to be used | ||
func (rm *Room) Enter(u User) *Client { | ||
receiver := make(chan Message) | ||
client := Client{ | ||
User: u, | ||
Room: rm, | ||
receiver: receiver, | ||
} | ||
|
||
rm.verifiedNames[strings.ToLower(u.Name)] = u.Email | ||
rm.clientReceivers[&client] = receiver | ||
go client.StartListening() | ||
|
||
return &client | ||
} | ||
|
||
// CanEnter reports whether a user should be allowed in a room. | ||
// A user may not enter a room if another user with a different email | ||
// but a matching username is already inside. | ||
func (rm *Room) CanEnter(u User) bool { | ||
existingEmail, prs := rm.verifiedNames[strings.ToLower(u.Name)] | ||
if prs { | ||
return u.Email == existingEmail | ||
} | ||
|
||
return true | ||
} | ||
|
||
// Broadcast sends a new Message to every client | ||
// in the Room | ||
func (rm *Room) Broadcast(msg Message) { | ||
for _, receiver := range rm.clientReceivers { | ||
receiver <- msg | ||
} | ||
} |
Oops, something went wrong.