Skip to content

Commit

Permalink
Merge pull request #1998 from ably/feature/ECO-5116-chat-support
Browse files Browse the repository at this point in the history
[ECO-5116] Added new fields to the `ARTMessage`.
  • Loading branch information
maratal authored Nov 26, 2024
2 parents a241afa + 9f1cbd9 commit 5a9b7ba
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 14 deletions.
1 change: 1 addition & 0 deletions Source/ARTBaseMessage.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ - (id)copyWithZone:(NSZone *)zone {
message->_data = [self.data copy];
message->_connectionId = self.connectionId;
message->_encoding = self.encoding;
message->_extras = self.extras;
return message;
}

Expand Down
43 changes: 35 additions & 8 deletions Source/ARTJsonLikeEncoder.m
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,11 @@ - (NSString *)formatAsString {
}

- (ARTMessage *)decodeMessage:(NSData *)data error:(NSError **)error {
return [self messageFromDictionary:[self decodeDictionary:data error:error]];
return [self messageFromDictionary:[self decodeDictionary:data error:error] protocolMessage:nil];
}

- (NSArray *)decodeMessages:(NSData *)data error:(NSError **)error {
return [self messagesFromArray:[self decodeArray:data error:error]];
return [self messagesFromArray:[self decodeArray:data error:error] protocolMessage:nil];
}

- (NSData *)encodeMessage:(ARTMessage *)message error:(NSError **)error {
Expand Down Expand Up @@ -266,7 +266,15 @@ - (NSArray *)decodeStats:(NSData *)data error:(NSError **)error {
return [self statsFromArray:[self decodeArray:data error:error]];
}

- (ARTMessage *)messageFromDictionary:(NSDictionary *)input {
- (nullable NSString *)versionFromInput:(NSDictionary *)input withProtocolMessage:(nullable ARTProtocolMessage *)protocolMessage {
if (protocolMessage.channelSerial == nil) {
return nil;
}
int index = [[input artNumber:@"_index"] intValue];
return [NSString stringWithFormat:@"%@:%03d", protocolMessage.channelSerial, index]; // TM2p <channelSerial>:<padded_index>
}

- (ARTMessage *)messageFromDictionary:(NSDictionary *)input protocolMessage:(ARTProtocolMessage *)protocolMessage {
ARTLogVerbose(_logger, @"RS:%p ARTJsonLikeEncoder<%@>: messageFromDictionary %@", _rest, [_delegate formatAsString], input);
if (![input isKindOfClass:[NSDictionary class]]) {
return nil;
Expand All @@ -275,24 +283,34 @@ - (ARTMessage *)messageFromDictionary:(NSDictionary *)input {
ARTMessage *message = [[ARTMessage alloc] init];
message.id = [input artString:@"id"];
message.name = [input artString:@"name"];
message.action = ([input artNumber:@"action"] ?: [[NSNumber alloc] initWithInt:ARTMessageActionCreate]).integerValue;
message.version = [input artString:@"version"] ?: [self versionFromInput:input withProtocolMessage:protocolMessage]; // TM2p
message.serial = [input artString:@"serial"];
if (!message.serial && message.action == ARTMessageActionCreate) { // TM2k
message.serial = message.version;
}
message.clientId = [input artString:@"clientId"];
message.data = [input objectForKey:@"data"];
message.encoding = [input artString:@"encoding"];;
message.encoding = [input artString:@"encoding"];
message.timestamp = [input artTimestamp:@"timestamp"];
message.createdAt = [input artTimestamp:@"createdAt"];
if (!message.createdAt && message.action == ARTMessageActionCreate) { // TM2o
message.createdAt = message.timestamp;
}
message.connectionId = [input artString:@"connectionId"];
message.extras = [input objectForKey:@"extras"];

return message;
}

- (NSArray *)messagesFromArray:(NSArray *)input {
- (NSArray *)messagesFromArray:(NSArray *)input protocolMessage:(ARTProtocolMessage *)protocolMessage {
if (![input isKindOfClass:[NSArray class]]) {
return nil;
}

NSMutableArray *output = [NSMutableArray array];
for (NSDictionary *item in input) {
ARTMessage *message = [self messageFromDictionary:item];
ARTMessage *message = [self messageFromDictionary:item protocolMessage:protocolMessage];
if (!message) {
return nil;
}
Expand Down Expand Up @@ -734,8 +752,6 @@ - (ARTProtocolMessage *)protocolMessageFromDictionary:(NSDictionary *)input {
message.id = [input artString:@"id"];
message.msgSerial = [input artNumber:@"msgSerial"];
message.timestamp = [input artTimestamp:@"timestamp"];
message.messages = [self messagesFromArray:[input objectForKey:@"messages"]];
message.presence = [self presenceMessagesFromArray:[input objectForKey:@"presence"]];
message.connectionKey = [input artString:@"connectionKey"];
message.flags = [[input artNumber:@"flags"] longLongValue];
message.connectionDetails = [self connectionDetailsFromDictionary:[input valueForKey:@"connectionDetails"]];
Expand All @@ -747,6 +763,17 @@ - (ARTProtocolMessage *)protocolMessageFromDictionary:(NSDictionary *)input {
message.error = [ARTErrorInfo createWithCode:[[error artNumber:@"code"] intValue] status:[[error artNumber:@"statusCode"] intValue] message:[error artString:@"message"]];
}

NSMutableArray *messages = [[input objectForKey:@"messages"] mutableCopy];

// There is probably a better way to do this, but I have limited time to implement TM2p
for (int i = 0; i < messages.count; i++) {
NSMutableDictionary *msgDict = [messages[i] mutableCopy];
msgDict[@"_index"] = @(i);
messages[i] = msgDict;
}
message.messages = [self messagesFromArray:messages protocolMessage:message];
message.presence = [self presenceMessagesFromArray:[input objectForKey:@"presence"]];

return message;
}

Expand Down
9 changes: 6 additions & 3 deletions Source/ARTMessage.m
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ - (NSString *)description {
- (id)copyWithZone:(NSZone *)zone {
ARTMessage *message = [super copyWithZone:zone];
message.name = self.name;
message.extras = self.extras;
message.action = self.action;
message.serial = self.serial;
message.version = self.version;
message.createdAt = self.createdAt;
return message;
}

Expand All @@ -67,7 +70,7 @@ + (instancetype)fromEncoded:(NSDictionary *)jsonObject channelOptions:(ARTChanne
return nil;
}

ARTMessage *message = [jsonEncoder messageFromDictionary:jsonObject];
ARTMessage *message = [jsonEncoder messageFromDictionary:jsonObject protocolMessage:nil];

NSError *decodeError = nil;
message = [message decodeWithEncoder:decoder error:&decodeError];
Expand Down Expand Up @@ -97,7 +100,7 @@ + (instancetype)fromEncoded:(NSDictionary *)jsonObject channelOptions:(ARTChanne
return nil;
}

NSArray<ARTMessage *> *messages = [jsonEncoder messagesFromArray:jsonArray];
NSArray<ARTMessage *> *messages = [jsonEncoder messagesFromArray:jsonArray protocolMessage:nil];

NSMutableArray<ARTMessage *> *decodedMessages = [NSMutableArray array];
for (ARTMessage *message in messages) {
Expand Down
4 changes: 2 additions & 2 deletions Source/PrivateHeaders/Ably/ARTJsonLikeEncoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ NS_ASSUME_NONNULL_BEGIN

@interface ARTJsonLikeEncoder ()

- (nullable ARTMessage *)messageFromDictionary:(NSDictionary *)input;
- (nullable NSArray *)messagesFromArray:(NSArray *)input;
- (nullable ARTMessage *)messageFromDictionary:(NSDictionary *)input protocolMessage:(nullable ARTProtocolMessage *)protocolMessage;
- (nullable NSArray *)messagesFromArray:(NSArray *)input protocolMessage:(nullable ARTProtocolMessage *)protocolMessage;

- (nullable ARTPresenceMessage *)presenceMessageFromDictionary:(NSDictionary *)input;
- (nullable NSArray *)presenceMessagesFromArray:(NSArray *)input;
Expand Down
48 changes: 48 additions & 0 deletions Source/include/Ably/ARTMessage.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,41 @@
#import <Ably/ARTTypes.h>
#import <Ably/ARTChannelOptions.h>

/**
* The namespace containing the different types of message actions.
*/
NS_SWIFT_SENDABLE
typedef NS_ENUM(NSUInteger, ARTMessageAction) {
/**
* Message action has not been set.
*/
ARTMessageActionUnset,
/**
* Message action for a newly created message.
*/
ARTMessageActionCreate,
/**
* Message action for an updated message.
*/
ARTMessageActionUpdate,
/**
* Message action for a deleted message.
*/
ARTMessageActionDelete,
/**
* Message action for a newly created annotation.
*/
ARTMessageActionAnnotationCreate,
/**
* Message action for a deleted annotation.
*/
ARTMessageActionAnnotationDelete,
/**
* Message action for a meta-message that contains channel occupancy information.
*/
ARTMessageActionMetaOccupancy,
};

NS_ASSUME_NONNULL_BEGIN

/**
Expand All @@ -14,6 +49,19 @@ NS_ASSUME_NONNULL_BEGIN
/// The event name, if available
@property (nullable, readwrite, nonatomic) NSString *name;

/// The action type of the message, one of the `ARTMessageAction` enum values.
@property (readwrite, nonatomic) ARTMessageAction action;

/// The version of the message, lexicographically-comparable with other versions (that share the same serial).
/// Will differ from the serial only if the message has been updated or deleted.
@property (nullable, readwrite, nonatomic) NSString *version;

/// This message's unique serial (an identifier that will be the same in all future updates of this message).
@property (nullable, readwrite, nonatomic) NSString *serial;

/// The timestamp of the very first version of a given message.
@property (nonatomic, nullable) NSDate *createdAt;

/**
* Construct an `ARTMessage` object with an event name and payload.
*
Expand Down
2 changes: 1 addition & 1 deletion Test/Tests/RestClientChannelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,7 @@ class RestClientChannelTests: XCTestCase {
XCTAssertTrue((client.internal.encoders["application/json"] as! ARTJsonLikeEncoder).message(from: [
"data": "foo",
"extras": ["push": ["notification": ["title": "Hello from Ably!"]]],
])?.extras == extras)
], protocolMessage: nil)?.extras == extras)

waitUntil(timeout: testTimeout) { done in
channel.publish("name", data: "some data", extras: extras) { error in
Expand Down
104 changes: 104 additions & 0 deletions Test/Tests/UtilitiesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -484,4 +484,108 @@ class UtilitiesTests: XCTestCase {
let expectedSize = "{\"test\":\"test\"}".count + "{\"push\":{\"key\":\"value\"}}".count + clientId.count + message.name!.count
XCTAssertEqual(message.messageSize(), expectedSize)
}

// TM2k
func test__025_Utilities__message_received_with_action_create_does_not_contain_a_serial_takes_it_from_version() throws {
beforeEach__Utilities__JSON_Encoder()
var json = """
{
"messages": [
{
"action": 1,
"version": "123"
}
]
}
"""
var data = json.data(using: .utf8)!
var pm = try jsonEncoder.decodeProtocolMessage(data)
var messages = try XCTUnwrap(pm.messages)
XCTAssert(messages[0].action == .create)
XCTAssert(messages[0].version == "123")
XCTAssert(messages[0].serial == "123")

// should only apply to creates
json = """
{
"messages": [
{
"action": 0,
"version": "123"
}
]
}
"""
data = json.data(using: .utf8)!
pm = try jsonEncoder.decodeProtocolMessage(data)
messages = try XCTUnwrap(pm.messages)
XCTAssert(messages[0].action == .unset)
XCTAssert(messages[0].version == "123")
XCTAssertNil(messages[0].serial)
}

// TM2p
func test__026_Utilities__message_received_over_a_realtime_transport_does_not_contain_a_version() throws {
beforeEach__Utilities__JSON_Encoder()
let json = """
{
"channelSerial": "foo",
"messages": [
{
"serial": "12345"
},
{
"serial": "123456"
}
]
}
"""
let data = json.data(using: .utf8)!
let pm = try jsonEncoder.decodeProtocolMessage(data)
let messages = try XCTUnwrap(pm.messages)
XCTAssert(messages[0].action == .create)
XCTAssert(messages[0].version == "foo:000")
XCTAssert(messages[0].serial == "12345")
XCTAssert(messages[1].action == .create)
XCTAssert(messages[1].version == "foo:001")
XCTAssert(messages[1].serial == "123456")
}

// TM2o
func test__027_Utilities__message_received_with_action_create_does_not_contain_createdAt() throws {
beforeEach__Utilities__JSON_Encoder()
var json = """
{
"messages": [
{
"action": 1,
"timestamp": "1234512345",
}
]
}
"""
var data = json.data(using: .utf8)!
var pm = try jsonEncoder.decodeProtocolMessage(data)
var messages = try XCTUnwrap(pm.messages)
XCTAssert(messages[0].action == .create)
XCTAssertNotNil(messages[0].createdAt)
XCTAssertEqual(messages[0].createdAt, messages[0].timestamp)

// should only apply to creates
json = """
{
"messages": [
{
"action": 0,
"timestamp": "1234512345",
}
]
}
"""
data = json.data(using: .utf8)!
pm = try jsonEncoder.decodeProtocolMessage(data)
messages = try XCTUnwrap(pm.messages)
XCTAssert(messages[0].action == .unset)
XCTAssertNil(messages[0].createdAt)
}
}

0 comments on commit 5a9b7ba

Please sign in to comment.