Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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 "FirebaseMessaging/Sources/FIRMessagingPubSub.h"
18
 
19
#import <GoogleUtilities/GULSecureCoding.h>
20
#import <GoogleUtilities/GULUserDefaults.h>
21
#import "FirebaseMessaging/Sources/FIRMessagingDefines.h"
22
#import "FirebaseMessaging/Sources/FIRMessagingLogger.h"
23
#import "FirebaseMessaging/Sources/FIRMessagingPendingTopicsList.h"
24
#import "FirebaseMessaging/Sources/FIRMessagingTopicOperation.h"
25
#import "FirebaseMessaging/Sources/FIRMessagingTopicsCommon.h"
26
#import "FirebaseMessaging/Sources/FIRMessagingUtilities.h"
27
#import "FirebaseMessaging/Sources/FIRMessaging_Private.h"
28
#import "FirebaseMessaging/Sources/NSDictionary+FIRMessaging.h"
29
#import "FirebaseMessaging/Sources/NSError+FIRMessaging.h"
30
#import "FirebaseMessaging/Sources/Public/FirebaseMessaging/FIRMessaging.h"
31
#import "FirebaseMessaging/Sources/Token/FIRMessagingTokenManager.h"
32
 
33
static NSString *const kPendingSubscriptionsListKey =
34
    @"com.firebase.messaging.pending-subscriptions";
35
 
36
@interface FIRMessagingPubSub () <FIRMessagingPendingTopicsListDelegate>
37
 
38
@property(nonatomic, readwrite, strong) FIRMessagingPendingTopicsList *pendingTopicUpdates;
39
@property(nonatomic, readonly, strong) NSOperationQueue *topicOperations;
40
// Common errors, instantiated, to avoid generating multiple copies
41
@property(nonatomic, readwrite, strong) NSError *operationInProgressError;
42
@property(nonatomic, readwrite, strong) FIRMessagingTokenManager *tokenManager;
43
 
44
@end
45
 
46
@implementation FIRMessagingPubSub
47
 
48
- (instancetype)initWithTokenManager:(FIRMessagingTokenManager *)tokenManager {
49
  self = [super init];
50
  if (self) {
51
    _topicOperations = [[NSOperationQueue alloc] init];
52
    // Do 10 topic operations at a time; it's enough to keep the TCP connection to the host alive,
53
    // saving hundreds of milliseconds on each request (compared to a serial queue).
54
    _topicOperations.maxConcurrentOperationCount = 10;
55
    _tokenManager = tokenManager;
56
    [self restorePendingTopicsList];
57
  }
58
  return self;
59
}
60
 
61
- (void)subscribeWithToken:(NSString *)token
62
                     topic:(NSString *)topic
63
                   options:(NSDictionary *)options
64
                   handler:(FIRMessagingTopicOperationCompletion)handler {
65
  token = [token copy];
66
  topic = [topic copy];
67
 
68
  if (![options count]) {
69
    options = @{};
70
  }
71
 
72
  if (![[self class] isValidTopicWithPrefix:topic]) {
73
    NSString *failureReason =
74
        [NSString stringWithFormat:@"Invalid subscription topic :'%@'", topic];
75
    FIRMessagingLoggerError(kFIRMessagingMessageCodePubSub000, @"%@", failureReason);
76
    handler([NSError messagingErrorWithCode:kFIRMessagingErrorCodeInvalidTopicName
77
                              failureReason:failureReason]);
78
    return;
79
  }
80
 
81
  if (![self verifyPubSubOptions:options]) {
82
    // we do not want to quit even if options have some invalid values.
83
    FIRMessagingLoggerError(kFIRMessagingMessageCodePubSub001,
84
                            @"Invalid options passed to FIRMessagingPubSub with non-string keys or "
85
                             "values.");
86
  }
87
  // copy the dictionary would trim non-string keys or values if any.
88
  options = [options fcm_trimNonStringValues];
89
 
90
  [self updateSubscriptionWithToken:token
91
                              topic:topic
92
                            options:options
93
                       shouldDelete:NO
94
                            handler:handler];
95
}
96
 
97
- (void)dealloc {
98
  [self.topicOperations cancelAllOperations];
99
}
100
 
101
#pragma mark - FIRMessaging subscribe
102
 
103
- (void)updateSubscriptionWithToken:(NSString *)token
104
                              topic:(NSString *)topic
105
                            options:(NSDictionary *)options
106
                       shouldDelete:(BOOL)shouldDelete
107
                            handler:(FIRMessagingTopicOperationCompletion)handler {
108
  if ([_tokenManager hasValidCheckinInfo]) {
109
    FIRMessagingTopicAction action =
110
        shouldDelete ? FIRMessagingTopicActionUnsubscribe : FIRMessagingTopicActionSubscribe;
111
    FIRMessagingTopicOperation *operation = [[FIRMessagingTopicOperation alloc]
112
        initWithTopic:topic
113
               action:action
114
         tokenManager:_tokenManager
115
              options:options
116
           completion:^(NSError *_Nullable error) {
117
             if (error) {
118
               FIRMessagingLoggerError(kFIRMessagingMessageCodeClient001,
119
                                       @"Failed to subscribe to topic %@", error);
120
             } else {
121
               if (shouldDelete) {
122
                 FIRMessagingLoggerInfo(kFIRMessagingMessageCodeClient002,
123
                                        @"Successfully unsubscribed from topic %@", topic);
124
               } else {
125
                 FIRMessagingLoggerInfo(kFIRMessagingMessageCodeClient003,
126
                                        @"Successfully subscribed to topic %@", topic);
127
               }
128
             }
129
             if (handler) {
130
               handler(error);
131
             }
132
           }];
133
    [self.topicOperations addOperation:operation];
134
  } else {
135
    NSString *failureReason = @"Device ID and checkin info is not found. Will not proceed with "
136
                              @"subscription/unsubscription.";
137
    FIRMessagingLoggerDebug(kFIRMessagingMessageCodeRegistrar000, @"%@", failureReason);
138
    NSError *error = [NSError messagingErrorWithCode:kFIRMessagingErrorCodeMissingDeviceID
139
                                       failureReason:failureReason];
140
    handler(error);
141
  }
142
}
143
 
144
- (void)unsubscribeWithToken:(NSString *)token
145
                       topic:(NSString *)topic
146
                     options:(NSDictionary *)options
147
                     handler:(FIRMessagingTopicOperationCompletion)handler {
148
  token = [token copy];
149
  topic = [topic copy];
150
  if (![options count]) {
151
    options = @{};
152
  }
153
 
154
  if (![[self class] isValidTopicWithPrefix:topic]) {
155
    NSString *failureReason =
156
        [NSString stringWithFormat:@"Invalid topic name : '%@' for unsubscription.", topic];
157
    FIRMessagingLoggerError(kFIRMessagingMessageCodePubSub002, @"%@", failureReason);
158
    handler([NSError messagingErrorWithCode:kFIRMessagingErrorCodeInvalidTopicName
159
                              failureReason:failureReason]);
160
    return;
161
  }
162
  if (![self verifyPubSubOptions:options]) {
163
    // we do not want to quit even if options have some invalid values.
164
    FIRMessagingLoggerError(
165
        kFIRMessagingMessageCodePubSub003,
166
        @"Invalid options passed to FIRMessagingPubSub with non-string keys or values.");
167
  }
168
  // copy the dictionary would trim non-string keys or values if any.
169
  options = [options fcm_trimNonStringValues];
170
 
171
  [self updateSubscriptionWithToken:token
172
                              topic:topic
173
                            options:options
174
                       shouldDelete:YES
175
                            handler:^void(NSError *error) {
176
                              handler(error);
177
                            }];
178
}
179
 
180
- (void)subscribeToTopic:(NSString *)topic
181
                 handler:(nullable FIRMessagingTopicOperationCompletion)handler {
182
  [self.pendingTopicUpdates addOperationForTopic:topic
183
                                      withAction:FIRMessagingTopicActionSubscribe
184
                                      completion:handler];
185
}
186
 
187
- (void)unsubscribeFromTopic:(NSString *)topic
188
                     handler:(nullable FIRMessagingTopicOperationCompletion)handler {
189
  [self.pendingTopicUpdates addOperationForTopic:topic
190
                                      withAction:FIRMessagingTopicActionUnsubscribe
191
                                      completion:handler];
192
}
193
 
194
- (void)scheduleSync:(BOOL)immediately {
195
  NSString *fcmToken = _tokenManager.defaultFCMToken;
196
  if (fcmToken.length) {
197
    [self.pendingTopicUpdates resumeOperationsIfNeeded];
198
  }
199
}
200
 
201
#pragma mark - FIRMessagingPendingTopicsListDelegate
202
 
203
- (void)pendingTopicsList:(FIRMessagingPendingTopicsList *)list
204
    requestedUpdateForTopic:(NSString *)topic
205
                     action:(FIRMessagingTopicAction)action
206
                 completion:(FIRMessagingTopicOperationCompletion)completion {
207
  NSString *fcmToken = _tokenManager.defaultFCMToken;
208
  if (action == FIRMessagingTopicActionSubscribe) {
209
    [self subscribeWithToken:fcmToken topic:topic options:nil handler:completion];
210
  } else {
211
    [self unsubscribeWithToken:fcmToken topic:topic options:nil handler:completion];
212
  }
213
}
214
 
215
- (void)pendingTopicsListDidUpdate:(FIRMessagingPendingTopicsList *)list {
216
  [self archivePendingTopicsList:list];
217
}
218
 
219
- (BOOL)pendingTopicsListCanRequestTopicUpdates:(FIRMessagingPendingTopicsList *)list {
220
  NSString *fcmToken = _tokenManager.defaultFCMToken;
221
  return (fcmToken.length > 0);
222
}
223
 
224
#pragma mark - Storing Pending Topics
225
 
226
- (void)archivePendingTopicsList:(FIRMessagingPendingTopicsList *)topicsList {
227
  GULUserDefaults *defaults = [GULUserDefaults standardUserDefaults];
228
  NSError *error;
229
  NSData *pendingData = [GULSecureCoding archivedDataWithRootObject:topicsList error:&error];
230
  if (error) {
231
    FIRMessagingLoggerError(kFIRMessagingMessageCodePubSubArchiveError,
232
                            @"Failed to archive topic list data %@", error);
233
    return;
234
  }
235
  [defaults setObject:pendingData forKey:kPendingSubscriptionsListKey];
236
  [defaults synchronize];
237
}
238
 
239
- (void)restorePendingTopicsList {
240
  GULUserDefaults *defaults = [GULUserDefaults standardUserDefaults];
241
  NSData *pendingData = [defaults objectForKey:kPendingSubscriptionsListKey];
242
  FIRMessagingPendingTopicsList *subscriptions;
243
  if (pendingData) {
244
    NSError *error;
245
    subscriptions = [GULSecureCoding
246
        unarchivedObjectOfClasses:[NSSet setWithObjects:FIRMessagingPendingTopicsList.class, nil]
247
                         fromData:pendingData
248
                            error:&error];
249
    if (error) {
250
      FIRMessagingLoggerError(kFIRMessagingMessageCodePubSubUnarchiveError,
251
                              @"Failed to unarchive topic list data %@", error);
252
    }
253
  }
254
  if (subscriptions) {
255
    self.pendingTopicUpdates = subscriptions;
256
  } else {
257
    self.pendingTopicUpdates = [[FIRMessagingPendingTopicsList alloc] init];
258
  }
259
  self.pendingTopicUpdates.delegate = self;
260
}
261
 
262
#pragma mark - Private Helpers
263
 
264
- (BOOL)verifyPubSubOptions:(NSDictionary *)options {
265
  return ![options fcm_hasNonStringKeysOrValues];
266
}
267
 
268
#pragma mark - Topic Name Helpers
269
 
270
static NSString *const kTopicsPrefix = @"/topics/";
271
static NSString *const kTopicRegexPattern = @"/topics/([a-zA-Z0-9-_.~%]+)";
272
 
273
+ (NSString *)addPrefixToTopic:(NSString *)topic {
274
  if (![self hasTopicsPrefix:topic]) {
275
    return [NSString stringWithFormat:@"%@%@", kTopicsPrefix, topic];
276
  } else {
277
    return [topic copy];
278
  }
279
}
280
 
281
+ (NSString *)removePrefixFromTopic:(NSString *)topic {
282
  if ([self hasTopicsPrefix:topic]) {
283
    return [topic substringFromIndex:kTopicsPrefix.length];
284
  } else {
285
    return [topic copy];
286
  }
287
}
288
 
289
+ (BOOL)hasTopicsPrefix:(NSString *)topic {
290
  return [topic hasPrefix:kTopicsPrefix];
291
}
292
 
293
/**
294
 *  Returns a regular expression for matching a topic sender.
295
 *
296
 *  @return The topic matching regular expression
297
 */
298
+ (NSRegularExpression *)topicRegex {
299
  // Since this is a static regex pattern, we only only need to declare it once.
300
  static NSRegularExpression *topicRegex;
301
  static dispatch_once_t onceToken;
302
  dispatch_once(&onceToken, ^{
303
    NSError *error;
304
    topicRegex =
305
        [NSRegularExpression regularExpressionWithPattern:kTopicRegexPattern
306
                                                  options:NSRegularExpressionAnchorsMatchLines
307
                                                    error:&error];
308
  });
309
  return topicRegex;
310
}
311
 
312
/**
313
 *  Gets the class describing occurences of topic names and sender IDs in the sender.
314
 *
315
 *  @param topic The topic expression used to generate a pubsub topic
316
 *
317
 *  @return Representation of captured subexpressions in topic regular expression
318
 */
319
+ (BOOL)isValidTopicWithPrefix:(NSString *)topic {
320
  NSRange topicRange = NSMakeRange(0, topic.length);
321
  NSRange regexMatchRange = [[self topicRegex] rangeOfFirstMatchInString:topic
322
                                                                 options:NSMatchingAnchored
323
                                                                   range:topicRange];
324
  return NSEqualRanges(topicRange, regexMatchRange);
325
}
326
 
327
@end