aboutsummaryrefslogtreecommitdiffhomepage
path: root/Functions/FirebaseFunctions
diff options
context:
space:
mode:
authorGravatar Paul Beusterien <paulbeusterien@google.com>2018-03-20 11:59:03 -0700
committerGravatar GitHub <noreply@github.com>2018-03-20 11:59:03 -0700
commitb7f35a0b76bb2afd682b806d2b25568611612557 (patch)
treedeb4577d3e54c3fafa2a065605faef228a186c1b /Functions/FirebaseFunctions
parent7e65885762757209e0e14ec28e99ec91380e9c2f (diff)
Initial Firebase Functions (#948)
Diffstat (limited to 'Functions/FirebaseFunctions')
-rw-r--r--Functions/FirebaseFunctions/FIRFunctions+Internal.h50
-rw-r--r--Functions/FirebaseFunctions/FIRFunctions.m247
-rw-r--r--Functions/FirebaseFunctions/FIRHTTPSCallable+Internal.h46
-rw-r--r--Functions/FirebaseFunctions/FIRHTTPSCallable.m71
-rw-r--r--Functions/FirebaseFunctions/FUNContext.h41
-rw-r--r--Functions/FirebaseFunctions/FUNContext.m84
-rw-r--r--Functions/FirebaseFunctions/FUNError.h31
-rw-r--r--Functions/FirebaseFunctions/FUNError.m186
-rw-r--r--Functions/FirebaseFunctions/FUNInstanceIDProxy.h29
-rw-r--r--Functions/FirebaseFunctions/FUNInstanceIDProxy.m57
-rw-r--r--Functions/FirebaseFunctions/FUNSerializer.h33
-rw-r--r--Functions/FirebaseFunctions/FUNSerializer.m231
-rw-r--r--Functions/FirebaseFunctions/FUNUsageValidation.h38
-rw-r--r--Functions/FirebaseFunctions/FUNUsageValidation.m28
-rw-r--r--Functions/FirebaseFunctions/Public/FIRError.h90
-rw-r--r--Functions/FirebaseFunctions/Public/FIRFunctions.h66
-rw-r--r--Functions/FirebaseFunctions/Public/FIRHTTPSCallable.h94
17 files changed, 1422 insertions, 0 deletions
diff --git a/Functions/FirebaseFunctions/FIRFunctions+Internal.h b/Functions/FirebaseFunctions/FIRFunctions+Internal.h
new file mode 100644
index 0000000..6c555f0
--- /dev/null
+++ b/Functions/FirebaseFunctions/FIRFunctions+Internal.h
@@ -0,0 +1,50 @@
+// 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 <Foundation/Foundation.h>
+
+#import "FIRFunctions.h"
+
+@class FIRHTTPSCallableResult;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRFunctions (Internal)
+
+/**
+ * Calls an http trigger endpoint.
+ * @param name The name of the http trigger.
+ * @param data Parameters to pass to the function. Can be anything encodable as JSON.
+ * @param completion The block to call when the request is complete.
+ */
+- (void)callFunction:(NSString *)name
+ withObject:(nullable id)data
+ completion:(void (^)(FIRHTTPSCallableResult *_Nullable result,
+ NSError *_Nullable error))completion;
+
+/**
+ * Constructs the url for an http trigger. This is exposed only for testing.
+ * @param name The name of the endpoint.
+ */
+- (NSString *)URLWithName:(NSString *)name;
+
+/**
+ * Sets the functions client to send requests to localhost instead of Firebase.
+ * For testing only.
+ */
+- (void)useLocalhost;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FIRFunctions.m b/Functions/FirebaseFunctions/FIRFunctions.m
new file mode 100644
index 0000000..274d058
--- /dev/null
+++ b/Functions/FirebaseFunctions/FIRFunctions.m
@@ -0,0 +1,247 @@
+// 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
diff --git a/Functions/FirebaseFunctions/FIRHTTPSCallable+Internal.h b/Functions/FirebaseFunctions/FIRHTTPSCallable+Internal.h
new file mode 100644
index 0000000..0a8dae1
--- /dev/null
+++ b/Functions/FirebaseFunctions/FIRHTTPSCallable+Internal.h
@@ -0,0 +1,46 @@
+// 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 <Foundation/Foundation.h>
+
+#import "FIRHTTPSCallable.h"
+
+@class FIRFunctions;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRHTTPSCallableResult (Internal)
+
+/**
+ * Initializes a callable result.
+ *
+ * @param result The data to wrap.
+ */
+- (instancetype)initWithData:(id)result;
+
+@end
+
+@interface FIRHTTPSCallable (Internal)
+
+/**
+ * Initializes a reference to the given http trigger.
+ *
+ * @param functionsClient The functions client to use for making network requests.
+ * @param name The name of the http trigger.
+ */
+- (instancetype)initWithFunctions:(FIRFunctions *)functionsClient name:(NSString *)name;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FIRHTTPSCallable.m b/Functions/FirebaseFunctions/FIRHTTPSCallable.m
new file mode 100644
index 0000000..2979ca5
--- /dev/null
+++ b/Functions/FirebaseFunctions/FIRHTTPSCallable.m
@@ -0,0 +1,71 @@
+// 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 "FIRHTTPSCallable.h"
+#import "FIRHTTPSCallable+Internal.h"
+
+#import "FIRFunctions+Internal.h"
+#import "FUNUsageValidation.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@implementation FIRHTTPSCallableResult
+
+- (instancetype)initWithData:(id)data {
+ self = [super init];
+ if (self) {
+ _data = data;
+ }
+ return self;
+}
+
+@end
+
+@interface FIRHTTPSCallable () {
+ // The functions client to use for making calls.
+ FIRFunctions *_functions;
+ // The name of the http endpoint this reference refers to.
+ NSString *_name;
+}
+
+@end
+
+@implementation FIRHTTPSCallable
+
+- (instancetype)initWithFunctions:(FIRFunctions *)functions name:(NSString *)name {
+ self = [super init];
+ if (self) {
+ if (!name) {
+ FUNThrowInvalidArgument(@"FIRHTTPSCallable name cannot be nil.");
+ }
+ _name = [name copy];
+ _functions = functions;
+ }
+ return self;
+}
+
+- (void)callWithCompletion:(void (^)(FIRHTTPSCallableResult *_Nullable result,
+ NSError *_Nullable error))completion {
+ [self callWithObject:nil completion:completion];
+}
+
+- (void)callWithObject:(nullable id)data
+ completion:(void (^)(FIRHTTPSCallableResult *_Nullable result,
+ NSError *_Nullable error))completion {
+ [_functions callFunction:_name withObject:data completion:completion];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FUNContext.h b/Functions/FirebaseFunctions/FUNContext.h
new file mode 100644
index 0000000..0979b67
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNContext.h
@@ -0,0 +1,41 @@
+// 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 <Foundation/Foundation.h>
+
+@class FIRApp;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * FUNContext is a helper class for gathering metadata for a function call.
+ */
+@interface FUNContext : NSObject
+- (id)init NS_UNAVAILABLE;
+@property(nonatomic, copy, nullable, readonly) NSString *authToken;
+@property(nonatomic, copy, nullable, readonly) NSString *instanceIDToken;
+@end
+
+/**
+ * A FUNContextProvider gathers metadata and creats a FUNContext.
+ */
+@interface FUNContextProvider : NSObject
+
+- (id)init NS_UNAVAILABLE;
+
+- (instancetype)initWithApp:(FIRApp *)app NS_DESIGNATED_INITIALIZER;
+
+- (void)getContext:(void (^)(FUNContext *_Nullable context, NSError *_Nullable error))completion;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FUNContext.m b/Functions/FirebaseFunctions/FUNContext.m
new file mode 100644
index 0000000..0ce4c2b
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNContext.m
@@ -0,0 +1,84 @@
+// 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 "FUNContext.h"
+
+#import "FIRApp.h"
+#import "FIRAppInternal.h"
+#import "FUNInstanceIDProxy.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FUNContext ()
+
+- (instancetype)initWithAuthToken:(NSString *_Nullable)authToken
+ instanceIDToken:(NSString *_Nullable)instanceIDToken NS_DESIGNATED_INITIALIZER;
+
+@end
+
+@implementation FUNContext
+
+- (instancetype)initWithAuthToken:(NSString *_Nullable)authToken
+ instanceIDToken:(NSString *_Nullable)instanceIDToken {
+ self = [super init];
+ if (self) {
+ _authToken = [authToken copy];
+ _instanceIDToken = [instanceIDToken copy];
+ }
+ return self;
+}
+
+@end
+
+@interface FUNContextProvider () {
+ FIRApp *_app;
+ FUNInstanceIDProxy *_instanceIDProxy;
+}
+@end
+
+@implementation FUNContextProvider
+
+- (instancetype)initWithApp:(FIRApp *)app {
+ self = [super init];
+ if (self) {
+ _app = app;
+ _instanceIDProxy = [[FUNInstanceIDProxy alloc] init];
+ }
+ return self;
+}
+
+// This is broken out so it can be mocked for tests.
+- (NSString *)instanceIDToken {
+ return [_instanceIDProxy token];
+}
+
+- (void)getContext:(void (^)(FUNContext *_Nullable context, NSError *_Nullable error))completion {
+ // Get the auth token.
+ [_app getTokenForcingRefresh:NO
+ withCallback:^(NSString *_Nullable token, NSError *_Nullable error) {
+ if (error) {
+ completion(nil, error);
+ return;
+ }
+
+ // Get the instance id token.
+ NSString *_Nullable instanceIDToken = [self instanceIDToken];
+
+ FUNContext *context = [[FUNContext alloc] initWithAuthToken:token
+ instanceIDToken:instanceIDToken];
+ completion(context, nil);
+ }];
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FUNError.h b/Functions/FirebaseFunctions/FUNError.h
new file mode 100644
index 0000000..04a9257
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNError.h
@@ -0,0 +1,31 @@
+// 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 <Foundation/Foundation.h>
+
+@class FUNSerializer;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Takes an HTTP status code and optional body and returns a corresponding NSError.
+ * If an explicit error is encoded in the JSON body, it will be used.
+ * Otherwise, uses the standard HTTP status code -> error mapping defined in:
+ * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
+ * @param status An HTTP status code.
+ * @param body Optional body of the HTTP response.
+ * @param serializer A serializer to use to decode the details in the error response.
+ * @return The corresponding error.
+ */
+NSError *FUNErrorForResponse(NSInteger status, NSData *_Nullable body, FUNSerializer *serializer);
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FUNError.m b/Functions/FirebaseFunctions/FUNError.m
new file mode 100644
index 0000000..d2fac6d
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNError.m
@@ -0,0 +1,186 @@
+// 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 "FUNError.h"
+#import "FIRError.h"
+
+#import "FUNSerializer.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+NSString *const FIRFunctionsErrorDomain = @"com.firebase.functions";
+NSString *const FIRFunctionsErrorDetailsKey = @"details";
+
+/**
+ * Takes an HTTP status code and returns the corresponding FIRFunctionsErrorCode error code.
+ * This is the standard HTTP status code -> error mapping defined in:
+ * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
+ * @param status An HTTP status code.
+ * @return The corresponding error code, or FIRFunctionsErrorCodeUnknown if none.
+ */
+FIRFunctionsErrorCode FIRFunctionsErrorCodeForHTTPStatus(NSInteger status) {
+ switch (status) {
+ case 200:
+ return FIRFunctionsErrorCodeOK;
+ case 400:
+ return FIRFunctionsErrorCodeInvalidArgument;
+ case 401:
+ return FIRFunctionsErrorCodeUnauthenticated;
+ case 403:
+ return FIRFunctionsErrorCodePermissionDenied;
+ case 404:
+ return FIRFunctionsErrorCodeNotFound;
+ case 409:
+ return FIRFunctionsErrorCodeAborted;
+ case 429:
+ return FIRFunctionsErrorCodeResourceExhausted;
+ case 499:
+ return FIRFunctionsErrorCodeCancelled;
+ case 500:
+ return FIRFunctionsErrorCodeInternal;
+ case 501:
+ return FIRFunctionsErrorCodeUnimplemented;
+ case 503:
+ return FIRFunctionsErrorCodeUnavailable;
+ case 504:
+ return FIRFunctionsErrorCodeDeadlineExceeded;
+ }
+ return FIRFunctionsErrorCodeInternal;
+}
+
+/**
+ * Takes the name of an error code and returns the enum value for it.
+ * @param name An error name.
+ * @return The error code with this name, or FIRFunctionsErrorCodeUnknown if none.
+ */
+FIRFunctionsErrorCode FIRFunctionsErrorCodeForName(NSString *name) {
+ static NSDictionary<NSString *, NSNumber *> *errors;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ errors = @{
+ @"OK" : @(FIRFunctionsErrorCodeOK),
+ @"CANCELLED" : @(FIRFunctionsErrorCodeCancelled),
+ @"UNKNOWN" : @(FIRFunctionsErrorCodeUnknown),
+ @"INVALID_ARGUMENT" : @(FIRFunctionsErrorCodeInvalidArgument),
+ @"DEADLINE_EXCEEDED" : @(FIRFunctionsErrorCodeDeadlineExceeded),
+ @"NOT_FOUND" : @(FIRFunctionsErrorCodeNotFound),
+ @"ALREADY_EXISTS" : @(FIRFunctionsErrorCodeAlreadyExists),
+ @"PERMISSION_DENIED" : @(FIRFunctionsErrorCodePermissionDenied),
+ @"RESOURCE_EXHAUSTED" : @(FIRFunctionsErrorCodeResourceExhausted),
+ @"FAILED_PRECONDITION" : @(FIRFunctionsErrorCodeFailedPrecondition),
+ @"ABORTED" : @(FIRFunctionsErrorCodeAborted),
+ @"OUT_OF_RANGE" : @(FIRFunctionsErrorCodeOutOfRange),
+ @"UNIMPLEMENTED" : @(FIRFunctionsErrorCodeUnimplemented),
+ @"INTERNAL" : @(FIRFunctionsErrorCodeInternal),
+ @"UNAVAILABLE" : @(FIRFunctionsErrorCodeUnavailable),
+ @"DATA_LOSS" : @(FIRFunctionsErrorCodeDataLoss),
+ @"UNAUTHENTICATED" : @(FIRFunctionsErrorCodeUnauthenticated),
+ };
+ });
+ NSNumber *code = errors[name];
+ if (code) {
+ return code.intValue;
+ }
+ return FIRFunctionsErrorCodeInternal;
+}
+
+/**
+ * Takes a FIRFunctionsErrorCode and returns an English description of it.
+ * @param code An error code.
+ * @return A description of the code, or "UNKNOWN" if none.
+ */
+NSString *FUNDescriptionForErrorCode(FIRFunctionsErrorCode code) {
+ switch (code) {
+ case FIRFunctionsErrorCodeOK:
+ return @"OK";
+ case FIRFunctionsErrorCodeCancelled:
+ return @"CANCELLED";
+ case FIRFunctionsErrorCodeUnknown:
+ return @"UNKNOWN";
+ case FIRFunctionsErrorCodeInvalidArgument:
+ return @"INVALID ARGUMENT";
+ case FIRFunctionsErrorCodeDeadlineExceeded:
+ return @"DEADLINE EXCEEDED";
+ case FIRFunctionsErrorCodeNotFound:
+ return @"NOT FOUND";
+ case FIRFunctionsErrorCodeAlreadyExists:
+ return @"ALREADY EXISTS";
+ case FIRFunctionsErrorCodePermissionDenied:
+ return @"PERMISSION DENIED";
+ case FIRFunctionsErrorCodeResourceExhausted:
+ return @"RESOURCE EXHAUSTED";
+ case FIRFunctionsErrorCodeFailedPrecondition:
+ return @"FAILED PRECONDITION";
+ case FIRFunctionsErrorCodeAborted:
+ return @"ABORTED";
+ case FIRFunctionsErrorCodeOutOfRange:
+ return @"OUT OF RANGE";
+ case FIRFunctionsErrorCodeUnimplemented:
+ return @"UNIMPLEMENTED";
+ case FIRFunctionsErrorCodeInternal:
+ return @"INTERNAL";
+ case FIRFunctionsErrorCodeUnavailable:
+ return @"UNAVAILABLE";
+ case FIRFunctionsErrorCodeDataLoss:
+ return @"DATA LOSS";
+ case FIRFunctionsErrorCodeUnauthenticated:
+ return @"UNAUTHENTICATED";
+ }
+ return @"UNKNOWN";
+}
+
+NSError *FUNErrorForResponse(NSInteger status, NSData *_Nullable body, FUNSerializer *serializer) {
+ // Start with reasonable defaults from the status code.
+ FIRFunctionsErrorCode code = FIRFunctionsErrorCodeForHTTPStatus(status);
+ NSString *description = FUNDescriptionForErrorCode(code);
+ id details = nil;
+
+ // Then look through the body for explicit details.
+ if (body) {
+ NSError *parseError = nil;
+ id json = [NSJSONSerialization JSONObjectWithData:body options:0 error:&parseError];
+ if (!parseError && [json isKindOfClass:[NSDictionary class]]) {
+ id errorDetails = json[@"error"];
+ if ([errorDetails isKindOfClass:[NSDictionary class]]) {
+ if ([errorDetails[@"status"] isKindOfClass:[NSString class]]) {
+ code = FIRFunctionsErrorCodeForName(errorDetails[@"status"]);
+ // The default description needs to be updated for the new code.
+ description = FUNDescriptionForErrorCode(code);
+ }
+ if ([errorDetails[@"message"] isKindOfClass:[NSString class]]) {
+ description = (NSString *)errorDetails[@"message"];
+ }
+ details = errorDetails[@"details"];
+ if (details) {
+ NSError *decodeError = nil;
+ details = [serializer decode:details error:&decodeError];
+ // Just ignore the details if there an error decoding them.
+ }
+ }
+ }
+ }
+
+ if (code == FIRFunctionsErrorCodeOK) {
+ // Technically, there's an edge case where a developer could explicitly return an error code of
+ // OK, and we will treat it as success, but that seems reasonable.
+ return nil;
+ }
+
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
+ userInfo[NSLocalizedDescriptionKey] = description;
+ if (details) {
+ userInfo[FIRFunctionsErrorDetailsKey] = details;
+ }
+ return [NSError errorWithDomain:FIRFunctionsErrorDomain code:code userInfo:userInfo];
+}
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FUNInstanceIDProxy.h b/Functions/FirebaseFunctions/FUNInstanceIDProxy.h
new file mode 100644
index 0000000..17ec9ef
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNInstanceIDProxy.h
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+// Note: This file is forked from FIRMessagingInstanceIDProxy.h
+
+#import <Foundation/Foundation.h>
+
+/**
+ * FirebaseFunctions cannot always depend on FIRInstanceID directly, due to how it is
+ * packaged. To make it easier to make calls to FIRInstanceID, this proxy class, will provide
+ * method names duplicated from FIRInstanceID, while using reflection-based called to proxy
+ * the requests.
+ */
+@interface FUNInstanceIDProxy : NSObject
+- (nullable NSString *)token;
+@end
diff --git a/Functions/FirebaseFunctions/FUNInstanceIDProxy.m b/Functions/FirebaseFunctions/FUNInstanceIDProxy.m
new file mode 100644
index 0000000..f89ec98
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNInstanceIDProxy.m
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+// Note: This is forked from FIRMessagingInstanceIDProxy.m
+
+#import "FUNInstanceIDProxy.h"
+
+@implementation FUNInstanceIDProxy
+
++ (nonnull instancetype)instanceIDProxy {
+ static id proxyInstanceID = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ Class instanceIDClass = NSClassFromString(@"FIRInstanceID");
+ if (!instanceIDClass) {
+ proxyInstanceID = nil;
+ return;
+ }
+ SEL instanceIDSelector = NSSelectorFromString(@"instanceID");
+ if (![instanceIDClass respondsToSelector:instanceIDSelector]) {
+ proxyInstanceID = nil;
+ return;
+ }
+ IMP instanceIDImp = [instanceIDClass methodForSelector:instanceIDSelector];
+ id (*instanceIDFunc)(id, SEL) = (void *)instanceIDImp;
+ proxyInstanceID = instanceIDFunc(instanceIDClass, instanceIDSelector);
+ });
+ return (FUNInstanceIDProxy *)proxyInstanceID;
+}
+
+#pragma mark - Tokens
+
+- (nullable NSString *)token {
+ id proxy = [[self class] instanceIDProxy];
+ SEL getTokenSelector = NSSelectorFromString(@"token");
+ if (![proxy respondsToSelector:getTokenSelector]) {
+ return nil;
+ }
+ IMP getTokenIMP = [proxy methodForSelector:getTokenSelector];
+ NSString *(*getToken)(id, SEL) = (void *)getTokenIMP;
+ return getToken(proxy, getTokenSelector);
+}
+
+@end
diff --git a/Functions/FirebaseFunctions/FUNSerializer.h b/Functions/FirebaseFunctions/FUNSerializer.h
new file mode 100644
index 0000000..598a2a1
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNSerializer.h
@@ -0,0 +1,33 @@
+// 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 <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FUNSerializer : NSObject
+
+/**
+ * Converts raw Objective-C types into JSON objects.
+ */
+- (id)encode:(id)object;
+
+/**
+ * Converts objects in JSON to Objective-C types.
+ */
+- (id)decode:(id)object error:(NSError **)error;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FUNSerializer.m b/Functions/FirebaseFunctions/FUNSerializer.m
new file mode 100644
index 0000000..682f981
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNSerializer.m
@@ -0,0 +1,231 @@
+// 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 "FUNSerializer.h"
+
+#import "FIRError.h"
+#import "FUNUsageValidation.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+static NSString *const kLongType = @"type.googleapis.com/google.protobuf.Int64Value";
+static NSString *const kUnsignedLongType = @"type.googleapis.com/google.protobuf.UInt64Value";
+static NSString *const kDateType = @"type.googleapis.com/google.protobuf.Timestamp";
+
+@interface FUNSerializer () {
+ NSDateFormatter *_dateFormatter;
+}
+@end
+
+@implementation FUNSerializer
+
+- (instancetype)init {
+ self = [super init];
+ if (self) {
+ _dateFormatter = [[NSDateFormatter alloc] init];
+ _dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
+ _dateFormatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
+ }
+ return self;
+}
+
+- (id)encodeNumber:(NSNumber *)number {
+ // Recover the underlying type of the number, using the method described here:
+ // http://stackoverflow.com/questions/2518761/get-type-of-nsnumber
+ const char *cType = [number objCType];
+
+ // Type Encoding values taken from
+ // https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/
+ // Articles/ocrtTypeEncodings.html
+ switch (cType[0]) {
+ case 'q':
+ // "long long" might be larger than JS supports, so make it a string.
+ return @{
+ @"@type" : kLongType,
+ @"value" : [NSString stringWithFormat:@"%@", number],
+ };
+ case 'Q':
+ // "unsigned long long" might be larger than JS supports, so make it a string.
+ return @{
+ @"@type" : kUnsignedLongType,
+ @"value" : [NSString stringWithFormat:@"%@", number],
+ };
+
+ case 'i':
+ case 's':
+ case 'l':
+ case 'I':
+ case 'S':
+ // If it's an integer that isn't too long, so just use the number.
+ return number;
+
+ case 'f':
+ case 'd':
+ // It's a float/double that's not too large.
+ return number;
+
+ case 'B':
+ case 'c':
+ case 'C':
+ // Boolean values are weird.
+ //
+ // On arm64, objCType of a BOOL-valued NSNumber will be "c", even though @encode(BOOL)
+ // returns "B". "c" is the same as @encode(signed char). Unfortunately this means that
+ // legitimate usage of signed chars is impossible, but this should be rare.
+ //
+ // Just return Boolean values as-is.
+ return number;
+
+ default:
+ // All documented codes should be handled above, so this shouldn't happen.
+ FUNThrowInvalidArgument(@"Unknown NSNumber objCType %s on %@", cType, number);
+ }
+}
+
+- (id)encode:(id)object {
+ if ([object isEqual:[NSNull null]]) {
+ return object;
+ }
+ if ([object isKindOfClass:[NSNumber class]]) {
+ return [self encodeNumber:object];
+ }
+ if ([object isKindOfClass:[NSString class]]) {
+ return object;
+ }
+ if ([object isKindOfClass:[NSDictionary class]]) {
+ NSMutableDictionary *encoded = [NSMutableDictionary dictionary];
+ [object
+ enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {
+ encoded[key] = [self encode:obj];
+ }];
+ return encoded;
+ }
+ if ([object isKindOfClass:[NSArray class]]) {
+ NSMutableArray *encoded = [NSMutableArray arrayWithCapacity:[object count]];
+ for (id obj in object) {
+ [encoded addObject:[self encode:obj]];
+ }
+ return encoded;
+ }
+ // TODO(klimt): Add this back when we support NSDate.
+ /*
+ if ([object isKindOfClass:[NSDate class]]) {
+ NSString *iso8601 = [_dateFormatter stringFromDate:object];
+ return @{
+ @"@type" : kDateType,
+ @"value" : iso8601,
+ };
+ }
+ */
+ FUNThrowInvalidArgument(@"Unsupported type: %@ for value %@", NSStringFromClass([object class]),
+ object);
+}
+
+NSError *FUNInvalidNumberError(id value, id wrapped) {
+ NSString *description = [NSString stringWithFormat:@"Invalid number: %@ for %@", value, wrapped];
+ NSDictionary *userInfo = @{
+ NSLocalizedDescriptionKey : description,
+ };
+ return [NSError errorWithDomain:FIRFunctionsErrorDomain
+ code:FIRFunctionsErrorCodeInternal
+ userInfo:userInfo];
+}
+
+- (id)decodeWrappedType:(NSDictionary *)wrapped error:(NSError **)error {
+ NSAssert(error, @"error must not be nil");
+ NSString *type = wrapped[@"@type"];
+ NSString *value = wrapped[@"value"];
+ if (!value) {
+ return nil;
+ }
+ if ([type isEqualToString:kLongType]) {
+ NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
+ NSNumber *n = [formatter numberFromString:value];
+ if (!n) {
+ *error = FUNInvalidNumberError(value, wrapped);
+ return nil;
+ }
+ return n;
+ } else if ([type isEqualToString:kUnsignedLongType]) {
+ // NSNumber formatter doesn't handle unsigned long long, so we have to parse it.
+ const char *str = value.UTF8String;
+ char *end = NULL;
+ unsigned long long n = strtoull(str, &end, 10);
+ if (errno == ERANGE) {
+ // This number was actually too big for an unsigned long long.
+ *error = FUNInvalidNumberError(value, wrapped);
+ return nil;
+ }
+ if (*end) {
+ // The whole string wasn't parsed.
+ *error = FUNInvalidNumberError(value, wrapped);
+ return nil;
+ }
+ return @(n);
+ }
+ return nil;
+}
+
+- (id)decode:(id)object error:(NSError **)error {
+ NSAssert(error, @"error must not be nil");
+ if ([object isKindOfClass:[NSDictionary class]]) {
+ if (object[@"@type"]) {
+ id result = [self decodeWrappedType:object error:error];
+ if (*error) {
+ return nil;
+ }
+ if (result) {
+ return result;
+ }
+ // Treat unknown types as dictionaries, so we don't crash old clients when we add types.
+ }
+ NSMutableDictionary *decoded = [NSMutableDictionary dictionary];
+ __block NSError *decodeError = nil;
+ [object
+ enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {
+ id decodedItem = [self decode:obj error:&decodeError];
+ if (decodeError) {
+ *stop = YES;
+ return;
+ }
+ decoded[key] = decodedItem;
+ }];
+ if (decodeError) {
+ *error = decodeError;
+ return nil;
+ }
+ return decoded;
+ }
+ if ([object isKindOfClass:[NSArray class]]) {
+ NSMutableArray *result = [NSMutableArray arrayWithCapacity:[object count]];
+ for (id obj in object) {
+ id decoded = [self decode:obj error:error];
+ if (*error) {
+ return nil;
+ }
+ [result addObject:decoded];
+ }
+ return result;
+ }
+ if ([object isKindOfClass:[NSNumber class]] || [object isKindOfClass:[NSString class]] ||
+ [object isEqual:[NSNull null]]) {
+ return object;
+ }
+ FUNThrowInvalidArgument(@"Unsupported type: %@ for value %@", NSStringFromClass([object class]),
+ object);
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FUNUsageValidation.h b/Functions/FirebaseFunctions/FUNUsageValidation.h
new file mode 100644
index 0000000..17b1c4f
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNUsageValidation.h
@@ -0,0 +1,38 @@
+// 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 <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/** Helper for creating a general exception for invalid usage of an API. */
+NSException *FUNInvalidUsage(NSString *exceptionName, NSString *format, ...);
+
+/**
+ * Macro to throw exceptions in response to API usage errors. Avoids the lint warning you usually
+ * get when using @throw and (unlike a function) doesn't trigger warnings about not all codepaths
+ * returning a value.
+ *
+ * Exceptions should only be used for programmer errors made by consumers of the SDK, e.g.
+ * invalid method arguments.
+ *
+ * For recoverable runtime errors, use NSError**.
+ * For internal programming errors, use FSTFail().
+ */
+#define FUNThrowInvalidArgument(format, ...) \
+ do { \
+ @throw FUNInvalidUsage(@"FIRInvalidArgumentException", format, ##__VA_ARGS__); \
+ } while (0)
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/FUNUsageValidation.m b/Functions/FirebaseFunctions/FUNUsageValidation.m
new file mode 100644
index 0000000..a50f525
--- /dev/null
+++ b/Functions/FirebaseFunctions/FUNUsageValidation.m
@@ -0,0 +1,28 @@
+// 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 "FUNUsageValidation.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+NSException *FUNInvalidUsage(NSString *exceptionName, NSString *format, ...) {
+ va_list arg_list;
+ va_start(arg_list, format);
+ NSString *formattedString = [[NSString alloc] initWithFormat:format arguments:arg_list];
+ va_end(arg_list);
+
+ return [[NSException alloc] initWithName:exceptionName reason:formattedString userInfo:nil];
+}
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/Public/FIRError.h b/Functions/FirebaseFunctions/Public/FIRError.h
new file mode 100644
index 0000000..a275633
--- /dev/null
+++ b/Functions/FirebaseFunctions/Public/FIRError.h
@@ -0,0 +1,90 @@
+// 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 <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+// The error domain for codes in the FIRFunctionsErrorCode enum.
+FOUNDATION_EXPORT NSString *const FIRFunctionsErrorDomain NS_SWIFT_NAME(FunctionsErrorDomain);
+
+// The key for finding error details in the NSError userInfo.
+FOUNDATION_EXPORT NSString *const FIRFunctionsErrorDetailsKey NS_SWIFT_NAME(FunctionsErrorDetailsKey);
+
+/**
+ * The set of error status codes that can be returned from a Callable HTTPS tigger. These are the
+ * canonical error codes for Google APIs, as documented here:
+ * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto#L26
+ */
+typedef NS_ENUM(NSInteger, FIRFunctionsErrorCode) {
+ /** The operation completed successfully. */
+ FIRFunctionsErrorCodeOK = 0,
+ /** The operation was cancelled (typically by the caller). */
+ FIRFunctionsErrorCodeCancelled = 1,
+ /** Unknown error or an error from a different error domain. */
+ FIRFunctionsErrorCodeUnknown = 2,
+ /**
+ * Client specified an invalid argument. Note that this differs from `FailedPrecondition`.
+ * `InvalidArgument` indicates arguments that are problematic regardless of the state of the
+ * system (e.g., an invalid field name).
+ */
+ FIRFunctionsErrorCodeInvalidArgument = 3,
+ /**
+ * Deadline expired before operation could complete. For operations that change the state of the
+ * system, this error may be returned even if the operation has completed successfully. For
+ * example, a successful response from a server could have been delayed long enough for the
+ * deadline to expire.
+ */
+ FIRFunctionsErrorCodeDeadlineExceeded = 4,
+ /** Some requested document was not found. */
+ FIRFunctionsErrorCodeNotFound = 5,
+ /** Some document that we attempted to create already exists. */
+ FIRFunctionsErrorCodeAlreadyExists = 6,
+ /** The caller does not have permission to execute the specified operation. */
+ FIRFunctionsErrorCodePermissionDenied = 7,
+ /**
+ * Some resource has been exhausted, perhaps a per-user quota, or perhaps the entire file system
+ * is out of space.
+ */
+ FIRFunctionsErrorCodeResourceExhausted = 8,
+ /**
+ * Operation was rejected because the system is not in a state required for the operation's
+ * execution.
+ */
+ FIRFunctionsErrorCodeFailedPrecondition = 9,
+ /**
+ * The operation was aborted, typically due to a concurrency issue like transaction aborts, etc.
+ */
+ FIRFunctionsErrorCodeAborted = 10,
+ /** Operation was attempted past the valid range. */
+ FIRFunctionsErrorCodeOutOfRange = 11,
+ /** Operation is not implemented or not supported/enabled. */
+ FIRFunctionsErrorCodeUnimplemented = 12,
+ /**
+ * Internal errors. Means some invariant expected by underlying system has been broken. If you
+ * see one of these errors, something is very broken.
+ */
+ FIRFunctionsErrorCodeInternal = 13,
+ /**
+ * The service is currently unavailable. This is a most likely a transient condition and may be
+ * corrected by retrying with a backoff.
+ */
+ FIRFunctionsErrorCodeUnavailable = 14,
+ /** Unrecoverable data loss or corruption. */
+ FIRFunctionsErrorCodeDataLoss = 15,
+ /** The request does not have valid authentication credentials for the operation. */
+ FIRFunctionsErrorCodeUnauthenticated = 16,
+};
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/Public/FIRFunctions.h b/Functions/FirebaseFunctions/Public/FIRFunctions.h
new file mode 100644
index 0000000..40ec634
--- /dev/null
+++ b/Functions/FirebaseFunctions/Public/FIRFunctions.h
@@ -0,0 +1,66 @@
+// 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 <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class FIRApp;
+@class FIRHTTPSCallable;
+
+/**
+ * `FIRFunctions` is the client for Cloud Functions for a Firebase project.
+ */
+NS_SWIFT_NAME(Functions)
+@interface FIRFunctions : NSObject
+
+- (id)init NS_UNAVAILABLE;
+
+/**
+ * Creates a Cloud Functions client with the default app.
+ */
++ (instancetype)functions NS_SWIFT_NAME(functions());
+
+/**
+ * Creates a Cloud Functions client with the given app.
+ * @param app The app for the Firebase project.
+ */
++ (instancetype)functionsForApp:(FIRApp *)app NS_SWIFT_NAME(functions(app:));
+
+/**
+ * Creates a Cloud Functions client with the default app and given region.
+ * @param region The region for the http trigger, such as "us-central1".
+ */
+// + (instancetype)functionsForRegion:(NSString *)region NS_SWIFT_NAME(functions(region:));
+
+/**
+ * Creates a 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".
+ */
+// clang-format off
+// because it incorrectly breaks this NS_SWIFT_NAME.
+// + (instancetype)functionsForApp:(FIRApp *)app
+// region:(NSString *)region NS_SWIFT_NAME(functions(app:region:));
+// clang-format on
+
+/**
+ * Creates a reference to the Callable HTTPS trigger with the given name.
+ * @param name The name of the Callable HTTPS trigger.
+ */
+- (FIRHTTPSCallable *)HTTPSCallableWithName:(NSString *)name NS_SWIFT_NAME(httpsCallable(_:));
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Functions/FirebaseFunctions/Public/FIRHTTPSCallable.h b/Functions/FirebaseFunctions/Public/FIRHTTPSCallable.h
new file mode 100644
index 0000000..948696d
--- /dev/null
+++ b/Functions/FirebaseFunctions/Public/FIRHTTPSCallable.h
@@ -0,0 +1,94 @@
+// 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 <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * A `FIRHTTPSCallableResult` contains the result of calling a `FIRHTTPSCallable`.
+ */
+NS_SWIFT_NAME(HTTPSCallableResult)
+@interface FIRHTTPSCallableResult : NSObject
+
+- (id)init NS_UNAVAILABLE;
+
+
+/**
+ * The data that was returned from the Callable HTTPS trigger.
+ *
+ * The data is in the form of native objects. For example, if your trigger returned an
+ * array, this object would be an NSArray. If your trigger returned a JavaScript object with
+ * keys and values, this object would be an NSDictionary.
+ */
+@property(nonatomic, strong, readonly) id data;
+
+@end
+
+/**
+ * A `FIRHTTPSCallable` is reference to a particular Callable HTTPS trigger in Cloud Functions.
+ */
+NS_SWIFT_NAME(HTTPSCallable)
+@interface FIRHTTPSCallable : NSObject
+
+- (id)init NS_UNAVAILABLE;
+
+/**
+ * Executes this Callable HTTPS trigger asynchronously without any parameters.
+ *
+ * The request to the Cloud Functions backend made by this method automatically includes a
+ * Firebase Instance ID token to identify the app instance. If a user is logged in with Firebase
+ * Auth, an auth ID token for the user is also automatically included.
+ *
+ * Firebase Instance ID sends data to the Firebase backend periodically to collect information
+ * regarding the app instance. To stop this, see `[FIRInstanceID deleteIDWithHandler:]`. It
+ * resumes with a new Instance ID the next time you call this method.
+ *
+ * @param completion The block to call when the HTTPS request has completed.
+ */
+- (void)callWithCompletion:
+ (void (^)(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error))completion
+ NS_SWIFT_NAME(call(completion:));
+
+/**
+ * Executes this Callable HTTPS trigger asynchronously.
+ *
+ * The data passed into the trigger can be any of the following types:
+ * * NSNull
+ * * NSString
+ * * NSNumber
+ * * NSArray<id>, where the contained objects are also one of these types.
+ * * NSDictionary<NSString, id>, where the values are also one of these types.
+ *
+ * The request to the Cloud Functions backend made by this method automatically includes a
+ * Firebase Instance ID token to identify the app instance. If a user is logged in with Firebase
+ * Auth, an auth ID token for the user is also automatically included.
+ *
+ * Firebase Instance ID sends data to the Firebase backend periodically to collect information
+ * regarding the app instance. To stop this, see `[FIRInstanceID deleteIDWithHandler:]`. It
+ * resumes with a new Instance ID the next time you call this method.
+ *
+ * @param data Parameters to pass to the trigger.
+ * @param completion The block to call when the HTTPS request has completed.
+ */
+// clang-format off
+// because it incorrectly breaks this NS_SWIFT_NAME.
+- (void)callWithObject:(nullable id)data
+ completion:(void (^)(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error))completion
+ NS_SWIFT_NAME(call(_:completion:));
+// clang-format on
+
+@end
+
+NS_ASSUME_NONNULL_END