diff --git a/go.mod b/go.mod index e7274a3..62287be 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/volatiletech/sqlboiler-sqlite3 v0.0.0-20210314195744-a1c697a68aef // indirect github.com/volatiletech/sqlboiler/v4 v4.5.0 github.com/volatiletech/strmangle v0.0.1 - go.cryptoscope.co/muxrpc/v2 v2.0.1-0.20210414085324-530a7df6ea1c + go.cryptoscope.co/muxrpc/v2 v2.0.1-0.20210420101321-264bf6a7df2c go.cryptoscope.co/netwrap v0.1.1 go.cryptoscope.co/secretstream v1.2.2 go.mindeco.de v1.11.0 diff --git a/go.sum b/go.sum index 04ddffd..908f976 100644 --- a/go.sum +++ b/go.sum @@ -495,6 +495,8 @@ go.cryptoscope.co/muxrpc/v2 v2.0.1-0.20210414084152-08e3e6acc50d h1:PYOUNvgxKOeJ go.cryptoscope.co/muxrpc/v2 v2.0.1-0.20210414084152-08e3e6acc50d/go.mod h1:MgaeojIkWY3lLuoNw1mlMT3b3jiZwOj/fgsoGZp/VNA= go.cryptoscope.co/muxrpc/v2 v2.0.1-0.20210414085324-530a7df6ea1c h1:UQ2PCZ2VDKwvuWXtvyLkCXfdztJPr8tGhTnjsK36JGI= go.cryptoscope.co/muxrpc/v2 v2.0.1-0.20210414085324-530a7df6ea1c/go.mod h1:MgaeojIkWY3lLuoNw1mlMT3b3jiZwOj/fgsoGZp/VNA= +go.cryptoscope.co/muxrpc/v2 v2.0.1-0.20210420101321-264bf6a7df2c h1:+78PDKICrSKwcsszzzLAO+kDiC46eVbgQfLivnBk5Wo= +go.cryptoscope.co/muxrpc/v2 v2.0.1-0.20210420101321-264bf6a7df2c/go.mod h1:MgaeojIkWY3lLuoNw1mlMT3b3jiZwOj/fgsoGZp/VNA= go.cryptoscope.co/muxrpc/v2 v2.0.1 h1:U1dS1dsFk7/LygH8o2qsWy8dHB5g3YeLtm0lsN7c1CI= go.cryptoscope.co/muxrpc/v2 v2.0.1/go.mod h1:MgaeojIkWY3lLuoNw1mlMT3b3jiZwOj/fgsoGZp/VNA= go.cryptoscope.co/netwrap v0.1.0/go.mod h1:7zcYswCa4CT+ct54e9uH9+IIbYYETEMHKDNpzl8Ukew= diff --git a/muxrpc/handlers/tunnel/server/state.go b/muxrpc/handlers/tunnel/server/state.go index 49559be..a508181 100644 --- a/muxrpc/handlers/tunnel/server/state.go +++ b/muxrpc/handlers/tunnel/server/state.go @@ -48,7 +48,7 @@ func (h *Handler) announce(_ context.Context, req *muxrpc.Request) (interface{}, h.state.AddEndpoint(*ref, req.Endpoint()) - return false, nil + return true, nil } func (h *Handler) leave(_ context.Context, req *muxrpc.Request) (interface{}, error) { @@ -59,7 +59,7 @@ func (h *Handler) leave(_ context.Context, req *muxrpc.Request) (interface{}, er h.state.Remove(*ref) - return false, nil + return true, nil } func (h *Handler) endpoints(ctx context.Context, req *muxrpc.Request, snk *muxrpc.ByteSink) error { @@ -70,7 +70,8 @@ func (h *Handler) endpoints(ctx context.Context, req *muxrpc.Request, snk *muxrp // for future updates h.state.Register(toPeer) - ref, err := network.GetFeedRefFromAddr(req.RemoteAddr()) + // get public key from the calling peer + peer, err := network.GetFeedRefFromAddr(req.RemoteAddr()) if err != nil { return err } @@ -84,17 +85,18 @@ func (h *Handler) endpoints(ctx context.Context, req *muxrpc.Request, snk *muxrp case roomdb.ModeCommunity: fallthrough case roomdb.ModeRestricted: - _, err := h.members.GetByFeed(ctx, *ref) + _, err := h.members.GetByFeed(ctx, *peer) if err != nil { return fmt.Errorf("external user are not allowed to enumerate members") } } - has := h.state.AlreadyAdded(*ref, req.Endpoint()) - if !has { - // just send the current state to the new peer - toPeer.Update(h.state.List()) - } + // add the peer to the room state if they arent already + h.state.AlreadyAdded(*peer, req.Endpoint()) + + // update the peer with + toPeer.Update(h.state.List()) + return nil } diff --git a/muxrpc/test/go/alias_test.go b/muxrpc/test/go/alias_test.go index 309c995..e6a4d22 100644 --- a/muxrpc/test/go/alias_test.go +++ b/muxrpc/test/go/alias_test.go @@ -13,7 +13,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.cryptoscope.co/muxrpc/v2" - "golang.org/x/sync/errgroup" "github.com/ssb-ngi-pointer/go-ssb-room/internal/aliases" "github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemod/keys" @@ -26,8 +25,6 @@ import ( func TestAliasRegister(t *testing.T) { testInit(t) ctx, cancel := context.WithCancel(context.Background()) - botgroup, ctx := errgroup.WithContext(ctx) - bs := newBotServer(ctx, mainLog) r := require.New(t) a := assert.New(t) @@ -41,55 +38,45 @@ func TestAliasRegister(t *testing.T) { roomsrv.WithContext(ctx), } - theBots := []*roomsrv.Server{} + theBots := []*testSession{} - srvMembers, serv := makeNamedTestBot(t, "srv", netOpts) - botgroup.Go(bs.Serve(serv)) - theBots = append(theBots, serv) + session := makeNamedTestBot(t, "srv", ctx, netOpts) + theBots = append(theBots, session) // we need bobs key to create the signature bobsKey, err := keys.NewKeyPair(nil) r.NoError(err) - bobsMembers, bob := makeNamedTestBot(t, "bob", append(netOpts, + bobSession := makeNamedTestBot(t, "bob", ctx, append(netOpts, roomsrv.WithKeyPair(bobsKey), )) - botgroup.Go(bs.Serve(bob)) - theBots = append(theBots, bob) - - t.Cleanup(func() { - for _, bot := range theBots { - bot.Shutdown() - r.NoError(bot.Close()) - } - r.NoError(botgroup.Wait()) - }) + theBots = append(theBots, bobSession) // adds - _, err = srvMembers.Add(ctx, bob.Whoami(), roomdb.RoleMember) + _, err = session.srv.Members.Add(ctx, bobSession.srv.Whoami(), roomdb.RoleMember) r.NoError(err) // allow bots to dial the remote // side-effect of re-using a room-server as the client - _, err = bobsMembers.Add(ctx, serv.Whoami(), roomdb.RoleMember) + _, err = bobSession.srv.Members.Add(ctx, session.srv.Whoami(), roomdb.RoleMember) r.NoError(err) // should work (we allowed A) - err = bob.Network.Connect(ctx, serv.Network.GetListenAddr()) + err = bobSession.srv.Network.Connect(ctx, session.srv.Network.GetListenAddr()) r.NoError(err, "connect A to the Server") t.Log("letting handshaking settle..") time.Sleep(1 * time.Second) - clientForServer, ok := bob.Network.GetEndpointFor(serv.Whoami()) + clientForServer, ok := bobSession.srv.Network.GetEndpointFor(session.srv.Whoami()) r.True(ok) t.Log("got endpoint") var testReg aliases.Registration testReg.Alias = "bob" - testReg.RoomID = serv.Whoami() - testReg.UserID = bob.Whoami() + testReg.RoomID = session.srv.Whoami() + testReg.UserID = bobSession.srv.Whoami() confirmation := testReg.Sign(bobsKey.Pair.Secret) t.Logf("signature created: %x...", confirmation.Signature[:16]) @@ -109,7 +96,7 @@ func TestAliasRegister(t *testing.T) { a.Equal("", resolveURL.Path) // server should have the alias now - alias, err := serv.Aliases.Resolve(ctx, "bob") + alias, err := session.srv.Aliases.Resolve(ctx, "bob") r.NoError(err) a.Equal(confirmation.Alias, alias.Name) @@ -118,5 +105,10 @@ func TestAliasRegister(t *testing.T) { t.Log("alias stored") + for _, bot := range theBots { + bot.srv.Shutdown() + r.NoError(bot.srv.Close()) + r.NoError(bot.serveGroup.Wait()) + } cancel() } diff --git a/muxrpc/test/go/deny_test.go b/muxrpc/test/go/deny_test.go index e6ac63b..5c8ca12 100644 --- a/muxrpc/test/go/deny_test.go +++ b/muxrpc/test/go/deny_test.go @@ -32,21 +32,21 @@ func TestConnEstablishmentDeniedKey(t *testing.T) { indexB ) - serv := theBots[indexSrv].Server - botA := theBots[indexA].Server - botB := theBots[indexB].Server + serv := theBots[indexSrv].srv + botA := theBots[indexA].srv + botB := theBots[indexB].srv // allow A, deny B - theBots[indexSrv].Members.Add(ctx, botA.Whoami(), roomdb.RoleMember) + theBots[indexSrv].srv.Members.Add(ctx, botA.Whoami(), roomdb.RoleMember) // since we want to verify denied keys in particular, let us both: // a) add B as a member - theBots[indexSrv].Members.Add(ctx, botB.Whoami(), roomdb.RoleMember) + theBots[indexSrv].srv.Members.Add(ctx, botB.Whoami(), roomdb.RoleMember) // b) ban B by adding them to the DeniedKeys database - theBots[indexSrv].Server.DeniedKeys.Add(ctx, botB.Whoami(), "rude") + theBots[indexSrv].srv.DeniedKeys.Add(ctx, botB.Whoami(), "rude") // hack: allow bots to dial the server - theBots[indexA].Members.Add(ctx, serv.Whoami(), roomdb.RoleMember) - theBots[indexB].Members.Add(ctx, serv.Whoami(), roomdb.RoleMember) + theBots[indexA].srv.Members.Add(ctx, serv.Whoami(), roomdb.RoleMember) + theBots[indexB].srv.Members.Add(ctx, serv.Whoami(), roomdb.RoleMember) // dial up B->A and C->A // should work (we allowed A) diff --git a/muxrpc/test/go/endpoints_test.go b/muxrpc/test/go/endpoints_test.go new file mode 100644 index 0000000..a1abf11 --- /dev/null +++ b/muxrpc/test/go/endpoints_test.go @@ -0,0 +1,197 @@ +package go_test + +import ( + "context" + "encoding/base64" + "encoding/json" + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.cryptoscope.co/muxrpc/v2" + "go.cryptoscope.co/muxrpc/v2/debug" + "go.cryptoscope.co/netwrap" + "go.cryptoscope.co/secretstream" + + "github.com/ssb-ngi-pointer/go-ssb-room/internal/maybemod/keys" + "github.com/ssb-ngi-pointer/go-ssb-room/roomdb" + refs "go.mindeco.de/ssb-refs" +) + +type announcements map[string]struct{} + +// we will let three clients (alf, bre, crl) join and see that the endpoint output is as expected +func TestEndpointClients(t *testing.T) { + r := require.New(t) + a := assert.New(t) + + testPath := filepath.Join("testrun", t.Name()) + os.RemoveAll(testPath) + + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + t.Cleanup(cancel) + + // create the roomsrv + ts := makeNamedTestBot(t, "server", ctx, nil) + ctx = ts.ctx + + // create three test clients + alf, alfFeed := ts.makeTestClient("alf") + bre, breFeed := ts.makeTestClient("bre") + carl, carlFeed := ts.makeTestClient("carl") + + // let carl join the room + // carl wont announce to emulate manyverse + carlEndpointsSerc, err := carl.Source(ctx, muxrpc.TypeJSON, muxrpc.Method{"tunnel", "endpoints"}) + r.NoError(err) + t.Log("carl opened endpoints") + + announcementsForCarl := make(announcements) + go logStream(ts, carlEndpointsSerc, "carl", announcementsForCarl) + time.Sleep(1 * time.Second) // give some time to process new events + + _, seen := announcementsForCarl[carlFeed.Ref()] + a.True(seen, "carl saw himself") + + // let alf join the room + alfEndpointsSerc, err := alf.Source(ctx, muxrpc.TypeJSON, muxrpc.Method{"tunnel", "endpoints"}) + r.NoError(err) + + announcementsForAlf := make(announcements) + go logStream(ts, alfEndpointsSerc, "alf", announcementsForAlf) + time.Sleep(1 * time.Second) // give some time to process new events + + // assert what alf saw + _, seen = announcementsForAlf[carlFeed.Ref()] + a.True(seen, "alf saw carl") + _, seen = announcementsForAlf[alfFeed.Ref()] + a.True(seen, "alf saw himself") + + // assert what carl saw + _, seen = announcementsForCarl[alfFeed.Ref()] + a.True(seen, "carl saw alf") + + // let bre join the room + breEndpointsSrc, err := bre.Source(ctx, muxrpc.TypeJSON, muxrpc.Method{"tunnel", "endpoints"}) + r.NoError(err) + + announcementsForBre := make(announcements) + go logStream(ts, breEndpointsSrc, "bre", announcementsForBre) + + time.Sleep(1 * time.Second) // give some time to process new events + + // assert bre saw the other two and herself + _, seen = announcementsForBre[carlFeed.Ref()] + a.True(seen, "bre saw carl") + _, seen = announcementsForBre[alfFeed.Ref()] + a.True(seen, "bre saw alf") + _, seen = announcementsForBre[breFeed.Ref()] + a.True(seen, "bre saw herself") + + // assert the others saw bre + _, seen = announcementsForAlf[breFeed.Ref()] + a.True(seen, "alf saw bre") + _, seen = announcementsForCarl[breFeed.Ref()] + a.True(seen, "carl saw bre") + + // terminate server and the clients + ts.srv.Shutdown() + alf.Terminate() + bre.Terminate() + carl.Terminate() + ts.srv.Close() + + // wait for all muxrpc serve()s to exit + r.NoError(ts.serveGroup.Wait()) + cancel() +} + +func logStream(ts *testSession, src *muxrpc.ByteSource, who string, a announcements) { + var edps []refs.FeedRef + + for src.Next(ts.ctx) { + body, err := src.Bytes() + if err != nil { + panic(err) + } + // ts.t.Log(who, "got body:", string(body)) + + err = json.Unmarshal(body, &edps) + if err != nil { + panic(err) + } + ts.t.Log(who, "got endpoints:", len(edps)) + for i, f := range edps { + ts.t.Log(who, ":", i, f.ShortRef()) + + // mark as f is present + a[f.Ref()] = struct{}{} + } + } + + if err := src.Err(); err != nil { + ts.t.Log("source errored: ", err) + return + } + + ts.t.Log(who, "stream closed") +} + +func (ts *testSession) makeTestClient(name string) (muxrpc.Endpoint, refs.FeedRef) { + r := require.New(ts.t) + + // create a fresh keypairs for the clients + client, err := keys.NewKeyPair(nil) + r.NoError(err) + + ts.t.Log(name, "is", client.Feed.ShortRef()) + + // add it as a memeber + memberID, err := ts.srv.Members.Add(ts.ctx, client.Feed, roomdb.RoleMember) + r.NoError(err) + ts.t.Log(name, "is member ID:", memberID) + + // default app key for the secret-handshake connection + ak, err := base64.StdEncoding.DecodeString("1KHLiKZvAvjbY1ziZEHMXawbCEIM6qwjCDm3VYRan/s=") + r.NoError(err) + + // create a shs client to authenticate and encrypt the connection + clientSHS, err := secretstream.NewClient(client.Pair, ak) + r.NoError(err) + + // returns a new connection that went through shs and does boxstream + + tcpAddr := netwrap.GetAddr(ts.srv.Network.GetListenAddr(), "tcp") + + authedConn, err := netwrap.Dial(tcpAddr, clientSHS.ConnWrapper(ts.srv.Whoami().PubKey())) + r.NoError(err) + + var muxMock muxrpc.FakeHandler + + testPath := filepath.Join("testrun", ts.t.Name()) + debugConn := debug.Dump(filepath.Join(testPath, "client-"+name), authedConn) + pkr := muxrpc.NewPacker(debugConn) + + wsEndpoint := muxrpc.Handle(pkr, &muxMock, muxrpc.WithContext(ts.ctx)) + + srv := wsEndpoint.(muxrpc.Server) + ts.serveGroup.Go(func() error { + err = srv.Serve() + if err != nil { + ts.t.Logf("mux server %s error: %v", name, err) + } + return err + }) + + // check we are talking to a room + var yup bool + err = wsEndpoint.Async(ts.ctx, &yup, muxrpc.TypeJSON, muxrpc.Method{"tunnel", "isRoom"}) + r.NoError(err) + r.True(yup, "server is not a room?") + + return wsEndpoint, client.Feed +} diff --git a/muxrpc/test/go/simple_test.go b/muxrpc/test/go/simple_test.go index ba5632d..2dc6821 100644 --- a/muxrpc/test/go/simple_test.go +++ b/muxrpc/test/go/simple_test.go @@ -29,16 +29,16 @@ func TestTunnelServerSimple(t *testing.T) { indexB ) - serv := theBots[indexSrv].Server - botA := theBots[indexA].Server - botB := theBots[indexB].Server + serv := theBots[indexSrv].srv + botA := theBots[indexA].srv + botB := theBots[indexB].srv // only allow A - theBots[indexSrv].Members.Add(ctx, botA.Whoami(), roomdb.RoleMember) + theBots[indexSrv].srv.Members.Add(ctx, botA.Whoami(), roomdb.RoleMember) // allow bots to dial the remote - theBots[indexA].Members.Add(ctx, serv.Whoami(), roomdb.RoleMember) - theBots[indexB].Members.Add(ctx, serv.Whoami(), roomdb.RoleMember) + theBots[indexA].srv.Members.Add(ctx, serv.Whoami(), roomdb.RoleMember) + theBots[indexB].srv.Members.Add(ctx, serv.Whoami(), roomdb.RoleMember) // dial up B->A and C->A @@ -105,17 +105,17 @@ func TestRoomAnnounce(t *testing.T) { indexB ) - serv := theBots[indexSrv].Server - botA := theBots[indexA].Server - botB := theBots[indexB].Server + serv := theBots[indexSrv].srv + botA := theBots[indexA].srv + botB := theBots[indexB].srv // allow both clients - theBots[indexSrv].Members.Add(ctx, botA.Whoami(), roomdb.RoleMember) - theBots[indexSrv].Members.Add(ctx, botB.Whoami(), roomdb.RoleMember) + theBots[indexSrv].srv.Members.Add(ctx, botA.Whoami(), roomdb.RoleMember) + theBots[indexSrv].srv.Members.Add(ctx, botB.Whoami(), roomdb.RoleMember) // allow bots to dial the remote - theBots[indexA].Members.Add(ctx, serv.Whoami(), roomdb.RoleMember) - theBots[indexB].Members.Add(ctx, serv.Whoami(), roomdb.RoleMember) + theBots[indexA].srv.Members.Add(ctx, serv.Whoami(), roomdb.RoleMember) + theBots[indexB].srv.Members.Add(ctx, serv.Whoami(), roomdb.RoleMember) // should work (we allowed A) err := botA.Network.Connect(ctx, serv.Network.GetListenAddr()) @@ -169,7 +169,7 @@ func TestRoomAnnounce(t *testing.T) { var ret bool err = endpointA.Async(ctx, &ret, muxrpc.TypeJSON, muxrpc.Method{"tunnel", "announce"}) r.NoError(err) - a.False(ret) // + a.True(ret) select { case <-time.After(10 * time.Second): @@ -182,7 +182,7 @@ func TestRoomAnnounce(t *testing.T) { err = endpointA.Async(ctx, &ret, muxrpc.TypeJSON, muxrpc.Method{"tunnel", "leave"}) r.NoError(err) - a.False(ret) // + a.True(ret) select { case <-time.After(10 * time.Second): diff --git a/muxrpc/test/go/utils_test.go b/muxrpc/test/go/utils_test.go index c48e01e..a9ef813 100644 --- a/muxrpc/test/go/utils_test.go +++ b/muxrpc/test/go/utils_test.go @@ -71,7 +71,16 @@ func (bs botServer) Serve(s *roomsrv.Server) func() error { } } -func makeNamedTestBot(t testing.TB, name string, opts []roomsrv.Option) (roomdb.MembersService, *roomsrv.Server) { +type testSession struct { + t testing.TB + + srv *roomsrv.Server + + ctx context.Context + serveGroup *errgroup.Group +} + +func makeNamedTestBot(t testing.TB, name string, ctx context.Context, opts []roomsrv.Option) *testSession { r := require.New(t) testPath := filepath.Join("testrun", t.Name(), "bot-"+name) os.RemoveAll(testPath) @@ -108,22 +117,26 @@ func makeNamedTestBot(t testing.TB, name string, opts []roomsrv.Option) (roomdb. sb := signinwithssb.NewSignalBridge() theBot, err := roomsrv.New(db.Members, db.DeniedKeys, db.Aliases, db.AuthWithSSB, sb, db.Config, netInfo, botOptions...) r.NoError(err) - return db.Members, theBot + + ts := testSession{ + t: t, + srv: theBot, + } + + ts.serveGroup, ts.ctx = errgroup.WithContext(ctx) + + ts.serveGroup.Go(func() error { + return theBot.Network.Serve(ts.ctx) + }) + + return &ts } -type testBot struct { - Server *roomsrv.Server - Members roomdb.MembersService -} - -func createServerAndBots(t *testing.T, ctx context.Context, count uint) []testBot { +// TODO: refactor for single test session and use makeTestClient() +func createServerAndBots(t *testing.T, ctx context.Context, count uint) []*testSession { testInit(t) r := require.New(t) - botgroup, ctx := errgroup.WithContext(ctx) - - bs := newBotServer(ctx, mainLog) - appKey := make([]byte, 32) rand.Read(appKey) @@ -131,31 +144,25 @@ func createServerAndBots(t *testing.T, ctx context.Context, count uint) []testBo roomsrv.WithAppKey(appKey), roomsrv.WithContext(ctx), } - theBots := []testBot{} - srvsMembers, serv := makeNamedTestBot(t, "srv", netOpts) - botgroup.Go(bs.Serve(serv)) - theBots = append(theBots, testBot{ - Server: serv, - Members: srvsMembers, - }) + theBots := []*testSession{} + + session := makeNamedTestBot(t, "srv", ctx, netOpts) + + theBots = append(theBots, session) for i := uint(1); i < count+1; i++ { - botMembers, botSrv := makeNamedTestBot(t, fmt.Sprintf("%d", i), netOpts) - botgroup.Go(bs.Serve(botSrv)) - theBots = append(theBots, testBot{ - Server: botSrv, - Members: botMembers, - }) + // TODO: replace with makeClient?! + clientSession := makeNamedTestBot(t, fmt.Sprintf("%d", i), ctx, netOpts) + theBots = append(theBots, clientSession) } t.Cleanup(func() { time.Sleep(1 * time.Second) for _, bot := range theBots { - bot.Server.Shutdown() - r.NoError(bot.Server.Close()) + bot.srv.Shutdown() + r.NoError(bot.srv.Close()) } - r.NoError(botgroup.Wait()) }) return theBots diff --git a/muxrpc/test/go/websocket_test.go b/muxrpc/test/go/websocket_test.go index 8b31c01..6cbfc11 100644 --- a/muxrpc/test/go/websocket_test.go +++ b/muxrpc/test/go/websocket_test.go @@ -36,7 +36,8 @@ func TestWebsocketDialing(t *testing.T) { t.Cleanup(cancel) // create the roomsrv - serverMembers, server := makeNamedTestBot(t, "server", nil) + session := makeNamedTestBot(t, "server", ctx, nil) + server := session.srv // open a TCP listener for HTTP l, err := net.Listen("tcp4", "localhost:0") @@ -53,7 +54,7 @@ func TestWebsocketDialing(t *testing.T) { r.NoError(err) // add it as a memeber - memberID, err := serverMembers.Add(ctx, client.Feed, roomdb.RoleMember) + memberID, err := server.Members.Add(ctx, client.Feed, roomdb.RoleMember) r.NoError(err) t.Log("client member:", memberID) diff --git a/roomstate/roomstate.go b/roomstate/roomstate.go index ed0214d..8388632 100644 --- a/roomstate/roomstate.go +++ b/roomstate/roomstate.go @@ -125,7 +125,6 @@ func (m *Manager) AlreadyAdded(who refs.FeedRef, edp muxrpc.Endpoint) bool { m.updater.Update(m.room.AsList()) } - // send the current state m.roomMu.Unlock() return has }