Skip to content

Commit bd1859b

Browse files
authored
feat: persist irc connections between websocket disconnects (#164)
- currently, when the websocket connection is dropped, the irc connection is closed immediately. this change makes it so that after the websocket connection closed, the user has 3 minutes to reconnect before we close the irc connection. - the 3 minute 'self-destruct' is only triggered when the websocket is disconnected and is disabled once there is another connection - this should help with certain devices (such as iPad) where switching apps / tabs seems to close the websocket. i believe that the IRC server is sensitive to clients connecting/disconnecting too often and will ban users.
1 parent c2c8e01 commit bd1859b

File tree

5 files changed

+80
-29
lines changed

5 files changed

+80
-29
lines changed

.devcontainer/devcontainer.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,19 @@
1212
"remoteUser": "root",
1313
"features": {
1414
"ghcr.io/devcontainers/features/go:1": {},
15-
"ghcr.io/devcontainers/features/node:1": {}
15+
"ghcr.io/devcontainers/features/node:1": {},
16+
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
1617
},
1718
"customizations": {
1819
"vscode": {
19-
"extensions": ["golang.go", "heybourn.headwind", "esbenp.prettier-vscode", "bradlc.vscode-tailwindcss", "ms-vscode.makefile-tools", "redhat.vscode-yaml"]
20+
"extensions": [
21+
"golang.go",
22+
"heybourn.headwind",
23+
"esbenp.prettier-vscode",
24+
"bradlc.vscode-tailwindcss",
25+
"ms-vscode.makefile-tools",
26+
"redhat.vscode-yaml"
27+
]
2028
}
2129
}
2230
}

server/client.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ type Client struct {
6060
// reads from this goroutine.
6161
func (server *server) readPump(c *Client) {
6262
defer func() {
63-
c.irc.Disconnect()
6463
c.conn.Close()
6564
server.unregister <- c
6665
}()

server/routes.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"encoding/json"
77
"errors"
88
"fmt"
9-
"github.com/evan-buss/openbooks/irc"
109
"io/fs"
1110
"log"
1211
"net/http"
@@ -17,6 +16,8 @@ import (
1716
"strings"
1817
"time"
1918

19+
"github.com/evan-buss/openbooks/irc"
20+
2021
"github.com/go-chi/chi/v5"
2122
"github.com/google/uuid"
2223
)
@@ -90,9 +91,6 @@ func (server *server) serveWs() http.HandlerFunc {
9091
client.log.Println("New client created.")
9192

9293
server.register <- client
93-
94-
go server.writePump(client)
95-
go server.readPump(client)
9694
}
9795
}
9896

server/server.go

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,17 +103,47 @@ func Start(config Config) {
103103
// The client hub is to be run in a goroutine and handles management of
104104
// websocket client registrations.
105105
func (server *server) startClientHub(ctx context.Context) {
106+
type selfDestructor struct {
107+
timer *time.Timer
108+
client *Client
109+
}
110+
111+
selfDestructors := make(map[uuid.UUID]selfDestructor)
112+
106113
for {
107114
select {
108115
case client := <-server.register:
116+
if destructor, ok := selfDestructors[client.uuid]; ok {
117+
destructor.timer.Stop()
118+
server.log.Printf("Client %s reconnected\n", client.uuid.String())
119+
120+
// Update the existing client's websocket connection to the new one
121+
destructor.client.conn = client.conn
122+
client = destructor.client
123+
124+
delete(selfDestructors, client.uuid)
125+
}
126+
109127
server.clients[client.uuid] = client
128+
go server.writePump(client)
129+
go server.readPump(client)
130+
110131
case client := <-server.unregister:
111-
if _, ok := server.clients[client.uuid]; ok {
112-
_, cancel := context.WithCancel(client.ctx)
113-
close(client.send)
114-
cancel()
115-
delete(server.clients, client.uuid)
116-
}
132+
// Keep the client and IRC connection alive for 3 minutes in case the client reconnects
133+
timer := time.AfterFunc(time.Minute*3, func() {
134+
if _, ok := selfDestructors[client.uuid]; ok {
135+
client.irc.Disconnect()
136+
_, cancel := context.WithCancel(client.ctx)
137+
close(client.send)
138+
cancel()
139+
delete(selfDestructors, client.uuid)
140+
server.log.Printf("Client %s self-destructed\n", client.uuid.String())
141+
}
142+
})
143+
144+
selfDestructors[client.uuid] = selfDestructor{timer, client}
145+
delete(server.clients, client.uuid)
146+
server.log.Printf("Client %s disconnected\n", client.uuid.String())
117147
case <-ctx.Done():
118148
for _, client := range server.clients {
119149
_, cancel := context.WithCancel(client.ctx)

server/websocket_requests.go

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -49,34 +49,50 @@ func (server *server) routeMessage(message Request, c *Client) {
4949

5050
// handle ConnectionRequests and either connect to the server or do nothing
5151
func (c *Client) startIrcConnection(server *server) {
52-
err := core.Join(c.irc, server.config.Server, server.config.EnableTLS)
53-
if err != nil {
54-
c.log.Println(err)
55-
c.send <- newErrorResponse("Unable to connect to IRC server.")
56-
return
57-
}
52+
// The IRC connection could be re-used if it is already connected
53+
if !c.irc.IsConnected() {
54+
err := core.Join(c.irc, server.config.Server, server.config.EnableTLS)
55+
if err != nil {
56+
c.log.Println(err)
57+
c.send <- newErrorResponse("Unable to connect to IRC server.")
58+
return
59+
}
5860

59-
handler := server.NewIrcEventHandler(c)
61+
handler := server.NewIrcEventHandler(c)
6062

61-
if server.config.Log {
62-
logger, _, err := util.CreateLogFile(c.irc.Username, server.config.DownloadDir)
63-
if err != nil {
64-
server.log.Println(err)
63+
if server.config.Log {
64+
logger, _, err := util.CreateLogFile(c.irc.Username, server.config.DownloadDir)
65+
if err != nil {
66+
server.log.Println(err)
67+
}
68+
handler[core.Message] = func(text string) { logger.Println(text) }
6569
}
66-
handler[core.Message] = func(text string) { logger.Println(text) }
67-
}
6870

69-
go core.StartReader(c.ctx, c.irc, handler)
71+
go core.StartReader(c.ctx, c.irc, handler)
72+
73+
c.send <- ConnectionResponse{
74+
StatusResponse: StatusResponse{
75+
MessageType: CONNECT,
76+
NotificationType: SUCCESS,
77+
Title: "Welcome, connection established.",
78+
Detail: fmt.Sprintf("IRC username %s", c.irc.Username),
79+
},
80+
Name: c.irc.Username,
81+
}
82+
83+
return
84+
}
7085

7186
c.send <- ConnectionResponse{
7287
StatusResponse: StatusResponse{
7388
MessageType: CONNECT,
74-
NotificationType: SUCCESS,
75-
Title: "Welcome, connection established.",
89+
NotificationType: NOTIFY,
90+
Title: "Welcome back, re-using open IRC connection.",
7691
Detail: fmt.Sprintf("IRC username %s", c.irc.Username),
7792
},
7893
Name: c.irc.Username,
7994
}
95+
8096
}
8197

8298
// handle SearchRequests and send the query to the book server

0 commit comments

Comments
 (0)