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
2 changes: 1 addition & 1 deletion .cursor/rules/swift.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ When writing Swift:
- When writing a JSON string, favour using Swift raw string literals instead of escaping double quotes.
- When you need to import the following modules inside the AblyLiveObjects library code (that is, in non-test code), do so in the following way:
- Ably: use `import Ably`
- AblyPlugin: use `internal import AblyPlugin`
- `_AblyPluginSupportPrivate`: use `internal import _AblyPluginSupportPrivate`
- When writing an array literal that starts with an initializer expression, start the initializer expression on the line after the opening square bracket of the array literal. That is, instead of writing:
```swift
objectMessages: [InboundObjectMessage(
Expand Down
2 changes: 1 addition & 1 deletion .cursor/rules/testing.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ When writing tests:
- When you need to import the following modules in the tests, do so in the following way:
- Ably: use `import Ably`
- AblyLiveObjects: use `@testable import AblyLiveObjects`
- AblyPlugin: use `import AblyPlugin`; _do not_ do `internal import`
- `_AblyPluginSupportPrivate`: use `import _AblyPluginSupportPrivate`; _do not_ do `internal import`
- When you need to pass a logger to internal components in the tests, pass `TestLogger()`.
- When you need to unwrap an optional value in the tests, favour using `#require` instead of `guard let`.
- When creating `testsOnly_` property declarations, do not write generic comments of the form "Test-only access to the private createOperationIsMerged property"; the meaning of these properties is already well understood.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 8 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ let package = Package(
.package(
path: "ably-cocoa",
),
.package(
url: "https://github.com/ably/ably-cocoa-plugin-support",
from: "0.1.0",
),
.package(
url: "https://github.com/apple/swift-argument-parser",
from: "1.5.0",
Expand All @@ -47,8 +51,8 @@ let package = Package(
package: "ably-cocoa",
),
.product(
name: "AblyPlugin",
package: "ably-cocoa",
name: "_AblyPluginSupportPrivate",
package: "ably-cocoa-plugin-support",
),
],
),
Expand All @@ -61,8 +65,8 @@ let package = Package(
package: "ably-cocoa",
),
.product(
name: "AblyPlugin",
package: "ably-cocoa",
name: "_AblyPluginSupportPrivate",
package: "ably-cocoa-plugin-support",
),
],
resources: [
Expand Down
11 changes: 6 additions & 5 deletions Sources/AblyLiveObjects/Internal/ARTClientOptions+Objects.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
internal import AblyPlugin
internal import _AblyPluginSupportPrivate
import Ably

internal extension ARTClientOptions {
private class Box<T> {
Expand All @@ -14,9 +15,9 @@ internal extension ARTClientOptions {
/// Can be overriden for testing purposes.
var garbageCollectionOptions: InternalDefaultRealtimeObjects.GarbageCollectionOptions? {
get {
let optionsValue = PluginAPI.sharedInstance().pluginOptionsValue(
let optionsValue = Plugin.defaultPluginAPI.pluginOptionsValue(
forKey: Self.garbageCollectionOptionsKey,
clientOptions: self,
clientOptions: asPluginPublicClientOptions,
)

guard let optionsValue else {
Expand All @@ -35,10 +36,10 @@ internal extension ARTClientOptions {
preconditionFailure("Not implemented the ability to un-set GC options")
}

PluginAPI.sharedInstance().setPluginOptionsValue(
Plugin.defaultPluginAPI.setPluginOptionsValue(
Box<InternalDefaultRealtimeObjects.GarbageCollectionOptions>(boxed: newValue),
forKey: Self.garbageCollectionOptionsKey,
clientOptions: self,
clientOptions: asPluginPublicClientOptions,
)
}
}
Expand Down
24 changes: 12 additions & 12 deletions Sources/AblyLiveObjects/Internal/CoreSDK.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
internal import _AblyPluginSupportPrivate
import Ably
internal import AblyPlugin

/// The API that the internal components of the SDK (that is, `DefaultLiveObjects` and down) use to interact with our core SDK (i.e. ably-cocoa).
///
/// This provides us with a mockable interface to ably-cocoa, and it also allows internal components and their tests not to need to worry about some of the boring details of how we bridge Swift types to AblyPlugin's Objective-C API (i.e. boxing).
/// This provides us with a mockable interface to ably-cocoa, and it also allows internal components and their tests not to need to worry about some of the boring details of how we bridge Swift types to `_AblyPluginSupportPrivate`'s Objective-C API (i.e. boxing).
internal protocol CoreSDK: AnyObject, Sendable {
/// Implements the internal `#publish` method of RTO15.
func publish(objectMessages: [OutboundObjectMessage]) async throws(InternalError)
Expand All @@ -14,17 +14,17 @@ internal protocol CoreSDK: AnyObject, Sendable {
func testsOnly_overridePublish(with newImplementation: @escaping ([OutboundObjectMessage]) async throws(InternalError) -> Void)

/// Returns the current state of the Realtime channel that this wraps.
var channelState: ARTRealtimeChannelState { get }
var channelState: _AblyPluginSupportPrivate.RealtimeChannelState { get }
}

internal final class DefaultCoreSDK: CoreSDK {
/// Used to synchronize access to internal mutable state.
private let mutex = NSLock()

private let channel: AblyPlugin.RealtimeChannel
private let client: AblyPlugin.RealtimeClient
private let channel: _AblyPluginSupportPrivate.RealtimeChannel
private let client: _AblyPluginSupportPrivate.RealtimeClient
private let pluginAPI: PluginAPIProtocol
private let logger: AblyPlugin.Logger
private let logger: Logger

/// If set to true, ``publish(objectMessages:)`` will behave like a no-op.
///
Expand All @@ -34,10 +34,10 @@ internal final class DefaultCoreSDK: CoreSDK {
private nonisolated(unsafe) var overriddenPublishImplementation: (([OutboundObjectMessage]) async throws -> Void)?

internal init(
channel: AblyPlugin.RealtimeChannel,
client: AblyPlugin.RealtimeClient,
channel: _AblyPluginSupportPrivate.RealtimeChannel,
client: _AblyPluginSupportPrivate.RealtimeClient,
pluginAPI: PluginAPIProtocol,
logger: AblyPlugin.Logger
logger: Logger
) {
self.channel = channel
self.client = client
Expand Down Expand Up @@ -81,8 +81,8 @@ internal final class DefaultCoreSDK: CoreSDK {
}
}

internal var channelState: ARTRealtimeChannelState {
channel.state
internal var channelState: _AblyPluginSupportPrivate.RealtimeChannelState {
pluginAPI.state(for: channel)
}
}

Expand All @@ -97,7 +97,7 @@ internal extension CoreSDK {
/// - operationDescription: A description of the operation being performed, used in error messages
/// - Throws: `ARTErrorInfo` with code 90001 and statusCode 400 if the channel is in any of the invalid states
func validateChannelState(
notIn invalidStates: [ARTRealtimeChannelState],
notIn invalidStates: [_AblyPluginSupportPrivate.RealtimeChannelState],
operationDescription: String,
) throws(ARTErrorInfo) {
let currentChannelState = channelState
Expand Down
48 changes: 25 additions & 23 deletions Sources/AblyLiveObjects/Internal/DefaultInternalPlugin.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
internal import AblyPlugin
internal import _AblyPluginSupportPrivate
import Ably

// We explicitly import the NSObject class, else it seems to get transitively imported from `internal import AblyPlugin`, leading to the error "Class cannot be declared public because its superclass is internal".
// We explicitly import the NSObject class, else it seems to get transitively imported from `internal import _AblyPluginSupportPrivate`, leading to the error "Class cannot be declared public because its superclass is internal".
import ObjectiveC.NSObject

/// The default implementation of `AblyPlugin`'s `LiveObjectsInternalPluginProtocol`. Implements the interface that ably-cocoa uses to access the functionality provided by the LiveObjects plugin.
/// The default implementation of `_AblyPluginSupportPrivate`'s `LiveObjectsInternalPluginProtocol`. Implements the interface that ably-cocoa uses to access the functionality provided by the LiveObjects plugin.
@objc
internal final class DefaultInternalPlugin: NSObject, AblyPlugin.LiveObjectsInternalPluginProtocol {
private let pluginAPI: AblyPlugin.PluginAPIProtocol
internal final class DefaultInternalPlugin: NSObject, _AblyPluginSupportPrivate.LiveObjectsInternalPluginProtocol {
private let pluginAPI: _AblyPluginSupportPrivate.PluginAPIProtocol

internal init(pluginAPI: AblyPlugin.PluginAPIProtocol) {
internal init(pluginAPI: _AblyPluginSupportPrivate.PluginAPIProtocol) {
self.pluginAPI = pluginAPI
}

Expand All @@ -20,7 +21,7 @@ internal final class DefaultInternalPlugin: NSObject, AblyPlugin.LiveObjectsInte
/// Retrieves the `RealtimeObjects` for this channel.
///
/// We expect this value to have been previously set by ``prepare(_:)``.
internal static func realtimeObjects(for channel: AblyPlugin.RealtimeChannel, pluginAPI: AblyPlugin.PluginAPIProtocol) -> InternalDefaultRealtimeObjects {
internal static func realtimeObjects(for channel: _AblyPluginSupportPrivate.RealtimeChannel, pluginAPI: _AblyPluginSupportPrivate.PluginAPIProtocol) -> InternalDefaultRealtimeObjects {
guard let pluginData = pluginAPI.pluginDataValue(forKey: pluginDataKey, channel: channel) else {
// InternalPlugin.prepare was not called
fatalError("To access LiveObjects functionality, you must pass the LiveObjects plugin in the client options when creating the ARTRealtime instance: `clientOptions.plugins = [.liveObjects: AblyLiveObjects.Plugin.self]`")
Expand All @@ -33,11 +34,12 @@ internal final class DefaultInternalPlugin: NSObject, AblyPlugin.LiveObjectsInte
// MARK: - LiveObjectsInternalPluginProtocol

// Populates the channel's `objects` property.
internal func prepare(_ channel: AblyPlugin.RealtimeChannel, client: AblyPlugin.RealtimeClient) {
let logger = pluginAPI.logger(for: channel)
internal func prepare(_ channel: _AblyPluginSupportPrivate.RealtimeChannel, client: _AblyPluginSupportPrivate.RealtimeClient) {
let pluginLogger = pluginAPI.logger(for: channel)
let callbackQueue = pluginAPI.callbackQueue(for: client)
let options = pluginAPI.options(for: client)
let options = ARTClientOptions.castPluginPublicClientOptions(pluginAPI.options(for: client))

let logger = DefaultLogger(pluginLogger: pluginLogger, pluginAPI: pluginAPI)
logger.log("LiveObjects.DefaultInternalPlugin received prepare(_:)", level: .debug)
let liveObjects = InternalDefaultRealtimeObjects(
logger: logger,
Expand All @@ -49,14 +51,14 @@ internal final class DefaultInternalPlugin: NSObject, AblyPlugin.LiveObjectsInte
}

/// Retrieves the internally-typed `objects` property for the channel.
private func realtimeObjects(for channel: AblyPlugin.RealtimeChannel) -> InternalDefaultRealtimeObjects {
private func realtimeObjects(for channel: _AblyPluginSupportPrivate.RealtimeChannel) -> InternalDefaultRealtimeObjects {
Self.realtimeObjects(for: channel, pluginAPI: pluginAPI)
}

/// A class that wraps an object message.
///
/// We need this intermediate type because we want object messages to be structs — because they're nicer to work with internally — but a struct can't conform to the class-bound `AblyPlugin.ObjectMessageProtocol`.
private final class ObjectMessageBox<T>: AblyPlugin.ObjectMessageProtocol where T: Sendable {
/// We need this intermediate type because we want object messages to be structs — because they're nicer to work with internally — but a struct can't conform to the class-bound `_AblyPluginSupportPrivate.ObjectMessageProtocol`.
private final class ObjectMessageBox<T>: _AblyPluginSupportPrivate.ObjectMessageProtocol where T: Sendable {
internal let objectMessage: T

init(objectMessage: T) {
Expand All @@ -68,9 +70,9 @@ internal final class DefaultInternalPlugin: NSObject, AblyPlugin.LiveObjectsInte
_ serialized: [String: Any],
context: DecodingContextProtocol,
format: EncodingFormat,
error errorPtr: AutoreleasingUnsafeMutablePointer<ARTErrorInfo?>?,
error errorPtr: AutoreleasingUnsafeMutablePointer<_AblyPluginSupportPrivate.PublicErrorInfo?>?,
) -> (any ObjectMessageProtocol)? {
let wireObject = WireValue.objectFromAblyPluginData(serialized)
let wireObject = WireValue.objectFromPluginSupportData(serialized)

do {
let wireObjectMessage = try InboundWireObjectMessage(
Expand All @@ -83,28 +85,28 @@ internal final class DefaultInternalPlugin: NSObject, AblyPlugin.LiveObjectsInte
)
return ObjectMessageBox(objectMessage: objectMessage)
} catch {
errorPtr?.pointee = error.toARTErrorInfo()
errorPtr?.pointee = error.toARTErrorInfo().asPluginPublicErrorInfo
return nil
}
}

internal func encodeObjectMessage(
_ publicObjectMessage: any AblyPlugin.ObjectMessageProtocol,
_ publicObjectMessage: any _AblyPluginSupportPrivate.ObjectMessageProtocol,
format: EncodingFormat,
) -> [String: Any] {
guard let outboundObjectMessageBox = publicObjectMessage as? ObjectMessageBox<OutboundObjectMessage> else {
preconditionFailure("Expected to receive the same OutboundObjectMessage type as we emit")
}

let wireObjectMessage = outboundObjectMessageBox.objectMessage.toWire(format: format)
return wireObjectMessage.toWireObject.toAblyPluginDataDictionary
return wireObjectMessage.toWireObject.toPluginSupportDataDictionary
}

internal func onChannelAttached(_ channel: AblyPlugin.RealtimeChannel, hasObjects: Bool) {
internal func onChannelAttached(_ channel: _AblyPluginSupportPrivate.RealtimeChannel, hasObjects: Bool) {
realtimeObjects(for: channel).onChannelAttached(hasObjects: hasObjects)
}

internal func handleObjectProtocolMessage(withObjectMessages publicObjectMessages: [any AblyPlugin.ObjectMessageProtocol], channel: AblyPlugin.RealtimeChannel) {
internal func handleObjectProtocolMessage(withObjectMessages publicObjectMessages: [any _AblyPluginSupportPrivate.ObjectMessageProtocol], channel: _AblyPluginSupportPrivate.RealtimeChannel) {
guard let inboundObjectMessageBoxes = publicObjectMessages as? [ObjectMessageBox<InboundObjectMessage>] else {
preconditionFailure("Expected to receive the same InboundObjectMessage type as we emit")
}
Expand All @@ -116,7 +118,7 @@ internal final class DefaultInternalPlugin: NSObject, AblyPlugin.LiveObjectsInte
)
}

internal func handleObjectSyncProtocolMessage(withObjectMessages publicObjectMessages: [any AblyPlugin.ObjectMessageProtocol], protocolMessageChannelSerial: String?, channel: AblyPlugin.RealtimeChannel) {
internal func handleObjectSyncProtocolMessage(withObjectMessages publicObjectMessages: [any _AblyPluginSupportPrivate.ObjectMessageProtocol], protocolMessageChannelSerial: String?, channel: _AblyPluginSupportPrivate.RealtimeChannel) {
guard let inboundObjectMessageBoxes = publicObjectMessages as? [ObjectMessageBox<InboundObjectMessage>] else {
preconditionFailure("Expected to receive the same InboundObjectMessage type as we emit")
}
Expand All @@ -133,8 +135,8 @@ internal final class DefaultInternalPlugin: NSObject, AblyPlugin.LiveObjectsInte

internal static func sendObject(
objectMessages: [OutboundObjectMessage],
channel: AblyPlugin.RealtimeChannel,
client: AblyPlugin.RealtimeClient,
channel: _AblyPluginSupportPrivate.RealtimeChannel,
client: _AblyPluginSupportPrivate.RealtimeClient,
pluginAPI: PluginAPIProtocol,
) async throws(InternalError) {
let objectMessageBoxes: [ObjectMessageBox<OutboundObjectMessage>] = objectMessages.map { .init(objectMessage: $0) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
internal import _AblyPluginSupportPrivate
import Ably
internal import AblyPlugin
import Foundation

/// This provides the implementation behind ``PublicDefaultLiveCounter``, via internal versions of the ``LiveCounter`` API.
Expand All @@ -21,7 +21,7 @@ internal final class InternalDefaultLiveCounter: Sendable {
}
}

private let logger: AblyPlugin.Logger
private let logger: Logger
private let userCallbackQueue: DispatchQueue
private let clock: SimpleClock

Expand All @@ -30,7 +30,7 @@ internal final class InternalDefaultLiveCounter: Sendable {
internal convenience init(
testsOnly_data data: Double,
objectID: String,
logger: AblyPlugin.Logger,
logger: Logger,
userCallbackQueue: DispatchQueue,
clock: SimpleClock
) {
Expand All @@ -40,7 +40,7 @@ internal final class InternalDefaultLiveCounter: Sendable {
private init(
data: Double,
objectID: String,
logger: AblyPlugin.Logger,
logger: Logger,
userCallbackQueue: DispatchQueue,
clock: SimpleClock
) {
Expand All @@ -56,7 +56,7 @@ internal final class InternalDefaultLiveCounter: Sendable {
/// - objectID: The value for the "private objectId field" of RTO5c1b1a.
internal static func createZeroValued(
objectID: String,
logger: AblyPlugin.Logger,
logger: Logger,
userCallbackQueue: DispatchQueue,
clock: SimpleClock,
) -> Self {
Expand Down
Loading