| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247 |
- // Copyright 2017 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 "FIRFunctions.h"
- #import "FIRFunctions+Internal.h"
- #import "FIRError.h"
- #import "FIRHTTPSCallable+Internal.h"
- #import "FIRHTTPSCallable.h"
- #import "FUNContext.h"
- #import "FUNError.h"
- #import "FUNSerializer.h"
- #import "FUNUsageValidation.h"
- #import "FIRApp.h"
- #import "FIRAppInternal.h"
- #import "FIROptions.h"
- #import "GTMSessionFetcherService.h"
- NS_ASSUME_NONNULL_BEGIN
- NSString *const kFUNInstanceIDTokenHeader = @"Firebase-Instance-ID-Token";
- @interface FIRFunctions () {
- // The network client to use for http requests.
- GTMSessionFetcherService *_fetcherService;
- // The projectID to use for all function references.
- FIRApp *_app;
- // The region to use for all function references.
- NSString *_region;
- // A serializer to encode/decode data and return values.
- FUNSerializer *_serializer;
- // A factory for getting the metadata to include with function calls.
- FUNContextProvider *_contextProvider;
- // For testing only. If this is set, functions will be called against localhost instead of
- // Firebase.
- BOOL _useLocalhost;
- }
- /**
- * Initialize the Cloud Functions client with the given app and region.
- * @param app The app for the Firebase project.
- * @param region The region for the http trigger, such as "us-central1".
- */
- - (id)initWithApp:(FIRApp *)app region:(NSString *)region NS_DESIGNATED_INITIALIZER;
- @end
- @implementation FIRFunctions
- + (instancetype)functions {
- return [[self alloc] initWithApp:[FIRApp defaultApp] region:@"us-central1"];
- }
- + (instancetype)functionsForApp:(FIRApp *)app {
- return [[self alloc] initWithApp:app region:@"us-central1"];
- }
- + (instancetype)functionsForRegion:(NSString *)region {
- return [[self alloc] initWithApp:[FIRApp defaultApp] region:region];
- }
- + (instancetype)functionsForApp:(FIRApp *)app region:(NSString *)region {
- return [[self alloc] initWithApp:app region:region];
- }
- - (instancetype)initWithApp:(FIRApp *)app region:(NSString *)region {
- self = [super init];
- if (self) {
- if (!region) {
- FUNThrowInvalidArgument(@"FIRFunctions region cannot be nil.");
- }
- _fetcherService = [[GTMSessionFetcherService alloc] init];
- _app = app;
- _region = [region copy];
- _serializer = [[FUNSerializer alloc] init];
- _contextProvider = [[FUNContextProvider alloc] initWithApp:app];
- _useLocalhost = NO;
- }
- return self;
- }
- - (void)useLocalhost {
- _useLocalhost = YES;
- }
- - (NSString *)URLWithName:(NSString *)name {
- if (!name) {
- FUNThrowInvalidArgument(@"FIRFunctions function name cannot be nil.");
- }
- NSString *projectID = _app.options.projectID;
- if (!projectID) {
- FUNThrowInvalidArgument(@"FIRFunctions app projectID cannot be nil.");
- }
- if (_useLocalhost) {
- return [NSString stringWithFormat:@"http://localhost:5005/%@/%@/%@", projectID, _region, name];
- }
- return
- [NSString stringWithFormat:@"https://%@-%@.cloudfunctions.net/%@", _region, projectID, name];
- }
- - (void)callFunction:(NSString *)name
- withObject:(nullable id)data
- completion:(void (^)(FIRHTTPSCallableResult *_Nullable result,
- NSError *_Nullable error))completion {
- [_contextProvider getContext:^(FUNContext *_Nullable context, NSError *_Nullable error) {
- if (error) {
- if (completion) {
- completion(nil, error);
- }
- return;
- }
- return [self callFunction:name withObject:data context:context completion:completion];
- }];
- }
- - (void)callFunction:(NSString *)name
- withObject:(nullable id)data
- context:(FUNContext *)context
- completion:(void (^)(FIRHTTPSCallableResult *_Nullable result,
- NSError *_Nullable error))completion {
- GTMSessionFetcher *fetcher = [_fetcherService fetcherWithURLString:[self URLWithName:name]];
- NSMutableDictionary *body = [NSMutableDictionary dictionary];
- // Encode the data in the body.
- if (!data) {
- data = [NSNull null];
- }
- id encoded = [_serializer encode:data];
- if (!encoded) {
- FUNThrowInvalidArgument(@"FIRFunctions data encoded as nil. This should not happen.");
- }
- body[@"data"] = encoded;
- NSError *error = nil;
- NSData *payload = [NSJSONSerialization dataWithJSONObject:body options:0 error:&error];
- if (error) {
- if (completion) {
- dispatch_async(dispatch_get_main_queue(), ^{
- completion(nil, error);
- });
- }
- return;
- }
- fetcher.bodyData = payload;
- // Set the headers.
- [fetcher setRequestValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
- if (context.authToken) {
- NSString *value = [NSString stringWithFormat:@"Bearer %@", context.authToken];
- [fetcher setRequestValue:value forHTTPHeaderField:@"Authorization"];
- }
- if (context.instanceIDToken) {
- [fetcher setRequestValue:context.instanceIDToken forHTTPHeaderField:kFUNInstanceIDTokenHeader];
- }
- // Override normal security rules if this is a local test.
- if (_useLocalhost) {
- fetcher.allowLocalhostRequest = YES;
- fetcher.allowedInsecureSchemes = @[ @"http" ];
- }
- FUNSerializer *serializer = _serializer;
- [fetcher beginFetchWithCompletionHandler:^(NSData *_Nullable data, NSError *_Nullable error) {
- // If there was an HTTP error, convert it to our own error domain.
- if (error) {
- if ([error.domain isEqualToString:kGTMSessionFetcherStatusDomain]) {
- error = FUNErrorForResponse(error.code, data, serializer);
- }
- } else {
- // If there wasn't an HTTP error, see if there was an error in the body.
- error = FUNErrorForResponse(200, data, serializer);
- }
- // If there was an error, report it to the user and stop.
- if (error) {
- if (completion) {
- completion(nil, error);
- }
- return;
- }
- id responseJSON = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
- if (error) {
- if (completion) {
- completion(nil, error);
- }
- return;
- }
- if (![responseJSON isKindOfClass:[NSDictionary class]]) {
- NSDictionary *userInfo = @{NSLocalizedDescriptionKey : @"Response was not a dictionary."};
- error = [NSError errorWithDomain:FIRFunctionsErrorDomain
- code:FIRFunctionsErrorCodeInternal
- userInfo:userInfo];
- if (completion) {
- completion(nil, error);
- }
- return;
- }
- id dataJSON = responseJSON[@"data"];
- // TODO(klimt): Allow "result" instead of "data" for now, for backwards compatibility.
- if (!dataJSON) {
- dataJSON = responseJSON[@"result"];
- }
- if (!dataJSON) {
- NSDictionary *userInfo =
- @{NSLocalizedDescriptionKey : @"Response did not include data field."};
- error = [NSError errorWithDomain:FIRFunctionsErrorDomain
- code:FIRFunctionsErrorCodeInternal
- userInfo:userInfo];
- if (completion) {
- completion(nil, error);
- }
- return;
- }
- id resultData = [serializer decode:dataJSON error:&error];
- if (error) {
- if (completion) {
- completion(nil, error);
- }
- return;
- }
- id result = [[FIRHTTPSCallableResult alloc] initWithData:resultData];
- if (completion) {
- // If there's no result field, this will return nil, which is fine.
- completion(result, nil);
- }
- }];
- }
- - (FIRHTTPSCallable *)HTTPSCallableWithName:(NSString *)name {
- return [[FIRHTTPSCallable alloc] initWithFunctions:self name:name];
- }
- @end
- NS_ASSUME_NONNULL_END
|