From daad7b5ab9b81b0e61324702d773cb97174a73ec Mon Sep 17 00:00:00 2001 From: Andy Ford Date: Thu, 20 Jul 2023 14:29:26 +0100 Subject: [PATCH 1/8] [MFI1, MFI2] add message filter interface --- Source/include/Ably/ARTMessageFilter.h | 37 ++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 Source/include/Ably/ARTMessageFilter.h diff --git a/Source/include/Ably/ARTMessageFilter.h b/Source/include/Ably/ARTMessageFilter.h new file mode 100644 index 000000000..0670057e7 --- /dev/null +++ b/Source/include/Ably/ARTMessageFilter.h @@ -0,0 +1,37 @@ +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ARTMessageFilter : NSObject + +/** + * Whether the message should contain a `extras.ref` field. + * Spec: MFI2a + */ +@property (readwrite, nonatomic) bool isRef; + +/** + * Value to check against `extras.ref.timeserial`.`. + * Spec: MFI2b + */ +@property (readwrite, nonatomic) NSString *refTimeserial; + +/** + * Value to check against `extras.ref.type`.`. + * Spec: MFI2c + */ +@property (readwrite, nonatomic) NSString *refType; + +/** + * Value to check against the `name` of a message. + * Spec: MFI2d + */ +@property (readwrite, nonatomic) NSString *name; + +/** + * Value to check against the `cliendId` that published the message. + * Spec: MFI2e + */ +@property (readwrite, nonatomic) NSString *cliendId; + +@end From 740b54ba095f7f07ab9bfd21f03f6677dedfdc76 Mon Sep 17 00:00:00 2001 From: Andy Ford Date: Thu, 20 Jul 2023 21:06:08 +0100 Subject: [PATCH 2/8] Implement MessageExtrasFilter Similar to the ably-java implementation of the same class, checks the message extras and a few other fields on the message to check that they match the user-provided filter. This class will be used as part of a message filterer to implement filtered message interactions. --- Ably.xcodeproj/project.pbxproj | 80 ++++-- .../xcschemes/Ably-SoakTest-App.xcscheme | 2 +- .../xcshareddata/xcschemes/Ably-iOS.xcscheme | 2 +- .../xcschemes/Ably-macOS.xcscheme | 2 +- .../xcshareddata/xcschemes/Ably-tvOS.xcscheme | 2 +- Source/ARTMessageExtrasFilter.m | 109 +++++++ Source/ARTMessageFilter.m | 28 ++ Source/Ably.modulemap | 1 + .../Ably/ARTMessageExtrasFilter.h | 18 ++ Source/include/Ably.modulemap | 1 + Source/include/Ably/ARTMessageFilter.h | 17 +- Source/include/Ably/Ably.h | 1 + Test/Tests/ARTMessageExtrasFilterTests.swift | 272 ++++++++++++++++++ 13 files changed, 507 insertions(+), 28 deletions(-) create mode 100644 Source/ARTMessageExtrasFilter.m create mode 100644 Source/ARTMessageFilter.m create mode 100644 Source/PrivateHeaders/Ably/ARTMessageExtrasFilter.h create mode 100644 Test/Tests/ARTMessageExtrasFilterTests.swift diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index 3bc202090..d55ca26b0 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -69,15 +69,15 @@ 21113B5529DC6ACD00652C86 /* ARTTestClientOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5429DC6ACD00652C86 /* ARTTestClientOptions.m */; }; 21113B5629DC6ACD00652C86 /* ARTTestClientOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5429DC6ACD00652C86 /* ARTTestClientOptions.m */; }; 21113B5729DC6ACD00652C86 /* ARTTestClientOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5429DC6ACD00652C86 /* ARTTestClientOptions.m */; }; + 21113B5929DCA4C700652C86 /* DataGatherer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5829DCA4C700652C86 /* DataGatherer.swift */; }; + 21113B5A29DCA4C700652C86 /* DataGatherer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5829DCA4C700652C86 /* DataGatherer.swift */; }; + 21113B5B29DCA4C700652C86 /* DataGatherer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5829DCA4C700652C86 /* DataGatherer.swift */; }; 21113B5F29DDDDD000652C86 /* LogAdapterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5E29DDDDD000652C86 /* LogAdapterTests.swift */; }; 21113B6029DDDDD000652C86 /* LogAdapterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5E29DDDDD000652C86 /* LogAdapterTests.swift */; }; 21113B6129DDDDD000652C86 /* LogAdapterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5E29DDDDD000652C86 /* LogAdapterTests.swift */; }; 21113B6329DDF7E800652C86 /* ARTInternalLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 21113B6229DDF7E800652C86 /* ARTInternalLogTests.m */; }; 21113B6429DDF7ED00652C86 /* ARTInternalLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 21113B6229DDF7E800652C86 /* ARTInternalLogTests.m */; }; 21113B6529DDF7EF00652C86 /* ARTInternalLogTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 21113B6229DDF7E800652C86 /* ARTInternalLogTests.m */; }; - 21113B5929DCA4C700652C86 /* DataGatherer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5829DCA4C700652C86 /* DataGatherer.swift */; }; - 21113B5A29DCA4C700652C86 /* DataGatherer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5829DCA4C700652C86 /* DataGatherer.swift */; }; - 21113B5B29DCA4C700652C86 /* DataGatherer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 21113B5829DCA4C700652C86 /* DataGatherer.swift */; }; 211A60D729D6D2C300D169C5 /* BackoffRetryDelayCalculatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2132C31829D5E574000C4355 /* BackoffRetryDelayCalculatorTests.swift */; }; 211A60D829D6D2C400D169C5 /* BackoffRetryDelayCalculatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2132C31829D5E574000C4355 /* BackoffRetryDelayCalculatorTests.swift */; }; 211A60D929D6D2C500D169C5 /* BackoffRetryDelayCalculatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2132C31829D5E574000C4355 /* BackoffRetryDelayCalculatorTests.swift */; }; @@ -343,6 +343,21 @@ 96E408441A38939E00087F77 /* ARTProtocolMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 96E408421A38939E00087F77 /* ARTProtocolMessage.m */; }; 96E408471A3895E800087F77 /* ARTWebSocketTransport.h in Headers */ = {isa = PBXBuildFile; fileRef = 96E408451A3895E800087F77 /* ARTWebSocketTransport.h */; settings = {ATTRIBUTES = (Private, ); }; }; 96E408481A3895E800087F77 /* ARTWebSocketTransport.m in Sources */ = {isa = PBXBuildFile; fileRef = 96E408461A3895E800087F77 /* ARTWebSocketTransport.m */; }; + B17D45BA2A699B1400D61A54 /* ARTMessageExtrasFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B17D45B92A699B1400D61A54 /* ARTMessageExtrasFilterTests.swift */; }; + B17D45BB2A699B1400D61A54 /* ARTMessageExtrasFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B17D45B92A699B1400D61A54 /* ARTMessageExtrasFilterTests.swift */; }; + B17D45BC2A699B1400D61A54 /* ARTMessageExtrasFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B17D45B92A699B1400D61A54 /* ARTMessageExtrasFilterTests.swift */; }; + B17D45BE2A699B9300D61A54 /* ARTMessageFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = B17D45BD2A699B9300D61A54 /* ARTMessageFilter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B17D45BF2A699B9300D61A54 /* ARTMessageFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = B17D45BD2A699B9300D61A54 /* ARTMessageFilter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B17D45C02A699B9300D61A54 /* ARTMessageFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = B17D45BD2A699B9300D61A54 /* ARTMessageFilter.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B17D45C22A699BB700D61A54 /* ARTMessageExtrasFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = B17D45C12A699BB700D61A54 /* ARTMessageExtrasFilter.m */; }; + B17D45C32A699BB700D61A54 /* ARTMessageExtrasFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = B17D45C12A699BB700D61A54 /* ARTMessageExtrasFilter.m */; }; + B17D45C42A699BB700D61A54 /* ARTMessageExtrasFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = B17D45C12A699BB700D61A54 /* ARTMessageExtrasFilter.m */; }; + B17D45CA2A699BE300D61A54 /* ARTMessageExtrasFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = B17D45C92A699BE300D61A54 /* ARTMessageExtrasFilter.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B17D45CB2A699BE300D61A54 /* ARTMessageExtrasFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = B17D45C92A699BE300D61A54 /* ARTMessageExtrasFilter.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B17D45CC2A699BE300D61A54 /* ARTMessageExtrasFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = B17D45C92A699BE300D61A54 /* ARTMessageExtrasFilter.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B17D45D22A69A9E700D61A54 /* ARTMessageFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = B17D45D12A69A9E700D61A54 /* ARTMessageFilter.m */; }; + B17D45D32A69A9E700D61A54 /* ARTMessageFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = B17D45D12A69A9E700D61A54 /* ARTMessageFilter.m */; }; + B17D45D42A69A9E700D61A54 /* ARTMessageFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = B17D45D12A69A9E700D61A54 /* ARTMessageFilter.m */; }; D3AD0EBD215E2FB000312105 /* ARTNSString+ARTUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = D3AD0EBB215E2FB000312105 /* ARTNSString+ARTUtil.h */; settings = {ATTRIBUTES = (Private, ); }; }; D3AD0EBE215E2FB000312105 /* ARTNSString+ARTUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = D3AD0EBC215E2FB000312105 /* ARTNSString+ARTUtil.m */; }; D50D86E929E9444600EA72EA /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = D50D86E829E9444600EA72EA /* JSON.swift */; }; @@ -1149,9 +1164,9 @@ 21113B4829DB60F800652C86 /* MockRetryDelayCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockRetryDelayCalculator.swift; sourceTree = ""; }; 21113B5029DC6AAF00652C86 /* ARTTestClientOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ARTTestClientOptions.h; path = PrivateHeaders/Ably/ARTTestClientOptions.h; sourceTree = ""; }; 21113B5429DC6ACD00652C86 /* ARTTestClientOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARTTestClientOptions.m; sourceTree = ""; }; + 21113B5829DCA4C700652C86 /* DataGatherer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataGatherer.swift; sourceTree = ""; }; 21113B5E29DDDDD000652C86 /* LogAdapterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogAdapterTests.swift; sourceTree = ""; }; 21113B6229DDF7E800652C86 /* ARTInternalLogTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARTInternalLogTests.m; sourceTree = ""; }; - 21113B5829DCA4C700652C86 /* DataGatherer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataGatherer.swift; sourceTree = ""; }; 211A60DA29D726F800D169C5 /* ARTConnectionStateChangeMetadata.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ARTConnectionStateChangeMetadata.h; path = PrivateHeaders/Ably/ARTConnectionStateChangeMetadata.h; sourceTree = ""; }; 211A60DE29D7272000D169C5 /* ARTConnectionStateChangeMetadata.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARTConnectionStateChangeMetadata.m; sourceTree = ""; }; 211A60FA29D8ABCF00D169C5 /* ARTChannelStateChangeMetadata.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ARTChannelStateChangeMetadata.h; path = PrivateHeaders/Ably/ARTChannelStateChangeMetadata.h; sourceTree = ""; }; @@ -1237,7 +1252,7 @@ 217FCF3D29D626E4006E5F2D /* StaticJitterCoefficients.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticJitterCoefficients.swift; sourceTree = ""; }; 217FCF3E29D626E4006E5F2D /* MockJitterCoefficientGenerator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockJitterCoefficientGenerator.swift; sourceTree = ""; }; 217FCF4529D626F6006E5F2D /* DefaultJitterCoefficientGeneratorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultJitterCoefficientGeneratorTests.swift; sourceTree = ""; }; - 21DCDA8229F818630073A211 /* Ably-iOS.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = file; name = "Ably-iOS.xctestplan"; path = "Test/Ably-iOS.xctestplan"; sourceTree = SOURCE_ROOT; }; + 21DCDA8229F818630073A211 /* Ably-iOS.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = "Ably-iOS.xctestplan"; path = "Test/Ably-iOS.xctestplan"; sourceTree = SOURCE_ROOT; }; 21DCDA8329F81B350073A211 /* Ably-macOS.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = "Ably-macOS.xctestplan"; sourceTree = ""; }; 21DCDA8429F81B550073A211 /* Ably-tvOS.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = "Ably-tvOS.xctestplan"; sourceTree = ""; }; 21E1C0E42A0DC47400A5DB65 /* ARTWebSocketFactory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ARTWebSocketFactory.h; path = PrivateHeaders/ARTWebSocketFactory.h; sourceTree = ""; }; @@ -1298,6 +1313,11 @@ 96E408421A38939E00087F77 /* ARTProtocolMessage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTProtocolMessage.m; sourceTree = ""; }; 96E408451A3895E800087F77 /* ARTWebSocketTransport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ARTWebSocketTransport.h; path = PrivateHeaders/Ably/ARTWebSocketTransport.h; sourceTree = ""; }; 96E408461A3895E800087F77 /* ARTWebSocketTransport.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTWebSocketTransport.m; sourceTree = ""; }; + B17D45B92A699B1400D61A54 /* ARTMessageExtrasFilterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARTMessageExtrasFilterTests.swift; sourceTree = ""; }; + B17D45BD2A699B9300D61A54 /* ARTMessageFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ARTMessageFilter.h; path = include/Ably/ARTMessageFilter.h; sourceTree = ""; }; + B17D45C12A699BB700D61A54 /* ARTMessageExtrasFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTMessageExtrasFilter.m; sourceTree = ""; }; + B17D45C92A699BE300D61A54 /* ARTMessageExtrasFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ARTMessageExtrasFilter.h; path = PrivateHeaders/Ably/ARTMessageExtrasFilter.h; sourceTree = ""; }; + B17D45D12A69A9E700D61A54 /* ARTMessageFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTMessageFilter.m; sourceTree = ""; }; D3AD0EBB215E2FB000312105 /* ARTNSString+ARTUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "ARTNSString+ARTUtil.h"; path = "PrivateHeaders/Ably/ARTNSString+ARTUtil.h"; sourceTree = ""; }; D3AD0EBC215E2FB000312105 /* ARTNSString+ARTUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "ARTNSString+ARTUtil.m"; sourceTree = ""; }; D50D86E829E9444600EA72EA /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = ""; }; @@ -1696,6 +1716,7 @@ 21BF06092758477700AE4C43 /* Tests */ = { isa = PBXGroup; children = ( + B17D45B92A699B1400D61A54 /* ARTMessageExtrasFilterTests.swift */, 841134772722205400CFA837 /* ARTArchiveTests.m */, 560579D824AF1BA900A4D03D /* ARTDefaultTests.swift */, D7C1B8761BBEA81A0087B55F /* AuthTests.swift */, @@ -1929,6 +1950,8 @@ D746AE311BBC29B2003ECEF8 /* Realtime */ = { isa = PBXGroup; children = ( + B17D45C92A699BE300D61A54 /* ARTMessageExtrasFilter.h */, + B17D45C12A699BB700D61A54 /* ARTMessageExtrasFilter.m */, 96A507BB1A3791490077CDF8 /* ARTRealtime.h */, 1C05CF1E1AC1D7EB00687AC9 /* ARTRealtime+Private.h */, 96A507BC1A3791490077CDF8 /* ARTRealtime.m */, @@ -1991,6 +2014,7 @@ D746AE331BBC29FF003ECEF8 /* Types */ = { isa = PBXGroup; children = ( + B17D45BD2A699B9300D61A54 /* ARTMessageFilter.h */, D7D8F81F1BC2BE15009718F2 /* ARTAuthOptions.h */, D7D5A6991CA3D9040071BD6D /* ARTAuthOptions+Private.h */, D7D8F8201BC2BE15009718F2 /* ARTAuthOptions.m */, @@ -2045,6 +2069,7 @@ D520C4D426809BB5000012B2 /* ARTStringifiable.h */, D520C4D526809BB5000012B2 /* ARTStringifiable.m */, D520C4DC26809D49000012B2 /* ARTStringifiable+Private.h */, + B17D45D12A69A9E700D61A54 /* ARTMessageFilter.m */, ); name = Types; sourceTree = ""; @@ -2294,6 +2319,7 @@ 1CD8DC9F1B1C7315007EAF36 /* ARTDefault.h in Headers */, D70EECAC1FEAF331008A50CD /* ARTPendingMessage.h in Headers */, 217FCF3629D6269D006E5F2D /* ARTJitterCoefficientGenerator.h in Headers */, + B17D45BE2A699B9300D61A54 /* ARTMessageFilter.h in Headers */, 2132C32029D5FE74000C4355 /* ARTTypes+Private.h in Headers */, D7D8F8211BC2BE16009718F2 /* ARTAuthOptions.h in Headers */, EB82F8511C59D29B00661917 /* ARTDataEncoder.h in Headers */, @@ -2304,6 +2330,7 @@ D746AE401BBC5B14003ECEF8 /* ARTEventEmitter.h in Headers */, D746AE531BBD85C5003ECEF8 /* ARTChannels.h in Headers */, D7D8F82D1BC2C706009718F2 /* ARTTokenParams.h in Headers */, + B17D45CA2A699BE300D61A54 /* ARTMessageExtrasFilter.h in Headers */, 211A60FB29D8ABCF00D169C5 /* ARTChannelStateChangeMetadata.h in Headers */, EB0505FC1C5BD7C4006BA7E2 /* ARTBaseMessage+Private.h in Headers */, D777EEE42063A64E002EBA03 /* ARTNSMutableRequest+ARTPush.h in Headers */, @@ -2448,6 +2475,7 @@ D710D4CE21949BB2008F54AD /* ARTWebSocketTransport+Private.h in Headers */, D710D56C21949CB9008F54AD /* ARTPushChannelSubscriptions.h in Headers */, D710D56721949CA1008F54AD /* ARTPushActivationStateMachine+Private.h in Headers */, + B17D45BF2A699B9300D61A54 /* ARTMessageFilter.h in Headers */, D798556123ECCDAF00946BE2 /* ARTVCDiffDecoder.h in Headers */, 21E1C0E62A0DC47400A5DB65 /* ARTWebSocketFactory.h in Headers */, 2132C32129D5FE74000C4355 /* ARTTypes+Private.h in Headers */, @@ -2498,6 +2526,7 @@ D710D50721949C18008F54AD /* ARTConnectionDetails+Private.h in Headers */, 2132C30229D5D157000C4355 /* ARTRetryDelayCalculator.h in Headers */, 2132C21B29D230CF000C4355 /* ARTErrorChecker.h in Headers */, + B17D45CB2A699BE300D61A54 /* ARTMessageExtrasFilter.h in Headers */, D710D51B21949C42008F54AD /* ARTDeviceIdentityTokenDetails.h in Headers */, D76F153D23DB012100B5133C /* ARTRealtimeChannelOptions.h in Headers */, D5D83C0826AAED1A00AADC8E /* ARTStringifiable+Private.h in Headers */, @@ -2617,6 +2646,7 @@ 21447D45254A2ED100B3905A /* ARTSRWebSocket.h in Headers */, EBB721CD2376B454001C3550 /* ARTURLSession.h in Headers */, D710D4D021949BB3008F54AD /* ARTWebSocketTransport+Private.h in Headers */, + B17D45C02A699B9300D61A54 /* ARTMessageFilter.h in Headers */, D710D57221949CBA008F54AD /* ARTPushChannelSubscriptions.h in Headers */, 21E1C0E72A0DC47400A5DB65 /* ARTWebSocketFactory.h in Headers */, 2132C32229D5FE74000C4355 /* ARTTypes+Private.h in Headers */, @@ -2667,6 +2697,7 @@ D710D51321949C19008F54AD /* ARTConnectionDetails+Private.h in Headers */, 2132C30329D5D157000C4355 /* ARTRetryDelayCalculator.h in Headers */, 2132C21C29D230D0000C4355 /* ARTErrorChecker.h in Headers */, + B17D45CC2A699BE300D61A54 /* ARTMessageExtrasFilter.h in Headers */, D5BB210F26AA98A900AA5F3E /* ARTStringifiable.h in Headers */, D5C0CB3F268317B500C06521 /* NSURLQueryItem+Stringifiable.h in Headers */, D710D52D21949C44008F54AD /* ARTDeviceIdentityTokenDetails.h in Headers */, @@ -2894,7 +2925,7 @@ CLASSPREFIX = ART; DefaultBuildSystemTypeForWorkspace = Original; LastSwiftUpdateCheck = 1120; - LastUpgradeCheck = 1310; + LastUpgradeCheck = 1420; ORGANIZATIONNAME = Ably; TargetAttributes = { 856AAC891B6E304B00B07119 = { @@ -3054,6 +3085,7 @@ 21113B5929DCA4C700652C86 /* DataGatherer.swift in Sources */, D7093CA9219EFA8A00723F17 /* MockDeviceStorage.swift in Sources */, D7CEF1321C8DD3BC004FB242 /* RealtimeClientPresenceTests.swift in Sources */, + B17D45BA2A699B1400D61A54 /* ARTMessageExtrasFilterTests.swift in Sources */, 853ED7C41B7A1A3C006F1C6F /* RestClientStatsTests.swift in Sources */, 217FCF4629D626F6006E5F2D /* DefaultJitterCoefficientGeneratorTests.swift in Sources */, D74EFAEB1C4D09B500CFF98E /* RealtimeClientChannelTests.swift in Sources */, @@ -3155,6 +3187,7 @@ 96A507B61A37881C0077CDF8 /* ARTNSDate+ARTUtil.m in Sources */, 217D182C254222F500DFF07E /* ARTSRProxyConnect.m in Sources */, 850BFB4D1B79323C009D0ADD /* ARTPaginatedResult.m in Sources */, + B17D45D22A69A9E700D61A54 /* ARTMessageFilter.m in Sources */, EB9C530D1CD7BFF300.8.557 /* ARTJsonLikeEncoder.m in Sources */, D746AE1F1BBB5207003ECEF8 /* ARTDataQuery.m in Sources */, 961343D91A42E0B7006DC822 /* ARTClientOptions.m in Sources */, @@ -3212,6 +3245,7 @@ 967A43221A39AEAF00E4CE23 /* ARTNSArray+ARTFunctional.m in Sources */, 217D1835254222F600DFF07E /* ARTSRIOConsumer.m in Sources */, D71966EF1E5E0081000974DD /* ARTPushActivationEvent.m in Sources */, + B17D45C22A699BB700D61A54 /* ARTMessageExtrasFilter.m in Sources */, 96A507A61A377DE90077CDF8 /* ARTNSDictionary+ARTDictionaryUtil.m in Sources */, 217D182D254222F500DFF07E /* ARTSRSIMDHelpers.m in Sources */, D5BB210D26AA98A500AA5F3E /* ARTStringifiable.m in Sources */, @@ -3261,6 +3295,7 @@ D798554923EB96C000946BE2 /* DeltaCodecTests.swift in Sources */, 2124B78829DB127900AD8361 /* MockVersion2Log.swift in Sources */, D510E4AC29F1659F00F77F43 /* Aspects.m in Sources */, + B17D45BB2A699B1400D61A54 /* ARTMessageExtrasFilterTests.swift in Sources */, 2132C21729D20F69000C4355 /* ResumeRequestResponseTests.swift in Sources */, 21113B4A29DB60F800652C86 /* MockRetryDelayCalculator.swift in Sources */, 217FCF4329D626E4006E5F2D /* MockJitterCoefficientGenerator.swift in Sources */, @@ -3320,6 +3355,7 @@ D798554A23EB96C000946BE2 /* DeltaCodecTests.swift in Sources */, 2124B78929DB127900AD8361 /* MockVersion2Log.swift in Sources */, D510E4AD29F1659F00F77F43 /* Aspects.m in Sources */, + B17D45BC2A699B1400D61A54 /* ARTMessageExtrasFilterTests.swift in Sources */, 2132C21829D20F69000C4355 /* ResumeRequestResponseTests.swift in Sources */, 21113B4B29DB60F800652C86 /* MockRetryDelayCalculator.swift in Sources */, 217FCF4429D626E4006E5F2D /* MockJitterCoefficientGenerator.swift in Sources */, @@ -3399,6 +3435,7 @@ D710D57321949CC4008F54AD /* ARTPushAdmin.m in Sources */, 217D1843254222F700DFF07E /* ARTSRProxyConnect.m in Sources */, D710D53421949C54008F54AD /* ARTDeviceDetails.m in Sources */, + B17D45D32A69A9E700D61A54 /* ARTMessageFilter.m in Sources */, D710D66A21949E78008F54AD /* ARTJsonLikeEncoder.m in Sources */, D710D57521949CC4008F54AD /* ARTPushChannelSubscriptions.m in Sources */, D710D55E21949C97008F54AD /* ARTPushActivationStateMachine.m in Sources */, @@ -3456,6 +3493,7 @@ D710D63121949E03008F54AD /* ARTHTTPPaginatedResponse.m in Sources */, 217D184C254222F700DFF07E /* ARTSRIOConsumer.m in Sources */, D710D5DC21949D78008F54AD /* ARTPresence.m in Sources */, + B17D45C32A699BB700D61A54 /* ARTMessageExtrasFilter.m in Sources */, D710D53821949C54008F54AD /* ARTLocalDeviceStorage.m in Sources */, D5BB210C26AA98A500AA5F3E /* ARTStringifiable.m in Sources */, 217D1844254222F700DFF07E /* ARTSRSIMDHelpers.m in Sources */, @@ -3525,6 +3563,7 @@ D710D57921949CC5008F54AD /* ARTPushAdmin.m in Sources */, 217D185A254222F900DFF07E /* ARTSRProxyConnect.m in Sources */, D710D54621949C55008F54AD /* ARTDeviceDetails.m in Sources */, + B17D45D42A69A9E700D61A54 /* ARTMessageFilter.m in Sources */, D710D65021949E77008F54AD /* ARTJsonLikeEncoder.m in Sources */, D710D57B21949CC5008F54AD /* ARTPushChannelSubscriptions.m in Sources */, D710D56421949C98008F54AD /* ARTPushActivationStateMachine.m in Sources */, @@ -3582,6 +3621,7 @@ D710D64F21949E77008F54AD /* ARTCrypto.m in Sources */, D710D64121949E04008F54AD /* ARTHTTPPaginatedResponse.m in Sources */, 217D1863254222FA00DFF07E /* ARTSRIOConsumer.m in Sources */, + B17D45C42A699BB700D61A54 /* ARTMessageExtrasFilter.m in Sources */, D710D60221949D79008F54AD /* ARTPresence.m in Sources */, D710D54A21949C55008F54AD /* ARTLocalDeviceStorage.m in Sources */, 217D185B254222F900DFF07E /* ARTSRSIMDHelpers.m in Sources */, @@ -3670,7 +3710,7 @@ "TEST_SUITE=1", ); INFOPLIST_FILE = "Test/Info-iOS.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3699,7 +3739,7 @@ "TEST_SUITE=1", ); INFOPLIST_FILE = "Test/Info-iOS.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3767,7 +3807,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; RUN_CLANG_STATIC_ANALYZER = YES; @@ -3825,7 +3865,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; @@ -3850,7 +3890,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/Source/Private"; INFOPLIST_FILE = "Source/Info-iOS.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3877,7 +3917,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/Source/Private"; INFOPLIST_FILE = "Source/Info-iOS.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 10.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3905,6 +3945,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "Test/Info-macOS.plist"; @@ -3913,7 +3954,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "-D TARGET_OS_OSX -D TARGET_OS_MAC"; @@ -3941,6 +3982,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "Test/Info-macOS.plist"; @@ -3949,7 +3991,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "-D TARGET_OS_OSX -D TARGET_OS_MAC"; PRODUCT_BUNDLE_IDENTIFIER = "io.ably.Ably-macOS-Tests"; @@ -4041,6 +4083,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -4054,7 +4097,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; MODULEMAP_FILE = Source/Ably.modulemap; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -4081,6 +4124,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -4094,7 +4138,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; MODULEMAP_FILE = Source/Ably.modulemap; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.ably.Ably; @@ -4139,7 +4183,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 10.0; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; @@ -4176,7 +4220,7 @@ SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 10.0; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; }; diff --git a/Ably.xcodeproj/xcshareddata/xcschemes/Ably-SoakTest-App.xcscheme b/Ably.xcodeproj/xcshareddata/xcschemes/Ably-SoakTest-App.xcscheme index 545482692..f55dc4823 100644 --- a/Ably.xcodeproj/xcshareddata/xcschemes/Ably-SoakTest-App.xcscheme +++ b/Ably.xcodeproj/xcshareddata/xcschemes/Ably-SoakTest-App.xcscheme @@ -1,6 +1,6 @@ + +// 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 diff --git a/Source/ARTMessageFilter.m b/Source/ARTMessageFilter.m new file mode 100644 index 000000000..601e605c7 --- /dev/null +++ b/Source/ARTMessageFilter.m @@ -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 diff --git a/Source/Ably.modulemap b/Source/Ably.modulemap index 7b3c8f9f0..85802efa6 100644 --- a/Source/Ably.modulemap +++ b/Source/Ably.modulemap @@ -109,5 +109,6 @@ framework module Ably { header "ARTWebSocketFactory.h" header "ARTAttachRetryState.h" header "ARTConnectRetryState.h" + header "ARTMessageExtrasFilter.h" } } diff --git a/Source/PrivateHeaders/Ably/ARTMessageExtrasFilter.h b/Source/PrivateHeaders/Ably/ARTMessageExtrasFilter.h new file mode 100644 index 000000000..1d4f14b2d --- /dev/null +++ b/Source/PrivateHeaders/Ably/ARTMessageExtrasFilter.h @@ -0,0 +1,18 @@ +#import + +#import +#import + +NS_ASSUME_NONNULL_BEGIN + + +@interface ARTMessageExtrasFilter : NSObject + +/// :nodoc: +- (instancetype)init NS_UNAVAILABLE; +- (instancetype)initWithFilter:(ARTMessageFilter*) filter; +- (bool) onMessage:(ARTMessage*) message; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/include/Ably.modulemap b/Source/include/Ably.modulemap index a33967d55..acde09bbc 100644 --- a/Source/include/Ably.modulemap +++ b/Source/include/Ably.modulemap @@ -109,5 +109,6 @@ framework module Ably { header "Ably/ARTWebSocketFactory.h" header "Ably/ARTAttachRetryState.h" header "Ably/ARTConnectRetryState.h" + header "Ably/AblyMessageExtrasFilter.h" } } diff --git a/Source/include/Ably/ARTMessageFilter.h b/Source/include/Ably/ARTMessageFilter.h index 0670057e7..f079b0069 100644 --- a/Source/include/Ably/ARTMessageFilter.h +++ b/Source/include/Ably/ARTMessageFilter.h @@ -8,30 +8,35 @@ NS_ASSUME_NONNULL_BEGIN * Whether the message should contain a `extras.ref` field. * Spec: MFI2a */ -@property (readwrite, nonatomic) bool isRef; +@property (readwrite, nonatomic, nullable) NSNumber* isRef; /** * Value to check against `extras.ref.timeserial`.`. * Spec: MFI2b */ -@property (readwrite, nonatomic) NSString *refTimeserial; +@property (readwrite, nonatomic, nullable) NSString *refTimeserial; /** * Value to check against `extras.ref.type`.`. * Spec: MFI2c */ -@property (readwrite, nonatomic) NSString *refType; +@property (readwrite, nonatomic, nullable) NSString *refType; /** * Value to check against the `name` of a message. * Spec: MFI2d */ -@property (readwrite, nonatomic) NSString *name; +@property (readwrite, nonatomic, nullable) NSString *name; /** - * Value to check against the `cliendId` that published the message. + * Value to check against the `clientId` that published the message. * Spec: MFI2e */ -@property (readwrite, nonatomic) NSString *cliendId; +@property (readwrite, nonatomic, nullable) NSString *clientId; + +// nodoc +- (instancetype)init; @end + +NS_ASSUME_NONNULL_END diff --git a/Source/include/Ably/Ably.h b/Source/include/Ably/Ably.h index 1e79fb2dc..c0d96599d 100644 --- a/Source/include/Ably/Ably.h +++ b/Source/include/Ably/Ably.h @@ -32,6 +32,7 @@ FOUNDATION_EXPORT const unsigned char ablyVersionString[]; #import #import #import +#import #import #import #import diff --git a/Test/Tests/ARTMessageExtrasFilterTests.swift b/Test/Tests/ARTMessageExtrasFilterTests.swift new file mode 100644 index 000000000..99045d60b --- /dev/null +++ b/Test/Tests/ARTMessageExtrasFilterTests.swift @@ -0,0 +1,272 @@ +import XCTest +import Ably + +class ARTMessageExtrasFilterTests: XCTestCase { + + private func getFilter(clientId: String?, name: String?, isRef: Bool?, refType: String?, refTimeserial: String?) -> ARTMessageFilter + { + let filter = ARTMessageFilter() + filter.clientId = clientId + filter.name = name + filter.isRef = nil + filter.refType = refType + filter.refTimeserial = refTimeserial + + if (isRef != nil) { + filter.isRef = NSNumber.init(booleanLiteral: isRef!) + } + + return filter + } + + func test_itPassesWithNoFilter() { + let filter = getFilter(clientId: nil, name: nil, isRef: nil, refType: nil, refTimeserial: nil) + let message = ARTMessage() + message.clientId = "client" + message.name = "name" + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertTrue(messageExtrasFilter.onMessage(message)) + } + + func test_itPassesWithAllTheFilters() { + let filter = getFilter(clientId: "client", name: "name", isRef: true, refType: "refType", refTimeserial: "fooserial") + let message = ARTMessage() + message.clientId = "client" + message.name = "name" + message.extras = ["ref": ["type": "refType", "timeserial": "fooserial"]] as ARTJsonCompatible + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertTrue(messageExtrasFilter.onMessage(message)) + } + + func test_itPassesWithClientIdFilter() { + let filter = getFilter(clientId: "client", name: nil, isRef: nil, refType: nil, refTimeserial: nil) + let message = ARTMessage() + message.clientId = "client" + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertTrue(messageExtrasFilter.onMessage(message)) + } + + func test_itFailsWithClientIdFilter() { + let filter = getFilter(clientId: "client2", name: nil, isRef: nil, refType: nil, refTimeserial: nil) + let message = ARTMessage() + message.clientId = "client" + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertFalse(messageExtrasFilter.onMessage(message)) + } + + func test_itPassesWithNameFilter() { + let filter = getFilter(clientId: nil, name: "name", isRef: nil, refType: nil, refTimeserial: nil) + let message = ARTMessage() + message.name = "name" + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertTrue(messageExtrasFilter.onMessage(message)) + } + + func test_itFailsWithNameFilter() { + let filter = getFilter(clientId: nil, name: "name", isRef: nil, refType: nil, refTimeserial: nil) + let message = ARTMessage() + message.name = "name2" + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertFalse(messageExtrasFilter.onMessage(message)) + } + + func test_itPassesWithIsRefFilterTrue() { + let filter = getFilter(clientId: nil, name: nil, isRef: true, refType: nil, refTimeserial: nil) + let message = ARTMessage() + message.extras = ["ref": ["type": "refType", "timeserial": "fooserial"]] as ARTJsonCompatible + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertTrue(messageExtrasFilter.onMessage(message)) + } + + func test_itFailsWithIsRefFilterTrueNoTimeserial() { + let filter = getFilter(clientId: nil, name: nil, isRef: true, refType: nil, refTimeserial: nil) + let message = ARTMessage() + message.extras = ["ref": ["type": "refType"]] as ARTJsonCompatible + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertFalse(messageExtrasFilter.onMessage(message)) + } + + func test_itFailsWithIsRefFilterTrueNoType() { + let filter = getFilter(clientId: nil, name: nil, isRef: true, refType: nil, refTimeserial: nil) + let message = ARTMessage() + message.extras = ["ref": ["timeserial": "fooserial"]] as ARTJsonCompatible + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertFalse(messageExtrasFilter.onMessage(message)) + } + + func test_itFailsWithIsRefFilterTrueNoRef() { + let filter = getFilter(clientId: nil, name: nil, isRef: true, refType: nil, refTimeserial: nil) + let message = ARTMessage() + message.extras = NSDictionary() as ARTJsonCompatible + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertFalse(messageExtrasFilter.onMessage(message)) + } + + func test_itFailsWithIsRefFilterTrueNoExtras() { + let filter = getFilter(clientId: nil, name: nil, isRef: true, refType: nil, refTimeserial: nil) + let message = ARTMessage() + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertFalse(messageExtrasFilter.onMessage(message)) + } + + func test_itPassesWithIsRefFilterFalseNoTimeserial() { + let filter = getFilter(clientId: nil, name: nil, isRef: false, refType: nil, refTimeserial: nil) + let message = ARTMessage() + message.extras = ["ref": ["type": "refType"]] as ARTJsonCompatible + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertTrue(messageExtrasFilter.onMessage(message)) + } + + func test_itPassesWithIsRefFilterFalseNoType() { + let filter = getFilter(clientId: nil, name: nil, isRef: false, refType: nil, refTimeserial: nil) + let message = ARTMessage() + message.extras = ["ref": ["timeserial": "fooserial"]] as ARTJsonCompatible + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertTrue(messageExtrasFilter.onMessage(message)) + } + + func test_itPassesWithIsRefFilterFalseNoRef() { + let filter = getFilter(clientId: nil, name: nil, isRef: false, refType: nil, refTimeserial: nil) + let message = ARTMessage() + message.extras = NSDictionary() as ARTJsonCompatible + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertTrue(messageExtrasFilter.onMessage(message)) + } + + func test_itPassesWithIsRefFilterFalseNoExtras() { + let filter = getFilter(clientId: nil, name: nil, isRef: false, refType: nil, refTimeserial: nil) + let message = ARTMessage() + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertTrue(messageExtrasFilter.onMessage(message)) + } + + func test_itFailsWithIsRefFilterComplete() { + let filter = getFilter(clientId: nil, name: nil, isRef: false, refType: nil, refTimeserial: nil) + let message = ARTMessage() + message.extras = ["ref": ["type": "refType", "timeserial": "fooserial"]] as ARTJsonCompatible + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertFalse(messageExtrasFilter.onMessage(message)) + } + + func test_itPassesWithTimeserialFilter() { + let filter = getFilter(clientId: nil, name: nil, isRef: nil, refType: nil, refTimeserial: "fooserial") + let message = ARTMessage() + message.extras = ["ref": ["type": "refType", "timeserial": "fooserial"]] as ARTJsonCompatible + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertTrue(messageExtrasFilter.onMessage(message)) + } + + func test_itFailsWithTimeserialFilter() { + let filter = getFilter(clientId: nil, name: nil, isRef: nil, refType: nil, refTimeserial: "fooserial") + let message = ARTMessage() + message.extras = ["ref": ["type": "refType", "timeserial": "fooserial2"]] as ARTJsonCompatible + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertFalse(messageExtrasFilter.onMessage(message)) + } + + func test_itFailsWithTimeserialFilterNoTimeserial() { + let filter = getFilter(clientId: nil, name: nil, isRef: nil, refType: nil, refTimeserial: "fooserial") + let message = ARTMessage() + message.extras = ["ref": ["type": "refType"]] as ARTJsonCompatible + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertFalse(messageExtrasFilter.onMessage(message)) + } + + func test_itFailsWithTimeserialFilterNoType() { + let filter = getFilter(clientId: nil, name: nil, isRef: nil, refType: nil, refTimeserial: "fooserial") + let message = ARTMessage() + message.extras = ["ref": ["timeserial": "fooserial"]] as ARTJsonCompatible + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertFalse(messageExtrasFilter.onMessage(message)) + } + + func test_itFailsWithTimeserialFilterNoRef() { + let filter = getFilter(clientId: nil, name: nil, isRef: nil, refType: nil, refTimeserial: "fooserial") + let message = ARTMessage() + message.extras = NSDictionary() as ARTJsonCompatible + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertFalse(messageExtrasFilter.onMessage(message)) + } + + func test_itFailsWithTimeserialFilterNoExtras() { + let filter = getFilter(clientId: nil, name: nil, isRef: nil, refType: nil, refTimeserial: "fooserial") + let message = ARTMessage() + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertFalse(messageExtrasFilter.onMessage(message)) + } + + func test_itPassesWithTypeFilter() { + let filter = getFilter(clientId: nil, name: nil, isRef: nil, refType: "refType", refTimeserial: nil) + let message = ARTMessage() + message.extras = ["ref": ["type": "refType", "timeserial": "fooserial"]] as ARTJsonCompatible + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertTrue(messageExtrasFilter.onMessage(message)) + } + + func test_itFailsWithTyoeFilter() { + let filter = getFilter(clientId: nil, name: nil, isRef: nil, refType: "refType", refTimeserial: nil) + let message = ARTMessage() + message.extras = ["ref": ["type": "refType2", "timeserial": "fooserial2"]] as ARTJsonCompatible + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertFalse(messageExtrasFilter.onMessage(message)) + } + + func test_itFailsWithTypeFilterNoTimeserial() { + let filter = getFilter(clientId: nil, name: nil, isRef: nil, refType: "refType", refTimeserial: nil) + let message = ARTMessage() + message.extras = ["ref": ["type": "refType"]] as ARTJsonCompatible + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertFalse(messageExtrasFilter.onMessage(message)) + } + + func test_itFailsWithTypeFilterNoType() { + let filter = getFilter(clientId: nil, name: nil, isRef: nil, refType: "refType", refTimeserial: nil) + let message = ARTMessage() + message.extras = ["ref": ["timeserial": "fooserial"]] as ARTJsonCompatible + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertFalse(messageExtrasFilter.onMessage(message)) + } + + func test_itFailsWithTypeFilterNoRef() { + let filter = getFilter(clientId: nil, name: nil, isRef: nil, refType: "refType", refTimeserial: nil) + let message = ARTMessage() + message.extras = NSDictionary() as ARTJsonCompatible + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertFalse(messageExtrasFilter.onMessage(message)) + } + + func test_itFailsWithTypeFilterNoExtras() { + let filter = getFilter(clientId: nil, name: nil, isRef: nil, refType: "refType", refTimeserial: nil) + let message = ARTMessage() + + let messageExtrasFilter = ARTMessageExtrasFilter(filter: filter) + XCTAssertFalse(messageExtrasFilter.onMessage(message)) + } +} From b69693d9ec3eaa4e8b2ef240d9559ca188683818 Mon Sep 17 00:00:00 2001 From: Andy Ford Date: Fri, 21 Jul 2023 09:14:04 +0100 Subject: [PATCH 3/8] build: reset unintended changes in xcodeproj --- Ably.xcodeproj/project.pbxproj | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index d55ca26b0..39fe9b93c 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -3710,7 +3710,7 @@ "TEST_SUITE=1", ); INFOPLIST_FILE = "Test/Info-iOS.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3739,7 +3739,7 @@ "TEST_SUITE=1", ); INFOPLIST_FILE = "Test/Info-iOS.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3807,7 +3807,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; RUN_CLANG_STATIC_ANALYZER = YES; @@ -3865,7 +3865,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = NO; RUN_CLANG_STATIC_ANALYZER = YES; SDKROOT = iphoneos; @@ -3890,7 +3890,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/Source/Private"; INFOPLIST_FILE = "Source/Info-iOS.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3917,7 +3917,7 @@ DYLIB_INSTALL_NAME_BASE = "@rpath"; HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/Source/Private"; INFOPLIST_FILE = "Source/Info-iOS.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3945,7 +3945,6 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "Test/Info-macOS.plist"; @@ -3954,7 +3953,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "-D TARGET_OS_OSX -D TARGET_OS_MAC"; @@ -3982,7 +3981,6 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "Test/Info-macOS.plist"; @@ -3991,7 +3989,7 @@ "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_FAST_MATH = YES; OTHER_SWIFT_FLAGS = "-D TARGET_OS_OSX -D TARGET_OS_MAC"; PRODUCT_BUNDLE_IDENTIFIER = "io.ably.Ably-macOS-Tests"; @@ -4083,7 +4081,6 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -4097,7 +4094,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + MACOSX_DEPLOYMENT_TARGET = 10.12; MODULEMAP_FILE = Source/Ably.modulemap; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -4124,7 +4121,6 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; - DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -4138,7 +4134,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; + MACOSX_DEPLOYMENT_TARGET = 10.12; MODULEMAP_FILE = Source/Ably.modulemap; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = io.ably.Ably; @@ -4183,7 +4179,7 @@ SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 12.0; + TVOS_DEPLOYMENT_TARGET = 10.0; }; name = Debug; }; @@ -4220,7 +4216,7 @@ SKIP_INSTALL = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 12.0; + TVOS_DEPLOYMENT_TARGET = 10.0; }; name = Release; }; From 09c4eaa48d889001c69c9c3bc75d9712751389bf Mon Sep 17 00:00:00 2001 From: Andy Ford Date: Fri, 21 Jul 2023 09:16:00 +0100 Subject: [PATCH 4/8] git: ignore swiftpm --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 2d7560b9b..fe1e9b9c5 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,6 @@ Docs/jazzy # Tooling xcparse/ + +# Swift +.swiftpm From 9a4ef984005a50a80053c789687a91fa447ea9b3 Mon Sep 17 00:00:00 2001 From: Andy Ford Date: Fri, 21 Jul 2023 15:28:02 +0100 Subject: [PATCH 5/8] Implement manager class for filtered listeners Rather than manage filtered listeners directly in the channel class, which makes it awkward to test in isolation, the ARTFilteredListeners class separates out this behaviour. Also adds a filtered listener class that applies the filter before calling the listener. --- Ably.xcodeproj/project.pbxproj | 54 +++++++- Source/ARTFilteredListeners.m | 99 ++++++++++++++ Source/ARTFilteredMessageCallbackFactory.m | 18 +++ Source/Ably.modulemap | 2 + .../Ably/ARTFilteredListeners.h | 43 +++++++ .../Ably/ARTFilteredMessageCallbackFactory.h | 14 ++ Source/include/Ably.modulemap | 4 +- Test/Tests/ARTFilteredListenersTests.swift | 121 ++++++++++++++++++ ...RTFilteredMessageCallbackFactoryTest.swift | 43 +++++++ 9 files changed, 394 insertions(+), 4 deletions(-) create mode 100644 Source/ARTFilteredListeners.m create mode 100644 Source/ARTFilteredMessageCallbackFactory.m create mode 100644 Source/PrivateHeaders/Ably/ARTFilteredListeners.h create mode 100644 Source/PrivateHeaders/Ably/ARTFilteredMessageCallbackFactory.h create mode 100644 Test/Tests/ARTFilteredListenersTests.swift create mode 100644 Test/Tests/ARTFilteredMessageCallbackFactoryTest.swift diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index 39fe9b93c..08298620f 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -343,6 +343,9 @@ 96E408441A38939E00087F77 /* ARTProtocolMessage.m in Sources */ = {isa = PBXBuildFile; fileRef = 96E408421A38939E00087F77 /* ARTProtocolMessage.m */; }; 96E408471A3895E800087F77 /* ARTWebSocketTransport.h in Headers */ = {isa = PBXBuildFile; fileRef = 96E408451A3895E800087F77 /* ARTWebSocketTransport.h */; settings = {ATTRIBUTES = (Private, ); }; }; 96E408481A3895E800087F77 /* ARTWebSocketTransport.m in Sources */ = {isa = PBXBuildFile; fileRef = 96E408461A3895E800087F77 /* ARTWebSocketTransport.m */; }; + B1397E642A6ABB6E00BB2712 /* ARTFilteredListenersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1AABD0B2A6AB71800E9A3E2 /* ARTFilteredListenersTests.swift */; }; + B1397E652A6ABB6F00BB2712 /* ARTFilteredListenersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1AABD0B2A6AB71800E9A3E2 /* ARTFilteredListenersTests.swift */; }; + B1397E662A6ABB7100BB2712 /* ARTFilteredListenersTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1AABD0B2A6AB71800E9A3E2 /* ARTFilteredListenersTests.swift */; }; B17D45BA2A699B1400D61A54 /* ARTMessageExtrasFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B17D45B92A699B1400D61A54 /* ARTMessageExtrasFilterTests.swift */; }; B17D45BB2A699B1400D61A54 /* ARTMessageExtrasFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B17D45B92A699B1400D61A54 /* ARTMessageExtrasFilterTests.swift */; }; B17D45BC2A699B1400D61A54 /* ARTMessageExtrasFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B17D45B92A699B1400D61A54 /* ARTMessageExtrasFilterTests.swift */; }; @@ -358,6 +361,21 @@ B17D45D22A69A9E700D61A54 /* ARTMessageFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = B17D45D12A69A9E700D61A54 /* ARTMessageFilter.m */; }; B17D45D32A69A9E700D61A54 /* ARTMessageFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = B17D45D12A69A9E700D61A54 /* ARTMessageFilter.m */; }; B17D45D42A69A9E700D61A54 /* ARTMessageFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = B17D45D12A69A9E700D61A54 /* ARTMessageFilter.m */; }; + B1AABCFC2A6A7BC900E9A3E2 /* ARTFilteredListeners.m in Sources */ = {isa = PBXBuildFile; fileRef = B1AABCFB2A6A7BC900E9A3E2 /* ARTFilteredListeners.m */; }; + B1AABCFD2A6A7BC900E9A3E2 /* ARTFilteredListeners.m in Sources */ = {isa = PBXBuildFile; fileRef = B1AABCFB2A6A7BC900E9A3E2 /* ARTFilteredListeners.m */; }; + B1AABCFE2A6A7BC900E9A3E2 /* ARTFilteredListeners.m in Sources */ = {isa = PBXBuildFile; fileRef = B1AABCFB2A6A7BC900E9A3E2 /* ARTFilteredListeners.m */; }; + B1AABD002A6A8FDE00E9A3E2 /* ARTFilteredMessageCallbackFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = B1AABCFF2A6A8FDE00E9A3E2 /* ARTFilteredMessageCallbackFactory.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B1AABD012A6A8FDE00E9A3E2 /* ARTFilteredMessageCallbackFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = B1AABCFF2A6A8FDE00E9A3E2 /* ARTFilteredMessageCallbackFactory.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B1AABD022A6A8FDE00E9A3E2 /* ARTFilteredMessageCallbackFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = B1AABCFF2A6A8FDE00E9A3E2 /* ARTFilteredMessageCallbackFactory.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B1AABD042A6A90B400E9A3E2 /* ARTFilteredMessageCallbackFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = B1AABD032A6A90B400E9A3E2 /* ARTFilteredMessageCallbackFactory.m */; }; + B1AABD052A6A90B400E9A3E2 /* ARTFilteredMessageCallbackFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = B1AABD032A6A90B400E9A3E2 /* ARTFilteredMessageCallbackFactory.m */; }; + B1AABD062A6A90B400E9A3E2 /* ARTFilteredMessageCallbackFactory.m in Sources */ = {isa = PBXBuildFile; fileRef = B1AABD032A6A90B400E9A3E2 /* ARTFilteredMessageCallbackFactory.m */; }; + B1AABD102A6AB90100E9A3E2 /* ARTFilteredListeners.h in Headers */ = {isa = PBXBuildFile; fileRef = B1AABD0F2A6AB90100E9A3E2 /* ARTFilteredListeners.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B1AABD112A6AB90100E9A3E2 /* ARTFilteredListeners.h in Headers */ = {isa = PBXBuildFile; fileRef = B1AABD0F2A6AB90100E9A3E2 /* ARTFilteredListeners.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B1AABD122A6AB90100E9A3E2 /* ARTFilteredListeners.h in Headers */ = {isa = PBXBuildFile; fileRef = B1AABD0F2A6AB90100E9A3E2 /* ARTFilteredListeners.h */; settings = {ATTRIBUTES = (Private, ); }; }; + B1D5DCE62A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D5DCE52A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift */; }; + B1D5DCE72A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D5DCE52A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift */; }; + B1D5DCE82A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D5DCE52A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift */; }; D3AD0EBD215E2FB000312105 /* ARTNSString+ARTUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = D3AD0EBB215E2FB000312105 /* ARTNSString+ARTUtil.h */; settings = {ATTRIBUTES = (Private, ); }; }; D3AD0EBE215E2FB000312105 /* ARTNSString+ARTUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = D3AD0EBC215E2FB000312105 /* ARTNSString+ARTUtil.m */; }; D50D86E929E9444600EA72EA /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = D50D86E829E9444600EA72EA /* JSON.swift */; }; @@ -1318,6 +1336,12 @@ B17D45C12A699BB700D61A54 /* ARTMessageExtrasFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTMessageExtrasFilter.m; sourceTree = ""; }; B17D45C92A699BE300D61A54 /* ARTMessageExtrasFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ARTMessageExtrasFilter.h; path = PrivateHeaders/Ably/ARTMessageExtrasFilter.h; sourceTree = ""; }; B17D45D12A69A9E700D61A54 /* ARTMessageFilter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTMessageFilter.m; sourceTree = ""; }; + B1AABCFB2A6A7BC900E9A3E2 /* ARTFilteredListeners.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARTFilteredListeners.m; sourceTree = ""; }; + B1AABCFF2A6A8FDE00E9A3E2 /* ARTFilteredMessageCallbackFactory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ARTFilteredMessageCallbackFactory.h; path = PrivateHeaders/Ably/ARTFilteredMessageCallbackFactory.h; sourceTree = ""; }; + B1AABD032A6A90B400E9A3E2 /* ARTFilteredMessageCallbackFactory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARTFilteredMessageCallbackFactory.m; sourceTree = ""; }; + B1AABD0B2A6AB71800E9A3E2 /* ARTFilteredListenersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARTFilteredListenersTests.swift; sourceTree = ""; }; + B1AABD0F2A6AB90100E9A3E2 /* ARTFilteredListeners.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ARTFilteredListeners.h; path = PrivateHeaders/Ably/ARTFilteredListeners.h; sourceTree = ""; }; + B1D5DCE52A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARTFilteredMessageCallbackFactoryTest.swift; sourceTree = ""; }; D3AD0EBB215E2FB000312105 /* ARTNSString+ARTUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "ARTNSString+ARTUtil.h"; path = "PrivateHeaders/Ably/ARTNSString+ARTUtil.h"; sourceTree = ""; }; D3AD0EBC215E2FB000312105 /* ARTNSString+ARTUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "ARTNSString+ARTUtil.m"; sourceTree = ""; }; D50D86E829E9444600EA72EA /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = ""; }; @@ -1757,6 +1781,8 @@ EB1AE0CD1C5C3A4900D62250 /* UtilitiesTests.swift */, 2110CC392A530D42007310D4 /* AttachRetryStateTests.swift */, 21088DCA2A53560C0033C722 /* ConnectRetryStateTests.swift */, + B1AABD0B2A6AB71800E9A3E2 /* ARTFilteredListenersTests.swift */, + B1D5DCE52A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift */, ); path = Tests; sourceTree = ""; @@ -1950,6 +1976,7 @@ D746AE311BBC29B2003ECEF8 /* Realtime */ = { isa = PBXGroup; children = ( + B1AABD0F2A6AB90100E9A3E2 /* ARTFilteredListeners.h */, B17D45C92A699BE300D61A54 /* ARTMessageExtrasFilter.h */, B17D45C12A699BB700D61A54 /* ARTMessageExtrasFilter.m */, 96A507BB1A3791490077CDF8 /* ARTRealtime.h */, @@ -1988,6 +2015,9 @@ 2104EFA72A4CC30C00CC1184 /* ARTAttachRetryState.m */, 21088DC22A5354F10033C722 /* ARTConnectRetryState.h */, 21088DC62A5355510033C722 /* ARTConnectRetryState.m */, + B1AABCFB2A6A7BC900E9A3E2 /* ARTFilteredListeners.m */, + B1AABCFF2A6A8FDE00E9A3E2 /* ARTFilteredMessageCallbackFactory.h */, + B1AABD032A6A90B400E9A3E2 /* ARTFilteredMessageCallbackFactory.m */, ); name = Realtime; sourceTree = ""; @@ -2281,6 +2311,7 @@ 1C1EC3FA1AE26A8B00AAADD7 /* ARTStatus.h in Headers */, D70EAAED1BC3376200CD8B9E /* ARTRestChannel.h in Headers */, 96BF61581A35B52C004CF2B3 /* ARTHttp.h in Headers */, + B1AABD002A6A8FDE00E9A3E2 /* ARTFilteredMessageCallbackFactory.h in Headers */, 96BF61641A35CDE1004CF2B3 /* ARTBaseMessage.h in Headers */, D746AE221BBB60EE003ECEF8 /* ARTChannel.h in Headers */, D7D5A69A1CA3D9040071BD6D /* ARTAuthOptions+Private.h in Headers */, @@ -2389,6 +2420,7 @@ D7F2B8B21E42410D00B65151 /* ARTPresenceMessage+Private.h in Headers */, EBB721C52376A948001C3550 /* ARTWebSocket.h in Headers */, EBFA366E1D58B05000B09AA7 /* ARTRestPresence+Private.h in Headers */, + B1AABD102A6AB90100E9A3E2 /* ARTFilteredListeners.h in Headers */, EBFFAC1D1E97FB76003E7326 /* ARTPush+Private.h in Headers */, 960D07971A46FFC300ED8C8C /* ARTRest+Private.h in Headers */, D73691FF1DB788C40062C150 /* ARTAuthDetails.h in Headers */, @@ -2473,8 +2505,10 @@ 21447D40254A2ECE00B3905A /* ARTSRWebSocket.h in Headers */, EBB721CC2376B454001C3550 /* ARTURLSession.h in Headers */, D710D4CE21949BB2008F54AD /* ARTWebSocketTransport+Private.h in Headers */, + B1AABD112A6AB90100E9A3E2 /* ARTFilteredListeners.h in Headers */, D710D56C21949CB9008F54AD /* ARTPushChannelSubscriptions.h in Headers */, D710D56721949CA1008F54AD /* ARTPushActivationStateMachine+Private.h in Headers */, + B1AABD012A6A8FDE00E9A3E2 /* ARTFilteredMessageCallbackFactory.h in Headers */, B17D45BF2A699B9300D61A54 /* ARTMessageFilter.h in Headers */, D798556123ECCDAF00946BE2 /* ARTVCDiffDecoder.h in Headers */, 21E1C0E62A0DC47400A5DB65 /* ARTWebSocketFactory.h in Headers */, @@ -2644,8 +2678,10 @@ D710D55D21949C8D008F54AD /* ARTPushActivationEvent.h in Headers */, D520C4E62680A882000012B2 /* ARTStringifiable+Private.h in Headers */, 21447D45254A2ED100B3905A /* ARTSRWebSocket.h in Headers */, + B1AABD122A6AB90100E9A3E2 /* ARTFilteredListeners.h in Headers */, EBB721CD2376B454001C3550 /* ARTURLSession.h in Headers */, D710D4D021949BB3008F54AD /* ARTWebSocketTransport+Private.h in Headers */, + B1AABD022A6A8FDE00E9A3E2 /* ARTFilteredMessageCallbackFactory.h in Headers */, B17D45C02A699B9300D61A54 /* ARTMessageFilter.h in Headers */, D710D57221949CBA008F54AD /* ARTPushChannelSubscriptions.h in Headers */, 21E1C0E72A0DC47400A5DB65 /* ARTWebSocketFactory.h in Headers */, @@ -2934,7 +2970,7 @@ }; 96BF61301A35B2AB004CF2B3 = { CreatedOnToolsVersion = 6.1.1; - LastSwiftMigration = 1250; + LastSwiftMigration = 1420; }; D7093C09219E2DB200723F17 = { CreatedOnToolsVersion = 10.1; @@ -2948,12 +2984,12 @@ }; D710D45A219495E2008F54AD = { CreatedOnToolsVersion = 10.1; - LastSwiftMigration = 1250; + LastSwiftMigration = 1420; ProvisioningStyle = Automatic; }; D710D474219495FC008F54AD = { CreatedOnToolsVersion = 10.1; - LastSwiftMigration = 1250; + LastSwiftMigration = 1420; ProvisioningStyle = Automatic; }; EB36308823804F7A00B83598 = { @@ -3069,6 +3105,7 @@ EB1B53F922F85CE4006A59AC /* ObjectLifetimesTests.swift in Sources */, 210F67B129E9DB62007B9345 /* TestProxyTransportFactory.swift in Sources */, D7DF73851EA600240013CD36 /* PushActivationStateMachineTests.swift in Sources */, + B1D5DCE62A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift in Sources */, 211A60D729D6D2C300D169C5 /* BackoffRetryDelayCalculatorTests.swift in Sources */, 217FCF3229D62460006E5F2D /* RetrySequenceTests.swift in Sources */, D7FC1ECB209CEA2E001E4153 /* PushTests.swift in Sources */, @@ -3082,6 +3119,7 @@ D780846E1C68B3E50083009D /* NSObject+TestSuite.m in Sources */, 21881E7A283BD08300CFD9E2 /* GCDTests.swift in Sources */, 2124B79729DB144600AD8361 /* DefaultInternalLogCoreTests.swift in Sources */, + B1397E642A6ABB6E00BB2712 /* ARTFilteredListenersTests.swift in Sources */, 21113B5929DCA4C700652C86 /* DataGatherer.swift in Sources */, D7093CA9219EFA8A00723F17 /* MockDeviceStorage.swift in Sources */, D7CEF1321C8DD3BC004FB242 /* RealtimeClientPresenceTests.swift in Sources */, @@ -3200,6 +3238,7 @@ 96A507961A370F860077CDF8 /* ARTStats.m in Sources */, D5BB211326AA994300AA5F3E /* ARTNSURL+ARTUtils.m in Sources */, 96E408481A3895E800087F77 /* ARTWebSocketTransport.m in Sources */, + B1AABCFC2A6A7BC900E9A3E2 /* ARTFilteredListeners.m in Sources */, D7D29B421BE3DEB300374295 /* ARTConnection.m in Sources */, D74CBC08212EB5B900D090E4 /* ARTNSMutableURLRequest+ARTPaginated.m in Sources */, 96BF61711A35FB7C004CF2B3 /* ARTAuth.m in Sources */, @@ -3232,6 +3271,7 @@ D73B655823EF2B2900D459A6 /* ARTDeltaCodec.m in Sources */, D5BB211B26AA9AA700AA5F3E /* ARTNSMutableDictionary+ARTDictionaryUtil.m in Sources */, 2132C31529D5E50A000C4355 /* ARTBackoffRetryDelayCalculator.m in Sources */, + B1AABD042A6A90B400E9A3E2 /* ARTFilteredMessageCallbackFactory.m in Sources */, D768C6AD1E4B5B0200436011 /* ARTDevicePushDetails.m in Sources */, 21E1C0E92A0DC5E600A5DB65 /* ARTWebSocketFactory.m in Sources */, 1C6C18A41ADFDAB100AB79E4 /* ARTLog.m in Sources */, @@ -3263,6 +3303,7 @@ D7093C23219E466E00723F17 /* RestPaginatedTests.swift in Sources */, 21113B6029DDDDD000652C86 /* LogAdapterTests.swift in Sources */, D7093C19219E465300723F17 /* TestUtilities.swift in Sources */, + B1D5DCE72A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift in Sources */, 217FCF4729D626F6006E5F2D /* DefaultJitterCoefficientGeneratorTests.swift in Sources */, 560579DA24AF1BA900A4D03D /* ARTDefaultTests.swift in Sources */, 21113B5A29DCA4C700652C86 /* DataGatherer.swift in Sources */, @@ -3270,6 +3311,7 @@ 2132C22329D233EB000C4355 /* DefaultErrorCheckerTests.swift in Sources */, D5FFA6A929E97EF30082DB4B /* CryptoData.swift in Sources */, D7093C1C219E466400723F17 /* ReadmeExamplesTests.swift in Sources */, + B1397E652A6ABB6F00BB2712 /* ARTFilteredListenersTests.swift in Sources */, 21881E7B283BD0DF00CFD9E2 /* StringifiableTests.swift in Sources */, D7093C27219E466E00723F17 /* RealtimeClientChannelsTests.swift in Sources */, 848ED97426E50D0F0087E800 /* ObjcppTest.mm in Sources */, @@ -3323,6 +3365,7 @@ D7093C7F219EE26400723F17 /* RealtimeClientPresenceTests.swift in Sources */, 21113B6129DDDDD000652C86 /* LogAdapterTests.swift in Sources */, D7093C73219EE26000723F17 /* ReadmeExamplesTests.swift in Sources */, + B1D5DCE82A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift in Sources */, 217FCF4829D626F6006E5F2D /* DefaultJitterCoefficientGeneratorTests.swift in Sources */, 560579DB24AF1BA900A4D03D /* ARTDefaultTests.swift in Sources */, 21113B5B29DCA4C700652C86 /* DataGatherer.swift in Sources */, @@ -3330,6 +3373,7 @@ 2132C22429D233EB000C4355 /* DefaultErrorCheckerTests.swift in Sources */, D5FFA6AA29E97EF30082DB4B /* CryptoData.swift in Sources */, D7093C71219EE25800723F17 /* NSObject+TestSuite.m in Sources */, + B1397E662A6ABB7100BB2712 /* ARTFilteredListenersTests.swift in Sources */, D7093C70219EE25400723F17 /* TestUtilities.swift in Sources */, D7093C80219EE26400723F17 /* StatsTests.swift in Sources */, D7093C77219EE26400723F17 /* RestClientChannelTests.swift in Sources */, @@ -3448,6 +3492,7 @@ D710D66F21949E78008F54AD /* ARTNSArray+ARTFunctional.m in Sources */, D5BB211226AA994200AA5F3E /* ARTNSURL+ARTUtils.m in Sources */, D710D67421949E79008F54AD /* ARTNSString+ARTUtil.m in Sources */, + B1AABCFD2A6A7BC900E9A3E2 /* ARTFilteredListeners.m in Sources */, D710D49921949ACA008F54AD /* ARTAuth.m in Sources */, D710D5D321949D78008F54AD /* ARTTokenParams.m in Sources */, D710D53221949C54008F54AD /* ARTPushChannel.m in Sources */, @@ -3480,6 +3525,7 @@ D710D62D21949E03008F54AD /* ARTHttp.m in Sources */, D5BB211A26AA9AA600AA5F3E /* ARTNSMutableDictionary+ARTDictionaryUtil.m in Sources */, 2132C31629D5E50A000C4355 /* ARTBackoffRetryDelayCalculator.m in Sources */, + B1AABD052A6A90B400E9A3E2 /* ARTFilteredMessageCallbackFactory.m in Sources */, D710D63021949E03008F54AD /* ARTPaginatedResult.m in Sources */, 21E1C0EA2A0DC5E600A5DB65 /* ARTWebSocketFactory.m in Sources */, D710D5E121949D78008F54AD /* ARTStatus.m in Sources */, @@ -3576,6 +3622,7 @@ D710D65521949E77008F54AD /* ARTNSArray+ARTFunctional.m in Sources */, D710D65A21949E77008F54AD /* ARTNSString+ARTUtil.m in Sources */, D710D4A321949ACB008F54AD /* ARTAuth.m in Sources */, + B1AABCFE2A6A7BC900E9A3E2 /* ARTFilteredListeners.m in Sources */, D710D5F921949D79008F54AD /* ARTTokenParams.m in Sources */, D710D54421949C55008F54AD /* ARTPushChannel.m in Sources */, D710D5FA21949D79008F54AD /* ARTTokenDetails.m in Sources */, @@ -3608,6 +3655,7 @@ D710D63D21949E04008F54AD /* ARTHttp.m in Sources */, D710D64021949E04008F54AD /* ARTPaginatedResult.m in Sources */, 2132C31729D5E50A000C4355 /* ARTBackoffRetryDelayCalculator.m in Sources */, + B1AABD062A6A90B400E9A3E2 /* ARTFilteredMessageCallbackFactory.m in Sources */, D710D60721949D79008F54AD /* ARTStatus.m in Sources */, 21E1C0EB2A0DC5E600A5DB65 /* ARTWebSocketFactory.m in Sources */, D5C0CB42268317B500C06521 /* NSURLQueryItem+Stringifiable.m in Sources */, diff --git a/Source/ARTFilteredListeners.m b/Source/ARTFilteredListeners.m new file mode 100644 index 000000000..ddcd17bbd --- /dev/null +++ b/Source/ARTFilteredListeners.m @@ -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 * _filteredListeners; +} + + +- (instancetype) init { + self = [super init]; + if (self) { + _filteredListeners = [[NSMutableArray alloc] init]; + } + + return self; +} + +- (NSMutableArray *) 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 *) removeFilteredListenersByFilter:(ARTMessageFilter *) filter { + + NSMutableArray *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 diff --git a/Source/ARTFilteredMessageCallbackFactory.m b/Source/ARTFilteredMessageCallbackFactory.m new file mode 100644 index 000000000..d5c3a3677 --- /dev/null +++ b/Source/ARTFilteredMessageCallbackFactory.m @@ -0,0 +1,18 @@ +#import "ARTFilteredMessageCallbackFactory.h" +#import + +@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 diff --git a/Source/Ably.modulemap b/Source/Ably.modulemap index 85802efa6..d8c786125 100644 --- a/Source/Ably.modulemap +++ b/Source/Ably.modulemap @@ -110,5 +110,7 @@ framework module Ably { header "ARTAttachRetryState.h" header "ARTConnectRetryState.h" header "ARTMessageExtrasFilter.h" + header "ARTFilteredMessageCallbackFactory.h" + header "ARTFilteredListeners.h" } } diff --git a/Source/PrivateHeaders/Ably/ARTFilteredListeners.h b/Source/PrivateHeaders/Ably/ARTFilteredListeners.h new file mode 100644 index 000000000..d914b57ba --- /dev/null +++ b/Source/PrivateHeaders/Ably/ARTFilteredListeners.h @@ -0,0 +1,43 @@ +#import +#include +#include + +NS_ASSUME_NONNULL_BEGIN + +/// Convenience struct for storing filter/listener pairs +@interface FilteredListenerFilterPair : NSObject + +@property (readonly) ARTEventListener* listener; +@property (readonly) ARTMessageFilter* filter; + +- (instancetype) init NS_UNAVAILABLE; +- (instancetype) initWithListenerAndFilter:(ARTEventListener *) listener withFilter:(ARTMessageFilter *) filter; + +@end + + + +/// A collection of filtered listeners to their filter object. Used for storing client-filtered subscriptions to message interaction. +/// Use of methods on this object is thread-safe. +@interface ARTFilteredListeners : NSObject + +/// Get all the filtered listeners - for testing purposes. +- (NSMutableArray *) getFilteredListeners; + +/// :nodoc: +- (instancetype)init; + +- (void) removeAllListeners; + +/// Add a filtered listener and filter pair to the collection +- (void) addFilteredListener:(ARTEventListener *) listener filter:(ARTMessageFilter *) filter; + +/// Remove all the listeners currently registered for a given filter, returns the removed listeners so that they can be removed from the channel subscriptions +- (NSMutableArray *) removeFilteredListenersByFilter:(ARTMessageFilter *) filter; + +/// Remove the specific filtered listener from the collection. There will only be one instance of each. +- (void) removeFilteredListener:(ARTEventListener *) listener; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/PrivateHeaders/Ably/ARTFilteredMessageCallbackFactory.h b/Source/PrivateHeaders/Ably/ARTFilteredMessageCallbackFactory.h new file mode 100644 index 000000000..140405a48 --- /dev/null +++ b/Source/PrivateHeaders/Ably/ARTFilteredMessageCallbackFactory.h @@ -0,0 +1,14 @@ +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ARTFilteredMessageCallbackFactory : NSObject + +- (instancetype) init NS_UNAVAILABLE; ++ (ARTMessageCallback) createFilteredCallback:(ARTMessageCallback) original filter:(ARTMessageFilter *) filter; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Source/include/Ably.modulemap b/Source/include/Ably.modulemap index acde09bbc..8906b12b1 100644 --- a/Source/include/Ably.modulemap +++ b/Source/include/Ably.modulemap @@ -109,6 +109,8 @@ framework module Ably { header "Ably/ARTWebSocketFactory.h" header "Ably/ARTAttachRetryState.h" header "Ably/ARTConnectRetryState.h" - header "Ably/AblyMessageExtrasFilter.h" + header "Ably/ARTMessageExtrasFilter.h" + header "Ably/ARTFilteredListeners.h" + header "Ably/ARTFilteredMessageCallbackFactory.h" } } diff --git a/Test/Tests/ARTFilteredListenersTests.swift b/Test/Tests/ARTFilteredListenersTests.swift new file mode 100644 index 000000000..4fd5d1b18 --- /dev/null +++ b/Test/Tests/ARTFilteredListenersTests.swift @@ -0,0 +1,121 @@ +import XCTest +import Ably +import Ably.Private + +class ARTFilteredListenersTests : XCTestCase { + + func test_itAddsAFilteredListener() + { + let listener = ARTEventListener(); + let filter = ARTMessageFilter(); + let filteredListeners = ARTFilteredListeners(); + + filteredListeners.addFilteredListener(listener, filter:filter); + + let registeredListeners = filteredListeners.getFilteredListeners() + XCTAssertEqual(1, registeredListeners.count) + + let pair = registeredListeners.object(at: 0) as! FilteredListenerFilterPair + XCTAssertEqual(listener, pair.listener) + XCTAssertEqual(filter, pair.filter) + } + + func test_itClearsFilteredListeners() + { + let listener = ARTEventListener(); + let filter = ARTMessageFilter(); + let filteredListeners = ARTFilteredListeners(); + + filteredListeners.addFilteredListener(listener, filter:filter); + + let registeredListeners = filteredListeners.getFilteredListeners() + XCTAssertEqual(1, registeredListeners.count) + filteredListeners.removeAllListeners() + XCTAssertEqual(0, registeredListeners.count) + } + + func test_itRemovesAListener() + { + let listener1 = ARTEventListener(); + let listener2 = ARTEventListener(); + let filter1 = ARTMessageFilter(); + let filter2 = ARTMessageFilter(); + let filteredListeners = ARTFilteredListeners(); + + filteredListeners.addFilteredListener(listener1, filter:filter1); + filteredListeners.addFilteredListener(listener2, filter:filter2); + + XCTAssertEqual(2, filteredListeners.getFilteredListeners().count); + filteredListeners.removeFilteredListener(listener2) + XCTAssertEqual(1, filteredListeners.getFilteredListeners().count) + + let retainedListener = filteredListeners.getFilteredListeners().object(at: 0) as! FilteredListenerFilterPair + XCTAssertEqual(listener1, retainedListener.listener) + XCTAssertEqual(filter1, retainedListener.filter) + } + + func test_itDoesntRemoveAListener() + { + let listener1 = ARTEventListener(); + let listener2 = ARTEventListener(); + let filter1 = ARTMessageFilter(); + let filter2 = ARTMessageFilter(); + let filteredListeners = ARTFilteredListeners(); + + filteredListeners.addFilteredListener(listener1, filter:filter1); + filteredListeners.addFilteredListener(listener2, filter:filter2); + + XCTAssertEqual(2, filteredListeners.getFilteredListeners().count); + filteredListeners.removeFilteredListener(ARTEventListener()) + XCTAssertEqual(2, filteredListeners.getFilteredListeners().count) + } + + func test_itRemovesAllListenersForAFilter() + { + let listener1 = ARTEventListener(); + let listener2 = ARTEventListener(); + let listener3 = ARTEventListener(); + let listener4 = ARTEventListener(); + let listener5 = ARTEventListener(); + let listener6 = ARTEventListener(); + let listener7 = ARTEventListener(); + let filter1 = ARTMessageFilter(); + let filter2 = ARTMessageFilter(); + let filter3 = ARTMessageFilter(); + + + let filteredListeners = ARTFilteredListeners(); + + filteredListeners.addFilteredListener(listener1, filter:filter1); + filteredListeners.addFilteredListener(listener2, filter:filter2); + filteredListeners.addFilteredListener(listener3, filter:filter1); + filteredListeners.addFilteredListener(listener4, filter:filter1); + filteredListeners.addFilteredListener(listener5, filter:filter2); + filteredListeners.addFilteredListener(listener6, filter:filter3); + filteredListeners.addFilteredListener(listener7, filter:filter1); + + XCTAssertEqual(7, filteredListeners.getFilteredListeners().count); + let removedFilters = filteredListeners.remove(by: filter1) + + + // Check the right listeners are leftover + XCTAssertEqual(3, filteredListeners.getFilteredListeners().count) + let retainedFilter1 = filteredListeners.getFilteredListeners().object(at: 0) as! FilteredListenerFilterPair + XCTAssertEqual(listener2, retainedFilter1.listener) + XCTAssertEqual(filter2, retainedFilter1.filter) + let retainedFilter2 = filteredListeners.getFilteredListeners().object(at: 1) as! FilteredListenerFilterPair + XCTAssertEqual(listener5, retainedFilter2.listener) + XCTAssertEqual(filter2, retainedFilter2.filter) + let retainedFilter3 = filteredListeners.getFilteredListeners().object(at: 2) as! FilteredListenerFilterPair + XCTAssertEqual(listener6, retainedFilter3.listener) + XCTAssertEqual(filter3, retainedFilter3.filter) + + + // Check the right listeners are returned for removal + XCTAssertEqual(4, removedFilters.count) + XCTAssertEqual(listener1, removedFilters.object(at: 0) as! ARTEventListener) + XCTAssertEqual(listener3, removedFilters.object(at: 1) as! ARTEventListener) + XCTAssertEqual(listener4, removedFilters.object(at: 2) as! ARTEventListener) + XCTAssertEqual(listener7, removedFilters.object(at: 3) as! ARTEventListener) + } +} diff --git a/Test/Tests/ARTFilteredMessageCallbackFactoryTest.swift b/Test/Tests/ARTFilteredMessageCallbackFactoryTest.swift new file mode 100644 index 000000000..489427f24 --- /dev/null +++ b/Test/Tests/ARTFilteredMessageCallbackFactoryTest.swift @@ -0,0 +1,43 @@ +import Foundation +import Ably +import Ably.Private +import XCTest + +class ARTFilteredMessageCallbackFactoryTest: XCTestCase { + + func test_returnedInstanceCallsMessageHandlerIfFilterPasses() + { + let filter = ARTMessageFilter(); + filter.clientId = "clientId"; + + var functionCalled = false; + let filteredHandler = ARTFilteredMessageCallbackFactory.createFilteredCallback( + { (message: ARTMessage) in + functionCalled = true + }, + filter: filter); + + let message = ARTMessage(name: "name", data: "abc", clientId: "clientId"); + filteredHandler(message) + + XCTAssertTrue(functionCalled); + } + + func test_returnedInstanceDoesntCallMessageHandlerIfFilterPasses() + { + let filter = ARTMessageFilter(); + filter.clientId = "clientId"; + + var functionCalled = false; + let filteredHandler = ARTFilteredMessageCallbackFactory.createFilteredCallback( + { (message: ARTMessage) in + functionCalled = true + }, + filter: filter); + + let message = ARTMessage(name: "name", data: "abc", clientId: "clientId2"); + filteredHandler(message) + + XCTAssertFalse(functionCalled); + } +} From f2f9f7647297001efaaf851e708a64756a0d3d96 Mon Sep 17 00:00:00 2001 From: Andy Ford Date: Fri, 21 Jul 2023 18:49:12 +0100 Subject: [PATCH 6/8] [RTL22] Message interaction subscriptions This change implements the message interactions specification RTL22 for the ARTRealtimeChannel class. Includes a method to subscribe a listener with a filter as well as one to remove listeners based on the filter they have associated with them (if at all). --- Source/ARTRealtimeChannel.m | 38 ++++ Source/include/Ably/ARTRealtimeChannel.h | 17 ++ Source/include/Ably/ARTTypes.h | 1 + Test/Tests/RealtimeClientChannelTests.swift | 231 ++++++++++++++++++++ 4 files changed, 287 insertions(+) diff --git a/Source/ARTRealtimeChannel.m b/Source/ARTRealtimeChannel.m index 6aa50ce74..e0d068085 100644 --- a/Source/ARTRealtimeChannel.m +++ b/Source/ARTRealtimeChannel.m @@ -33,6 +33,8 @@ #import "ARTBackoffRetryDelayCalculator.h" #import "ARTInternalLog.h" #import "ARTAttachRetryState.h" +#import +#import #if TARGET_OS_IPHONE #import "ARTPushChannel+Private.h" #endif @@ -166,6 +168,10 @@ - (ARTEventListener *_Nullable)subscribe:(NSString *)name onAttach:(nullable ART return [_internal subscribe:name onAttach:onAttach callback:cb]; } +- (ARTEventListener * _Nullable)subscribe:(nonnull ARTMessageCallback)callback filter:(nonnull ARTMessageFilter *)filter { + return [_internal subscribe:callback filter:filter]; +} + - (void)unsubscribe { [_internal unsubscribe]; } @@ -178,6 +184,10 @@ - (void)unsubscribe:(NSString *)name listener:(ARTEventListener *_Nullable)liste [_internal unsubscribe:name listener:listener]; } +- (void)unsubscribeFilter:(ARTMessageFilter *_Nullable)filter { + [_internal unsubscribeFilter:filter]; +} + - (BOOL)history:(ARTRealtimeHistoryQuery *_Nullable)query callback:(ARTPaginatedMessagesCallback)callback error:(NSError *_Nullable *_Nullable)errorPtr { return [_internal history:query callback:callback error:errorPtr]; } @@ -218,6 +228,7 @@ - (void)setOptions:(ARTRealtimeChannelOptions *_Nullable)options callback:(nulla [_internal setOptions:options callback:cb]; } + @end @interface ARTRealtimeChannelInternal () { @@ -250,6 +261,7 @@ @implementation ARTRealtimeChannelInternal { dispatch_queue_t _queue; dispatch_queue_t _userQueue; ARTErrorInfo *_errorReason; + ARTFilteredListeners *_filteredListeners; } - (instancetype)initWithRealtime:(ARTRealtimeInternal *)realtime andName:(NSString *)name withOptions:(ARTRealtimeChannelOptions *)options logger:(ARTInternalLog *)logger { @@ -274,6 +286,7 @@ - (instancetype)initWithRealtime:(ARTRealtimeInternal *)realtime andName:(NSStri _attachRetryState = [[ARTAttachRetryState alloc] initWithRetryDelayCalculator:attachRetryDelayCalculator logger:logger logMessagePrefix:[NSString stringWithFormat:@"RT: %p C:%p ", _realtime, self]]; + _filteredListeners = [[ARTFilteredListeners alloc] init]; } return self; } @@ -540,8 +553,22 @@ - (ARTEventListener *)subscribe:(NSString *)name onAttach:(ARTCallback)onAttach return listener; } +- (ARTEventListener * _Nullable)subscribe:(nonnull ARTMessageCallback)callback filter:(nonnull ARTMessageFilter *)filter { + if (filter == nil) { + ARTLogVerbose(self.logger, @"R:%p C:%p (%@) filter passed to subscribe is null", self->_realtime, self, self.name); + return [self subscribe:callback]; + } + + ARTMessageCallback filteredListener = [ARTFilteredMessageCallbackFactory createFilteredCallback:callback filter:filter]; + ARTEventListener * registeredListener = [self subscribeWithAttachCallback:nil callback:filteredListener]; + [_filteredListeners addFilteredListener:registeredListener filter:filter]; + + return registeredListener; +} + - (void)unsubscribe { dispatch_sync(_queue, ^{ + [_filteredListeners removeAllListeners]; [self _unsubscribe]; ARTLogVerbose(self.logger, @"R:%p C:%p (%@) unsubscribe to all events", self->_realtime, self, self.name); }); @@ -553,11 +580,22 @@ - (void)_unsubscribe { - (void)unsubscribe:(ARTEventListener *)listener { dispatch_sync(_queue, ^{ + [_filteredListeners removeFilteredListener:listener]; [self.messagesEventEmitter off:listener]; ARTLogVerbose(self.logger, @"RT:%p C:%p (%@) unsubscribe to all events", self->_realtime, self, self.name); }); } +- (void)unsubscribeFilter:(ARTMessageFilter *)filter { +dispatch_sync(_queue, ^{ + NSMutableArray * listenersToRemove = [_filteredListeners removeFilteredListenersByFilter:filter]; + for (ARTEventListener * listener in listenersToRemove) { + [self.messagesEventEmitter off:listener]; + } + ARTLogVerbose(self.logger, @"RT:%p C:%p (%@) unsubscribed to all events for filter", self->_realtime, self, self.name); +}); +} + - (void)unsubscribe:(NSString *)name listener:(ARTEventListener *)listener { dispatch_sync(_queue, ^{ [self.messagesEventEmitter off:name listener:listener]; diff --git a/Source/include/Ably/ARTRealtimeChannel.h b/Source/include/Ably/ARTRealtimeChannel.h index 6a153d8aa..64c8ab6c8 100644 --- a/Source/include/Ably/ARTRealtimeChannel.h +++ b/Source/include/Ably/ARTRealtimeChannel.h @@ -97,6 +97,15 @@ NS_ASSUME_NONNULL_BEGIN */ - (ARTEventListener *_Nullable)subscribe:(NSString *)name onAttach:(nullable ARTCallback)onAttach callback:(ARTMessageCallback)callback; +/** + * Registers a listener for messages on this channel. The caller supplies a listener function, which is called each time one or more messages arrives on the channel. + * + * @param callback An event listener function. + * + * @return An `ARTEventListener` object. + */ +- (ARTEventListener *_Nullable)subscribe:(ARTMessageCallback)callback filter:(ARTMessageFilter* ) filter; + /** * Deregisters all listeners to messages on this channel. This removes all earlier subscriptions. */ @@ -117,6 +126,14 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)unsubscribe:(NSString *)name listener:(ARTEventListener *_Nullable)listener; + +/** + * Deregisters any listener for the given filter. + * + * @param filter A filter object to unsubscribe. + */ +- (void)unsubscribeFilter:(ARTMessageFilter *_Nullable)filter; + /** * Retrieves an `ARTPaginatedResult` object, containing an array of historical `ARTMessage` objects for the channel. If the channel is configured to persist messages, then messages can be retrieved from history for up to 72 hours in the past. If not, messages can only be retrieved from history for up to two minutes in the past. * diff --git a/Source/include/Ably/ARTTypes.h b/Source/include/Ably/ARTTypes.h index 0db42f07c..73afb9391 100644 --- a/Source/include/Ably/ARTTypes.h +++ b/Source/include/Ably/ARTTypes.h @@ -2,6 +2,7 @@ #import #import +#import @class ARTStatus; @class ARTHttpResponse; diff --git a/Test/Tests/RealtimeClientChannelTests.swift b/Test/Tests/RealtimeClientChannelTests.swift index 2d93c5661..cd51808da 100644 --- a/Test/Tests/RealtimeClientChannelTests.swift +++ b/Test/Tests/RealtimeClientChannelTests.swift @@ -4688,4 +4688,235 @@ class RealtimeClientChannelTests: XCTestCase { } } } + + // RTL22 + func test_it_subscribes_to_messages_with_filters() throws { + let test = Test() + let ably = ARTRealtime(options: try AblyTests.commonAppSetup(for: test)) + defer { ably.dispose(); ably.close() } + + + // Set up message handler for filtered messages + let filteredMessagesExpectation = XCTestExpectation(description: "filtered-messages-received"); + let filteredMessages = NSMutableArray(); + let filteredHandler = { (message: ARTMessage) in + filteredMessages.add(message) + + if (filteredMessages.count == 2) { + filteredMessagesExpectation.fulfill(); + } + }; + + // Set up message handler for unfiltered messages + let unfilteredMessagesExpectation1 = XCTestExpectation(description: "unfiltered-messages-received-1"); + var firstUnfilteredExpectationFulfilled = false; + let unfilteredMessagesExpectation2 = XCTestExpectation(description: "unfiltered-messages-received-2"); + let unfilteredMessages = NSMutableArray(); + let unfilteredHandler = { (message: ARTMessage) in + unfilteredMessages.add(message) + + if (firstUnfilteredExpectationFulfilled && unfilteredMessages.count == 2) { + unfilteredMessagesExpectation2.fulfill() + } + + if (unfilteredMessages.count == 4) { + firstUnfilteredExpectationFulfilled = true + unfilteredMessagesExpectation1.fulfill() + } + }; + + // Subscribe two listeners, one with a filter, one without. + let filter = ARTMessageFilter() + filter.name = "event-1" + + let channel = ably.channels.get("client-filter-test"); + let filteredListener = channel.subscribe(filteredHandler, filter: filter) + channel.subscribe(unfilteredHandler) + + waitUntil(timeout: testTimeout) { done in + channel.attach { _ in + done() + } + } + + + // Publish some messages + channel.publish([ARTMessage(name: "event-1", data: "test-1")]) + channel.publish([ARTMessage(name: "event-2", data: "test-2")]) + channel.publish([ARTMessage(name: "event-3", data: "test-3")]) + channel.publish([ARTMessage(name: "event-1", data: "test-4")]) + + AblyTests.wait(for: [unfilteredMessagesExpectation1], timeout: testTimeout) + AblyTests.wait(for: [filteredMessagesExpectation], timeout: testTimeout) + + // Check we get them + XCTAssertEqual("test-1", (filteredMessages.object(at: 0) as! ARTMessage).data as! String) + XCTAssertEqual("test-4", (filteredMessages.object(at: 1) as! ARTMessage).data as! String) + + XCTAssertEqual("test-1", (unfilteredMessages.object(at: 0) as! ARTMessage).data as! String) + XCTAssertEqual("test-2", (unfilteredMessages.object(at: 1) as! ARTMessage).data as! String) + XCTAssertEqual("test-3", (unfilteredMessages.object(at: 2) as! ARTMessage).data as! String) + XCTAssertEqual("test-4", (unfilteredMessages.object(at: 3) as! ARTMessage).data as! String) + + // Reset + filteredMessages.removeAllObjects(); + unfilteredMessages.removeAllObjects(); + + // Unsubscribe + channel.unsubscribe(filteredListener) + + // Publish more + channel.publish([ARTMessage(name: "event-1", data: "test-5")]) + channel.publish([ARTMessage(name: "event-1", data: "test-6")]) + + // Wait + AblyTests.wait(for: [unfilteredMessagesExpectation2], timeout: testTimeout) + + // Check unfiltered + XCTAssertEqual("test-5", (unfilteredMessages.object(at: 0) as! ARTMessage).data as! String) + XCTAssertEqual("test-6", (unfilteredMessages.object(at: 1) as! ARTMessage).data as! String) + + // Check filtered receives nothing + XCTAssertEqual(0, filteredMessages.count) + } + + // RTL22 + func test_it_unsubscribes_with_a_filter() throws { + let test = Test() + let ably = ARTRealtime(options: try AblyTests.commonAppSetup(for: test)) + defer { ably.dispose(); ably.close() } + + + // Set up message handler for filtered messages + let filteredMessagesExpectation1 = XCTestExpectation(description: "filtered-messages-received"); + let filteredMessages1 = NSMutableArray(); + let filteredHandler1 = { (message: ARTMessage) in + filteredMessages1.add(message) + + if (filteredMessages1.count == 2) { + filteredMessagesExpectation1.fulfill(); + } + }; + + // Set up another message handler for filtered messages + let filteredMessagesExpectation2 = XCTestExpectation(description: "filtered-messages-received-2"); + let filteredMessages2 = NSMutableArray(); + let filteredHandler2 = { (message: ARTMessage) in + filteredMessages2.add(message) + + if (filteredMessages2.count == 2) { + filteredMessagesExpectation2.fulfill(); + } + }; + + // Set up another message handler for filtered messages + let filteredMessagesExpectation3a = XCTestExpectation(description: "filtered-messages-received-3a"); + let filteredMessagesExpectation3b = XCTestExpectation(description: "filtered-messages-received-3b"); + var firstFiltered3ExpectationMet = false; + let filteredMessages3 = NSMutableArray(); + let filteredHandler3 = { (message: ARTMessage) in + filteredMessages3.add(message) + + if (filteredMessages3.count == 1) { + if (firstFiltered3ExpectationMet) { + filteredMessagesExpectation3b.fulfill() + } else { + firstFiltered3ExpectationMet = true; + filteredMessagesExpectation3a.fulfill() + } + } + }; + + // Set up message handler for unfiltered messages + let unfilteredMessagesExpectation1 = XCTestExpectation(description: "unfiltered-messages-received-1"); + var firstUnfilteredExpectationFulfilled = false; + let unfilteredMessagesExpectation2 = XCTestExpectation(description: "unfiltered-messages-received-2"); + let unfilteredMessages = NSMutableArray(); + let unfilteredHandler = { (message: ARTMessage) in + unfilteredMessages.add(message) + + if (firstUnfilteredExpectationFulfilled && unfilteredMessages.count == 3) { + unfilteredMessagesExpectation2.fulfill() + } + + if (unfilteredMessages.count == 4) { + firstUnfilteredExpectationFulfilled = true + unfilteredMessagesExpectation1.fulfill() + } + }; + + // Subscribe listeners + let filter1 = ARTMessageFilter() + filter1.name = "event-1" + + let filter2 = ARTMessageFilter() + filter2.name = "event-2" + + let channel = ably.channels.get("client-filter-test"); + channel.subscribe(filteredHandler1, filter: filter1) + channel.subscribe(filteredHandler2, filter: filter1) + channel.subscribe(filteredHandler3, filter: filter2) + channel.subscribe(unfilteredHandler) + + waitUntil(timeout: testTimeout) { done in + channel.attach { _ in + done() + } + } + + + // Publish some messages + channel.publish([ARTMessage(name: "event-1", data: "test-1")]) + channel.publish([ARTMessage(name: "event-2", data: "test-2")]) + channel.publish([ARTMessage(name: "event-3", data: "test-3")]) + channel.publish([ARTMessage(name: "event-1", data: "test-4")]) + + AblyTests.wait(for: [unfilteredMessagesExpectation1], timeout: testTimeout) + AblyTests.wait(for: [filteredMessagesExpectation1], timeout: testTimeout) + AblyTests.wait(for: [filteredMessagesExpectation2], timeout: testTimeout) + AblyTests.wait(for: [filteredMessagesExpectation3a], timeout: testTimeout) + + // Check we get them + XCTAssertEqual("test-1", (filteredMessages1.object(at: 0) as! ARTMessage).data as! String) + XCTAssertEqual("test-4", (filteredMessages1.object(at: 1) as! ARTMessage).data as! String) + + XCTAssertEqual("test-1", (filteredMessages2.object(at: 0) as! ARTMessage).data as! String) + XCTAssertEqual("test-4", (filteredMessages2.object(at: 1) as! ARTMessage).data as! String) + + XCTAssertEqual("test-2", (filteredMessages3.object(at: 0) as! ARTMessage).data as! String) + + XCTAssertEqual("test-1", (unfilteredMessages.object(at: 0) as! ARTMessage).data as! String) + XCTAssertEqual("test-2", (unfilteredMessages.object(at: 1) as! ARTMessage).data as! String) + XCTAssertEqual("test-3", (unfilteredMessages.object(at: 2) as! ARTMessage).data as! String) + XCTAssertEqual("test-4", (unfilteredMessages.object(at: 3) as! ARTMessage).data as! String) + + // Reset + filteredMessages1.removeAllObjects(); + filteredMessages2.removeAllObjects(); + filteredMessages3.removeAllObjects(); + unfilteredMessages.removeAllObjects(); + + // Unsubscribe + channel.unsubscribeFilter(filter1) + + // Publish more + channel.publish([ARTMessage(name: "event-1", data: "test-5")]) + channel.publish([ARTMessage(name: "event-1", data: "test-6")]) + channel.publish([ARTMessage(name: "event-2", data: "test-7")]) + + // Wait + AblyTests.wait(for: [unfilteredMessagesExpectation2], timeout: testTimeout) + AblyTests.wait(for: [filteredMessagesExpectation3b], timeout: testTimeout) + + // Check unfiltered and kept filtered receives + XCTAssertEqual("test-5", (unfilteredMessages.object(at: 0) as! ARTMessage).data as! String) + XCTAssertEqual("test-6", (unfilteredMessages.object(at: 1) as! ARTMessage).data as! String) + XCTAssertEqual("test-7", (unfilteredMessages.object(at: 2) as! ARTMessage).data as! String) + + XCTAssertEqual("test-7", (filteredMessages3.object(at: 0) as! ARTMessage).data as! String) + + // Check removed filtered receives nothing + XCTAssertEqual(0, filteredMessages1.count) + XCTAssertEqual(0, filteredMessages2.count) + } } From 2c27e29c363a1b09b33f46734b63204fdac56c9a Mon Sep 17 00:00:00 2001 From: Andy Ford Date: Fri, 21 Jul 2023 18:54:58 +0100 Subject: [PATCH 7/8] rename class --- Ably.xcodeproj/project.pbxproj | 16 ++++++++-------- ...ARTFilteredMessageCallbackFactoryTests.swift} | 0 2 files changed, 8 insertions(+), 8 deletions(-) rename Test/Tests/{ARTFilteredMessageCallbackFactoryTest.swift => ARTFilteredMessageCallbackFactoryTests.swift} (100%) diff --git a/Ably.xcodeproj/project.pbxproj b/Ably.xcodeproj/project.pbxproj index 08298620f..7110e6d49 100644 --- a/Ably.xcodeproj/project.pbxproj +++ b/Ably.xcodeproj/project.pbxproj @@ -373,9 +373,9 @@ B1AABD102A6AB90100E9A3E2 /* ARTFilteredListeners.h in Headers */ = {isa = PBXBuildFile; fileRef = B1AABD0F2A6AB90100E9A3E2 /* ARTFilteredListeners.h */; settings = {ATTRIBUTES = (Private, ); }; }; B1AABD112A6AB90100E9A3E2 /* ARTFilteredListeners.h in Headers */ = {isa = PBXBuildFile; fileRef = B1AABD0F2A6AB90100E9A3E2 /* ARTFilteredListeners.h */; settings = {ATTRIBUTES = (Private, ); }; }; B1AABD122A6AB90100E9A3E2 /* ARTFilteredListeners.h in Headers */ = {isa = PBXBuildFile; fileRef = B1AABD0F2A6AB90100E9A3E2 /* ARTFilteredListeners.h */; settings = {ATTRIBUTES = (Private, ); }; }; - B1D5DCE62A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D5DCE52A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift */; }; - B1D5DCE72A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D5DCE52A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift */; }; - B1D5DCE82A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D5DCE52A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift */; }; + B1D5DCE62A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D5DCE52A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTests.swift */; }; + B1D5DCE72A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D5DCE52A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTests.swift */; }; + B1D5DCE82A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D5DCE52A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTests.swift */; }; D3AD0EBD215E2FB000312105 /* ARTNSString+ARTUtil.h in Headers */ = {isa = PBXBuildFile; fileRef = D3AD0EBB215E2FB000312105 /* ARTNSString+ARTUtil.h */; settings = {ATTRIBUTES = (Private, ); }; }; D3AD0EBE215E2FB000312105 /* ARTNSString+ARTUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = D3AD0EBC215E2FB000312105 /* ARTNSString+ARTUtil.m */; }; D50D86E929E9444600EA72EA /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = D50D86E829E9444600EA72EA /* JSON.swift */; }; @@ -1341,7 +1341,7 @@ B1AABD032A6A90B400E9A3E2 /* ARTFilteredMessageCallbackFactory.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ARTFilteredMessageCallbackFactory.m; sourceTree = ""; }; B1AABD0B2A6AB71800E9A3E2 /* ARTFilteredListenersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARTFilteredListenersTests.swift; sourceTree = ""; }; B1AABD0F2A6AB90100E9A3E2 /* ARTFilteredListeners.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ARTFilteredListeners.h; path = PrivateHeaders/Ably/ARTFilteredListeners.h; sourceTree = ""; }; - B1D5DCE52A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARTFilteredMessageCallbackFactoryTest.swift; sourceTree = ""; }; + B1D5DCE52A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARTFilteredMessageCallbackFactoryTests.swift; sourceTree = ""; }; D3AD0EBB215E2FB000312105 /* ARTNSString+ARTUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "ARTNSString+ARTUtil.h"; path = "PrivateHeaders/Ably/ARTNSString+ARTUtil.h"; sourceTree = ""; }; D3AD0EBC215E2FB000312105 /* ARTNSString+ARTUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "ARTNSString+ARTUtil.m"; sourceTree = ""; }; D50D86E829E9444600EA72EA /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = ""; }; @@ -1782,7 +1782,7 @@ 2110CC392A530D42007310D4 /* AttachRetryStateTests.swift */, 21088DCA2A53560C0033C722 /* ConnectRetryStateTests.swift */, B1AABD0B2A6AB71800E9A3E2 /* ARTFilteredListenersTests.swift */, - B1D5DCE52A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift */, + B1D5DCE52A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTests.swift */, ); path = Tests; sourceTree = ""; @@ -3105,7 +3105,7 @@ EB1B53F922F85CE4006A59AC /* ObjectLifetimesTests.swift in Sources */, 210F67B129E9DB62007B9345 /* TestProxyTransportFactory.swift in Sources */, D7DF73851EA600240013CD36 /* PushActivationStateMachineTests.swift in Sources */, - B1D5DCE62A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift in Sources */, + B1D5DCE62A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTests.swift in Sources */, 211A60D729D6D2C300D169C5 /* BackoffRetryDelayCalculatorTests.swift in Sources */, 217FCF3229D62460006E5F2D /* RetrySequenceTests.swift in Sources */, D7FC1ECB209CEA2E001E4153 /* PushTests.swift in Sources */, @@ -3303,7 +3303,7 @@ D7093C23219E466E00723F17 /* RestPaginatedTests.swift in Sources */, 21113B6029DDDDD000652C86 /* LogAdapterTests.swift in Sources */, D7093C19219E465300723F17 /* TestUtilities.swift in Sources */, - B1D5DCE72A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift in Sources */, + B1D5DCE72A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTests.swift in Sources */, 217FCF4729D626F6006E5F2D /* DefaultJitterCoefficientGeneratorTests.swift in Sources */, 560579DA24AF1BA900A4D03D /* ARTDefaultTests.swift in Sources */, 21113B5A29DCA4C700652C86 /* DataGatherer.swift in Sources */, @@ -3365,7 +3365,7 @@ D7093C7F219EE26400723F17 /* RealtimeClientPresenceTests.swift in Sources */, 21113B6129DDDDD000652C86 /* LogAdapterTests.swift in Sources */, D7093C73219EE26000723F17 /* ReadmeExamplesTests.swift in Sources */, - B1D5DCE82A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTest.swift in Sources */, + B1D5DCE82A6AC857004A2156 /* ARTFilteredMessageCallbackFactoryTests.swift in Sources */, 217FCF4829D626F6006E5F2D /* DefaultJitterCoefficientGeneratorTests.swift in Sources */, 560579DB24AF1BA900A4D03D /* ARTDefaultTests.swift in Sources */, 21113B5B29DCA4C700652C86 /* DataGatherer.swift in Sources */, diff --git a/Test/Tests/ARTFilteredMessageCallbackFactoryTest.swift b/Test/Tests/ARTFilteredMessageCallbackFactoryTests.swift similarity index 100% rename from Test/Tests/ARTFilteredMessageCallbackFactoryTest.swift rename to Test/Tests/ARTFilteredMessageCallbackFactoryTests.swift From a4ae1d55ab5b7d1ee1ce642bea7a41706adb50a8 Mon Sep 17 00:00:00 2001 From: Andy Ford Date: Fri, 21 Jul 2023 19:13:18 +0100 Subject: [PATCH 8/8] docs: README update for message interactions --- README.md | 61 +++++++++++++++++++ ...TFilteredMessageCallbackFactoryTests.swift | 2 +- Test/Tests/RealtimeClientChannelTests.swift | 38 ++++++------ 3 files changed, 81 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 034d85bad..4d288257a 100644 --- a/README.md +++ b/README.md @@ -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** diff --git a/Test/Tests/ARTFilteredMessageCallbackFactoryTests.swift b/Test/Tests/ARTFilteredMessageCallbackFactoryTests.swift index 489427f24..7c94a2d4b 100644 --- a/Test/Tests/ARTFilteredMessageCallbackFactoryTests.swift +++ b/Test/Tests/ARTFilteredMessageCallbackFactoryTests.swift @@ -3,7 +3,7 @@ import Ably import Ably.Private import XCTest -class ARTFilteredMessageCallbackFactoryTest: XCTestCase { +class ARTFilteredMessageCallbackFactoryTests: XCTestCase { func test_returnedInstanceCallsMessageHandlerIfFilterPasses() { diff --git a/Test/Tests/RealtimeClientChannelTests.swift b/Test/Tests/RealtimeClientChannelTests.swift index cd51808da..94a150faa 100644 --- a/Test/Tests/RealtimeClientChannelTests.swift +++ b/Test/Tests/RealtimeClientChannelTests.swift @@ -4697,21 +4697,21 @@ class RealtimeClientChannelTests: XCTestCase { // Set up message handler for filtered messages - let filteredMessagesExpectation = XCTestExpectation(description: "filtered-messages-received"); - let filteredMessages = NSMutableArray(); + let filteredMessagesExpectation = XCTestExpectation(description: "filtered-messages-received") + let filteredMessages = NSMutableArray() let filteredHandler = { (message: ARTMessage) in filteredMessages.add(message) if (filteredMessages.count == 2) { - filteredMessagesExpectation.fulfill(); + filteredMessagesExpectation.fulfill() } }; // Set up message handler for unfiltered messages - let unfilteredMessagesExpectation1 = XCTestExpectation(description: "unfiltered-messages-received-1"); - var firstUnfilteredExpectationFulfilled = false; - let unfilteredMessagesExpectation2 = XCTestExpectation(description: "unfiltered-messages-received-2"); - let unfilteredMessages = NSMutableArray(); + let unfilteredMessagesExpectation1 = XCTestExpectation(description: "unfiltered-messages-received-1") + var firstUnfilteredExpectationFulfilled = false + let unfilteredMessagesExpectation2 = XCTestExpectation(description: "unfiltered-messages-received-2") + let unfilteredMessages = NSMutableArray() let unfilteredHandler = { (message: ARTMessage) in unfilteredMessages.add(message) @@ -4788,30 +4788,30 @@ class RealtimeClientChannelTests: XCTestCase { // Set up message handler for filtered messages - let filteredMessagesExpectation1 = XCTestExpectation(description: "filtered-messages-received"); - let filteredMessages1 = NSMutableArray(); + let filteredMessagesExpectation1 = XCTestExpectation(description: "filtered-messages-received") + let filteredMessages1 = NSMutableArray() let filteredHandler1 = { (message: ARTMessage) in filteredMessages1.add(message) if (filteredMessages1.count == 2) { - filteredMessagesExpectation1.fulfill(); + filteredMessagesExpectation1.fulfill() } }; // Set up another message handler for filtered messages - let filteredMessagesExpectation2 = XCTestExpectation(description: "filtered-messages-received-2"); + let filteredMessagesExpectation2 = XCTestExpectation(description: "filtered-messages-received-2") let filteredMessages2 = NSMutableArray(); let filteredHandler2 = { (message: ARTMessage) in filteredMessages2.add(message) if (filteredMessages2.count == 2) { - filteredMessagesExpectation2.fulfill(); + filteredMessagesExpectation2.fulfill() } - }; + } // Set up another message handler for filtered messages - let filteredMessagesExpectation3a = XCTestExpectation(description: "filtered-messages-received-3a"); - let filteredMessagesExpectation3b = XCTestExpectation(description: "filtered-messages-received-3b"); + let filteredMessagesExpectation3a = XCTestExpectation(description: "filtered-messages-received-3a") + let filteredMessagesExpectation3b = XCTestExpectation(description: "filtered-messages-received-3b") var firstFiltered3ExpectationMet = false; let filteredMessages3 = NSMutableArray(); let filteredHandler3 = { (message: ARTMessage) in @@ -4828,10 +4828,10 @@ class RealtimeClientChannelTests: XCTestCase { }; // Set up message handler for unfiltered messages - let unfilteredMessagesExpectation1 = XCTestExpectation(description: "unfiltered-messages-received-1"); - var firstUnfilteredExpectationFulfilled = false; - let unfilteredMessagesExpectation2 = XCTestExpectation(description: "unfiltered-messages-received-2"); - let unfilteredMessages = NSMutableArray(); + let unfilteredMessagesExpectation1 = XCTestExpectation(description: "unfiltered-messages-received-1") + var firstUnfilteredExpectationFulfilled = false + let unfilteredMessagesExpectation2 = XCTestExpectation(description: "unfiltered-messages-received-2") + let unfilteredMessages = NSMutableArray() let unfilteredHandler = { (message: ARTMessage) in unfilteredMessages.add(message)