-
Notifications
You must be signed in to change notification settings - Fork 1
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
Send/Receive message draft #45
base: main
Are you sure you want to change the base?
Conversation
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
@umair-ably what was your aim in using existential types (i.e. the |
Also, just to check — are you intending to add tests? |
I started off with a generic implementation hence the protocol itself being passed in. I didn't want the base network "layer" to know about any concrete types in case we add additional implementations of the protocol later. But on second thoughts (and from seeing your solution), I'd already implemented the concrete type to also be fairly generic so I achieve my initial aim too - I just became blind to tying all of it together from staring at this too long 😅 Thanks for this - it perfectly solves the problem! |
Yeah for sure, ideally I want to put the completed implementation of the message spec in this PR... hoping you can review this first to point out any obvious errors/deviations/unnecessary public api changes, etc, and then it'll be a good base to finish the rest off |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a few general thoughts, as requested. Some of the review is quite superficial, will look in more detail at the final one.
Also, please could you make it clear where the behaviour implemented here has come from, to aid with review now and to help our future selves understand it better. My suggestion would be to make sure that:
- the code includes spec references, where they exist (I know that the code that I’m looking at now predates the existence of the spec)
- code that's based on the implementation in the JS SDK should state this and should state the specific version (i.e. commit hash) of the JS SDK that it's based on – either do this at the place where the behaviour is implemented, or in commit message, whichever seems most appropriate
@@ -1,24 +1,160 @@ | |||
import AblyChat | |||
// |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume all this stuff is going to get replaced by what Marat’s doing in #34?
Sources/AblyChat/Channel.swift
Outdated
@@ -0,0 +1,22 @@ | |||
import Ably | |||
|
|||
func getChannel(_ name: String, realtime: RealtimeClient, opts: ARTRealtimeChannelOptions? = nil) -> any RealtimeChannelProtocol { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be an internal protocol extension on RealtimeProtocol
Sources/AblyChat/Channel.swift
Outdated
} | ||
|
||
resolvedOptions.params = opts?.params?.merging( | ||
DEFAULT_CHANNEL_OPTIONS.params ?? [:], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why capitalised?
Sources/AblyChat/ChatAPI.swift
Outdated
|
||
func getMessages(roomId: String, params: QueryOptions) async throws -> any PaginatedResult<Message> { | ||
let endpoint = "/chat/v1/rooms/\(roomId)/messages" | ||
let response: any PaginatedResult<Message> = try await makeAuthorizedPaginatedRequest(endpoint, params: params.toDictionary()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't this directly be returned?
Sources/AblyChat/ChatAPI.swift
Outdated
} | ||
|
||
let response: SendMessageResponse = try await makeAuthorizedRequest(endpoint, method: "POST", body: body) | ||
let timeIntervalInSeconds = TimeInterval(integerLiteral: response.createdAt) / 1000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't make sense to initialise a TimeInterval
with a number of milliseconds — TimeInterval
always represents seconds. Also this usage of the TimeInterval(integerLiteral:)
initializer looks a bit out of place here; I'd suggest removing it.
Sources/AblyChat/Metadata.swift
Outdated
import Foundation | ||
|
||
// Define protocol for types that are Sendable and Encodable | ||
public protocol SendableEncodable: Sendable, Codable, Hashable {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a need for this? The name is particularly confusing because it doesn't mention Hashable
.
Sources/AblyChat/Metadata.swift
Outdated
public protocol SendableEncodable: Sendable, Codable, Hashable {} | ||
|
||
// Example conforming type | ||
public struct MetadataValue: SendableEncodable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the purpose of this new public type?
Sources/AblyChat/Timeserial.swift
Outdated
} | ||
|
||
// DefaultTimeserial Class | ||
final class DefaultTimeserial: Timeserial { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why a class? Looks like something struct-like to me. Does it need reference semantics?
Sources/AblyChat/Timeserial.swift
Outdated
} | ||
|
||
// Convert to String | ||
func toString() -> String { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a function which gets called automatically in JS. No such thing happens in Swift so this function doesn't really serve any purpose.
Sources/AblyChat/Version.swift
Outdated
// Update this when you release a new version | ||
|
||
// Version information | ||
public let VERSION = "0.1.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These capitalised constants aren’t very Swift-like; they could just be let
s` with camel-cased names.
Sources/AblyChat/Room.swift
Outdated
self.realtime = realtime | ||
self.roomID = roomID | ||
self.options = options | ||
self.chatAPI = chatAPI | ||
|
||
self._messages = await DefaultMessages( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why await
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@maratal for some reason I can't reply to #45 (comment) so I’ll do it here ("I agree that creating initializers that must be called with await
should be avoided.")
I wasn’t saying that it should necessarily be avoided (e.g. in #53 I’m making the room lifecycle manager initializer async
because it needs to perform some async
setup work before it can be used); I was just curious what the need was here.
# Conflicts: # Package@swift-6.swift # Sources/AblyChat/ChatClient.swift # Sources/AblyChat/Room.swift # Sources/AblyChat/Rooms.swift Small refactor and start documenting spec points
Before removing from draft please could you also tidy up the commits; i.e. in addition to what I mentioned here also make sure to remove the merge commit and to incorporate linting fixes into the commit that introduced the errors. Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes in subscribe
method prototype should be addressed as well as other minor suggestions.
|
||
// (CHA-M6) Messages should be queryable from a paginated REST API. | ||
public func getMessages(roomId: String, params: QueryOptions) async throws -> any PaginatedResult<Message> { | ||
let endpoint = "/chat/v1/rooms/\(roomId)/messages" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be constructed from hardcoded strings. Ditto elsewhere.
let response: SendMessageResponse = try await makeAuthorizedRequest(endpoint, method: "POST", body: body) | ||
|
||
// response.createdAt is in milliseconds, convert it to seconds | ||
let createdAtInSeconds = TimeInterval(integerLiteral: response.createdAt) / 1000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's TimeInterval(response.createdAt / 1000)
, TimeInterval
is just a typealias for Double
.
import Ably | ||
|
||
// Typealias for the timeserial used to sync message subscriptions with. This is a string representation of a timestamp. | ||
private typealias FromSerial = String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not TimeSerial
then? FromSerial
gives no understanding what this type is for.
private let chatAPI: ChatAPI | ||
private let clientID: String | ||
private var listenerSubscriptionPoints: MessageListeners = [:] | ||
private let realtime: RealtimeClient |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think ChatAPI
should absorb all the functionality of the RealtimeClient
, so you could use only ChatAPI
elsewhere.
self.clientID = clientID | ||
self.realtime = realtime | ||
|
||
Task { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that creating initializers that must be called with await
should be avoided.
@@ -20,23 +20,34 @@ public protocol Room: AnyObject, Sendable { | |||
internal actor DefaultRoom: Room { | |||
internal nonisolated let roomID: String | |||
internal nonisolated let options: RoomOptions | |||
private let chatAPI: ChatAPI | |||
|
|||
private let _messages: any Messages |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Subjective, but I would avoid using _
as swift private vars name notation. Might as well be private let defaultMessages: DefaultMessages
import Foundation | ||
|
||
// Timeserial Protocol | ||
internal protocol Timeserial: Sendable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it falls under camel case notation here - TimeSerial
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
asked here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Timeserial
is correct, apparently
public let channelOptionsAgentString = "chat-ios/\(version)" | ||
|
||
// Default channel options | ||
public let defaultChannelOptions: ARTRealtimeChannelOptions = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be better inside ARTRealtimeChannelOptions
as a static var - ARTRealtimeChannelOptions.default
public let version = "0.1.0" | ||
|
||
// Channel options agent string | ||
public let channelOptionsAgentString = "chat-ios/\(version)" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't it include "ably" as prefix? Also "ios" will be added by cocoa. WDYT @lawrence-forooghian
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume this is copied from the JS implementation; let's wait and see if @umair-ably adds a spec point reference
// Update this when you release a new version | ||
|
||
// Version information | ||
public let version = "0.1.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't it be in a config file?
- move getChannel inside the ChatAPI to avoid needing both Realtime and ChatAPI when initialising DefaultMessages
…il due to out of memory error
@lawrence-forooghian - I was wrong in that the macOS and iOS features diverged... I'm actually targeting iOS 16 but not macOS 16 hence the need to check available OS version... ChatAPI is the class to check (where I'm passing in a Protocol as a parameter instead of a concrete implementation)
Will merge main and resolve conflicts but this draft is good enough to get a gist of what is going on. I'll also refactor some of the access given to certain classes/structs, as well as improve general organisation
https://ably.atlassian.net/browse/ECO-4942