added iOS source code
[wl-app.git] / iOS / Pods / FirebaseMessaging / Firebase / Messaging / FIRMessaging.m
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 #if  !__has_feature(objc_arc)
18 #error FIRMessagingLib should be compiled with ARC.
19 #endif
20
21 #import "FIRMessaging.h"
22 #import "FIRMessaging_Private.h"
23
24 #import <UIKit/UIKit.h>
25
26 #import "FIRMessagingClient.h"
27 #import "FIRMessagingConstants.h"
28 #import "FIRMessagingContextManagerService.h"
29 #import "FIRMessagingDataMessageManager.h"
30 #import "FIRMessagingDefines.h"
31 #import "FIRMessagingLogger.h"
32 #import "FIRMessagingPubSub.h"
33 #import "FIRMessagingReceiver.h"
34 #import "FIRMessagingRemoteNotificationsProxy.h"
35 #import "FIRMessagingRmqManager.h"
36 #import "FIRMessagingSyncMessageManager.h"
37 #import "FIRMessagingUtilities.h"
38 #import "FIRMessagingVersionUtilities.h"
39 #import "FIRMessaging_Private.h"
40
41 #import <FirebaseCore/FIRAppInternal.h>
42 #import <FirebaseInstanceID/FirebaseInstanceID.h>
43 #import <GoogleUtilities/GULReachabilityChecker.h>
44
45 #import "NSError+FIRMessaging.h"
46
47 static NSString *const kFIRMessagingMessageViaAPNSRootKey = @"aps";
48 static NSString *const kFIRMessagingReachabilityHostname = @"www.google.com";
49 static NSString *const kFIRMessagingDefaultTokenScope = @"*";
50 static NSString *const kFIRMessagingFCMTokenFetchAPNSOption = @"apns_token";
51
52 #if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
53 const NSNotificationName FIRMessagingSendSuccessNotification =
54     @"com.firebase.messaging.notif.send-success";
55 const NSNotificationName FIRMessagingSendErrorNotification =
56     @"com.firebase.messaging.notif.send-error";
57 const NSNotificationName FIRMessagingMessagesDeletedNotification =
58     @"com.firebase.messaging.notif.messages-deleted";
59 const NSNotificationName FIRMessagingConnectionStateChangedNotification =
60     @"com.firebase.messaging.notif.connection-state-changed";
61 const NSNotificationName FIRMessagingRegistrationTokenRefreshedNotification =
62     @"com.firebase.messaging.notif.fcm-token-refreshed";
63 #else
64 NSString *const FIRMessagingSendSuccessNotification =
65     @"com.firebase.messaging.notif.send-success";
66 NSString *const FIRMessagingSendErrorNotification =
67     @"com.firebase.messaging.notif.send-error";
68 NSString * const FIRMessagingMessagesDeletedNotification =
69     @"com.firebase.messaging.notif.messages-deleted";
70 NSString * const FIRMessagingConnectionStateChangedNotification =
71     @"com.firebase.messaging.notif.connection-state-changed";
72 NSString * const FIRMessagingRegistrationTokenRefreshedNotification =
73     @"com.firebase.messaging.notif.fcm-token-refreshed";
74 #endif  // defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
75
76 NSString *const kFIRMessagingUserDefaultsKeyAutoInitEnabled =
77     @"com.firebase.messaging.auto-init.enabled";  // Auto Init Enabled key stored in NSUserDefaults
78
79 NSString *const kFIRMessagingAPNSTokenType = @"APNSTokenType"; // APNS Token type key stored in user info.
80
81 NSString *const kFIRMessagingPlistAutoInitEnabled =
82     @"FirebaseMessagingAutoInitEnabled";  // Auto Init Enabled key stored in Info.plist
83
84 @interface FIRMessagingMessageInfo ()
85
86 @property(nonatomic, readwrite, assign) FIRMessagingMessageStatus status;
87
88 @end
89
90 @implementation FIRMessagingMessageInfo
91
92 - (instancetype)init {
93   FIRMessagingInvalidateInitializer();
94 }
95
96 - (instancetype)initWithStatus:(FIRMessagingMessageStatus)status {
97   self = [super init];
98   if (self) {
99     _status = status;
100   }
101   return self;
102 }
103
104 @end
105
106 #pragma mark - for iOS 10 compatibility
107 @implementation FIRMessagingRemoteMessage
108
109 - (instancetype)init {
110   self = [super init];
111   if (self) {
112     _appData = [[NSMutableDictionary alloc] init];
113   }
114
115   return self;
116 }
117
118 - (instancetype)initWithMessage:(FIRMessagingRemoteMessage *)message {
119   self = [self init];
120   if (self) {
121     _appData = [message.appData copy];
122   }
123
124   return self;
125 }
126
127 @end
128
129 @interface FIRMessaging ()<FIRMessagingClientDelegate, FIRMessagingReceiverDelegate,
130                            GULReachabilityDelegate>
131
132 // FIRApp properties
133 @property(nonatomic, readwrite, strong) NSData *apnsTokenData;
134 @property(nonatomic, readwrite, strong) NSString *defaultFcmToken;
135
136 @property(nonatomic, readwrite, strong) FIRInstanceID *instanceID;
137
138 @property(nonatomic, readwrite, assign) BOOL isClientSetup;
139
140 @property(nonatomic, readwrite, strong) FIRMessagingClient *client;
141 @property(nonatomic, readwrite, strong) GULReachabilityChecker *reachability;
142 @property(nonatomic, readwrite, strong) FIRMessagingDataMessageManager *dataMessageManager;
143 @property(nonatomic, readwrite, strong) FIRMessagingPubSub *pubsub;
144 @property(nonatomic, readwrite, strong) FIRMessagingRmqManager *rmq2Manager;
145 @property(nonatomic, readwrite, strong) FIRMessagingReceiver *receiver;
146 @property(nonatomic, readwrite, strong) FIRMessagingSyncMessageManager *syncMessageManager;
147 @property(nonatomic, readwrite, strong) NSUserDefaults *messagingUserDefaults;
148
149 /// Message ID's logged for analytics. This prevents us from logging the same message twice
150 /// which can happen if the user inadvertently calls `appDidReceiveMessage` along with us
151 /// calling it implicitly during swizzling.
152 @property(nonatomic, readwrite, strong) NSMutableSet *loggedMessageIDs;
153
154 - (instancetype)initWithInstanceID:(FIRInstanceID *)instanceID
155                       userDefaults:(NSUserDefaults *)defaults NS_DESIGNATED_INITIALIZER;
156
157 @end
158
159 @implementation FIRMessaging
160
161 + (FIRMessaging *)messaging {
162   static FIRMessaging *messaging;
163   static dispatch_once_t onceToken;
164   dispatch_once(&onceToken, ^{
165     messaging = [[FIRMessaging alloc] initPrivately];
166     [messaging start];
167   });
168   return messaging;
169 }
170
171 - (instancetype)initWithInstanceID:(FIRInstanceID *)instanceID
172                       userDefaults:(NSUserDefaults *)defaults {
173   self = [super init];
174   if (self != nil) {
175     _loggedMessageIDs = [NSMutableSet set];
176     _instanceID = instanceID;
177     _messagingUserDefaults = defaults;
178   }
179   return self;
180 }
181
182 - (instancetype)initPrivately {
183   return [self initWithInstanceID:[FIRInstanceID instanceID]
184                      userDefaults:[NSUserDefaults standardUserDefaults]];
185 }
186
187 - (void)dealloc {
188   [self.reachability stop];
189   [[NSNotificationCenter defaultCenter] removeObserver:self];
190   [self teardown];
191 }
192
193 #pragma mark - Config
194
195 + (void)load {
196   [[NSNotificationCenter defaultCenter] addObserver:self
197                                            selector:@selector(didReceiveConfigureSDKNotification:)
198                                                name:kFIRAppReadyToConfigureSDKNotification
199                                              object:nil];
200 }
201
202 + (void)didReceiveConfigureSDKNotification:(NSNotification *)notification {
203   NSDictionary *appInfoDict = notification.userInfo;
204   NSNumber *isDefaultApp = appInfoDict[kFIRAppIsDefaultAppKey];
205   if (![isDefaultApp boolValue]) {
206     // Only configure for the default FIRApp.
207     FIRMessagingLoggerDebug(kFIRMessagingMessageCodeFIRApp001,
208                             @"Firebase Messaging only works with the default app.");
209     return;
210   }
211
212   NSString *appName = appInfoDict[kFIRAppNameKey];
213   FIRApp *app = [FIRApp appNamed:appName];
214   [[FIRMessaging messaging] configureMessaging:app];
215 }
216
217 - (void)configureMessaging:(FIRApp *)app {
218   // Swizzle remote-notification-related methods (app delegate and UNUserNotificationCenter)
219   if ([FIRMessagingRemoteNotificationsProxy canSwizzleMethods]) {
220     NSString *docsURLString = @"https://firebase.google.com/docs/cloud-messaging/ios/client"
221                               @"#method_swizzling_in_firebase_messaging";
222     FIRMessagingLoggerNotice(kFIRMessagingMessageCodeFIRApp000,
223                              @"FIRMessaging Remote Notifications proxy enabled, will swizzle "
224                              @"remote notification receiver handlers. If you'd prefer to manually "
225                              @"integrate Firebase Messaging, add \"%@\" to your Info.plist, "
226                              @"and set it to NO. Follow the instructions at:\n%@\nto ensure "
227                              @"proper integration.",
228                              kFIRMessagingRemoteNotificationsProxyEnabledInfoPlistKey,
229                              docsURLString);
230     [FIRMessagingRemoteNotificationsProxy swizzleMethods];
231   }
232 }
233
234 - (void)start {
235   // Print the library version for logging.
236   NSString *currentLibraryVersion = FIRMessagingCurrentLibraryVersion();
237   FIRMessagingLoggerInfo(kFIRMessagingMessageCodeMessagingPrintLibraryVersion,
238                          @"FIRMessaging library version %@",
239                          currentLibraryVersion);
240
241   [self setupReceiver];
242
243   NSString *hostname = kFIRMessagingReachabilityHostname;
244   self.reachability = [[GULReachabilityChecker alloc] initWithReachabilityDelegate:self
245                                                                           withHost:hostname];
246   [self.reachability start];
247
248   [self setupApplicationSupportSubDirectory];
249   // setup FIRMessaging objects
250   [self setupRmqManager];
251   [self setupClient];
252   [self setupSyncMessageManager];
253   [self setupDataMessageManager];
254   [self setupTopics];
255
256   self.isClientSetup = YES;
257   [self setupNotificationListeners];
258 }
259
260 - (void)setupApplicationSupportSubDirectory {
261   NSString *messagingSubDirectory = kFIRMessagingApplicationSupportSubDirectory;
262   if (![[self class] hasApplicationSupportSubDirectory:messagingSubDirectory]) {
263     [[self class] createApplicationSupportSubDirectory:messagingSubDirectory];
264   }
265 }
266
267 - (void)setupNotificationListeners {
268   // To prevent multiple notifications remove self as observer for all events.
269   NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
270   [center removeObserver:self];
271
272   [center addObserver:self
273              selector:@selector(didReceiveDefaultInstanceIDToken:)
274                  name:kFIRMessagingFCMTokenNotification
275                object:nil];
276   [center addObserver:self
277              selector:@selector(defaultInstanceIDTokenWasRefreshed:)
278                  name:kFIRMessagingRegistrationTokenRefreshNotification
279                object:nil];
280   [center addObserver:self
281              selector:@selector(applicationStateChanged)
282                  name:UIApplicationDidBecomeActiveNotification
283                object:nil];
284   [center addObserver:self
285              selector:@selector(applicationStateChanged)
286                  name:UIApplicationDidEnterBackgroundNotification
287                object:nil];
288 }
289
290 - (void)setupReceiver {
291   self.receiver = [[FIRMessagingReceiver alloc] init];
292   self.receiver.delegate = self;
293 }
294
295 - (void)setupClient {
296   self.client = [[FIRMessagingClient alloc] initWithDelegate:self
297                                                 reachability:self.reachability
298                                                  rmq2Manager:self.rmq2Manager];
299 }
300
301 - (void)setupDataMessageManager {
302   self.dataMessageManager =
303       [[FIRMessagingDataMessageManager alloc] initWithDelegate:self.receiver
304                                                         client:self.client
305                                                    rmq2Manager:self.rmq2Manager
306                                             syncMessageManager:self.syncMessageManager];
307
308   [self.dataMessageManager refreshDelayedMessages];
309   [self.client setDataMessageManager:self.dataMessageManager];
310 }
311
312 - (void)setupRmqManager {
313   self.rmq2Manager = [[FIRMessagingRmqManager alloc] initWithDatabaseName:@"rmq2"];
314   [self.rmq2Manager loadRmqId];
315 }
316
317 - (void)setupTopics {
318   _FIRMessagingDevAssert(self.client, @"Invalid nil client before init pubsub.");
319   self.pubsub = [[FIRMessagingPubSub alloc] initWithClient:self.client];
320 }
321
322 - (void)setupSyncMessageManager {
323   self.syncMessageManager =
324       [[FIRMessagingSyncMessageManager alloc] initWithRmqManager:self.rmq2Manager];
325
326   // Delete the expired messages with a delay. We don't want to block startup with a somewhat
327   // expensive db call.
328   FIRMessaging_WEAKIFY(self);
329   dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
330   dispatch_after(time, dispatch_get_main_queue(), ^{
331     FIRMessaging_STRONGIFY(self);
332     [self.syncMessageManager removeExpiredSyncMessages];
333   });
334 }
335
336 - (void)teardown {
337   _FIRMessagingDevAssert([NSThread isMainThread],
338                          @"FIRMessaging should be called from main thread only.");
339   [self.client teardown];
340   self.pubsub = nil;
341   self.syncMessageManager = nil;
342   self.rmq2Manager = nil;
343   self.dataMessageManager = nil;
344   self.client = nil;
345   self.isClientSetup = NO;
346   FIRMessagingLoggerDebug(kFIRMessagingMessageCodeMessaging001, @"Did successfully teardown");
347 }
348
349 #pragma mark - Messages
350
351 - (FIRMessagingMessageInfo *)appDidReceiveMessage:(NSDictionary *)message {
352   if (!message.count) {
353     return [[FIRMessagingMessageInfo alloc] initWithStatus:FIRMessagingMessageStatusUnknown];
354   }
355
356   // For downstream messages that go via MCS we should strip out this key before sending
357   // the message to the device.
358   BOOL isOldMessage = NO;
359   NSString *messageID = message[kFIRMessagingMessageIDKey];
360   if ([messageID length]) {
361     [self.rmq2Manager saveS2dMessageWithRmqId:messageID];
362
363     BOOL isSyncMessage = [[self class] isAPNSSyncMessage:message];
364     if (isSyncMessage) {
365       isOldMessage = [self.syncMessageManager didReceiveAPNSSyncMessage:message];
366     }
367   }
368   // Prevent duplicates by keeping a cache of all the logged messages during each session.
369   // The duplicates only happen when the 3P app calls `appDidReceiveMessage:` along with
370   // us swizzling their implementation to call the same method implicitly.
371   if (!isOldMessage && messageID.length) {
372     isOldMessage = [self.loggedMessageIDs containsObject:messageID];
373     if (!isOldMessage) {
374       [self.loggedMessageIDs addObject:messageID];
375     }
376   }
377
378   if (!isOldMessage) {
379     Class firMessagingLogClass = NSClassFromString(@"FIRMessagingLog");
380     SEL logMessageSelector = NSSelectorFromString(@"logMessage:");
381
382     if ([firMessagingLogClass respondsToSelector:logMessageSelector]) {
383 #pragma clang diagnostic push
384 #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
385       [firMessagingLogClass performSelector:logMessageSelector
386                                  withObject:message];
387     }
388 #pragma clang diagnostic pop
389     [self handleContextManagerMessage:message];
390     [self handleIncomingLinkIfNeededFromMessage:message];
391   }
392   return [[FIRMessagingMessageInfo alloc] initWithStatus:FIRMessagingMessageStatusNew];
393 }
394
395 - (BOOL)handleContextManagerMessage:(NSDictionary *)message {
396   if ([FIRMessagingContextManagerService isContextManagerMessage:message]) {
397     return [FIRMessagingContextManagerService handleContextManagerMessage:message];
398   }
399   return NO;
400 }
401
402 + (BOOL)isAPNSSyncMessage:(NSDictionary *)message {
403   if ([message[kFIRMessagingMessageViaAPNSRootKey] isKindOfClass:[NSDictionary class]]) {
404     NSDictionary *aps = message[kFIRMessagingMessageViaAPNSRootKey];
405     return [aps[kFIRMessagingMessageAPNSContentAvailableKey] boolValue];
406   }
407   return NO;
408 }
409
410 - (void)handleIncomingLinkIfNeededFromMessage:(NSDictionary *)message {
411   NSURL *url = [self linkURLFromMessage:message];
412   if (url == nil) {
413     return;
414   }
415   if (![NSThread isMainThread]) {
416     dispatch_async(dispatch_get_main_queue(), ^{
417       [self handleIncomingLinkIfNeededFromMessage:message];
418
419     });
420     return;
421   }
422   UIApplication *application = FIRMessagingUIApplication();
423   if (!application) {
424     return;
425   }
426   id<UIApplicationDelegate> appDelegate = application.delegate;
427   SEL continueUserActivitySelector =
428       @selector(application:continueUserActivity:restorationHandler:);
429   SEL openURLWithOptionsSelector = @selector(application:openURL:options:);
430   SEL openURLWithSourceApplicationSelector =
431       @selector(application:openURL:sourceApplication:annotation:);
432   SEL handleOpenURLSelector = @selector(application:handleOpenURL:);
433   // Due to FIRAAppDelegateProxy swizzling, this selector will most likely get chosen, whether or
434   // not the actual application has implemented
435   // |application:continueUserActivity:restorationHandler:|. A warning will be displayed to the user
436   // if they haven't implemented it.
437   if ([NSUserActivity class] != nil &&
438       [appDelegate respondsToSelector:continueUserActivitySelector]) {
439     NSUserActivity *userActivity =
440         [[NSUserActivity alloc] initWithActivityType:NSUserActivityTypeBrowsingWeb];
441     userActivity.webpageURL = url;
442     [appDelegate application:application
443         continueUserActivity:userActivity
444           restorationHandler:^(NSArray * _Nullable restorableObjects) {
445       // Do nothing, as we don't support the app calling this block
446     }];
447
448   } else if ([appDelegate respondsToSelector:openURLWithOptionsSelector]) {
449 #pragma clang diagnostic push
450 #pragma clang diagnostic ignored "-Wunguarded-availability"
451     [appDelegate application:application openURL:url options:@{}];
452 #pragma clang diagnostic pop
453
454   // Similarly, |application:openURL:sourceApplication:annotation:| will also always be called, due
455   // to the default swizzling done by FIRAAppDelegateProxy in Firebase Analytics
456   } else if ([appDelegate respondsToSelector:openURLWithSourceApplicationSelector]) {
457 #pragma clang diagnostic push
458 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
459     [appDelegate application:application
460                      openURL:url
461            sourceApplication:FIRMessagingAppIdentifier()
462                   annotation:@{}];
463   } else if ([appDelegate respondsToSelector:handleOpenURLSelector]) {
464     [appDelegate application:application handleOpenURL:url];
465 #pragma clang diagnostic pop
466   }
467 }
468
469 - (NSURL *)linkURLFromMessage:(NSDictionary *)message {
470   NSString *urlString = message[kFIRMessagingMessageLinkKey];
471   if (urlString == nil || ![urlString isKindOfClass:[NSString class]] || urlString.length == 0) {
472     return nil;
473   }
474   NSURL *url = [NSURL URLWithString:urlString];
475   return url;
476 }
477
478 #pragma mark - APNS
479
480 - (NSData *)APNSToken {
481   return self.apnsTokenData;
482 }
483
484 - (void)setAPNSToken:(NSData *)APNSToken {
485   [self setAPNSToken:APNSToken type:FIRMessagingAPNSTokenTypeUnknown];
486 }
487
488 - (void)setAPNSToken:(NSData *)apnsToken type:(FIRMessagingAPNSTokenType)type {
489   if ([apnsToken isEqual:self.apnsTokenData]) {
490     return;
491   }
492   self.apnsTokenData = apnsToken;
493
494   // Notify InstanceID that APNS Token has been set.
495   NSDictionary *userInfo = @{kFIRMessagingAPNSTokenType : @(type)};
496   NSNotification *notification =
497       [NSNotification notificationWithName:kFIRMessagingAPNSTokenNotification
498                                     object:[apnsToken copy]
499                                   userInfo:userInfo];
500   [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];
501 }
502
503 #pragma mark - FCM
504
505 - (BOOL)isAutoInitEnabled {
506   // Check storage
507   id isAutoInitEnabledObject =
508       [_messagingUserDefaults objectForKey:kFIRMessagingUserDefaultsKeyAutoInitEnabled];
509   if (isAutoInitEnabledObject) {
510     return [isAutoInitEnabledObject boolValue];
511   }
512
513   // Check Info.plist
514   isAutoInitEnabledObject =
515       [[NSBundle mainBundle] objectForInfoDictionaryKey:kFIRMessagingPlistAutoInitEnabled];
516   if (isAutoInitEnabledObject) {
517     return [isAutoInitEnabledObject boolValue];
518   }
519
520   // If none of above exists, we default to the global switch that comes from FIRApp.
521   return [[FIRApp defaultApp] isDataCollectionDefaultEnabled];
522 }
523
524 - (void)setAutoInitEnabled:(BOOL)autoInitEnabled {
525   BOOL isFCMAutoInitEnabled = [self isAutoInitEnabled];
526   [_messagingUserDefaults setBool:autoInitEnabled
527                            forKey:kFIRMessagingUserDefaultsKeyAutoInitEnabled];
528   [_messagingUserDefaults synchronize];
529   if (!isFCMAutoInitEnabled && autoInitEnabled) {
530 #pragma clang diagnostic push
531 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
532     self.defaultFcmToken = self.instanceID.token;
533 #pragma clang diagnostic pop
534   }
535 }
536
537 - (NSString *)FCMToken {
538   NSString *token = self.defaultFcmToken;
539   if (!token) {
540     // We may not have received it from Instance ID yet (via NSNotification), so extract it directly
541 #pragma clang diagnostic push
542 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
543     token = self.instanceID.token;
544 #pragma clang diagnostic pop
545   }
546   return token;
547 }
548
549 - (void)retrieveFCMTokenForSenderID:(nonnull NSString *)senderID
550                          completion:(nonnull FIRMessagingFCMTokenFetchCompletion)completion {
551   if (!senderID.length) {
552     FIRMessagingLoggerError(kFIRMessagingMessageCodeSenderIDNotSuppliedForTokenFetch,
553                             @"Sender ID not supplied. It is required for a token fetch, "
554                             @"to identify the sender.");
555     if (completion) {
556       NSString *description = @"Couldn't fetch token because a Sender ID was not supplied. A valid "
557                               @"Sender ID is required to fetch an FCM token";
558       NSError *error = [NSError fcm_errorWithCode:FIRMessagingErrorInvalidRequest
559                                          userInfo:@{NSLocalizedDescriptionKey : description}];
560       completion(nil, error);
561     }
562     return;
563   }
564   NSDictionary *options = nil;
565   if (self.APNSToken) {
566     options = @{kFIRMessagingFCMTokenFetchAPNSOption : self.APNSToken};
567   } else {
568     FIRMessagingLoggerWarn(kFIRMessagingMessageCodeAPNSTokenNotAvailableDuringTokenFetch,
569                            @"APNS device token not set before retrieving FCM Token for Sender ID "
570                            @"'%@'. Notifications to this FCM Token will not be delivered over APNS."
571                            @"Be sure to re-retrieve the FCM token once the APNS device token is "
572                            @"set.", senderID);
573   }
574   [self.instanceID tokenWithAuthorizedEntity:senderID
575                                        scope:kFIRMessagingDefaultTokenScope
576                                      options:options
577                                      handler:completion];
578 }
579
580 - (void)deleteFCMTokenForSenderID:(nonnull NSString *)senderID
581                        completion:(nonnull FIRMessagingDeleteFCMTokenCompletion)completion {
582   if (!senderID.length) {
583     FIRMessagingLoggerError(kFIRMessagingMessageCodeSenderIDNotSuppliedForTokenDelete,
584                             @"Sender ID not supplied. It is required to delete an FCM token.");
585     if (completion) {
586       NSString *description = @"Couldn't delete token because a Sender ID was not supplied. A "
587                               @"valid Sender ID is required to delete an FCM token";
588       NSError *error = [NSError fcm_errorWithCode:FIRMessagingErrorInvalidRequest
589                                          userInfo:@{NSLocalizedDescriptionKey : description}];
590       completion(error);
591     }
592     return;
593   }
594   [self.instanceID deleteTokenWithAuthorizedEntity:senderID
595                                              scope:kFIRMessagingDefaultTokenScope
596                                            handler:completion];
597 }
598
599 #pragma mark - FIRMessagingDelegate helper methods
600 - (void)setDelegate:(id<FIRMessagingDelegate>)delegate {
601   _delegate = delegate;
602   [self validateDelegateConformsToTokenAvailabilityMethods];
603 }
604
605 // Check if the delegate conforms to |didReceiveRegistrationToken:|
606 // and display a warning to the developer if not.
607 // NOTE: Once |didReceiveRegistrationToken:| can be made a required method, this
608 // check can be removed.
609 - (void)validateDelegateConformsToTokenAvailabilityMethods {
610   if (self.delegate &&
611       ![self.delegate respondsToSelector:@selector(messaging:didReceiveRegistrationToken:)]) {
612     FIRMessagingLoggerWarn(kFIRMessagingMessageCodeTokenDelegateMethodsNotImplemented,
613                            @"The object %@ does not respond to "
614                            @"-messaging:didReceiveRegistrationToken:. Please implement "
615                            @"-messaging:didReceiveRegistrationToken: to be provided with an FCM "
616                            @"token.", self.delegate.description);
617   }
618 }
619
620 - (void)notifyDelegateOfFCMTokenAvailability {
621   __weak FIRMessaging *weakSelf = self;
622   if (![NSThread isMainThread]) {
623     dispatch_async(dispatch_get_main_queue(), ^{
624       [weakSelf notifyDelegateOfFCMTokenAvailability];
625     });
626     return;
627   }
628   if ([self.delegate respondsToSelector:@selector(messaging:didReceiveRegistrationToken:)]) {
629     [self.delegate messaging:self didReceiveRegistrationToken:self.defaultFcmToken];
630   }
631 }
632
633 #pragma mark - Application State Changes
634
635 - (void)applicationStateChanged {
636   if (self.shouldEstablishDirectChannel) {
637     [self updateAutomaticClientConnection];
638   }
639 }
640
641 #pragma mark - Direct Channel
642
643 - (void)setShouldEstablishDirectChannel:(BOOL)shouldEstablishDirectChannel {
644   if (_shouldEstablishDirectChannel == shouldEstablishDirectChannel) {
645     return;
646   }
647   _shouldEstablishDirectChannel = shouldEstablishDirectChannel;
648   [self updateAutomaticClientConnection];
649 }
650
651 - (BOOL)isDirectChannelEstablished {
652   return self.client.isConnectionActive;
653 }
654
655 - (BOOL)shouldBeConnectedAutomatically {
656   // We require a token from Instance ID
657   NSString *token = self.defaultFcmToken;
658   // Only on foreground connections
659   UIApplication *application = FIRMessagingUIApplication();
660   if (!application) {
661     return NO;
662   }
663   UIApplicationState applicationState = application.applicationState;
664   BOOL shouldBeConnected = _shouldEstablishDirectChannel &&
665                            (token.length > 0) &&
666                            applicationState == UIApplicationStateActive;
667   return shouldBeConnected;
668 }
669
670 - (void)updateAutomaticClientConnection {
671   if (![NSThread isMainThread]) {
672     // Call this method from the main thread
673     dispatch_async(dispatch_get_main_queue(), ^{
674       [self updateAutomaticClientConnection];
675     });
676     return;
677   }
678   BOOL shouldBeConnected = [self shouldBeConnectedAutomatically];
679   if (shouldBeConnected && !self.client.isConnected) {
680     [self.client connectWithHandler:^(NSError *error) {
681       if (!error) {
682         // It means we connected. Fire connection change notification
683         [self notifyOfDirectChannelConnectionChange];
684       }
685     }];
686   } else if (!shouldBeConnected && self.client.isConnected) {
687     [self.client disconnect];
688     [self notifyOfDirectChannelConnectionChange];
689   }
690 }
691
692 - (void)notifyOfDirectChannelConnectionChange {
693   NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
694   [center postNotificationName:FIRMessagingConnectionStateChangedNotification object:self];
695 }
696
697 #pragma mark - Connect
698
699 - (void)connectWithCompletion:(FIRMessagingConnectCompletion)handler {
700   _FIRMessagingDevAssert([NSThread isMainThread],
701                          @"FIRMessaging connect should be called from main thread only.");
702   _FIRMessagingDevAssert(self.isClientSetup, @"FIRMessaging client not setup.");
703   [self.client connectWithHandler:^(NSError *error) {
704     if (handler) {
705       handler(error);
706     }
707     if (!error) {
708       // It means we connected. Fire connection change notification
709       [self notifyOfDirectChannelConnectionChange];
710     }
711   }];
712
713 }
714
715 - (void)disconnect {
716   _FIRMessagingDevAssert([NSThread isMainThread],
717                          @"FIRMessaging should be called from main thread only.");
718   if ([self.client isConnected]) {
719     [self.client disconnect];
720     [self notifyOfDirectChannelConnectionChange];
721   }
722 }
723
724 #pragma mark - Topics
725
726 + (NSString *)normalizeTopic:(NSString *)topic {
727   if (!topic.length) {
728     return nil;
729   }
730   if (![FIRMessagingPubSub hasTopicsPrefix:topic]) {
731     topic = [FIRMessagingPubSub addPrefixToTopic:topic];
732   }
733   if ([FIRMessagingPubSub isValidTopicWithPrefix:topic]) {
734     return [topic copy];
735   }
736   return nil;
737 }
738
739 - (void)subscribeToTopic:(NSString *)topic {
740   [self subscribeToTopic:topic completion:nil];
741 }
742
743 - (void)subscribeToTopic:(NSString *)topic
744               completion:(nullable FIRMessagingTopicOperationCompletion)completion {
745   if ([FIRMessagingPubSub hasTopicsPrefix:topic]) {
746     FIRMessagingLoggerWarn(kFIRMessagingMessageCodeTopicFormatIsDeprecated,
747                            @"Format '%@' is deprecated. Only '%@' should be used in "
748                            @"subscribeToTopic.",
749                            topic, [FIRMessagingPubSub removePrefixFromTopic:topic]);
750   }
751   if (!self.defaultFcmToken.length) {
752     FIRMessagingLoggerWarn(kFIRMessagingMessageCodeMessaging010,
753                            @"The subscription operation is suspended because you don't have a "
754                            @"token. The operation will resume once you get an FCM token.");
755   }
756   NSString *normalizeTopic = [[self class] normalizeTopic:topic];
757   if (normalizeTopic.length) {
758     [self.pubsub subscribeToTopic:normalizeTopic handler:completion];
759     return;
760   }
761   FIRMessagingLoggerError(kFIRMessagingMessageCodeMessaging009,
762                           @"Cannot parse topic name %@. Will not subscribe.", topic);
763 }
764
765 - (void)unsubscribeFromTopic:(NSString *)topic {
766   [self unsubscribeFromTopic:topic completion:nil];
767 }
768
769 - (void)unsubscribeFromTopic:(NSString *)topic
770                   completion:(nullable FIRMessagingTopicOperationCompletion)completion {
771   if ([FIRMessagingPubSub hasTopicsPrefix:topic]) {
772     FIRMessagingLoggerWarn(kFIRMessagingMessageCodeTopicFormatIsDeprecated,
773                            @"Format '%@' is deprecated. Only '%@' should be used in "
774                            @"unsubscribeFromTopic.",
775                            topic, [FIRMessagingPubSub removePrefixFromTopic:topic]);
776   }
777   if (!self.defaultFcmToken.length) {
778     FIRMessagingLoggerWarn(kFIRMessagingMessageCodeMessaging012,
779                            @"The unsubscription operation is suspended because you don't have a "
780                            @"token. The operation will resume once you get an FCM token.");
781   }
782   NSString *normalizeTopic = [[self class] normalizeTopic:topic];
783   if (normalizeTopic.length) {
784     [self.pubsub unsubscribeFromTopic:normalizeTopic handler:completion];
785     return;
786   }
787   FIRMessagingLoggerError(kFIRMessagingMessageCodeMessaging011,
788                           @"Cannot parse topic name %@. Will not unsubscribe.", topic);
789 }
790
791 #pragma mark - Send
792
793 - (void)sendMessage:(NSDictionary *)message
794                  to:(NSString *)to
795       withMessageID:(NSString *)messageID
796          timeToLive:(int64_t)ttl {
797   _FIRMessagingDevAssert([to length] != 0, @"Invalid receiver id for FIRMessaging-message");
798
799   NSMutableDictionary *fcmMessage = [[self class] createFIRMessagingMessageWithMessage:message
800                                                                            to:to
801                                                                        withID:messageID
802                                                                    timeToLive:ttl
803                                                                         delay:0];
804   FIRMessagingLoggerInfo(kFIRMessagingMessageCodeMessaging013, @"Sending message: %@ with id: %@",
805                          message, messageID);
806   [self.dataMessageManager sendDataMessageStanza:fcmMessage];
807 }
808
809 + (NSMutableDictionary *)createFIRMessagingMessageWithMessage:(NSDictionary *)message
810                                                   to:(NSString *)to
811                                               withID:(NSString *)msgID
812                                           timeToLive:(int64_t)ttl
813                                                delay:(int)delay {
814   NSMutableDictionary *fcmMessage = [NSMutableDictionary dictionary];
815   fcmMessage[kFIRMessagingSendTo] = [to copy];
816   fcmMessage[kFIRMessagingSendMessageID] = msgID ? [msgID copy] : @"";
817   fcmMessage[kFIRMessagingSendTTL] = @(ttl);
818   fcmMessage[kFIRMessagingSendDelay] = @(delay);
819   fcmMessage[KFIRMessagingSendMessageAppData] =
820       [NSMutableDictionary dictionaryWithDictionary:message];
821   return fcmMessage;
822 }
823
824 #pragma mark - IID dependencies
825
826 + (NSString *)FIRMessagingSDKVersion {
827   return FIRMessagingCurrentLibraryVersion();
828 }
829
830 + (NSString *)FIRMessagingSDKCurrentLocale {
831   return [self currentLocale];
832 }
833
834 #pragma mark - FIRMessagingReceiverDelegate
835
836 - (void)receiver:(FIRMessagingReceiver *)receiver
837       receivedRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage {
838   if ([self.delegate respondsToSelector:@selector(messaging:didReceiveMessage:)]) {
839 #pragma clang diagnostic push
840 #pragma clang diagnostic ignored "-Wunguarded-availability"
841     [self.delegate messaging:self didReceiveMessage:remoteMessage];
842 #pragma pop
843   } else {
844     // Delegate methods weren't implemented, so messages are being dropped, log a warning
845     FIRMessagingLoggerWarn(kFIRMessagingMessageCodeRemoteMessageDelegateMethodNotImplemented,
846                            @"FIRMessaging received data-message, but FIRMessagingDelegate's"
847                            @"-messaging:didReceiveMessage: not implemented");
848   }
849 }
850
851 #pragma mark - GULReachabilityDelegate
852
853 - (void)reachability:(GULReachabilityChecker *)reachability
854        statusChanged:(GULReachabilityStatus)status {
855   [self onNetworkStatusChanged];
856 }
857
858 #pragma mark - Network
859
860 - (void)onNetworkStatusChanged {
861   if (![self.client isConnected] && [self isNetworkAvailable]) {
862     if (self.client.shouldStayConnected) {
863       FIRMessagingLoggerDebug(kFIRMessagingMessageCodeMessaging014,
864                               @"Attempting to establish direct channel.");
865       [self.client retryConnectionImmediately:YES];
866     }
867     [self.pubsub scheduleSync:YES];
868   }
869 }
870
871 - (BOOL)isNetworkAvailable {
872   GULReachabilityStatus status = self.reachability.reachabilityStatus;
873   return (status == kGULReachabilityViaCellular || status == kGULReachabilityViaWifi);
874 }
875
876 - (FIRMessagingNetworkStatus)networkType {
877   GULReachabilityStatus status = self.reachability.reachabilityStatus;
878   if (![self isNetworkAvailable]) {
879     return kFIRMessagingReachabilityNotReachable;
880   } else if (status == kGULReachabilityViaCellular) {
881     return kFIRMessagingReachabilityReachableViaWWAN;
882   } else {
883     return kFIRMessagingReachabilityReachableViaWiFi;
884   }
885 }
886
887 #pragma mark - Notifications
888
889 - (void)didReceiveDefaultInstanceIDToken:(NSNotification *)notification {
890   if (notification.object && ![notification.object isKindOfClass:[NSString class]]) {
891     FIRMessagingLoggerDebug(kFIRMessagingMessageCodeMessaging015,
892                             @"Invalid default FCM token type %@",
893                             NSStringFromClass([notification.object class]));
894     return;
895   }
896   NSString *oldToken = self.defaultFcmToken;
897   self.defaultFcmToken = [(NSString *)notification.object copy];
898   if (self.defaultFcmToken && ![self.defaultFcmToken isEqualToString:oldToken]) {
899     [self notifyDelegateOfFCMTokenAvailability];
900   }
901   [self.pubsub scheduleSync:YES];
902   if (self.shouldEstablishDirectChannel) {
903     [self updateAutomaticClientConnection];
904   }
905 }
906
907 - (void)defaultInstanceIDTokenWasRefreshed:(NSNotification *)notification {
908   // Retrieve the Instance ID default token, and if it is non-nil, post it
909 #pragma clang diagnostic push
910 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
911   NSString *token = self.instanceID.token;
912 #pragma clang diagnostic pop
913   // Sometimes Instance ID doesn't yet have a token, so wait until the default
914   // token is fetched, and then notify. This ensures that this token should not
915   // be nil when the developer accesses it.
916   if (token != nil) {
917     NSString *oldToken = self.defaultFcmToken;
918     self.defaultFcmToken = [token copy];
919     if (self.defaultFcmToken && ![self.defaultFcmToken isEqualToString:oldToken]) {
920       [self notifyDelegateOfFCMTokenAvailability];
921     }
922     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
923     [center postNotificationName:FIRMessagingRegistrationTokenRefreshedNotification object:nil];
924   }
925 }
926
927 #pragma mark - Application Support Directory
928
929 + (BOOL)hasApplicationSupportSubDirectory:(NSString *)subDirectoryName {
930   NSString *subDirectoryPath = [self pathForApplicationSupportSubDirectory:subDirectoryName];
931   BOOL isDirectory;
932   if (![[NSFileManager defaultManager] fileExistsAtPath:subDirectoryPath
933                                             isDirectory:&isDirectory]) {
934     return NO;
935   } else if (!isDirectory) {
936     return NO;
937   }
938   return YES;
939 }
940
941 + (NSString *)pathForApplicationSupportSubDirectory:(NSString *)subDirectoryName {
942   NSArray *directoryPaths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory,
943                                                                 NSUserDomainMask, YES);
944   NSString *applicationSupportDirPath = directoryPaths.lastObject;
945   NSArray *components = @[applicationSupportDirPath, subDirectoryName];
946   return [NSString pathWithComponents:components];
947 }
948
949 + (BOOL)createApplicationSupportSubDirectory:(NSString *)subDirectoryName {
950   NSString *subDirectoryPath = [self pathForApplicationSupportSubDirectory:subDirectoryName];
951   BOOL hasSubDirectory;
952
953   if (![[NSFileManager defaultManager] fileExistsAtPath:subDirectoryPath
954                                             isDirectory:&hasSubDirectory]) {
955     NSError *error;
956     [[NSFileManager defaultManager] createDirectoryAtPath:subDirectoryPath
957                               withIntermediateDirectories:YES
958                                                attributes:nil
959                                                     error:&error];
960     if (error) {
961       FIRMessagingLoggerError(kFIRMessagingMessageCodeMessaging017,
962                               @"Cannot create directory %@, error: %@", subDirectoryPath, error);
963       return NO;
964     }
965   } else {
966     if (!hasSubDirectory) {
967       FIRMessagingLoggerError(kFIRMessagingMessageCodeMessaging018,
968                               @"Found file instead of directory at %@", subDirectoryPath);
969       return NO;
970     }
971   }
972   return YES;
973 }
974
975 #pragma mark - Locales
976
977 + (NSString *)currentLocale {
978   NSArray *locales = [self firebaseLocales];
979   NSArray *preferredLocalizations =
980     [NSBundle preferredLocalizationsFromArray:locales
981                                forPreferences:[NSLocale preferredLanguages]];
982   NSString *legalDocsLanguage = [preferredLocalizations firstObject];
983   // Use en as the default language
984   return legalDocsLanguage ? legalDocsLanguage : @"en";
985 }
986
987 + (NSArray *)firebaseLocales {
988   NSMutableArray *locales = [NSMutableArray array];
989   NSDictionary *localesMap = [self firebaselocalesMap];
990   for (NSString *key in localesMap) {
991     [locales addObjectsFromArray:localesMap[key]];
992   }
993   return locales;
994 }
995
996 + (NSDictionary *)firebaselocalesMap {
997   return @{
998     // Albanian
999     @"sq" : @[ @"sq_AL" ],
1000     // Belarusian
1001     @"be" : @[ @"be_BY" ],
1002     // Bulgarian
1003     @"bg" : @[ @"bg_BG" ],
1004     // Catalan
1005     @"ca" : @[ @"ca", @"ca_ES" ],
1006     // Croatian
1007     @"hr" : @[ @"hr", @"hr_HR" ],
1008     // Czech
1009     @"cs" : @[ @"cs", @"cs_CZ" ],
1010     // Danish
1011     @"da" : @[ @"da", @"da_DK" ],
1012     // Estonian
1013     @"et" : @[ @"et_EE" ],
1014     // Finnish
1015     @"fi" : @[ @"fi", @"fi_FI" ],
1016     // Hebrew
1017     @"he" : @[ @"he", @"iw_IL" ],
1018     // Hindi
1019     @"hi" : @[ @"hi_IN" ],
1020     // Hungarian
1021     @"hu" : @[ @"hu", @"hu_HU" ],
1022     // Icelandic
1023     @"is" : @[ @"is_IS" ],
1024     // Indonesian
1025     @"id" : @[ @"id", @"in_ID", @"id_ID" ],
1026     // Irish
1027     @"ga" : @[ @"ga_IE" ],
1028     // Korean
1029     @"ko" : @[ @"ko", @"ko_KR", @"ko-KR" ],
1030     // Latvian
1031     @"lv" : @[ @"lv_LV" ],
1032     // Lithuanian
1033     @"lt" : @[ @"lt_LT" ],
1034     // Macedonian
1035     @"mk" : @[ @"mk_MK" ],
1036     // Malay
1037     @"ms" : @[ @"ms_MY" ],
1038     // Maltese
1039     @"ms" : @[ @"mt_MT" ],
1040     // Polish
1041     @"pl" : @[ @"pl", @"pl_PL", @"pl-PL" ],
1042     // Romanian
1043     @"ro" : @[ @"ro", @"ro_RO" ],
1044     // Russian
1045     @"ru" : @[ @"ru_RU", @"ru", @"ru_BY", @"ru_KZ", @"ru-RU" ],
1046     // Slovak
1047     @"sk" : @[ @"sk", @"sk_SK" ],
1048     // Slovenian
1049     @"sl" : @[ @"sl_SI" ],
1050     // Swedish
1051     @"sv" : @[ @"sv", @"sv_SE", @"sv-SE" ],
1052     // Turkish
1053     @"tr" : @[ @"tr", @"tr-TR", @"tr_TR" ],
1054     // Ukrainian
1055     @"uk" : @[ @"uk", @"uk_UA" ],
1056     // Vietnamese
1057     @"vi" : @[ @"vi", @"vi_VN" ],
1058     // The following are groups of locales or locales that sub-divide a
1059     // language).
1060     // Arabic
1061     @"ar" : @[
1062       @"ar",
1063       @"ar_DZ",
1064       @"ar_BH",
1065       @"ar_EG",
1066       @"ar_IQ",
1067       @"ar_JO",
1068       @"ar_KW",
1069       @"ar_LB",
1070       @"ar_LY",
1071       @"ar_MA",
1072       @"ar_OM",
1073       @"ar_QA",
1074       @"ar_SA",
1075       @"ar_SD",
1076       @"ar_SY",
1077       @"ar_TN",
1078       @"ar_AE",
1079       @"ar_YE",
1080       @"ar_GB",
1081       @"ar-IQ",
1082       @"ar_US"
1083     ],
1084     // Simplified Chinese
1085     @"zh_Hans" : @[ @"zh_CN", @"zh_SG", @"zh-Hans" ],
1086     // Traditional Chinese
1087     @"zh_Hant" : @[ @"zh_HK", @"zh_TW", @"zh-Hant", @"zh-HK", @"zh-TW" ],
1088     // Dutch
1089     @"nl" : @[ @"nl", @"nl_BE", @"nl_NL", @"nl-NL" ],
1090     // English
1091     @"en" : @[
1092       @"en",
1093       @"en_AU",
1094       @"en_CA",
1095       @"en_IN",
1096       @"en_IE",
1097       @"en_MT",
1098       @"en_NZ",
1099       @"en_PH",
1100       @"en_SG",
1101       @"en_ZA",
1102       @"en_GB",
1103       @"en_US",
1104       @"en_AE",
1105       @"en-AE",
1106       @"en_AS",
1107       @"en-AU",
1108       @"en_BD",
1109       @"en-CA",
1110       @"en_EG",
1111       @"en_ES",
1112       @"en_GB",
1113       @"en-GB",
1114       @"en_HK",
1115       @"en_ID",
1116       @"en-IN",
1117       @"en_NG",
1118       @"en-PH",
1119       @"en_PK",
1120       @"en-SG",
1121       @"en-US"
1122     ],
1123     // French
1124
1125     @"fr" : @[
1126       @"fr",
1127       @"fr_BE",
1128       @"fr_CA",
1129       @"fr_FR",
1130       @"fr_LU",
1131       @"fr_CH",
1132       @"fr-CA",
1133       @"fr-FR",
1134       @"fr_MA"
1135     ],
1136     // German
1137     @"de" : @[ @"de", @"de_AT", @"de_DE", @"de_LU", @"de_CH", @"de-DE" ],
1138     // Greek
1139     @"el" : @[ @"el", @"el_CY", @"el_GR" ],
1140     // Italian
1141     @"it" : @[ @"it", @"it_IT", @"it_CH", @"it-IT" ],
1142     // Japanese
1143     @"ja" : @[ @"ja", @"ja_JP", @"ja_JP_JP", @"ja-JP" ],
1144     // Norwegian
1145     @"no" : @[ @"nb", @"no_NO", @"no_NO_NY", @"nb_NO" ],
1146     // Brazilian Portuguese
1147     @"pt_BR" : @[ @"pt_BR", @"pt-BR" ],
1148     // European Portuguese
1149     @"pt_PT" : @[ @"pt", @"pt_PT", @"pt-PT" ],
1150     // Serbian
1151     @"sr" : @[
1152       @"sr_BA",
1153       @"sr_ME",
1154       @"sr_RS",
1155       @"sr_Latn_BA",
1156       @"sr_Latn_ME",
1157       @"sr_Latn_RS"
1158     ],
1159     // European Spanish
1160     @"es_ES" : @[ @"es", @"es_ES", @"es-ES" ],
1161     // Mexican Spanish
1162     @"es_MX" : @[ @"es-MX", @"es_MX", @"es_US", @"es-US" ],
1163     // Latin American Spanish
1164     @"es_419" : @[
1165       @"es_AR",
1166       @"es_BO",
1167       @"es_CL",
1168       @"es_CO",
1169       @"es_CR",
1170       @"es_DO",
1171       @"es_EC",
1172       @"es_SV",
1173       @"es_GT",
1174       @"es_HN",
1175       @"es_NI",
1176       @"es_PA",
1177       @"es_PY",
1178       @"es_PE",
1179       @"es_PR",
1180       @"es_UY",
1181       @"es_VE",
1182       @"es-AR",
1183       @"es-CL",
1184       @"es-CO"
1185     ],
1186     // Thai
1187     @"th" : @[ @"th", @"th_TH", @"th_TH_TH" ],
1188   };
1189 }
1190
1191 @end