Skip to content

Commit 51ded3f

Browse files
should not persist context message ID in loggedMessageIDs (#3670)
1 parent 6537441 commit 51ded3f

File tree

5 files changed

+275
-25
lines changed

5 files changed

+275
-25
lines changed

Example/Messaging/Tests/FIRMessagingClientTest.m

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,6 @@
1515
*/
1616

1717
#import <XCTest/XCTest.h>
18-
19-
20-
2118
#import <OCMock/OCMock.h>
2219

2320
#import <FirebaseInstanceID/FIRInstanceID_Private.h>
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
/*
2+
* Copyright 2017 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import <XCTest/XCTest.h>
18+
19+
#import <OCMock/OCMock.h>
20+
21+
#import <FirebaseCore/FIRAppInternal.h>
22+
#import <FirebaseInstanceID/FirebaseInstanceID.h>
23+
#import <FirebaseAnalyticsInterop/FIRAnalyticsInterop.h>
24+
#import <FirebaseMessaging/FIRMessaging.h>
25+
26+
#import "Example/Messaging/Tests/FIRMessagingTestUtilities.h"
27+
#import "Firebase/Messaging/FIRMessaging_Private.h"
28+
#import "Firebase/Messaging/FIRMessagingAnalytics.h"
29+
#import "Firebase/Messaging/FIRMessagingRmqManager.h"
30+
#import "Firebase/Messaging/FIRMessagingSyncMessageManager.h"
31+
32+
extern NSString *const kFIRMessagingFCMTokenFetchAPNSOption;
33+
34+
/// The NSUserDefaults domain for testing.
35+
static NSString *const kFIRMessagingDefaultsTestDomain = @"com.messaging.tests";
36+
37+
@interface FIRMessaging ()
38+
39+
@property(nonatomic, readwrite, strong) NSString *defaultFcmToken;
40+
@property(nonatomic, readwrite, strong) FIRInstanceID *instanceID;
41+
@property(nonatomic, readwrite, strong) FIRMessagingRmqManager *rmq2Manager;
42+
43+
- (BOOL)handleContextManagerMessage:(NSDictionary *)message;
44+
- (void)handleIncomingLinkIfNeededFromMessage:(NSDictionary *)message;
45+
46+
@end
47+
48+
/*
49+
* This class checks if we handle the received message properly
50+
* based on each type of messages. Checks include duplicate message handling,
51+
* analytics logging, etc.
52+
*/
53+
@interface FIRMessagingHandlingTest : XCTestCase
54+
55+
@property(nonatomic, readonly, strong) FIRMessaging *messaging;
56+
@property(nonatomic, strong) FIRMessagingAnalytics *messageAnalytics;
57+
@property(nonatomic, strong) id mockMessaging;
58+
@property(nonatomic, strong) id mockInstanceID;
59+
@property(nonatomic, strong) id mockFirebaseApp;
60+
@property(nonatomic, strong) id mockMessagingAnalytics;
61+
62+
@end
63+
64+
@implementation FIRMessagingHandlingTest
65+
66+
- (void)setUp {
67+
[super setUp];
68+
69+
// Create the messaging instance with all the necessary dependencies.
70+
NSUserDefaults *defaults =
71+
[[NSUserDefaults alloc] initWithSuiteName:kFIRMessagingDefaultsTestDomain];
72+
_messaging = [FIRMessagingTestUtilities messagingForTestsWithUserDefaults:defaults];
73+
_mockFirebaseApp = OCMClassMock([FIRApp class]);
74+
OCMStub([_mockFirebaseApp defaultApp]).andReturn(_mockFirebaseApp);
75+
_mockInstanceID = OCMPartialMock(self.messaging.instanceID);
76+
[[NSUserDefaults standardUserDefaults]
77+
removePersistentDomainForName:[NSBundle mainBundle].bundleIdentifier];
78+
_mockMessaging = OCMPartialMock(_messaging);
79+
_mockMessagingAnalytics = OCMClassMock([FIRMessagingAnalytics class]);
80+
}
81+
82+
- (void)tearDown {
83+
[self.messaging.messagingUserDefaults removePersistentDomainForName:kFIRMessagingDefaultsTestDomain];
84+
self.messaging.shouldEstablishDirectChannel = NO;
85+
self.messaging.defaultFcmToken = nil;
86+
[_mockMessagingAnalytics stopMocking];
87+
[_mockMessaging stopMocking];
88+
[_mockInstanceID stopMocking];
89+
[_mockFirebaseApp stopMocking];
90+
_messaging = nil;
91+
[super tearDown];
92+
}
93+
94+
-(void)testEmptyNotification {
95+
XCTAssertEqualObjects(@(FIRMessagingMessageStatusUnknown), @([_mockMessaging appDidReceiveMessage:@{}].status));
96+
}
97+
98+
-(void)testAPNSDisplayNotification {
99+
NSDictionary *notificationPayload = @{
100+
@"aps": @{
101+
@"alert" : @{
102+
@"body" : @"body of notification",
103+
@"title" : @"title of notification",
104+
}
105+
},
106+
@"gcm.message_id" : @"1566515013484879",
107+
@"gcm.n.e" : @1,
108+
@"google.c.a.c_id" : @"7379928225816991517",
109+
@"google.c.a.e" : @1,
110+
@"google.c.a.ts" : @1566515009,
111+
@"google.c.a.udt" : @0
112+
};
113+
OCMExpect([_mockMessaging handleContextManagerMessage:notificationPayload]);
114+
OCMExpect([_mockMessaging handleIncomingLinkIfNeededFromMessage:notificationPayload]);
115+
OCMExpect([_mockMessagingAnalytics logMessage:notificationPayload toAnalytics:[OCMArg any]]);
116+
XCTAssertEqualObjects(@(FIRMessagingMessageStatusNew),
117+
@([_messaging appDidReceiveMessage:notificationPayload].status));
118+
OCMVerifyAll(_mockMessaging);
119+
120+
OCMReject([_mockMessaging handleContextManagerMessage:notificationPayload]);
121+
OCMReject([_mockMessaging handleIncomingLinkIfNeededFromMessage:notificationPayload]);
122+
OCMReject([_mockMessagingAnalytics logMessage:notificationPayload toAnalytics:[OCMArg any]]);
123+
124+
XCTAssertEqualObjects(@(FIRMessagingMessageStatusNew),
125+
@([_messaging appDidReceiveMessage:notificationPayload].status));
126+
OCMVerifyAll(_mockMessaging);
127+
// Clear database
128+
[_messaging.rmq2Manager deleteSyncMessageWithRmqID:@"1566515013484879"];
129+
}
130+
131+
-(void)testAPNSContentAvailableNotification {
132+
NSDictionary *notificationPayload = @{
133+
@"aps": @{
134+
@"content-available" : @1
135+
},
136+
@"gcm.message_id" : @"1566513591299872",
137+
@"image" : @"bunny.png",
138+
@"google.c.a.e" : @1
139+
};
140+
OCMExpect([_mockMessaging handleContextManagerMessage:notificationPayload]);
141+
OCMExpect([_mockMessaging handleIncomingLinkIfNeededFromMessage:notificationPayload]);
142+
OCMExpect([_mockMessagingAnalytics logMessage:notificationPayload toAnalytics:[OCMArg any]]);
143+
XCTAssertEqualObjects(@(FIRMessagingMessageStatusNew),
144+
@([_messaging appDidReceiveMessage:notificationPayload].status));
145+
OCMVerifyAll(_mockMessaging);
146+
147+
OCMReject([_mockMessaging handleContextManagerMessage:notificationPayload]);
148+
OCMReject([_mockMessaging handleIncomingLinkIfNeededFromMessage:notificationPayload]);
149+
OCMReject([_mockMessagingAnalytics logMessage:notificationPayload toAnalytics:[OCMArg any]]);
150+
151+
XCTAssertEqualObjects(@(FIRMessagingMessageStatusNew),
152+
@([_messaging appDidReceiveMessage:notificationPayload].status));
153+
OCMVerifyAll(_mockMessaging);
154+
[_messaging.rmq2Manager deleteSyncMessageWithRmqID:@"1566513591299872"];
155+
156+
}
157+
158+
-(void)testAPNSContentAvailableContextualNotification {
159+
NSDictionary *notificationPayload = @{
160+
@"aps" : @{
161+
@"content-available": @1
162+
},
163+
@"gcm.message_id": @"1566515531287827",
164+
@"gcm.n.e" : @1,
165+
@"gcm.notification.body" : @"Local time zone message!",
166+
@"gcm.notification.title" : @"Hello",
167+
@"gcms" : @"gcm.gmsproc.cm",
168+
@"google.c.a.c_id" : @"5941428497527920876",
169+
@"google.c.a.e" : @1,
170+
@"google.c.a.ts" : @1566565920,
171+
@"google.c.a.udt" : @1,
172+
@"google.c.cm.cat" : @"com.google.firebase.messaging.testapp.dev",
173+
@"google.c.cm.lt_end" : @"2019-09-20 13:12:00",
174+
@"google.c.cm.lt_start" : @"2019-08-23 13:12:00",
175+
};
176+
OCMExpect([_mockMessaging handleContextManagerMessage:notificationPayload]);
177+
OCMExpect([_mockMessaging handleIncomingLinkIfNeededFromMessage:notificationPayload]);
178+
OCMExpect([_mockMessagingAnalytics logMessage:notificationPayload toAnalytics:[OCMArg any]]);
179+
XCTAssertEqualObjects(@(FIRMessagingMessageStatusNew),
180+
@([_messaging appDidReceiveMessage:notificationPayload].status));
181+
OCMVerifyAll(_mockMessaging);
182+
183+
OCMReject([_mockMessaging handleContextManagerMessage:notificationPayload]);
184+
OCMReject([_mockMessaging handleIncomingLinkIfNeededFromMessage:notificationPayload]);
185+
OCMReject([_mockMessagingAnalytics logMessage:notificationPayload toAnalytics:[OCMArg any]]);
186+
187+
XCTAssertEqualObjects(@(FIRMessagingMessageStatusNew),
188+
@([_messaging appDidReceiveMessage:notificationPayload].status));
189+
OCMVerifyAll(_mockMessaging);
190+
[_messaging.rmq2Manager deleteSyncMessageWithRmqID:@"1566515531287827"];
191+
192+
}
193+
194+
-(void)testContextualLocalNotification {
195+
NSDictionary *notificationPayload = @{
196+
@"gcm.message_id": @"1566515531281975",
197+
@"gcm.n.e" : @1,
198+
@"gcm.notification.body" : @"Local time zone message!",
199+
@"gcm.notification.title" : @"Hello",
200+
@"gcms" : @"gcm.gmsproc.cm",
201+
@"google.c.a.c_id" : @"5941428497527920876",
202+
@"google.c.a.e" : @1,
203+
@"google.c.a.ts" : @1566565920,
204+
@"google.c.a.udt" : @1,
205+
};
206+
OCMExpect([_mockMessaging handleContextManagerMessage:notificationPayload]);
207+
OCMExpect([_mockMessaging handleIncomingLinkIfNeededFromMessage:notificationPayload]);
208+
OCMExpect([_mockMessagingAnalytics logMessage:notificationPayload toAnalytics:[OCMArg any]]);
209+
XCTAssertEqualObjects(@(FIRMessagingMessageStatusNew),
210+
@([_messaging appDidReceiveMessage:notificationPayload].status));
211+
OCMVerifyAll(_mockMessaging);
212+
213+
OCMReject([_mockMessaging handleContextManagerMessage:notificationPayload]);
214+
OCMReject([_mockMessaging handleIncomingLinkIfNeededFromMessage:notificationPayload]);
215+
OCMReject([_mockMessagingAnalytics logMessage:notificationPayload toAnalytics:[OCMArg any]]);
216+
217+
XCTAssertEqualObjects(@(FIRMessagingMessageStatusNew),
218+
@([_messaging appDidReceiveMessage:notificationPayload].status));
219+
OCMVerifyAll(_mockMessaging);
220+
[_messaging.rmq2Manager deleteSyncMessageWithRmqID:@"1566515531281975"];
221+
}
222+
223+
-(void)testMCSNotification {
224+
NSDictionary *notificationPayload = @{
225+
@"from" : @"35006771263",
226+
@"image" : @"bunny.png"
227+
};
228+
OCMExpect([_mockMessaging handleContextManagerMessage:notificationPayload]);
229+
OCMExpect([_mockMessaging handleIncomingLinkIfNeededFromMessage:notificationPayload]);
230+
OCMExpect([_mockMessagingAnalytics logMessage:notificationPayload toAnalytics:[OCMArg any]]);
231+
XCTAssertEqualObjects(@(FIRMessagingMessageStatusNew),
232+
@([_messaging appDidReceiveMessage:notificationPayload].status));
233+
OCMVerifyAll(_mockMessaging);
234+
235+
OCMExpect([_mockMessaging handleContextManagerMessage:notificationPayload]);
236+
OCMExpect([_mockMessaging handleIncomingLinkIfNeededFromMessage:notificationPayload]);
237+
OCMExpect([_mockMessagingAnalytics logMessage:notificationPayload toAnalytics:[OCMArg any]]);
238+
239+
XCTAssertEqualObjects(@(FIRMessagingMessageStatusNew),
240+
@([_messaging appDidReceiveMessage:notificationPayload].status));
241+
OCMVerifyAll(_mockMessaging);
242+
}
243+
244+
@end

Example/Messaging/Tests/FIRMessagingTest.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
extern NSString *const kFIRMessagingFCMTokenFetchAPNSOption;
3030

3131
/// The NSUserDefaults domain for testing.
32-
NSString *const kFIRMessagingDefaultsTestDomain = @"com.messaging.tests";
32+
static NSString *const kFIRMessagingDefaultsTestDomain = @"com.messaging.tests";
3333

3434
@interface FIRMessaging ()
3535

Firebase/Messaging/FIRMessaging.m

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,20 @@
8686
NSString *const kFIRMessagingPlistAutoInitEnabled =
8787
@"FirebaseMessagingAutoInitEnabled"; // Auto Init Enabled key stored in Info.plist
8888

89+
const BOOL FIRMessagingIsAPNSSyncMessage(NSDictionary *message) {
90+
if ([message[kFIRMessagingMessageViaAPNSRootKey] isKindOfClass:[NSDictionary class]]) {
91+
NSDictionary *aps = message[kFIRMessagingMessageViaAPNSRootKey];
92+
if (aps && [aps isKindOfClass:[NSDictionary class]]) {
93+
return [aps[kFIRMessagingMessageAPNSContentAvailableKey] boolValue];
94+
}
95+
}
96+
return NO;
97+
}
98+
99+
BOOL FIRMessagingIsContextManagerMessage(NSDictionary *message) {
100+
return [FIRMessagingContextManagerService isContextManagerMessage:message];
101+
}
102+
89103
@interface FIRMessagingMessageInfo ()
90104

91105
@property(nonatomic, readwrite, assign) FIRMessagingMessageStatus status;
@@ -384,21 +398,25 @@ - (FIRMessagingMessageInfo *)appDidReceiveMessage:(NSDictionary *)message {
384398
// the message to the device.
385399
BOOL isOldMessage = NO;
386400
NSString *messageID = message[kFIRMessagingMessageIDKey];
387-
if ([messageID length]) {
401+
if (messageID.length) {
388402
[self.rmq2Manager saveS2dMessageWithRmqId:messageID];
389403

390-
BOOL isSyncMessage = [[self class] isAPNSSyncMessage:message];
404+
BOOL isSyncMessage = FIRMessagingIsAPNSSyncMessage(message);
391405
if (isSyncMessage) {
392406
isOldMessage = [self.syncMessageManager didReceiveAPNSSyncMessage:message];
393407
}
394-
}
395-
// Prevent duplicates by keeping a cache of all the logged messages during each session.
396-
// The duplicates only happen when the 3P app calls `appDidReceiveMessage:` along with
397-
// us swizzling their implementation to call the same method implicitly.
398-
if (!isOldMessage && messageID.length) {
399-
isOldMessage = [self.loggedMessageIDs containsObject:messageID];
400-
if (!isOldMessage) {
401-
[self.loggedMessageIDs addObject:messageID];
408+
409+
// Prevent duplicates by keeping a cache of all the logged messages during each session.
410+
// The duplicates only happen when the 3P app calls `appDidReceiveMessage:` along with
411+
// us swizzling their implementation to call the same method implicitly.
412+
// We need to rule out the contextual message because it shares the same message ID
413+
// as the local notification it will schedule. And because it is also a APNSSync message
414+
// its duplication is already checked previously.
415+
if (!isOldMessage && !FIRMessagingIsContextManagerMessage(message)) {
416+
isOldMessage = [self.loggedMessageIDs containsObject:messageID];
417+
if (!isOldMessage) {
418+
[self.loggedMessageIDs addObject:messageID];
419+
}
402420
}
403421
}
404422

@@ -411,20 +429,12 @@ - (FIRMessagingMessageInfo *)appDidReceiveMessage:(NSDictionary *)message {
411429
}
412430

413431
- (BOOL)handleContextManagerMessage:(NSDictionary *)message {
414-
if ([FIRMessagingContextManagerService isContextManagerMessage:message]) {
432+
if (FIRMessagingIsContextManagerMessage(message)) {
415433
return [FIRMessagingContextManagerService handleContextManagerMessage:message];
416434
}
417435
return NO;
418436
}
419437

420-
+ (BOOL)isAPNSSyncMessage:(NSDictionary *)message {
421-
if ([message[kFIRMessagingMessageViaAPNSRootKey] isKindOfClass:[NSDictionary class]]) {
422-
NSDictionary *aps = message[kFIRMessagingMessageViaAPNSRootKey];
423-
return [aps[kFIRMessagingMessageAPNSContentAvailableKey] boolValue];
424-
}
425-
return NO;
426-
}
427-
428438
- (void)handleIncomingLinkIfNeededFromMessage:(NSDictionary *)message {
429439
#if TARGET_OS_IOS || TARGET_OS_TV
430440
NSURL *url = [self linkURLFromMessage:message];

Firebase/Messaging/FIRMessagingContextManagerService.m

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@
4444
NSString *const kFIRMessagingContextManagerSoundKey = kFIRMessagingContextManagerNotificationKeyPrefix @"sound";
4545
NSString *const kFIRMessagingContextManagerContentAvailableKey =
4646
kFIRMessagingContextManagerNotificationKeyPrefix @"content-available";
47-
NSString *const kFIRMessagingAPNSPayloadKey = @"aps";
48-
47+
static NSString *const kFIRMessagingAPNSPayloadKey = @"aps";
4948

5049
typedef NS_ENUM(NSUInteger, FIRMessagingContextManagerMessageType) {
5150
FIRMessagingContextManagerMessageTypeNone,

0 commit comments

Comments
 (0)