Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

identity v3 #438

Merged
merged 62 commits into from
Mar 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
59bd4be
fixed small potential race condition when resetting users
aboedo Dec 30, 2020
a157840
created basic skeleton for login call, refactored naming of some of t…
aboedo Jan 4, 2021
618e5f8
small refactor and improve logIn logic
aboedo Jan 4, 2021
fc51be7
created new Identity group
aboedo Jan 4, 2021
60b9c61
created a new class, RCPurchaserInfoManager, to hold logic for RCPurc…
aboedo Jan 5, 2021
7e43f62
moved stuff into the RCPurchaserInfoManager
aboedo Jan 6, 2021
72d1890
added purchaserInfoManager usage to tests
aboedo Jan 6, 2021
60c2dd9
fixed some behavior changes from refactor
aboedo Jan 6, 2021
6ba2302
fixed issues with delegates
aboedo Jan 7, 2021
9b0a253
formatting
aboedo Jan 7, 2021
32b87ae
fixed test cases where the purchaserInfo had no content or wouldn't c…
aboedo Jan 7, 2021
0e8cb81
renamed RCPurchaserInfoManager -> PurchaserInfoManager in Swift, adde…
aboedo Jan 7, 2021
6c19a42
marked createAlias as unavailable
aboedo Jan 7, 2021
65d9f41
deprecated allowSharingAppStoreAccount
aboedo Jan 7, 2021
8a08116
implemented login method everywhere but in RCBackend
aboedo Jan 7, 2021
eb3e9a6
formatting
aboedo Jan 7, 2021
dd635cd
small renames, added base code for processing the backend response
aboedo Jan 7, 2021
c9c6f69
finished implementation of login in RCBackend
aboedo Jan 7, 2021
5aad648
updated callbacks so the cache is properly updated and the delegate g…
aboedo Jan 7, 2021
d770c0c
updated deprecation comment for createAlias
aboedo Jan 8, 2021
bec7b20
handled logOut case where user is anonymous, which sends an error now
aboedo Jan 8, 2021
85de042
slight cleanup
aboedo Jan 11, 2021
087d9b3
unified some duplicated logic and polished edge cases a bit
aboedo Jan 11, 2021
122d3aa
reverted changes to clear attribution data
aboedo Jan 12, 2021
ee20400
removed unnecessary method that cleared cache and attempted to send n…
aboedo Jan 12, 2021
139254a
ensured that the delegate is called after login finishes successfully
aboedo Jan 12, 2021
4491b2f
clean up RCIdentityManager interface
aboedo Jan 12, 2021
b8ff63c
moved IdentityManagerTests to Identity group, added skeleton for tests
aboedo Jan 12, 2021
77a8ee7
added tests skeletons for backend
aboedo Jan 12, 2021
bcaf112
added skeleton for tests for purchaserInfoManager
aboedo Jan 12, 2021
3c57c7a
added some tests, removed redundant MockBackend
aboedo Jan 13, 2021
b5a31f6
added test case, improved PurchaserInfoManager mock
aboedo Jan 15, 2021
2087a4d
added more test cases, fixed nullability specifier for errors in login
aboedo Jan 15, 2021
c4455d6
added more test cases
aboedo Jan 15, 2021
f34070b
renamed logInAppUserID -> logInWithAppUserID for better swift compati…
aboedo Jan 15, 2021
ca79deb
more test cases and naming cleanup
aboedo Jan 15, 2021
1b05eea
added test cases for logout, added protected extensions for RCIdentit…
aboedo Jan 15, 2021
7503a82
added more test cases
aboedo Jan 18, 2021
132dc75
added more test cases, fixed bad status code handling, updated "creat…
aboedo Jan 18, 2021
321384d
added final test cases for RCBackend
aboedo Jan 18, 2021
22b9470
added some test cases for purchaserInfoManager
aboedo Jan 19, 2021
1c038e6
more test cases
aboedo Jan 19, 2021
5e2d1b1
added more test cases, simplified logic for calling the delegate meth…
aboedo Jan 19, 2021
75a0786
typo
aboedo Jan 19, 2021
49c2e65
more test cases for purchaserInfoManager
aboedo Jan 19, 2021
8678073
added more test cases, added nullability specification to cachedPurch…
aboedo Jan 19, 2021
eef8b03
fixed potential crash if json object can't be parsed, added more test…
aboedo Jan 19, 2021
b6e99f5
final purchaserInfoManager test cases
aboedo Jan 20, 2021
9b91e9c
fix build warnings
aboedo Jan 20, 2021
6143ad5
reverted public-facing changes so the PR can easily be integrated
aboedo Jan 20, 2021
a008105
added comment
aboedo Jan 20, 2021
6a3a0ec
removed extra line break
aboedo Jan 20, 2021
87972a0
added more logs, improved some messages
aboedo Feb 9, 2021
01f2a04
updated the behavior of logIn when the currentUserID is nil so that i…
aboedo Feb 9, 2021
f749508
updated logOut completion so that it always gets calls from the main …
aboedo Feb 9, 2021
43845ba
updated copy of identity failure message when the current appUserID i…
aboedo Feb 9, 2021
cd25017
updated Package.swift to include Identity in the sources list
aboedo Feb 19, 2021
6a5a162
fix typo
aboedo Mar 1, 2021
4464903
cleaned up logic that decides whether or not to call completion after…
aboedo Mar 1, 2021
0bbbb22
updated login endpoint: /login -> /identify.
aboedo Mar 1, 2021
6024f3c
fixed tests and nullability of errors
aboedo Mar 1, 2021
404edf4
fix rebasing issues
aboedo Mar 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func resolveTargets() -> [Target] {
"Purchases/Purchasing",
"Purchases/ProtectedExtensions",
"Purchases/SubscriberAttributes",
"Purchases/SwiftInterfaces"]
"Purchases/Identity"]
let infoPlist = "Purchases/Info.plist"
let swiftSources = "Purchases/SwiftSources"

Expand Down
66 changes: 58 additions & 8 deletions Purchases.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions Purchases/Identity/RCIdentityManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// Created by RevenueCat.
// Copyright (c) 2019 Purchases. All rights reserved.
//

#import <Foundation/Foundation.h>

@class RCPurchaserInfo, RCPurchaserInfoManager, RCBackend, RCDeviceCache;

NS_ASSUME_NONNULL_BEGIN

@interface RCIdentityManager : NSObject
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mostly the same class as it used to be.
I moved it to /Identity/ so it shows up as a new class.

Changes summary:

  • clean up interface: removed some stuff that didn't need to be public, replaced import with forward-declaration for RCDeviceCache
  • added new methods
  • added deprecated pragma mark for old methods


@property (nonatomic, readonly) NSString *currentAppUserID;
@property (nonatomic, readonly) BOOL currentUserIsAnonymous;

- (instancetype)initWith:(RCDeviceCache *)deviceCache
backend:(RCBackend *)backend
purchaserInfoManager:(RCPurchaserInfoManager *)purchaserInfoManager;

- (void)configureWithAppUserID:(nullable NSString *)appUserID;

- (void)logInWithAppUserID:(NSString *)newAppUserID
completion:(void (^)(RCPurchaserInfo * _Nullable purchaserInfo,
BOOL created,
NSError * _Nullable error))completion;

- (void)logOutWithCompletion:(void (^)(NSError * _Nullable error))completion;

#pragma MARK - deprecated methods

Comment on lines +30 to +31
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added the deprecated pragma, but since these are the internal methods, I didn't actually mark them deprecated otherwise we'd get a compiler warning ourselves

- (void)identifyAppUserID:(NSString *)appUserID completion:(void (^)(NSError * _Nullable error))completion;

- (void)createAliasForAppUserID:(NSString *)alias completion:(void (^)(NSError * _Nullable error))completion;

- (void)resetAppUserID;

@end

NS_ASSUME_NONNULL_END
157 changes: 157 additions & 0 deletions Purchases/Identity/RCIdentityManager.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
//
// Created by RevenueCat.
// Copyright (c) 2019 Purchases. All rights reserved.
//

#import "RCIdentityManager.h"
#import "RCLogUtils.h"
#import "RCBackend.h"
#import "RCDeviceCache.h"
#import "RCPurchasesErrorUtils.h"
#import "RCPurchaserInfoManager.h"
@import PurchasesCoreSwift;


@interface RCIdentityManager ()

@property (nonatomic) RCDeviceCache *deviceCache;
@property (nonatomic) RCBackend *backend;
@property (nonatomic) RCPurchaserInfoManager *purchaserInfoManager;

@end

@implementation RCIdentityManager

- (instancetype)initWith:(RCDeviceCache *)deviceCache
backend:(RCBackend *)backend
purchaserInfoManager:(RCPurchaserInfoManager *)purchaserInfoManager {
self = [super init];
if (self) {
self.deviceCache = deviceCache;
self.backend = backend;
self.purchaserInfoManager = purchaserInfoManager;
}

return self;
}

- (NSString *)generateRandomID {
NSString *uuid = [NSUUID.new.UUIDString stringByReplacingOccurrencesOfString:@"-" withString:@""];
return [NSString stringWithFormat:@"$RCAnonymousID:%@", uuid.lowercaseString];
}

- (void)configureWithAppUserID:(nullable NSString *)appUserID {
if (appUserID == nil) {
appUserID = [self.deviceCache cachedAppUserID];
if (appUserID == nil) {
appUserID = [self.deviceCache cachedLegacyAppUserID];
if (appUserID == nil) {
appUserID = [self generateRandomID];
RCUserLog(RCStrings.identity.identifying_app_user_id, appUserID);
}
}
}

[self saveAppUserID:appUserID];
[self.deviceCache cleanupSubscriberAttributes];
}

- (void)identifyAppUserID:(NSString *)appUserID completion:(void (^)(NSError *_Nullable error))completion {
if (self.currentUserIsAnonymous) {
RCUserLog(RCStrings.identity.identifying_anon_id, self.currentAppUserID);
[self createAliasForAppUserID:appUserID completion:completion];
} else {
RCUserLog(RCStrings.identity.changing_app_user_id, self.currentAppUserID, appUserID);
[self.deviceCache clearCachesForAppUserID:self.currentAppUserID andSaveNewUserID:appUserID];
completion(nil);
}
}

- (void)saveAppUserID:(NSString *)appUserID {
[self.deviceCache cacheAppUserID:appUserID];
}

- (void)createAliasForAppUserID:(NSString *)alias completion:(void (^)(NSError *_Nullable error))completion {
NSString *currentAppUserID = self.currentAppUserID;
if (!currentAppUserID) {
RCWarnLog(@"%@", RCStrings.identity.creating_alias_failed_null_currentappuserid);
completion(RCPurchasesErrorUtils.missingAppUserIDError);
return;
}
RCUserLog(RCStrings.identity.creating_alias, currentAppUserID, alias);
[self.backend createAliasForAppUserID:currentAppUserID withNewAppUserID:alias completion:^(NSError *_Nullable error) {
if (error == nil) {
RCUserLog(@"%@", RCStrings.identity.creating_alias_success);
[self.deviceCache clearCachesForAppUserID:currentAppUserID andSaveNewUserID:alias];
}
completion(error);
}];
}

- (void)resetAppUserID {
NSString *randomId = [self generateRandomID];
NSString *oldAppUserID = self.currentAppUserID;
[self.deviceCache clearCachesForAppUserID:oldAppUserID andSaveNewUserID:randomId];
[self.deviceCache clearLatestNetworkAndAdvertisingIdsSentForAppUserID:oldAppUserID];
}

- (NSString *)currentAppUserID {
return [self.deviceCache cachedAppUserID];
}

- (BOOL)currentUserIsAnonymous {
BOOL currentAppUserIDLooksAnonymous = [[self.deviceCache cachedAppUserID] rangeOfString:@"\\$RCAnonymousID:([a-z0-9]{32})$" options:NSRegularExpressionSearch].length > 0;
BOOL isLegacyAnonymousAppUserID = [self.deviceCache.cachedAppUserID isEqualToString:self.deviceCache.cachedLegacyAppUserID];
return currentAppUserIDLooksAnonymous || isLegacyAnonymousAppUserID;
}

- (void)logInWithAppUserID:(NSString *)newAppUserID
completion:(void (^)(RCPurchaserInfo * _Nullable purchaserInfo,
BOOL created,
NSError * _Nullable error))completion {
NSString *currentAppUserID = self.currentAppUserID;

if (!currentAppUserID || !newAppUserID || [newAppUserID isEqualToString:@""]) {
NSString *errorMessage = currentAppUserID == nil ? RCStrings.identity.logging_in_with_initial_appuserid_nil
: RCStrings.identity.logging_in_with_nil_appuserid;
RCErrorLog(@"%@", errorMessage);
completion(nil, NO, RCPurchasesErrorUtils.missingAppUserIDError);
return;
}

if ([newAppUserID isEqualToString:currentAppUserID]) {
RCWarnLog(@"%@", RCStrings.identity.logging_in_with_same_appuserid);
[self.purchaserInfoManager purchaserInfoWithAppUserID:currentAppUserID
completionBlock:^(RCPurchaserInfo *purchaserInfo, NSError *error) {
completion(purchaserInfo, NO, error);
}];
return;
}

[self.backend logInWithCurrentAppUserID:currentAppUserID
newAppUserID:newAppUserID
completion:^(RCPurchaserInfo *purchaserInfo, BOOL created, NSError * _Nullable error) {
if (error == nil) {
RCUserLog(@"%@", RCStrings.identity.login_success);

[self.deviceCache clearCachesForAppUserID:currentAppUserID
andSaveNewUserID:newAppUserID];
[self.purchaserInfoManager cachePurchaserInfo:purchaserInfo
forAppUserID:newAppUserID];
Comment on lines +137 to +140
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logIn returns a purchaser info, so we store it and that in turn calls the delegate, saving one backend call when compared with identify

}
completion(purchaserInfo, created, error);
}];
}

- (void)logOutWithCompletion:(void (^)(NSError * _Nullable error))completion {
aboedo marked this conversation as resolved.
Show resolved Hide resolved
RCLog(RCStrings.identity.logging_out_user, self.currentAppUserID);
if (self.currentUserIsAnonymous) {
completion(RCPurchasesErrorUtils.logOutAnonymousUserError);
return;
}
[self resetAppUserID];
RCLog(@"%@", RCStrings.identity.log_out_success);
completion(nil);
}

@end
47 changes: 47 additions & 0 deletions Purchases/Identity/RCPurchaserInfoManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//
// Created by Andrés Boedo on 1/4/21.
// Copyright (c) 2021 Purchases. All rights reserved.
//


#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN
@class RCPurchaserInfo, RCDeviceCache, RCBackend, RCOperationDispatcher, RCSystemInfo;
typedef void (^RCReceivePurchaserInfoBlock)(RCPurchaserInfo * _Nullable, NSError * _Nullable) NS_SWIFT_NAME(Purchases.ReceivePurchaserInfoBlock);

NS_SWIFT_NAME(PurchaserInfoManagerDelegate)
@protocol RCPurchaserInfoManagerDelegate <NSObject>
- (void)purchaserInfoManagerDidReceiveUpdatedPurchaserInfo:(RCPurchaserInfo *)purchaserInfo;
@end

NS_SWIFT_NAME(PurchaserInfoManager)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this class is mostly stuff extracted from RCPurchases

@interface RCPurchaserInfoManager : NSObject

@property (nonatomic, weak, nullable) id<RCPurchaserInfoManagerDelegate> delegate;

- (instancetype)initWithOperationDispatcher:(RCOperationDispatcher *)operationDispatcher
deviceCache:(RCDeviceCache *)deviceCache
backend:(RCBackend *)backend
systemInfo:(RCSystemInfo *)systemInfo;

- (void)fetchAndCachePurchaserInfoWithAppUserID:(NSString *)appUserID
isAppBackgrounded:(BOOL)isAppBackgrounded
completion:(nullable RCReceivePurchaserInfoBlock)completion;

- (void)fetchAndCachePurchaserInfoIfStaleWithAppUserID:(NSString *)appUserID
isAppBackgrounded:(BOOL)isAppBackgrounded
completion:(nullable RCReceivePurchaserInfoBlock)completion;
- (void)sendCachedPurchaserInfoIfAvailableForAppUserID:(NSString *)appUserID;

- (void)purchaserInfoWithAppUserID:(NSString *)appUserID
completionBlock:(nullable RCReceivePurchaserInfoBlock)completion;

- (nullable RCPurchaserInfo *)cachedPurchaserInfoForAppUserID:(NSString *)appUserID;
- (void)cachePurchaserInfo:(RCPurchaserInfo *)info forAppUserID:(NSString *)appUserID;
- (void)clearPurchaserInfoCacheForAppUserID:(NSString *)appUserID;

@end


NS_ASSUME_NONNULL_END