feat(auth): verifyBeforeUpdateEmail API (#3862)

* feat(auth): 1st commit verifyBeforeUpdateEmail

* feat(auth): verifyBeforeUpdateEmail updates

* tests(auth): more coverage

* chore(ios): update podfile.lock

* tests(auth): update verifyBeforeUpdateEmail tests

* tests(auth): verifyBeforeUpdateEmail updates
[publish]
This commit is contained in:
Russell Wheatley
2020-07-10 14:08:12 +01:00
committed by GitHub
parent 9b73e0ad67
commit aaff624025
6 changed files with 649 additions and 5 deletions

View File

@@ -599,6 +599,52 @@ class ReactNativeFirebaseAuthModule extends ReactNativeFirebaseModule {
}
}
/**
* verifyBeforeUpdateEmail
*
* @param promise
*/
@ReactMethod
public void verifyBeforeUpdateEmail(
String appName,
String email,
ReadableMap actionCodeSettings,
final Promise promise
) {
FirebaseApp firebaseApp = FirebaseApp.getInstance(appName);
final FirebaseAuth firebaseAuth = FirebaseAuth.getInstance(firebaseApp);
FirebaseUser user = firebaseAuth.getCurrentUser();
Log.d(TAG, "verifyBeforeUpdateEmail");
if (user == null) {
promiseNoUser(promise, false);
Log.e(TAG, "verifyBeforeUpdateEmail:failure:noCurrentUser");
} else {
OnCompleteListener<Void> listener = task -> {
if (task.isSuccessful()) {
Log.d(TAG, "verifyBeforeUpdateEmail:onComplete:success");
promiseWithUser(firebaseAuth.getCurrentUser(), promise);
} else {
Exception exception = task.getException();
Log.e(TAG, "verifyBeforeUpdateEmail:onComplete:failure", exception);
promiseRejectAuthException(promise, exception);
}
};
if (actionCodeSettings == null) {
user
.verifyBeforeUpdateEmail(email)
.addOnCompleteListener(getExecutor(), listener);
} else {
ActionCodeSettings settings = buildActionCodeSettings(actionCodeSettings);
user
.verifyBeforeUpdateEmail(email, settings)
.addOnCompleteListener(getExecutor(), listener);
}
}
}
/**
* updateEmail
*

View File

@@ -154,6 +154,160 @@ describe('auth().currentUser', () => {
});
describe('sendEmailVerification()', () => {
it('should error if actionCodeSettings.url is not present', async () => {
const random = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase.auth().currentUser.sendEmailVerification({});
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql("'actionCodeSettings.url' expected a string value.");
}
await firebase.auth().currentUser.delete();
});
it('should error if actionCodeSettings.url is not a string', async () => {
const random = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase.auth().currentUser.sendEmailVerification({ url: 123 });
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql("'actionCodeSettings.url' expected a string value.");
}
await firebase.auth().currentUser.delete();
});
it('should error if actionCodeSettings.dynamicLinkDomain is not a string', async () => {
const random = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase
.auth()
.currentUser.sendEmailVerification({ url: 'string', dynamicLinkDomain: 123 });
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql(
"'actionCodeSettings.dynamicLinkDomain' expected a string value.",
);
}
await firebase.auth().currentUser.delete();
});
it('should error if handleCodeInApp is not a string', async () => {
const random = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase.auth().currentUser.sendEmailVerification({ url: 'string', handleCodeInApp: 123 });
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql(
"'actionCodeSettings.handleCodeInApp' expected a boolean value.",
);
}
await firebase.auth().currentUser.delete();
});
it('should error if actionCodeSettings.iOS is not an object', async () => {
const random = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase.auth().currentUser.sendEmailVerification({ url: 'string', iOS: 123 });
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql("'actionCodeSettings.iOS' expected an object value.");
}
await firebase.auth().currentUser.delete();
});
it('should error if actionCodeSettings.iOS.bundleId is not a string', async () => {
const random = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase
.auth()
.currentUser.sendEmailVerification({ url: 'string', iOS: { bundleId: 123 } });
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql(
"'actionCodeSettings.iOS.bundleId' expected a string value.",
);
}
await firebase.auth().currentUser.delete();
});
it('should error if actionCodeSettings.android is not an object', async () => {
const random = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase.auth().currentUser.sendEmailVerification({ url: 'string', android: 123 });
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql("'actionCodeSettings.android' expected an object value.");
}
await firebase.auth().currentUser.delete();
});
it('should error if actionCodeSettings.android.packageName is not a string', async () => {
const random = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase
.auth()
.currentUser.sendEmailVerification({ url: 'string', android: { packageName: 123 } });
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql(
"'actionCodeSettings.android.packageName' expected a string value.",
);
}
await firebase.auth().currentUser.delete();
});
it('should error if actionCodeSettings.android.installApp is not a string', async () => {
const random = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase.auth().currentUser.sendEmailVerification({
url: 'string',
android: { packageName: 'packageName', installApp: 123 },
});
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql(
"'actionCodeSettings.android.installApp' expected a boolean value.",
);
}
await firebase.auth().currentUser.delete();
});
it('should error if actionCodeSettings.android.minimumVersion is not a string', async () => {
const random = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase.auth().currentUser.sendEmailVerification({
url: 'string',
android: { packageName: 'packageName', minimumVersion: 123 },
});
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql(
"'actionCodeSettings.android.minimumVersion' expected a string value.",
);
}
await firebase.auth().currentUser.delete();
});
it('should not error', async () => {
const random = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
@@ -204,6 +358,254 @@ describe('auth().currentUser', () => {
});
});
describe('verifyBeforeUpdateEmail()', () => {
it('should error if newEmail is not a string', async () => {
const random = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase.auth().currentUser.verifyBeforeUpdateEmail(123);
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql("'newEmail' expected a string value");
}
await firebase.auth().currentUser.delete();
});
it('should error if actionCodeSettings.url is not present', async () => {
const random = Utils.randString(12, '#aA');
const random2 = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
const updateEmail = `${random2}@${random2}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, {});
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql("'actionCodeSettings.url' expected a string value.");
}
await firebase.auth().currentUser.delete();
});
it('should error if actionCodeSettings.url is not a string', async () => {
const random = Utils.randString(12, '#aA');
const random2 = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
const updateEmail = `${random2}@${random2}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, { url: 123 });
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql("'actionCodeSettings.url' expected a string value.");
}
await firebase.auth().currentUser.delete();
});
it('should error if actionCodeSettings.dynamicLinkDomain is not a string', async () => {
const random = Utils.randString(12, '#aA');
const random2 = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
const updateEmail = `${random2}@${random2}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, {
url: 'string',
dynamicLinkDomain: 123,
});
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql(
"'actionCodeSettings.dynamicLinkDomain' expected a string value.",
);
}
await firebase.auth().currentUser.delete();
});
it('should error if handleCodeInApp is not a boolean', async () => {
const random = Utils.randString(12, '#aA');
const random2 = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
const updateEmail = `${random2}@${random2}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, {
url: 'string',
handleCodeInApp: 123,
});
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql(
"'actionCodeSettings.handleCodeInApp' expected a boolean value.",
);
}
await firebase.auth().currentUser.delete();
});
it('should error if actionCodeSettings.iOS is not an object', async () => {
const random = Utils.randString(12, '#aA');
const random2 = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
const updateEmail = `${random2}@${random2}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, {
url: 'string',
iOS: 123,
});
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql("'actionCodeSettings.iOS' expected an object value.");
}
await firebase.auth().currentUser.delete();
});
it('should error if actionCodeSettings.iOS.bundleId is not a string', async () => {
const random = Utils.randString(12, '#aA');
const random2 = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
const updateEmail = `${random2}@${random2}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, {
url: 'string',
iOS: { bundleId: 123 },
});
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql(
"'actionCodeSettings.iOS.bundleId' expected a string value.",
);
}
await firebase.auth().currentUser.delete();
});
it('should error if actionCodeSettings.android is not an object', async () => {
const random = Utils.randString(12, '#aA');
const random2 = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
const updateEmail = `${random2}@${random2}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, {
url: 'string',
android: 123,
});
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql("'actionCodeSettings.android' expected an object value.");
}
await firebase.auth().currentUser.delete();
});
it('should error if actionCodeSettings.android.packageName is not a string', async () => {
const random = Utils.randString(12, '#aA');
const random2 = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
const updateEmail = `${random2}@${random2}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, {
url: 'string',
android: { packageName: 123 },
});
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql(
"'actionCodeSettings.android.packageName' expected a string value.",
);
}
await firebase.auth().currentUser.delete();
});
it('should error if actionCodeSettings.android.installApp is not a boolean', async () => {
const random = Utils.randString(12, '#aA');
const random2 = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
const updateEmail = `${random2}@${random2}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, {
url: 'string',
android: { packageName: 'string', installApp: 123 },
});
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql(
"'actionCodeSettings.android.installApp' expected a boolean value.",
);
}
await firebase.auth().currentUser.delete();
});
it('should error if actionCodeSettings.android.minimumVersion is not a string', async () => {
const random = Utils.randString(12, '#aA');
const random2 = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
const updateEmail = `${random2}@${random2}.com`;
await firebase.auth().createUserWithEmailAndPassword(email, random);
try {
firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, {
url: 'string',
android: { packageName: 'string', minimumVersion: 123 },
});
return Promise.reject(new Error('it did not error'));
} catch (error) {
error.message.should.containEql(
"'actionCodeSettings.android.minimumVersion' expected a string value.",
);
}
await firebase.auth().currentUser.delete();
});
it('should not error', async () => {
const random = Utils.randString(12, '#aA');
const random2 = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
const updateEmail = `${random2}@${random2}.com`;
try {
await firebase.auth().createUserWithEmailAndPassword(email, random);
await firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail);
} catch (_) {
return Promise.reject("'verifyBeforeUpdateEmail()' did not work");
}
await firebase.auth().currentUser.delete();
});
it('should work with actionCodeSettings', async () => {
const random = Utils.randString(12, '#aA');
const random2 = Utils.randString(12, '#aA');
const email = `${random}@${random}.com`;
const updateEmail = `${random2}@${random2}.com`;
const actionCodeSettings = {
url: 'https://react-native-firebase-testing.firebaseapp.com/foo',
};
try {
await firebase.auth().createUserWithEmailAndPassword(email, random);
await firebase.auth().currentUser.verifyBeforeUpdateEmail(updateEmail, actionCodeSettings);
await firebase.auth().currentUser.delete();
} catch (error) {
try {
await firebase.auth().currentUser.delete();
} catch (_) {
/* do nothing */
}
return Promise.reject("'verifyBeforeUpdateEmail()' with 'actionCodeSettings' did not work");
}
return Promise.resolve();
});
});
describe('unlink()', () => {
it('should unlink the email address', async () => {
const random = Utils.randString(12, '#aA');

View File

@@ -329,6 +329,34 @@ RCT_EXPORT_METHOD(sendEmailVerification:
}
}
RCT_EXPORT_METHOD(verifyBeforeUpdateEmail:
(FIRApp *) firebaseApp
:(NSString *) email
:(NSDictionary *) actionCodeSettings
:(RCTPromiseResolveBlock) resolve
:(RCTPromiseRejectBlock) reject
) {
FIRUser *user = [FIRAuth authWithApp:firebaseApp].currentUser;
if (user) {
id handler = ^(NSError *_Nullable error) {
if (error) {
[self promiseRejectAuthException:reject error:error];
} else {
FIRUser *userAfterUpdate = [FIRAuth authWithApp:firebaseApp].currentUser;
[self promiseWithUser:resolve rejecter:reject user:userAfterUpdate];
}
};
if (actionCodeSettings) {
FIRActionCodeSettings *settings = [self buildActionCodeSettings:actionCodeSettings];
[user sendEmailVerificationBeforeUpdatingEmail:email actionCodeSettings:settings completion:handler];
} else {
[user sendEmailVerificationBeforeUpdatingEmail:email completion:handler];
}
} else {
[self promiseNoUser:resolve rejecter:reject isError:YES];
}
}
RCT_EXPORT_METHOD(updateEmail:
(FIRApp *) firebaseApp
:(NSString *) email

View File

@@ -15,7 +15,7 @@
*
*/
import { isObject, isString } from '@react-native-firebase/app/lib/common';
import { isObject, isString, isUndefined, isBoolean } from '@react-native-firebase/app/lib/common';
export default class User {
constructor(auth, user) {
@@ -107,6 +107,68 @@ export default class User {
"firebase.auth.User.sendEmailVerification(*) 'actionCodeSettings.url' expected a string value.",
);
}
if (
!isUndefined(actionCodeSettings.dynamicLinkDomain) &&
!isString(actionCodeSettings.dynamicLinkDomain)
) {
throw new Error(
"firebase.auth.User.sendEmailVerification(*) 'actionCodeSettings.dynamicLinkDomain' expected a string value.",
);
}
if (
!isUndefined(actionCodeSettings.handleCodeInApp) &&
!isBoolean(actionCodeSettings.handleCodeInApp)
) {
throw new Error(
"firebase.auth.User.sendEmailVerification(*) 'actionCodeSettings.handleCodeInApp' expected a boolean value.",
);
}
if (!isUndefined(actionCodeSettings.iOS)) {
if (!isObject(actionCodeSettings.iOS)) {
throw new Error(
"firebase.auth.User.sendEmailVerification(*) 'actionCodeSettings.iOS' expected an object value.",
);
}
if (!isString(actionCodeSettings.iOS.bundleId)) {
throw new Error(
"firebase.auth.User.sendEmailVerification(*) 'actionCodeSettings.iOS.bundleId' expected a string value.",
);
}
}
if (!isUndefined(actionCodeSettings.android)) {
if (!isObject(actionCodeSettings.android)) {
throw new Error(
"firebase.auth.User.sendEmailVerification(*) 'actionCodeSettings.android' expected an object value.",
);
}
if (!isString(actionCodeSettings.android.packageName)) {
throw new Error(
"firebase.auth.User.sendEmailVerification(*) 'actionCodeSettings.android.packageName' expected a string value.",
);
}
if (
!isUndefined(actionCodeSettings.android.installApp) &&
!isBoolean(actionCodeSettings.android.installApp)
) {
throw new Error(
"firebase.auth.User.sendEmailVerification(*) 'actionCodeSettings.android.installApp' expected a boolean value.",
);
}
if (
!isUndefined(actionCodeSettings.android.minimumVersion) &&
!isString(actionCodeSettings.android.minimumVersion)
) {
throw new Error(
"firebase.auth.User.sendEmailVerification(*) 'actionCodeSettings.android.minimumVersion' expected a string value.",
);
}
}
}
return this._auth.native.sendEmailVerification(actionCodeSettings).then(user => {
@@ -148,6 +210,88 @@ export default class User {
});
}
verifyBeforeUpdateEmail(newEmail, actionCodeSettings) {
if (!isString(newEmail)) {
throw new Error(
"firebase.auth.User.verifyBeforeUpdateEmail(*) 'newEmail' expected a string value.",
);
}
if (isObject(actionCodeSettings)) {
if (!isString(actionCodeSettings.url)) {
throw new Error(
"firebase.auth.User.verifyBeforeUpdateEmail(_, *) 'actionCodeSettings.url' expected a string value.",
);
}
if (
!isUndefined(actionCodeSettings.dynamicLinkDomain) &&
!isString(actionCodeSettings.dynamicLinkDomain)
) {
throw new Error(
"firebase.auth.User.verifyBeforeUpdateEmail(_, *) 'actionCodeSettings.dynamicLinkDomain' expected a string value.",
);
}
if (
!isUndefined(actionCodeSettings.handleCodeInApp) &&
!isBoolean(actionCodeSettings.handleCodeInApp)
) {
throw new Error(
"firebase.auth.User.verifyBeforeUpdateEmail(_, *) 'actionCodeSettings.handleCodeInApp' expected a boolean value.",
);
}
if (!isUndefined(actionCodeSettings.iOS)) {
if (!isObject(actionCodeSettings.iOS)) {
throw new Error(
"firebase.auth.User.verifyBeforeUpdateEmail(_, *) 'actionCodeSettings.iOS' expected an object value.",
);
}
if (!isString(actionCodeSettings.iOS.bundleId)) {
throw new Error(
"firebase.auth.User.verifyBeforeUpdateEmail(_, *) 'actionCodeSettings.iOS.bundleId' expected a string value.",
);
}
}
if (!isUndefined(actionCodeSettings.android)) {
if (!isObject(actionCodeSettings.android)) {
throw new Error(
"firebase.auth.User.verifyBeforeUpdateEmail(_, *) 'actionCodeSettings.android' expected an object value.",
);
}
if (!isString(actionCodeSettings.android.packageName)) {
throw new Error(
"firebase.auth.User.verifyBeforeUpdateEmail(_, *) 'actionCodeSettings.android.packageName' expected a string value.",
);
}
if (
!isUndefined(actionCodeSettings.android.installApp) &&
!isBoolean(actionCodeSettings.android.installApp)
) {
throw new Error(
"firebase.auth.User.verifyBeforeUpdateEmail(_, *) 'actionCodeSettings.android.installApp' expected a boolean value.",
);
}
if (
!isUndefined(actionCodeSettings.android.minimumVersion) &&
!isString(actionCodeSettings.android.minimumVersion)
) {
throw new Error(
"firebase.auth.User.verifyBeforeUpdateEmail(_, *) 'actionCodeSettings.android.minimumVersion' expected a string value.",
);
}
}
}
return this._auth.native.verifyBeforeUpdateEmail(newEmail, actionCodeSettings).then(user => {
this._auth._setUser(user);
});
}
/**
* KNOWN UNSUPPORTED METHODS
*/

View File

@@ -1037,6 +1037,30 @@ export namespace FirebaseAuthTypes {
* @param actionCodeSettings Any optional additional settings to be set before sending the verification email.
*/
sendEmailVerification(actionCodeSettings?: ActionCodeSettings): Promise<void>;
/**
* Sends a link to the user's email address, when clicked, the user's Authentication email address will be updated to whatever
* was passed as the first argument.
*
* #### Example
*
* ```js
* await firebase.auth().currentUser.verifyBeforeUpdateEmail(
* 'foo@emailaddress.com',
* {
* handleCodeInApp: true,
* });
* ```
*
* > This will Promise reject if the user is anonymous.
*
* @error auth/missing-android-pkg-name An Android package name must be provided if the Android app is required to be installed.
* @error auth/missing-continue-uri A continue URL must be provided in the request.
* @error auth/missing-ios-bundle-id An iOS bundle ID must be provided if an App Store ID is provided.
* @error auth/invalid-continue-uri The continue URL provided in the request is invalid.
* @error auth/unauthorized-continue-uri The domain of the continue URL is not whitelisted. Whitelist the domain in the Firebase console.
* @param actionCodeSettings Any optional additional settings to be set before sending the verification email.
*/
verifyBeforeUpdateEmail(email: string, actionCodeSettings?: ActionCodeSettings): Promise<void>;
/**
* Returns a JSON-serializable representation of this object.

View File

@@ -220,9 +220,9 @@ PODS:
- glog (0.3.5)
- Google-Mobile-Ads-SDK (7.61.0):
- GoogleAppMeasurement (~> 6.0)
- GoogleAPIClientForREST/Core (1.4.2):
- GoogleAPIClientForREST/Core (1.4.1):
- GTMSessionFetcher (>= 1.1.7)
- GoogleAPIClientForREST/Vision (1.4.2):
- GoogleAPIClientForREST/Vision (1.4.1):
- GoogleAPIClientForREST/Core
- GTMSessionFetcher (>= 1.1.7)
- GoogleAppMeasurement (6.6.1):
@@ -811,7 +811,7 @@ SPEC CHECKSUMS:
Folly: 30e7936e1c45c08d884aa59369ed951a8e68cf51
glog: 1f3da668190260b06b429bb211bfbee5cd790c28
Google-Mobile-Ads-SDK: 4c3b24b04293b4bb370b2cb392cdd3ee97c87752
GoogleAPIClientForREST: 9f49df9fac7867b459187e687fed3066b2be049a
GoogleAPIClientForREST: e50dc3267b3131ee3a25e707c10204f6bec15ae9
GoogleAppMeasurement: 2fd5c5a56c069db635c8e7b92d4809a9591d0a69
GoogleDataTransport: 9a8a16f79feffc7f42096743de2a7c4815e84020
GoogleDataTransportCCTSupport: 489c1265d2c85b68187a83a911913d190012158d
@@ -864,4 +864,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 1cbd09c80745bbdb0e6f1ac8f0240d2c4fc031dd
COCOAPODS: 1.9.3
COCOAPODS: 1.9.1