update chat; add persistent flag

Kim committed Oct 22, 2025 at 11:51 UTC c5348857d476e8051bf802176c16db7e19b6a52f
5 files changed +197 -7
cmd/example_chat/main.go
+27
@@ -24,6 +24,7 @@ var (
24 flagServerURL string
25 flagPort int
26 flagName string
27 + flagDataPath string
28 )
29
30 func init() {
@@ -31,6 +32,7 @@ func init() {
32 flags.StringVar(&flagServerURL, "server-url", "http://relaydns.gosuda.org", "relayserver base URL to auto-fetch multiaddrs from /health")
33 flags.IntVar(&flagPort, "port", 8091, "local chat HTTP port")
34 flags.StringVar(&flagName, "name", "example-chat", "backend display name")
35 + flags.StringVar(&flagDataPath, "data-path", "", "optional directory to persist chat history via PebbleDB")
36 }
37
38 func main() {
@@ -49,6 +51,24 @@ func runChat(cmd *cobra.Command, args []string) error {
51 return fmt.Errorf("listen chat: %w", err)
52 }
53 hub := newHub()
54 +
55 + // Optional: open persistent store and preload history
56 + var store *messageStore
57 + if flagDataPath != "" {
58 + s, err := openMessageStore(flagDataPath)
59 + if err != nil {
60 + log.Warn().Err(err).Msg("[chat] open store failed; running in memory only")
61 + } else {
62 + store = s
63 + if msgs, err := store.LoadAll(); err != nil {
64 + log.Warn().Err(err).Msg("[chat] load history failed")
65 + } else if len(msgs) > 0 {
66 + hub.bootstrap(msgs)
67 + log.Info().Msgf("[chat] loaded %d messages from store", len(msgs))
68 + }
69 + hub.attachStore(store)
70 + }
71 + }
72 srv := serveChatHTTP(ln, flagName, hub)
73
74 // 2) advertise over RelayDNS (HTTP tunneled via server /peer route)
@@ -91,6 +111,13 @@ func runChat(cmd *cobra.Command, args []string) error {
111 hub.closeAll()
112 hub.wait()
113
114 + // 4. Close persistent store if opened
115 + if store != nil {
116 + if err := store.Close(); err != nil {
117 + log.Warn().Err(err).Msg("[chat] store close error")
118 + }
119 + }
120 +
121 log.Info().Msg("[chat] shutdown complete")
122 return nil
123 }
cmd/example_chat/persist.go new
+86
@@ -0,0 +1,86 @@
1 +package main
2 +
3 +import (
4 + "encoding/binary"
5 + "encoding/json"
6 + "os"
7 + "path/filepath"
8 + "sync"
9 +
10 + "github.com/cockroachdb/pebble"
11 +)
12 +
13 +// messageStore persists chat messages in a PebbleDB key-value store.
14 +// Keys are 8-byte big-endian sequence numbers increasing monotonically.
15 +type messageStore struct {
16 + db *pebble.DB
17 + mu sync.Mutex
18 + next uint64
19 +}
20 +
21 +func openMessageStore(dir string) (*messageStore, error) {
22 + if dir == "" {
23 + return nil, nil
24 + }
25 + if err := os.MkdirAll(dir, 0o755); err != nil {
26 + return nil, err
27 + }
28 + // Pebble database lives directly at the provided directory.
29 + // Accept default options for simplicity.
30 + db, err := pebble.Open(filepath.Clean(dir), &pebble.Options{})
31 + if err != nil {
32 + return nil, err
33 + }
34 + s := &messageStore{db: db}
35 + // Discover next sequence by reading the last key.
36 + it, err := db.NewIter(nil)
37 + if err != nil {
38 + return nil, err
39 + }
40 + defer it.Close()
41 + if it.Last() {
42 + if len(it.Key()) >= 8 {
43 + s.next = binary.BigEndian.Uint64(it.Key()[:8]) + 1
44 + }
45 + }
46 + return s, nil
47 +}
48 +
49 +func (s *messageStore) Append(m message) error {
50 + if s == nil || s.db == nil {
51 + return nil
52 + }
53 + s.mu.Lock()
54 + defer s.mu.Unlock()
55 + key := make([]byte, 8)
56 + binary.BigEndian.PutUint64(key, s.next)
57 + s.next++
58 + val, _ := json.Marshal(m)
59 + return s.db.Set(key, val, pebble.Sync)
60 +}
61 +
62 +func (s *messageStore) LoadAll() ([]message, error) {
63 + if s == nil || s.db == nil {
64 + return nil, nil
65 + }
66 + it, err := s.db.NewIter(nil)
67 + if err != nil {
68 + return nil, err
69 + }
70 + defer it.Close()
71 + out := make([]message, 0, 256)
72 + for it.First(); it.Valid(); it.Next() {
73 + var m message
74 + if err := json.Unmarshal(it.Value(), &m); err == nil {
75 + out = append(out, m)
76 + }
77 + }
78 + return out, nil
79 +}
80 +
81 +func (s *messageStore) Close() error {
82 + if s == nil || s.db == nil {
83 + return nil
84 + }
85 + return s.db.Close()
86 +}
cmd/example_chat/view.go
+43 -7
@@ -20,6 +20,7 @@ type hub struct {
20 conns map[*websocket.Conn]struct{}
21 names map[*websocket.Conn]string
22 wg sync.WaitGroup
23 + store *messageStore
24 }
25
26 type message struct {
@@ -41,6 +42,11 @@ func (h *hub) broadcast(m message) {
42 conns = append(conns, c)
43 }
44 h.mu.Unlock()
45 + if h.store != nil {
46 + if err := h.store.Append(m); err != nil {
47 + log.Debug().Err(err).Msg("persist message")
48 + }
49 + }
50 for _, c := range conns {
51 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
52 _ = wsjson.Write(ctx, c, m)
@@ -48,6 +54,20 @@ func (h *hub) broadcast(m message) {
54 }
55 }
56
57 +// attachStore connects a persistent store to the hub.
58 +func (h *hub) attachStore(s *messageStore) {
59 + h.mu.Lock()
60 + h.store = s
61 + h.mu.Unlock()
62 +}
63 +
64 +// bootstrap preloads history into the in-memory buffer.
65 +func (h *hub) bootstrap(msgs []message) {
66 + h.mu.Lock()
67 + h.messages = append(h.messages, msgs...)
68 + h.mu.Unlock()
69 +}
70 +
71 // closeAll force-closes all active websocket connections (used during shutdown).
72 func (h *hub) closeAll() {
73 h.mu.Lock()
@@ -80,9 +100,7 @@ func handleWS(w http.ResponseWriter, r *http.Request, h *hub) {
100 h.conns[conn] = struct{}{}
101 backlog := append([]message(nil), h.messages...)
102 h.mu.Unlock()
83 - if len(backlog) > 20 {
84 - backlog = backlog[len(backlog)-20:]
85 - }
103 +
104 for _, m := range backlog {
105 _ = wsjson.Write(connCtx, conn, m)
106 }
@@ -174,11 +192,13 @@ var indexTmpl = template.Must(template.New("chat").Parse(`<!DOCTYPE html>
192 --cursor: #22c55e;
193 }
194 *{ box-sizing: border-box }
195 + /* Prefer locally-installed D2Coding for Korean monospaced rendering */
196 + @font-face { font-family: 'D2Coding'; src: local('D2Coding'), local('D2Coding Ligature'), local('D2Coding Nerd'); font-display: swap; }
197 body { margin:0; padding:24px; background:var(--bg); color:var(--fg); font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial }
198 .wrap { max-width: 920px; margin: 0 auto }
199 h1 { margin:0 0 12px 0; font-weight:700 }
200 .term { border:1px solid var(--border); border-radius:10px; background:var(--panel); overflow:hidden }
181 - .termbar { display:flex; align-items:center; justify-content:space-between; padding:10px 12px; border-bottom:1px solid var(--border); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size:14px }
201 + .termbar { display:flex; align-items:center; justify-content:space-between; padding:10px 12px; border-bottom:1px solid var(--border); font-family: 'D2Coding', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size:14px }
202 .dots { display:flex; gap:6px }
203 .dot { width:10px; height:10px; border-radius:50%; }
204 .dot.red{ background:#ef4444 }
@@ -187,14 +207,27 @@ var indexTmpl = template.Must(template.New("chat").Parse(`<!DOCTYPE html>
207 .nick { display:flex; align-items:center; gap:8px }
208 .nick input{ background:transparent; border:1px solid var(--border); color:var(--fg); padding:6px 8px; border-radius:6px; font-family:inherit; font-size:13px; width:180px }
209 .nick button{ background:transparent; border:1px solid var(--border); color:var(--fg); padding:6px 8px; border-radius:6px; font-family:inherit; font-size:13px; cursor:pointer }
190 - .screen { height:420px; overflow:auto; padding:14px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size:14px; line-height:1.5; }
210 + .screen { height:420px; overflow:auto; padding:14px; font-family: 'D2Coding', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size:14px; line-height:1.5; }
211 .line { white-space: pre-wrap; word-break: break-word }
212 .ts { color:var(--muted) }
213 .usr { color:#60a5fa }
194 - .promptline { display:flex; align-items:center; gap:8px; padding:12px 14px; border-top:1px solid var(--border); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
214 + .promptline { display:flex; align-items:center; gap:8px; padding:12px 14px; border-top:1px solid var(--border); font-family: 'D2Coding', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
215 #prompt { color:var(--accent) }
216 #cmd { flex:1; background:transparent; border:none; outline:none; color:var(--fg); font-family: inherit; font-size:14px; caret-color: var(--cursor) }
217 small{ color:var(--muted); display:block; margin-top:10px }
218 +
219 + /* Mobile responsiveness */
220 + @media (max-width: 640px) {
221 + body { padding: 12px; }
222 + .wrap { max-width: 100%; }
223 + h1 { font-size: 18px; }
224 + .termbar { flex-wrap: wrap; gap: 8px; }
225 + .nick input { width: 100%; font-size: 16px; }
226 + .nick button { font-size: 16px; }
227 + .screen { height: 50vh; font-size: 13px; }
228 + .promptline { flex-wrap: wrap; gap: 6px; }
229 + #cmd { font-size: 16px; }
230 + }
231 </style>
232 </head>
233 <body>
@@ -305,8 +338,11 @@ var indexTmpl = template.Must(template.New("chat").Parse(`<!DOCTYPE html>
338 ws.send(JSON.stringify(payload));
339 cmd.value='';
340 }
341 + // Handle IME composition properly to avoid duplicated last character
342 + // on Enter when using Korean/Japanese/Chinese input methods.
343 cmd.addEventListener('keydown', e => {
309 - if(e.key === 'Enter') { e.preventDefault(); send(); }
344 + if (e.isComposing || e.keyCode === 229) { return; }
345 + if (e.key === 'Enter') { e.preventDefault(); send(); }
346 });
347 // Focus command line on load
348 setTimeout(()=>cmd.focus(), 0);
go.mod
+13
@@ -4,6 +4,7 @@ go 1.25.0
4
5 require (
6 github.com/cockroachdb/crlib v0.0.0-20251001180057-2a49e1873587
7 + github.com/cockroachdb/pebble v1.1.5
8 github.com/coder/websocket v1.8.14
9 github.com/libp2p/go-libp2p v0.44.0
10 github.com/libp2p/go-libp2p-pubsub v0.15.0
@@ -13,14 +14,22 @@ require (
14 )
15
16 require (
17 + github.com/DataDog/zstd v1.4.5 // indirect
18 github.com/benbjohnson/clock v1.3.5 // indirect
19 github.com/beorn7/perks v1.0.1 // indirect
20 github.com/cespare/xxhash/v2 v2.3.0 // indirect
21 + github.com/cockroachdb/errors v1.11.3 // indirect
22 + github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect
23 + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
24 + github.com/cockroachdb/redact v1.1.5 // indirect
25 + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
26 github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect
27 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
28 github.com/flynn/noise v1.1.0 // indirect
29 github.com/francoispqt/gojay v1.2.13 // indirect
30 + github.com/getsentry/sentry-go v0.27.0 // indirect
31 github.com/gogo/protobuf v1.3.2 // indirect
32 + github.com/golang/snappy v0.0.4 // indirect
33 github.com/google/uuid v1.6.0 // indirect
34 github.com/gorilla/websocket v1.5.3 // indirect
35 github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
@@ -32,6 +41,8 @@ require (
41 github.com/klauspost/compress v1.18.0 // indirect
42 github.com/klauspost/cpuid/v2 v2.2.10 // indirect
43 github.com/koron/go-ssdp v0.0.6 // indirect
44 + github.com/kr/pretty v0.3.1 // indirect
45 + github.com/kr/text v0.2.0 // indirect
46 github.com/libp2p/go-buffer-pool v0.1.0 // indirect
47 github.com/libp2p/go-flow-metrics v0.2.0 // indirect
48 github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect
@@ -77,6 +88,7 @@ require (
88 github.com/pion/transport/v3 v3.0.7 // indirect
89 github.com/pion/turn/v4 v4.0.2 // indirect
90 github.com/pion/webrtc/v4 v4.1.2 // indirect
91 + github.com/pkg/errors v0.9.1 // indirect
92 github.com/prometheus/client_golang v1.22.0 // indirect
93 github.com/prometheus/client_model v0.6.2 // indirect
94 github.com/prometheus/common v0.64.0 // indirect
@@ -84,6 +96,7 @@ require (
96 github.com/quic-go/qpack v0.5.1 // indirect
97 github.com/quic-go/quic-go v0.55.0 // indirect
98 github.com/quic-go/webtransport-go v0.9.0 // indirect
99 + github.com/rogpeppe/go-internal v1.10.0 // indirect
100 github.com/spaolacci/murmur3 v1.1.0 // indirect
101 github.com/spf13/pflag v1.0.9 // indirect
102 github.com/wlynxg/anet v0.0.5 // indirect
go.sum
+28
@@ -8,6 +8,8 @@ dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1
8 dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU=
9 git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
10 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
11 +github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
12 +github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
13 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
14 github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
15 github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
@@ -21,11 +23,26 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
23 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
24 github.com/cockroachdb/crlib v0.0.0-20251001180057-2a49e1873587 h1:qjG2TrBrPbGRVYp5obcAi8OSsuFJ8s1AElDImHLV9tY=
25 github.com/cockroachdb/crlib v0.0.0-20251001180057-2a49e1873587/go.mod h1:ae57yNis2F1FThSNdPdoXfiPOVi8G1TLreCBQYPOdqo=
26 +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
27 +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
28 +github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=
29 +github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8=
30 +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4=
31 +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M=
32 +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
33 +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
34 +github.com/cockroachdb/pebble v1.1.5 h1:5AAWCBWbat0uE0blr8qzufZP5tBjkRyy/jWe1QWLnvw=
35 +github.com/cockroachdb/pebble v1.1.5/go.mod h1:17wO9el1YEigxkP/YtV8NtCivQDgoCyBg5c4VR/eOWo=
36 +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
37 +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
38 +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
39 +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
40 github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
41 github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
42 github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
43 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
44 github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
45 +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
46 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
47 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
48 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -42,9 +59,13 @@ github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwU
59 github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
60 github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
61 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
62 +github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
63 +github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
64 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
65 github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
66 github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
67 +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
68 +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
69 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
70 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
71 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
@@ -55,6 +76,8 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
76 github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
77 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
78 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
79 +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
80 +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
81 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
82 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
83 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
@@ -184,6 +207,8 @@ github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a
207 github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
208 github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
209 github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
210 +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
211 +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
212 github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o=
213 github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M=
214 github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s=
@@ -226,7 +251,9 @@ github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps=
251 github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs=
252 github.com/pion/webrtc/v4 v4.1.2 h1:mpuUo/EJ1zMNKGE79fAdYNFZBX790KE7kQQpLMjjR54=
253 github.com/pion/webrtc/v4 v4.1.2/go.mod h1:xsCXiNAmMEjIdFxAYU0MbB3RwRieJsegSB2JZsGN+8U=
254 +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
255 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
256 +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
257 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
258 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
259 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -248,6 +275,7 @@ github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9M
275 github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=
276 github.com/quic-go/webtransport-go v0.9.0 h1:jgys+7/wm6JarGDrW+lD/r9BGqBAmqY/ssklE09bA70=
277 github.com/quic-go/webtransport-go v0.9.0/go.mod h1:4FUYIiUc75XSsF6HShcLeXXYZJ9AGwo/xh3L8M/P1ao=
278 +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
279 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
280 github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
281 github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=