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

1

5

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="#/">Overview</a>
        <a data-ng-href="#" data-ng-controller="SyncCtrl" data-ng-click="syncDb()">Synchronise</a>
        <a data-ng-href="" data-ng-controller="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}}">{{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>
Frutescent answered 12/5, 2014 at 7:50 Comment(2)
If you have a jsfiddle or something that can reproduce this, that would be really helpful. Feel free to file an issue on the PouchDB GitHub page.Stamata
Never used jsfiddle, but I don't think it will work as I have no publically available database servers. I've posted the most relevant code here. Sorry if this is post is now crazy long.Frutescent
P
10

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).

Possessed answered 15/5, 2014 at 10:10 Comment(1)
That was the one. Confirmed working now. Many thanksFrutescent

© 2022 - 2024 — McMap. All rights reserved.