1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
|
/*
* Copyright 2019 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "FIRInstanceIDTokenFetchOperation.h"
#import "FIRInstanceIDCheckinPreferences.h"
#import "FIRInstanceIDConstants.h"
#import "FIRInstanceIDDefines.h"
#import "FIRInstanceIDLogger.h"
#import "FIRInstanceIDTokenOperation+Private.h"
#import "FIRInstanceIDUtilities.h"
#import "NSError+FIRInstanceID.h"
#import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
// We can have a static int since this error should theoretically only
// happen once (for the first time). If it repeats there is something
// else that is wrong.
static int phoneRegistrationErrorRetryCount = 0;
static const int kMaxPhoneRegistrationErrorRetryCount = 10;
NSString *const kFIRInstanceIDFirebaseUserAgentKey = @"X-firebase-client";
NSString *const kFIRInstanceIDFirebaseHeartbeatKey = @"X-firebase-client-log-type";
NSString *const kFIRInstanceIDHeartbeatTag = @"fire-iid";
@implementation FIRInstanceIDTokenFetchOperation
- (instancetype)initWithAuthorizedEntity:(NSString *)authorizedEntity
scope:(NSString *)scope
options:(nullable NSDictionary<NSString *, NSString *> *)options
checkinPreferences:(FIRInstanceIDCheckinPreferences *)checkinPreferences
instanceID:(NSString *)instanceID {
self = [super initWithAction:FIRInstanceIDTokenActionFetch
forAuthorizedEntity:authorizedEntity
scope:scope
options:options
checkinPreferences:checkinPreferences
instanceID:instanceID];
if (self) {
}
return self;
}
- (void)performTokenOperation {
NSMutableURLRequest *request = [self tokenRequest];
NSString *checkinVersionInfo = self.checkinPreferences.versionInfo;
[request setValue:checkinVersionInfo forHTTPHeaderField:@"info"];
[request setValue:[FIRApp firebaseUserAgent]
forHTTPHeaderField:kFIRInstanceIDFirebaseUserAgentKey];
[request setValue:@([FIRHeartbeatInfo heartbeatCodeForTag:kFIRInstanceIDHeartbeatTag]).stringValue
forHTTPHeaderField:kFIRInstanceIDFirebaseHeartbeatKey];
// Build form-encoded body
NSString *deviceAuthID = self.checkinPreferences.deviceID;
NSMutableArray<NSURLQueryItem *> *queryItems =
[[self class] standardQueryItemsWithDeviceID:deviceAuthID scope:self.scope];
[queryItems addObject:[NSURLQueryItem queryItemWithName:@"sender" value:self.authorizedEntity]];
[queryItems addObject:[NSURLQueryItem queryItemWithName:@"X-subtype"
value:self.authorizedEntity]];
if (self.instanceID.length > 0) {
[queryItems addObject:[NSURLQueryItem queryItemWithName:kFIRInstanceIDParamInstanceID
value:self.instanceID]];
}
// Create query items from passed-in options
id apnsTokenData = self.options[kFIRInstanceIDTokenOptionsAPNSKey];
id apnsSandboxValue = self.options[kFIRInstanceIDTokenOptionsAPNSIsSandboxKey];
if ([apnsTokenData isKindOfClass:[NSData class]] &&
[apnsSandboxValue isKindOfClass:[NSNumber class]]) {
NSString *APNSString = FIRInstanceIDAPNSTupleStringForTokenAndServerType(
apnsTokenData, ((NSNumber *)apnsSandboxValue).boolValue);
// The name of the query item happens to be the same as the dictionary key
NSURLQueryItem *item = [NSURLQueryItem queryItemWithName:kFIRInstanceIDTokenOptionsAPNSKey
value:APNSString];
[queryItems addObject:item];
}
id firebaseAppID = self.options[kFIRInstanceIDTokenOptionsFirebaseAppIDKey];
if ([firebaseAppID isKindOfClass:[NSString class]]) {
// The name of the query item happens to be the same as the dictionary key
NSURLQueryItem *item =
[NSURLQueryItem queryItemWithName:kFIRInstanceIDTokenOptionsFirebaseAppIDKey
value:(NSString *)firebaseAppID];
[queryItems addObject:item];
}
NSURLComponents *components = [[NSURLComponents alloc] init];
components.queryItems = queryItems;
NSString *content = components.query;
request.HTTPBody = [content dataUsingEncoding:NSUTF8StringEncoding];
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenFetchOperationFetchRequest,
@"Register request to %@ content: %@", FIRInstanceIDRegisterServer(),
content);
FIRInstanceID_WEAKIFY(self);
void (^requestHandler)(NSData *, NSURLResponse *, NSError *) =
^(NSData *data, NSURLResponse *response, NSError *error) {
FIRInstanceID_STRONGIFY(self);
[self handleResponseWithData:data response:response error:error];
};
// Test block
if (self.testBlock) {
self.testBlock(request, requestHandler);
return;
}
NSURLSession *session = [FIRInstanceIDTokenOperation sharedURLSession];
self.dataTask = [session dataTaskWithRequest:request completionHandler:requestHandler];
[self.dataTask resume];
}
#pragma mark - Request Handling
- (void)handleResponseWithData:(NSData *)data
response:(NSURLResponse *)response
error:(NSError *)error {
if (error) {
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenFetchOperationRequestError,
@"Token fetch HTTP error. Error Code: %ld", (long)error.code);
[self finishWithResult:FIRInstanceIDTokenOperationError token:nil error:error];
return;
}
NSString *dataResponse = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (dataResponse.length == 0) {
NSError *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown];
[self finishWithResult:FIRInstanceIDTokenOperationError token:nil error:error];
return;
}
NSDictionary *parsedResponse = [self parseFetchTokenResponse:dataResponse];
if ([parsedResponse[@"token"] length]) {
[self finishWithResult:FIRInstanceIDTokenOperationSucceeded
token:parsedResponse[@"token"]
error:nil];
return;
}
NSString *errorValue = parsedResponse[@"Error"];
NSError *responseError;
if (errorValue.length) {
NSArray *errorComponents = [errorValue componentsSeparatedByString:@":"];
// HACK (Kansas replication delay), PHONE_REGISTRATION_ERROR on App
// uninstall and reinstall.
if ([errorComponents containsObject:@"PHONE_REGISTRATION_ERROR"]) {
// Encountered issue http://b/27043795
// Retry register until successful or another error encountered or a
// certain number of tries are over.
if (phoneRegistrationErrorRetryCount < kMaxPhoneRegistrationErrorRetryCount) {
const int nextRetryInterval = 1 << phoneRegistrationErrorRetryCount;
FIRInstanceID_WEAKIFY(self);
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(nextRetryInterval * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
FIRInstanceID_STRONGIFY(self);
phoneRegistrationErrorRetryCount++;
[self performTokenOperation];
});
return;
}
} else if ([errorComponents containsObject:kFIRInstanceID_CMD_RST]) {
// Server detected the identity we use is no longer valid.
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotificationName:kFIRInstanceIDIdentityInvalidatedNotification object:nil];
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeInternal001,
@"Identity is invalid. Server request identity reset.");
responseError =
[NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeInvalidIdentity];
}
}
if (!responseError) {
FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeTokenFetchOperationBadResponse,
@"Invalid fetch response, expected 'token' or 'Error' key");
responseError = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeUnknown];
}
[self finishWithResult:FIRInstanceIDTokenOperationError token:nil error:responseError];
}
// expect a response e.g. "token=<reg id>\nGOOG.ttl=123"
- (NSDictionary *)parseFetchTokenResponse:(NSString *)response {
NSArray *lines = [response componentsSeparatedByString:@"\n"];
NSMutableDictionary *parsedResponse = [NSMutableDictionary dictionary];
for (NSString *line in lines) {
NSArray *keyAndValue = [line componentsSeparatedByString:@"="];
if ([keyAndValue count] > 1) {
parsedResponse[keyAndValue[0]] = keyAndValue[1];
}
}
return parsedResponse;
}
@end
|