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>