AngularJS/PouchDB app stops syncing to CouchDB when cache.manifest added

I have a single page web app written using AngularJS. It uses PouchDB to replicate to a CouchDB server and works fine.

The problem comes when I try to convert the webpage to be available offline by adding cache.manifest. Suddenly ALL the replication tasks throw errors and stop working, whether working offline or online.

In Chrome it just says “GET …myCouchIP/myDB/?_nonce=CxVFIwnEJeGFcyoJ net::ERR_FAILED”

In Firefox it also throws an error but mentions that the request is blocked – try enabling CORS.

CORS is enabled on the remote CouchDB as per the instructions from PouchDB setup page. Plus it works fine while not using the cache.manifest (i.e. it is quite happy with all the different ip addresses between my desk, the server and a VM – it is a prototype so there are no domain names at this time).

Incidentally, at this time I am not using any kind of authentication. Admin party is in effect.

So what changes when adding the cache.manifest? Clues gratefully welcomed.
Thanks in advance.

app.js

var app = angular.module('Assets', ['assets.controllers', 'ngRoute']);

app.config(['$routeProvider', function($routeProvider) {
    $routeProvider.
    when('/', {
        controller: 'OverviewCtrl',
        templateUrl: 'views/overview.html'
    }).
    when('/new', {
        controller: 'NewMachineCtrl',
        templateUrl: 'views/machineForm.html'
    }).
    otherwise({redirectTo: '/'});
}]);

controller.js

var _control = angular.module('assets.controllers', ['assets.services']);

_control.controller('OverviewCtrl', ['$scope', 'Machine', function($scope, Machine) {
    var promise = Machine.getAll();

    promise.then(function(machineList) {
        $scope.machines = machineList;
    }, function(reason) {
        alert('Machine list is empty: ' + reason);
    });
}]);

_control.controller('UpdateMachineCtrl', ['$scope', '$routeParams', 'Machine', 
                                            function($scope, $routeParams, Machine) {
    $scope.title = "Update Installation Details";
    var promise = Machine.getSingle($routeParams.docId);

    promise.then(function(machine) {
        $scope.machine = machine;
    }, function(reason) {
        alert('Record could not be retrieved');
    });

    $scope.save = function() {
        Machine.update($scope.machine);
    };
}]);

_control.controller('SyncCtrl', ['$scope', 'Machine', function($scope, Machine) {
    $scope.syncDb = function() {
        Machine.sync();
        Machine.checkConflicts();
    };

    $scope.checkCors = function() {
        // Check CORS is supported
        var corsCheck = function(method, url) {
          var xhr = new XMLHttpRequest();

          if ("withCredentials" in xhr) {
            // XHR for Chrome/Firefox/Opera/Safari.
            xhr.open(method, url, true);
          } else if (typeof XDomainRequest != "undefined") {
            // XDomainRequest for IE.
            xhr = new XDomainRequest();
            xhr.open(method, url);
          } else {
            // CORS not supported.
            console.log('CORS not supported by browser');
          }

          xhr.onload = function() {
              console.log('Response from CORS ' + method + ' request to ' + url + ': ' + xhr.responseText);
          };
          xhr.onerror = function() {
              console.log('Error response from CORS ' + method + ' request to ' + url + ': ' + xhr.responseText);
          };

          xhr.send();
        };

        var server = 'http://10.100.3.21:5984/ass_support';

        corsCheck('GET', server);
        corsCheck('PUT', server);
        corsCheck('POST', server);
        corsCheck('HEAD', server);
//      corsCheck('DELETE', server);
    };
}]);

service.js

var _service = angular.module('assets.services', []);

_service.constant('dbConfig',{
    dbName: 'assets',
    dbServer: 'http://myCouchServerIp:5984/'
});

/**
 * Make PouchDB available in AngularJS.
 */
_service.factory('$db', ['dbConfig', function(dbConfig) {
    PouchDB.enableAllDbs = true;
    var localDb = new PouchDB(dbConfig.dbName);
    var remoteDb = dbConfig.dbServer + dbConfig.dbName;
    var options = {live: true};
    var syncError = function() {
        console.log('Problem encountered during database synchronisation');
    };

    console.log('Replicating from local to server');
    localDb.replicate.to(remoteDb, options, syncError);

    console.log('Replicating from server back to local');
    localDb.replicate.from(remoteDb, options, syncError);   

    return localDb; 
}]);

_service.factory('Machine', ['$q', '$db', '$rootScope', 'dbConfig', 
                   function($q, $db, $rootScope, dbConfig) {
    return {
        update: function(machine)  {
            var delay = $q.defer();

            var doc = {
               _id: machine._id,
               _rev: machine._rev,
               type: machine.type,
               customer: machine.customer,
               factory: machine.factory,
               lineId: machine.lineId,
               plcVersion: machine.plcVersion,
               dateCreated: machine.dateCreated,
               lastUpdated: new Date().toUTCString()
            };

            $db.put(doc, function(error, response) {
                $rootScope.$apply(function() {
                    if (error) {
                        console.log('Update failed: ');
                        console.log(error);
                        delay.reject(error);
                    } else {
                        console.log('Update succeeded: ');
                        console.log(response);
                        delay.resolve(response);
                    }
                });
            });

            return delay.promise;
        },
        getAll: function() {
            var delay = $q.defer();

            var map = function(doc) {
                if (doc.type === 'machine') {
                    emit([doc.customer, doc.factory], 
                            {
                                _id: doc._id,
                                customer: doc.customer, 
                                factory: doc.factory,
                                lineId: doc.lineId, 
                                plcVersion: doc.plcVersion,
                            }
                    );
                }
            };

            $db.query({map: map}, function(error, response) {
                $rootScope.$apply(function() {
                    if (error) {
                        delay.reject(error);
                    } else {
                        console.log('Query retrieved ' + response.rows.length + ' rows');
                        var queryResults = [];

                        // Create an array from the response 
                        response.rows.forEach(function(row) {
                            queryResults.push(row.value);
                        });

                        delay.resolve(queryResults);
                    }
                });
            });

            return delay.promise;
        },
        sync: function() {
            var remoteDb = dbConfig.dbServer + dbConfig.dbName;
            var options = {live: true};
            var syncError = function(error, changes) {
                console.log('Problem encountered during database synchronisation');
                console.log(error);
                console.log(changes);
            };
            var syncSuccess = function(error, changes) {
                console.log('Sync success');
                console.log(error);
                console.log(changes);
            };

            console.log('Replicating from local to server');
            $db.replicate.to(remoteDb, options, syncError).
                on('error', syncError).
                on('complete', syncSuccess);

            console.log('Replicating from server back to local');
            $db.replicate.from(remoteDb, options, syncError);       
        }
    };
}]);

_service.factory('dbListener', ['$rootScope', '$db', function($rootScope, $db) {
    console.log('Registering a onChange listener');
    $db.info(function(error, response) {
        $db.changes({
            since: response.update_seq,
            live: true,
        }).on('change', function() {
            console.log('Change detected by the dbListener');
            // TODO work out why this never happens
        });
    });
}]);

cache.manifest

CACHE MANIFEST

# views
views/machineForm.html
views/overview.html

# scripts
scripts/vendor/pouchdb-2.2.0.min.js
scripts/vendor/angular-1.2.16.min.js
scripts/vendor/angular-route-1.2.16.min.js

scripts/app.js
scripts/controllers/controller.js
scripts/services/service.js

index.html

<!DOCTYPE html>
<html lang="en" manifest="cache.manifest" data-ng-app="Assets">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Asset Management</title>

<script src="scripts/vendor/angular-1.2.16.min.js" type="text/javascript"></script>
<script src="scripts/vendor/angular-route-1.2.16.min.js" type="text/javascript></script>
<script src="scripts/vendor/pouchdb-2.2.0.min.js" type="text/javascript"></script>

<script src="scripts/app.js" type="text/javascript"></script>
<script src="scripts/services/service.js" type="text/javascript"></script>
<script src="scripts/controllers/controller.js" type="text/javascript"></script>
</head>
<body>
    <div id="content">
    <nav class="sidebar">
    <h3>Options</h3>
    <div>
        <a class="active" data-ng-href="#/" rel="nofollow noreferrer noopener">Overview</a>
        <a data-ng-href="#" rel="nofollow noreferrer noopener" data-ng-controller="SyncCtrl" data-ng-click="syncDb()">Synchronise</a>
        <a data-ng-href="" data-ng-controller=" rel="nofollow noreferrer noopener"SyncCtrl" data-ng-click="checkCors()">Check CORS</a>
    </div>
    </nav>

    <section class="main">
        <div data-ng-view></div>
    </section>
    </div>  
</body>
</html>

overview.html

<h3>Installation Overview</h3>
<table>
    <tr>
        <th>Customer</th>
        <th>Factory</th>
        <th>Line Id</th>
        <th>PLC Version</th>
    </tr>
    <tr data-ng-repeat="machine in machines">
        <td>{{machine.customer}}</td>
        <td>{{machine.factory}}</td>
        <td><a data-ng-href="#/view/{{machine._id}}" rel="nofollow noreferrer noopener">{{machine.lineId}}</a></td>
        <td>{{machine.plcVersion}}</td>
    </tr>
</table>

machineForm.html

<h3>{{title}}</h3>
<form name="machineForm" data-ng-submit="save()">
    <div>
    <label for="customer">Customer:</label>
<div><input data-ng-model="machine.customer" id="customer" required></div>
    </div>

    <div>
    <label for="factory">Factory:</label>
    <div><input data-ng-model="machine.factory" id="factory" required></div>
    </div>

    <div>
<label for="lineId">Line ID:</label>
    <div><input data-ng-model="machine.lineId" id="lineId" required></div>
    </div>

    <div>
<label for="plcVersion">PLC Version:</label>
    <div><input data-ng-model="machine.plcVersion" id="plcVersion"></div>
    </div>

    <div><button data-ng-disabled="machineForm.$invalid">Save</button></div>
</form>

Here is Solutions:

We have many solutions to this problem, But we recommend you to use the first solution because it is tested & true solution that will 100% work for you.

Solution 1

Try changing your cache.manifest file to this:

CACHE MANIFEST

CACHE:
# views
views/machineForm.html
views/overview.html

# scripts
scripts/vendor/pouchdb-2.2.0.min.js
scripts/vendor/angular-1.2.16.min.js
scripts/vendor/angular-route-1.2.16.min.js

scripts/app.js
scripts/controllers/controller.js
scripts/services/service.js

NETWORK:
*

When using a manifest file, all non-cached resources will fail on a cached page, even when you’re online. The NETWORK section tells the browser to allow requests to non-cached resources (they’ll still fail while offline, of course).

Note: Use and implement solution 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply