Skip to content

Commit

Permalink
Merge pull request #129 from ably-labs/102-disable-implicit-attach
Browse files Browse the repository at this point in the history
[ECO-5904] Disable implicit attach
  • Loading branch information
lawrence-forooghian authored Nov 20, 2024
2 parents bb9e357 + cf5b20d commit 5344d7f
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 27 deletions.
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()

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
}

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

0 comments on commit 5344d7f

Please sign in to comment.