pwa-builder / PWABuilder

The simplest way to create progressive web apps across platforms and devices. Start here. This repo is home to several projects in the PWABuilder family of tools.
https://docs.pwabuilder.com
Other
2.77k stars 285 forks source link

[iOS] Tracking pixels and similar assets cause issues #2442

Open justrealmilk opened 2 years ago

justrealmilk commented 2 years ago

App is live on the store https://apps.apple.com/us/app/braytech/id1593151816 🎉

https://user-images.githubusercontent.com/156681/142793894-41fb6b26-09c5-4a7c-babc-977a7f986a09.mp4

The problem is that when using auth stuff, the webView function in WebView.swift doesn't totally suppress these pixel trackers that are behaving unusually. In other browsing contexts, Bungie.net doesn't open Snapchat and Amazon trackers in new tabs but for some reason WKWebView is. I've been experimenting with the code and once I suppressed Snapchat, Amazon came out to play. Short of maintaining a curated list of explicitly disallowed origins, I'm unsure of what to do. I removed some conditions to block anything that doesn't match an auth origin but that obviously can block harmless and sometimes necessary assets

I haven't tried a curated list of explicitly disallowed origins yet, but I'm about to because I'm out of options

justrealmilk commented 2 years ago

This works well albeit the strange oddity that is the fact these trackers open their own tabs when run from my app lol

Settings.swift

let disallowedOrigins: [String] = [".amazon-adsystem.com",".snapchat.com",".snapchat.com",".sc-static.net"]

WebView.swift

let matchingAuthOrigin = authOrigins.first(where: { requestHost.range(of: $0) != nil })
let matchingDisallowedOrigin = disallowedOrigins.first(where: { requestHost.range(of: $0) != nil })

// unchanged start
if (matchingAuthOrigin != nil) {
    decisionHandler(.allow)
    if (toolbarView.isHidden) {
        toolbarView.isHidden = false
        webView.frame = calcWebviewFrame(webviewView: webviewView, toolbarView: toolbarView)
    }
    return
// unchanged end
} else if(matchingDisallowedOrigin != nil) {
    decisionHandler(.cancel)
    return
// unchanged start
} else {
    if (navigationAction.navigationType == .other &&
        navigationAction.value(forKey: "syntheticClickType") as! Int == 0 &&
        (navigationAction.targetFrame != nil)
    ) {
        decisionHandler(.allow)
        return
    }
    else {
        decisionHandler(.cancel)
    }
}
// unchanged end
emindeniz99 commented 2 years ago

I had been getting this issue with expo react-native-webview, so I know the solution.

It is resolved at react-native-webview with isTopFrame option like below. https://github.com/react-native-webview/react-native-webview/commit/6a9116f2d19a8cfd76ee691c2d793b34aee963e7#

ios code, obj c https://github.com/react-native-webview/react-native-webview/blob/14cde20b243c62fe869b0c35c5a421e4dc2d851f/apple/RNCWebView.m#L1096

Usage:

            <WebView
              limitsNavigationsToAppBoundDomains={true}
              onShouldStartLoadWithRequest={(request) => {
                // Only allow navigating within this website
                console.log('onShouldStartLoadWithRequest', request);
                if (!request.isTopFrame) return true;
                // TODO: add allowed origins for event, customdomain etc.
                if (
                  alloweddomainList
                    .some((s) => {
                      console.log(s, request.url);
                      return request.url.includes(s);
                    })
                ) {
                  return true;
                } else {
                  Linking.openURL(request.url);
                  return false;
                }
              }}
....

I don't have any experience with objective c and ios dev, so I can not implement it now. But it seems not hard :)

https://developer.apple.com/documentation/foundation/nsurlrequest/1414134-maindocumenturl BOOL isTopFrame = [request.URL isEqual:request.mainDocumentURL]; // if true, allow request The equality check should be added to https://github.com/pwa-builder/pwabuilder-ios/blob/b340cea2c4f46ef3d91ea9413d49e9f5fffcd2c3/Microsoft.PWABuilder.IOS.Web/Resources/ios-project-src/pwa-shell/WebView.swift#L149-L210

JudahGabriel commented 2 years ago

@justrealmilk So you worked around the issue by disallowing certain domain navigation. OK.

I'm trying to understand the problem better. What, exactly, is triggering the navigation? Just a <img> or CSS pointing to a remote server?

I'd love to see a simple repro.

justrealmilk commented 2 years ago

@justrealmilk So you worked around the issue by disallowing certain domain navigation. OK.

I'm trying to understand the problem better. What, exactly, is triggering the navigation? Just a <img> or CSS pointing to a remote server?

I'd love to see a simple repro.

You can repro it by using bray.tech to create an iOS project

I've found the offending HTML and Script...

<script type="text/javascript" id="">
  (function (a, b, d) {
    if (!a.snaptr) {
      var c = (a.snaptr = function () {
        c.handleRequest ? c.handleRequest.apply(c, arguments) : c.queue.push(arguments);
      });
      c.queue = [];
      a = 'script';
      r = b.createElement(a);
      r.async = !0;
      r.src = d;
      b = b.getElementsByTagName(a)[0];
      b.parentNode.insertBefore(r, b);
    }
  })(window, document, 'https://sc-static.net/scevent.min.js');
  snaptr('init', '3cb71771-ac09-4d1d-b4cc-21625f3f68ce', { user_email: '__INSERT_USER_EMAIL__' });
  snaptr('track', 'PAGE_VIEW');
</script>

<form method="GET" action="https://tr.snapchat.com/cm/i" target="snap04979163085288999" accept-charset="utf-8" style="display: none">
  <iframe id="snap04979163085288999" name="snap04979163085288999"></iframe>
  <input name="pid" />
</form>

Similarly, an Amazon tracker also uses an iframe... So I think it's pretty obvious it's iframes haha

JudahGabriel commented 2 years ago

Interesting. Having solved it yourself, do you recommend a "disallowed domains" like you did? Or is there a better solution we could cook up here?

justrealmilk commented 2 years ago

Interesting. Having solved it yourself, do you recommend a "disallowed domains" like you did? Or is there a better solution we could cook up here?

I was actually looking at pwa-builder/pwabuilder-ios#30 and wondering if a different control might handle these iframes differently? I don't yet have enough experience to test it for myself - until I read that issue I thought there was only 2 ways to use a web view control and now there's 3!?

I think "disallowed domains" idea is fairly straight forward to implement for novices (like me) in that you're really just copying code that's already been written but a small explanation somewhere on how to swap out web view controls and the pros and cons of doing so might be better (my understanding of pwa-builder/pwabuilder-ios#30 is that one of the cons is cookies aren't/are shared) assuming using a different control would alter the behaviour of these iframes

ghost commented 2 years ago

Hello justrealmilk, thank you for opening an issue with us!

I have automatically added a "needs triage" label to help get things started. Our team will investigate the issue and help solve it ASAP. Other community members may also look into the issue and provide feedback 🙌