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

[ECO-5904] Disable implicit attach #129

Merged
merged 4 commits into from
Nov 20, 2024
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
51 changes: 35 additions & 16 deletions Sources/AblyChat/Dependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,44 @@ public protocol RealtimeChannelsProtocol: ARTRealtimeChannelsProtocol, Sendable
/// Expresses the requirements of the object returned by ``RealtimeChannelsProtocol.get(_:)``.
public protocol RealtimeChannelProtocol: ARTRealtimeChannelProtocol, Sendable {}

/// Like (a subset of) `ARTRealtimeChannelOptions` but with value semantics. (It’s unfortunate that `ARTRealtimeChannelOptions` doesn’t have a `-copy` method.)
internal struct RealtimeChannelOptions {
internal var modes: ARTChannelMode
internal var params: [String: String]?
internal var attachOnSubscribe: Bool

internal init() {
// Get our default values from ably-cocoa
let artRealtimeChannelOptions = ARTRealtimeChannelOptions()
modes = artRealtimeChannelOptions.modes
params = artRealtimeChannelOptions.params
attachOnSubscribe = artRealtimeChannelOptions.attachOnSubscribe
}

internal var toARTRealtimeChannelOptions: ARTRealtimeChannelOptions {
let result = ARTRealtimeChannelOptions()
result.modes = modes
result.params = params
result.attachOnSubscribe = attachOnSubscribe
return result
}
}

internal extension RealtimeClientProtocol {
// Function to get the channel with merged options
func getChannel(_ name: String, opts: ARTRealtimeChannelOptions? = nil) -> any RealtimeChannelProtocol {
// Create a new instance of ARTRealtimeChannelOptions if opts is nil
let resolvedOptions = ARTRealtimeChannelOptions()

// Merge params if available, using opts first, then defaultChannelOptions as fallback
resolvedOptions.params = (opts?.params ?? [:]).merging(
defaultChannelOptions.params ?? [:]
) { _, new in new }

// Apply other options from `opts` if necessary
if let customOpts = opts {
resolvedOptions.modes = customOpts.modes
resolvedOptions.cipher = customOpts.cipher
resolvedOptions.attachOnSubscribe = customOpts.attachOnSubscribe
func getChannel(_ name: String, opts: RealtimeChannelOptions? = nil) -> any RealtimeChannelProtocol {
var resolvedOptions = opts ?? .init()

// Add in the default params
resolvedOptions.params = (resolvedOptions.params ?? [:]).merging(
defaultChannelParams
) { _, new
in new
}

// Return the resolved channel
return channels.get(name, options: resolvedOptions)
// CHA-GP2a
resolvedOptions.attachOnSubscribe = false

return channels.get(name, options: resolvedOptions.toARTRealtimeChannelOptions)
}
}
2 changes: 1 addition & 1 deletion Sources/AblyChat/Room.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ internal actor DefaultRoom<LifecycleManagerFactory: RoomLifecycleManagerFactory>
RoomFeature.presence,
RoomFeature.occupancy,
].map { feature in
let channelOptions = ARTRealtimeChannelOptions()
var channelOptions = RealtimeChannelOptions()

// channel setup for presence and occupancy
if feature == .presence {
Expand Down
12 changes: 3 additions & 9 deletions Sources/AblyChat/Version.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,8 @@ import Ably

// Update this when you release a new version
// Version information
public let version = "0.1.0"
internal let version = "0.1.0"

// Channel options agent string
public let channelOptionsAgentString = "chat-ios/\(version)"
internal let channelOptionsAgentString = "chat-ios/\(version)"

// Default channel options
public var defaultChannelOptions: ARTRealtimeChannelOptions {
let options = ARTRealtimeChannelOptions()
options.params = ["agent": channelOptionsAgentString]
return options
}
internal let defaultChannelParams = ["agent": channelOptionsAgentString]
20 changes: 20 additions & 0 deletions Tests/AblyChatTests/DefaultRoomTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@ import Ably
import Testing

struct DefaultRoomTests {
// MARK: - Fetching channels

// @spec CHA-GP2a
@Test
func disablesImplicitAttach() async throws {
// Given: A DefaultRoom instance
let channelsList = [
MockRealtimeChannel(name: "basketball::$chat::$chatMessages", attachResult: .success),
MockRealtimeChannel(name: "basketball::$chat::$reactions", attachResult: .success),
]
let channels = MockChannels(channels: channelsList)
let realtime = MockRealtime.create(channels: channels)
_ = try await DefaultRoom(realtime: realtime, chatAPI: ChatAPI(realtime: realtime), roomID: "basketball", options: .init(), logger: TestLogger(), lifecycleManagerFactory: MockRoomLifecycleManagerFactory())

// Then: When it fetches a channel, it does so with the `attachOnSubscribe` channel option set to false
let channelsGetArguments = channels.getArguments
#expect(!channelsGetArguments.isEmpty)
#expect(channelsGetArguments.allSatisfy { $0.options.attachOnSubscribe == false })
}

// MARK: - Features

// @spec CHA-M1
Expand Down
16 changes: 15 additions & 1 deletion Tests/AblyChatTests/Mocks/MockChannels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,34 @@ final class MockChannels: RealtimeChannelsProtocol, @unchecked Sendable {
private let channels: [MockRealtimeChannel]
private let mutex = NSLock()
/// Access must be synchronized via ``mutex``.
private(set) var _getArguments: [(name: String, options: ARTRealtimeChannelOptions)] = []
/// Access must be synchronized via ``mutex``.
private(set) var _releaseArguments: [String] = []

init(channels: [MockRealtimeChannel]) {
self.channels = channels
}

func get(_ name: String, options _: ARTRealtimeChannelOptions) -> MockRealtimeChannel {
func get(_ name: String, options: ARTRealtimeChannelOptions) -> MockRealtimeChannel {
mutex.lock()
_getArguments.append((name: name, options: options))
mutex.unlock()

lawrence-forooghian marked this conversation as resolved.
Show resolved Hide resolved
guard let channel = (channels.first { $0.name == name }) else {
fatalError("There is no mock channel with name \(name)")
}

return channel
}

var getArguments: [(name: String, options: ARTRealtimeChannelOptions)] {
let result: [(name: String, options: ARTRealtimeChannelOptions)]
mutex.lock()
result = _getArguments
mutex.unlock()
return result
}
lawrence-forooghian marked this conversation as resolved.
Show resolved Hide resolved

func exists(_: String) -> Bool {
fatalError("Not implemented")
}
Expand Down