Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for transforming Messages and Channels #3564

Draft
wants to merge 5 commits into
base: develop
Choose a base branch
from

Conversation

nuno-vieira
Copy link
Member

@nuno-vieira nuno-vieira commented Jan 16, 2025

🔗 Issue Links

TODO

🎯 Goal

Add support for transforming Messages and Channels. This will allow changing some of the data of these models at run time.

The most common use case for this feature is to easily allow E2EE encryption of Channels and Messages.

📝 Summary

A new StreamModelsTransformer protocol is added and can be implemented by customers. Currently, only Channels and Messages are transformable, with limited data that can be changed.

Example usage:

class CustomStreamModelsTransformer: StreamModelsTransformer {
    func transform(channel: ChatChannel) -> ChatChannel {
        channel.replacing(
            name: "Hey!",
            imageURL: channel.imageURL,
            extraData: channel.extraData
        )
    }

    func transform(message: ChatMessage) -> ChatMessage {
        message.replacing(
            text: "Yo",
            extraData: message.extraData,
            attachments: message.allAttachments
        )
    }

    func transform(newMessageInfo: NewMessageTransformableInfo) -> NewMessageTransformableInfo {
        newMessageInfo.replacing(
            text: "Changed",
            attachments: newMessageInfo.attachments,
            extraData: newMessageInfo.extraData
        )
    }
}

With the replacing function, if customers want to only change the data of a specific controller, they can do it like so:

let transformedChannels = controller.channels.map {
    $0.replacing(name: "YO", imageURL: $0.imageURL, extraData: $0.extraData)
}

// with combine:
controller.observableObject
      .channels
      .publisher
      .map {
         $0.replacing(name: "YO", imageURL: $0.imageURL, extraData: $0.extraData)
      }
      .sink(receiveValue: { transformedChannel in
         print(transformedChannel)
      })

🛠 Implementation

The way it works is that we pass the transformer to our DTO -> Mapping, and right before creating the models, we pass it to the transformer so that a new Message/Channel can be returned.

For now, the models from events are not mapped since they are not rendered in the UI. We can add them in the future if needed.

🧪 Manual Testing Notes

N/A

☑️ Contributor Checklist

  • I have signed the Stream CLA (required)
  • This change should be manually QAed
  • Changelog is updated with client-facing changes
  • Changelog is updated with new localization keys
  • New code is covered by unit tests
  • Documentation has been updated in the docs-content repo

@Stream-SDK-Bot
Copy link
Collaborator

Stream-SDK-Bot commented Jan 16, 2025

SDK Size

title develop branch diff status
StreamChat 7.0 MB 7.02 MB +18 KB 🟢
StreamChatUI 4.77 MB 4.77 MB 0 KB 🟢

@nuno-vieira nuno-vieira force-pushed the add/stream-models-transformer-support branch from 6066703 to 206c580 Compare January 16, 2025 17:57
Copy link

1 Message
📖 Skipping Danger since the Pull Request is classed as Draft/Work In Progress

Generated by 🚫 Danger

Comment on lines +1651 to +1653
try $0.asModel(
transformer: self.client.config.modelsTransformer
) as ChatChannel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When thinking ahead to cleaning up deletedMessagesVisibility and others then that would require to have an access to ChatConfig within asModel(). Therefore, should we just pass in the config here and read the transformer within the asModel implementation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I'm not sure how that will look like, but we can improve it once we work on that 👍 But I don't think we need to pass the whole config to the asModel though 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, can be changed later if needed. When I briefly checked it then ChannelDTO.asModel requires multiple values from the config.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, in that case I will probably change this yet 👍

@laevandus
Copy link
Contributor

StateLayer needs also this.

@nuno-vieira
Copy link
Member Author

@laevandus I've add it to the state layer already 🤔

@nuno-vieira nuno-vieira force-pushed the add/stream-models-transformer-support branch from c7b09f2 to c2307b6 Compare January 17, 2025 11:27
Copy link
Contributor

@martinmitrevski martinmitrevski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great, I like this approach more! Few more finishing touches (docs, tests) and we're good to 🚢

class CustomStreamModelsTransformer: StreamModelsTransformer {
func transform(channel: ChatChannel) -> ChatChannel {
channel.replacing(
name: "Hey!",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: maybe just uppercase all the letter of the channel name? Would feel weird otherwise to see only "Hey!" everywhere.
Same for messages, some small transformation of the original text would be nice.

@@ -228,6 +228,46 @@ public struct ChatChannel {
self.muteDetails = muteDetails
self.previewMessage = previewMessage
}

public func replacing(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to document this one (and the one below)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants