Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Example/AblyChatExample/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,9 @@ struct ContentView: View {
}

func subscribeToReactions(room: Room) {
room.reactions.subscribe { reaction in
room.reactions.subscribe { event in
withAnimation {
showReaction(reaction.displayedText)
showReaction(event.reaction.displayedText)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Example/AblyChatExample/Mocks/Misc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ enum ReactionType: String, CaseIterable {
}
}

extension Reaction {
extension RoomReaction {
var displayedText: String {
type
}
Expand Down
14 changes: 8 additions & 6 deletions Example/AblyChatExample/Mocks/MockClients.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ class MockMessages: Messages {
)
}

func get(options _: QueryOptions) async throws(ARTErrorInfo) -> any PaginatedResult<Message> {
func history(options _: QueryOptions) async throws(ARTErrorInfo) -> any PaginatedResult<Message> {
MockMessagesPaginatedResult(clientID: clientID, roomName: roomName)
}

Expand Down Expand Up @@ -308,37 +308,39 @@ class MockRoomReactions: RoomReactions {
let clientID: String
let roomName: String

private let mockSubscriptions = MockSubscriptionStorage<Reaction>()
private let mockSubscriptions = MockSubscriptionStorage<RoomReactionEvent>()

init(clientID: String, roomName: String) {
self.clientID = clientID
self.roomName = roomName
}

func send(params: SendReactionParams) async throws(ARTErrorInfo) {
let reaction = Reaction(
let reaction = RoomReaction(
type: params.type,
metadata: [:],
headers: [:],
createdAt: Date(),
clientID: clientID,
isSelf: false
)
mockSubscriptions.emit(reaction)
let event = RoomReactionEvent(type: .reaction, reaction: reaction)
mockSubscriptions.emit(event)
}

@discardableResult
func subscribe(_ callback: @escaping @MainActor (Reaction) -> Void) -> SubscriptionProtocol {
func subscribe(_ callback: @escaping @MainActor (RoomReactionEvent) -> Void) -> SubscriptionProtocol {
mockSubscriptions.create(
randomElement: {
Reaction(
let reaction = RoomReaction(
type: ReactionType.allCases.randomElement()!.emoji,
metadata: [:],
headers: [:],
createdAt: Date(),
clientID: self.clientID,
isSelf: false
)
return RoomReactionEvent(type: .reaction, reaction: reaction)
},
interval: 0.5,
callback: callback
Expand Down
6 changes: 3 additions & 3 deletions Sources/AblyChat/DefaultMessages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ internal final class DefaultMessages: Messages {
try await implementation.subscribe(callback)
}

internal func get(options: QueryOptions) async throws(ARTErrorInfo) -> any PaginatedResult<Message> {
try await implementation.get(options: options)
internal func history(options: QueryOptions) async throws(ARTErrorInfo) -> any PaginatedResult<Message> {
try await implementation.history(options: options)
}

internal func send(params: SendMessageParams) async throws(ARTErrorInfo) -> Message {
Expand Down Expand Up @@ -174,7 +174,7 @@ internal final class DefaultMessages: Messages {
}

// (CHA-M6a) A method must be exposed that accepts the standard Ably REST API query parameters. It shall call the "REST API"#rest-fetching-messages and return a PaginatedResult containing messages, which can then be paginated through.
internal func get(options: QueryOptions) async throws(ARTErrorInfo) -> any PaginatedResult<Message> {
internal func history(options: QueryOptions) async throws(ARTErrorInfo) -> any PaginatedResult<Message> {
do {
return try await chatAPI.getMessages(roomName: roomName, params: options)
} catch {
Expand Down
11 changes: 6 additions & 5 deletions Sources/AblyChat/DefaultRoomReactions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ internal final class DefaultRoomReactions: RoomReactions {
}

@discardableResult
internal func subscribe(_ callback: @escaping @MainActor (Reaction) -> Void) -> SubscriptionProtocol {
internal func subscribe(_ callback: @escaping @MainActor (RoomReactionEvent) -> Void) -> SubscriptionProtocol {
implementation.subscribe(callback)
}

Expand Down Expand Up @@ -52,7 +52,7 @@ internal final class DefaultRoomReactions: RoomReactions {
// (CHA-ER4) A user may subscribe to reaction events in Realtime.
// (CHA-ER4a) A user may provide a listener to subscribe to reaction events. This operation must have no side-effects in relation to room or underlying status. When a realtime message with name roomReaction is received, this message is converted into a reaction object and emitted to subscribers.
@discardableResult
internal func subscribe(_ callback: @escaping @MainActor (Reaction) -> Void) -> SubscriptionProtocol {
internal func subscribe(_ callback: @escaping @MainActor (RoomReactionEvent) -> Void) -> SubscriptionProtocol {
logger.log(message: "Subscribing to reaction events", level: .debug)

// (CHA-ER4c) Realtime events with an unknown name shall be silently discarded.
Expand Down Expand Up @@ -81,16 +81,17 @@ internal final class DefaultRoomReactions: RoomReactions {
)

// (CHA-ER4d) Realtime events that are malformed (unknown fields should be ignored) shall not be emitted to listeners.
let reaction = Reaction(
let reaction = RoomReaction(
type: dto.type,
metadata: dto.metadata ?? [:],
headers: dto.headers ?? [:],
createdAt: timestamp,
clientID: messageClientID,
isSelf: messageClientID == clientID
)
logger.log(message: "Emitting reaction: \(reaction)", level: .debug)
callback(reaction)
let event = RoomReactionEvent(type: .reaction, reaction: reaction)
logger.log(message: "Emitting room reaction: \(reaction)", level: .debug)
callback(event)
} catch {
logger.log(message: "Error processing incoming reaction message: \(error)", level: .error)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/AblyChat/Messages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public protocol Messages: AnyObject, Sendable {
*
* - Returns: A paginated result object that can be used to fetch more messages if available.
*/
func get(options: QueryOptions) async throws(ARTErrorInfo) -> any PaginatedResult<Message>
func history(options: QueryOptions) async throws(ARTErrorInfo) -> any PaginatedResult<Message>

/**
* Send a message in the chat room.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public typealias ReactionMetadata = Metadata
/**
* Represents a room-level reaction.
*/
public struct Reaction: Sendable {
public struct RoomReaction: Sendable {
/**
* The type of the reaction, for example "like" or "love".
*/
Expand Down
33 changes: 24 additions & 9 deletions Sources/AblyChat/RoomReactions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ public protocol RoomReactions: AnyObject, Sendable {
* Subscribes a given listener to the room reactions.
*
* - Parameters:
* - callback: The listener closure for capturing room ``Reaction``.
* - callback: The listener closure for capturing room `RoomReactionEvent`.
*
* - Returns: A subscription that can be used to unsubscribe from ``Reaction`` events.
* - Returns: A subscription that can be used to unsubscribe from `RoomReactionEvent` events.
*/
@discardableResult
func subscribe(_ callback: @escaping @MainActor (Reaction) -> Void) -> SubscriptionProtocol
func subscribe(_ callback: @escaping @MainActor (RoomReactionEvent) -> Void) -> SubscriptionProtocol
}

/// `AsyncSequence` variant of receiving room reactions.
Expand All @@ -37,13 +37,13 @@ public extension RoomReactions {
* - Parameters:
* - bufferingPolicy: The ``BufferingPolicy`` for the created subscription.
*
* - Returns: A subscription `AsyncSequence` that can be used to iterate through ``Reaction`` events.
* - Returns: A subscription `AsyncSequence` that can be used to iterate through `RoomReactionEvent` events.
*/
func subscribe(bufferingPolicy: BufferingPolicy) -> SubscriptionAsyncSequence<Reaction> {
let subscriptionAsyncSequence = SubscriptionAsyncSequence<Reaction>(bufferingPolicy: bufferingPolicy)
func subscribe(bufferingPolicy: BufferingPolicy) -> SubscriptionAsyncSequence<RoomReactionEvent> {
let subscriptionAsyncSequence = SubscriptionAsyncSequence<RoomReactionEvent>(bufferingPolicy: bufferingPolicy)

let subscription = subscribe { reaction in
subscriptionAsyncSequence.emit(reaction)
let subscription = subscribe { event in
subscriptionAsyncSequence.emit(event)
}

subscriptionAsyncSequence.addTerminationHandler {
Expand All @@ -56,7 +56,7 @@ public extension RoomReactions {
}

/// Same as calling ``subscribe(bufferingPolicy:)`` with ``BufferingPolicy/unbounded``.
func subscribe() -> SubscriptionAsyncSequence<Reaction> {
func subscribe() -> SubscriptionAsyncSequence<RoomReactionEvent> {
subscribe(bufferingPolicy: .unbounded)
}
}
Expand Down Expand Up @@ -104,3 +104,18 @@ public struct SendReactionParams: Sendable {
self.headers = headers
}
}

/// Event type for room reaction subscription.
public enum RoomReactionEventType: String, Sendable {
case reaction
}

/// Event emitted by room reaction subscriptions, containing the type and the reaction.
public struct RoomReactionEvent: Sendable {
public let type: RoomReactionEventType
public let reaction: RoomReaction
public init(type: RoomReactionEventType = .reaction, reaction: RoomReaction) {
self.type = type
self.reaction = reaction
}
}
4 changes: 2 additions & 2 deletions Tests/AblyChatTests/DefaultMessagesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ struct DefaultMessagesTests {
let defaultMessages = DefaultMessages(channel: channel, chatAPI: chatAPI, roomName: "basketball", clientID: "clientId", logger: TestLogger())

// When
let paginatedResult = try await defaultMessages.get(options: .init())
let paginatedResult = try await defaultMessages.history(options: .init())

// Then
// CHA-M6a: The method return a PaginatedResult containing messages
Expand Down Expand Up @@ -308,7 +308,7 @@ struct DefaultMessagesTests {
// When
// TODO: avoids compiler crash (https://github.com/ably/ably-chat-swift/issues/233), revert once Xcode 16.3 released
let doIt = {
_ = try await defaultMessages.get(options: .init())
_ = try await defaultMessages.history(options: .init())
}
// Then
await #expect {
Expand Down
8 changes: 4 additions & 4 deletions Tests/AblyChatTests/DefaultRoomReactionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ struct DefaultRoomReactionsTests {
let defaultRoomReactions = DefaultRoomReactions(channel: channel, clientID: "mockClientId", roomName: "basketball", logger: TestLogger())

// When
let subscription = defaultRoomReactions.subscribe { reaction in
let subscription = defaultRoomReactions.subscribe { event in
// Then
#expect(reaction.type == ":like:")
#expect(event.reaction.type == ":like:")
}

// CHA-ER4b
Expand Down Expand Up @@ -89,8 +89,8 @@ struct DefaultRoomReactionsTests {
let defaultRoomReactions = DefaultRoomReactions(channel: channel, clientID: "mockClientId", roomName: "basketball", logger: TestLogger())

// When
defaultRoomReactions.subscribe { reaction in
#expect(reaction.type == ":like:")
defaultRoomReactions.subscribe { event in
#expect(event.reaction.type == ":like:")
}
// will not be received and expectations above will not fail
channel.simulateIncomingMessage(
Expand Down
6 changes: 3 additions & 3 deletions Tests/AblyChatTests/IntegrationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -332,9 +332,9 @@ struct IntegrationTests {
)
)
let rxReactionFromSubscription = try #require(await rxReactionSubscription.first { @Sendable _ in true })
#expect(rxReactionFromSubscription.type == "heart")
#expect(rxReactionFromSubscription.metadata == ["someMetadataKey": .number(123), "someOtherMetadataKey": .string("foo")])
#expect(rxReactionFromSubscription.headers == ["someHeadersKey": .number(456), "someOtherHeadersKey": .string("bar")])
#expect(rxReactionFromSubscription.reaction.type == "heart")
#expect(rxReactionFromSubscription.reaction.metadata == ["someMetadataKey": .number(123), "someOtherMetadataKey": .string("foo")])
#expect(rxReactionFromSubscription.reaction.headers == ["someHeadersKey": .number(456), "someOtherHeadersKey": .string("bar")])

// MARK: - Occupancy

Expand Down