6

I have a web app for Android and iOS that opens a web page. The app works fine for Android and most of the iOS devices.

But for some reason, for some iOS users, the app randomly redirects users to the initial page after a few minutes using the app.

A user opens the app on the initial page, clicks on a link for a different page, the user is reading the page, then for some reason the app redirects back to the initial page.

There's no JavaScript code that does redirect in the application, there's a service worker, but with no redirects.

It does not happen all times, but it does happen and annoys the users.

Any ideas on what could be happening?

Update: code snippet

ViewController.swift

import UIKit
import WebKit
import Firebase
import UserNotifications
class ViewController: UIViewController , WKNavigationDelegate, WKUIDelegate{

    @IBOutlet weak var loading: UIActivityIndicatorView!
    @IBOutlet weak var screenSplash: UIImageView!
    @IBOutlet weak var webView: WKWebView!
    let reachability = Reachability()!

    var request = URLRequest(url: URL(string: "https://app.com/webview")!)

    var screen = CGRect.zero

    var flag = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        screen = UIScreen.main.bounds
        webView.frame.origin.x = 0
        webView.frame.origin.y = 0
        webView.frame.size.height = screen.height
        webView.frame.size.width = screen.width

        webView?.navigationDelegate = self

        webView.scrollView.bounces = false
        webView.isOpaque = false
        webView.backgroundColor = UIColor.clear
        webView?.load(request)
        self.view.addSubview(webView)

        screen = UIScreen.main.bounds
        webView.frame.origin.x = 0
        webView.frame.origin.y = 0
        webView.frame.size.height = screen.height
        webView.frame.size.width = screen.width


        webView.navigationDelegate = self
        self.webView?.uiDelegate = self
        view.addSubview(webView!)
    }


    @objc func internetChanged(note: Notification){
        let reachability = note.object as! Reachability
        if reachability.connection != .none{
            print("Volvio la conexion")
            webView?.load(request)
            viewDidLoad()

        }else{
            //let alert = UIAlertController(title: "", message: "Es necesario tener una conexión activa a internet", preferredStyle: .alert)
            //alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
            //self.present(alert, animated: true, completion: nil)
            print("Es necesario tener una conexión activa a internet")

            let htmlPath = Bundle.main.path(forResource: "error", ofType: "html")
            let folderPath = Bundle.main.bundlePath
            let baseUrl = URL(fileURLWithPath: folderPath, isDirectory: true)
            do{

                let htmlString = try NSString(contentsOfFile:htmlPath!, encoding:String.Encoding.utf8.rawValue)
                self.webView.loadHTMLString(htmlString as String,baseURL:  baseUrl)
            }catch{

            }
        }
    }


    func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!)
    {

    }
    func webView(webView: WKWebView!, createWebViewWithConfiguration configuration: WKWebViewConfiguration!, forNavigationAction navigationAction: WKNavigationAction!, windowFeatures: WKWindowFeatures!) -> WKWebView! {
        if navigationAction.targetFrame == nil {
            webView.load(navigationAction.request)
        }
        return nil
    }


    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)
    {

        let fmcToken=InstanceID.instanceID().token() as! String
       print(fmcToken)
        webView.evaluateJavaScript("(function() { if(typeof sendDeviceToken === 'function'){  return sendDeviceToken('ios','\(fmcToken)');  }else{ return false; }  })()", completionHandler: { (data, error) in
            if let err = error {
                print(err)
                print(err.localizedDescription)
            } else {
                guard let dataValue = data else {return}
                print("res sendDeviceToken")
                print(dataValue)
            }
        })

        screenSplash.isHidden = true
        loading.isHidden = true
        webView.evaluateJavaScript("document.getElementsByTagName('meta')['viewport'].content='initial-scale=1.0, user-scalable=no';"){
            (result,error) in if error != nil  {
                print(result ?? "")
            }
        }

        if(flag==0){
            NotificationCenter.default.addObserver(self, selector: #selector(internetChanged), name: Notification.Name.reachabilityChanged, object: reachability)
            do{
                try reachability.startNotifier()

            }catch{
                print("No se pudo iniciar la notificacion")
            }
            flag=flag+1
        }
    }

    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        if navigationAction.navigationType == .linkActivated  {
            if let url = navigationAction.request.url,
                let host = url.host, !host.hasPrefix("app.com")  || url.absoluteString.contains("/share/"),
                UIApplication.shared.canOpenURL(url) {
                UIApplication.shared.open(url)
                print(url)
                print("Redirected to browser. No need to open it locally")
                decisionHandler(.cancel)
            } else {
                // print("Open it locally")
                decisionHandler(.allow)
            }
        } else {
            // print("not a user click")
            decisionHandler(.allow)
        }
    }


    //fix the alert
    func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {

        let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)

        alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
            completionHandler()
        }))

        self.present(alertController, animated: true, completion: nil)
    }
    func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {

        let alertController = UIAlertController(title: nil, message: message, preferredStyle: .actionSheet)

        alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
            completionHandler(true)
        }))

        alertController.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) in
            completionHandler(false)
        }))

        self.present(alertController, animated: true, completion: nil)
    }

    func webView(_ webView: WKWebView, runJavaScriptTextInputPanelWithPrompt prompt: String, defaultText: String?, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (String?) -> Void) {

        let alertController = UIAlertController(title: nil, message: prompt, preferredStyle: .actionSheet)

        alertController.addTextField { (textField) in
            textField.text = defaultText
        }

        alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
            if let text = alertController.textFields?.first?.text {
                completionHandler(text)
            } else {
                completionHandler(defaultText)
            }

        }))

        alertController.addAction(UIAlertAction(title: "Cancel", style: .default, handler: { (action) in

            completionHandler(nil)

        }))

        self.present(alertController, animated: true, completion: nil)
    }
    //end fix the alert

    func clickActionOpen(action: String) {
        request = URLRequest(url: URL(string: "https://app.com/webview?click_action="+action)!)

       var requestClick = URLRequest(url: URL(string: "https://app.com/webview?click_action="+action)!)
        webView.load(requestClick)
    }
}
Service worker
// Incrementing CACHE_VERSION will kick off the install event and force previously cached
// resources to be cached again.
// https://github.com/GoogleChrome/samples/blob/gh-pages/service-worker/custom-offline-page/service-worker.js
var CACHE_VERSION = 'v4'
var CACHE_NAME = CACHE_VERSION + ':sw-cache-'

function onInstall(event) {
  console.log('[Serviceworker]', 'Installing!', event)
  event.waitUntil(
    caches.open(CACHE_NAME).then(function prefill(cache) {
      return cache.addAll([
        '<%= asset_path "admin.js" %>',
        '<%= asset_path "admin.css" %>',
        '<%= asset_path "site/homepage-logo.png" %>'
      ])
    })
  )
}

function onActivate(event) {
  console.log('[Serviceworker]', 'Activating!', event)
  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames
          .filter(function(cacheName) {
            // Return true if you want to remove this cache,
            // but remember that caches are shared across
            // the whole origin
            return cacheName.indexOf(CACHE_VERSION) !== 0
          })
          .map(function(cacheName) {
            return caches.delete(cacheName)
          })
      )
    })
  )
}

// Borrowed from https://github.com/TalAter/UpUp
// then from https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/onfetch
function onFetch(event) {
  event.respondWith(
    caches.match(event.request).then(function(response) {
      if (response) {
        // console.log('Found response in cache:', response)

        return response
      }
      // console.log('No response found in cache. About to fetch from network...')

      return fetch(event.request).then(function(response) {
        // console.log('Response from network is:', response)

        return response
      }).catch(function(error) {
        // console.error('Fetching failed:', error)

        throw error
      })
    })
  )
}

self.addEventListener('install', onInstall)
self.addEventListener('activate', onActivate)
self.addEventListener('fetch', onFetch)

sureshprasanna70
  • 1,043
  • 1
  • 10
  • 27
Pablo Cantero
  • 6,239
  • 4
  • 33
  • 44
  • 4
    you need to add some code and/or implementation details – Ana María Martínez Gómez Oct 10 '19 at 23:45
  • If it's a web app, could you paste the URL so others can try to replicate? – Mathias Oct 13 '19 at 11:39
  • May be the service worker is causing the redirect. need more info on the issue. which devices are throwing the issue and where?? – V Jayakar Edidiah Oct 14 '19 at 06:36
  • I have lots of ideas but little to go on from the question to narrow them down. Do you have Xcode? If so you could run an iPhone simulator launch Safari and run your web app from there, see if you can recreate the issue. Trying different models and settings. Aside from that try to collate the bug reports to see if there is a common theme (same model, same iOS version, using 4g, etc). – Najinsky Oct 16 '19 at 00:26
  • Thank you all. I updated the question with some code snippets, could you check it again? I will be happy to share any other code if needed. I really appreciate your support - I have no idea what's going on and I couldn't reproduce it myself. – Pablo Cantero Oct 17 '19 at 15:31
  • Is it possible that the `reachabilityChanged` notification fires more than once? – zenzelezz Oct 24 '19 at 14:05
  • Hi @zenzelezz - how can I check that? On my iOS (device, simulator) I couldn't reproduce the problem. – Pablo Cantero Oct 24 '19 at 17:38

2 Answers2

8

From the scenario that you have explained, the page might get reloaded if internetChanged method is getting called in an unexpected scenario.

I can see that scenario happening if the network status of the device is changed while the viewController is shown. You have observed a notification in method func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!). so when the network status is changed, the internetChanged method will get called. In both if and else of the method, there is logic to load the webView.

Also i can see that, you have observed the notification but never removed the observer. So ViewController will have memory leaks and might not get deallocated from the memory. I would suggest you to remove observer in deinit

One more thing is, you can better observe the notification in -viewDidLoad instead of didFinish method. Now what happens as per the current logic is, whenever the webView is reloaded a new observer will be added to the ViewController in the method func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!). So let's take somehow internetChanged method is called 3 times, then 3 observers will be added. So the next time when network is changed, it will make internetChanged getting called thrice leading to webPage getting loaded 3 times one after the other and the cycle continues.

Karthick Ramesh
  • 1,451
  • 20
  • 30
  • 1
    thank you! your feedback seems very promising. I addressed the changes, and here's a snippet of the changes https://gist.github.com/phstc/d4b8a72304ab942e45bbffd453a6f00d could you have a look? – Pablo Cantero Oct 26 '19 at 15:17
  • Don't know your business logic but i can see that you are trying to address the issue with the variable offline. you are trying to reload the webpage according to the fact whether the user goes offline or not. I have seen one more problem which i have updated in the answer. – Karthick Ramesh Oct 26 '19 at 20:21
  • If you feel that my answer solves your question, can you please mark my answer as accepted one. It will help me to improve on my SO rating w.r.t iOS. – Karthick Ramesh Oct 27 '19 at 11:33
0

The reason can be

  • ISP(Internet service provider)
  • for ruby-on-rails, authentication and using their authentication_token to validate users network requests to your API. if not, the app will terminate.

I hope this will help you.

gosuto
  • 5,422
  • 6
  • 36
  • 57
Emre Gürses
  • 1,992
  • 1
  • 23
  • 28
  • Hi @Emre. Thanks for your response. It is happening for a different iOS version. I wasn't able to reproduce that with my iPhone, neither with the iOS simulator. But it is definitely happening for some users. I attached a code snippet showing we load the web app. I'm not completely familiar with iOS, so if there's any other code needed and I will be happy to share. It is unlikely to be a token problem because the users are logged in fine. – Pablo Cantero Oct 17 '19 at 15:29