diff options
38 files changed, 1351 insertions, 63 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index c2bf56d..a935c4d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,8 +52,10 @@ enable_testing() # does not exist yet. include(external/FirebaseCore) include(external/googletest) +include(external/zlib) include(external/leveldb) include(external/protobuf) include(external/nanopb) +include(external/c-ares) include(external/grpc) include(external/firestore) diff --git a/Example/Auth/Tests/FIRAuthTests.m b/Example/Auth/Tests/FIRAuthTests.m index 34c0499..8859cb3 100644 --- a/Example/Auth/Tests/FIRAuthTests.m +++ b/Example/Auth/Tests/FIRAuthTests.m @@ -1863,16 +1863,17 @@ static const NSTimeInterval kWaitInterval = .5; [self waitForSignInWithAccessToken:kTestAccessToken APIKey:kTestAPIKey completion:nil]; - NSString *kTestAPIKey2 = @"fakeAPIKey2"; - FIRUser *user2 = [FIRAuth auth].currentUser; - user2.requestConfiguration = [[FIRAuthRequestConfiguration alloc]initWithAPIKey:kTestAPIKey2]; - OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]]) - .andDispatchError2([FIRAuthErrorUtils networkErrorWithUnderlyingError:[NSError new]]); - XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; - [[FIRAuth auth] updateCurrentUser:user2 completion:^(NSError *_Nullable error) { - XCTAssertEqual(error.code, FIRAuthErrorCodeNetworkError); - [expectation fulfill]; - }]; + NSString *kTestAPIKey2 = @"fakeAPIKey2"; + FIRUser *user2 = [FIRAuth auth].currentUser; + user2.requestConfiguration = [[FIRAuthRequestConfiguration alloc]initWithAPIKey:kTestAPIKey2]; + NSError *underlyingError = [NSError errorWithDomain:@"Test Error" code:1 userInfo:nil]; + OCMExpect([_mockBackend getAccountInfo:[OCMArg any] callback:[OCMArg any]]) + .andDispatchError2([FIRAuthErrorUtils networkErrorWithUnderlyingError:underlyingError]); + XCTestExpectation *expectation = [self expectationWithDescription:@"callback"]; + [[FIRAuth auth] updateCurrentUser:user2 completion:^(NSError *_Nullable error) { + XCTAssertEqual(error.code, FIRAuthErrorCodeNetworkError); + [expectation fulfill]; + }]; [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; OCMVerifyAll(_mockBackend); } diff --git a/Example/Auth/Tests/FIRPhoneAuthProviderTests.m b/Example/Auth/Tests/FIRPhoneAuthProviderTests.m index 1bb42b1..6c925a5 100644 --- a/Example/Auth/Tests/FIRPhoneAuthProviderTests.m +++ b/Example/Auth/Tests/FIRPhoneAuthProviderTests.m @@ -453,7 +453,8 @@ static const NSTimeInterval kExpectationTimeout = 2; // Assert that the app credential is nil when in test mode. XCTAssertNil(request.appCredential); dispatch_async(FIRAuthGlobalWorkQueue(), ^() { - callback(nil, [FIRAuthErrorUtils networkErrorWithUnderlyingError:[NSError new]]); + NSError *underlying = [NSError errorWithDomain:@"Test Error" code:1 userInfo:nil]; + callback(nil, [FIRAuthErrorUtils networkErrorWithUnderlyingError:underlying]); }); }); diff --git a/Example/Auth/Tests/FIRUserTests.m b/Example/Auth/Tests/FIRUserTests.m index 8bb6786..957fd24 100644 --- a/Example/Auth/Tests/FIRUserTests.m +++ b/Example/Auth/Tests/FIRUserTests.m @@ -1151,8 +1151,8 @@ static const NSTimeInterval kExpectationTimeout = 2; XCTAssertEqualObjects(request.APIKey, kAPIKey); dispatch_async(FIRAuthGlobalWorkQueue(), ^() { - - callback(nil, [FIRAuthErrorUtils networkErrorWithUnderlyingError:[NSError new]]); + NSError *underlying = [NSError errorWithDomain:@"Test Error" code:1 userInfo:nil]; + callback(nil, [FIRAuthErrorUtils networkErrorWithUnderlyingError:underlying]); }); }); [user getIDTokenResultForcingRefresh:YES diff --git a/Example/Core/Tests/FIRComponentContainerTest.m b/Example/Core/Tests/FIRComponentContainerTest.m new file mode 100644 index 0000000..b255973 --- /dev/null +++ b/Example/Core/Tests/FIRComponentContainerTest.m @@ -0,0 +1,157 @@ +// 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. + +#import "FIRTestCase.h" + +#import <FirebaseCore/FIRAppInternal.h> +#import <FirebaseCore/FIRComponent.h> +#import <FirebaseCore/FIRComponentContainerInternal.h> + +#import "FIRTestComponents.h" + +/// Internally exposed methods and properties for testing. +@interface FIRComponentContainer (TestInternal) + +@property(nonatomic, strong) NSMutableDictionary<NSString *, FIRComponentCreationBlock> *components; +@property(nonatomic, strong) NSMutableDictionary<NSString *, id> *cachedInstances; + ++ (void)registerAsComponentRegistrant:(Class)klass inSet:(NSMutableSet<Class> *)allRegistrants; + +- (instancetype)initWithApp:(FIRApp *)app registrants:(NSMutableSet<Class> *)allRegistrants; + +@end + +@interface FIRComponentContainerTest : FIRTestCase + +@end + +@implementation FIRComponentContainerTest + +#pragma mark - Registration Tests + +- (void)testRegisteringConformingClass { + NSMutableSet<Class> *allRegistrants = [NSMutableSet<Class> set]; + Class testClass = [FIRTestClass class]; + [FIRComponentContainer registerAsComponentRegistrant:testClass inSet:allRegistrants]; + XCTAssertTrue([allRegistrants containsObject:testClass]); +} + +- (void)testRegisteringNonConformingClass { + NSMutableSet<Class> *allRegistrants = [NSMutableSet<Class> set]; + XCTAssertThrows( + [FIRComponentContainer registerAsComponentRegistrant:[NSString class] inSet:allRegistrants]); + XCTAssertTrue(allRegistrants.count == 0); +} + +- (void)testComponentsPopulatedOnInit { + FIRComponentContainer *container = [self containerWithRegistrants:@[ [FIRTestClass class] ]]; + + // Verify that the block is stored. + NSString *protocolName = NSStringFromProtocol(@protocol(FIRTestProtocol)); + FIRComponentCreationBlock creationBlock = container.components[protocolName]; + OCMExpect(creationBlock); +} + +#pragma mark - Caching Tests + +- (void)testInstanceCached { + FIRComponentContainer *container = + [self containerWithRegistrants:@[ [FIRTestClassCached class] ]]; + + // Fetch an instance for `FIRTestProtocolCached`, then fetch it again to assert it's cached. + id<FIRTestProtocolCached> instance1 = FIR_COMPONENT(FIRTestProtocolCached, container); + XCTAssertNotNil(instance1); + id<FIRTestProtocolCached> instance2 = FIR_COMPONENT(FIRTestProtocolCached, container); + XCTAssertNotNil(instance2); + XCTAssertEqual(instance1, instance2); +} + +- (void)testInstanceNotCached { + FIRComponentContainer *container = [self containerWithRegistrants:@[ [FIRTestClass class] ]]; + + // Retrieve an instance from the container, then fetch it again and ensure it's not the same + // instance. + id<FIRTestProtocol> instance1 = FIR_COMPONENT(FIRTestProtocol, container); + XCTAssertNotNil(instance1); + id<FIRTestProtocol> instance2 = FIR_COMPONENT(FIRTestProtocol, container); + XCTAssertNotNil(instance2); + XCTAssertNotEqual(instance1, instance2); +} + +- (void)testRemoveAllCachedInstances { + FIRComponentContainer *container = [self containerWithRegistrants:@[ + [FIRTestClass class], [FIRTestClassCached class], [FIRTestClassEagerCached class] + ]]; + + // Retrieve an instance of FIRTestClassCached to ensure it's cached. + id<FIRTestProtocolCached> cachedInstance1 = FIR_COMPONENT(FIRTestProtocolCached, container); + id<FIRTestProtocolEagerCached> eagerInstance1 = + FIR_COMPONENT(FIRTestProtocolEagerCached, container); + + // FIRTestClassEagerCached and FIRTestClassCached instances should be cached at this point. + XCTAssertTrue(container.cachedInstances.count == 2); + + // Remove the instances and verify cachedInstances is empty, and that new instances returned from + // the container don't match the old ones. + [container removeAllCachedInstances]; + XCTAssertTrue(container.cachedInstances.count == 0); + + id<FIRTestProtocolCached> cachedInstance2 = FIR_COMPONENT(FIRTestProtocolCached, container); + XCTAssertNotEqual(cachedInstance1, cachedInstance2); + id<FIRTestProtocolEagerCached> eagerInstance2 = + FIR_COMPONENT(FIRTestProtocolEagerCached, container); + XCTAssertNotEqual(eagerInstance1, eagerInstance2); +} + +#pragma mark - Instantiation Tests + +- (void)testEagerInstantiation { + // Create a container with `FIRTestClassEagerCached` as a registrant, which provides the + // implementation for `FIRTestProtocolEagerCached` and requires eager instantiation as well as + // caching so the test can verify it was eagerly instantiated. + FIRComponentContainer *container = + [self containerWithRegistrants:@[ [FIRTestClassEagerCached class] ]]; + NSString *protocolName = NSStringFromProtocol(@protocol(FIRTestProtocolEagerCached)); + XCTAssertNotNil(container.cachedInstances[protocolName]); +} + +#pragma mark - Input Validation Tests + +- (void)testProtocolAlreadyRegistered { + // Register two classes that provide the same protocol. Only one should be stored, and there + // should be a log stating that the protocol has already been registered. Right now there's no + // guarantee which one will be registered first since it's an NSSet under the hood, but that could + // change in the future. + // TODO(wilsonryan): Assert that the log gets called warning that it's already been registered. + FIRComponentContainer *container = + [self containerWithRegistrants:@[ [FIRTestClass class], [FIRTestClassDuplicate class] ]]; + XCTAssert(container.components.count == 1); +} + +#pragma mark - Convenience Methods + +/// Create a container that has registered the test class. +- (FIRComponentContainer *)containerWithRegistrants:(NSArray<Class> *)registrants { + id appMock = OCMClassMock([FIRApp class]); + NSMutableSet<Class> *allRegistrants = [NSMutableSet<Class> set]; + + // Initialize the container with the test classes. + for (Class c in registrants) { + [FIRComponentContainer registerAsComponentRegistrant:c inSet:allRegistrants]; + } + + return [[FIRComponentContainer alloc] initWithApp:appMock registrants:allRegistrants]; +} + +@end diff --git a/Example/Core/Tests/FIRComponentTypeTest.m b/Example/Core/Tests/FIRComponentTypeTest.m new file mode 100644 index 0000000..7076fdd --- /dev/null +++ b/Example/Core/Tests/FIRComponentTypeTest.m @@ -0,0 +1,60 @@ +// 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. + +#import "FIRTestCase.h" + +#import <FirebaseCore/FIRComponentContainerInternal.h> +#import <FirebaseCore/FIRComponentType.h> + +#import "FIRTestComponents.h" + +@interface FIRComponentTypeTest : FIRTestCase + +@property(nonatomic, strong) id componentContainerMock; +@end + +@implementation FIRComponentTypeTest + +- (void)setUp { + [super setUp]; + _componentContainerMock = OCMClassMock([FIRComponentContainer class]); +} + +- (void)tearDown { + [super tearDown]; + [_componentContainerMock stopMocking]; +} + +- (void)testForwardsCallToContainer { + Protocol *testProtocol = @protocol(FIRTestProtocol); + OCMExpect([self.componentContainerMock instanceForProtocol:testProtocol]); + + // Grab an instance from the container, through ComponentType. + __unused id<FIRTestProtocol> instance = + [FIRComponentType<id<FIRTestProtocol>> instanceForProtocol:@protocol(FIRTestProtocol) + inContainer:self.componentContainerMock]; + OCMVerifyAll(self.componentContainerMock); +} + +- (void)testMacroForwardsCallToContainer { + Protocol *testProtocol = @protocol(FIRTestProtocol); + OCMExpect([self.componentContainerMock instanceForProtocol:testProtocol]); + + // Grab an instance from the container, through the macro that uses FIRComponentType. + __unused id<FIRTestProtocol> instance = + FIR_COMPONENT(FIRTestProtocol, self.componentContainerMock); + + OCMVerifyAll(self.componentContainerMock); +} +@end diff --git a/Example/Core/Tests/FIRTestComponents.h b/Example/Core/Tests/FIRTestComponents.h new file mode 100644 index 0000000..63b2075 --- /dev/null +++ b/Example/Core/Tests/FIRTestComponents.h @@ -0,0 +1,63 @@ +// 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. + +#import <Foundation/Foundation.h> + +#import <FirebaseCore/FIRComponent.h> +#import <FirebaseCore/FIRComponentContainer.h> +#import <FirebaseCore/FIRComponentRegistrant.h> + +@protocol FIRComponentRegistrant; + +#pragma mark - Standard Component + +/// A test protocol to be used for container testing. +@protocol FIRTestProtocol +- (void)doSomething; +@end + +/// A test class that is a component registrant. +@interface FIRTestClass + : NSObject <FIRTestProtocol, FIRComponentRegistrant, FIRComponentLifecycleMaintainer> +@end + +/// A test class that is a component registrant, a duplicate of FIRTestClass. +@interface FIRTestClassDuplicate + : NSObject <FIRTestProtocol, FIRComponentRegistrant, FIRComponentLifecycleMaintainer> +@end + +#pragma mark - Eager Component + +/// A test protocol to be used for container testing. +@protocol FIRTestProtocolEagerCached +- (void)doSomethingFaster; +@end + +/// A test class that is a component registrant that provides a component requiring eager +/// instantiation, and is cached for easier validation that it was instantiated. +@interface FIRTestClassEagerCached + : NSObject <FIRTestProtocolEagerCached, FIRComponentRegistrant, FIRComponentLifecycleMaintainer> +@end + +#pragma mark - Cached Component + +/// A test protocol to be used for container testing. +@protocol FIRTestProtocolCached +@end + +/// A test class that is a component registrant that provides a component that requests to be +/// cached. +@interface FIRTestClassCached + : NSObject <FIRTestProtocolCached, FIRComponentRegistrant, FIRComponentLifecycleMaintainer> +@end diff --git a/Example/Core/Tests/FIRTestComponents.m b/Example/Core/Tests/FIRTestComponents.m new file mode 100644 index 0000000..68346f3 --- /dev/null +++ b/Example/Core/Tests/FIRTestComponents.m @@ -0,0 +1,118 @@ +// 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. + +#import "FIRTestComponents.h" + +#import <FirebaseCore/FIRComponent.h> + +#pragma mark - Standard Component + +@implementation FIRTestClass + +/// FIRTestProtocol conformance. +- (void)doSomething { +} + +/// FIRComponentRegistrant conformance. ++ (nonnull NSArray<FIRComponent *> *)componentsToRegister { + FIRComponent *testComponent = + [FIRComponent componentWithProtocol:@protocol(FIRTestProtocol) + creationBlock:^id _Nullable(FIRComponentContainer *_Nonnull container, + BOOL *_Nonnull isCacheable) { + return [[FIRTestClass alloc] init]; + }]; + return @[ testComponent ]; +} + +/// FIRComponentLifecycleMaintainer conformance. +- (void)appWillBeDeleted:(FIRApp *)app { +} + +@end + +/// A test class that is a component registrant, a duplicate of FIRTestClass. +@implementation FIRTestClassDuplicate + +- (void)doSomething { +} + +/// FIRComponentRegistrant conformance. ++ (nonnull NSArray<FIRComponent *> *)componentsToRegister { + FIRComponent *testComponent = + [FIRComponent componentWithProtocol:@protocol(FIRTestProtocol) + creationBlock:^id _Nullable(FIRComponentContainer *_Nonnull container, + BOOL *_Nonnull isCacheable) { + return [[FIRTestClassDuplicate alloc] init]; + }]; + return @[ testComponent ]; +} + +/// FIRComponentLifecycleMaintainer conformance. +- (void)appWillBeDeleted:(FIRApp *)app { +} + +@end + +#pragma mark - Eager Component + +@implementation FIRTestClassEagerCached + +/// FIRTestProtocolEager conformance. +- (void)doSomethingFaster { +} + +/// FIRComponentRegistrant conformance. ++ (nonnull NSArray<FIRComponent *> *)componentsToRegister { + FIRComponent *testComponent = [FIRComponent + componentWithProtocol:@protocol(FIRTestProtocolEagerCached) + instantiationTiming:FIRInstantiationTimingAlwaysEager + dependencies:@[] + creationBlock:^id _Nullable(FIRComponentContainer *_Nonnull container, + BOOL *_Nonnull isCacheable) { + FIRTestClassEagerCached *instance = [[FIRTestClassEagerCached alloc] init]; + *isCacheable = YES; + [instance doSomethingFaster]; + return instance; + }]; + return @[ testComponent ]; +} + +/// FIRComponentLifecycleMaintainer conformance. +- (void)appWillBeDeleted:(FIRApp *)app { +} + +@end + +#pragma mark - Cached Component + +@implementation FIRTestClassCached + +/// FIRComponentRegistrant conformance. ++ (nonnull NSArray<FIRComponent *> *)componentsToRegister { + FIRComponent *testComponent = [FIRComponent + componentWithProtocol:@protocol(FIRTestProtocolCached) + creationBlock:^id _Nullable(FIRComponentContainer *_Nonnull container, + BOOL *_Nonnull isCacheable) { + FIRTestClassCached *instanceToCache = [[FIRTestClassCached alloc] init]; + *isCacheable = YES; + return instanceToCache; + }]; + return @[ testComponent ]; +} + +/// FIRComponentLifecycleMaintainer conformance. +- (void)appWillBeDeleted:(FIRApp *)app { +} + +@end diff --git a/Example/Firebase.xcodeproj/project.pbxproj b/Example/Firebase.xcodeproj/project.pbxproj index 61c4c6a..8d3c13b 100644 --- a/Example/Firebase.xcodeproj/project.pbxproj +++ b/Example/Firebase.xcodeproj/project.pbxproj @@ -568,6 +568,15 @@ DEF6C33D1FBCE775005D0740 /* FIRVerifyPasswordRequestTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE93151F1E86C6FF0083EDBF /* FIRVerifyPasswordRequestTest.m */; }; DEF6C33E1FBCE775005D0740 /* FIRVerifyPasswordResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315201E86C6FF0083EDBF /* FIRVerifyPasswordResponseTests.m */; }; DEF6C3411FBCE775005D0740 /* OCMStubRecorder+FIRAuthUnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE9315241E86C6FF0083EDBF /* OCMStubRecorder+FIRAuthUnitTests.m */; }; + ED34CF4E20DC16DD000EA5D1 /* FIRComponentContainerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = ED34CF4A20DC16DC000EA5D1 /* FIRComponentContainerTest.m */; }; + ED34CF4F20DC16DD000EA5D1 /* FIRComponentContainerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = ED34CF4A20DC16DC000EA5D1 /* FIRComponentContainerTest.m */; }; + ED34CF5020DC16DD000EA5D1 /* FIRComponentContainerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = ED34CF4A20DC16DC000EA5D1 /* FIRComponentContainerTest.m */; }; + ED34CF5120DC16DD000EA5D1 /* FIRTestComponents.m in Sources */ = {isa = PBXBuildFile; fileRef = ED34CF4B20DC16DC000EA5D1 /* FIRTestComponents.m */; }; + ED34CF5220DC16DD000EA5D1 /* FIRTestComponents.m in Sources */ = {isa = PBXBuildFile; fileRef = ED34CF4B20DC16DC000EA5D1 /* FIRTestComponents.m */; }; + ED34CF5320DC16DD000EA5D1 /* FIRTestComponents.m in Sources */ = {isa = PBXBuildFile; fileRef = ED34CF4B20DC16DC000EA5D1 /* FIRTestComponents.m */; }; + ED34CF5420DC16DD000EA5D1 /* FIRComponentTypeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = ED34CF4D20DC16DD000EA5D1 /* FIRComponentTypeTest.m */; }; + ED34CF5520DC16DD000EA5D1 /* FIRComponentTypeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = ED34CF4D20DC16DD000EA5D1 /* FIRComponentTypeTest.m */; }; + ED34CF5620DC16DD000EA5D1 /* FIRComponentTypeTest.m in Sources */ = {isa = PBXBuildFile; fileRef = ED34CF4D20DC16DD000EA5D1 /* FIRComponentTypeTest.m */; }; ED8C81002088EFA20093EB8A /* FIRMutableDictionaryTest.m in Sources */ = {isa = PBXBuildFile; fileRef = ED8C80FA2088EFA10093EB8A /* FIRMutableDictionaryTest.m */; }; ED8C81012088EFA20093EB8A /* FIRMutableDictionaryTest.m in Sources */ = {isa = PBXBuildFile; fileRef = ED8C80FA2088EFA10093EB8A /* FIRMutableDictionaryTest.m */; }; ED8C81022088EFA20093EB8A /* FIRMutableDictionaryTest.m in Sources */ = {isa = PBXBuildFile; fileRef = ED8C80FA2088EFA10093EB8A /* FIRMutableDictionaryTest.m */; }; @@ -1255,6 +1264,10 @@ DEE14D7D1E844677006FA992 /* Tests-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = "<group>"; }; DEF288401F9AB6E100D480CF /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = "<group>"; }; E2C2834C90DBAB56D568189F /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; }; + ED34CF4A20DC16DC000EA5D1 /* FIRComponentContainerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRComponentContainerTest.m; sourceTree = "<group>"; }; + ED34CF4B20DC16DC000EA5D1 /* FIRTestComponents.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRTestComponents.m; sourceTree = "<group>"; }; + ED34CF4C20DC16DD000EA5D1 /* FIRTestComponents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FIRTestComponents.h; sourceTree = "<group>"; }; + ED34CF4D20DC16DD000EA5D1 /* FIRComponentTypeTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRComponentTypeTest.m; sourceTree = "<group>"; }; ED8C80FA2088EFA10093EB8A /* FIRMutableDictionaryTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRMutableDictionaryTest.m; sourceTree = "<group>"; }; ED8C80FB2088EFA10093EB8A /* FIRNetworkTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRNetworkTest.m; sourceTree = "<group>"; }; ED8C80FC2088EFA20093EB8A /* FIRReachabilityCheckerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRReachabilityCheckerTest.m; sourceTree = "<group>"; }; @@ -2265,21 +2278,25 @@ DEE14D741E844677006FA992 /* Tests */ = { isa = PBXGroup; children = ( - ED8C80FA2088EFA10093EB8A /* FIRMutableDictionaryTest.m */, - ED8C80FB2088EFA10093EB8A /* FIRNetworkTest.m */, - ED8C80FC2088EFA20093EB8A /* FIRReachabilityCheckerTest.m */, - ED8C80FD2088EFA20093EB8A /* third_party */, EDD43AA320BF7C7B005EBB36 /* FIRAnalyticsConfigurationTest.m */, - DE4B26DE20855F1F0030A38C /* FIRAppEnvironmentUtilTest.m */, - DEE14D7B1E844677006FA992 /* FIRTestCase.h */, DEE14D751E844677006FA992 /* FIRAppAssociationRegistrationUnitTests.m */, + DE4B26DE20855F1F0030A38C /* FIRAppEnvironmentUtilTest.m */, DEE14D761E844677006FA992 /* FIRAppTest.m */, DEE14D771E844677006FA992 /* FIRBundleUtilTest.m */, + ED34CF4A20DC16DC000EA5D1 /* FIRComponentContainerTest.m */, + ED34CF4D20DC16DD000EA5D1 /* FIRComponentTypeTest.m */, DEE14D781E844677006FA992 /* FIRConfigurationTest.m */, DEE14D791E844677006FA992 /* FIRLoggerTest.m */, + ED8C80FA2088EFA10093EB8A /* FIRMutableDictionaryTest.m */, + ED8C80FB2088EFA10093EB8A /* FIRNetworkTest.m */, DEE14D7A1E844677006FA992 /* FIROptionsTest.m */, + ED8C80FC2088EFA20093EB8A /* FIRReachabilityCheckerTest.m */, + DEE14D7B1E844677006FA992 /* FIRTestCase.h */, DEE14D7C1E844677006FA992 /* FIRTestCase.m */, + ED34CF4C20DC16DD000EA5D1 /* FIRTestComponents.h */, + ED34CF4B20DC16DC000EA5D1 /* FIRTestComponents.m */, DEE14D7D1E844677006FA992 /* Tests-Info.plist */, + ED8C80FD2088EFA20093EB8A /* third_party */, ); path = Tests; sourceTree = "<group>"; @@ -3621,12 +3638,15 @@ D064E6AF1ED9B31C001956DF /* FIRAppAssociationRegistrationUnitTests.m in Sources */, ED8C81012088EFA20093EB8A /* FIRMutableDictionaryTest.m in Sources */, D064E6B01ED9B31C001956DF /* FIRAppTest.m in Sources */, + ED34CF5220DC16DD000EA5D1 /* FIRTestComponents.m in Sources */, D064E6B11ED9B31C001956DF /* FIRConfigurationTest.m in Sources */, + ED34CF5520DC16DD000EA5D1 /* FIRComponentTypeTest.m in Sources */, DE4B26E120855F500030A38C /* FIRAppEnvironmentUtilTest.m in Sources */, D064E6B21ED9B31C001956DF /* FIRLoggerTest.m in Sources */, D064E6B31ED9B31C001956DF /* FIROptionsTest.m in Sources */, ED8C810A2088EFA20093EB8A /* GTMHTTPServer.m in Sources */, D064E6B41ED9B31C001956DF /* FIRBundleUtilTest.m in Sources */, + ED34CF4F20DC16DD000EA5D1 /* FIRComponentContainerTest.m in Sources */, EDD43AA520BF7C7B005EBB36 /* FIRAnalyticsConfigurationTest.m in Sources */, D064E6B51ED9B31C001956DF /* FIRTestCase.m in Sources */, ); @@ -4080,12 +4100,15 @@ DEAAD3DA1FBA34250053BF48 /* FIROptionsTest.m in Sources */, ED8C81022088EFA20093EB8A /* FIRMutableDictionaryTest.m in Sources */, DEAAD3D51FBA34250053BF48 /* FIRAppAssociationRegistrationUnitTests.m in Sources */, + ED34CF5320DC16DD000EA5D1 /* FIRTestComponents.m in Sources */, DEAAD3D91FBA34250053BF48 /* FIRLoggerTest.m in Sources */, + ED34CF5620DC16DD000EA5D1 /* FIRComponentTypeTest.m in Sources */, DE4B26E220855F520030A38C /* FIRAppEnvironmentUtilTest.m in Sources */, DEAAD3D61FBA34250053BF48 /* FIRAppTest.m in Sources */, DEAAD3D81FBA34250053BF48 /* FIRConfigurationTest.m in Sources */, ED8C810B2088EFA20093EB8A /* GTMHTTPServer.m in Sources */, DEAAD3DB1FBA34250053BF48 /* FIRTestCase.m in Sources */, + ED34CF5020DC16DD000EA5D1 /* FIRComponentContainerTest.m in Sources */, EDD43AA620BF7C7B005EBB36 /* FIRAnalyticsConfigurationTest.m in Sources */, DEAAD3D71FBA34250053BF48 /* FIRBundleUtilTest.m in Sources */, ); @@ -4164,12 +4187,15 @@ DEE14D8E1E84468D006FA992 /* FIRAppAssociationRegistrationUnitTests.m in Sources */, ED8C81002088EFA20093EB8A /* FIRMutableDictionaryTest.m in Sources */, DEE14D8F1E84468D006FA992 /* FIRAppTest.m in Sources */, + ED34CF5120DC16DD000EA5D1 /* FIRTestComponents.m in Sources */, DEE14D911E84468D006FA992 /* FIRConfigurationTest.m in Sources */, + ED34CF5420DC16DD000EA5D1 /* FIRComponentTypeTest.m in Sources */, DE4B26E020855F4C0030A38C /* FIRAppEnvironmentUtilTest.m in Sources */, DEE14D921E84468D006FA992 /* FIRLoggerTest.m in Sources */, DEE14D931E84468D006FA992 /* FIROptionsTest.m in Sources */, ED8C81092088EFA20093EB8A /* GTMHTTPServer.m in Sources */, DEE14D901E84468D006FA992 /* FIRBundleUtilTest.m in Sources */, + ED34CF4E20DC16DD000EA5D1 /* FIRComponentContainerTest.m in Sources */, EDD43AA420BF7C7B005EBB36 /* FIRAnalyticsConfigurationTest.m in Sources */, DEE14D941E84468D006FA992 /* FIRTestCase.m in Sources */, ); diff --git a/Firebase/Auth/CHANGELOG.md b/Firebase/Auth/CHANGELOG.md index b5c4560..2709831 100644 --- a/Firebase/Auth/CHANGELOG.md +++ b/Firebase/Auth/CHANGELOG.md @@ -1,3 +1,7 @@ +# v5.1.0 +- Adds `FIRAuthErrorCodeMalformedJWT`, which is raised on JWT token parsing + failures during auth operations (#1436). + # v5.0.2 - Fix an issue where JWT date timestamps weren't parsed correctly. (#1319) - Fix an issue where anonymous accounts weren't correctly promoted to diff --git a/Firebase/Auth/Source/FIRAuthErrorUtils.h b/Firebase/Auth/Source/FIRAuthErrorUtils.h index 5b8205f..6cc22ab 100644 --- a/Firebase/Auth/Source/FIRAuthErrorUtils.h +++ b/Firebase/Auth/Source/FIRAuthErrorUtils.h @@ -85,6 +85,19 @@ NS_ASSUME_NONNULL_BEGIN */ + (NSError *)unexpectedErrorResponseWithDeserializedResponse:(id)deserializedResponse; +/** @fn malformedJWTErrorWithToken:underlyingError: + @brief Constructs an @c NSError with the code set to @c FIRAuthErrorCodeMalformedJWT and + populates the userInfo dictionary with an error message, the bad token, and an underlying + error that may have occurred when parsing. + @param token The token that failed to parse. + @param underlyingError The error that caused this error. If this parameter is nil, the + NSUnderlyingErrorKey value will not be set. + @remarks This error is returned when JWT parsing fails. + @returns An @c FIRAuthErrorCodeMalformedJWT error wrapping an underlying error, if available. + */ ++ (NSError *)malformedJWTErrorWithToken:(NSString *)token + underlyingError:(NSError *_Nullable)underlyingError; + /** @fn unexpectedResponseWithData:underlyingError: @brief Constructs an @c NSError with the @c FIRAuthInternalErrorCodeUnexpectedResponse code, and a populated @c FIRAuthErrorUserInfoDataKey key in the @c NSError.userInfo diff --git a/Firebase/Auth/Source/FIRAuthErrorUtils.m b/Firebase/Auth/Source/FIRAuthErrorUtils.m index 62c569c..b91231a 100644 --- a/Firebase/Auth/Source/FIRAuthErrorUtils.m +++ b/Firebase/Auth/Source/FIRAuthErrorUtils.m @@ -413,6 +413,12 @@ static NSString *const kFIRAuthErrorMessageNullUser = @"A null user object was p static NSString *const kFIRAuthErrorMessageInternalError = @"An internal error has occurred, " "print and inspect the error details for more information."; +/** @var kFIRAuthErrorMessageMalformedJWT + @brief Error message constant describing @c FIRAuthErrorCodeMalformedJWT errors. + */ +static NSString *const kFIRAuthErrorMessageMalformedJWT = + @"Failed to parse JWT. Check the userInfo dictionary for the full token."; + /** @var FIRAuthErrorDescription @brief The error descrioption, based on the error code. @remarks No default case so that we get a compiler warning if a new value was added to the enum. @@ -531,6 +537,8 @@ static NSString *FIRAuthErrorDescription(FIRAuthErrorCode code) { return kFIRAuthErrorMessageNullUser; case FIRAuthErrorCodeWebInternalError: return kFIRAuthErrorMessageWebInternalError; + case FIRAuthErrorCodeMalformedJWT: + return kFIRAuthErrorMessageMalformedJWT; } } @@ -652,6 +660,8 @@ static NSString *const FIRAuthErrorCodeString(FIRAuthErrorCode code) { return @"ERROR_NULL_USER"; case FIRAuthErrorCodeWebInternalError: return @"ERROR_WEB_INTERNAL_ERROR"; + case FIRAuthErrorCodeMalformedJWT: + return @"ERROR_MALFORMED_JWT"; } } @@ -735,6 +745,18 @@ static NSString *const FIRAuthErrorCodeString(FIRAuthErrorCode code) { }]; } ++ (NSError *)malformedJWTErrorWithToken:(NSString *)token + underlyingError:(NSError *_Nullable)underlyingError { + NSMutableDictionary *userInfo = + [NSMutableDictionary dictionaryWithObject:kFIRAuthErrorMessageMalformedJWT + forKey:NSLocalizedDescriptionKey]; + [userInfo setObject:token forKey:FIRAuthErrorUserInfoDataKey]; + if (underlyingError != nil) { + [userInfo setObject:underlyingError forKey:NSUnderlyingErrorKey]; + } + return [self errorWithCode:FIRAuthInternalErrorCodeMalformedJWT userInfo:[userInfo copy]]; +} + + (NSError *)unexpectedResponseWithData:(NSData *)data underlyingError:(NSError *)underlyingError { return [self errorWithCode:FIRAuthInternalErrorCodeUnexpectedResponse userInfo:@{ diff --git a/Firebase/Auth/Source/FIRAuthInternalErrors.h b/Firebase/Auth/Source/FIRAuthInternalErrors.h index fd08022..4cdd8cf 100644 --- a/Firebase/Auth/Source/FIRAuthInternalErrors.h +++ b/Firebase/Auth/Source/FIRAuthInternalErrors.h @@ -376,6 +376,9 @@ typedef NS_ENUM(NSInteger, FIRAuthInternalErrorCode) { FIRAuthInternalErrorCodeNullUser = FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeNullUser, + FIRAuthInternalErrorCodeMalformedJWT = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeMalformedJWT, + /** @var FIRAuthInternalErrorCodeRPCRequestEncodingError @brief Indicates an error encoding the RPC request. @remarks This is typically due to some sort of unexpected input value. diff --git a/Firebase/Auth/Source/FIRUser.m b/Firebase/Auth/Source/FIRUser.m index 9bae744..ad0b1d4 100644 --- a/Firebase/Auth/Source/FIRUser.m +++ b/Firebase/Auth/Source/FIRUser.m @@ -851,9 +851,17 @@ static void callInMainThreadWithAuthDataResultAndError( "error" out parameter. */ - (FIRAuthTokenResult *)parseIDToken:(NSString *)token error:(NSError **)error { + // Though this is an internal method, errors returned here are surfaced in user-visible + // callbacks. *error = nil; NSArray *tokenStringArray = [token componentsSeparatedByString:@"."]; + // The JWT should have three parts, though we only use the second in this method. + if (tokenStringArray.count != 3) { + *error = [FIRAuthErrorUtils malformedJWTErrorWithToken:token underlyingError:nil]; + return nil; + } + // The token payload is always the second index of the array. NSString *idToken = tokenStringArray[1]; @@ -863,8 +871,10 @@ static void callInMainThreadWithAuthDataResultAndError( [[idToken stringByReplacingOccurrencesOfString:@"_" withString:@"/"] mutableCopy]; // Replace "-" with "+" - tokenPayload = - [[tokenPayload stringByReplacingOccurrencesOfString:@"-" withString:@"+"] mutableCopy]; + [tokenPayload replaceOccurrencesOfString:@"-" + withString:@"+" + options:kNilOptions + range:NSMakeRange(0, tokenPayload.length)]; // Pad the token payload with "=" signs if the payload's length is not a multiple of 4. while ((tokenPayload.length % 4) != 0) { @@ -874,19 +884,22 @@ static void callInMainThreadWithAuthDataResultAndError( [[NSData alloc] initWithBase64EncodedString:tokenPayload options:NSDataBase64DecodingIgnoreUnknownCharacters]; if (!decodedTokenPayloadData) { - *error = [FIRAuthErrorUtils unexpectedResponseWithDeserializedResponse:token]; + *error = [FIRAuthErrorUtils malformedJWTErrorWithToken:token underlyingError:nil]; return nil; } + NSError *jsonError = nil; + NSJSONReadingOptions options = NSJSONReadingMutableContainers|NSJSONReadingAllowFragments; NSDictionary *tokenPayloadDictionary = [NSJSONSerialization JSONObjectWithData:decodedTokenPayloadData - options:NSJSONReadingMutableContainers|NSJSONReadingAllowFragments - error:error]; - if (*error) { + options:options + error:&jsonError]; + if (jsonError != nil) { + *error = [FIRAuthErrorUtils malformedJWTErrorWithToken:token underlyingError:jsonError]; return nil; } if (!tokenPayloadDictionary) { - *error = [FIRAuthErrorUtils unexpectedResponseWithDeserializedResponse:token]; + *error = [FIRAuthErrorUtils malformedJWTErrorWithToken:token underlyingError:nil]; return nil; } diff --git a/Firebase/Auth/Source/Public/FIRAuthErrors.h b/Firebase/Auth/Source/Public/FIRAuthErrors.h index 4a1a6f1..a3fbe26 100644 --- a/Firebase/Auth/Source/Public/FIRAuthErrors.h +++ b/Firebase/Auth/Source/Public/FIRAuthErrors.h @@ -311,6 +311,11 @@ typedef NS_ENUM(NSInteger, FIRAuthErrorCode) { /** Indicates an internal error occurred. */ FIRAuthErrorCodeInternalError = 17999, + + /** Raised when a JWT fails to parse correctly. May be accompanied by an underlying error + describing which step of the JWT parsing process failed. + */ + FIRAuthErrorCodeMalformedJWT = 18000, } NS_SWIFT_NAME(AuthErrorCode); @end diff --git a/Firebase/Core/FIRApp.m b/Firebase/Core/FIRApp.m index 91a7aba..2ea7f6b 100644 --- a/Firebase/Core/FIRApp.m +++ b/Firebase/Core/FIRApp.m @@ -19,6 +19,8 @@ #import "Private/FIRAnalyticsConfiguration+Internal.h" #import "Private/FIRAppInternal.h" #import "Private/FIRBundleUtil.h" +#import "Private/FIRComponentContainerInternal.h" +#import "Private/FIRCoreConfigurable.h" #import "Private/FIRLogger.h" #import "Private/FIROptionsInternal.h" #import "third_party/FIRAppEnvironmentUtil.h" @@ -76,6 +78,12 @@ NSString *const FIRAuthStateDidChangeInternalNotificationUIDKey = */ static NSString *const kPlistURL = @"https://console.firebase.google.com/"; +/** + * An array of all classes that registered as `FIRCoreConfigurable` in order to receive lifecycle + * events from Core. + */ +static NSMutableArray<Class<FIRCoreConfigurable>> *gRegisteredAsConfigurable; + @interface FIRApp () @property(nonatomic) BOOL alreadySentConfigureNotification; @@ -233,6 +241,10 @@ static NSMutableDictionary *sLibraryVersions; @synchronized([self class]) { if (sAllApps && sAllApps[self.name]) { FIRLogDebug(kFIRLoggerCore, @"I-COR000006", @"Deleting app named %@", self.name); + + // Remove all cached instances from the container before deleting the app. + [self.container removeAllCachedInstances]; + [sAllApps removeObjectForKey:self.name]; [self clearDataCollectionSwitchFromUserDefaults]; if ([self.name isEqualToString:kFIRDefaultAppName]) { @@ -280,6 +292,8 @@ static NSMutableDictionary *sLibraryVersions; _name = [name copy]; _options = [options copy]; _options.editingLocked = YES; + _isDefaultApp = [name isEqualToString:kFIRDefaultAppName]; + _container = [[FIRComponentContainer alloc] initWithApp:self]; FIRApp *app = sAllApps[name]; _alreadySentConfigureNotification = app.alreadySentConfigureNotification; @@ -384,6 +398,7 @@ static NSMutableDictionary *sLibraryVersions; #pragma mark - private + (void)sendNotificationsToSDKs:(FIRApp *)app { + // TODO: Remove this notification once all SDKs are registered with `FIRCoreConfigurable`. NSNumber *isDefaultApp = [NSNumber numberWithBool:(app == sDefaultApp)]; NSDictionary *appInfoDict = @{ kFIRAppNameKey : app.name, @@ -393,6 +408,12 @@ static NSMutableDictionary *sLibraryVersions; [[NSNotificationCenter defaultCenter] postNotificationName:kFIRAppReadyToConfigureSDKNotification object:self userInfo:appInfoDict]; + + // This is the new way of sending information to SDKs. + // TODO: Do we want this on a background thread, maybe? + for (Class<FIRCoreConfigurable> library in gRegisteredAsConfigurable) { + [library configureWithApp:app]; + } } + (NSError *)errorForMissingOptions { @@ -430,6 +451,18 @@ static NSMutableDictionary *sLibraryVersions; userInfo:errorDict]; } ++ (void)registerAsConfigurable:(Class<FIRCoreConfigurable>)klass { + // This is called at +load time, keep the work to a minimum. + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + gRegisteredAsConfigurable = [[NSMutableArray alloc] initWithCapacity:1]; + }); + + NSAssert([(Class)klass conformsToProtocol:@protocol(FIRCoreConfigurable)], + @"The class being registered (%@) must conform to `FIRCoreConfigurable`.", klass); + [gRegisteredAsConfigurable addObject:klass]; +} + + (BOOL)isDefaultAppConfigured { return (sDefaultApp != nil); } diff --git a/Firebase/Core/FIRComponent.m b/Firebase/Core/FIRComponent.m new file mode 100644 index 0000000..2474d1a --- /dev/null +++ b/Firebase/Core/FIRComponent.m @@ -0,0 +1,65 @@ +/* + * 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. + */ + +#import "Private/FIRComponent.h" + +#import "Private/FIRComponentContainer.h" +#import "Private/FIRDependency.h" + +@interface FIRComponent () + +- (instancetype)initWithProtocol:(Protocol *)protocol + instantiationTiming:(FIRInstantiationTiming)instantiationTiming + dependencies:(NSArray<FIRDependency *> *)dependencies + creationBlock:(FIRComponentCreationBlock)creationBlock; + +@end + +@implementation FIRComponent + ++ (instancetype)componentWithProtocol:(Protocol *)protocol + creationBlock:(FIRComponentCreationBlock)creationBlock { + return [[FIRComponent alloc] initWithProtocol:protocol + instantiationTiming:FIRInstantiationTimingLazy + dependencies:@[] + creationBlock:creationBlock]; +} + ++ (instancetype)componentWithProtocol:(Protocol *)protocol + instantiationTiming:(FIRInstantiationTiming)instantiationTiming + dependencies:(NSArray<FIRDependency *> *)dependencies + creationBlock:(FIRComponentCreationBlock)creationBlock { + return [[FIRComponent alloc] initWithProtocol:protocol + instantiationTiming:instantiationTiming + dependencies:dependencies + creationBlock:creationBlock]; +} + +- (instancetype)initWithProtocol:(Protocol *)protocol + instantiationTiming:(FIRInstantiationTiming)instantiationTiming + dependencies:(NSArray<FIRDependency *> *)dependencies + creationBlock:(FIRComponentCreationBlock)creationBlock { + self = [super init]; + if (self) { + _protocol = protocol; + _instantiationTiming = instantiationTiming; + _dependencies = [dependencies copy]; + _creationBlock = creationBlock; + } + return self; +} + +@end diff --git a/Firebase/Core/FIRComponentContainer.m b/Firebase/Core/FIRComponentContainer.m new file mode 100644 index 0000000..381c95c --- /dev/null +++ b/Firebase/Core/FIRComponentContainer.m @@ -0,0 +1,205 @@ +/* + * 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. + */ + +#import "Private/FIRComponentContainer.h" + +#import "Private/FIRAppInternal.h" +#import "Private/FIRComponent.h" +#import "Private/FIRComponentRegistrant.h" +#import "Private/FIRLogger.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRComponentContainer () + +/// The dictionary of components that are registered for a particular app. The key is an NSString +/// of the protocol. +@property(nonatomic, strong) NSMutableDictionary<NSString *, FIRComponentCreationBlock> *components; + +/// Cached instances of components that requested to be cached. +@property(nonatomic, strong) NSMutableDictionary<NSString *, id> *cachedInstances; + +@end + +@implementation FIRComponentContainer + +// Collection of all classes that register to provide components. +static NSMutableSet<Class> *gFIRComponentRegistrants; + +#pragma mark - Public Registration + ++ (void)registerAsComponentRegistrant:(Class)klass { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + gFIRComponentRegistrants = [[NSMutableSet<Class> alloc] init]; + }); + + [self registerAsComponentRegistrant:klass inSet:gFIRComponentRegistrants]; +} + ++ (void)registerAsComponentRegistrant:(Class)klass inSet:(NSMutableSet<Class> *)allRegistrants { + // Validate the array to store the components is initialized. + if (!allRegistrants) { + FIRLogWarning(kFIRLoggerCore, @"I-COR000025", + @"Attempted to store registered components in an empty set."); + return; + } + + // Ensure the class given conforms to the proper protocol. + if (![klass conformsToProtocol:@protocol(FIRComponentRegistrant)] || + ![klass respondsToSelector:@selector(componentsToRegister)]) { + [NSException raise:NSInvalidArgumentException + format: + @"Class %@ attempted to register components, but it does not conform to " + @"`FIRComponentRegistrant` or provide a `componentsToRegister:` method.", + klass]; + } + + [allRegistrants addObject:klass]; +} + +#pragma mark - Internal Initialization + +- (instancetype)initWithApp:(FIRApp *)app { + return [self initWithApp:app registrants:gFIRComponentRegistrants]; +} + +- (instancetype)initWithApp:(FIRApp *)app registrants:(NSMutableSet<Class> *)allRegistrants { + self = [super init]; + if (self) { + _app = app; + _cachedInstances = [NSMutableDictionary<NSString *, id> dictionary]; + _components = [NSMutableDictionary<NSString *, FIRComponentCreationBlock> dictionary]; + + [self populateComponentsFromRegisteredClasses:allRegistrants forApp:app]; + } + return self; +} + +- (void)populateComponentsFromRegisteredClasses:(NSSet<Class> *)classes forApp:(FIRApp *)app { + // Loop through the verified component registrants and populate the components array. + for (Class<FIRComponentRegistrant> klass in classes) { + // Loop through all the components being registered and store them as appropriate. + // Classes which do not provide functionality should use a dummy FIRComponentRegistrant + // protocol. + for (FIRComponent *component in [klass componentsToRegister]) { + // Check if the component has been registered before, and error out if so. + NSString *protocolName = NSStringFromProtocol(component.protocol); + if (self.components[protocolName]) { + FIRLogError(kFIRLoggerCore, @"I-COR000029", + @"Attempted to register protocol %@, but it already has an implementation.", + protocolName); + continue; + } + + // Store the creation block for later usage. + self.components[protocolName] = component.creationBlock; + + // Instantiate the + BOOL shouldInstantiateEager = + (component.instantiationTiming == FIRInstantiationTimingAlwaysEager); + BOOL shouldInstantiateDefaultEager = + (component.instantiationTiming == FIRInstantiationTimingEagerInDefaultApp && + [app isDefaultApp]); + if (shouldInstantiateEager || shouldInstantiateDefaultEager) { + [self instantiateInstanceForProtocol:component.protocol withBlock:component.creationBlock]; + } + } + } +} + +#pragma mark - Instance Creation + +/// Instantiate an instance of a class that conforms to the specified protocol. +/// This will: +/// - Call the block to create an instance if possible, +/// - Validate that the instance returned conforms to the protocol it claims to, +/// - Cache the instance if the block requests it +- (nullable id)instantiateInstanceForProtocol:(Protocol *)protocol + withBlock:(FIRComponentCreationBlock)creationBlock { + if (!creationBlock) { + return nil; + } + + // Create an instance using the creation block. + BOOL shouldCache = NO; + id instance = creationBlock(self, &shouldCache); + if (!instance) { + return nil; + } + + // An instance was created, validate that it conforms to the protocol it claims to. + NSString *protocolName = NSStringFromProtocol(protocol); + if (![instance conformsToProtocol:protocol]) { + FIRLogError(kFIRLoggerCore, @"I-COR000030", + @"An instance conforming to %@ was requested, but the instance provided does not " + @"conform to the protocol", + protocolName); + } + + // The instance is ready to be returned, but check if it should be cached first before returning. + if (shouldCache) { + self.cachedInstances[protocolName] = instance; + } + + return instance; +} + +#pragma mark - Internal Retrieval + +- (nullable id)instanceForProtocol:(Protocol *)protocol { + // Check if there is a cached instance, and return it if so. + NSString *protocolName = NSStringFromProtocol(protocol); + id cachedInstance = self.cachedInstances[protocolName]; + if (cachedInstance) { + return cachedInstance; + } + + // Use the creation block to instantiate an instance and return it. + FIRComponentCreationBlock creationBlock = self.components[protocolName]; + return [self instantiateInstanceForProtocol:protocol withBlock:creationBlock]; +} + +#pragma mark - Lifecycle + +- (void)removeAllCachedInstances { + // Loop through the cache and notify each instance that is a maintainer to clean up after itself. + for (id instance in self.cachedInstances.allValues) { + if ([instance conformsToProtocol:@protocol(FIRComponentLifecycleMaintainer)] && + [instance respondsToSelector:@selector(appWillBeDeleted:)]) { + [instance appWillBeDeleted:self.app]; + } + } + + [self.cachedInstances removeAllObjects]; +} + +#pragma mark - Testing Initializers + +// TODO(wilsonryan): Set up a testing flag so this only is compiled in with unit tests. +/// Initialize an instance with an app and existing components. +- (instancetype)initWithApp:(FIRApp *)app + components:(NSDictionary<NSString *, FIRComponentCreationBlock> *)components { + self = [self initWithApp:app registrants:[[NSMutableSet alloc] init]]; + if (self) { + _components = [components mutableCopy]; + } + return self; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Core/FIRComponentType.m b/Firebase/Core/FIRComponentType.m new file mode 100644 index 0000000..bdc004f --- /dev/null +++ b/Firebase/Core/FIRComponentType.m @@ -0,0 +1,28 @@ +/* + * 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. + */ + +#import "Private/FIRComponentType.h" + +#import "Private/FIRComponentContainerInternal.h" + +@implementation FIRComponentType + ++ (id)instanceForProtocol:(Protocol *)protocol inContainer:(FIRComponentContainer *)container { + // Forward the call to the container. + return [container instanceForProtocol:protocol]; +} + +@end diff --git a/Firebase/Core/FIRDependency.m b/Firebase/Core/FIRDependency.m new file mode 100644 index 0000000..f979984 --- /dev/null +++ b/Firebase/Core/FIRDependency.m @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#import "Private/FIRDependency.h" + +@interface FIRDependency () + +- (instancetype)initWithProtocol:(Protocol *)protocol isRequired:(BOOL)required; + +@end + +@implementation FIRDependency + ++ (instancetype)dependencyWithProtocol:(Protocol *)protocol { + return [[self alloc] initWithProtocol:protocol isRequired:YES]; +} + ++ (instancetype)dependencyWithProtocol:(Protocol *)protocol isRequired:(BOOL)required { + return [[self alloc] initWithProtocol:protocol isRequired:required]; +} + +- (instancetype)initWithProtocol:(Protocol *)protocol isRequired:(BOOL)required { + self = [super init]; + if (self) { + _protocol = protocol; + _isRequired = required; + } + return self; +} + +@end diff --git a/Firebase/Core/Private/FIRAppAssociationRegistration.h b/Firebase/Core/Private/FIRAppAssociationRegistration.h index a1f8c41..007e2bf 100644 --- a/Firebase/Core/Private/FIRAppAssociationRegistration.h +++ b/Firebase/Core/Private/FIRAppAssociationRegistration.h @@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN +// TODO: Remove this once Auth moves over to Core's instance registration system. /** @class FIRAppAssociationRegistration @brief Manages object associations as a singleton-dependent: At most one object is registered for any given host/key pair, and the object shall be created on-the-fly when diff --git a/Firebase/Core/Private/FIRAppInternal.h b/Firebase/Core/Private/FIRAppInternal.h index 66979eb..45de2ba 100644 --- a/Firebase/Core/Private/FIRAppInternal.h +++ b/Firebase/Core/Private/FIRAppInternal.h @@ -17,6 +17,9 @@ #import "FIRApp.h" #import "FIRErrors.h" +@class FIRComponentContainer; +@protocol FIRCoreConfigurable; + /** * The internal interface to FIRApp. This is meant for first-party integrators, who need to receive * FIRApp notifications, log info about the success or failure of their configuration, and access @@ -126,6 +129,11 @@ typedef NSString *_Nullable (^FIRAppGetUIDImplementation)(void); @interface FIRApp () +/** + * A flag indicating if this is the default app. + */ +@property(nonatomic, readonly) BOOL isDefaultApp; + /** @property getTokenImplementation @brief Gets or sets the block to use for the implementation of @c getTokenForcingRefresh:withCallback: @@ -137,6 +145,11 @@ typedef NSString *_Nullable (^FIRAppGetUIDImplementation)(void); */ @property(nonatomic, copy) FIRAppGetUIDImplementation getUIDImplementation; +/* + * The container of interop SDKs for this app. + */ +@property(nonatomic) FIRComponentContainer *container; + /** * Creates an error for failing to configure a subspec service. This method is called by each * FIRApp notification listener. @@ -151,6 +164,13 @@ typedef NSString *_Nullable (^FIRAppGetUIDImplementation)(void); + (BOOL)isDefaultAppConfigured; /** + * Register a class that conforms to `FIRCoreConfigurable`. Each SDK should have one class that + * registers in order to provide critical information for interoperability and lifecycle events. + * TODO(wilsonryan): Write more documentation. + */ ++ (void)registerAsConfigurable:(Class<FIRCoreConfigurable>)klass; + +/** * Registers a given third-party library with the given version number to be reported for * analyitcs. * diff --git a/Firebase/Core/Private/FIRComponent.h b/Firebase/Core/Private/FIRComponent.h new file mode 100644 index 0000000..cb51ee7 --- /dev/null +++ b/Firebase/Core/Private/FIRComponent.h @@ -0,0 +1,91 @@ +/* + * 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. + */ + +#import <Foundation/Foundation.h> + +@class FIRApp; +@class FIRComponentContainer; + +NS_ASSUME_NONNULL_BEGIN + +/// Provides a system to clean up cached instances returned from the component system. +NS_SWIFT_NAME(ComponentLifecycleMaintainer) +@protocol FIRComponentLifecycleMaintainer +/// The associated app will be deleted, clean up any resources as they are about to be deallocated. +- (void)appWillBeDeleted:(FIRApp *)app; +@end + +typedef _Nullable id (^FIRComponentCreationBlock)(FIRComponentContainer *container, + BOOL *isCacheable) + NS_SWIFT_NAME(ComponentCreationBlock); + +@class FIRDependency; + +/// Describes the timing of instantiation. Note: new components should default to lazy unless there +/// is a strong reason to be eager. +typedef NS_ENUM(NSInteger, FIRInstantiationTiming) { + FIRInstantiationTimingLazy, + FIRInstantiationTimingAlwaysEager, + FIRInstantiationTimingEagerInDefaultApp +} NS_SWIFT_NAME(InstantiationTiming); + +/// A component that can be used from other Firebase SDKs. +NS_SWIFT_NAME(Component) +@interface FIRComponent : NSObject + +/// The protocol describing functionality provided from the Component. +@property(nonatomic, strong, readonly) Protocol *protocol; + +/// The timing of instantiation. +@property(nonatomic, readonly) FIRInstantiationTiming instantiationTiming; + +/// An array of dependencies for the component. +@property(nonatomic, copy, readonly) NSArray<FIRDependency *> *dependencies; + +/// A block to instantiate an instance of the component with the appropriate dependencies. +@property(nonatomic, copy, readonly) FIRComponentCreationBlock creationBlock; + +// There's an issue with long NS_SWIFT_NAMES that causes compilation to fail, disable clang-format +// for the next two methods. +// clang-format off + +/// Creates a component with no dependencies that will be lazily initialized. ++ (instancetype)componentWithProtocol:(Protocol *)protocol + creationBlock:(FIRComponentCreationBlock)creationBlock +NS_SWIFT_NAME(init(_:creationBlock:)); + +/// Creates a component to be registered with the component container. +/// +/// @param protocol - The protocol describing functionality provided by the component. +/// @param instantiationTiming - When the component should be initialized. Use .lazy unless there's +/// a good reason to be instantiated earlier. +/// @param dependencies - Any dependencies the `implementingClass` has, optional or required. +/// @param creationBlock - A block to instantiate the component with a container, and if +/// @return A component that can be registered with the component container. ++ (instancetype)componentWithProtocol:(Protocol *)protocol + instantiationTiming:(FIRInstantiationTiming)instantiationTiming + dependencies:(NSArray<FIRDependency *> *)dependencies + creationBlock:(FIRComponentCreationBlock)creationBlock +NS_SWIFT_NAME(init(_:instantiationTiming:dependencies:creationBlock:)); + +// clang-format on + +/// Unavailable. +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Core/Private/FIRComponentContainer.h b/Firebase/Core/Private/FIRComponentContainer.h new file mode 100644 index 0000000..10e2255 --- /dev/null +++ b/Firebase/Core/Private/FIRComponentContainer.h @@ -0,0 +1,47 @@ +/* + * 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. + */ +#import <Foundation/Foundation.h> + +#import "FIRComponentType.h" + +NS_ASSUME_NONNULL_BEGIN + +/// A type-safe macro to retrieve a component from a container. This should be used to retrieve +/// components instead of using the container directly. +#define FIR_COMPONENT(type, container) \ + [FIRComponentType<id<type>> instanceForProtocol:@protocol(type) inContainer:container] + +@class FIRApp; + +/// A container that holds different components that are registered via the +/// `registerAsComponentRegistrant:` call. These classes should conform to `FIRComponentRegistrant` +/// in order to properly register components for Core. +NS_SWIFT_NAME(FirebaseComponentContainer) +@interface FIRComponentContainer : NSObject + +/// A weak reference to the app that an instance of the container belongs to. +@property(nonatomic, weak, readonly) FIRApp *app; + +/// Unavailable. Use the `container` property on `FIRApp`. +- (instancetype)init NS_UNAVAILABLE; + +/// Register a class to provide components for the interoperability system. The class should conform +/// to `FIRComponentRegistrant` and provide an array of `FIRComponent` objects. ++ (void)registerAsComponentRegistrant:(Class)klass; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Core/Private/FIRComponentContainerInternal.h b/Firebase/Core/Private/FIRComponentContainerInternal.h new file mode 100644 index 0000000..bb73e7b --- /dev/null +++ b/Firebase/Core/Private/FIRComponentContainerInternal.h @@ -0,0 +1,39 @@ +/* + * 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. + */ +#import <Foundation/Foundation.h> + +#import "FIRComponent.h" +#import "FIRComponentContainer.h" + +@class FIRApp; + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRComponentContainer (Private) + +/// Initializes a contain for a given app. This should only be called by the app itself. +- (instancetype)initWithApp:(FIRApp *)app; + +/// Retrieves an instance that conforms to the specified protocol. This will return `nil` if the +/// protocol wasn't registered, or if the instance couldn't instantiate for the provided app. +- (nullable id)instanceForProtocol:(Protocol *)protocol NS_SWIFT_NAME(instance(for:)); + +/// Remove all of the cached instances stored and allow them to clean up after themselves. +- (void)removeAllCachedInstances; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Core/Private/FIRComponentRegistrant.h b/Firebase/Core/Private/FIRComponentRegistrant.h new file mode 100644 index 0000000..ad2cad2 --- /dev/null +++ b/Firebase/Core/Private/FIRComponentRegistrant.h @@ -0,0 +1,38 @@ +/* + * 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. + */ + +#ifndef FIRComponentRegistrant_h +#define FIRComponentRegistrant_h + +#import <Foundation/Foundation.h> + +@class FIRComponent; + +NS_ASSUME_NONNULL_BEGIN + +/// Describes functionality for SDKs registering components in the `FIRComponentContainer`. +NS_SWIFT_NAME(ComponentRegistrant) +@protocol FIRComponentRegistrant + +/// Returns one or more FIRComponents that will be registered in +/// FIRApp and participate in dependency resolution and injection. ++ (NSArray<FIRComponent *> *)componentsToRegister; + +@end + +NS_ASSUME_NONNULL_END + +#endif /* FIRComponentRegistrant_h */ diff --git a/Firebase/Core/Private/FIRComponentType.h b/Firebase/Core/Private/FIRComponentType.h new file mode 100644 index 0000000..3254835 --- /dev/null +++ b/Firebase/Core/Private/FIRComponentType.h @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#import <Foundation/Foundation.h> + +@class FIRComponentContainer; + +NS_ASSUME_NONNULL_BEGIN + +/// Do not use directly. A placeholder type in order to provide a macro that will warn users of +/// mis-matched protocols. +NS_SWIFT_NAME(ComponentType) +@interface FIRComponentType <__covariant T> : NSObject + +/// Do not use directly. A factory method to retrieve an instance that provides a specific +/// functionality. ++ (T)instanceForProtocol:(Protocol *)protocol + inContainer:(FIRComponentContainer *)container; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Core/Private/FIRCoreConfigurable.h b/Firebase/Core/Private/FIRCoreConfigurable.h new file mode 100644 index 0000000..6c2b077 --- /dev/null +++ b/Firebase/Core/Private/FIRCoreConfigurable.h @@ -0,0 +1,38 @@ +/* + * 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. + */ + +#ifndef FIRCoreConfigurable_h +#define FIRCoreConfigurable_h + +#import <Foundation/Foundation.h> + +@class FIRApp; + +NS_ASSUME_NONNULL_BEGIN + +/// Provides an interface to set up an SDK once a `FIRApp` is configured. +NS_SWIFT_NAME(CoreConfigurable) +@protocol FIRCoreConfigurable + +/// Configure the SDK if needed ahead of time. This method is called when the developer calls +/// `FirebaseApp.configure()`. ++ (void)configureWithApp:(FIRApp *)app; + +@end + +NS_ASSUME_NONNULL_END + +#endif /* FIRCoreConfigurable_h */ diff --git a/Firebase/Core/Private/FIRDependency.h b/Firebase/Core/Private/FIRDependency.h new file mode 100644 index 0000000..46e9b7e --- /dev/null +++ b/Firebase/Core/Private/FIRDependency.h @@ -0,0 +1,45 @@ +/* + * 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. + */ + +#import <Foundation/Foundation.h> + +NS_ASSUME_NONNULL_BEGIN + +/// A dependency on a specific protocol's functionality. +NS_SWIFT_NAME(Dependency) +@interface FIRDependency : NSObject + +/// The protocol describing functionality being depended on. +@property(nonatomic, strong, readonly) Protocol *protocol; + +/// A flag to specify if the dependency is required or not. +@property(nonatomic, readonly) BOOL isRequired; + +/// Initializes a dependency that is required. Calls `initWithProtocol:isRequired` with `YES` for +/// the required parameter. +/// Creates a required dependency on the specified protocol's functionality. ++ (instancetype)dependencyWithProtocol:(Protocol *)protocol; + +/// Creates a dependency on the specified protocol's functionality and specify if it's required for +/// the class's functionality. ++ (instancetype)dependencyWithProtocol:(Protocol *)protocol isRequired:(BOOL)required; + +/// Use `dependencyWithProtocol:isRequired:` instead. +- (instancetype)init NS_UNAVAILABLE; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Messaging/CHANGELOG.md b/Firebase/Messaging/CHANGELOG.md index 4148f76..d0f7da7 100644 --- a/Firebase/Messaging/CHANGELOG.md +++ b/Firebase/Messaging/CHANGELOG.md @@ -1,9 +1,9 @@ -# 2015-07-10 -- v3.0.3 +# 2018-07-10 -- v3.0.3 - Fixed an issue that client should suspend the topic requests when token is not available and resume the topic operation when the token is generated. - Corrected the deprecation warning when subscribing to or unsubscribing from an invalid topic. (#1397) - Removed unused heart beat time stamp tracking. -# 2015-06-12 -- v3.0.2 +# 2018-06-12 -- v3.0.2 - Added a warning message when subscribing to topics with incorrect name formats. - Silenced a deprecation warning in FIRMessaging. diff --git a/cmake/FindGRPC.cmake b/cmake/FindGRPC.cmake index 22c9655..ba8f857 100644 --- a/cmake/FindGRPC.cmake +++ b/cmake/FindGRPC.cmake @@ -56,20 +56,10 @@ find_package(OpenSSL REQUIRED) ## C-Ares -find_library( - CARES_LIBRARY - NAMES cares - HINTS ${BINARY_DIR}/src/grpc-build/third_party/cares/cares/lib -) -if(NOT (CARES_LIBRARY STREQUAL "CARES_LIBRARY-NOTFOUND")) - if (NOT TARGET c-ares::ares) - add_library(c-ares::ares UNKNOWN IMPORTED) - set_target_properties( - c-ares::ares PROPERTIES - IMPORTED_LOCATION ${CARES_LIBRARY} - ) - endif() +if(NOT c-ares_DIR) + set(c-ares_DIR ${FIREBASE_INSTALL_DIR}/lib/cmake/c-ares) endif() +find_package(c-ares CONFIG REQUIRED) ## GRPC @@ -124,7 +114,7 @@ if(GRPC_FOUND) if (NOT TARGET grpc::grpc) set( GRPC_LINK_LIBRARIES - c-ares::ares + c-ares::cares grpc::gpr OpenSSL::SSL OpenSSL::Crypto diff --git a/cmake/external/c-ares.cmake b/cmake/external/c-ares.cmake new file mode 100644 index 0000000..4c866c1 --- /dev/null +++ b/cmake/external/c-ares.cmake @@ -0,0 +1,34 @@ +# 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. + +include(ExternalProject) + +ExternalProject_Add( + c-ares + + DOWNLOAD_DIR ${FIREBASE_DOWNLOAD_DIR} + URL https://github.com/c-ares/c-ares/archive/cares-1_14_0.tar.gz + URL_HASH SHA256=62dd12f0557918f89ad6f5b759f0bf4727174ae9979499f5452c02be38d9d3e8 + + PREFIX ${PROJECT_BINARY_DIR}/external/cares + + CMAKE_ARGS + -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} + -DCMAKE_INSTALL_PREFIX:STRING=${FIREBASE_INSTALL_DIR} + -DCARES_STATIC:BOOL=ON + -DCARES_SHARED:BOOL=OFF + -DCARES_STATIC_PIC:BOOL=ON + + TEST_COMMAND "" +) diff --git a/cmake/external/googletest.cmake b/cmake/external/googletest.cmake index 3af136e..b80c098 100644 --- a/cmake/external/googletest.cmake +++ b/cmake/external/googletest.cmake @@ -27,6 +27,5 @@ ExternalProject_Add( CMAKE_CACHE_ARGS -DCMAKE_INSTALL_PREFIX:STRING=${FIREBASE_INSTALL_DIR} - UPDATE_COMMAND "" TEST_COMMAND "" ) diff --git a/cmake/external/grpc.cmake b/cmake/external/grpc.cmake index ee4d246..a789838 100644 --- a/cmake/external/grpc.cmake +++ b/cmake/external/grpc.cmake @@ -14,7 +14,6 @@ include(ExternalProject) include(ExternalProjectFlags) -include(FindZLIB) if(GRPC_ROOT) # If the user has supplied a GRPC_ROOT then just use it. Add an empty custom @@ -25,7 +24,6 @@ else() set( GIT_SUBMODULES third_party/boringssl - third_party/cares/cares ) set( @@ -47,44 +45,55 @@ else() ) + ## c-ares + if(NOT c-ares_DIR) + set(c-ares_DIR ${FIREBASE_INSTALL_DIR}/lib/cmake/c-ares) + endif() + + list( + APPEND CMAKE_ARGS + -DgRPC_CARES_PROVIDER:STRING=package + -Dc-ares_DIR:PATH=${c-ares_DIR} + ) + + ## protobuf # Unlike other dependencies of gRPC, we control the protobuf version because we # have checked-in protoc outputs that must match the runtime. # The location where protobuf-config.cmake will be installed varies by platform - if (WIN32) - set(PROTOBUF_CMAKE_DIR "${FIREBASE_INSTALL_DIR}/cmake") - else() - set(PROTOBUF_CMAKE_DIR "${FIREBASE_INSTALL_DIR}/lib/cmake/protobuf") + if(NOT Protobuf_DIR) + if(WIN32) + set(Protobuf_DIR "${FIREBASE_INSTALL_DIR}/cmake") + else() + set(Protobuf_DIR "${FIREBASE_INSTALL_DIR}/lib/cmake/protobuf") + endif() endif() list( APPEND CMAKE_ARGS -DgRPC_PROTOBUF_PROVIDER:STRING=package -DgRPC_PROTOBUF_PACKAGE_TYPE:STRING=CONFIG - -DProtobuf_DIR:PATH=${PROTOBUF_CMAKE_DIR} + -DProtobuf_DIR:PATH=${Protobuf_DIR} ) ## zlib - # zlib can be built by grpc but we can avoid it on platforms that provide it - # by default. - find_package(ZLIB) + # cmake/external/zlib.cmake figures out whether or not to build zlib. Either + # way, from the gRPC build's point of view it's a package. + list( + APPEND CMAKE_ARGS + -DgRPC_ZLIB_PROVIDER:STRING=package + ) if(ZLIB_FOUND) + # Propagate possible user configuration to FindZLIB.cmake in the sub-build. list( APPEND CMAKE_ARGS - -DgRPC_ZLIB_PROVIDER:STRING=package -DZLIB_INCLUDE_DIR=${ZLIB_INCLUDE_DIR} -DZLIB_LIBRARY=${ZLIB_LIBRARY} ) - - else() - list( - APPEND GIT_SUBMODULES - third_party/zlib - ) endif() @@ -98,7 +107,9 @@ else() ExternalProject_Add( grpc DEPENDS + c-ares protobuf + zlib ${GRPC_GIT} diff --git a/cmake/external/leveldb.cmake b/cmake/external/leveldb.cmake index db8aa4c..9daab02 100644 --- a/cmake/external/leveldb.cmake +++ b/cmake/external/leveldb.cmake @@ -66,7 +66,6 @@ else() INSTALL_DIR ${FIREBASE_INSTALL_DIR} - UPDATE_COMMAND "" INSTALL_COMMAND "" TEST_COMMAND "" ) diff --git a/cmake/external/nanopb.cmake b/cmake/external/nanopb.cmake index 39eb833..b5c74ce 100644 --- a/cmake/external/nanopb.cmake +++ b/cmake/external/nanopb.cmake @@ -34,6 +34,5 @@ ExternalProject_Add( -Dnanopb_BUILD_GENERATOR:BOOL=ON -Dnanopb_PROTOC_PATH:STRING=${NANOPB_PROTOC_BIN} - UPDATE_COMMAND "" TEST_COMMAND "" ) diff --git a/cmake/external/protobuf.cmake b/cmake/external/protobuf.cmake index 66d1426..e8060b2 100644 --- a/cmake/external/protobuf.cmake +++ b/cmake/external/protobuf.cmake @@ -105,6 +105,5 @@ ExternalProject_Add( ${commands} - UPDATE_COMMAND "" TEST_COMMAND "" ) diff --git a/cmake/external/zlib.cmake b/cmake/external/zlib.cmake new file mode 100644 index 0000000..a90a511 --- /dev/null +++ b/cmake/external/zlib.cmake @@ -0,0 +1,40 @@ +# 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. + +include(ExternalProject) + +# Use a system- or user-supplied zlib if available +find_package(ZLIB) +if(ZLIB_FOUND) + add_custom_target(zlib) + +else() + ExternalProject_Add( + zlib + + DOWNLOAD_DIR ${FIREBASE_DOWNLOAD_DIR} + DOWNLOAD_NAME zlib-v1.2.11.tar.gz + URL https://github.com/madler/zlib/archive/v1.2.11.tar.gz + URL_HASH SHA256=629380c90a77b964d896ed37163f5c3a34f6e6d897311f1df2a7016355c45eff + + PREFIX ${PROJECT_BINARY_DIR}/external/zlib + + CMAKE_ARGS + -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} + -DCMAKE_INSTALL_PREFIX:STRING=${FIREBASE_INSTALL_DIR} + -DBUILD_SHARED_LIBS:BOOL=OFF + + TEST_COMMAND "" + ) +endif() |