diff --git a/AuthSamples/Samples.xcodeproj/project.pbxproj b/AuthSamples/Samples.xcodeproj/project.pbxproj index 647c1758c66..69990c0eb2e 100644 --- a/AuthSamples/Samples.xcodeproj/project.pbxproj +++ b/AuthSamples/Samples.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 52C975C71EE10B1304EBBEB2 /* Pods_SwiftSample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BC8C39EF1F42A0C750FF5186 /* Pods_SwiftSample.framework */; }; 569C3F4E18627674CABE02AE /* Pods_EarlGreyTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AEE2E563FADF8C3382956B4F /* Pods_EarlGreyTests.framework */; }; 67AFFB52FF0FC4668D92F2E4 /* Pods_Sample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D5FE06BD9AA795DFBA9EFAAD /* Pods_Sample.framework */; }; + 7E04ACA41F565EA900788114 /* FIRAuthURLPresenterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E04ACA31F565EA900788114 /* FIRAuthURLPresenterTests.m */; }; 7E9969EE1F4E277900627C2B /* FIRGetProjectConfigRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E9969EC1F4E277900627C2B /* FIRGetProjectConfigRequestTests.m */; }; 7EDFD35B1F0EA29200B29DC5 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 7EDFD35D1F0EA29200B29DC5 /* Localizable.strings */; }; 7EEEFEE61F4E4F75000FF966 /* FIRGetProjectConfigResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7EEA8ADD1F4E4E840014A23B /* FIRGetProjectConfigResponseTests.m */; }; @@ -183,6 +184,7 @@ 4FFAD3F37BC4D7CEF0CAD579 /* Pods_FirebaseAuthUnitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FirebaseAuthUnitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 57150555A6B03949ECB58AD9 /* Pods-TestApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TestApp.debug.xcconfig"; path = "Pods/Target Support Files/Pods-TestApp/Pods-TestApp.debug.xcconfig"; sourceTree = ""; }; 6EC09307D636721EAAB89BB2 /* Pods_ApiTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ApiTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7E04ACA31F565EA900788114 /* FIRAuthURLPresenterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRAuthURLPresenterTests.m; path = ../../Example/Auth/Tests/FIRAuthURLPresenterTests.m; sourceTree = ""; }; 7E0BC64A1F199D86008BE4E0 /* fr-FR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-FR"; path = "fr-FR.lproj/Localizable.strings"; sourceTree = ""; }; 7E0BC64E1F19A77C008BE4E0 /* ru-RU */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ru-RU"; path = "ru-RU.lproj/Localizable.strings"; sourceTree = ""; }; 7E9969EC1F4E277900627C2B /* FIRGetProjectConfigRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIRGetProjectConfigRequestTests.m; path = ../../Example/Auth/Tests/FIRGetProjectConfigRequestTests.m; sourceTree = ""; }; @@ -557,6 +559,7 @@ DE5371B21EA7E89D000DA57F /* Tests-Info.plist */, 7E9969EC1F4E277900627C2B /* FIRGetProjectConfigRequestTests.m */, 7EEA8ADD1F4E4E840014A23B /* FIRGetProjectConfigResponseTests.m */, + 7E04ACA31F565EA900788114 /* FIRAuthURLPresenterTests.m */, ); path = UnitTests; sourceTree = ""; @@ -1332,6 +1335,7 @@ DECEA56E1EBBED1200273585 /* FIRAuthAppCredentialManagerTests.m in Sources */, DE5371DE1EA7E89D000DA57F /* OCMStubRecorder+FIRAuthUnitTests.m in Sources */, DE5371DC1EA7E89D000DA57F /* FIRVerifyPhoneNumberRequestTests.m in Sources */, + 7E04ACA41F565EA900788114 /* FIRAuthURLPresenterTests.m in Sources */, DE5371B51EA7E89D000DA57F /* FIRAuthAppCredentialTests.m in Sources */, DE5371CE1EA7E89D000DA57F /* FIRSetAccountInfoRequestTests.m in Sources */, DE5371D41EA7E89D000DA57F /* FIRVerifyAssertionRequestTests.m in Sources */, diff --git a/Example/Auth/Tests/FIRAuthURLPresenterTests.m b/Example/Auth/Tests/FIRAuthURLPresenterTests.m new file mode 100644 index 00000000000..602c54afc4f --- /dev/null +++ b/Example/Auth/Tests/FIRAuthURLPresenterTests.m @@ -0,0 +1,183 @@ +/* + * 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 +#import +#import + +#import "FIRAuthURLPresenter.h" +#import "FIRAuthUIDelegate.h" +#import + +/** @var kExpectationTimeout + @brief The maximum time waiting for expectations to fulfill. + */ +static NSTimeInterval kExpectationTimeout = 1; + +@interface FIRAuthDefaultUIDelegate : NSObject +/** @fn defaultUIDelegate + @brief Returns a default FIRAuthUIDelegate object. + @return The default FIRAuthUIDelegate object. + */ ++ (id)defaultUIDelegate; +@end + +@interface FIRAuthURLPresenterTests : XCTestCase + +@end + +@implementation FIRAuthURLPresenterTests + +/** @fn testFIRAuthURLPresenterNonNilUIDelegate + @brief Tests @c FIRAuthURLPresenter class showing UI with a non-nil UIDelegate. + */ +- (void)testFIRAuthURLPresenterNonNilUIDelegate { + id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate)); + NSURL *presenterURL = [NSURL URLWithString:@"https://presenter.url"]; + FIRAuthURLPresenter *presenter = [[FIRAuthURLPresenter alloc] init]; + + if ([SFSafariViewController class]) { + XCTestExpectation *callbackMatcherExpectation = + [self expectationWithDescription:@"callbackMatcher callback"]; + FIRAuthURLCallbackMatcher callbackMatcher = ^BOOL(NSURL *_Nonnull callbackURL) { + XCTAssertEqualObjects(callbackURL, presenterURL); + [callbackMatcherExpectation fulfill]; + return YES; + }; + + XCTestExpectation *completionBlockExpectation = + [self expectationWithDescription:@"completion callback"]; + FIRAuthURLPresentationCompletion completionBlock = ^(NSURL *_Nullable callbackURL, + NSError *_Nullable error) { + XCTAssertEqualObjects(callbackURL, presenterURL); + XCTAssertNil(error); + [completionBlockExpectation fulfill]; + }; + + id presenterArg = [OCMArg isKindOfClass:[SFSafariViewController class]]; + OCMExpect([mockUIDelegate presentViewController:presenterArg + animated:YES + completion:nil]).andDo(^(NSInvocation *invocation) { + __unsafe_unretained id unretainedArgument; + // Indices 0 and 1 indicate the hidden arguments self and _cmd. + // `presentViewController` is at index 2. + [invocation getArgument:&unretainedArgument atIndex:2]; + + SFSafariViewController *viewController = unretainedArgument; + XCTAssertEqual(viewController.delegate, presenter); + XCTAssertTrue([viewController isKindOfClass:[SFSafariViewController class]]); + }); + [presenter presentURL:presenterURL + UIDelegate:mockUIDelegate + callbackMatcher:callbackMatcher + completion:completionBlock]; + OCMVerifyAll(mockUIDelegate); + OCMExpect([mockUIDelegate dismissViewControllerAnimated:OCMOCK_ANY + completion:OCMOCK_ANY]). + andDo(^(NSInvocation *invocation) { + __unsafe_unretained id unretainedArgument; + // Indices 0 and 1 indicate the hidden arguments self and _cmd. + // `completion` is at index 3. + [invocation getArgument:&unretainedArgument atIndex:3]; + void (^finishBlock)() = unretainedArgument; + finishBlock(); + }); + [presenter canHandleURL:presenterURL]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + OCMVerifyAll(mockUIDelegate); + } +} + +/** @fn testFIRAuthURLPresenterNilUIDelegate + @brief Tests @c FIRAuthURLPresenter class showing UI with a nil UIDelegate. + */ +- (void)testFIRAuthURLPresenterNilUIDelegate { + NSURL *presenterURL = [NSURL URLWithString:@"https://presenter.url"]; + FIRAuthURLPresenter *presenter = [[FIRAuthURLPresenter alloc] init]; + + if ([SFSafariViewController class]) { + XCTestExpectation *callbackMatcherExpectation = + [self expectationWithDescription:@"callbackMatcher callback"]; + FIRAuthURLCallbackMatcher callbackMatcher = ^BOOL(NSURL *_Nonnull callbackURL) { + XCTAssertEqualObjects(callbackURL, presenterURL); + [callbackMatcherExpectation fulfill]; + return YES; + }; + XCTestExpectation *completionBlockExpectation = + [self expectationWithDescription:@"completion callback"]; + FIRAuthURLPresentationCompletion completionBlock = ^(NSURL *_Nullable callbackURL, + NSError *_Nullable error) { + XCTAssertEqualObjects(callbackURL, presenterURL); + XCTAssertNil(error); + [completionBlockExpectation fulfill]; + }; + + id mockUIDelegate = OCMProtocolMock(@protocol(FIRAuthUIDelegate)); + + // Swizzle default UIDelegate + Method method = class_getClassMethod([FIRAuthDefaultUIDelegate class], + @selector(defaultUIDelegate)); + __block IMP originalImplementation; + IMP newImplmentation = imp_implementationWithBlock(^id(id object) { + return mockUIDelegate; + }); + originalImplementation = method_setImplementation(method, newImplmentation); + if ([SFSafariViewController class]) { + id presenterArg = [OCMArg isKindOfClass:[SFSafariViewController class]]; + OCMExpect([mockUIDelegate presentViewController:presenterArg + animated:[OCMArg any] + completion:[OCMArg any]]). + andDo(^(NSInvocation *invocation) { + __unsafe_unretained id unretainedArgument; + // Indices 0 and 1 indicate the hidden arguments self and _cmd. + // `presentViewController` is at index 2. + [invocation getArgument:&unretainedArgument atIndex:2]; + SFSafariViewController *viewController = unretainedArgument; + XCTAssertEqual(viewController.delegate, presenter); + XCTAssertTrue([viewController isKindOfClass:[SFSafariViewController class]]); + }); + } + [presenter presentURL:presenterURL + UIDelegate:nil + callbackMatcher:callbackMatcher + completion:completionBlock]; + OCMVerifyAll(mockUIDelegate); + + OCMExpect([mockUIDelegate dismissViewControllerAnimated:OCMOCK_ANY + completion:OCMOCK_ANY]). + andDo(^(NSInvocation *invocation) { + __unsafe_unretained id unretainedArgument; + // Indices 0 and 1 indicate the hidden arguments self and _cmd. + // `completion` is at index 3. + [invocation getArgument:&unretainedArgument atIndex:3]; + void (^finishBlock)() = unretainedArgument; + finishBlock(); + }); + [presenter canHandleURL:presenterURL]; + [self waitForExpectationsWithTimeout:kExpectationTimeout handler:nil]; + OCMVerifyAll(mockUIDelegate); + // Unswizzle. + imp_removeBlock(method_setImplementation(method, originalImplementation)); + } +} + +#pragma mark - Method Swizzling + ++ (id)mockDefaultUIDelegate { + return OCMProtocolMock(@protocol(FIRAuthUIDelegate)); +} + +@end diff --git a/Example/Auth/Tests/FIRGetProjectConfigRequestTests.m b/Example/Auth/Tests/FIRGetProjectConfigRequestTests.m index 82f854e6d53..2c5d5fd1213 100644 --- a/Example/Auth/Tests/FIRGetProjectConfigRequestTests.m +++ b/Example/Auth/Tests/FIRGetProjectConfigRequestTests.m @@ -87,7 +87,7 @@ - (void)testGetProjectConfigRequest { gAPIHost, kGetProjectConfigEndPoint, kTestAPIKey]; - XCTAssertTrue([URLString isEqualToString:[request requestURL]]); + XCTAssertEqualObjects(URLString, [request requestURL].absoluteString); } @end diff --git a/Example/Firebase.xcodeproj/project.pbxproj b/Example/Firebase.xcodeproj/project.pbxproj index 70a51dfb432..e07302d30a6 100644 --- a/Example/Firebase.xcodeproj/project.pbxproj +++ b/Example/Firebase.xcodeproj/project.pbxproj @@ -104,6 +104,7 @@ 79A15731AA31012CD937CF3A /* Pods_Core_Example_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CAD129FFEC477E1129AE6AA1 /* Pods_Core_Example_iOS.framework */; }; 7A02646DEF386689CCFB9011 /* Pods_Core_Tests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AE6C9DD139E1FD21DC0F1082 /* Pods_Core_Tests_iOS.framework */; }; 7AE9A7433F2BD9A52022AC71 /* Pods_Messaging_Tests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4F902A29FA956ADD762F6921 /* Pods_Messaging_Tests_iOS.framework */; }; + 7E9485421F578AC4005A3939 /* FIRAuthURLPresenterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7E94853F1F578A9D005A3939 /* FIRAuthURLPresenterTests.m */; }; 960665EC1C5F7A0E843A354F /* Pods_Database_Tests_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 97768125F45377F35CA86EDC /* Pods_Database_Tests_macOS.framework */; }; AFAF36F51EC28C25004BDEE5 /* Shared.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AFAF36F41EC28C25004BDEE5 /* Shared.xcassets */; }; AFAF36F61EC28C25004BDEE5 /* Shared.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AFAF36F41EC28C25004BDEE5 /* Shared.xcassets */; }; @@ -285,6 +286,10 @@ D0FE8A951ED9CAAE003F6722 /* FIRViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = D0FE8A1C1ED9C6D2003F6722 /* FIRViewController.m */; }; D0FE8A961ED9CAAE003F6722 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = D0FE8A1D1ED9C6D2003F6722 /* main.m */; }; D22080FB4B7F4238FAC548D6 /* Pods_Auth_Tests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CECEF04E3B788FA2FA9B29F1 /* Pods_Auth_Tests_iOS.framework */; }; + D9B0D41E1F578F6D00A567C2 /* FIRGetProjectConfigRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D92C82C61F578DF000D5EAFF /* FIRGetProjectConfigRequestTests.m */; }; + D9B0D41F1F578F6E00A567C2 /* FIRGetProjectConfigRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D92C82C61F578DF000D5EAFF /* FIRGetProjectConfigRequestTests.m */; }; + D9B0D4201F578F7200A567C2 /* FIRGetProjectConfigResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D92C82C51F578DF000D5EAFF /* FIRGetProjectConfigResponseTests.m */; }; + D9B0D4211F578F7300A567C2 /* FIRGetProjectConfigResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D92C82C51F578DF000D5EAFF /* FIRGetProjectConfigResponseTests.m */; }; DE0E5BBB1EA7D92E00FAA825 /* FIRVerifyClientRequestTest.m in Sources */ = {isa = PBXBuildFile; fileRef = DE0E5BB91EA7D92E00FAA825 /* FIRVerifyClientRequestTest.m */; }; DE0E5BBC1EA7D92E00FAA825 /* FIRVerifyClientResponseTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE0E5BBA1EA7D92E00FAA825 /* FIRVerifyClientResponseTests.m */; }; DE0E5BBD1EA7D93100FAA825 /* FIRAuthAppCredentialTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE0E5BB51EA7D91C00FAA825 /* FIRAuthAppCredentialTests.m */; }; @@ -735,6 +740,7 @@ 6F7376F39846E902979416D4 /* Pods_Database_Example_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Database_Example_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 6FAA689FDCBD3261300292D5 /* Pods-Database_IntegrationTests_macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Database_IntegrationTests_macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Database_IntegrationTests_macOS/Pods-Database_IntegrationTests_macOS.debug.xcconfig"; sourceTree = ""; }; 73B480AA654FC97FA72C6293 /* Pods_Storage_Example_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Storage_Example_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7E94853F1F578A9D005A3939 /* FIRAuthURLPresenterTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRAuthURLPresenterTests.m; sourceTree = ""; }; 81E83B5ABAE219234F213B27 /* Pods_Database_Tests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Database_Tests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 8496034D8156555C5FCF8F14 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 862CC98282A6123508E8CA49 /* Pods-Database_Example_macOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Database_Example_macOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Database_Example_macOS/Pods-Database_Example_macOS.release.xcconfig"; sourceTree = ""; }; @@ -825,6 +831,8 @@ D0FE8A8C1ED9C87B003F6722 /* Database_IntegrationTests_macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Database_IntegrationTests_macOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D4E15AD159B5F9FD595AD761 /* Pods-Auth_Example_macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth_Example_macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Auth_Example_macOS/Pods-Auth_Example_macOS.debug.xcconfig"; sourceTree = ""; }; D8324AEFAEEF81EEDE114E33 /* Pods-Auth_Tests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Auth_Tests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Auth_Tests_iOS/Pods-Auth_Tests_iOS.debug.xcconfig"; sourceTree = ""; }; + D92C82C51F578DF000D5EAFF /* FIRGetProjectConfigResponseTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRGetProjectConfigResponseTests.m; sourceTree = ""; }; + D92C82C61F578DF000D5EAFF /* FIRGetProjectConfigRequestTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRGetProjectConfigRequestTests.m; sourceTree = ""; }; DAEC0C3CA3C043F584C0D281 /* Pods-Database_IntegrationTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Database_IntegrationTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Database_IntegrationTests_iOS/Pods-Database_IntegrationTests_iOS.debug.xcconfig"; sourceTree = ""; }; DE0E5BB51EA7D91C00FAA825 /* FIRAuthAppCredentialTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRAuthAppCredentialTests.m; sourceTree = ""; }; DE0E5BB61EA7D91C00FAA825 /* FIRAuthAppDelegateProxyTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRAuthAppDelegateProxyTests.m; sourceTree = ""; }; @@ -1684,6 +1692,7 @@ DE9314F91E86C6FF0083EDBF /* Tests */ = { isa = PBXGroup; children = ( + 7E94853F1F578A9D005A3939 /* FIRAuthURLPresenterTests.m */, DE750DB51EB3DD4000A75E47 /* FIRAuthAPNSTokenManagerTests.m */, DE750DB61EB3DD4000A75E47 /* FIRAuthAPNSTokenTests.m */, DE750DB71EB3DD4000A75E47 /* FIRAuthAppCredentialManagerTests.m */, @@ -1715,6 +1724,8 @@ DE93150C1E86C6FF0083EDBF /* FIRGetAccountInfoResponseTests.m */, DE93150D1E86C6FF0083EDBF /* FIRGetOOBConfirmationCodeRequestTests.m */, DE93150E1E86C6FF0083EDBF /* FIRGetOOBConfirmationCodeResponseTests.m */, + D92C82C61F578DF000D5EAFF /* FIRGetProjectConfigRequestTests.m */, + D92C82C51F578DF000D5EAFF /* FIRGetProjectConfigResponseTests.m */, DE93150F1E86C6FF0083EDBF /* FIRGitHubAuthProviderTests.m */, DE9315111E86C6FF0083EDBF /* FIRResetPasswordRequestTests.m */, DE9315121E86C6FF0083EDBF /* FIRResetPasswordResponseTests.m */, @@ -3918,8 +3929,10 @@ D01853B11EDAD364003A645C /* FIRSignUpNewUserRequestTests.m in Sources */, D01853B21EDAD364003A645C /* FIRGetAccountInfoResponseTests.m in Sources */, D01853B31EDAD364003A645C /* FIRSetAccountInfoRequestTests.m in Sources */, + D9B0D41F1F578F6E00A567C2 /* FIRGetProjectConfigRequestTests.m in Sources */, D01853B41EDAD364003A645C /* FIRAuthAppCredentialTests.m in Sources */, D01853B51EDAD364003A645C /* FIRAuthSerialTaskQueueTests.m in Sources */, + D9B0D4211F578F7300A567C2 /* FIRGetProjectConfigResponseTests.m in Sources */, D01853B61EDAD364003A645C /* FIRApp+FIRAuthUnitTests.m in Sources */, D01853B71EDAD364003A645C /* FIRSetAccountInfoResponseTests.m in Sources */, D01853B81EDAD364003A645C /* FIRAuthTests.m in Sources */, @@ -4131,6 +4144,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D9B0D41E1F578F6D00A567C2 /* FIRGetProjectConfigRequestTests.m in Sources */, DE9315691E86C71C0083EDBF /* FIRGetOOBConfirmationCodeResponseTests.m in Sources */, DE9315661E86C71C0083EDBF /* FIRGetAccountInfoRequestTests.m in Sources */, DE9315731E86C71C0083EDBF /* FIRSignUpNewUserResponseTests.m in Sources */, @@ -4173,8 +4187,10 @@ DE0E5BBD1EA7D93100FAA825 /* FIRAuthAppCredentialTests.m in Sources */, DE93155E1E86C71C0083EDBF /* FIRAuthSerialTaskQueueTests.m in Sources */, DE9315581E86C71C0083EDBF /* FIRApp+FIRAuthUnitTests.m in Sources */, + D9B0D4201F578F7200A567C2 /* FIRGetProjectConfigResponseTests.m in Sources */, DE9315711E86C71C0083EDBF /* FIRSetAccountInfoResponseTests.m in Sources */, DE93155F1E86C71C0083EDBF /* FIRAuthTests.m in Sources */, + 7E9485421F578AC4005A3939 /* FIRAuthURLPresenterTests.m in Sources */, DE750DBD1EB3DD5B00A75E47 /* FIRAuthAPNSTokenTests.m in Sources */, DE0E5BBE1EA7D93500FAA825 /* FIRAuthAppDelegateProxyTests.m in Sources */, DE0E5BBC1EA7D92E00FAA825 /* FIRVerifyClientResponseTests.m in Sources */, diff --git a/Firebase/Auth/FirebaseAuth.podspec b/Firebase/Auth/FirebaseAuth.podspec index 3d7eb2f88b0..b213941ecc5 100644 --- a/Firebase/Auth/FirebaseAuth.podspec +++ b/Firebase/Auth/FirebaseAuth.podspec @@ -31,6 +31,7 @@ Simplify your iOS development, grow your user base, and monetize more effectivel 'Source/**/FIRAuthAPNSTokenType.[mh]', 'Source/**/FIRAuthAPNSToken.[mh]', 'Source/**/FIRPhoneAuthCredential.[mh]', + 'Source/**/FIRAuthDefaultUIDelegate.[mh]', 'Source/**/FIRPhoneAuthProvider.[mh]' s.public_header_files = 'Source/Public/*.h' s.preserve_paths = diff --git a/Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m b/Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m new file mode 100644 index 00000000000..118b73c3606 --- /dev/null +++ b/Firebase/Auth/Source/FIRAuthDefaultUIDelegate.m @@ -0,0 +1,85 @@ +/* + * 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 + +#import "FIRAuthUIDelegate.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRAuthDefaultUIDelegate : NSObject +/** @fn defaultUIDelegate + @brief Unavailable. Please use initWithViewController: + */ +- (instancetype)init NS_UNAVAILABLE; + +/** @fn initWithViewController: + @brief Initializes the instance with a view controller. + @param viewController The view controller as the presenting view controller in @c GOIUIDelegate. + @return The initialized instance. + */ +- (instancetype)initWithViewController:(UIViewController *)viewController NS_DESIGNATED_INITIALIZER; +@end + +@implementation FIRAuthDefaultUIDelegate { + /** @var _viewController + @brief The presenting view controller. + */ + UIViewController *_viewController; +} + +- (instancetype)initWithViewController:(UIViewController *)viewController { + self = [super init]; + if (self) { + _viewController = viewController; + } + return self; +} + +- (void)presentViewController:(UIViewController *)viewControllerToPresent + animated:(BOOL)flag + completion:(nullable void (^)(void))completion { + [_viewController presentViewController:viewControllerToPresent + animated:flag + completion:completion]; +} + +- (void)dismissViewControllerAnimated:(BOOL)flag completion:(nullable void (^)(void))completion { + [_viewController dismissViewControllerAnimated:flag completion:completion]; +} + ++ (id)defaultUIDelegate { + UIViewController *topViewController = + [UIApplication sharedApplication].keyWindow.rootViewController; + while (true){ + if (topViewController.presentedViewController) { + topViewController = topViewController.presentedViewController; + } else if ([topViewController isKindOfClass:[UINavigationController class]]) { + UINavigationController *nav = (UINavigationController *)topViewController; + topViewController = nav.topViewController; + } else if ([topViewController isKindOfClass:[UITabBarController class]]) { + UITabBarController *tab = (UITabBarController *)topViewController; + topViewController = tab.selectedViewController; + } else { + break; + } + } + return [[FIRAuthDefaultUIDelegate alloc] initWithViewController:topViewController]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/FIRAuthErrorUtils.h b/Firebase/Auth/Source/FIRAuthErrorUtils.h index c2c91710ac3..4fdfe9bd104 100644 --- a/Firebase/Auth/Source/FIRAuthErrorUtils.h +++ b/Firebase/Auth/Source/FIRAuthErrorUtils.h @@ -451,6 +451,20 @@ NS_ASSUME_NONNULL_BEGIN */ + (NSError *)captchaCheckFailedErrorWithMessage:(nullable NSString *)message; +/** @fn webContextAlreadyPresentedErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeWebContextAlreadyPresented code. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)webContextAlreadyPresentedErrorWithMessage:(nullable NSString *)message; + +/** @fn webContextCancelledErrorWithMessage: + @brief Constructs an @c NSError with the @c FIRAuthErrorCodeWebContextCancelledcode. + @param message Error message from the backend, if any. + @return The NSError instance associated with the given FIRAuthError. + */ ++ (NSError *)webContextCancelledErrorWithMessage:(nullable NSString *)message; + /** @fn keychainErrorWithFunction:status: @brief Constructs an @c NSError with the @c FIRAuthErrorCodeKeychainError code. @param keychainFunction The keychain function which was invoked and yielded an unexpected diff --git a/Firebase/Auth/Source/FIRAuthErrorUtils.m b/Firebase/Auth/Source/FIRAuthErrorUtils.m index 0439f81697b..ad9ad55f09c 100644 --- a/Firebase/Auth/Source/FIRAuthErrorUtils.m +++ b/Firebase/Auth/Source/FIRAuthErrorUtils.m @@ -345,6 +345,18 @@ static NSString *const kFIRAuthErrorMessageCaptchaCheckFailed = @"The reCAPTCHA response token " "provided is either invalid, expired or already"; +/** @var kFIRAuthErrorMessageWebContextAlreadyPresented + @brief Message for @c FIRAuthErrorCodeWebContextAlreadyPresented error code. + */ +static NSString *const kFIRAuthErrorMessageWebContextAlreadyPresented = @"User interaction is " + "still ongoing, another view cannot be presented."; + +/** @var kFIRAuthErrorMessageWebContextCancelled + @brief Message for @c FIRAuthErrorCodeWebContextCancelled error code. + */ +static NSString *const kFIRAuthErrorMessageWebContextCancelled = @"The interaction was cancelled " + "by the user."; + /** @var kFIRAuthErrorMessageInternalError @brief Message for @c FIRAuthErrorCodeInternalError error code. */ @@ -455,6 +467,10 @@ return kFIRAuthErrorMessageAppNotVerified; case FIRAuthErrorCodeCaptchaCheckFailed: return kFIRAuthErrorMessageCaptchaCheckFailed; + case FIRAuthErrorCodeWebContextAlreadyPresented: + return kFIRAuthErrorMessageWebContextAlreadyPresented; + case FIRAuthErrorCodeWebContextCancelled: + return kFIRAuthErrorMessageWebContextCancelled; } } @@ -562,6 +578,10 @@ return @"ERROR_APP_NOT_VERIFIED"; case FIRAuthErrorCodeCaptchaCheckFailed: return @"ERROR_CAPTCHA_CHECK_FAILED"; + case FIRAuthErrorCodeWebContextAlreadyPresented: + return @"ERROR_WEB_CONTEXT_ALREADY_PRESENTED"; + case FIRAuthErrorCodeWebContextCancelled: + return @"ERROR_WEB_CONTEXT_CANCELLED"; } } @@ -873,6 +893,14 @@ + (NSError *)captchaCheckFailedErrorWithMessage:(nullable NSString *)message { return [self errorWithCode:FIRAuthInternalErrorCodeCaptchaCheckFailed message:message]; } ++ (NSError *)webContextAlreadyPresentedErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeWebContextAlreadyPresented message:message]; +} + ++ (NSError *)webContextCancelledErrorWithMessage:(nullable NSString *)message { + return [self errorWithCode:FIRAuthInternalErrorCodeWebContextCancelled message:message]; +} + + (NSError *)keychainErrorWithFunction:(NSString *)keychainFunction status:(OSStatus)status { NSString *failureReason = [NSString stringWithFormat:@"%@ (%li)", keychainFunction, (long)status]; return [self errorWithCode:FIRAuthInternalErrorCodeKeychainError userInfo:@{ diff --git a/Firebase/Auth/Source/FIRAuthInternalErrors.h b/Firebase/Auth/Source/FIRAuthInternalErrors.h index 724b95c23c3..ffdb068a6ba 100644 --- a/Firebase/Auth/Source/FIRAuthInternalErrors.h +++ b/Firebase/Auth/Source/FIRAuthInternalErrors.h @@ -322,6 +322,17 @@ typedef NS_ENUM(NSInteger, FIRAuthInternalErrorCode) { FIRAuthInternalErrorCodeQuotaExceeded = FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeQuotaExceeded, + /** Indicates that an attempt was made to present a new web context while one was already being + presented. + */ + FIRAuthInternalErrorCodeWebContextAlreadyPresented = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeWebContextAlreadyPresented, + + /** Indicates that the URL presentation was cancelled prematurely by the user. + */ + FIRAuthInternalErrorCodeWebContextCancelled = + FIRAuthPublicErrorCodeFlag | FIRAuthErrorCodeWebContextCancelled, + // The enum values between 17046 and 17051 are reserved and should NOT be used for new error // codes. diff --git a/Firebase/Auth/Source/FIRAuthURLPresenter.h b/Firebase/Auth/Source/FIRAuthURLPresenter.h new file mode 100644 index 00000000000..694b3982d2f --- /dev/null +++ b/Firebase/Auth/Source/FIRAuthURLPresenter.h @@ -0,0 +1,61 @@ +/* + * 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 + +NS_ASSUME_NONNULL_BEGIN + +@protocol FIRAuthUIDelegate; + +/** @typedef FIRAuthURLPresentationCompletion + @brief The type of block invoked when the URLPresentation completes. + @param callbackURL The callback URL if the presentation ends with a matching callback. + @param error The error if the presentation fails to start or ends with an error. + */ +typedef void (^FIRAuthURLPresentationCompletion)(NSURL *_Nullable callbackURL, + NSError *_Nullable error); + +/** @typedef FIRAuthCallbackMatcher + @brief The type of block invoked for checking whether a callback URL matches. + @param callbackURL The callback URL to check for match. + @return Whether or not the specific callback URL matches or not. + */ +typedef BOOL (^FIRAuthURLCallbackMatcher)(NSURL * _Nullable callbackURL); + +@interface FIRAuthURLPresenter : NSObject + +/** @fn presentURL:UIDelegate:callbackMatcher:completion: + @brief Presents an URL to interact with user. + @param URL The URL to present. + @param UIDelegate The UI delegate to present view controller. + @param completion A block to be called either synchronously if the presentation fails to start, + or asynchronously in future on an unspecified thread once the presentation finishes. + */ +- (void)presentURL:(NSURL *)URL + UIDelegate:(nullable id)UIDelegate + callbackMatcher:(FIRAuthURLCallbackMatcher)callbackMatcher + completion:(FIRAuthURLPresentationCompletion)completion; + +/** @fn canHandleURL: + @brief Determines if a URL was produced by the currently presented URL. + @param URL The URL to handle. + @return Whether the URL could be handled or not. + */ +- (BOOL)canHandleURL:(NSURL *)URL; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/FIRAuthURLPresenter.m b/Firebase/Auth/Source/FIRAuthURLPresenter.m new file mode 100644 index 00000000000..90ceed61b02 --- /dev/null +++ b/Firebase/Auth/Source/FIRAuthURLPresenter.m @@ -0,0 +1,137 @@ +/* + * 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 "FIRAuthURLPresenter.h" + +#import "FIRAuthErrorUtils.h" +#import "FIRAuthUIDelegate.h" + +@import SafariServices; + +NS_ASSUME_NONNULL_BEGIN + +@interface FIRAuthDefaultUIDelegate : NSObject +/** @fn defaultUIDelegate + @brief Returns a default FIRAuthUIDelegate object. + @return The default FIRAuthUIDelegate object. + */ ++ (id)defaultUIDelegate; +@end + +@interface FIRAuthURLPresenter () +@end + +@implementation FIRAuthURLPresenter { + /** @var _callbackMatcher + @brief The callback URL matcher for the current presentation, if one is active. + */ + FIRAuthURLCallbackMatcher _Nullable _callbackMatcher; + + /** @var _safariViewController + @brief The SFSafariViewController used for the current presentation, if any. + */ + SFSafariViewController *_Nullable _safariViewController; + + /** @var _UIDelegate + @brief The UIDelegate used to present the SFSafariViewController. + */ + id _UIDelegate; + + /** @var _completion + @brief The completion handler for the current presentaion, if one is active. + @remarks This variable is also used as a flag to indicate a presentation is active. + */ + FIRAuthURLPresentationCompletion _Nullable _completion; +} + +- (void)presentURL:(NSURL *)URL + UIDelegate:(nullable id)UIDelegate + callbackMatcher:(FIRAuthURLCallbackMatcher)callbackMatcher + completion:(FIRAuthURLPresentationCompletion)completion { + _callbackMatcher = callbackMatcher; + _completion = completion; + _UIDelegate = UIDelegate ?: [FIRAuthDefaultUIDelegate defaultUIDelegate]; + [self presentWebContextWithController:_UIDelegate URL:URL]; +} + +- (BOOL)canHandleURL:(NSURL *)URL { + if (_callbackMatcher(URL)) { + _callbackMatcher = nil; + [self finishPresentationWithURL:URL error:nil]; + return YES; + } + return NO; +} + +/** @fn presentWebContextWithController:URL: + @brief Presents a SFSafariViewController or WKWebView to display the contents of the URL + provided. + @param controller The controller used to present the SFSafariViewController or WKWebView. + @param URL The URL to display in the SFSafariViewController or WKWebView. + */ +- (void)presentWebContextWithController:(id)controller URL:(NSURL *)URL { + if ([SFSafariViewController class]) { + if (_safariViewController) { + // Unable to start a new presentation on top of modal SFSVC presentation. + _completion(nil, [FIRAuthErrorUtils webContextAlreadyPresentedErrorWithMessage:nil]); + return; + } + SFSafariViewController *safariViewController = [[SFSafariViewController alloc] initWithURL:URL]; + _safariViewController = safariViewController; + _safariViewController.delegate = self; + [controller presentViewController:safariViewController animated:YES completion:nil]; + return; + } +} + +#pragma mark - SFSafariViewControllerDelegate + +- (void)safariViewControllerDidFinish:(SFSafariViewController *)controller { + if (controller == _safariViewController) { + _safariViewController = nil; + //TODO:Ensure that the SFSafariViewController is actually removed from the screen before + //invoking finishPresentationWithURL:error: + [self finishPresentationWithURL:nil + error:[FIRAuthErrorUtils webContextCancelledErrorWithMessage:nil]]; + } +} + +#pragma mark - Private methods + +/** @fn finishPresentationWithURL:error: + @brief Finishes the presentation for a given URL, if any. + @param URL The URL to finish presenting. + @param error The error with which to finish presenting, if any. + */ +- (void)finishPresentationWithURL:(nullable NSURL *)URL + error:(nullable NSError *)error { + FIRAuthURLPresentationCompletion completion = _completion; + void (^finishBlock)() = ^() { + completion(URL, nil); + }; + _completion = nil; + if ([SFSafariViewController class]) { + SFSafariViewController *safariViewController = _safariViewController; + _safariViewController = nil; + if (safariViewController) { + [_UIDelegate dismissViewControllerAnimated:YES completion:finishBlock]; + } + } +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Auth/Source/Public/FIRAuthErrors.h b/Firebase/Auth/Source/Public/FIRAuthErrors.h index 6ab69008629..2b7ef965211 100644 --- a/Firebase/Auth/Source/Public/FIRAuthErrors.h +++ b/Firebase/Auth/Source/Public/FIRAuthErrors.h @@ -276,6 +276,15 @@ typedef NS_ENUM(NSInteger, FIRAuthErrorCode) { */ FIRAuthErrorCodeCaptchaCheckFailed = 17056, + /** Indicates that an attempt was made to present a new web context while one was already being + presented. + */ + FIRAuthErrorCodeWebContextAlreadyPresented = 17057, + + /** Indicates that the URL presentation was cancelled prematurely by the user. + */ + FIRAuthErrorCodeWebContextCancelled = 17058, + /** Indicates an error occurred while attempting to access the keychain. */ FIRAuthErrorCodeKeychainError = 17995, diff --git a/Firebase/Auth/Source/Public/FIRAuthUIDelegate.h b/Firebase/Auth/Source/Public/FIRAuthUIDelegate.h new file mode 100644 index 00000000000..7f9adb047ca --- /dev/null +++ b/Firebase/Auth/Source/Public/FIRAuthUIDelegate.h @@ -0,0 +1,51 @@ +/* + * 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 +#import + +NS_ASSUME_NONNULL_BEGIN + +/** @protocol FIRAuthUIDelegate + @brief A protocol to handle user interface interactions for Firebase Auth. +*/ + +@protocol FIRAuthUIDelegate + +/** @fn presentViewController:animated:completion: + @brief If implemented, this method will be invoked when Firebase Auth needs to display a view + controller. + @param viewControllerToPresent The view controller to be presented. + @param flag Decides whether the view controller presentation should be animated or not. + @param completion The block to execute after the presentation finishes. This block has no return + value and takes no parameters. +*/ +- (void)presentViewController:(UIViewController *)viewControllerToPresent + animated:(BOOL)flag + completion:(void (^ _Nullable)(void))completion; + +/** @fn dismissViewControllerAnimated:completion: + @brief If implemented, this method will be invoked when Firebase Auth needs to display a view + controller. + @param flag Decides whether removing the view controller should be animated or not. + @param completion The block to execute after the presentation finishes. This block has no return + value and takes no parameters. +*/ +- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^ _Nullable)(void))completion; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firebase/Messaging/FIRMMessageCode.h b/Firebase/Messaging/FIRMMessageCode.h index 5a30f848050..6afc1bb8a78 100644 --- a/Firebase/Messaging/FIRMMessageCode.h +++ b/Firebase/Messaging/FIRMMessageCode.h @@ -166,6 +166,7 @@ typedef NS_ENUM(NSInteger, FIRMessagingMessageCode) { kFIRMessagingMessageCodeTopicOption000 = 17000, // I-FCM017000 kFIRMessagingMessageCodeTopicOption001 = 17001, // I-FCM017001 kFIRMessagingMessageCodeTopicOption002 = 17002, // I-FCM017002 + kFIRMessagingMessageCodeTopicOptionTopicEncodingFailed = 17003, // I-FCM017003 // FIRMessagingUtilities.m kFIRMessagingMessageCodeUtilities000 = 18000, // I-FCM018000 kFIRMessagingMessageCodeUtilities001 = 18001, // I-FCM018001 diff --git a/Firebase/Messaging/FIRMessagingTopicOperation.m b/Firebase/Messaging/FIRMessagingTopicOperation.m index 955c4a685c9..90760eb04d2 100644 --- a/Firebase/Messaging/FIRMessagingTopicOperation.m +++ b/Firebase/Messaging/FIRMessagingTopicOperation.m @@ -165,6 +165,19 @@ - (void)performSubscriptionChange { [request setValue:appIdentifier forHTTPHeaderField:@"app"]; [request setValue:self.checkinService.versionInfo forHTTPHeaderField:@"info"]; + // Topic can contain special characters (like `%`) so encode the value. + NSCharacterSet *characterSet = [NSCharacterSet URLQueryAllowedCharacterSet]; + NSString *encodedTopic = + [self.topic stringByAddingPercentEncodingWithAllowedCharacters:characterSet]; + if (encodedTopic == nil) { + // The transformation was somehow not possible, so use the original topic. + FIRMessagingLoggerWarn(kFIRMessagingMessageCodeTopicOptionTopicEncodingFailed, + @"Unable to encode the topic '%@' during topic subscription change. " + @"Please ensure that the topic name contains only valid characters.", + self.topic); + encodedTopic = self.topic; + } + NSMutableString *content = [NSMutableString stringWithFormat: @"sender=%@&app=%@&device=%@&" @"app_ver=%@&X-gcm.topic=%@&X-scope=%@", @@ -172,8 +185,8 @@ - (void)performSubscriptionChange { appIdentifier, deviceAuthID, FIRMessagingCurrentAppVersion(), - self.topic, - self.topic]; + encodedTopic, + encodedTopic]; if (self.action == FIRMessagingTopicActionUnsubscribe) { [content appendString:@"&delete=true"]; diff --git a/FirebaseCommunity.podspec b/FirebaseCommunity.podspec index 6ad9b1c6a08..e978a676cd4 100644 --- a/FirebaseCommunity.podspec +++ b/FirebaseCommunity.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'FirebaseCommunity' - s.version = '0.1.0' + s.version = '0.1.1' s.summary = 'Firebase Open Source Libraries for iOS.' s.description = <<-DESC