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

Message interactions #1785

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,6 @@ Docs/jazzy

# Tooling
xcparse/

# Swift
.swiftpm
106 changes: 97 additions & 9 deletions Ably.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1310"
LastUpgradeVersion = "1420"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
2 changes: 1 addition & 1 deletion Ably.xcodeproj/xcshareddata/xcschemes/Ably-iOS.xcscheme
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1310"
LastUpgradeVersion = "1420"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
2 changes: 1 addition & 1 deletion Ably.xcodeproj/xcshareddata/xcschemes/Ably-macOS.xcscheme
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1310"
LastUpgradeVersion = "1420"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
2 changes: 1 addition & 1 deletion Ably.xcodeproj/xcshareddata/xcschemes/Ably-tvOS.xcscheme
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1310"
LastUpgradeVersion = "1420"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,67 @@ channel.publish("greeting", data: "Hello World!")
[channel publish:@"greeting" data:@"Hello World!"];
```

### Message Interactions

Message Interactions allow you to interact with messages previously sent to a channel. Once a channel is enabled with Message Interactions, messages received by that channel will contain a unique `timeSerial` that can be referenced by later messages.

#### Publishing an Interaction

This example assumes that a message with timeserial `1656424960320-1` has previously been published on the channel.

**Swift**

```swift
let message = ARTMessage()
message.clientId = "client"
message.name = "name"
message.extras = ["ref": ["type": "refType", "timeserial": "1656424960320-1"]] as ARTJsonCompatible
channel.publish([message])
```

**Objective-C**

```objective-c
ARTMessage * message = [[ARTMessage init] alloc];
NSDictionary * ref = [[NSDictionary init] alloc];
[ref insertValue:@"1656424960320-1" inPropertyWithKey:@"timeserial"];
[ref insertValue:@"refType" inPropertyWithKey:@"type"];
NSDictionary * extras = [[NSDictionary init] alloc];
[extras insertValue:ref inPropertyWithKey:@"ref"];
message.extras = extras;
NSArray * messageArray = [[NSArray alloc] initWithObjects:message, nil];

[channel publish:messageArray];
```

#### Subscribing to Interactions

You can also filter messages received on the channel so that only messages matching the filter are passed on to your listener.

The following example sets up a listener that only receives messages that have an interaction type of `com.ably.reaction`.

**Swift**

```swift
let messageCallback = { (message: ARTMessage) in
// Your code
}
let filter = ARTMessageFilter()
filter.refType = "com.ably.reaction";
channel.subscribe(messageCallback, filter: filter)
```

**Objective-C**

```objective-c
ARTMessageFilter * filter = [[ARTMessageFilter init] alloc];
filter.refType = @"com.ably.reaction";
[channel subscribe:^(ARTMessage *message) {
NSLog(@"%@", message.name);
NSLog(@"%@", message.data);
} filter:filter];
```

### Querying the history

**Swift**
Expand Down
99 changes: 99 additions & 0 deletions Source/ARTFilteredListeners.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#import "ARTFilteredListeners.h"


// Convenience struct for storing filter/listener pairs
@implementation FilteredListenerFilterPair

- (instancetype) initWithListenerAndFilter:(ARTEventListener *) listener withFilter:(ARTMessageFilter *)filter {
self = [super init];
if (self) {
_listener = listener;
_filter = filter;
}

return self;
}

@end

#pragma mark ARTFilteredListeners
@implementation ARTFilteredListeners {
NSMutableArray<FilteredListenerFilterPair *> * _filteredListeners;
}


- (instancetype) init {
self = [super init];
if (self) {
_filteredListeners = [[NSMutableArray alloc] init];
}

return self;
}

- (NSMutableArray<FilteredListenerFilterPair *> *) getFilteredListeners {
@synchronized (self) {
return _filteredListeners;
}
}

- (void) removeAllListeners {
@synchronized (self) {
[_filteredListeners removeAllObjects];
}
}

- (void) addFilteredListener:(ARTEventListener *) listener filter:(ARTMessageFilter *) filter {
@synchronized (self) {
FilteredListenerFilterPair * pair = [[FilteredListenerFilterPair alloc] initWithListenerAndFilter:listener withFilter:filter];
[_filteredListeners addObject:pair];
}
}

- (NSMutableArray<ARTEventListener *> *) removeFilteredListenersByFilter:(ARTMessageFilter *) filter {

NSMutableArray<ARTEventListener *> *discardedListeners = [[NSMutableArray alloc] init];

if (filter == nil) {
return discardedListeners;
}

@synchronized (self) {
// Find all the pairs that match the filter, and note them and their listeners
NSMutableArray *discardedPairs = [[NSMutableArray alloc] init];
for (FilteredListenerFilterPair * pair in _filteredListeners) {
if (pair.filter == filter) {
[discardedListeners addObject:pair.listener];
[discardedPairs addObject:pair];
}
}

// Remove the pairs from the array
[_filteredListeners removeObjectsInArray:discardedPairs];

// Return the discarded listeners
return discardedListeners;
}

}

- (void) removeFilteredListener:(ARTEventListener *) listener {

if (listener == nil) {
return;
}

@synchronized (self) {
int index = 0;
for (FilteredListenerFilterPair * pair in _filteredListeners) {
if (pair.listener == listener) {
[_filteredListeners removeObjectAtIndex:index];
return;
}

index++;
}
}
}

@end
18 changes: 18 additions & 0 deletions Source/ARTFilteredMessageCallbackFactory.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#import "ARTFilteredMessageCallbackFactory.h"
#import <Ably/ARTMessageExtrasFilter.h>

@implementation ARTFilteredMessageCallbackFactory

+ (ARTMessageCallback) createFilteredCallback:(ARTMessageCallback) original filter:(ARTMessageFilter *) filter {
ARTMessageExtrasFilter * extrasFilter = [[ARTMessageExtrasFilter alloc] initWithFilter:filter];

return ^(ARTMessage* message) {{
if (![extrasFilter onMessage:message]) {
return;
}

original(message);
}};
}

@end
109 changes: 109 additions & 0 deletions Source/ARTMessageExtrasFilter.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#import "ARTMessageExtrasFilter.h"
#import <Foundation/Foundation.h>

// Convenience struct for passing around message ref information
@interface MessageRef : NSObject
@property (readwrite) NSString* type;
@property (readwrite) NSString* timeserial;
@end

@implementation MessageRef

@end

#pragma mark ARTMessageExtrasFilter

@implementation ARTMessageExtrasFilter {
ARTMessageFilter *_filter;
}

- (instancetype) initWithFilter:(ARTMessageFilter *)filter {
self = [super init];
if (self) {
_filter = filter;
}

return self;
}

// A message is valid if the client id, name, and reference information matches the filter
- (bool) onMessage: (ARTMessage*) message {
return [self clientIdMatchesFilter:message] &&
[self nameMatchesFilter:message] &&
[self referenceMatchesFilter:message];
}

// Check that the message extras are/are not a reference to another message, and that the
// type and timeserials match the filter.
- (bool) referenceMatchesFilter: (ARTMessage *) message {
if (_filter.isRef == nil && _filter.refType == nil && _filter.refTimeserial == nil) {
return true;
}

MessageRef* messageRef = [self getMessageRefFromExtras:message];

return [self isRefMatchesFilter:messageRef] &&
[self refTimeserialMatchesFilter:messageRef] &&
[self refTypeMatchesFilter:messageRef];
}

- (bool) clientIdMatchesFilter: (ARTMessage*) message {
return _filter.clientId == nil || (message.clientId != nil && [message.clientId isEqualToString:_filter.clientId]);
}

- (bool) nameMatchesFilter: (ARTMessage*) message {
return _filter.name == nil || (message.name != nil && [message.name isEqualToString:_filter.name]);
}

- (MessageRef*) getMessageRefFromExtras: (ARTMessage *) message {
if (message.extras == nil) {
return nil;
}

NSError *e = nil;
NSDictionary *extrasDict = [message.extras toJSON:&e];
if (e) {
return nil;
}

if (extrasDict == nil) {
return nil;
}

NSDictionary* messageRef = [extrasDict valueForKey:@"ref"];
if (messageRef == nil) {
return nil;
}

NSString* refTimeserial = [messageRef valueForKey:@"timeserial"];
NSString* refType = [messageRef valueForKey:@"type"];

if (refTimeserial == nil || refType == nil) {
return nil;
}

MessageRef* ref;
ref = [MessageRef alloc];
ref.type = refType;
ref.timeserial = refTimeserial;

return ref;
}

- (bool) isRefMatchesFilter: (MessageRef*) messageRef {
return _filter.isRef == nil ||
([_filter.isRef isEqualToNumber:[NSNumber numberWithBool:YES]] && messageRef != nil) ||
([_filter.isRef isEqualToNumber:[NSNumber numberWithBool:NO]] && messageRef == nil);
}

- (bool) refTimeserialMatchesFilter: (MessageRef*) messageRef {
return _filter.refTimeserial == nil ||
(messageRef.timeserial != nil && [messageRef.timeserial isEqualToString:_filter.refTimeserial]);
}

- (bool) refTypeMatchesFilter: (MessageRef*) messageRef {
return _filter.refType == nil ||
(messageRef.type != nil && [messageRef.type isEqualToString:_filter.refType]);
}

@end
28 changes: 28 additions & 0 deletions Source/ARTMessageFilter.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#import "ARTMessageFilter.h"

@implementation ARTMessageFilter

- (instancetype)init {
self = [super init];
if (self) {
self.name = nil;
self.clientId = nil;
self.isRef = nil;
self.refType = nil;
self.refTimeserial = nil;
}
return self;
}

- (nonnull id)copyWithZone:(nullable NSZone *)zone {
ARTMessageFilter *filter = [[[self class] allocWithZone:zone] init];
filter.name = self.name;
filter.clientId = self.clientId;
filter.isRef = self.isRef;
filter.refTimeserial = self.refTimeserial;
filter.refType = self.refType;

return filter;
}

@end
Loading