diff --git a/ably/proto_message.go b/ably/proto_message.go index dc819cc4..5d5faea1 100644 --- a/ably/proto_message.go +++ b/ably/proto_message.go @@ -24,7 +24,7 @@ type Message struct { // ClientID is of the publisher of this message (RSL1g1, TM2b). ClientID string `json:"clientId,omitempty" codec:"clientId,omitempty"` // ConnectionID of the publisher of this message (TM2c). - ConnectionID string `json:"connectionId,omitempty" codec:"connectionID,omitempty"` + ConnectionID string `json:"connectionId,omitempty" codec:"connectionId,omitempty"` // Deprecated: This attribute is deprecated and will be removed in future versions // ConnectionKey is a connectionKey of the active connection. ConnectionKey string `json:"connectionKey,omitempty" codec:"connectionKey,omitempty"` diff --git a/ably/proto_presence_message.go b/ably/proto_presence_message.go index b1f9ea58..1a50bbc1 100644 --- a/ably/proto_presence_message.go +++ b/ably/proto_presence_message.go @@ -52,13 +52,13 @@ type PresenceMessage struct { } func (m PresenceMessage) String() string { - return fmt.Sprintf("", [...]string{ + return fmt.Sprintf("", [...]string{ "absent", "present", "enter", "leave", "update", - }[m.Action], m.ClientID, m.Data) + }[m.Action], m.ID, m.ConnectionID, m.ClientID, m.Data) } func (msg *PresenceMessage) isServerSynthesized() bool { diff --git a/ably/proto_protocol_message.go b/ably/proto_protocol_message.go index ecbb476e..4b6542c2 100644 --- a/ably/proto_protocol_message.go +++ b/ably/proto_protocol_message.go @@ -178,8 +178,8 @@ func (msg *protocolMessage) String() string { case actionDetached: return fmt.Sprintf("(action=%q, channel=%q)", msg.Action, msg.Channel) case actionPresence, actionSync: - return fmt.Sprintf("(action=%q, id=%q, channel=%q, timestamp=%d, presenceMessages=%v)", - msg.Action, msg.ConnectionID, msg.Channel, msg.Timestamp, msg.Presence) + return fmt.Sprintf("(action=%q, id=%v, channel=%q, timestamp=%d, connectionId=%v, presenceMessages=%v)", + msg.Action, msg.ID, msg.Channel, msg.Timestamp, msg.ConnectionID, msg.Presence) case actionMessage: return fmt.Sprintf("(action=%q, id=%q, messages=%v)", msg.Action, msg.ConnectionID, msg.Messages) diff --git a/ably/realtime_presence_integration_test.go b/ably/realtime_presence_integration_test.go index d27638fc..bcf6289e 100644 --- a/ably/realtime_presence_integration_test.go +++ b/ably/realtime_presence_integration_test.go @@ -141,6 +141,160 @@ func TestRealtimePresence_EnsureChannelIsAttached(t *testing.T) { assert.NoError(t, err) } +func SkipTestRealtimePresence_Presence_Enter_Update_Leave(t *testing.T) { + app, client1 := ablytest.NewRealtime(nil...) + defer safeclose(t, ablytest.FullRealtimeCloser(client1), app) + + client2 := app.NewRealtime(ably.WithClientID("client2")) + defer safeclose(t, ablytest.FullRealtimeCloser(client2)) + + err := ablytest.Wait(ablytest.ConnWaiter(client1, client1.Connect, ably.ConnectionEventConnected), nil) + assert.NoError(t, err) + err = ablytest.Wait(ablytest.ConnWaiter(client2, client2.Connect, ably.ConnectionEventConnected), nil) + assert.NoError(t, err) + + client1Channel := client1.Channels.Get("channel") + err = client1Channel.Attach(context.Background()) + assert.NoError(t, err) + + client2Channel := client2.Channels.Get("channel") + err = client2Channel.Attach(context.Background()) + assert.NoError(t, err) + + subCh1, unsub1, err := ablytest.ReceivePresenceMessages(client1Channel, nil) + assert.NoError(t, err) + defer unsub1() + + // ENTER + err = client2Channel.Presence.Enter(context.Background(), "enter client2") + assert.NoError(t, err) + + member_received := <-subCh1 + assert.Len(t, subCh1, 0) // Ensure no more updates received + + assert.Equal(t, ably.PresenceActionEnter, member_received.Action) + assert.Equal(t, "client2", member_received.ClientID) + assert.Equal(t, "enter client2", member_received.Data) + + // UPDATE + err = client2Channel.Presence.Update(context.Background(), "update client2") + assert.NoError(t, err) + + member_received = <-subCh1 + assert.Len(t, subCh1, 0) // Ensure no more updates received + + assert.Equal(t, ably.PresenceActionUpdate, member_received.Action) + assert.Equal(t, "client2", member_received.ClientID) + assert.Equal(t, "update client2", member_received.Data) + + // LEAVE + err = client2Channel.Presence.Leave(context.Background(), "leave client2") + assert.NoError(t, err) + + member_received = <-subCh1 + assert.Len(t, subCh1, 0) // Ensure no more updates received + + assert.Equal(t, ably.PresenceActionLeave, member_received.Action) + assert.Equal(t, "client2", member_received.ClientID) + assert.Equal(t, "leave client2", member_received.Data) +} + +func SkipTestRealtimePresence_ServerSynthesized_Leave(t *testing.T) { + app, client1 := ablytest.NewRealtime(nil...) + defer safeclose(t, ablytest.FullRealtimeCloser(client1), app) + + client2 := app.NewRealtime(ably.WithClientID("client2")) + defer safeclose(t, ablytest.FullRealtimeCloser(client2)) + + err := ablytest.Wait(ablytest.ConnWaiter(client1, client1.Connect, ably.ConnectionEventConnected), nil) + assert.NoError(t, err) + + err = ablytest.Wait(ablytest.ConnWaiter(client2, client2.Connect, ably.ConnectionEventConnected), nil) + assert.NoError(t, err) + + client1Channel := client1.Channels.Get("channel") + err = client1Channel.Attach(context.Background()) + + assert.NoError(t, err) + + client2Channel := client2.Channels.Get("channel") + err = client2Channel.Attach(context.Background()) + assert.NoError(t, err) + + subCh1, unsub1, err := ablytest.ReceivePresenceMessages(client1Channel, nil) + assert.NoError(t, err) + defer unsub1() + + // ENTER + err = client2Channel.Presence.Enter(context.Background(), "enter client2") + assert.NoError(t, err) + + member_received := <-subCh1 + assert.Len(t, subCh1, 0) // Ensure no more updates received + assert.Equal(t, ably.PresenceActionEnter, member_received.Action) + assert.Equal(t, "client2", member_received.ClientID) + assert.Equal(t, "enter client2", member_received.Data) + + members, err := client1Channel.Presence.Get(context.Background()) + assert.NoError(t, err) + assert.Len(t, members, 1) + + // Server Synthesized Leave when client2 disconnects + client2.Close() + + member_received = <-subCh1 + assert.Len(t, subCh1, 0) // Ensure no more updates received + + assert.Equal(t, ably.PresenceActionLeave, member_received.Action) + assert.Equal(t, "client2", member_received.ClientID) + + // Make sure no members are present on the channel + members, err = client1Channel.Presence.Get(context.Background()) + assert.NoError(t, err) + assert.Empty(t, members) +} + +func TestRealtimePresence_ServerSynthesized_Leave(t *testing.T) { + app, client1 := ablytest.NewRealtime(nil...) + defer safeclose(t, ablytest.FullRealtimeCloser(client1), app) + + client2 := app.NewRealtime(ably.WithClientID("client2")) + defer safeclose(t, ablytest.FullRealtimeCloser(client2)) + + err := ablytest.Wait(ablytest.ConnWaiter(client1, client1.Connect, ably.ConnectionEventConnected), nil) + assert.NoError(t, err) + + err = ablytest.Wait(ablytest.ConnWaiter(client2, client2.Connect, ably.ConnectionEventConnected), nil) + assert.NoError(t, err) + + client1Channel := client1.Channels.Get("channel") + err = client1Channel.Attach(context.Background()) + + assert.NoError(t, err) + + client2Channel := client2.Channels.Get("channel") + err = client2Channel.Attach(context.Background()) + assert.NoError(t, err) + + // ENTER + err = client2Channel.Presence.Enter(context.Background(), "enter client2") + assert.NoError(t, err) + + leaveAction := ably.PresenceActionLeave + subCh1, unsub1, err := ablytest.ReceivePresenceMessages(client1Channel, &leaveAction) + assert.NoError(t, err) + defer unsub1() + + // Server Synthesized Leave when client2 disconnects + client2.Close() + + member_received := <-subCh1 + assert.Len(t, subCh1, 0) // Ensure no more updates received + + assert.Equal(t, ably.PresenceActionLeave, member_received.Action) + assert.Equal(t, "client2", member_received.ClientID) +} + // When a client is created with a ClientID, Enter is used to announce the client's presence. // This example shows Client A entering their presence. func ExampleRealtimePresence_Enter() {