GetStream / stream-chat-swift

💬 iOS Chat SDK in Swift - Build your own app chat experience for iOS using the official Stream Chat API
https://getstream.io/chat/sdk/ios/
Other
845 stars 204 forks source link

Error decoding `channel.created` event #2167

Closed moubry closed 2 years ago

moubry commented 2 years ago

What did you do?

What did you expect to happen?

I expect to not see an error from Stream in logs, and to instead see new channels appear in channel list controller’s channels as they're created.

Note: Relaunching the app DOES show the newly created channel, despite the error in the logs. I’m hoping that perhaps this error message sheds some light on to why I might not be receiving channels as they’re created.

What happened instead?

I do not receive the newly created channel and instead (upon restarting the app), see this console/log error:

2022-07-13 13:46:58.581 [ERROR] [com.apple.NSURLSession-delegate] [EventPayload.swift:176] [asEvents()] > Failed to decode event from event payload: 
-----EventPayload-----
     eventType: EventType(rawValue: "channel.created")
     connectionId: nil
     cid: Optional(messaging:sean-6)
     currentUser: nil
     user: Optional(StreamChat.UserPayload)
     createdBy: nil
     memberContainer: nil
     channel: Optional(StreamChat.ChannelDetailPayload(cid: messaging:sean-6, name: Optional("Sean 6"), imageURL: nil, extraData: ["disabled": StreamChat.RawJSON.bool(false)], typeRawValue: "messaging", lastMessageAt: nil, createdAt: 2022-07-13 18:45:47 +0000, deletedAt: nil, updatedAt: 2022-07-13 18:45:47 +0000, truncatedAt: nil, createdBy: Optional(StreamChat.UserPayload), config: StreamChat.ChannelConfig, isFrozen: false, isHidden: nil, members: Optional([StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil), StreamChat.MemberPayload(user: StreamChat.UserPayload, role: Optional(StreamChat.MemberRole(rawValue: "member")), createdAt: 2022-07-13 18:45:47 +0000, updatedAt: 2022-07-13 18:45:47 +0000, banExpiresAt: nil, isBanned: Optional(false), isShadowBanned: Optional(false), isInvited: nil, inviteAcceptedAt: nil, inviteRejectedAt: nil)]), memberCount: 26, invitedMembers: [], team: nil, cooldownDuration: 0))
     message: nil
     reaction: nil
     watcherCount: nil
     unreadCount: nil
     createdAt: Optional(2022-07-13 18:45:47 +0000)
     isChannelHistoryCleared: nil
     banReason: nil
     banExpiredAt: nil
     parentId: nil
     hardDelete: false
, error: Error UnknownChannelEvent in /Users/sean/Library/Developer/Xcode/DerivedData/EarBuds-gaesnvgzbaztcvggknbpgtfkrpem/SourcePackages/checkouts/stream-chat-swift/Sources/StreamChat/WebSocketClient/Events/EventType.swift:173 -> Event with EventType(rawValue: "channel.created") cannot be decoded as system event.

I see many of these errors after quitting/reopening the app — all for channel creation events, which seems to be the only event that the SDK is failing to decode.

GetStream Environment

GetStream Chat version: 4.18.0 GetStream Chat frameworks: Both StreamChat and StreamChatUI. iOS version: iOS 15 Swift version: latest Xcode version: latest Device: iPhone 13 Pro

Additional detail

I’m assuming this does not matter, but just in case it’s useful information: I’m not creating these channels in the Swift client, but am instead creating them on the server using the Go package:

func (s *StreamService) CreateChannel(cType string, cID string, name string, userID string, users []string) (earbuds.Channel, error) {
    ctx := context.Background()
    response, err := s.client.CreateChannel(ctx, cType, cID, userID, &stream.ChannelRequest{
        Members: users,
        ExtraData: map[string]interface{}{
            "name": name,
        },
    })
    if err != nil {
        return earbuds.Channel{}, err
    }
    return earbuds.Channel{
        Name:              name,
        StreamChannelType: response.Channel.Type,
        StreamChannelID:   response.Channel.ID,
    }, nil
}
nuno-vieira commented 2 years ago

Hi @moubry!

Thank you for your time in creating the issue!

We are currently investigating it, and we will get back to you as soon as possible.

Best, Nuno

nuno-vieira commented 2 years ago

Hi @moubry!

Can you show us the query of your ChatChannelListController? It does seem that on iOS SDK we don't parse this event. And we should fix this, but usually, the channel list query is observing the current user as a member, and that triggers a different event, which is “notification.added_to_channel”. Any reason you are observing channels where the current user is not a member? If that is a mistake, you can fix it on your side, if not, you will need to wait for a fix. Please let me know your use case.

Thanks, Nuno

moubry commented 2 years ago

Thanks for investigating this @nuno-vieira — I really appreciate it!

I believe that I’m definitely observing channels where the user is a member. However, there could be something else I’m doing that could be exacerbating the issue. Here’s an approximation of what I’m currently doing (some non-relevant code removed):

struct StreamView: View {
    @ObservedObject var connection: ChatConnectionController.ObservableObject

    init(connectionController: ChatConnectionController) {
        self.connection = connectionController.observableObject
    }

    var body: some View {
        switch connection.connectionStatus {
        case .connected:
            if let userID = ChatClient.shared.currentUserId {
                let channelListController = ChatClient.shared.channelListController(
                    query: ChannelListQuery(filter: .containMembers(userIds: [userID]))
                )
                ChannelListView(channelListController: channelListController)
            } else {
                Text("Error: Connected but couldn’t find Stream user to log in as.")
            }
        case .disconnected, .disconnecting:
            Text("Disconnected.")
        case .connecting, .initialized:
            Text("Connecting…")
        }
    }
}

struct ChannelListView: View {
    var channelListController: ChatChannelListController
    @State var channels = [ChatChannel]()

    init(channelListController: ChatChannelListController) {
        self.channelListController = channelListController
    }

    var body: some View {
        NavigationView {
            VStack(alignment: .trailing) {
                List(channels, id: \.id) { channel in
                    NavigationLink(tag: channel, selection: $streamWindow.selectedChannel,
                    destination: {
                        HStack(alignment: .top, spacing: 0) {
                            ChatChannelView(
                                viewFactory: streamWindow.customViewFactory,
                                channelController: ChatClient.shared.channelController(for: channel.cid)
                            )
                                .navigationBarHidden(false)
                                .onAppear {
                                    print("Channel `\(channel.cid)` is appearing. Clearing last thread viewed.")
                                    streamWindow.lastThreadViewed = nil
                                }
                        }
                    }, label: {
                        Text(channel.name ?? "Untitled")
                    })
                }
                .navigationTitle("Channels")
            }
            // Channel List Toolbar
            .toolbar {
                // A hidden button that is activated when the
                // NewChannelView sets the showingNewChannel flag.
                ToolbarItem(placement: .navigationBarTrailing) {
                    NavigationLink(
                        isActive: $showingNewChannel,
                        destination: {
                            if let controller = newChatChannelController {
                                ChatChannelView(
                                    viewFactory: streamWindow.customViewFactory,
                                    channelController: controller
                                )
                            }
                        }, label: {
                            EmptyView()
                        }
                    )
                }
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button {
                        showingNewChannelForm = true
                    } label: {
                        Image(systemName: "plus")
                            .resizable()
                    }
                }
            }
            .sheet(isPresented: $showingNewChannelForm) {
                NewChannelView(controller: $newChatChannelController, showingNewChannel: $showingNewChannel)
            }
        }
        .workaroundNavigationStyle()
        .onAppear {
            print("StreamView has appeared. Synchronizing channel list controller.")
            // Update the locally cached list of channels with Stream servers.
            channelListController.observableObject.controller.synchronize()
        }
        // We are subscribing to changes on channels here, then updating our own
        // local channels state, instead of setting the channel list controller as
        // `@ObservedObject var channelList: ChatChannelListController.ObservableObject`
        // because that was causing the entire navigation view hierarchy to be blown away (and
        // navigation reset) on minor channel state changes.
        .onReceive(channelListController.observableObject.$channels) { channels in
            // When a new channel is created on the server, this never gets called.
            // However, it does otherwise get called quite often throughout normal usage of the app.
            print("Channel list's channels changing (\(channels.count)).")
            self.channels = channels.sorted(by: { channelA, channelB in
                channelA.lastMessageAt ?? Date() > channelB.lastMessageAt ?? Date()
            })
        }
    }
}
nuno-vieira commented 2 years ago

Thanks for the snippet @moubry!

What I think is missing is that you need to watch the channel on the backend side, when creating the channel you need to watch it so that events are propagated to the members of the channel. More details here: https://getstream.io/chat/docs/go-golang/watch_channel/?language=go

Let me know if that helped!

nuno-vieira commented 2 years ago

Hi @moubry!

Never mind what I said above, you can only watch a channel from the Client SDKs. Just to double check that the issue is actually from not observing that missing event, when you create a channel from the ServerSide SDK if you turn on the web socket logging in the iOS SDK, you should observe in the logs that "channel.created" event.

LogConfig.level = .debug
LogConfig.subsystems = .webSocket

Meanwhile, we will try to reproduce this ourselves and provide a fix as soon as possible 👍

Best, Nuno

moubry commented 2 years ago

Thanks for looking into it, @nuno-vieira!

I turned on verbose logging. Immediately after creating the channel on the server, I do successfully see one notification.added_to_channel event in the client, but I do not see a channel.created event, and channelListController.observableObject.$channels does not notify me of the new channel. Then when I quit and restart the app, I see the error about failing to decode channel.created right after /sync is called for missing events.

Just to be sure, I set LogConfig.subsystems = .all and I don’t see anything about channel.created when the channel is created, however I’m not sure if I should or not. If it’s useful, here’s the verbose logs (all subsystems) when my client receives the new channel: new_channel.log.

nuno-vieira commented 2 years ago

Hi @moubry!

That is all very useful information, thank you!

We will let you know once we have more details about the issue.

Best, Nuno

nuno-vieira commented 2 years ago

Hi @moubry!

After some more investigation, it seems everything is working fine from our testing, but having a better look at your example, what might actually be happening is a misuse of our Combine helpers. I recommend you to take a look at this example on how to work with the ChannelList Observable Object here: https://github.com/GetStream/stream-chat-swift/blob/develop/StreamChatSample/Samples/SwiftUISimpleChat/ChannelListView.swift#L14

It seems the way you are handling the channel list updates is not the correct way, and that is why the updates are not performed. You should use the ChannelList Controller as a @StateObject and just use the channelList.channels directly.

Please, let me know if our SwiftUI Sample was helpful. If you still encounter issues, let me know and we can schedule a call 👍

Best, Nuno

moubry commented 2 years ago

Thank you, @nuno-vieira. Yes, I can confirm making this change works for me — when I subscribe to changes in the channels list through @StateObject instead of using onReceive, the issue where channels does not change goes away.

I still see the error in my logs where channel.created cannot be decoded, but I no longer experience the UX symptoms that motivated me to create this issue. It sounds like these were totally separate things that I was conflating together.

Thank you so much for the help!

nuno-vieira commented 2 years ago

Hi @moubry !

Great that we could finally pinpoint the issue! Yes, that event does not seem to be important to handle since it is a bit redundant, at least for now. The other platforms are also not handling it, so for now I'll close this issue :)

Thank you for your patience!