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

convertUniversalLink documentation #196

Open
jetaix opened this issue Oct 31, 2022 · 1 comment
Open

convertUniversalLink documentation #196

jetaix opened this issue Oct 31, 2022 · 1 comment

Comments

@jetaix
Copy link

jetaix commented Oct 31, 2022

Do you plan to document the convertUniversalLink method ?

@RyanLinXiang
Copy link

RyanLinXiang commented Feb 20, 2023

I actually have the same question here. In my opinion the documentation in general regarding how Adjust transforms the links should be more transparent and should also be added into the documentation of this SDK. For me it was not clear in the beginning that for Android will will receive Adjust universal links in the customer schema format myApp://path and for iOS as universal link in the format: 'https://{myToken}.adj.st' so that I do need the convertUniversalLink method in order to have it for iOS as customer schema format and in order to align it with our Android deep link handling. I think that this definitely needs to be explained directly in the documentation here.

Also I think this convertUniversalLink method could be offered independent of the Adjust iOS SDK so that we can convert the links directly in the AppDelegate.mm of our React Native app before they reach the Linking deep link handler. In the following I would like to share my approach:

//AppDelegate.mm

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity
 restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler {

    if ([[userActivity activityType] isEqualToString:NSUserActivityTypeBrowsingWeb]) {
        NSURL *url = [userActivity webpageURL];

        NSURL *oldStyleDeepLink = [AppDelegate convertUniversalLink:url scheme:@"myApp"];

        NSDictionary<UIApplicationOpenURLOptionsKey,id> *options = @{@"UIApplicationOpenURLOptionsOpenInPlaceKey": @0};

        if (oldStyleDeepLink) {
            return [RCTLinkingManager application:application openURL:oldStyleDeepLink options:options];
        }
    }

    return [RCTLinkingManager application:application
                      continueUserActivity:userActivity
                        restorationHandler:restorationHandler];
}

The AppDelegate convertUniversalLink method from above can be just extracted from the iOS SDK and put as a standalone into AppDelegate.mm this way:

//AppDelegate.mm continue

static NSString * const kUniversalLinkPattern       = @"https://[^.]*\\.ulink\\.adjust\\.com/ulink/?(.*)";
static NSString * const kShortUniversalLinkPattern  = @"http[s]?://[a-z0-9]{4}\\.adj\\.st/?(.*)";
static NSString * const kDefaultScheme              = @"AdjustUniversalScheme";
static NSString * const kOptionalRedirectPattern    = @"adjust_redirect=[^&#]*";

+ (BOOL)isNull:(id)value {
    return value == nil || value == (id)[NSNull null];
}

+ (NSURL *)convertUniversalLink:(NSURL *)url scheme:(NSString *)scheme {
    NSError *error = NULL;

    NSRegularExpression *universalLinkRegex = [NSRegularExpression regularExpressionWithPattern:kUniversalLinkPattern
                                                                           options:NSRegularExpressionCaseInsensitive
                                                                             error:&error];

    NSRegularExpression *shortUniversalLinkRegex = [NSRegularExpression regularExpressionWithPattern:kShortUniversalLinkPattern
                                                                           options:NSRegularExpressionCaseInsensitive
                                                                             error:&error];
    if ([AppDelegate isNull:url]) {
        NSLog(@"Received universal link is nil");
        return nil;
    }

    if ([AppDelegate isNull:scheme] || [scheme length] == 0) {
        NSLog(@"Non-empty scheme required, using the scheme \"AdjustUniversalScheme\"");
        scheme = kDefaultScheme;
    }

    NSString *urlString = [url absoluteString];

    if ([AppDelegate isNull:urlString]) {
        NSLog(@"Parsed universal link is nil");
        return nil;
    }

    NSArray<NSTextCheckingResult *> *matches = [universalLinkRegex matchesInString:urlString options:0 range:NSMakeRange(0, [urlString length])];
    if ([matches count] == 0) {
        matches = [shortUniversalLinkRegex matchesInString:urlString options:0 range:NSMakeRange(0, [urlString length])];
        if ([matches count] == 0) {
            NSLog(@"Url doesn't match as universal link or short version");
            return nil;
        }
    }

    if ([matches count] > 1) {
        NSLog(@"Url match as universal link multiple times");
        return nil;
    }

    NSTextCheckingResult *match = matches[0];
    if ([match numberOfRanges] != 2) {
        NSLog(@"Wrong number of ranges matched");
        return nil;
    }

    NSString *tailSubString = [urlString substringWithRange:[match rangeAtIndex:1]];
    NSString *finalTailSubString = [AppDelegate removeOptionalRedirect:tailSubString];
    NSString *extractedUrlString = [NSString stringWithFormat:@"%@://%@", scheme, finalTailSubString];
    NSLog(@"Converted deeplink from universal link %@", extractedUrlString);
    NSURL *extractedUrl = [NSURL URLWithString:extractedUrlString];
    if ([AppDelegate isNull:extractedUrl]) {
        NSLog(@"Unable to parse converted deeplink from universal link %@", extractedUrlString);
        return nil;
    }
    return extractedUrl;
}

+ (NSString *)removeOptionalRedirect:(NSString *)tailSubString {
    NSError *error = NULL;
    NSRegularExpression *optionalRedirectRegex = [NSRegularExpression regularExpressionWithPattern:kOptionalRedirectPattern
                                                                               options:NSRegularExpressionCaseInsensitive
                                                                                 error:&error];

    if (optionalRedirectRegex == nil) {
        NSLog(@"Remove Optional Redirect regex not correctly configured");
        return tailSubString;
    }

    NSArray<NSTextCheckingResult *> *optionalRedirectmatches = [optionalRedirectRegex matchesInString:tailSubString
                                                                                              options:0
                                                                                                range:NSMakeRange(0, [tailSubString length])];
    if ([optionalRedirectmatches count] == 0) {
        NSLog(@"Universal link does not contain option adjust_redirect parameter");
        return tailSubString;
    }
    if ([optionalRedirectmatches count] > 1) {
        NSLog(@"Universal link contains multiple option adjust_redirect parameters");
        return tailSubString;
    }

    NSTextCheckingResult *redirectMatch = optionalRedirectmatches[0];
    NSRange redirectRange = [redirectMatch rangeAtIndex:0];
    NSString *beforeRedirect = [tailSubString substringToIndex:redirectRange.location];
    NSString *afterRedirect = [tailSubString substringFromIndex:(redirectRange.location + redirectRange.length)];
    if (beforeRedirect.length > 0 && afterRedirect.length > 0) {
        NSString *lastCharacterBeforeRedirect = [beforeRedirect substringFromIndex:beforeRedirect.length - 1];
        NSString *firstCharacterAfterRedirect = [afterRedirect substringToIndex:1];
        if ([@"&" isEqualToString:lastCharacterBeforeRedirect] &&
            [@"&" isEqualToString:firstCharacterAfterRedirect]) {
            beforeRedirect = [beforeRedirect substringToIndex:beforeRedirect.length - 1];
        }
        if ([@"&" isEqualToString:lastCharacterBeforeRedirect] &&
            [@"#" isEqualToString:firstCharacterAfterRedirect]) {
            beforeRedirect = [beforeRedirect substringToIndex:beforeRedirect.length - 1];
        }
        if ([@"?" isEqualToString:lastCharacterBeforeRedirect] &&
            [@"#" isEqualToString:firstCharacterAfterRedirect]) {
            beforeRedirect = [beforeRedirect substringToIndex:beforeRedirect.length - 1];
        }
        if ([@"?" isEqualToString:lastCharacterBeforeRedirect] &&
            [@"&" isEqualToString:firstCharacterAfterRedirect]) {
            afterRedirect = [afterRedirect substringFromIndex:1];
        }
    }

    NSString *removedRedirect = [NSString stringWithFormat:@"%@%@", beforeRedirect, afterRedirect];
    return removedRedirect;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants