@@ -33,10 +33,11 @@
33 33 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
messages []Message
34 34 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
client *girc.Client
35 35 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
errCh chan error
36 36 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
37 37 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
registeredByRelay bool
38 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ connectedAt time.Time
38 39 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
39 40 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
40 41 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
func newIRCConnector(cfg Config) (Connector, error) {
41 42 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if cfg.IRC.Addr == "" {
42 43 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return nil, fmt.Errorf("sessionrelay: irc transport requires irc addr")
@@ -87,11 +88,10 @@
87 88 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
case err := <-c.errCh:
88 89 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
_ = c.cleanupRegistration(context.Background())
89 90 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return fmt.Errorf("sessionrelay: irc connect: %w", err)
90 91 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
case <-joined:
91 92 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
go c.keepAlive(ctx, host, port)
92 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- go c.watchdog(ctx)
93 93 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return nil
94 94 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
95 95 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
96 96 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
97 97 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
// dial creates a fresh girc client, wires up handlers, and starts the
@@ -108,10 +108,13 @@
108 108 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
SASL: &girc.SASLPlain{User: c.nick, Pass: c.pass},
109 109 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
PingDelay: 30 * time.Second,
110 110 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
PingTimeout: 30 * time.Second,
111 111 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
})
112 112 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
client.Handlers.AddBg(girc.CONNECTED, func(cl *girc.Client, _ girc.Event) {
113 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ c.mu.Lock()
114 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ c.connectedAt = time.Now()
115 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ c.mu.Unlock()
113 116 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
for _, channel := range c.Channels() {
114 117 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
cl.Cmd.Join(channel)
115 118 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
116 119 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
})
117 120 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
client.Handlers.AddBg(girc.JOIN, func(_ *girc.Client, e girc.Event) {
@@ -210,70 +213,10 @@
210 213 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
fmt.Fprintf(os.Stderr, "sessionrelay: reconnected successfully\n")
211 214 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
})
212 215 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
213 216 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
214 217 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
215 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- // watchdog periodically checks if the IRC client is still connected and
216 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- // if the API is reachable. Forces reconnection when the connection is dead.
217 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- func (c *ircConnector) watchdog(ctx context.Context) {
218 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- failures := 0
219 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- for {
220 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- select {
221 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- case <-ctx.Done():
222 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- return
223 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- case <-time.After(10 * time.Second):
224 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- }
225 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
-
226 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- c.mu.RLock()
227 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- client := c.client
228 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- c.mu.RUnlock()
229 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- if client == nil {
230 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- failures = 0
231 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- continue
232 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- }
233 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
-
234 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- if !client.IsConnected() {
235 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- client.Close()
236 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- select {
237 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- case c.errCh <- fmt.Errorf("watchdog: client disconnected"):
238 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- default:
239 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- }
240 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- failures = 0
241 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- continue
242 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- }
243 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
-
244 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- // Probe the API to detect server restarts.
245 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- if c.apiURL != "" && c.token != "" {
246 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- probeCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
247 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- req, _ := http.NewRequestWithContext(probeCtx, http.MethodGet, c.apiURL+"/v1/status", nil)
248 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- if req != nil {
249 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- req.Header.Set("Authorization", "Bearer "+c.token)
250 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- resp, err := http.DefaultClient.Do(req)
251 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- if err != nil || resp.StatusCode != 200 {
252 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- failures++
253 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- if resp != nil {
254 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- resp.Body.Close()
255 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- }
256 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- } else {
257 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- resp.Body.Close()
258 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- failures = 0
259 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- }
260 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- }
261 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- cancel()
262 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- }
263 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
-
264 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- if failures >= 3 {
265 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- client.Close()
266 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- select {
267 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- case c.errCh <- fmt.Errorf("watchdog: API unreachable"):
268 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- default:
269 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- }
270 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- failures = 0
271 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- }
272 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- }
273 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- }
274 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
-
275 218 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
func (c *ircConnector) Post(_ context.Context, text string) error {
276 219 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
c.mu.RLock()
277 220 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
client := c.client
278 221 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
c.mu.RUnlock()
279 222 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
if client == nil {
@@ -311,11 +254,81 @@
311 254 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
312 255 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
313 256 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return out, nil
314 257 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
315 258 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
316 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
- func (c *ircConnector) Touch(context.Context) error {
259 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ func (c *ircConnector) Touch(ctx context.Context) error {
260 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ c.mu.RLock()
261 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ client := c.client
262 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ c.mu.RUnlock()
263 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
264 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if client == nil {
265 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return fmt.Errorf("sessionrelay: not connected")
266 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
267 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
268 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if !client.IsConnected() {
269 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ client.Close()
270 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ select {
271 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ case c.errCh <- fmt.Errorf("touch: client disconnected"):
272 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ default:
273 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
274 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return fmt.Errorf("sessionrelay: disconnected")
275 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
276 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
277 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Detect server restarts by checking the server's startup time.
278 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // If the server started after our IRC connection was established,
279 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // the IRC connection is stale and must be recycled.
280 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if c.apiURL != "" && c.token != "" {
281 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ probeCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
282 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ defer cancel()
283 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ req, err := http.NewRequestWithContext(probeCtx, http.MethodGet, c.apiURL+"/v1/status", nil)
284 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err != nil {
285 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return nil
286 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
287 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ req.Header.Set("Authorization", "Bearer "+c.token)
288 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ resp, err := http.DefaultClient.Do(req)
289 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err != nil {
290 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return nil // API unreachable, transient
291 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
292 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ defer resp.Body.Close()
293 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
294 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ var status struct {
295 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ Started string `json:"started"`
296 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
297 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err := json.NewDecoder(resp.Body).Decode(&status); err == nil && status.Started != "" {
298 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ serverStart, err := time.Parse(time.RFC3339Nano, status.Started)
299 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if err == nil {
300 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ c.mu.RLock()
301 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ connectedAt := c.connectedAt
302 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ c.mu.RUnlock()
303 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if !connectedAt.IsZero() && serverStart.After(connectedAt) {
304 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Server restarted after we connected — our IRC session is dead.
305 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ client.Close()
306 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ select {
307 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ case c.errCh <- fmt.Errorf("touch: server restarted (started %s, connected %s)", serverStart.Format(time.RFC3339), connectedAt.Format(time.RFC3339)):
308 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ default:
309 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
310 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ return fmt.Errorf("sessionrelay: server restarted")
311 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
312 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
313 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
314 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
315 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ // Also touch presence so the server tracks us.
316 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ presenceReq, _ := http.NewRequestWithContext(probeCtx, http.MethodPost,
317 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ c.apiURL+"/v1/channels/"+channelSlug(c.primary)+"/presence",
318 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ bytes.NewReader([]byte(`{"nick":"`+c.nick+`"}`)))
319 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if presenceReq != nil {
320 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ presenceReq.Header.Set("Authorization", "Bearer "+c.token)
321 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ presenceReq.Header.Set("Content-Type", "application/json")
322 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ pr, err := http.DefaultClient.Do(presenceReq)
323 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ if pr != nil {
324 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ pr.Body.Close()
325 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
326 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ _ = err
327 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
328 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+ }
329 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
+
317 330 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
return nil
318 331 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
}
319 332 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
320 333 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
func (c *ircConnector) JoinChannel(ctx context.Context, channel string) error {
321 334 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!
channel = normalizeChannel(channel)
322 335 { copied = false; pop = false }, 1000)" :class="copied && 'copied'">Copy link Copied!