diff options
author | Paul Beusterien <paulbeusterien@google.com> | 2018-03-20 15:41:44 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-03-20 15:41:44 -0700 |
commit | 87e511d52ad400e0a44a60c62325b2fd213930a3 (patch) | |
tree | 16bc6ef0777ded7f939145ba537b1b9fac6c938f | |
parent | 529666594951b86604730a8b400f71d7eedd1e85 (diff) | |
parent | 03275eb249946d2f8388e130011831e4bbd17d91 (diff) |
Merge pull request #950 from firebase/release-4.11.0
Release 4.11.0
66 files changed, 3919 insertions, 16 deletions
diff --git a/Example/Auth/Sample/MainViewController.m b/Example/Auth/Sample/MainViewController.m index 36ef92d..f6893d1 100644 --- a/Example/Auth/Sample/MainViewController.m +++ b/Example/Auth/Sample/MainViewController.m @@ -96,6 +96,11 @@ static NSString *const kSignInGoogleButtonText = @"Sign in with Google"; */ static NSString *const kSignInWithEmailLink = @"Sign in with Email Link"; +/** @var kVerifyEmailLinkAccount + @brief The text of the "Verify Email-link Account" button. + */ +static NSString *const kVerifyEmailLinkAccount = @"Verify Email-link Account"; + /** @var kSendEmailSignInLink @brief The text of the "Send Email SignIn link" button */ @@ -739,6 +744,8 @@ typedef enum { action:^{ [weakSelf signInGoogle]; }], [StaticContentTableViewCell cellWithTitle:kSignInWithEmailLink action:^{ [weakSelf signInWithEmailLink]; }], + [StaticContentTableViewCell cellWithTitle:kVerifyEmailLinkAccount + action:^{ [weakSelf verifyEmailLinkAccount]; }], [StaticContentTableViewCell cellWithTitle:kSendEmailSignInLink action:^{ [weakSelf sendEmailSignInLink]; }], [StaticContentTableViewCell cellWithTitle:kSignInGoogleAndRetrieveDataButtonText @@ -1784,6 +1791,29 @@ static NSDictionary<NSString *, NSString *> *parseURL(NSString *urlString) { }]; } +/** @fn verifyEmailLinkAccount + @brief Invoked to verify that the current user is an email-link user. + */ +- (void)verifyEmailLinkAccount { + if (![FIRAuth auth].currentUser.email) { + [self showMessagePrompt:@"There is no signed-in user available."]; + return; + } + [[FIRAuth auth] fetchSignInMethodsForEmail:[FIRAuth auth].currentUser.email + completion:^(NSArray<NSString *> *_Nullable signInMethods, + NSError *_Nullable error) { + if (error) { + [self showMessagePrompt:@"There was an error fetching sign-in methods."]; + return; + } + if (![signInMethods containsObject:FIREmailLinkAuthSignInMethod]) { + [self showMessagePrompt:@"Error: The current user is NOT an email-link user."]; + return; + } + [self showMessagePrompt:@"The current user is an email-link user."]; + }]; +} + /** @fn sendEmailSignInLink @brief Invoked when "Send email sign-in link" row is pressed. */ diff --git a/Example/Podfile b/Example/Podfile index f216308..35d348e 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -8,7 +8,7 @@ target 'Core_Example_iOS' do # The next line is the forcing function for the Firebase pod. The Firebase # version's subspecs should depend on the component versions in their # corresponding podspec's. - pod 'Firebase/Core', '4.10.1' + pod 'Firebase/Core', '4.11.0' target 'Core_Tests_iOS' do inherit! :search_paths diff --git a/Firebase/Auth/Source/FIRAuth.m b/Firebase/Auth/Source/FIRAuth.m index 3b29238..dc3154f 100644 --- a/Firebase/Auth/Source/FIRAuth.m +++ b/Firebase/Auth/Source/FIRAuth.m @@ -533,7 +533,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName; FIRCreateAuthURIRequest *request = [[FIRCreateAuthURIRequest alloc] initWithIdentifier:email continueURI:@"http://www.google.com/" - requestConfiguration:_requestConfiguration]; + requestConfiguration:self->_requestConfiguration]; [FIRAuthBackend createAuthURI:request callback:^(FIRCreateAuthURIResponse *_Nullable response, NSError *_Nullable error) { if (completion) { @@ -1124,7 +1124,7 @@ static NSMutableDictionary *gKeychainServiceNameForAppName; FIRGetOOBConfirmationCodeRequest *request = [FIRGetOOBConfirmationCodeRequest signInWithEmailLinkRequest:email actionCodeSettings:actionCodeSettings - requestConfiguration:_requestConfiguration]; + requestConfiguration:self->_requestConfiguration]; [FIRAuthBackend getOOBConfirmationCode:request callback:^(FIRGetOOBConfirmationCodeResponse *_Nullable response, NSError *_Nullable error) { diff --git a/Firebase/Auth/Source/FIRAuthProvider.m b/Firebase/Auth/Source/FIRAuthProvider.m index 6df86d7..72a00ef 100644 --- a/Firebase/Auth/Source/FIRAuthProvider.m +++ b/Firebase/Auth/Source/FIRAuthProvider.m @@ -16,6 +16,8 @@ #import <Foundation/Foundation.h> +#pragma mark - Provider ID constants + // Declared 'extern' in FIRGoogleAuthProvider.h NSString *const FIRGoogleAuthProviderID = @"google.com"; @@ -36,3 +38,26 @@ NSString *const FIRGitHubAuthProviderID = @"github.com"; // Declared 'extern' in FIRPhoneAuthProvider.h NSString *const FIRPhoneAuthProviderID = @"phone"; + +#pragma mark - sign-in methods constants + +// Declared 'extern' in FIRGoogleAuthProvider.h +NSString *const FIRGoogleAuthSignInMethod = @"google.com"; + +// Declared 'extern' in FIREmailAuthProvider.h +NSString *const FIREmailPasswordAuthSignInMethod = @"password"; + +// Declared 'extern' in FIREmailAuthProvider.h +NSString *const FIREmailLinkAuthSignInMethod = @"emailLink"; + +// Declared 'extern' in FIRTwitterAuthProvider.h +NSString *const FIRTwitterAuthSignInMethod = @"twitter.com"; + +// Declared 'extern' in FIRFacebookAuthProvider.h +NSString *const FIRFacebookAuthSignInMethod = @"facebook.com"; + +// Declared 'extern' in FIRGitHubAuthProvider.h +NSString *const FIRGitHubAuthSignInMethod = @"github.com"; + +// Declared 'extern' in FIRPhoneAuthProvider.h +NSString *const FIRPhoneAuthSignInMethod = @"phone"; diff --git a/Firebase/Auth/Source/Public/FIREmailAuthProvider.h b/Firebase/Auth/Source/Public/FIREmailAuthProvider.h index 5823911..b6375bd 100644 --- a/Firebase/Auth/Source/Public/FIREmailAuthProvider.h +++ b/Firebase/Auth/Source/Public/FIREmailAuthProvider.h @@ -26,6 +26,17 @@ NS_ASSUME_NONNULL_BEGIN extern NSString *const FIREmailAuthProviderID NS_SWIFT_NAME(EmailAuthProviderID); /** + @brief A string constant identifying the email-link sign-in method. + */ +extern NSString *const FIREmailLinkAuthSignInMethod NS_SWIFT_NAME(EmailLinkAuthSignInMethod); + +/** + @brief A string constant identifying the email & password sign-in method. + */ +extern NSString *const FIREmailPasswordAuthSignInMethod + NS_SWIFT_NAME(EmailPasswordAuthSignInMethod); + +/** @brief Please use `FIREmailAuthProviderID` for Objective-C or `EmailAuthProviderID` for Swift instead. */ extern NSString *const FIREmailPasswordAuthProviderID __attribute__((deprecated)); diff --git a/Firebase/Auth/Source/Public/FIRFacebookAuthProvider.h b/Firebase/Auth/Source/Public/FIRFacebookAuthProvider.h index f08740f..75efe13 100644 --- a/Firebase/Auth/Source/Public/FIRFacebookAuthProvider.h +++ b/Firebase/Auth/Source/Public/FIRFacebookAuthProvider.h @@ -25,6 +25,11 @@ NS_ASSUME_NONNULL_BEGIN */ extern NSString *const FIRFacebookAuthProviderID NS_SWIFT_NAME(FacebookAuthProviderID); +/** + @brief A string constant identifying the Facebook sign-in method. + */ +extern NSString *const _Nonnull FIRFacebookAuthSignInMethod NS_SWIFT_NAME(FacebookAuthSignInMethod); + /** @class FIRFacebookAuthProvider @brief Utility class for constructing Facebook credentials. */ diff --git a/Firebase/Auth/Source/Public/FIRGitHubAuthProvider.h b/Firebase/Auth/Source/Public/FIRGitHubAuthProvider.h index f0b5dbe..0610427 100644 --- a/Firebase/Auth/Source/Public/FIRGitHubAuthProvider.h +++ b/Firebase/Auth/Source/Public/FIRGitHubAuthProvider.h @@ -25,6 +25,12 @@ NS_ASSUME_NONNULL_BEGIN */ extern NSString *const FIRGitHubAuthProviderID NS_SWIFT_NAME(GitHubAuthProviderID); +/** + @brief A string constant identifying the GitHub sign-in method. + */ +extern NSString *const _Nonnull FIRGitHubAuthSignInMethod NS_SWIFT_NAME(GitHubAuthSignInMethod); + + /** @class FIRGitHubAuthProvider @brief Utility class for constructing GitHub credentials. */ diff --git a/Firebase/Auth/Source/Public/FIRGoogleAuthProvider.h b/Firebase/Auth/Source/Public/FIRGoogleAuthProvider.h index e80d87e..7d6fa22 100644 --- a/Firebase/Auth/Source/Public/FIRGoogleAuthProvider.h +++ b/Firebase/Auth/Source/Public/FIRGoogleAuthProvider.h @@ -25,6 +25,11 @@ NS_ASSUME_NONNULL_BEGIN */ extern NSString *const FIRGoogleAuthProviderID NS_SWIFT_NAME(GoogleAuthProviderID); +/** + @brief A string constant identifying the Google sign-in method. + */ +extern NSString *const _Nonnull FIRGoogleAuthSignInMethod NS_SWIFT_NAME(GoogleAuthSignInMethod); + /** @class FIRGoogleAuthProvider @brief Utility class for constructing Google Sign In credentials. */ diff --git a/Firebase/Auth/Source/Public/FIRPhoneAuthProvider.h b/Firebase/Auth/Source/Public/FIRPhoneAuthProvider.h index 34db683..bd68e84 100644 --- a/Firebase/Auth/Source/Public/FIRPhoneAuthProvider.h +++ b/Firebase/Auth/Source/Public/FIRPhoneAuthProvider.h @@ -27,6 +27,12 @@ NS_ASSUME_NONNULL_BEGIN */ extern NSString *const FIRPhoneAuthProviderID NS_SWIFT_NAME(PhoneAuthProviderID); +/** @var FIRPhoneAuthProviderID + @brief A string constant identifying the phone sign-in method. + */ +extern NSString *const _Nonnull FIRPhoneAuthSignInMethod NS_SWIFT_NAME(PhoneAuthSignInMethod); + + /** @typedef FIRVerificationResultCallback @brief The type of block invoked when a request to send a verification code has finished. diff --git a/Firebase/Auth/Source/Public/FIRTwitterAuthProvider.h b/Firebase/Auth/Source/Public/FIRTwitterAuthProvider.h index 2ef32f7..a0d1166 100644 --- a/Firebase/Auth/Source/Public/FIRTwitterAuthProvider.h +++ b/Firebase/Auth/Source/Public/FIRTwitterAuthProvider.h @@ -25,6 +25,12 @@ NS_ASSUME_NONNULL_BEGIN */ extern NSString *const FIRTwitterAuthProviderID NS_SWIFT_NAME(TwitterAuthProviderID); +/** + @brief A string constant identifying the Twitter sign-in method. + */ +extern NSString *const _Nonnull FIRTwitterAuthSignInMethod NS_SWIFT_NAME(TwitterAuthSignInMethod); + + /** @class FIRTwitterAuthProvider @brief Utility class for constructing Twitter credentials. */ diff --git a/Firebase/Core/FIROptions.m b/Firebase/Core/FIROptions.m index 384ef21..b53fa52 100644 --- a/Firebase/Core/FIROptions.m +++ b/Firebase/Core/FIROptions.m @@ -43,7 +43,7 @@ NSString *const kFIRIsSignInEnabled = @"IS_SIGNIN_ENABLED"; NSString *const kFIRLibraryVersionID = @"4" // Major version (one or more digits) @"00" // Minor version (exactly 2 digits) - @"17" // Build number (exactly 2 digits) + @"18" // Build number (exactly 2 digits) @"000"; // Fixed "000" // Plist file name. NSString *const kServiceInfoFileName = @"GoogleService-Info"; diff --git a/FirebaseAuth.podspec b/FirebaseAuth.podspec index 1f18d97..549c720 100644 --- a/FirebaseAuth.podspec +++ b/FirebaseAuth.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseAuth' - s.version = '4.4.4' + s.version = '4.5.0' s.summary = 'The official iOS client for Firebase Authentication' s.description = <<-DESC diff --git a/FirebaseCore.podspec b/FirebaseCore.podspec index eec8605..583dc39 100644 --- a/FirebaseCore.podspec +++ b/FirebaseCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCore' - s.version = '4.0.17' + s.version = '4.0.18' s.summary = 'Firebase Core for iOS' s.description = <<-DESC diff --git a/FirebaseFirestore.podspec b/FirebaseFirestore.podspec index 896dcfb..9af8ec9 100644 --- a/FirebaseFirestore.podspec +++ b/FirebaseFirestore.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = 'FirebaseFirestore' - s.version = '0.10.3' + s.version = '0.10.4' s.summary = 'Google Cloud Firestore for iOS' s.description = <<-DESC diff --git a/FirebaseFunctions.podspec b/FirebaseFunctions.podspec new file mode 100644 index 0000000..caf3023 --- /dev/null +++ b/FirebaseFunctions.podspec @@ -0,0 +1,33 @@ +# +# Be sure to run `pod lib lint FirebaseFunctions.podspec' to ensure this is a +# valid spec before submitting. +# +# Any lines starting with a # are optional, but their use is encouraged +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# + +Pod::Spec.new do |s| + s.name = 'FirebaseFunctions' + s.version = '1.0.0' + s.summary = 'Cloud Functions for Firebase iOS SDK.' + + s.description = <<-DESC +iOS SDK for Cloud Functions for Firebase. + DESC + + s.homepage = 'https://developers.google.com/' + s.authors = 'Google, Inc.' + s.source = { :git => 'https://github.com/TBD/FirebaseFunctions.git', :tag => s.version.to_s } + + s.ios.deployment_target = '8.0' + + s.cocoapods_version = '>= 1.4.0' + s.static_framework = true + s.prefix_header_file = false + + s.source_files = 'Functions/FirebaseFunctions/**/*' + s.public_header_files = 'Functions/FirebaseFunctions/Public/*.h' + + s.dependency 'FirebaseCore', '~> 4.0' + s.dependency 'GTMSessionFetcher/Core', '~> 1.1' +end diff --git a/Firestore/Example/Podfile b/Firestore/Example/Podfile index 71afc2b..f7527d2 100644 --- a/Firestore/Example/Podfile +++ b/Firestore/Example/Podfile @@ -1,7 +1,7 @@ # The next line is the forcing function for the Firebase pod. The Firebase # version's subspecs should depend on the component versions in their # corresponding podspec's. -pod 'Firebase/Core', '4.10.1' +pod 'Firebase/Core', '4.11.0' use_frameworks! platform :ios, '8.0' diff --git a/Firestore/Source/Util/FSTDispatchQueue.mm b/Firestore/Source/Util/FSTDispatchQueue.mm index 15d6e7b..52482df 100644 --- a/Firestore/Source/Util/FSTDispatchQueue.mm +++ b/Firestore/Source/Util/FSTDispatchQueue.mm @@ -230,8 +230,11 @@ NS_ASSUME_NONNULL_BEGIN block:(void (^)(void))block { // While not necessarily harmful, we currently don't expect to have multiple callbacks with the // same timerID in the queue, so defensively reject them. - FSTAssert(![self containsDelayedCallbackWithTimerID:timerID], - @"Attempted to schedule multiple callbacks with id %ld", (unsigned long)timerID); + // TODO(b/74749605): If a user change happens while offline we can end up with multiple backoff + // callbacks in the dispatch queue. This is non-harmful so I'm just disabling the assert until we + // get that cleaned up. + // FSTAssert(![self containsDelayedCallbackWithTimerID:timerID], + // @"Attempted to schedule multiple callbacks with id %ld", (unsigned long)timerID); FSTDelayedCallback *delayedCallback = [FSTDelayedCallback createAndScheduleWithQueue:self timerID:timerID delay:delay diff --git a/Functions/.clang-format b/Functions/.clang-format new file mode 100644 index 0000000..fb06210 --- /dev/null +++ b/Functions/.clang-format @@ -0,0 +1,7 @@ +BasedOnStyle: Google +ColumnLimit: 100 +BinPackParameters: false +AllowAllParametersOfDeclarationOnNextLine: true +ObjCSpaceBeforeProtocolList: false +SpacesInContainerLiterals: true +PointerAlignment: Right diff --git a/Functions/Backend/index.js b/Functions/Backend/index.js new file mode 100644 index 0000000..0245f1b --- /dev/null +++ b/Functions/Backend/index.js @@ -0,0 +1,95 @@ +const assert = require('assert'); +const functions = require('firebase-functions'); + +exports.dataTest = functions.https.onRequest((request, response) => { + assert.deepEqual(request.body, { + data: { + bool: true, + int: 2, + long: { + value: '3', + '@type': 'type.googleapis.com/google.protobuf.Int64Value', + }, + string: 'four', + array: [5, 6], + 'null': null, + } + }); + response.send({ + data: { + message: 'stub response', + code: 42, + long: { + value: '420', + '@type': 'type.googleapis.com/google.protobuf.Int64Value', + }, + } + }); +}); + +exports.scalarTest = functions.https.onRequest((request, response) => { + assert.deepEqual(request.body, { data: 17 }); + response.send({ data: 76 }); +}); + +exports.tokenTest = functions.https.onRequest((request, response) => { + assert.equal('Bearer token', request.get('Authorization')); + assert.deepEqual(request.body, { data: {} }); + response.send({ data: {} }); +}); + +exports.instanceIdTest = functions.https.onRequest((request, response) => { + assert.equal(request.get('Firebase-Instance-ID-Token'), 'iid'); + assert.deepEqual(request.body, { data: {} }); + response.send({ data: {} }); +}); + +exports.nullTest = functions.https.onRequest((request, response) => { + assert.deepEqual(request.body, { data: null }); + response.send({ data: null }); +}); + +exports.missingResultTest = functions.https.onRequest((request, response) => { + assert.deepEqual(request.body, { data: null }); + response.send({}); +}); + +exports.unhandledErrorTest = functions.https.onRequest((request, response) => { + // Fail in a way that the client shouldn't see. + throw 'nope'; +}); + +exports.unknownErrorTest = functions.https.onRequest((request, response) => { + // Send an http error with a body with an explicit code. + response.status(400).send({ + error: { + status: 'THIS_IS_NOT_VALID', + message: 'this should be ignored', + }, + }); +}); + +exports.explicitErrorTest = functions.https.onRequest((request, response) => { + // Send an http error with a body with an explicit code. + // Note that eventually the SDK will have a helper to automatically return + // the appropriate http status code for an error. + response.status(400).send({ + error: { + status: 'OUT_OF_RANGE', + message: 'explicit nope', + details: { + start: 10, + end: 20, + long: { + value: '30', + '@type': 'type.googleapis.com/google.protobuf.Int64Value', + }, + }, + }, + }); +}); + +exports.httpErrorTest = functions.https.onRequest((request, response) => { + // Send an http error with no body. + response.status(400).send(); +}); diff --git a/Functions/Backend/package.json b/Functions/Backend/package.json new file mode 100644 index 0000000..c227bb1 --- /dev/null +++ b/Functions/Backend/package.json @@ -0,0 +1,12 @@ +{ + "name": "functions", + "description": "Cloud Functions for Firebase", + "dependencies": { + "firebase-admin": "~4.2.1", + "firebase-functions": "^0.5.7" + }, + "private": true, + "devDependencies": { + "@google-cloud/functions-emulator": "^1.0.0-alpha.21" + } +} diff --git a/Functions/Backend/start.sh b/Functions/Backend/start.sh new file mode 100755 index 0000000..b09675a --- /dev/null +++ b/Functions/Backend/start.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +# Copyright 2018 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. + +# Sets up a project with the functions CLI and starts a backend to run +# integration tests against. + +set -e + +# Get the absolute path to the directory containing this script. +SCRIPT_DIR="$(cd $(dirname ${BASH_SOURCE[0]}) && pwd)" +TEMP_DIR="$(mktemp -d -t firebase-functions)" +echo "Creating functions in ${TEMP_DIR}" + +# Set up the functions directory. +cp "${SCRIPT_DIR}/index.js" "${TEMP_DIR}/" +cp "${SCRIPT_DIR}/package.json" "${TEMP_DIR}/" +cd "${TEMP_DIR}" +npm install + +# Start the server. +FUNCTIONS_BIN="./node_modules/.bin/functions" +"${FUNCTIONS_BIN}" config set projectId functions-integration-test +"${FUNCTIONS_BIN}" config set supervisorPort 5005 +"${FUNCTIONS_BIN}" config set region us-central1 +"${FUNCTIONS_BIN}" config set verbose true +"${FUNCTIONS_BIN}" restart +"${FUNCTIONS_BIN}" deploy dataTest --trigger-http +"${FUNCTIONS_BIN}" deploy scalarTest --trigger-http +"${FUNCTIONS_BIN}" deploy tokenTest --trigger-http +"${FUNCTIONS_BIN}" deploy instanceIdTest --trigger-http +"${FUNCTIONS_BIN}" deploy nullTest --trigger-http +"${FUNCTIONS_BIN}" deploy missingResultTest --trigger-http +"${FUNCTIONS_BIN}" deploy unhandledErrorTest --trigger-http +"${FUNCTIONS_BIN}" deploy unknownErrorTest --trigger-http +"${FUNCTIONS_BIN}" deploy explicitErrorTest --trigger-http +"${FUNCTIONS_BIN}" deploy httpErrorTest --trigger-http + +# Wait for the user to tell us to stop the server. +echo "Functions emulator now running in ${TEMP_DIR}." +read -n 1 -p "*** Press any key to stop the server. ***" +echo "\nStopping the emulator..." +"${FUNCTIONS_BIN}" stop diff --git a/Functions/CHANGELOG.md b/Functions/CHANGELOG.md new file mode 100644 index 0000000..d2ea864 --- /dev/null +++ b/Functions/CHANGELOG.md @@ -0,0 +1,2 @@ +# v1.0.0 +- Initial public release
\ No newline at end of file diff --git a/Functions/Example/FirebaseFunctions.xcodeproj/project.pbxproj b/Functions/Example/FirebaseFunctions.xcodeproj/project.pbxproj new file mode 100644 index 0000000..d70f4b5 --- /dev/null +++ b/Functions/Example/FirebaseFunctions.xcodeproj/project.pbxproj @@ -0,0 +1,848 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; + 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; }; + 6003F592195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; + 6003F598195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F596195388D20070C39A /* InfoPlist.strings */; }; + 6003F59A195388D20070C39A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F599195388D20070C39A /* main.m */; }; + 6003F59E195388D20070C39A /* FIRAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F59D195388D20070C39A /* FIRAppDelegate.m */; }; + 6003F5A7195388D20070C39A /* FIRViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5A6195388D20070C39A /* FIRViewController.m */; }; + 6003F5A9195388D20070C39A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5A8195388D20070C39A /* Images.xcassets */; }; + 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; }; + 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; + 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; + 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; }; + 6003F5BC195388D20070C39A /* FIRFunctionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5BB195388D20070C39A /* FIRFunctionsTests.m */; }; + 633D50991C0FDF68085030B5 /* Pods_FirebaseFunctions_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B954281766FFD7C7C9970E9B /* Pods_FirebaseFunctions_Example.framework */; }; + 6358342A9F1AF395C6C52471 /* Pods_FirebaseFunctions_IntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0A4C1781DC97515D32B33DBD /* Pods_FirebaseFunctions_IntegrationTests.framework */; }; + 71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */; }; + 78F883E44C88CC18414F3DAF /* Pods_FirebaseFunctions_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A19679D6C21E3206B4A13697 /* Pods_FirebaseFunctions_Tests.framework */; }; + 7C58B03A1F1441F0005ED954 /* FUNSerializerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7C58B0391F1441F0005ED954 /* FUNSerializerTests.m */; }; + 7CBFAA82205702AB00A65866 /* FIRIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7C9BFD3F1F10A12F001A19ED /* FIRIntegrationTests.m */; }; + 7CBFAA86205702AB00A65866 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; }; + 7CBFAA87205702AB00A65866 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; + 7CBFAA88205702AB00A65866 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; + 7CBFAA89205702AB00A65866 /* Pods_FirebaseFunctions_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A19679D6C21E3206B4A13697 /* Pods_FirebaseFunctions_Tests.framework */; }; + 7CBFAA8B205702AB00A65866 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; }; + 7CF3BEC21F97EE2F00B16B6E /* FUNFakeInstanceID.m in Sources */ = {isa = PBXBuildFile; fileRef = 7CF3BEC01F97EE2F00B16B6E /* FUNFakeInstanceID.m */; }; + 7CF563091F1FE70600FEE1F4 /* FUNFakeApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 7CF563081F1FE70600FEE1F4 /* FUNFakeApp.m */; }; + 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 6003F5B3195388D20070C39A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6003F582195388D10070C39A /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6003F589195388D20070C39A; + remoteInfo = FirebaseFunctions; + }; + 7CBFAA7F205702AB00A65866 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 6003F582195388D10070C39A /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6003F589195388D20070C39A; + remoteInfo = FirebaseFunctions; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 0A4C1781DC97515D32B33DBD /* Pods_FirebaseFunctions_IntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FirebaseFunctions_IntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 501CFEC7487BD4C12FCEAB03 /* Pods-FirebaseFunctions_IntegrationTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseFunctions_IntegrationTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseFunctions_IntegrationTests/Pods-FirebaseFunctions_IntegrationTests.debug.xcconfig"; sourceTree = "<group>"; }; + 59519EC771AF4770F85B6534 /* Pods-FirebaseFunctions_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseFunctions_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseFunctions_Example/Pods-FirebaseFunctions_Example.debug.xcconfig"; sourceTree = "<group>"; }; + 5B43C813C653DE3455952E9B /* Pods-FirebaseFunctions_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseFunctions_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseFunctions_Tests/Pods-FirebaseFunctions_Tests.release.xcconfig"; sourceTree = "<group>"; }; + 6003F58A195388D20070C39A /* FirebaseFunctions_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FirebaseFunctions_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; + 6003F591195388D20070C39A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 6003F595195388D20070C39A /* FirebaseFunctions-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "FirebaseFunctions-Info.plist"; sourceTree = "<group>"; }; + 6003F597195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + 6003F599195388D20070C39A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; }; + 6003F59C195388D20070C39A /* FIRAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FIRAppDelegate.h; sourceTree = "<group>"; }; + 6003F59D195388D20070C39A /* FIRAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRAppDelegate.m; sourceTree = "<group>"; }; + 6003F5A5195388D20070C39A /* FIRViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FIRViewController.h; sourceTree = "<group>"; }; + 6003F5A6195388D20070C39A /* FIRViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRViewController.m; sourceTree = "<group>"; }; + 6003F5A8195388D20070C39A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; }; + 6003F5AE195388D20070C39A /* FirebaseFunctions_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FirebaseFunctions_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 6003F5AF195388D20070C39A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; + 6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = "<group>"; }; + 6003F5B9195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; }; + 6003F5BB195388D20070C39A /* FIRFunctionsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRFunctionsTests.m; sourceTree = "<group>"; }; + 71719F9E1E33DC2100824A3D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; + 7BEA793625C8DE7C8EC60006 /* FirebaseFunctions.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = FirebaseFunctions.podspec; path = ../FirebaseFunctions.podspec; sourceTree = "<group>"; }; + 7C58B0391F1441F0005ED954 /* FUNSerializerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FUNSerializerTests.m; sourceTree = "<group>"; }; + 7C9BFD3F1F10A12F001A19ED /* FIRIntegrationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIRIntegrationTests.m; sourceTree = "<group>"; }; + 7CBFAA91205702AB00A65866 /* FirebaseFunctions_IntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FirebaseFunctions_IntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 7CBFAA92205702AC00A65866 /* FirebaseFunctions_Tests copy-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "FirebaseFunctions_Tests copy-Info.plist"; path = "/Users/klimt/src/firebase-ios-sdk/Functions/Example/FirebaseFunctions_Tests copy-Info.plist"; sourceTree = "<absolute>"; }; + 7CF3BEC01F97EE2F00B16B6E /* FUNFakeInstanceID.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FUNFakeInstanceID.m; sourceTree = "<group>"; }; + 7CF3BEC11F97EE2F00B16B6E /* FUNFakeInstanceID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FUNFakeInstanceID.h; sourceTree = "<group>"; }; + 7CF563081F1FE70600FEE1F4 /* FUNFakeApp.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FUNFakeApp.m; sourceTree = "<group>"; }; + 7CF5630A1F1FE76700FEE1F4 /* FUNFakeApp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FUNFakeApp.h; sourceTree = "<group>"; }; + 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; + A19679D6C21E3206B4A13697 /* Pods_FirebaseFunctions_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FirebaseFunctions_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B954281766FFD7C7C9970E9B /* Pods_FirebaseFunctions_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FirebaseFunctions_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E0A8D570636E99E7C3396DF8 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; }; + E2E6249788F1618471335BE7 /* Pods-FirebaseFunctions_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseFunctions_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseFunctions_Example/Pods-FirebaseFunctions_Example.release.xcconfig"; sourceTree = "<group>"; }; + F1F2A7C03C10A3A03F9502B8 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; }; + F840F9EDCB950E7509527AB7 /* Pods-FirebaseFunctions_IntegrationTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseFunctions_IntegrationTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseFunctions_IntegrationTests/Pods-FirebaseFunctions_IntegrationTests.release.xcconfig"; sourceTree = "<group>"; }; + FFE894EDD87F1EF24F0ED8DD /* Pods-FirebaseFunctions_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseFunctions_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseFunctions_Tests/Pods-FirebaseFunctions_Tests.debug.xcconfig"; sourceTree = "<group>"; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 6003F587195388D20070C39A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */, + 6003F592195388D20070C39A /* UIKit.framework in Frameworks */, + 6003F58E195388D20070C39A /* Foundation.framework in Frameworks */, + 633D50991C0FDF68085030B5 /* Pods_FirebaseFunctions_Example.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6003F5AB195388D20070C39A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */, + 6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */, + 6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */, + 78F883E44C88CC18414F3DAF /* Pods_FirebaseFunctions_Tests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7CBFAA85205702AB00A65866 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 7CBFAA86205702AB00A65866 /* XCTest.framework in Frameworks */, + 7CBFAA87205702AB00A65866 /* UIKit.framework in Frameworks */, + 7CBFAA88205702AB00A65866 /* Foundation.framework in Frameworks */, + 7CBFAA89205702AB00A65866 /* Pods_FirebaseFunctions_Tests.framework in Frameworks */, + 6358342A9F1AF395C6C52471 /* Pods_FirebaseFunctions_IntegrationTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 004DC60D1A62294B06E83453 /* Pods */ = { + isa = PBXGroup; + children = ( + 59519EC771AF4770F85B6534 /* Pods-FirebaseFunctions_Example.debug.xcconfig */, + E2E6249788F1618471335BE7 /* Pods-FirebaseFunctions_Example.release.xcconfig */, + FFE894EDD87F1EF24F0ED8DD /* Pods-FirebaseFunctions_Tests.debug.xcconfig */, + 5B43C813C653DE3455952E9B /* Pods-FirebaseFunctions_Tests.release.xcconfig */, + 501CFEC7487BD4C12FCEAB03 /* Pods-FirebaseFunctions_IntegrationTests.debug.xcconfig */, + F840F9EDCB950E7509527AB7 /* Pods-FirebaseFunctions_IntegrationTests.release.xcconfig */, + ); + name = Pods; + sourceTree = "<group>"; + }; + 6003F581195388D10070C39A = { + isa = PBXGroup; + children = ( + 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */, + 6003F593195388D20070C39A /* Example for FirebaseFunctions */, + 7C70065B20572C1B007A5573 /* TestUtils */, + 7CFC6F8720570365005630C2 /* IntegrationTests */, + 6003F5B5195388D20070C39A /* Tests */, + 6003F58C195388D20070C39A /* Frameworks */, + 6003F58B195388D20070C39A /* Products */, + 004DC60D1A62294B06E83453 /* Pods */, + 7CBFAA92205702AC00A65866 /* FirebaseFunctions_Tests copy-Info.plist */, + ); + sourceTree = "<group>"; + }; + 6003F58B195388D20070C39A /* Products */ = { + isa = PBXGroup; + children = ( + 6003F58A195388D20070C39A /* FirebaseFunctions_Example.app */, + 6003F5AE195388D20070C39A /* FirebaseFunctions_Tests.xctest */, + 7CBFAA91205702AB00A65866 /* FirebaseFunctions_IntegrationTests.xctest */, + ); + name = Products; + sourceTree = "<group>"; + }; + 6003F58C195388D20070C39A /* Frameworks */ = { + isa = PBXGroup; + children = ( + 6003F58D195388D20070C39A /* Foundation.framework */, + 6003F58F195388D20070C39A /* CoreGraphics.framework */, + 6003F591195388D20070C39A /* UIKit.framework */, + 6003F5AF195388D20070C39A /* XCTest.framework */, + B954281766FFD7C7C9970E9B /* Pods_FirebaseFunctions_Example.framework */, + A19679D6C21E3206B4A13697 /* Pods_FirebaseFunctions_Tests.framework */, + 0A4C1781DC97515D32B33DBD /* Pods_FirebaseFunctions_IntegrationTests.framework */, + ); + name = Frameworks; + sourceTree = "<group>"; + }; + 6003F593195388D20070C39A /* Example for FirebaseFunctions */ = { + isa = PBXGroup; + children = ( + 6003F59C195388D20070C39A /* FIRAppDelegate.h */, + 6003F59D195388D20070C39A /* FIRAppDelegate.m */, + 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */, + 6003F5A5195388D20070C39A /* FIRViewController.h */, + 6003F5A6195388D20070C39A /* FIRViewController.m */, + 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */, + 6003F5A8195388D20070C39A /* Images.xcassets */, + 6003F594195388D20070C39A /* Supporting Files */, + ); + name = "Example for FirebaseFunctions"; + path = FirebaseFunctions; + sourceTree = "<group>"; + }; + 6003F594195388D20070C39A /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 6003F595195388D20070C39A /* FirebaseFunctions-Info.plist */, + 6003F596195388D20070C39A /* InfoPlist.strings */, + 6003F599195388D20070C39A /* main.m */, + ); + name = "Supporting Files"; + sourceTree = "<group>"; + }; + 6003F5B5195388D20070C39A /* Tests */ = { + isa = PBXGroup; + children = ( + 6003F5BB195388D20070C39A /* FIRFunctionsTests.m */, + 7C58B0391F1441F0005ED954 /* FUNSerializerTests.m */, + 6003F5B6195388D20070C39A /* Supporting Files */, + ); + path = Tests; + sourceTree = "<group>"; + }; + 6003F5B6195388D20070C39A /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 6003F5B7195388D20070C39A /* Tests-Info.plist */, + 6003F5B8195388D20070C39A /* InfoPlist.strings */, + ); + name = "Supporting Files"; + sourceTree = "<group>"; + }; + 60FF7A9C1954A5C5007DD14C /* Podspec Metadata */ = { + isa = PBXGroup; + children = ( + 7BEA793625C8DE7C8EC60006 /* FirebaseFunctions.podspec */, + E0A8D570636E99E7C3396DF8 /* README.md */, + F1F2A7C03C10A3A03F9502B8 /* LICENSE */, + ); + name = "Podspec Metadata"; + sourceTree = "<group>"; + }; + 7C70065B20572C1B007A5573 /* TestUtils */ = { + isa = PBXGroup; + children = ( + 7CF5630A1F1FE76700FEE1F4 /* FUNFakeApp.h */, + 7CF563081F1FE70600FEE1F4 /* FUNFakeApp.m */, + 7CF3BEC11F97EE2F00B16B6E /* FUNFakeInstanceID.h */, + 7CF3BEC01F97EE2F00B16B6E /* FUNFakeInstanceID.m */, + ); + path = TestUtils; + sourceTree = "<group>"; + }; + 7CFC6F8720570365005630C2 /* IntegrationTests */ = { + isa = PBXGroup; + children = ( + 7C9BFD3F1F10A12F001A19ED /* FIRIntegrationTests.m */, + ); + path = IntegrationTests; + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 6003F589195388D20070C39A /* FirebaseFunctions_Example */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "FirebaseFunctions_Example" */; + buildPhases = ( + C1B1E870DA1D732EC2F1F946 /* [CP] Check Pods Manifest.lock */, + 6003F586195388D20070C39A /* Sources */, + 6003F587195388D20070C39A /* Frameworks */, + 6003F588195388D20070C39A /* Resources */, + BB7A7FC40453827CC7541BB7 /* [CP] Embed Pods Frameworks */, + 0409BC62B2C369D14416E480 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = FirebaseFunctions_Example; + productName = FirebaseFunctions; + productReference = 6003F58A195388D20070C39A /* FirebaseFunctions_Example.app */; + productType = "com.apple.product-type.application"; + }; + 6003F5AD195388D20070C39A /* FirebaseFunctions_Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "FirebaseFunctions_Tests" */; + buildPhases = ( + D9E332DA9E27ECA02C2B45D2 /* [CP] Check Pods Manifest.lock */, + 6003F5AA195388D20070C39A /* Sources */, + 6003F5AB195388D20070C39A /* Frameworks */, + 6003F5AC195388D20070C39A /* Resources */, + 3B7914C40161367B84BC9E52 /* [CP] Embed Pods Frameworks */, + F0A35EE9B43049BA133AD88F /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 6003F5B4195388D20070C39A /* PBXTargetDependency */, + ); + name = FirebaseFunctions_Tests; + productName = FirebaseFunctionsTests; + productReference = 6003F5AE195388D20070C39A /* FirebaseFunctions_Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 7CBFAA7D205702AB00A65866 /* FirebaseFunctions_IntegrationTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7CBFAA8E205702AB00A65866 /* Build configuration list for PBXNativeTarget "FirebaseFunctions_IntegrationTests" */; + buildPhases = ( + 7CBFAA80205702AB00A65866 /* [CP] Check Pods Manifest.lock */, + 7CBFAA81205702AB00A65866 /* Sources */, + 7CBFAA85205702AB00A65866 /* Frameworks */, + 7CBFAA8A205702AB00A65866 /* Resources */, + 7CBFAA8C205702AB00A65866 /* [CP] Embed Pods Frameworks */, + 7CBFAA8D205702AB00A65866 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 7CBFAA7E205702AB00A65866 /* PBXTargetDependency */, + ); + name = FirebaseFunctions_IntegrationTests; + productName = FirebaseFunctionsTests; + productReference = 7CBFAA91205702AB00A65866 /* FirebaseFunctions_IntegrationTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 6003F582195388D10070C39A /* Project object */ = { + isa = PBXProject; + attributes = { + CLASSPREFIX = FIR; + LastUpgradeCheck = 0720; + ORGANIZATIONNAME = "Google, Inc."; + TargetAttributes = { + 6003F5AD195388D20070C39A = { + TestTargetID = 6003F589195388D20070C39A; + }; + }; + }; + buildConfigurationList = 6003F585195388D10070C39A /* Build configuration list for PBXProject "FirebaseFunctions" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 6003F581195388D10070C39A; + productRefGroup = 6003F58B195388D20070C39A /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 6003F589195388D20070C39A /* FirebaseFunctions_Example */, + 6003F5AD195388D20070C39A /* FirebaseFunctions_Tests */, + 7CBFAA7D205702AB00A65866 /* FirebaseFunctions_IntegrationTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 6003F588195388D20070C39A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */, + 71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */, + 6003F5A9195388D20070C39A /* Images.xcassets in Resources */, + 6003F598195388D20070C39A /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6003F5AC195388D20070C39A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7CBFAA8A205702AB00A65866 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7CBFAA8B205702AB00A65866 /* InfoPlist.strings in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 0409BC62B2C369D14416E480 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_Example/Pods-FirebaseFunctions_Example-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3B7914C40161367B84BC9E52 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_Tests/Pods-FirebaseFunctions_Tests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 7CBFAA80205702AB00A65866 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-FirebaseFunctions_IntegrationTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 7CBFAA8C205702AB00A65866 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_IntegrationTests/Pods-FirebaseFunctions_IntegrationTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 7CBFAA8D205702AB00A65866 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_IntegrationTests/Pods-FirebaseFunctions_IntegrationTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + BB7A7FC40453827CC7541BB7 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_Example/Pods-FirebaseFunctions_Example-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework", + "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac/GoogleToolboxForMac.framework", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_Example/Pods-FirebaseFunctions_Example-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + C1B1E870DA1D732EC2F1F946 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-FirebaseFunctions_Example-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + D9E332DA9E27ECA02C2B45D2 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-FirebaseFunctions_Tests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + F0A35EE9B43049BA133AD88F /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_Tests/Pods-FirebaseFunctions_Tests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 6003F586195388D20070C39A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7CF563091F1FE70600FEE1F4 /* FUNFakeApp.m in Sources */, + 6003F59E195388D20070C39A /* FIRAppDelegate.m in Sources */, + 6003F5A7195388D20070C39A /* FIRViewController.m in Sources */, + 7CF3BEC21F97EE2F00B16B6E /* FUNFakeInstanceID.m in Sources */, + 6003F59A195388D20070C39A /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 6003F5AA195388D20070C39A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7C58B03A1F1441F0005ED954 /* FUNSerializerTests.m in Sources */, + 6003F5BC195388D20070C39A /* FIRFunctionsTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7CBFAA81205702AB00A65866 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7CBFAA82205702AB00A65866 /* FIRIntegrationTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 6003F5B4195388D20070C39A /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 6003F589195388D20070C39A /* FirebaseFunctions_Example */; + targetProxy = 6003F5B3195388D20070C39A /* PBXContainerItemProxy */; + }; + 7CBFAA7E205702AB00A65866 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 6003F589195388D20070C39A /* FirebaseFunctions_Example */; + targetProxy = 7CBFAA7F205702AB00A65866 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 6003F596195388D20070C39A /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 6003F597195388D20070C39A /* en */, + ); + name = InfoPlist.strings; + sourceTree = "<group>"; + }; + 6003F5B8195388D20070C39A /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 6003F5B9195388D20070C39A /* en */, + ); + name = InfoPlist.strings; + sourceTree = "<group>"; + }; + 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 71719F9E1E33DC2100824A3D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = "<group>"; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 6003F5BD195388D20070C39A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.3; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 6003F5BE195388D20070C39A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.3; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 6003F5C0195388D20070C39A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 59519EC771AF4770F85B6534 /* Pods-FirebaseFunctions_Example.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + GCC_PRECOMPILE_PREFIX_HEADER = NO; + INFOPLIST_FILE = "FirebaseFunctions/FirebaseFunctions-Info.plist"; + MODULE_NAME = ExampleApp; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 6003F5C1195388D20070C39A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E2E6249788F1618471335BE7 /* Pods-FirebaseFunctions_Example.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + GCC_PRECOMPILE_PREFIX_HEADER = NO; + INFOPLIST_FILE = "FirebaseFunctions/FirebaseFunctions-Info.plist"; + MODULE_NAME = ExampleApp; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME)"; + WRAPPER_EXTENSION = app; + }; + name = Release; + }; + 6003F5C3195388D20070C39A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = FFE894EDD87F1EF24F0ED8DD /* Pods-FirebaseFunctions_Tests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + "$(DEVELOPER_FRAMEWORKS_DIR)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + HEADER_SEARCH_PATHS = "$(SRCROOT)/../FirebaseFunctions"; + INFOPLIST_FILE = "Tests/Tests-Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FirebaseFunctions_Example.app/FirebaseFunctions_Example"; + WRAPPER_EXTENSION = xctest; + }; + name = Debug; + }; + 6003F5C4195388D20070C39A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5B43C813C653DE3455952E9B /* Pods-FirebaseFunctions_Tests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + "$(DEVELOPER_FRAMEWORKS_DIR)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = NO; + HEADER_SEARCH_PATHS = "$(SRCROOT)/../FirebaseFunctions"; + INFOPLIST_FILE = "Tests/Tests-Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FirebaseFunctions_Example.app/FirebaseFunctions_Example"; + WRAPPER_EXTENSION = xctest; + }; + name = Release; + }; + 7CBFAA8F205702AB00A65866 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 501CFEC7487BD4C12FCEAB03 /* Pods-FirebaseFunctions_IntegrationTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + "$(DEVELOPER_FRAMEWORKS_DIR)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + HEADER_SEARCH_PATHS = "$(SRCROOT)/../FirebaseFunctions"; + INFOPLIST_FILE = "IntegrationTests/IntegrationTests-Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FirebaseFunctions_Example.app/FirebaseFunctions_Example"; + WRAPPER_EXTENSION = xctest; + }; + name = Debug; + }; + 7CBFAA90205702AB00A65866 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F840F9EDCB950E7509527AB7 /* Pods-FirebaseFunctions_IntegrationTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + "$(DEVELOPER_FRAMEWORKS_DIR)", + ); + GCC_PRECOMPILE_PREFIX_HEADER = NO; + HEADER_SEARCH_PATHS = "$(SRCROOT)/../FirebaseFunctions"; + INFOPLIST_FILE = "IntegrationTests/IntegrationTests-Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FirebaseFunctions_Example.app/FirebaseFunctions_Example"; + WRAPPER_EXTENSION = xctest; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 6003F585195388D10070C39A /* Build configuration list for PBXProject "FirebaseFunctions" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6003F5BD195388D20070C39A /* Debug */, + 6003F5BE195388D20070C39A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "FirebaseFunctions_Example" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6003F5C0195388D20070C39A /* Debug */, + 6003F5C1195388D20070C39A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "FirebaseFunctions_Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6003F5C3195388D20070C39A /* Debug */, + 6003F5C4195388D20070C39A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7CBFAA8E205702AB00A65866 /* Build configuration list for PBXNativeTarget "FirebaseFunctions_IntegrationTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7CBFAA8F205702AB00A65866 /* Debug */, + 7CBFAA90205702AB00A65866 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 6003F582195388D10070C39A /* Project object */; +} diff --git a/Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions-Example.xcscheme b/Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions-Example.xcscheme new file mode 100644 index 0000000..b9983e8 --- /dev/null +++ b/Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions-Example.xcscheme @@ -0,0 +1,113 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "0720" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "6003F589195388D20070C39A" + BuildableName = "FirebaseFunctions_Example.app" + BlueprintName = "FirebaseFunctions_Example" + ReferencedContainer = "container:FirebaseFunctions.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" + shouldUseLaunchSchemeArgsEnv = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "6003F5AD195388D20070C39A" + BuildableName = "FirebaseFunctions_Tests.xctest" + BlueprintName = "FirebaseFunctions_Tests" + ReferencedContainer = "container:FirebaseFunctions.xcodeproj"> + </BuildableReference> + </TestableReference> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "7CBFAA7D205702AB00A65866" + BuildableName = "FirebaseFunctions_IntegrationTests.xctest" + BlueprintName = "FirebaseFunctions_IntegrationTests" + ReferencedContainer = "container:FirebaseFunctions.xcodeproj"> + </BuildableReference> + </TestableReference> + </Testables> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "6003F589195388D20070C39A" + BuildableName = "FirebaseFunctions_Example.app" + BlueprintName = "FirebaseFunctions_Example" + ReferencedContainer = "container:FirebaseFunctions.xcodeproj"> + </BuildableReference> + </MacroExpansion> + <AdditionalOptions> + </AdditionalOptions> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + <BuildableProductRunnable + runnableDebuggingMode = "0"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "6003F589195388D20070C39A" + BuildableName = "FirebaseFunctions_Example.app" + BlueprintName = "FirebaseFunctions_Example" + ReferencedContainer = "container:FirebaseFunctions.xcodeproj"> + </BuildableReference> + </BuildableProductRunnable> + <AdditionalOptions> + </AdditionalOptions> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <BuildableProductRunnable + runnableDebuggingMode = "0"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "6003F589195388D20070C39A" + BuildableName = "FirebaseFunctions_Example.app" + BlueprintName = "FirebaseFunctions_Example" + ReferencedContainer = "container:FirebaseFunctions.xcodeproj"> + </BuildableReference> + </BuildableProductRunnable> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions_IntegrationTests.xcscheme b/Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions_IntegrationTests.xcscheme new file mode 100644 index 0000000..46914fb --- /dev/null +++ b/Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions_IntegrationTests.xcscheme @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "0930" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "7CBFAA7D205702AB00A65866" + BuildableName = "FirebaseFunctions_IntegrationTests.xctest" + BlueprintName = "FirebaseFunctions_IntegrationTests" + ReferencedContainer = "container:FirebaseFunctions.xcodeproj"> + </BuildableReference> + </TestableReference> + </Testables> + <AdditionalOptions> + </AdditionalOptions> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + <AdditionalOptions> + </AdditionalOptions> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions_Tests.xcscheme b/Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions_Tests.xcscheme new file mode 100644 index 0000000..26063f9 --- /dev/null +++ b/Functions/Example/FirebaseFunctions.xcodeproj/xcshareddata/xcschemes/FirebaseFunctions_Tests.xcscheme @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "0930" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "6003F5AD195388D20070C39A" + BuildableName = "FirebaseFunctions_Tests.xctest" + BlueprintName = "FirebaseFunctions_Tests" + ReferencedContainer = "container:FirebaseFunctions.xcodeproj"> + </BuildableReference> + </TestableReference> + </Testables> + <AdditionalOptions> + </AdditionalOptions> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + <AdditionalOptions> + </AdditionalOptions> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Functions/Example/FirebaseFunctions/Base.lproj/LaunchScreen.storyboard b/Functions/Example/FirebaseFunctions/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..66a7681 --- /dev/null +++ b/Functions/Example/FirebaseFunctions/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM"> + <dependencies> + <deployment identifier="iOS"/> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/> + </dependencies> + <scenes> + <!--View Controller--> + <scene sceneID="EHf-IW-A2E"> + <objects> + <viewController id="01J-lp-oVM" sceneMemberID="viewController"> + <layoutGuides> + <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/> + <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/> + </layoutGuides> + <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3"> + <rect key="frame" x="0.0" y="0.0" width="600" height="600"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/> + </view> + </viewController> + <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/> + </objects> + <point key="canvasLocation" x="53" y="375"/> + </scene> + </scenes> +</document> diff --git a/Functions/Example/FirebaseFunctions/Base.lproj/Main.storyboard b/Functions/Example/FirebaseFunctions/Base.lproj/Main.storyboard new file mode 100644 index 0000000..d164a23 --- /dev/null +++ b/Functions/Example/FirebaseFunctions/Base.lproj/Main.storyboard @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="whP-gf-Uak"> + <dependencies> + <deployment identifier="iOS"/> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/> + </dependencies> + <scenes> + <!--View Controller--> + <scene sceneID="wQg-tq-qST"> + <objects> + <viewController id="whP-gf-Uak" customClass="FIRViewController" sceneMemberID="viewController"> + <layoutGuides> + <viewControllerLayoutGuide type="top" id="uEw-UM-LJ8"/> + <viewControllerLayoutGuide type="bottom" id="Mvr-aV-6Um"/> + </layoutGuides> + <view key="view" contentMode="scaleToFill" id="TpU-gO-2f1"> + <rect key="frame" x="0.0" y="0.0" width="600" height="600"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> + </view> + </viewController> + <placeholder placeholderIdentifier="IBFirstResponder" id="tc2-Qw-aMS" userLabel="First Responder" sceneMemberID="firstResponder"/> + </objects> + <point key="canvasLocation" x="305" y="433"/> + </scene> + </scenes> +</document> diff --git a/Functions/Example/FirebaseFunctions/FIRAppDelegate.h b/Functions/Example/FirebaseFunctions/FIRAppDelegate.h new file mode 100644 index 0000000..1d6d40b --- /dev/null +++ b/Functions/Example/FirebaseFunctions/FIRAppDelegate.h @@ -0,0 +1,21 @@ +// 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 UIKit; + +@interface FIRAppDelegate : UIResponder<UIApplicationDelegate> + +@property(strong, nonatomic) UIWindow *window; + +@end diff --git a/Functions/Example/FirebaseFunctions/FIRAppDelegate.m b/Functions/Example/FirebaseFunctions/FIRAppDelegate.m new file mode 100644 index 0000000..9568d06 --- /dev/null +++ b/Functions/Example/FirebaseFunctions/FIRAppDelegate.m @@ -0,0 +1,55 @@ +// 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 "FIRAppDelegate.h" + +@implementation FIRAppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Override point for customization after application launch. + return YES; +} + +- (void)applicationWillResignActive:(UIApplication *)application { + // Sent when the application is about to move from active to inactive state. This can occur for + // certain types of temporary interruptions (such as an incoming phone call or SMS message) or + // when the user quits the application and it begins the transition to the background state. Use + // this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. + // Games should use this method to pause the game. +} + +- (void)applicationDidEnterBackground:(UIApplication *)application { + // Use this method to release shared resources, save user data, invalidate timers, and store + // enough application state information to restore your application to its current state in case + // it is terminated later. If your application supports background execution, this method is + // called instead of applicationWillTerminate: when the user quits. +} + +- (void)applicationWillEnterForeground:(UIApplication *)application { + // Called as part of the transition from the background to the inactive state; here you can undo + // many of the changes made on entering the background. +} + +- (void)applicationDidBecomeActive:(UIApplication *)application { + // Restart any tasks that were paused (or not yet started) while the application was inactive. If + // the application was previously in the background, optionally refresh the user interface. +} + +- (void)applicationWillTerminate:(UIApplication *)application { + // Called when the application is about to terminate. Save data if appropriate. See also + // applicationDidEnterBackground:. +} + +@end diff --git a/Functions/Example/FirebaseFunctions/FIRViewController.h b/Functions/Example/FirebaseFunctions/FIRViewController.h new file mode 100644 index 0000000..5c4dff5 --- /dev/null +++ b/Functions/Example/FirebaseFunctions/FIRViewController.h @@ -0,0 +1,19 @@ +// 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 UIKit; + +@interface FIRViewController : UIViewController + +@end diff --git a/Functions/Example/FirebaseFunctions/FIRViewController.m b/Functions/Example/FirebaseFunctions/FIRViewController.m new file mode 100644 index 0000000..027aabf --- /dev/null +++ b/Functions/Example/FirebaseFunctions/FIRViewController.m @@ -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 "FIRViewController.h" + +@interface FIRViewController () + +@end + +@implementation FIRViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + // Do any additional setup after loading the view, typically from a nib. +} + +- (void)didReceiveMemoryWarning { + [super didReceiveMemoryWarning]; + // Dispose of any resources that can be recreated. +} + +@end diff --git a/Functions/Example/FirebaseFunctions/FirebaseFunctions-Info.plist b/Functions/Example/FirebaseFunctions/FirebaseFunctions-Info.plist new file mode 100644 index 0000000..fc26896 --- /dev/null +++ b/Functions/Example/FirebaseFunctions/FirebaseFunctions-Info.plist @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>en</string> + <key>CFBundleDisplayName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIdentifier</key> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>${PRODUCT_NAME}</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1.0</string> + <key>LSRequiresIPhoneOS</key> + <true/> + <key>NSAppTransportSecurity</key> + <dict> + <key>NSAllowsArbitraryLoads</key> + <true/> + </dict> + <key>UILaunchStoryboardName</key> + <string>LaunchScreen</string> + <key>UIMainStoryboardFile</key> + <string>Main</string> + <key>UIRequiredDeviceCapabilities</key> + <array> + <string>armv7</string> + </array> + <key>UISupportedInterfaceOrientations</key> + <array> + <string>UIInterfaceOrientationPortrait</string> + <string>UIInterfaceOrientationLandscapeLeft</string> + <string>UIInterfaceOrientationLandscapeRight</string> + </array> + <key>UISupportedInterfaceOrientations~ipad</key> + <array> + <string>UIInterfaceOrientationPortrait</string> + <string>UIInterfaceOrientationPortraitUpsideDown</string> + <string>UIInterfaceOrientationLandscapeLeft</string> + <string>UIInterfaceOrientationLandscapeRight</string> + </array> +</dict> +</plist> diff --git a/Functions/Example/FirebaseFunctions/Images.xcassets/AppIcon.appiconset/Contents.json b/Functions/Example/FirebaseFunctions/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d7070bc --- /dev/null +++ b/Functions/Example/FirebaseFunctions/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,93 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/Functions/Example/FirebaseFunctions/en.lproj/InfoPlist.strings b/Functions/Example/FirebaseFunctions/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/Functions/Example/FirebaseFunctions/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + diff --git a/Functions/Example/FirebaseFunctions/main.m b/Functions/Example/FirebaseFunctions/main.m new file mode 100644 index 0000000..39c05a5 --- /dev/null +++ b/Functions/Example/FirebaseFunctions/main.m @@ -0,0 +1,22 @@ +// 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 UIKit; +#import "FIRAppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([FIRAppDelegate class])); + } +} diff --git a/Functions/Example/IntegrationTests/FIRIntegrationTests.m b/Functions/Example/IntegrationTests/FIRIntegrationTests.m new file mode 100644 index 0000000..15a5af5 --- /dev/null +++ b/Functions/Example/IntegrationTests/FIRIntegrationTests.m @@ -0,0 +1,190 @@ +// 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 XCTest; + +#import "FIRError.h" +#import "FIRFunctions+Internal.h" +#import "FIRFunctions.h" +#import "FIRHTTPSCallable.h" +#import "FUNFakeApp.h" +#import "FUNFakeInstanceID.h" + +@interface FIRIntegrationTests : XCTestCase { + FIRFunctions *_functions; +} +@end + +@implementation FIRIntegrationTests + +- (void)setUp { + [super setUp]; + id app = [[FUNFakeApp alloc] initWithProjectID:@"functions-integration-test"]; + _functions = [FIRFunctions functionsForApp:app]; + [_functions useLocalhost]; +} + +- (void)tearDown { + [super tearDown]; +} + +- (void)testData { + NSDictionary *data = @{ + @"bool" : @YES, + @"int" : @2, + @"long" : @3L, + @"string" : @"four", + @"array" : @[ @5, @6 ], + @"null" : [NSNull null], + }; + + XCTestExpectation *expectation = [[XCTestExpectation alloc] init]; + FIRHTTPSCallable *function = [_functions HTTPSCallableWithName:@"dataTest"]; + [function callWithObject:data + completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) { + XCTAssertNil(error); + XCTAssertEqualObjects(@"stub response", result.data[@"message"]); + XCTAssertEqualObjects(@42, result.data[@"code"]); + XCTAssertEqualObjects(@420L, result.data[@"long"]); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:10]; +} + +- (void)testScalar { + XCTestExpectation *expectation = [[XCTestExpectation alloc] init]; + FIRHTTPSCallable *function = [_functions HTTPSCallableWithName:@"scalarTest"]; + [function callWithObject:@17 + completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) { + XCTAssertNil(error); + XCTAssertEqualObjects(@76, result.data); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:10]; +} + +- (void)testToken { + // Recreate _functions with a token. + id app = [[FUNFakeApp alloc] initWithProjectID:@"functions-integration-test" token:@"token"]; + FIRFunctions *functions = [FIRFunctions functionsForApp:app]; + [functions useLocalhost]; + + XCTestExpectation *expectation = [[XCTestExpectation alloc] init]; + FIRHTTPSCallable *function = [functions HTTPSCallableWithName:@"tokenTest"]; + [function callWithObject:@{} + completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) { + XCTAssertNil(error); + XCTAssertEqualObjects(@{}, result.data); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:10]; +} + +- (void)testInstanceID { + XCTestExpectation *expectation = [[XCTestExpectation alloc] init]; + FIRHTTPSCallable *function = [_functions HTTPSCallableWithName:@"instanceIdTest"]; + [function callWithObject:@{} + completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) { + XCTAssertNil(error); + XCTAssertEqualObjects(@{}, result.data); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:10]; +} + +- (void)testNull { + XCTestExpectation *expectation = [[XCTestExpectation alloc] init]; + FIRHTTPSCallable *function = [_functions HTTPSCallableWithName:@"nullTest"]; + [function callWithObject:[NSNull null] + completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) { + XCTAssertEqualObjects([NSNull null], result.data); + XCTAssertNil(error); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:10]; + + // Test the version with no arguments. + [function + callWithCompletion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) { + XCTAssertEqualObjects([NSNull null], result.data); + XCTAssertNil(error); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:10]; +} + +- (void)testMissingResult { + XCTestExpectation *expectation = [[XCTestExpectation alloc] init]; + FIRHTTPSCallable *function = [_functions HTTPSCallableWithName:@"missingResultTest"]; + [function + callWithCompletion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) { + XCTAssertNotNil(error); + XCTAssertEqual(FIRFunctionsErrorCodeInternal, error.code); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:10]; +} + +- (void)testUnhandledError { + XCTestExpectation *expectation = [[XCTestExpectation alloc] init]; + FIRHTTPSCallable *function = [_functions HTTPSCallableWithName:@"unhandledErrorTest"]; + [function callWithObject:@{} + completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) { + XCTAssertNotNil(error); + XCTAssertEqual(FIRFunctionsErrorCodeInternal, error.code); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:10]; +} + +- (void)testUnknownError { + XCTestExpectation *expectation = [[XCTestExpectation alloc] init]; + FIRHTTPSCallable *function = [_functions HTTPSCallableWithName:@"unknownErrorTest"]; + [function callWithObject:@{} + completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) { + XCTAssertNotNil(error); + XCTAssertEqual(FIRFunctionsErrorCodeInternal, error.code); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:10]; +} + +- (void)testExplicitError { + XCTestExpectation *expectation = [[XCTestExpectation alloc] init]; + FIRHTTPSCallable *function = [_functions HTTPSCallableWithName:@"explicitErrorTest"]; + [function callWithObject:@{} + completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) { + XCTAssertNotNil(error); + XCTAssertEqual(FIRFunctionsErrorCodeOutOfRange, error.code); + XCTAssertEqualObjects(@"explicit nope", error.userInfo[NSLocalizedDescriptionKey]); + NSDictionary *expectedDetails = @{ @"start" : @10, @"end" : @20, @"long" : @30L }; + XCTAssertEqualObjects(expectedDetails, error.userInfo[FIRFunctionsErrorDetailsKey]); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:10]; +} + +- (void)testHttpError { + XCTestExpectation *expectation = [[XCTestExpectation alloc] init]; + FIRHTTPSCallable *function = [_functions HTTPSCallableWithName:@"httpErrorTest"]; + [function callWithObject:@{} + completion:^(FIRHTTPSCallableResult *_Nullable result, NSError *_Nullable error) { + XCTAssertNotNil(error); + XCTAssertEqual(FIRFunctionsErrorCodeInvalidArgument, error.code); + [expectation fulfill]; + }]; + [self waitForExpectations:@[ expectation ] timeout:10]; +} + +@end diff --git a/Functions/Example/IntegrationTests/IntegrationTests-Info.plist b/Functions/Example/IntegrationTests/IntegrationTests-Info.plist new file mode 100644 index 0000000..169b6f7 --- /dev/null +++ b/Functions/Example/IntegrationTests/IntegrationTests-Info.plist @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>en</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIdentifier</key> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BNDL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1</string> +</dict> +</plist> diff --git a/Functions/Example/Podfile b/Functions/Example/Podfile new file mode 100644 index 0000000..ea07e11 --- /dev/null +++ b/Functions/Example/Podfile @@ -0,0 +1,16 @@ +use_frameworks! + +target 'FirebaseFunctions_Example' do + platform :ios, '8.0' + + pod 'FirebaseCore', :path => '../../' + pod 'FirebaseFunctions', :path => '../../' + + target 'FirebaseFunctions_Tests' do + inherit! :search_paths + end + + target 'FirebaseFunctions_IntegrationTests' do + inherit! :search_paths + end +end diff --git a/Functions/Example/TestUtils/FUNFakeApp.h b/Functions/Example/TestUtils/FUNFakeApp.h new file mode 100644 index 0000000..d07262b --- /dev/null +++ b/Functions/Example/TestUtils/FUNFakeApp.h @@ -0,0 +1,39 @@ +/* + * 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> + +@class FUNFakeOptions; + +NS_ASSUME_NONNULL_BEGIN + +/** + * FUNFakeApp is a mock app to use for tests. + */ +@interface FUNFakeApp : NSObject + +- (id)init NS_UNAVAILABLE; + +- (instancetype)initWithProjectID:(NSString *)projectID; + +- (instancetype)initWithProjectID:(NSString *)projectID + token:(NSString *_Nullable)token NS_DESIGNATED_INITIALIZER; + +@property(nonatomic, strong, readonly) FUNFakeOptions *options; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Functions/Example/TestUtils/FUNFakeApp.m b/Functions/Example/TestUtils/FUNFakeApp.m new file mode 100644 index 0000000..5b31d01 --- /dev/null +++ b/Functions/Example/TestUtils/FUNFakeApp.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 "FUNFakeApp.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FUNFakeOptions : NSObject + +@property(nonatomic, readonly, copy) NSString *projectID; + +- (id)init NS_UNAVAILABLE; + +- (instancetype)initWithProjectID:(NSString *)projectID NS_DESIGNATED_INITIALIZER; + +@end + +@implementation FUNFakeOptions + +- (instancetype)initWithProjectID:(NSString *)projectID { + self = [super init]; + if (self) { + self->_projectID = [projectID copy]; + } + return self; +} + +@end + +@interface FUNFakeApp () { + NSString *_token; +} +@end + +@implementation FUNFakeApp + +- (instancetype)initWithProjectID:(NSString *)projectID { + return [self initWithProjectID:projectID token:nil]; +} + +- (instancetype)initWithProjectID:(NSString *)projectID token:(NSString *_Nullable)token { + self = [super init]; + if (self) { + _options = [[FUNFakeOptions alloc] initWithProjectID:projectID]; + _token = [token copy]; + } + return self; +} + +- (void)getTokenForcingRefresh:(BOOL)forceRefresh + withCallback: + (void (^)(NSString *_Nullable token, NSError *_Nullable error))callback { + callback(_token, nil); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Functions/Example/TestUtils/FUNFakeInstanceID.h b/Functions/Example/TestUtils/FUNFakeInstanceID.h new file mode 100644 index 0000000..9925410 --- /dev/null +++ b/Functions/Example/TestUtils/FUNFakeInstanceID.h @@ -0,0 +1,34 @@ +/* + * 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 + +/** + * This FIRInstanceID is a mock instance ID provider to use for tests. + * Since FirebaseFunctions loads FIRInstanceID as a weak dependency by reflection, we just have to + * make a class with the same name. + */ +@interface FIRInstanceID : NSObject + ++ (instancetype)instanceID; + +- (NSString *)token; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Functions/Example/TestUtils/FUNFakeInstanceID.m b/Functions/Example/TestUtils/FUNFakeInstanceID.m new file mode 100644 index 0000000..a81e67a --- /dev/null +++ b/Functions/Example/TestUtils/FUNFakeInstanceID.m @@ -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 "FUNFakeInstanceID.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation FIRInstanceID + ++ (instancetype)instanceID { + return [[FIRInstanceID alloc] init]; +} + +- (NSString *)token { + return @"iid"; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Functions/Example/Tests/FIRFunctionsTests.m b/Functions/Example/Tests/FIRFunctionsTests.m new file mode 100644 index 0000000..bec96e6 --- /dev/null +++ b/Functions/Example/Tests/FIRFunctionsTests.m @@ -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 XCTest; + +#import "FIRFunctions+Internal.h" +#import "FIRFunctions.h" + +#import "FUNFakeApp.h" + +@interface FIRFunctionsTests : XCTestCase +@end + +@implementation FIRFunctionsTests + +- (void)setUp { + [super setUp]; +} + +- (void)tearDown { + [super tearDown]; +} + +- (void)testURLWithName { + // TODO(klimt): Add this test back when we add the constructor back. + /* + id app = [[FUNFakeApp alloc] initWithProjectID:@"my-project"]; + FIRFunctions *functions = [FIRFunctions functionsForApp:app region:@"my-region"]; + NSString *url = [functions URLWithName:@"my-endpoint"]; + XCTAssertEqualObjects(@"https://my-region-my-project.cloudfunctions.net/my-endpoint", url); + */ + + id app = [[FUNFakeApp alloc] initWithProjectID:@"my-project"]; + FIRFunctions *functions = [FIRFunctions functionsForApp:app]; + NSString *url = [functions URLWithName:@"my-endpoint"]; + XCTAssertEqualObjects(@"https://us-central1-my-project.cloudfunctions.net/my-endpoint", url); +} + +@end diff --git a/Functions/Example/Tests/FUNSerializerTests.m b/Functions/Example/Tests/FUNSerializerTests.m new file mode 100644 index 0000000..707c3e7 --- /dev/null +++ b/Functions/Example/Tests/FUNSerializerTests.m @@ -0,0 +1,240 @@ +// 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 XCTest; + +#import "FIRError.h" +#import "FUNSerializer.h" + +@interface FUNSerializerTests : XCTestCase +@end + +@implementation FUNSerializerTests + +- (void)setUp { + [super setUp]; +} + +- (void)tearDown { + [super tearDown]; +} + +- (void)testEncodeNull { + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + XCTAssertEqualObjects([NSNull null], [serializer encode:[NSNull null]]); +} + +- (void)testDecodeNull { + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + NSError *error = nil; + XCTAssertEqualObjects([NSNull null], [serializer decode:[NSNull null] error:&error]); + XCTAssertNil(error); +} + +- (void)testEncodeInt { + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + XCTAssertEqualObjects(@1, [serializer encode:@1]); +} + +- (void)testDecodeInt { + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + NSError *error = nil; + XCTAssertEqualObjects(@1, [serializer decode:@1 error:&error]); + XCTAssertNil(error); +} + +- (void)testEncodeLong { + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + NSDictionary *expected = @{ + @"@type" : @"type.googleapis.com/google.protobuf.Int64Value", + @"value" : @"-9223372036854775800", + }; + XCTAssertEqualObjects(expected, [serializer encode:@-9223372036854775800L]); +} + +- (void)testDecodeLong { + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + NSDictionary *input = @{ + @"@type" : @"type.googleapis.com/google.protobuf.Int64Value", + @"value" : @"-9223372036854775800", + }; + NSError *error = nil; + NSNumber *actual = [serializer decode:input error:&error]; + XCTAssertEqualObjects(@-9223372036854775800L, actual); + // A naive implementation might convert a number to a double and think that's close enough. + // We need to make sure it's a long long for accuracy. + XCTAssertEqual('q', actual.objCType[0]); + XCTAssertNil(error); +} + +- (void)testDecodeInvalidLong { + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + NSDictionary *input = @{ + @"@type" : @"type.googleapis.com/google.protobuf.Int64Value", + @"value" : @"-9223372036854775800 and some other junk", + }; + NSError *error = nil; + NSNumber *actual = [serializer decode:input error:&error]; + XCTAssertNil(actual); + XCTAssertNotNil(error); + XCTAssertEqualObjects(FIRFunctionsErrorDomain, error.domain); + XCTAssertEqual(FIRFunctionsErrorCodeInternal, error.code); +} + +- (void)testEncodeUnsignedLong { + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + NSDictionary *expected = @{ + @"@type" : @"type.googleapis.com/google.protobuf.UInt64Value", + @"value" : @"18446744073709551600", + }; + XCTAssertEqualObjects(expected, [serializer encode:@18446744073709551600UL]); +} + +- (void)testDecodeUnsignedLong { + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + NSDictionary *input = @{ + @"@type" : @"type.googleapis.com/google.protobuf.UInt64Value", + @"value" : @"17446744073709551688", + }; + NSError *error = nil; + NSNumber *actual = [serializer decode:input error:&error]; + XCTAssertEqualObjects(@17446744073709551688UL, actual); + // A naive NSNumberFormatter implementation will convert the number to a double and think + // that's close enough. We need to make sure it's an unsigned long long for accuracy. + XCTAssertEqual('Q', actual.objCType[0]); + XCTAssertNil(error); +} + +- (void)testEncodeDouble { + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + XCTAssertEqualObjects(@1.2, [serializer encode:@1.2]); +} + +- (void)testDecodeDouble { + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + NSError *error = nil; + XCTAssertEqualObjects(@1.2, [serializer decode:@1.2 error:&error]); + XCTAssertNil(error); +} + +- (void)testEncodeBool { + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + XCTAssertEqualObjects(@YES, [serializer encode:@YES]); +} + +- (void)testDecodeBool { + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + NSError *error = nil; + XCTAssertEqualObjects(@NO, [serializer decode:@NO error:&error]); + XCTAssertNil(error); +} + +- (void)testEncodeString { + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + XCTAssertEqualObjects(@"hello", [serializer encode:@"hello"]); +} + +- (void)testDecodeString { + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + NSError *error = nil; + XCTAssertEqualObjects(@"hello", [serializer decode:@"hello" error:&error]); + XCTAssertNil(error); +} + +- (void)testEncodeArray { + NSArray *input = @[ @1, @"two", @[ @3, @4L ] ]; + NSArray *expected = @[ + @1, @"two", + @[ + @3, @{ + @"@type" : @"type.googleapis.com/google.protobuf.Int64Value", + @"value" : @"4", + } + ] + ]; + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + XCTAssertEqualObjects(expected, [serializer encode:input]); +} + +- (void)testDecodeArray { + NSArray *input = @[ + @1, @"two", + @[ + @3, @{ + @"@type" : @"type.googleapis.com/google.protobuf.Int64Value", + @"value" : @"4", + } + ] + ]; + NSArray *expected = @[ @1, @"two", @[ @3, @4L ] ]; + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + NSError *error = nil; + + XCTAssertEqualObjects(expected, [serializer decode:input error:&error]); + XCTAssertNil(error); +} + +- (void)testEncodeMap { + NSDictionary *input = @{ @"foo" : @1, @"bar" : @"hello", @"baz" : @[ @3, @4L ] }; + NSDictionary *expected = @{ + @"foo" : @1, + @"bar" : @"hello", + @"baz" : @[ + @3, @{ + @"@type" : @"type.googleapis.com/google.protobuf.Int64Value", + @"value" : @"4", + } + ] + }; + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + XCTAssertEqualObjects(expected, [serializer encode:input]); +} + +- (void)testDecodeMap { + NSDictionary *input = @{ + @"foo" : @1, + @"bar" : @"hello", + @"baz" : @[ + @3, @{ + @"@type" : @"type.googleapis.com/google.protobuf.Int64Value", + @"value" : @"4", + } + ] + }; + NSDictionary *expected = @{ @"foo" : @1, @"bar" : @"hello", @"baz" : @[ @3, @4L ] }; + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + NSError *error = nil; + XCTAssertEqualObjects(expected, [serializer decode:input error:&error]); + XCTAssertNil(error); +} + +- (void)testDecodeUnknownType { + NSDictionary *input = @{@"@type" : @"unknown", @"value" : @"whatever"}; + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + NSError *error = nil; + XCTAssertEqualObjects(input, [serializer decode:input error:&error]); + XCTAssertNil(error); +} + +- (void)testDecodeUnknownTypeWithoutValue { + NSDictionary *input = @{ + @"@type" : @"unknown", + }; + FUNSerializer *serializer = [[FUNSerializer alloc] init]; + NSError *error = nil; + XCTAssertEqualObjects(input, [serializer decode:input error:&error]); + XCTAssertNil(error); +} + +@end diff --git a/Functions/Example/Tests/Tests-Info.plist b/Functions/Example/Tests/Tests-Info.plist new file mode 100644 index 0000000..169b6f7 --- /dev/null +++ b/Functions/Example/Tests/Tests-Info.plist @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>en</string> + <key>CFBundleExecutable</key> + <string>${EXECUTABLE_NAME}</string> + <key>CFBundleIdentifier</key> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundlePackageType</key> + <string>BNDL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>1</string> +</dict> +</plist> diff --git a/Functions/Example/Tests/en.lproj/InfoPlist.strings b/Functions/Example/Tests/en.lproj/InfoPlist.strings new file mode 100644 index 0000000..477b28f --- /dev/null +++ b/Functions/Example/Tests/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +/* Localized versions of Info.plist keys */ + 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..5cbc474 --- /dev/null +++ b/Functions/FirebaseFunctions/FUNContext.h @@ -0,0 +1,44 @@ +// +// 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> + +@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..890b5bf --- /dev/null +++ b/Functions/FirebaseFunctions/FUNContext.m @@ -0,0 +1,87 @@ +// +// 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 "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..d66a814 --- /dev/null +++ b/Functions/FirebaseFunctions/FUNError.h @@ -0,0 +1,34 @@ +// +// 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> + +@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..abe0287 --- /dev/null +++ b/Functions/FirebaseFunctions/FUNError.m @@ -0,0 +1,189 @@ +// +// 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 "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..3048dee --- /dev/null +++ b/Functions/FirebaseFunctions/Public/FIRError.h @@ -0,0 +1,91 @@ +// 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..d01175c --- /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..9298dda --- /dev/null +++ b/Functions/FirebaseFunctions/Public/FIRHTTPSCallable.h @@ -0,0 +1,93 @@ +// 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 diff --git a/Functions/README.md b/Functions/README.md new file mode 100644 index 0000000..f0b8d62 --- /dev/null +++ b/Functions/README.md @@ -0,0 +1,21 @@ +# Cloud Functions for Firebase iOS SDK + +## To run unit tests + +Choose the FirebaseFunctions_Tests scheme and press Command-u. + +## To run integration tests + +Before running the integration tests, you'll need to start a backend emulator +for them to talk to. + +1. Make sure you have `npm` installed. +2. Run the backend startup script: `Backend/start.sh` + It will use `npm install` to automatically download the libraries it needs + to run the [Cloud Functions Local Emulator](https://cloud.google.com/functions/docs/emulator). + The first time you run it, it will ask for a projectId. + You can put anything you like. It will be ignored. +3. Create the workspace in Functions/Example with `pod install`. +4. `open FirebaseFunctions.xcworkspace` +5. Choose the FirebaseFunctions_IntegrationTests scheme and press Command-u. +6. When you are finished, you can press any key to stop the backend. @@ -2,7 +2,7 @@ This repository contains a subset of the Firebase iOS SDK source. It currently includes FirebaseCore, FirebaseAuth, FirebaseDatabase, FirebaseFirestore, -FirebaseMessaging and FirebaseStorage. +FirebaseFunctions, FirebaseMessaging and FirebaseStorage. Firebase is an app development platform with tools to help you build, grow and monetize your app. More information about Firebase can be found at @@ -15,9 +15,10 @@ the Firebase iOS SDK. If you're interested in using the Firebase iOS SDK, start ## Context This repo contains a fully functional development environment for FirebaseCore, -FirebaseAuth, FirebaseDatabase, FirebaseFirestore, FirebaseMessaging, and -FirebaseStorage. By following the usage instructions below, they can be -developed and debugged with unit tests, integration tests, and reference samples. +FirebaseAuth, FirebaseDatabase, FirebaseFirestore, FirebaseFunctions, +FirebaseMessaging, and FirebaseStorage. By following the usage instructions +below, they can be developed and debugged with unit tests, integration tests, +and reference samples. ## Source pod integration @@ -70,8 +71,9 @@ $ pod update $ open Firebase.xcworkspace ``` -Firestore has a self contained Xcode project. See -[Firestore/README.md](Firestore/README.md). +Firestore and Functions have self contained Xcode projects. See +[Firestore/README.md](Firestore/README.md) and +[Functions/README.md](Functions/README.md). ### Running Unit Tests |